Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(simulation): Implement store decoder implementation from collections schema #16074

Merged
merged 20 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (modulemanager) [#15829](https://github.com/cosmos/cosmos-sdk/pull/15829) add new endblocker interface to handle valset updates
* (core) [#14860](https://github.com/cosmos/cosmos-sdk/pull/14860) Add `Precommit` and `PrepareCheckState` AppModule callbacks.
* (tx) [#15992](https://github.com/cosmos/cosmos-sdk/pull/15992) Add `WithExtensionOptions` in tx Factory to allow `SetExtensionOptions` with given extension options.

* (types/simulation) [#16074](https://github.com/cosmos/cosmos-sdk/pull/16074) Add generic SimulationStoreDecoder for modules using collections.
### Improvements

* (client) [#16075](https://github.com/cosmos/cosmos-sdk/pull/16075) Partly revert [#15953](https://github.com/cosmos/cosmos-sdk/issues/15953) and `factory.Prepare` does nothing in offline mode.
Expand Down
4 changes: 4 additions & 0 deletions collections/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

### Features

* [#16074](https://github.com/cosmos/cosmos-sdk/pull/16074) – makes the generic Collection interface public, still highly unstable.

## [v0.1.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.1.0)

Collections `v0.1.0` is released! Check out the [docs](https://docs.cosmos.network/main/packages/collections) to know how to use the APIs.
50 changes: 50 additions & 0 deletions collections/codec/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,56 @@ type ValueCodec[T any] interface {
ValueType() string
}

// NewUntypedValueCodec returns an UntypedValueCodec for the provided ValueCodec.
func NewUntypedValueCodec[V any](v ValueCodec[V]) UntypedValueCodec {
typeName := fmt.Sprintf("%T", *new(V))
checkType := func(value interface{}) (v V, err error) {
concrete, ok := value.(V)
if !ok {
return v, fmt.Errorf("%w: expected value of type %s, got %T", ErrEncoding, typeName, value)
}
return concrete, nil
}
return UntypedValueCodec{
Decode: func(b []byte) (interface{}, error) { return v.Decode(b) },
Encode: func(value interface{}) ([]byte, error) {
concrete, err := checkType(value)
if err != nil {
return nil, err
}
return v.Encode(concrete)
},
DecodeJSON: func(b []byte) (interface{}, error) {
return v.DecodeJSON(b)
},
EncodeJSON: func(value interface{}) ([]byte, error) {
concrete, err := checkType(value)
if err != nil {
return nil, err
}
return v.EncodeJSON(concrete)
},
Stringify: func(value interface{}) (string, error) {
concrete, err := checkType(value)
if err != nil {
return "", err
}
return v.Stringify(concrete), nil
},
ValueType: func() string { return v.ValueType() },
}
}

// UntypedValueCodec wraps a ValueCodec to expose an untyped API for encoding and decoding values.
type UntypedValueCodec struct {
Decode func(b []byte) (interface{}, error)
Encode func(value interface{}) ([]byte, error)
DecodeJSON func(b []byte) (interface{}, error)
EncodeJSON func(value interface{}) ([]byte, error)
Stringify func(value interface{}) (string, error)
ValueType func() string
}

// KeyToValueCodec converts a KeyCodec into a ValueCodec.
func KeyToValueCodec[K any](keyCodec KeyCodec[K]) ValueCodec[K] { return keyToValueCodec[K]{keyCodec} }

Expand Down
39 changes: 39 additions & 0 deletions collections/codec/codec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package codec

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestUntypedValueCodec(t *testing.T) {
vc := NewUntypedValueCodec(KeyToValueCodec(NewStringKeyCodec[string]()))

t.Run("encode/decode", func(t *testing.T) {
_, err := vc.Encode(0)
require.ErrorIs(t, err, ErrEncoding)
b, err := vc.Encode("hello")
require.NoError(t, err)
value, err := vc.Decode(b)
require.NoError(t, err)
require.Equal(t, "hello", value)
})

t.Run("json encode/decode", func(t *testing.T) {
_, err := vc.EncodeJSON(0)
require.ErrorIs(t, err, ErrEncoding)
b, err := vc.EncodeJSON("hello")
require.NoError(t, err)
value, err := vc.DecodeJSON(b)
require.NoError(t, err)
require.Equal(t, "hello", value)
})

t.Run("stringify", func(t *testing.T) {
_, err := vc.Stringify(0)
require.ErrorIs(t, err, ErrEncoding)
s, err := vc.Stringify("hello")
require.NoError(t, err)
require.Equal(t, "hello", s)
})
}
47 changes: 41 additions & 6 deletions collections/collections.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package collections

import (
"context"
"errors"
io "io"
"math"

"cosmossdk.io/collections/codec"
Expand Down Expand Up @@ -72,16 +74,20 @@ var (
BytesValue = codec.KeyToValueCodec(BytesKey)
)

// collection is the interface that all collections support. It will eventually
// Collection is the interface that all collections implement. It will eventually
// include methods for importing/exporting genesis data and schema
// reflection for clients.
type collection interface {
// getName is the unique name of the collection within a schema. It must
// NOTE: Unstable.
type Collection interface {
// GetName is the unique name of the collection within a schema. It must
// match format specified by NameRegex.
getName() string
GetName() string

// getPrefix is the unique prefix of the collection within a schema.
getPrefix() []byte
// GetPrefix is the unique prefix of the collection within a schema.
GetPrefix() []byte

// ValueCodec returns the codec used to encode/decode values of the collection.
ValueCodec() codec.UntypedValueCodec

genesisHandler
}
Expand Down Expand Up @@ -122,3 +128,32 @@ func NewPrefix[T interface{ int | string | []byte }](identifier T) Prefix {
}
return prefix
}

var _ Collection = (*collectionImpl[string, string])(nil)

// collectionImpl wraps a Map and implements Collection. This properly splits
// the generic and untyped Collection interface from the typed Map, which every
// collection builds on.
type collectionImpl[K, V any] struct {
m Map[K, V]
}

func (c collectionImpl[K, V]) ValueCodec() codec.UntypedValueCodec {
return codec.NewUntypedValueCodec(c.m.vc)
}

func (c collectionImpl[K, V]) GetName() string { return c.m.name }

func (c collectionImpl[K, V]) GetPrefix() []byte { return NewPrefix(c.m.prefix) }

func (c collectionImpl[K, V]) validateGenesis(r io.Reader) error { return c.m.validateGenesis(r) }

func (c collectionImpl[K, V]) importGenesis(ctx context.Context, r io.Reader) error {
return c.m.importGenesis(ctx, r)
}

func (c collectionImpl[K, V]) exportGenesis(ctx context.Context, w io.Writer) error {
return c.m.exportGenesis(ctx, w)
}

func (c collectionImpl[K, V]) defaultGenesis(w io.Writer) error { return c.m.defaultGenesis(w) }
2 changes: 1 addition & 1 deletion collections/iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ type KeyValue[K, V any] struct {

// encodeRangeBound encodes a range bound, modifying the key bytes to adhere to bound semantics.
func encodeRangeBound[T any](prefix []byte, keyCodec codec.KeyCodec[T], bound *RangeKey[T]) ([]byte, error) {
key, err := encodeKeyWithPrefix(prefix, keyCodec, bound.key)
key, err := EncodeKeyWithPrefix(prefix, keyCodec, bound.key)
if err != nil {
return nil, err
}
Expand Down
18 changes: 10 additions & 8 deletions collections/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,22 @@ func NewMap[K, V any](
prefix: prefix.Bytes(),
name: name,
}
schemaBuilder.addCollection(m)
schemaBuilder.addCollection(collectionImpl[K, V]{m})
return m
}

func (m Map[K, V]) getName() string {
func (m Map[K, V]) GetName() string {
return m.name
}

func (m Map[K, V]) getPrefix() []byte {
func (m Map[K, V]) GetPrefix() []byte {
return m.prefix
}

// Set maps the provided value to the provided key in the store.
// Errors with ErrEncoding if key or value encoding fails.
func (m Map[K, V]) Set(ctx context.Context, key K, value V) error {
bytesKey, err := encodeKeyWithPrefix(m.prefix, m.kc, key)
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return err
}
Expand All @@ -73,7 +73,7 @@ func (m Map[K, V]) Set(ctx context.Context, key K, value V) error {
// errors with ErrNotFound if the key does not exist, or
// with ErrEncoding if the key or value decoding fails.
func (m Map[K, V]) Get(ctx context.Context, key K) (v V, err error) {
bytesKey, err := encodeKeyWithPrefix(m.prefix, m.kc, key)
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return v, err
}
Expand All @@ -97,7 +97,7 @@ func (m Map[K, V]) Get(ctx context.Context, key K) (v V, err error) {
// Has reports whether the key is present in storage or not.
// Errors with ErrEncoding if key encoding fails.
func (m Map[K, V]) Has(ctx context.Context, key K) (bool, error) {
bytesKey, err := encodeKeyWithPrefix(m.prefix, m.kc, key)
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return false, err
}
Expand All @@ -109,7 +109,7 @@ func (m Map[K, V]) Has(ctx context.Context, key K) (bool, error) {
// Errors with ErrEncoding if key encoding fails.
// If the key does not exist then this is a no-op.
func (m Map[K, V]) Remove(ctx context.Context, key K) error {
bytesKey, err := encodeKeyWithPrefix(m.prefix, m.kc, key)
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return err
}
Expand Down Expand Up @@ -195,7 +195,9 @@ func (m Map[K, V]) KeyCodec() codec.KeyCodec[K] { return m.kc }
// ValueCodec returns the Map's ValueCodec.
func (m Map[K, V]) ValueCodec() codec.ValueCodec[V] { return m.vc }

func encodeKeyWithPrefix[K any](prefix []byte, kc codec.KeyCodec[K], key K) ([]byte, error) {
// EncodeKeyWithPrefix returns how the collection would store the key in storage given
// prefix, key codec and the concrete key.
func EncodeKeyWithPrefix[K any](prefix []byte, kc codec.KeyCodec[K], key K) ([]byte, error) {
prefixLen := len(prefix)
// preallocate buffer
keyBytes := make([]byte, prefixLen+kc.Size(key))
Expand Down
4 changes: 2 additions & 2 deletions collections/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestMap_IterateRaw(t *testing.T) {
require.NoError(t, m.Set(ctx, 2, 2))

// test non nil end in ascending order
twoBigEndian, err := encodeKeyWithPrefix(nil, Uint64Key, 2)
twoBigEndian, err := EncodeKeyWithPrefix(nil, Uint64Key, 2)
require.NoError(t, err)
iter, err := m.IterateRaw(ctx, nil, twoBigEndian, OrderAscending)
require.NoError(t, err)
Expand All @@ -76,7 +76,7 @@ func Test_encodeKey(t *testing.T) {
number := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
expectedKey := append([]byte(prefix), number...)

gotKey, err := encodeKeyWithPrefix(NewPrefix(prefix).Bytes(), Uint64Key, 0)
gotKey, err := EncodeKeyWithPrefix(NewPrefix(prefix).Bytes(), Uint64Key, 0)
require.NoError(t, err)
require.Equal(t, expectedKey, gotKey)
}
28 changes: 18 additions & 10 deletions collections/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ func NewSchemaBuilderFromAccessor(accessorFunc func(ctx context.Context) store.K
return &SchemaBuilder{
schema: &Schema{
storeAccessor: accessorFunc,
collectionsByName: map[string]collection{},
collectionsByPrefix: map[string]collection{},
collectionsByName: map[string]Collection{},
collectionsByPrefix: map[string]Collection{},
},
}
}
Expand Down Expand Up @@ -84,9 +84,9 @@ func (s *SchemaBuilder) Build() (Schema, error) {
return schema, nil
}

func (s *SchemaBuilder) addCollection(collection collection) {
prefix := collection.getPrefix()
name := collection.getName()
func (s *SchemaBuilder) addCollection(collection Collection) {
prefix := collection.GetPrefix()
name := collection.GetName()

if _, ok := s.schema.collectionsByPrefix[string(prefix)]; ok {
s.appendError(fmt.Errorf("prefix %v already taken within schema", prefix))
Expand Down Expand Up @@ -128,8 +128,8 @@ var nameRegex = regexp.MustCompile("^" + NameRegex + "$")
type Schema struct {
storeAccessor func(context.Context) store.KVStore
collectionsOrdered []string
collectionsByPrefix map[string]collection
collectionsByName map[string]collection
collectionsByPrefix map[string]Collection
collectionsByName map[string]Collection
}

// NewSchema creates a new schema for the provided KVStoreService.
Expand Down Expand Up @@ -157,8 +157,8 @@ func NewMemoryStoreSchema(service store.MemoryStoreService) Schema {
func NewSchemaFromAccessor(accessor func(context.Context) store.KVStore) Schema {
return Schema{
storeAccessor: accessor,
collectionsByName: map[string]collection{},
collectionsByPrefix: map[string]collection{},
collectionsByName: map[string]Collection{},
collectionsByPrefix: map[string]Collection{},
}
}

Expand Down Expand Up @@ -279,10 +279,18 @@ func (s Schema) exportGenesis(ctx context.Context, target appmodule.GenesisTarge
return coll.exportGenesis(ctx, wc)
}

func (s Schema) getCollection(name string) (collection, error) {
func (s Schema) getCollection(name string) (Collection, error) {
coll, ok := s.collectionsByName[name]
if !ok {
return nil, fmt.Errorf("unknown collection: %s", name)
}
return coll, nil
}

func (s Schema) ListCollections() []Collection {
colls := make([]Collection, len(s.collectionsOrdered))
for i, name := range s.collectionsOrdered {
colls[i] = s.collectionsByName[name]
}
return colls
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ require (

// Below are the long-lived replace of the Cosmos SDK
replace (
// TODO: remove me after collections 0.2. is released.
cosmossdk.io/collections => ./collections
cosmossdk.io/core => ./core
cosmossdk.io/store => ./store
// TODO: remove after 0.7.0 release
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cosmossdk.io/api v0.4.1 h1:0ikaYM6GyxTYYcfBiyR8YnLCfhNnhKpEFnaSepCTmqg=
cosmossdk.io/api v0.4.1/go.mod h1:jR7k5ok90LxW2lFUXvd8Vpo/dr4PpiyVegxdm7b1ZdE=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
cosmossdk.io/errors v1.0.0-beta.7.0.20230429155654-3ee8242364e4 h1:rOy7iw7HlwKc5Af5qIHLXdBx/F98o6du/I/WGwOW6eA=
Expand Down
2 changes: 2 additions & 0 deletions simapp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ replace (

// Below are the long-lived replace of the SimApp
replace (
// TODO: remove me after collections 0.2. is released.
cosmossdk.io/collections => ../collections
cosmossdk.io/core => ../core
// TODO: remove after 0.7.0 release
cosmossdk.io/x/tx => ../x/tx
Expand Down
2 changes: 0 additions & 2 deletions simapp/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,6 @@ cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xX
cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
cosmossdk.io/errors v1.0.0-beta.7.0.20230429155654-3ee8242364e4 h1:rOy7iw7HlwKc5Af5qIHLXdBx/F98o6du/I/WGwOW6eA=
Expand Down
2 changes: 2 additions & 0 deletions tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ replace (

// Below are the long-lived replace for tests.
replace (
// TODO: remove me after collections v0.2.0 is released
cosmossdk.io/collections => ../collections
cosmossdk.io/core => ../core
// We always want to test against the latest version of the simapp.
cosmossdk.io/simapp => ../simapp
Expand Down
2 changes: 0 additions & 2 deletions tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,6 @@ cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1V
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba h1:LuPHCncU2KLMNPItFECs709uo46I9wSu2fAWYVCx+/U=
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba/go.mod h1:SXdwqO7cN5htalh/lhXWP8V4zKtBrhhcSTU+ytuEtmM=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
cosmossdk.io/errors v1.0.0-beta.7.0.20230429155654-3ee8242364e4 h1:rOy7iw7HlwKc5Af5qIHLXdBx/F98o6du/I/WGwOW6eA=
Expand Down
Loading