diff --git a/CHANGELOG.md b/CHANGELOG.md index 595ecff88fea..ca02a54a89be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/collections/CHANGELOG.md b/collections/CHANGELOG.md index c1180a049fdd..87e84a0f9ba9 100644 --- a/collections/CHANGELOG.md +++ b/collections/CHANGELOG.md @@ -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. \ No newline at end of file diff --git a/collections/codec/codec.go b/collections/codec/codec.go index 284ac290fa32..2988c9f52425 100644 --- a/collections/codec/codec.go +++ b/collections/codec/codec.go @@ -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} } diff --git a/collections/codec/codec_test.go b/collections/codec/codec_test.go new file mode 100644 index 000000000000..16fd10553f41 --- /dev/null +++ b/collections/codec/codec_test.go @@ -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) + }) +} diff --git a/collections/collections.go b/collections/collections.go index a92aa0be6ee6..0f6b32253340 100644 --- a/collections/collections.go +++ b/collections/collections.go @@ -1,7 +1,9 @@ package collections import ( + "context" "errors" + io "io" "math" "cosmossdk.io/collections/codec" @@ -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 } @@ -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) } diff --git a/collections/iter.go b/collections/iter.go index 976493608092..38a21a22a353 100644 --- a/collections/iter.go +++ b/collections/iter.go @@ -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 } diff --git a/collections/map.go b/collections/map.go index a6f5f9419630..7623be6b14e5 100644 --- a/collections/map.go +++ b/collections/map.go @@ -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 } @@ -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 } @@ -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 } @@ -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 } @@ -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)) diff --git a/collections/map_test.go b/collections/map_test.go index 362a50c54d34..2887693d53a2 100644 --- a/collections/map_test.go +++ b/collections/map_test.go @@ -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) @@ -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) } diff --git a/collections/schema.go b/collections/schema.go index a094da6dc992..806f350e6b7b 100644 --- a/collections/schema.go +++ b/collections/schema.go @@ -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{}, }, } } @@ -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)) @@ -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. @@ -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{}, } } @@ -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 +} diff --git a/go.mod b/go.mod index ddd280af8d8b..85a850357729 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 1f31afe503b2..4502b12d5398 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/simapp/go.mod b/simapp/go.mod index 7dd1022a67df..0dad965b7302 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -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 diff --git a/simapp/go.sum b/simapp/go.sum index ff672a8b9ea2..96be30c9e6fc 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -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= diff --git a/tests/go.mod b/tests/go.mod index 31e0542556dd..e148d5efe503 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -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 diff --git a/tests/go.sum b/tests/go.sum index d36bf34538e9..8cb7a8a86671 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -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= diff --git a/tools/confix/go.mod b/tools/confix/go.mod index 28f884dd45ce..c62425590694 100644 --- a/tools/confix/go.mod +++ b/tools/confix/go.mod @@ -155,6 +155,8 @@ require ( sigs.k8s.io/yaml v1.3.0 // indirect ) +replace cosmossdk.io/collections => ../../collections + // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 // TODO investigate if we can outright delete this dependency, otherwise go install won't work :( diff --git a/tools/confix/go.sum b/tools/confix/go.sum index f25f2caf5892..066425ecf41c 100644 --- a/tools/confix/go.sum +++ b/tools/confix/go.sum @@ -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/core v0.6.1 h1:OBy7TI2W+/gyn2z40vVvruK3di+cAluinA6cybFbE7s= cosmossdk.io/core v0.6.1/go.mod h1:g3MMBCBXtxbDWBURDVnJE7XML4BG5qENhs0gzkcpuFA= cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= diff --git a/tools/hubl/go.mod b/tools/hubl/go.mod index 5313e372338b..578270e91644 100644 --- a/tools/hubl/go.mod +++ b/tools/hubl/go.mod @@ -120,3 +120,5 @@ require ( pgregory.net/rapid v0.5.7 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) + +replace cosmossdk.io/collections => ../../collections // TODO: remove me after collections v0.2.0 is released diff --git a/tools/hubl/go.sum b/tools/hubl/go.sum index c482d8685f63..418ef97b547f 100644 --- a/tools/hubl/go.sum +++ b/tools/hubl/go.sum @@ -39,8 +39,6 @@ cosmossdk.io/api v0.4.1 h1:0ikaYM6GyxTYYcfBiyR8YnLCfhNnhKpEFnaSepCTmqg= cosmossdk.io/api v0.4.1/go.mod h1:jR7k5ok90LxW2lFUXvd8Vpo/dr4PpiyVegxdm7b1ZdE= cosmossdk.io/client/v2 v2.0.0-20230426154441-2037a26d1235 h1:6aGhtjUgmacucrKMC9ZdF9G96YoxZqkTC2ZyxaAg1GE= cosmossdk.io/client/v2 v2.0.0-20230426154441-2037a26d1235/go.mod h1:ydI6QS3A+K2px6O8QpM0JtNaVV6lLeCJ5LVwtQXIMAg= -cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8= -cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo= cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4 h1:l1scDTT2VX18ZuR6P0irvT/bAP0h4297D/Lka5nz2vE= cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4/go.mod h1:J8R0E7soOpQFVqFiFd7EKepXCPpINa2n2t2EqbEsXnY= cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= diff --git a/tools/rosetta/go.mod b/tools/rosetta/go.mod index bf22008e0bbe..172a66c7dec0 100644 --- a/tools/rosetta/go.mod +++ b/tools/rosetta/go.mod @@ -141,6 +141,7 @@ require ( // TODO: remove after merge of https://github.com/cosmos/cosmos-sdk/pull/15873 and tagging releases replace ( + cosmossdk.io/collections => ../../collections // TODO: remove me after collections v0.2.0 is released cosmossdk.io/core => ../../core cosmossdk.io/store => ../../store cosmossdk.io/x/tx => ../../x/tx diff --git a/tools/rosetta/go.sum b/tools/rosetta/go.sum index d6e93aa259f0..91a7ab3ddcfb 100644 --- a/tools/rosetta/go.sum +++ b/tools/rosetta/go.sum @@ -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= diff --git a/types/simulation/collections.go b/types/simulation/collections.go new file mode 100644 index 000000000000..950068fc4835 --- /dev/null +++ b/types/simulation/collections.go @@ -0,0 +1,52 @@ +package simulation + +import ( + "bytes" + "fmt" + + "cosmossdk.io/collections" + collcodec "cosmossdk.io/collections/codec" + + "github.com/cosmos/cosmos-sdk/types/kv" +) + +func NewStoreDecoderFuncFromCollectionsSchema(schema collections.Schema) func(kvA, kvB kv.Pair) string { + colls := schema.ListCollections() + prefixes := make([][]byte, len(colls)) + valueCodecs := make([]collcodec.UntypedValueCodec, len(colls)) + for i, coll := range colls { + prefixes[i] = coll.GetPrefix() + valueCodecs[i] = coll.ValueCodec() + } + + return func(kvA, kvB kv.Pair) string { + for i, prefix := range prefixes { + if bytes.HasPrefix(kvA.Key, prefix) { + if !bytes.HasPrefix(kvB.Key, prefix) { + panic(fmt.Sprintf("prefix mismatch, keyA has prefix %x (%s), but keyB does not %x (%s)", prefix, prefix, kvB.Key, kvB.Key)) + } + vc := valueCodecs[i] + // unmarshal kvA.Value to the corresponding type + vA, err := vc.Decode(kvA.Value) + if err != nil { + panic(err) + } + // unmarshal kvB.Value to the corresponding type + vB, err := vc.Decode(kvB.Value) + if err != nil { + panic(err) + } + vAString, err := vc.Stringify(vA) + if err != nil { + panic(err) + } + vBString, err := vc.Stringify(vB) + if err != nil { + panic(err) + } + return vAString + "\n" + vBString + } + } + panic(fmt.Errorf("unexpected key %X (%s)", kvA.Key, kvA.Key)) + } +} diff --git a/types/simulation/collections_test.go b/types/simulation/collections_test.go new file mode 100644 index 000000000000..8c2e2f6cf88d --- /dev/null +++ b/types/simulation/collections_test.go @@ -0,0 +1,71 @@ +package simulation + +import ( + "testing" + + "cosmossdk.io/collections" + "cosmossdk.io/collections/colltest" + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/stretchr/testify/require" +) + +func TestNewStoreDecoderFuncFromCollectionsSchema(t *testing.T) { + store, _ := colltest.MockStore() + sb := collections.NewSchemaBuilder(store) + + prefixM1 := collections.NewPrefix("map_1") + prefixM2 := collections.NewPrefix("map_2") + + m1 := collections.NewMap(sb, prefixM1, "map_1", collections.StringKey, collections.StringValue) + m2 := collections.NewMap(sb, prefixM2, "map_2", collections.Int32Key, collections.Int32Value) + + schema, err := sb.Build() + require.NoError(t, err) + + // create a new store decoder function from the schema + dec := NewStoreDecoderFuncFromCollectionsSchema(schema) + + key1M1, err := collections.EncodeKeyWithPrefix(prefixM1, m1.KeyCodec(), "key_1") + require.NoError(t, err) + key2M1, err := collections.EncodeKeyWithPrefix(prefixM1, m1.KeyCodec(), "key_2") + require.NoError(t, err) + key1M2, err := collections.EncodeKeyWithPrefix(prefixM2, m2.KeyCodec(), int32(1)) + require.NoError(t, err) + key2M2, err := collections.EncodeKeyWithPrefix(prefixM2, m2.KeyCodec(), int32(2)) + require.NoError(t, err) + + storeDec1 := dec(kv.Pair{ + Key: key1M1, + Value: []byte("value_1"), + }, kv.Pair{ + Key: key2M1, + Value: []byte("value_2"), + }) + require.Equal(t, "value_1\nvalue_2", storeDec1) + + storeDec2 := dec(kv.Pair{ + Key: key1M2, + Value: []byte{0, 0, 0, 1}, + }, kv.Pair{ + Key: key2M2, + Value: []byte{0, 0, 0, 2}, + }) + + require.Equal(t, "-2147483647\n-2147483646", storeDec2) + + // test key conflict + + require.Panics(t, func() { + dec( + kv.Pair{Key: append(prefixM1.Bytes(), 0x1)}, + kv.Pair{Key: append(prefixM2.Bytes(), 0x1)}, + ) + }, "must panic when keys do not have the same prefix") + + require.Panics(t, func() { + dec( + kv.Pair{Key: []byte("unknown_1")}, + kv.Pair{Key: []byte("unknown_2")}, + ) + }, "must panic on unknown prefixes") +} diff --git a/types/simulation/rand_util.go b/types/simulation/rand_util.go index 9432a48d96e9..0f8fa11e432b 100644 --- a/types/simulation/rand_util.go +++ b/types/simulation/rand_util.go @@ -172,6 +172,6 @@ func (ms multiSource) Int63() (r int64) { return r } -func (ms multiSource) Seed(seed int64) { +func (ms multiSource) Seed(_ int64) { panic("multiSource Seed should not be called") } diff --git a/x/bank/module.go b/x/bank/module.go index c94d5c125d8f..893267bcae9e 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -190,7 +190,9 @@ func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.Weight } // RegisterStoreDecoder registers a decoder for supply module's types -func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.(keeper.BaseKeeper).Schema) +} // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { diff --git a/x/evidence/go.mod b/x/evidence/go.mod index 2adac6b35510..02fd5376f1d5 100644 --- a/x/evidence/go.mod +++ b/x/evidence/go.mod @@ -160,6 +160,8 @@ require ( replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.8.1 replace ( + // TODO: remove me when collections v0.2.0 is released + cosmossdk.io/collections => ../../collections cosmossdk.io/core => ../../core cosmossdk.io/store => ../../store cosmossdk.io/x/tx => ../tx diff --git a/x/evidence/go.sum b/x/evidence/go.sum index 69d5e53a625e..337412297330 100644 --- a/x/evidence/go.sum +++ b/x/evidence/go.sum @@ -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=