diff --git a/baseapp/abci.go b/baseapp/abci.go index 979089c5c86d..d9921a8e13d5 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -903,8 +903,12 @@ func (app *BaseApp) FinalizeBlock(req *abci.FinalizeBlockRequest) (res *abci.Fin defer func() { // call the streaming service hooks with the FinalizeBlock messages for _, streamingListener := range app.streamingManager.ABCIListeners { - if err := streamingListener.ListenFinalizeBlock(app.finalizeBlockState.Context(), *req, *res); err != nil { + if streamErr := streamingListener.ListenFinalizeBlock(app.finalizeBlockState.Context(), *req, *res); streamErr != nil { app.logger.Error("ListenFinalizeBlock listening hook failed", "height", req.Height, "err", err) + if app.streamingManager.StopNodeOnErr { + // if StopNodeOnErr is set, we should return the streamErr in order to stop the node + err = streamErr + } } } }() @@ -976,12 +980,12 @@ func (app *BaseApp) Commit() (*abci.CommitResponse, error) { rms.SetCommitHeader(header) } - app.cms.Commit() - resp := &abci.CommitResponse{ RetainHeight: retainHeight, } + app.cms.Commit() + abciListeners := app.streamingManager.ABCIListeners if len(abciListeners) > 0 { ctx := app.finalizeBlockState.Context() @@ -991,6 +995,14 @@ func (app *BaseApp) Commit() (*abci.CommitResponse, error) { for _, abciListener := range abciListeners { if err := abciListener.ListenCommit(ctx, *resp, changeSet); err != nil { app.logger.Error("Commit listening hook failed", "height", blockHeight, "err", err) + if app.streamingManager.StopNodeOnErr { + err = fmt.Errorf("Commit listening hook failed: %w", err) + rollbackErr := app.cms.RollbackToVersion(blockHeight - 1) + if rollbackErr != nil { + return nil, errors.Join(err, rollbackErr) + } + return nil, err + } } } } diff --git a/client/v2/go.mod b/client/v2/go.mod index 96dda7892079..3258969cd172 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/client/v2 -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 diff --git a/codec/collections.go b/codec/collections.go index 0137a989e790..3694558ad5c3 100644 --- a/codec/collections.go +++ b/codec/collections.go @@ -1,16 +1,23 @@ package codec import ( + "encoding/json" "fmt" "reflect" + "strings" "github.com/cosmos/gogoproto/proto" gogotypes "github.com/cosmos/gogoproto/types" "google.golang.org/protobuf/encoding/protojson" protov2 "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/dynamicpb" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" "cosmossdk.io/collections" collcodec "cosmossdk.io/collections/codec" + "cosmossdk.io/schema" ) // BoolValue implements a ValueCodec that saves the bool value @@ -51,12 +58,17 @@ type protoMessage[T any] interface { proto.Message } +type protoCollValueCodec[T any] interface { + collcodec.HasSchemaCodec[T] + collcodec.ValueCodec[T] +} + // CollValue inits a collections.ValueCodec for a generic gogo protobuf message. func CollValue[T any, PT protoMessage[T]](cdc interface { Marshal(proto.Message) ([]byte, error) Unmarshal([]byte, proto.Message) error }, -) collcodec.ValueCodec[T] { +) protoCollValueCodec[T] { return &collValue[T, PT]{cdc.(Codec), proto.MessageName(PT(new(T)))} } @@ -91,6 +103,139 @@ func (c collValue[T, PT]) ValueType() string { return "github.com/cosmos/gogoproto/" + c.messageName } +func (c collValue[T, PT]) SchemaCodec() (collcodec.SchemaCodec[T], error) { + var ( + t T + pt PT + ) + msgName := proto.MessageName(pt) + desc, err := proto.HybridResolver.FindDescriptorByName(protoreflect.FullName(msgName)) + if err != nil { + return collcodec.SchemaCodec[T]{}, fmt.Errorf("could not find descriptor for %s: %w", msgName, err) + } + schemaFields := protoCols(desc.(protoreflect.MessageDescriptor)) + + kind := schema.KindForGoValue(t) + if err := kind.Validate(); err == nil { + return collcodec.SchemaCodec[T]{ + Fields: []schema.Field{{ + // we don't set any name so that this can be set to a good default by the caller + Name: "", + Kind: kind, + }}, + // these can be nil because T maps directly to a schema value for this kind + ToSchemaType: nil, + FromSchemaType: nil, + }, nil + } else { + return collcodec.SchemaCodec[T]{ + Fields: schemaFields, + ToSchemaType: func(t T) (any, error) { + values := []interface{}{} + msgDesc, ok := desc.(protoreflect.MessageDescriptor) + if !ok { + return nil, fmt.Errorf("expected message descriptor, got %T", desc) + } + + nm := dynamicpb.NewMessage(msgDesc) + bz, err := c.cdc.Marshal(any(&t).(PT)) + if err != nil { + return nil, err + } + + err = c.cdc.Unmarshal(bz, nm) + if err != nil { + return nil, err + } + + for _, field := range schemaFields { + // Find the field descriptor by the Protobuf field name + fieldDesc := msgDesc.Fields().ByName(protoreflect.Name(field.Name)) + if fieldDesc == nil { + return nil, fmt.Errorf("field %q not found in message %s", field.Name, desc.FullName()) + } + + val := nm.ProtoReflect().Get(fieldDesc) + + // if the field is a map or list, we need to convert it to a slice of values + if fieldDesc.IsList() { + repeatedVals := []interface{}{} + list := val.List() + for i := 0; i < list.Len(); i++ { + repeatedVals = append(repeatedVals, list.Get(i).Interface()) + } + values = append(values, repeatedVals) + continue + } + + switch fieldDesc.Kind() { + case protoreflect.BoolKind: + values = append(values, val.Bool()) + case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, + protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: + values = append(values, val.Int()) + case protoreflect.Uint32Kind, protoreflect.Fixed32Kind, protoreflect.Uint64Kind, + protoreflect.Fixed64Kind: + values = append(values, val.Uint()) + case protoreflect.FloatKind, protoreflect.DoubleKind: + values = append(values, val.Float()) + case protoreflect.StringKind: + values = append(values, val.String()) + case protoreflect.BytesKind: + values = append(values, val.Bytes()) + case protoreflect.EnumKind: + // TODO: postgres uses the enum name, not the number + values = append(values, string(fieldDesc.Enum().Values().ByNumber(val.Enum()).Name())) + case protoreflect.MessageKind: + msg := val.Interface().(*dynamicpb.Message) + msgbz, err := c.cdc.Marshal(msg) + if err != nil { + return nil, err + } + + if field.Kind == schema.TimeKind { + // make it a time.Time + ts := ×tamppb.Timestamp{} + err = c.cdc.Unmarshal(msgbz, ts) + if err != nil { + return nil, fmt.Errorf("error unmarshalling timestamp: %w %x %s", err, msgbz, fieldDesc.FullName()) + } + values = append(values, ts.AsTime()) + } else if field.Kind == schema.DurationKind { + // make it a time.Duration + dur := &durationpb.Duration{} + err = c.cdc.Unmarshal(msgbz, dur) + if err != nil { + return nil, fmt.Errorf("error unmarshalling duration: %w", err) + } + values = append(values, dur.AsDuration()) + } else { + // if not a time or duration, just keep it as a JSON object + // we might want to change this to include the entire object as separate fields + bz, err := c.cdc.MarshalJSON(msg) + if err != nil { + return nil, fmt.Errorf("error marshaling message: %w", err) + } + + values = append(values, json.RawMessage(bz)) + } + } + + } + + // if there's only one value, return it directly + if len(values) == 1 { + return values[0], nil + } + return values, nil + }, + FromSchemaType: func(a any) (T, error) { + panic("not implemented") + }, + }, nil + } +} + type protoMessageV2[T any] interface { *T protov2.Message @@ -179,3 +324,101 @@ func (c collInterfaceValue[T]) ValueType() string { var t T return fmt.Sprintf("%T", t) } + +// SchemaCodec returns a schema codec, which will always have a single JSON field +// as there is no way to know in advance the necessary fields for an interface. +func (c collInterfaceValue[T]) SchemaCodec() (collcodec.SchemaCodec[T], error) { + var pt T + + kind := schema.KindForGoValue(pt) + if err := kind.Validate(); err == nil { + return collcodec.SchemaCodec[T]{ + Fields: []schema.Field{{ + // we don't set any name so that this can be set to a good default by the caller + Name: "", + Kind: kind, + }}, + // these can be nil because T maps directly to a schema value for this kind + ToSchemaType: nil, + FromSchemaType: nil, + }, nil + } else { + return collcodec.SchemaCodec[T]{ + Fields: []schema.Field{{ + Name: "value", + Kind: schema.JSONKind, + }}, + ToSchemaType: func(t T) (any, error) { + bz, err := c.codec.MarshalInterfaceJSON(t) + if err != nil { + return nil, err + } + + return json.RawMessage(bz), nil + }, + FromSchemaType: func(a any) (T, error) { + panic("not implemented") + }, + }, nil + } +} + +func protoCols(desc protoreflect.MessageDescriptor) []schema.Field { + nFields := desc.Fields() + cols := make([]schema.Field, 0, nFields.Len()) + for i := 0; i < nFields.Len(); i++ { + f := nFields.Get(i) + cols = append(cols, protoCol(f)) + } + return cols +} + +func protoCol(f protoreflect.FieldDescriptor) schema.Field { + col := schema.Field{Name: string(f.Name())} + if f.IsMap() || f.IsList() { + col.Kind = schema.JSONKind + col.Nullable = true + } else { + switch f.Kind() { + case protoreflect.BoolKind: + col.Kind = schema.BoolKind + case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: + col.Kind = schema.Int32Kind + case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: + col.Kind = schema.Int64Kind + case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: + col.Kind = schema.Int64Kind + case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: + col.Kind = schema.Uint64Kind + case protoreflect.FloatKind: + col.Kind = schema.Float32Kind + case protoreflect.DoubleKind: + col.Kind = schema.Float64Kind + case protoreflect.StringKind: + col.Kind = schema.StringKind + case protoreflect.BytesKind: + col.Kind = schema.BytesKind + case protoreflect.EnumKind: + // TODO: support enums + col.Kind = schema.EnumKind + // use the full name to avoid collissions + col.ReferencedType = string(f.Enum().FullName()) + col.ReferencedType = strings.ReplaceAll(col.ReferencedType, ".", "_") + case protoreflect.MessageKind: + col.Nullable = true + fullName := f.Message().FullName() + if fullName == "google.protobuf.Timestamp" { + col.Kind = schema.TimeKind + } else if fullName == "google.protobuf.Duration" { + col.Kind = schema.DurationKind + } else { + col.Kind = schema.JSONKind + } + } + if f.HasPresence() { + col.Nullable = true + } + } + + return col +} diff --git a/collections/CHANGELOG.md b/collections/CHANGELOG.md new file mode 100644 index 000000000000..142263bc5050 --- /dev/null +++ b/collections/CHANGELOG.md @@ -0,0 +1,81 @@ + + +# Changelog + +## [Unreleased] + +### Features + +* [#22641](https://github.com/cosmos/cosmos-sdk/pull/22641) Add reverse iterator support for `Triple`. +* [#17656](https://github.com/cosmos/cosmos-sdk/pull/17656) Introduces `Vec`, a collection type that allows to represent a growable array on top of a KVStore. +* [#18933](https://github.com/cosmos/cosmos-sdk/pull/18933) Add LookupMap implementation. It is basic wrapping of the standard Map methods but is not iterable. +* [#19343](https://github.com/cosmos/cosmos-sdk/pull/19343) Simplify IndexedMap creation by allowing to infer indexes through reflection. +* [#19861](https://github.com/cosmos/cosmos-sdk/pull/19861) Add `NewJSONValueCodec` value codec as an alternative for `codec.CollValue` from the SDK for non protobuf types. +* [#21090](https://github.com/cosmos/cosmos-sdk/pull/21090) Introduces `Quad`, a composite key with four keys. +* [#20704](https://github.com/cosmos/cosmos-sdk/pull/20704) Add `ModuleCodec` method to `Schema` and `HasSchemaCodec` interface in order to support `cosmossdk.io/schema` compatible indexing. +* [#20538](https://github.com/cosmos/cosmos-sdk/pull/20538) Add `Nameable` variations to `KeyCodec` and `ValueCodec` to allow for better indexing of `collections` types. +* [#22544](https://github.com/cosmos/cosmos-sdk/pull/22544) Schema's `ModuleCodec` will now also return Enum descriptors to be registered with the indexer. + +## [v0.4.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.4.0) + +### Features + +* [#17024](https://github.com/cosmos/cosmos-sdk/pull/17024) Introduces `Triple`, a composite key with three keys. + +### API Breaking + +* [#17290](https://github.com/cosmos/cosmos-sdk/pull/17290) Collections iteration methods (Iterate, Walk) will not error when the collection is empty. + +### Improvements + +* [#17021](https://github.com/cosmos/cosmos-sdk/pull/17021) Make collections implement the `appmodule.HasGenesis` interface. + +## [v0.3.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.3.0) + +### Features + +* [#16074](https://github.com/cosmos/cosmos-sdk/pull/16607) Introduces `Clear` method for `Map` and `KeySet` +* [#16773](https://github.com/cosmos/cosmos-sdk/pull/16773) + * Adds `AltValueCodec` which provides a way to decode a value in two ways. + * Adds the possibility to specify an alternative way to decode the values of `KeySet`, `indexes.Multi`, `indexes.ReversePair`. + +## [v0.2.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.2.0) + +### Features + +* [#16074](https://github.com/cosmos/cosmos-sdk/pull/16074) Makes the generic Collection interface public, still highly unstable. + +### API Breaking + +* [#16127](https://github.com/cosmos/cosmos-sdk/pull/16127) In the `Walk` method the call back function being passed is allowed to error. + +## [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/build/packages/collections) to know how to use the APIs. diff --git a/collections/go.mod b/collections/go.mod new file mode 100644 index 000000000000..9ea5cd3f6acf --- /dev/null +++ b/collections/go.mod @@ -0,0 +1,22 @@ +module cosmossdk.io/collections + +go 1.23.2 + +require ( + cosmossdk.io/core v1.0.0-alpha.6 + cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 + cosmossdk.io/schema v0.3.0 + github.com/cosmos/gogoproto v1.7.0 + github.com/stretchr/testify v1.10.0 + github.com/tidwall/btree v1.7.0 + google.golang.org/protobuf v1.35.2 + pgregory.net/rapid v1.1.0 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/collections/go.sum b/collections/go.sum new file mode 100644 index 000000000000..7cd741b3ec8e --- /dev/null +++ b/collections/go.sum @@ -0,0 +1,28 @@ +cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= +cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= +cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= +cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29/go.mod h1:8s2tPeJtSiQuoyPmr2Ag7meikonISO4Fv4MoO8+ORrs= +cosmossdk.io/schema v0.3.0 h1:01lcaM4trhzZ1HQTfTV8z6Ma1GziOZ/YmdzBN3F720c= +cosmossdk.io/schema v0.3.0/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ= +github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= +github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= +github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/collections/indexing.go b/collections/indexing.go new file mode 100644 index 000000000000..8a482c39810f --- /dev/null +++ b/collections/indexing.go @@ -0,0 +1,258 @@ +package collections + +import ( + "bytes" + "fmt" + "reflect" + "strings" + + "github.com/cosmos/gogoproto/proto" + "github.com/tidwall/btree" + "google.golang.org/protobuf/reflect/protoreflect" + + "cosmossdk.io/collections/codec" + "cosmossdk.io/schema" +) + +// IndexingOptions are indexing options for the collections schema. +type IndexingOptions struct { + // RetainDeletionsFor is the list of collections to retain deletions for. + RetainDeletionsFor []string +} + +// ModuleCodec returns the ModuleCodec for this schema for the provided options. +func (s Schema) ModuleCodec(opts IndexingOptions) (schema.ModuleCodec, error) { + decoder := moduleDecoder{ + collectionLookup: &btree.Map[string, *collectionSchemaCodec]{}, + } + + retainDeletions := make(map[string]bool) + for _, collName := range opts.RetainDeletionsFor { + retainDeletions[collName] = true + } + + var types []schema.Type + for _, collName := range s.collectionsOrdered { + coll := s.collectionsByName[collName] + + // skip secondary indexes + if coll.isSecondaryIndex() { + continue + } + + cdc, err := coll.schemaCodec() + if err != nil { + return schema.ModuleCodec{}, err + } + + if retainDeletions[coll.GetName()] { + cdc.objectType.RetainDeletions = true + } + + // this part below is a bit hacky, it will try to convert to a proto.Message + // in order to get any enum types inside of it. + emptyVal, err := coll.ValueCodec().Decode([]byte{}) + if err == nil { + // convert to proto.Message + pt, err := toProtoMessage(emptyVal) + if err == nil { + msgName := proto.MessageName(pt) + desc, err := proto.HybridResolver.FindDescriptorByName(protoreflect.FullName(msgName)) + if err != nil { + return schema.ModuleCodec{}, fmt.Errorf("could not find descriptor for %s: %w", msgName, err) + } + msgDesc := desc.(protoreflect.MessageDescriptor) + + // go through enum descriptors and add them to types + for i := 0; i < msgDesc.Fields().Len(); i++ { + field := msgDesc.Fields().Get(i) + enum := field.Enum() + if enum == nil { + continue + } + + enumType := schema.EnumType{ + Name: strings.ReplaceAll(string(enum.FullName()), ".", "_"), // make it compatible with schema + } + for j := 0; j < enum.Values().Len(); j++ { + val := enum.Values().Get(j) + enumType.Values = append(enumType.Values, schema.EnumValueDefinition{ + Name: string(val.Name()), + Value: int32(val.Number()), + }) + } + types = append(types, enumType) + } + + } + } + + types = append(types, cdc.objectType) + decoder.collectionLookup.Set(string(coll.GetPrefix()), cdc) + } + + modSchema, err := schema.CompileModuleSchema(types...) + if err != nil { + return schema.ModuleCodec{}, err + } + + return schema.ModuleCodec{ + Schema: modSchema, + KVDecoder: decoder.decodeKV, + }, nil +} + +type moduleDecoder struct { + // collectionLookup lets us efficiently look the correct collection based on raw key bytes + collectionLookup *btree.Map[string, *collectionSchemaCodec] +} + +func (m moduleDecoder) decodeKV(update schema.KVPairUpdate) ([]schema.StateObjectUpdate, error) { + key := update.Key + ks := string(key) + var cd *collectionSchemaCodec + // we look for the collection whose prefix is less than this key + m.collectionLookup.Descend(ks, func(prefix string, cur *collectionSchemaCodec) bool { + bytesPrefix := cur.coll.GetPrefix() + if bytes.HasPrefix(key, bytesPrefix) { + cd = cur + return true + } + return false + }) + if cd == nil { + return nil, nil + } + + return cd.decodeKVPair(update) +} + +func (c collectionSchemaCodec) decodeKVPair(update schema.KVPairUpdate) ([]schema.StateObjectUpdate, error) { + // strip prefix + key := update.Key + key = key[len(c.coll.GetPrefix()):] + + k, err := c.keyDecoder(key) + if err != nil { + return []schema.StateObjectUpdate{ + {TypeName: c.coll.GetName()}, + }, err + } + + if update.Remove { + return []schema.StateObjectUpdate{ + {TypeName: c.coll.GetName(), Key: k, Delete: true}, + }, nil + } + + v, err := c.valueDecoder(update.Value) + if err != nil { + return []schema.StateObjectUpdate{ + {TypeName: c.coll.GetName(), Key: k}, + }, err + } + + return []schema.StateObjectUpdate{ + {TypeName: c.coll.GetName(), Key: k, Value: v}, + }, nil +} + +func (c collectionImpl[K, V]) schemaCodec() (*collectionSchemaCodec, error) { + res := &collectionSchemaCodec{ + coll: c, + } + res.objectType.Name = c.GetName() + + keyDecoder, err := codec.KeySchemaCodec(c.m.kc) + if err != nil { + return nil, err + } + res.objectType.KeyFields = keyDecoder.Fields + res.keyDecoder = func(i []byte) (any, error) { + _, x, err := c.m.kc.Decode(i) + if err != nil { + return nil, err + } + if keyDecoder.ToSchemaType == nil { + return x, nil + } + return keyDecoder.ToSchemaType(x) + } + ensureFieldNames(c.m.kc, "key", res.objectType.KeyFields) + + valueDecoder, err := codec.ValueSchemaCodec(c.m.vc) + if err != nil { + return nil, err + } + res.objectType.ValueFields = valueDecoder.Fields + res.valueDecoder = func(i []byte) (any, error) { + x, err := c.m.vc.Decode(i) + if err != nil { + return nil, err + } + + if valueDecoder.ToSchemaType == nil { + return x, nil + } + + return valueDecoder.ToSchemaType(x) + } + ensureFieldNames(c.m.vc, "value", res.objectType.ValueFields) + + return res, nil +} + +// ensureFieldNames makes sure that all fields have valid names - either the +// names were specified by user or they get filled +func ensureFieldNames(x any, defaultName string, cols []schema.Field) { + var names []string = nil + if hasName, ok := x.(interface{ Name() string }); ok { + name := hasName.Name() + if name != "" { + names = strings.Split(hasName.Name(), ",") + } + } + for i, col := range cols { + if names != nil && i < len(names) { + col.Name = names[i] + } else if col.Name == "" { + if i == 0 && len(cols) == 1 { + col.Name = defaultName + } else { + col.Name = fmt.Sprintf("%s%d", defaultName, i+1) + } + } + cols[i] = col + } +} + +// toProtoMessage is a helper to convert a value to a proto.Message. +func toProtoMessage(value interface{}) (proto.Message, error) { + if value == nil { + return nil, fmt.Errorf("value is nil") + } + + // Check if the value already implements proto.Message + if msg, ok := value.(proto.Message); ok { + return msg, nil + } + + // Use reflection to handle non-pointer values + v := reflect.ValueOf(value) + if v.Kind() == reflect.Ptr { + // Already a pointer, but doesn't implement proto.Message + return nil, fmt.Errorf("value is a pointer but does not implement proto.Message") + } + + // If not a pointer, create a pointer to the value dynamically + ptr := reflect.New(v.Type()) + ptr.Elem().Set(v) + + // Assert if the pointer implements proto.Message + msg, ok := ptr.Interface().(proto.Message) + if !ok { + return nil, fmt.Errorf("value does not implement proto.Message") + } + + return msg, nil +} diff --git a/collections/protocodec/collections.go b/collections/protocodec/collections.go new file mode 100644 index 000000000000..f989cddbcd0f --- /dev/null +++ b/collections/protocodec/collections.go @@ -0,0 +1,137 @@ +package protocodec + +import ( + "fmt" + + "github.com/cosmos/gogoproto/proto" + gogotypes "github.com/cosmos/gogoproto/types" + "google.golang.org/protobuf/encoding/protojson" + protov2 "google.golang.org/protobuf/proto" + + "cosmossdk.io/collections" + collcodec "cosmossdk.io/collections/codec" + corecodec "cosmossdk.io/core/codec" +) + +// BoolValue implements a ValueCodec that saves the bool value +// as if it was a prototypes.BoolValue. Required for backwards +// compatibility of state. +var BoolValue collcodec.ValueCodec[bool] = boolValue{} + +type boolValue struct{} + +func (boolValue) Encode(value bool) ([]byte, error) { + return (&gogotypes.BoolValue{Value: value}).Marshal() +} + +func (boolValue) Decode(b []byte) (bool, error) { + v := new(gogotypes.BoolValue) + err := v.Unmarshal(b) + return v.Value, err +} + +func (boolValue) EncodeJSON(value bool) ([]byte, error) { + return collections.BoolValue.EncodeJSON(value) +} + +func (boolValue) DecodeJSON(b []byte) (bool, error) { + return collections.BoolValue.DecodeJSON(b) +} + +func (boolValue) Stringify(value bool) string { + return collections.BoolValue.Stringify(value) +} + +func (boolValue) ValueType() string { + return "protobuf/bool" +} + +type protoMessage[T any] interface { + *T + proto.Message +} + +// CollValue inits a collections.ValueCodec for a generic gogo protobuf message. +func CollValue[T any, PT protoMessage[T]](cdc interface { + Marshal(proto.Message) ([]byte, error) + Unmarshal([]byte, proto.Message) error +}, +) collcodec.ValueCodec[T] { + return &collValue[T, PT]{cdc.(corecodec.Codec), proto.MessageName(PT(new(T)))} +} + +type collValue[T any, PT protoMessage[T]] struct { + cdc corecodec.Codec + messageName string +} + +func (c collValue[T, PT]) Encode(value T) ([]byte, error) { + return c.cdc.Marshal(PT(&value)) +} + +func (c collValue[T, PT]) Decode(b []byte) (value T, err error) { + err = c.cdc.Unmarshal(b, PT(&value)) + return value, err +} + +func (c collValue[T, PT]) EncodeJSON(value T) ([]byte, error) { + return c.cdc.MarshalJSON(PT(&value)) +} + +func (c collValue[T, PT]) DecodeJSON(b []byte) (value T, err error) { + err = c.cdc.UnmarshalJSON(b, PT(&value)) + return +} + +func (c collValue[T, PT]) Stringify(value T) string { + return PT(&value).String() +} + +func (c collValue[T, PT]) ValueType() string { + return "github.com/cosmos/gogoproto/" + c.messageName +} + +type protoMessageV2[T any] interface { + *T + protov2.Message +} + +// CollValueV2 is used for protobuf values of the newest google.golang.org/protobuf API. +func CollValueV2[T any, PT protoMessageV2[T]]() collcodec.ValueCodec[PT] { + return &collValue2[T, PT]{ + messageName: string(PT(new(T)).ProtoReflect().Descriptor().FullName()), + } +} + +type collValue2[T any, PT protoMessageV2[T]] struct { + messageName string +} + +func (c collValue2[T, PT]) Encode(value PT) ([]byte, error) { + protov2MarshalOpts := protov2.MarshalOptions{Deterministic: true} + return protov2MarshalOpts.Marshal(value) +} + +func (c collValue2[T, PT]) Decode(b []byte) (PT, error) { + var value T + err := protov2.Unmarshal(b, PT(&value)) + return &value, err +} + +func (c collValue2[T, PT]) EncodeJSON(value PT) ([]byte, error) { + return protojson.Marshal(value) +} + +func (c collValue2[T, PT]) DecodeJSON(b []byte) (PT, error) { + var value T + err := protojson.Unmarshal(b, PT(&value)) + return &value, err +} + +func (c collValue2[T, PT]) Stringify(value PT) string { + return fmt.Sprintf("%v", value) +} + +func (c collValue2[T, PT]) ValueType() string { + return "google.golang.org/protobuf/" + c.messageName +} diff --git a/collections/protocodec/collections_test.go b/collections/protocodec/collections_test.go new file mode 100644 index 000000000000..cf2af632f788 --- /dev/null +++ b/collections/protocodec/collections_test.go @@ -0,0 +1,55 @@ +package protocodec_test + +import ( + "testing" + + gogotypes "github.com/cosmos/gogoproto/types" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/wrapperspb" + + "cosmossdk.io/collections/colltest" + codec "cosmossdk.io/collections/protocodec" +) + +func TestCollectionsCorrectness(t *testing.T) { + t.Run("CollValueV2", func(t *testing.T) { + // NOTE: we cannot use colltest.TestValueCodec because protov2 has different + // compare semantics than protov1. We need to use protocmp.Transform() alongside + // cmp to ensure equality. + encoder := codec.CollValueV2[wrapperspb.UInt64Value]() + value := &wrapperspb.UInt64Value{Value: 500} + encodedValue, err := encoder.Encode(value) + require.NoError(t, err) + decodedValue, err := encoder.Decode(encodedValue) + require.NoError(t, err) + require.True(t, cmp.Equal(value, decodedValue, protocmp.Transform()), "encoding and decoding produces different values") + + encodedJSONValue, err := encoder.EncodeJSON(value) + require.NoError(t, err) + decodedJSONValue, err := encoder.DecodeJSON(encodedJSONValue) + require.NoError(t, err) + require.True(t, cmp.Equal(value, decodedJSONValue, protocmp.Transform()), "encoding and decoding produces different values") + require.NotEmpty(t, encoder.ValueType()) + + _ = encoder.Stringify(value) + }) + + t.Run("BoolValue", func(t *testing.T) { + colltest.TestValueCodec(t, codec.BoolValue, true) + colltest.TestValueCodec(t, codec.BoolValue, false) + + // asserts produced bytes are equal + valueAssert := func(b bool) { + wantBytes, err := (&gogotypes.BoolValue{Value: b}).Marshal() + require.NoError(t, err) + gotBytes, err := codec.BoolValue.Encode(b) + require.NoError(t, err) + require.Equal(t, wantBytes, gotBytes) + } + + valueAssert(true) + valueAssert(false) + }) +} diff --git a/go.mod b/go.mod index 0157fde3aed1..c8bb7701e857 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -go 1.23.1 +go 1.23.2 module github.com/cosmos/cosmos-sdk diff --git a/indexer/postgres/create_table.go b/indexer/postgres/create_table.go new file mode 100644 index 000000000000..58a5177fde2d --- /dev/null +++ b/indexer/postgres/create_table.go @@ -0,0 +1,96 @@ +package postgres + +import ( + "context" + "fmt" + "io" + "strings" +) + +// createTable creates the table for the object type. +func (tm *objectIndexer) createTable(ctx context.Context, conn dbConn) error { + buf := new(strings.Builder) + err := tm.createTableSql(buf) + if err != nil { + return err + } + + sqlStr := buf.String() + if tm.options.logger != nil { + tm.options.logger.Debug("Creating table", "table", tm.tableName(), "sql", sqlStr) + } + _, err = conn.ExecContext(ctx, sqlStr) + return err +} + +// createTableSql generates a CREATE TABLE statement for the object type. +func (tm *objectIndexer) createTableSql(writer io.Writer) error { + _, err := fmt.Fprintf(writer, "CREATE TABLE IF NOT EXISTS %q (\n\t", tm.tableName()) + if err != nil { + return err + } + isSingleton := false + if len(tm.typ.KeyFields) == 0 { + isSingleton = true + _, err = fmt.Fprintf(writer, "_id INTEGER NOT NULL CHECK (_id = 1),\n\t") + if err != nil { + return err + } + } else { + for _, field := range tm.typ.KeyFields { + err = tm.createColumnDefinition(writer, field) + if err != nil { + return err + } + } + } + + for _, field := range tm.typ.ValueFields { + err = tm.createColumnDefinition(writer, field) + if err != nil { + return err + } + } + + // add _deleted column when we have RetainDeletions set and enabled + if !tm.options.disableRetainDeletions && tm.typ.RetainDeletions { + _, err = fmt.Fprintf(writer, "_deleted BOOLEAN NOT NULL DEFAULT FALSE,\n\t") + if err != nil { + return err + } + } + + var pKeys []string + if !isSingleton { + for _, field := range tm.typ.KeyFields { + name, err := tm.updatableColumnName(field) + if err != nil { + return err + } + + pKeys = append(pKeys, name) + } + } else { + pKeys = []string{"_id"} + } + + _, err = fmt.Fprintf(writer, "PRIMARY KEY (%s)", strings.Join(pKeys, ", ")) + if err != nil { + return err + } + + _, err = fmt.Fprintf(writer, "\n);\n") + if err != nil { + return err + } + + // we GRANT SELECT on the table to PUBLIC so that the table is automatically available + // for querying using off-the-shelf tools like pg_graphql, Postgrest, Postgraphile, etc. + // without any login permissions + _, err = fmt.Fprintf(writer, "GRANT SELECT ON TABLE %q TO PUBLIC;", tm.tableName()) + if err != nil { + return err + } + + return nil +} diff --git a/indexer/postgres/enum.go b/indexer/postgres/enum.go new file mode 100644 index 000000000000..10ce2bd13269 --- /dev/null +++ b/indexer/postgres/enum.go @@ -0,0 +1,69 @@ +package postgres + +import ( + "context" + "database/sql" + "fmt" + "io" + "strings" + + "cosmossdk.io/schema" +) + +// createEnumType creates an enum type in the database. +func (m *moduleIndexer) createEnumType(ctx context.Context, conn dbConn, enum schema.EnumType) error { + typeName := enumTypeName(m.moduleName, enum.Name) + row := conn.QueryRowContext(ctx, "SELECT 1 FROM pg_type WHERE typname = $1", typeName) + var res interface{} + if err := row.Scan(&res); err != nil { + if err != sql.ErrNoRows { + return fmt.Errorf("failed to check if enum type %q exists: %v", typeName, err) //nolint:errorlint // using %v for go 1.12 compat + } + } else { + // the enum type already exists + // TODO: add a check to ensure the existing enum type matches the expected values, and update it if necessary? + return nil + } + + buf := new(strings.Builder) + err := createEnumTypeSql(buf, m.moduleName, enum) + if err != nil { + return err + } + + sqlStr := buf.String() + if m.options.logger != nil { + m.options.logger.Debug("Creating enum type", "sql", sqlStr) + } + _, err = conn.ExecContext(ctx, sqlStr) + return err +} + +// createEnumTypeSql generates a CREATE TYPE statement for the enum definition. +func createEnumTypeSql(writer io.Writer, moduleName string, enum schema.EnumType) error { + _, err := fmt.Fprintf(writer, "CREATE TYPE %q AS ENUM (", enumTypeName(moduleName, enum.Name)) + if err != nil { + return err + } + + for i, value := range enum.Values { + if i > 0 { + _, err = fmt.Fprintf(writer, ", ") + if err != nil { + return err + } + } + _, err = fmt.Fprintf(writer, "'%s'", value.Name) + if err != nil { + return err + } + } + + _, err = fmt.Fprintf(writer, ");") + return err +} + +// enumTypeName returns the name of the enum type scoped to the module. +func enumTypeName(moduleName, enumName string) string { + return fmt.Sprintf("%s_%s", moduleName, enumName) +} diff --git a/indexer/postgres/listener.go b/indexer/postgres/listener.go new file mode 100644 index 000000000000..c2e982e2b02f --- /dev/null +++ b/indexer/postgres/listener.go @@ -0,0 +1,85 @@ +package postgres + +import ( + "fmt" + + "cosmossdk.io/schema/appdata" +) + +func (i *indexerImpl) listener() appdata.Listener { + return appdata.Listener{ + InitializeModuleData: func(data appdata.ModuleInitializationData) error { + moduleName := data.ModuleName + modSchema := data.Schema + _, ok := i.modules[moduleName] + if ok { + return fmt.Errorf("module %s already initialized", moduleName) + } + + mm := newModuleIndexer(moduleName, modSchema, i.opts) + i.modules[moduleName] = mm + + return mm.initializeSchema(i.ctx, i.tx) + }, + StartBlock: func(data appdata.StartBlockData) error { + var ( + headerBz []byte + err error + ) + + if data.HeaderJSON != nil { + headerBz, err = data.HeaderJSON() + if err != nil { + return err + } + } else if data.HeaderBytes != nil { + headerBz, err = data.HeaderBytes() + if err != nil { + return err + } + } + + // TODO: verify the format of headerBz, otherwise we'll get `ERROR: invalid input syntax for type json (SQLSTATE 22P02)` + _, err = i.tx.Exec("INSERT INTO block (number, header) VALUES ($1, $2)", data.Height, headerBz) + + return err + }, + OnObjectUpdate: func(data appdata.ObjectUpdateData) error { + module := data.ModuleName + mod, ok := i.modules[module] + if !ok { + return fmt.Errorf("module %s not initialized", module) + } + + for _, update := range data.Updates { + if i.logger != nil { + i.logger.Debug("OnObjectUpdate", "module", module, "type", update.TypeName, "key", update.Key, "delete", update.Delete, "value", update.Value) + } + tm, ok := mod.tables[update.TypeName] + if !ok { + return fmt.Errorf("object type %s not found in schema for module %s", update.TypeName, module) + } + + var err error + if update.Delete { + err = tm.delete(i.ctx, i.tx, update.Key) + } else { + err = tm.insertUpdate(i.ctx, i.tx, update.Key, update.Value) + } + if err != nil { + return err + } + } + return nil + }, + Commit: func(data appdata.CommitData) (func() error, error) { + err := i.tx.Commit() + if err != nil { + return nil, err + } + + i.tx, err = i.db.BeginTx(i.ctx, nil) + return nil, err + }, + } +} diff --git a/indexer/postgres/tests/testdata/init_schema.txt b/indexer/postgres/tests/testdata/init_schema.txt new file mode 100644 index 000000000000..d188c41c9264 --- /dev/null +++ b/indexer/postgres/tests/testdata/init_schema.txt @@ -0,0 +1,58 @@ +INFO: Starting indexing +INFO: Starting indexer + target_name: postgres + type: postgres +DEBUG: Creating enum type + sql: CREATE TYPE "test_my_enum" AS ENUM ('a', 'b', 'c'); +DEBUG: Creating enum type + sql: CREATE TYPE "test_vote_type" AS ENUM ('yes', 'no', 'abstain'); +DEBUG: Creating table + table: test_all_kinds + sql: CREATE TABLE IF NOT EXISTS "test_all_kinds" ( + "id" BIGINT NOT NULL, + "ts" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("ts_nanos")) STORED, + "ts_nanos" BIGINT NOT NULL, + "string" TEXT NOT NULL, + "bytes" BYTEA NOT NULL, + "int8" SMALLINT NOT NULL, + "uint8" SMALLINT NOT NULL, + "int16" SMALLINT NOT NULL, + "uint16" INTEGER NOT NULL, + "int32" INTEGER NOT NULL, + "uint32" BIGINT NOT NULL, + "int64" BIGINT NOT NULL, + "uint64" NUMERIC NOT NULL, + "integer" NUMERIC NOT NULL, + "decimal" NUMERIC NOT NULL, + "bool" BOOLEAN NOT NULL, + "time" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("time_nanos")) STORED, + "time_nanos" BIGINT NOT NULL, + "duration" BIGINT NOT NULL, + "float32" REAL NOT NULL, + "float64" DOUBLE PRECISION NOT NULL, + "address" TEXT NOT NULL, + "enum" "test_my_enum" NOT NULL, + "json" JSONB NOT NULL, + PRIMARY KEY ("id", "ts_nanos") +); +GRANT SELECT ON TABLE "test_all_kinds" TO PUBLIC; +DEBUG: Creating table + table: test_singleton + sql: CREATE TABLE IF NOT EXISTS "test_singleton" ( + _id INTEGER NOT NULL CHECK (_id = 1), + "foo" TEXT NOT NULL, + "bar" INTEGER NULL, + "an_enum" "test_my_enum" NOT NULL, + PRIMARY KEY (_id) +); +GRANT SELECT ON TABLE "test_singleton" TO PUBLIC; +DEBUG: Creating table + table: test_vote + sql: CREATE TABLE IF NOT EXISTS "test_vote" ( + "proposal" BIGINT NOT NULL, + "address" TEXT NOT NULL, + "vote" "test_vote_type" NOT NULL, + _deleted BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY ("proposal", "address") +); +GRANT SELECT ON TABLE "test_vote" TO PUBLIC; diff --git a/indexer/postgres/tests/testdata/init_schema_no_retain_delete.txt b/indexer/postgres/tests/testdata/init_schema_no_retain_delete.txt new file mode 100644 index 000000000000..a1adcb0c0c25 --- /dev/null +++ b/indexer/postgres/tests/testdata/init_schema_no_retain_delete.txt @@ -0,0 +1,57 @@ +INFO: Starting indexing +INFO: Starting indexer + target_name: postgres + type: postgres +DEBUG: Creating enum type + sql: CREATE TYPE "test_my_enum" AS ENUM ('a', 'b', 'c'); +DEBUG: Creating enum type + sql: CREATE TYPE "test_vote_type" AS ENUM ('yes', 'no', 'abstain'); +DEBUG: Creating table + table: test_all_kinds + sql: CREATE TABLE IF NOT EXISTS "test_all_kinds" ( + "id" BIGINT NOT NULL, + "ts" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("ts_nanos")) STORED, + "ts_nanos" BIGINT NOT NULL, + "string" TEXT NOT NULL, + "bytes" BYTEA NOT NULL, + "int8" SMALLINT NOT NULL, + "uint8" SMALLINT NOT NULL, + "int16" SMALLINT NOT NULL, + "uint16" INTEGER NOT NULL, + "int32" INTEGER NOT NULL, + "uint32" BIGINT NOT NULL, + "int64" BIGINT NOT NULL, + "uint64" NUMERIC NOT NULL, + "integer" NUMERIC NOT NULL, + "decimal" NUMERIC NOT NULL, + "bool" BOOLEAN NOT NULL, + "time" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("time_nanos")) STORED, + "time_nanos" BIGINT NOT NULL, + "duration" BIGINT NOT NULL, + "float32" REAL NOT NULL, + "float64" DOUBLE PRECISION NOT NULL, + "address" TEXT NOT NULL, + "enum" "test_my_enum" NOT NULL, + "json" JSONB NOT NULL, + PRIMARY KEY ("id", "ts_nanos") +); +GRANT SELECT ON TABLE "test_all_kinds" TO PUBLIC; +DEBUG: Creating table + table: test_singleton + sql: CREATE TABLE IF NOT EXISTS "test_singleton" ( + _id INTEGER NOT NULL CHECK (_id = 1), + "foo" TEXT NOT NULL, + "bar" INTEGER NULL, + "an_enum" "test_my_enum" NOT NULL, + PRIMARY KEY (_id) +); +GRANT SELECT ON TABLE "test_singleton" TO PUBLIC; +DEBUG: Creating table + table: test_vote + sql: CREATE TABLE IF NOT EXISTS "test_vote" ( + "proposal" BIGINT NOT NULL, + "address" TEXT NOT NULL, + "vote" "test_vote_type" NOT NULL, + PRIMARY KEY ("proposal", "address") +); +GRANT SELECT ON TABLE "test_vote" TO PUBLIC; diff --git a/math/legacy_dec_test.go b/math/legacy_dec_test.go new file mode 100644 index 000000000000..4f3897a0867e --- /dev/null +++ b/math/legacy_dec_test.go @@ -0,0 +1,1329 @@ +package math_test + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "sigs.k8s.io/yaml" + + "cosmossdk.io/math" +) + +type decimalTestSuite struct { + suite.Suite +} + +func TestDecimalTestSuite(t *testing.T) { + suite.Run(t, new(decimalTestSuite)) +} + +func TestDecApproxEq(t *testing.T) { + // d1 = 0.55, d2 = 0.6, tol = 0.1 + d1 := math.LegacyNewDecWithPrec(55, 2) + d2 := math.LegacyNewDecWithPrec(6, 1) + tol := math.LegacyNewDecWithPrec(1, 1) + + require.True(math.LegacyDecApproxEq(t, d1, d2, tol)) + + // d1 = 0.55, d2 = 0.6, tol = 1E-5 + d1 = math.LegacyNewDecWithPrec(55, 2) + d2 = math.LegacyNewDecWithPrec(6, 1) + tol = math.LegacyNewDecWithPrec(1, 5) + + require.False(math.LegacyDecApproxEq(t, d1, d2, tol)) + + // d1 = 0.6, d2 = 0.61, tol = 0.01 + d1 = math.LegacyNewDecWithPrec(6, 1) + d2 = math.LegacyNewDecWithPrec(61, 2) + tol = math.LegacyNewDecWithPrec(1, 2) + + require.True(math.LegacyDecApproxEq(t, d1, d2, tol)) +} + +// create a decimal from a decimal string (ex. "1234.5678") +func (s *decimalTestSuite) mustNewDecFromStr(str string) (d math.LegacyDec) { + d, err := math.LegacyNewDecFromStr(str) + s.Require().NoError(err) + + return d +} + +func (s *decimalTestSuite) TestNewDecFromStr() { + largeBigInt, ok := new(big.Int).SetString("3144605511029693144278234343371835", 10) + s.Require().True(ok) + + largerBigInt, ok := new(big.Int).SetString("8888888888888888888888888888888888888888888888888888888888888888888844444440", 10) + s.Require().True(ok) + + largestBigInt, ok := new(big.Int).SetString("33499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535", 10) + s.Require().True(ok) + + tests := []struct { + decimalStr string + expErr bool + exp math.LegacyDec + }{ + {"", true, math.LegacyDec{}}, + {"0.-75", true, math.LegacyDec{}}, + {"0", false, math.LegacyNewDec(0)}, + {"1", false, math.LegacyNewDec(1)}, + {"1.1", false, math.LegacyNewDecWithPrec(11, 1)}, + {"0.75", false, math.LegacyNewDecWithPrec(75, 2)}, + {"0.8", false, math.LegacyNewDecWithPrec(8, 1)}, + {"0.11111", false, math.LegacyNewDecWithPrec(11111, 5)}, + {"314460551102969.3144278234343371835", true, math.LegacyNewDec(3141203149163817869)}, + { + "314460551102969314427823434337.1835718092488231350", + true, math.LegacyNewDecFromBigIntWithPrec(largeBigInt, 4), + }, + { + "314460551102969314427823434337.1835", + false, math.LegacyNewDecFromBigIntWithPrec(largeBigInt, 4), + }, + {".", true, math.LegacyDec{}}, + {".0", true, math.LegacyNewDec(0)}, + {"1.", true, math.LegacyNewDec(1)}, + {"foobar", true, math.LegacyDec{}}, + {"0.foobar", true, math.LegacyDec{}}, + {"0.foobar.", true, math.LegacyDec{}}, + {"8888888888888888888888888888888888888888888888888888888888888888888844444440", false, math.LegacyNewDecFromBigInt(largerBigInt)}, + {"33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535", false, math.LegacyNewDecFromBigIntWithPrec(largestBigInt, 18)}, + {"133499189745056880149688856635597007162669032647290798121690100488888732861291", true, math.LegacyDec{}}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639936", true, math.LegacyDec{}}, // 2^256 + } + + for tcIndex, tc := range tests { + res, err := math.LegacyNewDecFromStr(tc.decimalStr) + if tc.expErr { + s.Require().NotNil(err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + } else { + s.Require().Nil(err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + s.Require().True(res.Equal(tc.exp), "equality was incorrect, res %v, expTruncated %v, tc %v", res, tc.exp, tcIndex) + } + + // negative tc + res, err = math.LegacyNewDecFromStr("-" + tc.decimalStr) + if tc.expErr { + s.Require().NotNil(err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + } else { + s.Require().Nil(err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + exp := tc.exp.Mul(math.LegacyNewDec(-1)) + s.Require().True(res.Equal(exp), "equality was incorrect, res %v, expTruncated %v, tc %v", res, exp, tcIndex) + } + } +} + +func (s *decimalTestSuite) TestDecString() { + tests := []struct { + d math.LegacyDec + want string + }{ + {math.LegacyNewDec(0), "0.000000000000000000"}, + {math.LegacyNewDec(1), "1.000000000000000000"}, + {math.LegacyNewDec(10), "10.000000000000000000"}, + {math.LegacyNewDec(12340), "12340.000000000000000000"}, + {math.LegacyNewDecWithPrec(12340, 4), "1.234000000000000000"}, + {math.LegacyNewDecWithPrec(12340, 5), "0.123400000000000000"}, + {math.LegacyNewDecWithPrec(12340, 8), "0.000123400000000000"}, + {math.LegacyNewDecWithPrec(1009009009009009009, 17), "10.090090090090090090"}, + } + for tcIndex, tc := range tests { + s.Require().Equal(tc.want, tc.d.String(), "bad String(), index: %v", tcIndex) + } +} + +func (s *decimalTestSuite) TestDecFloat64() { + tests := []struct { + d math.LegacyDec + want float64 + }{ + {math.LegacyNewDec(0), 0.000000000000000000}, + {math.LegacyNewDec(1), 1.000000000000000000}, + {math.LegacyNewDec(10), 10.000000000000000000}, + {math.LegacyNewDec(12340), 12340.000000000000000000}, + {math.LegacyNewDecWithPrec(12340, 4), 1.234000000000000000}, + {math.LegacyNewDecWithPrec(12340, 5), 0.123400000000000000}, + {math.LegacyNewDecWithPrec(12340, 8), 0.000123400000000000}, + {math.LegacyNewDecWithPrec(1009009009009009009, 17), 10.090090090090090090}, + } + for tcIndex, tc := range tests { + value, err := tc.d.Float64() + s.Require().Nil(err, "error getting Float64(), index: %v", tcIndex) + s.Require().Equal(tc.want, value, "bad Float64(), index: %v", tcIndex) + s.Require().Equal(tc.want, tc.d.MustFloat64(), "bad MustFloat64(), index: %v", tcIndex) + } +} + +func (s *decimalTestSuite) TestEqualities() { + tests := []struct { + d1, d2 math.LegacyDec + gt, lt, eq bool + }{ + {math.LegacyNewDec(0), math.LegacyNewDec(0), false, false, true}, + {math.LegacyNewDecWithPrec(0, 2), math.LegacyNewDecWithPrec(0, 4), false, false, true}, + {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(100, 0), false, false, true}, + {math.LegacyNewDecWithPrec(-100, 0), math.LegacyNewDecWithPrec(-100, 0), false, false, true}, + {math.LegacyNewDecWithPrec(-1, 1), math.LegacyNewDecWithPrec(-1, 1), false, false, true}, + {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(3333, 3), false, false, true}, + + {math.LegacyNewDecWithPrec(0, 0), math.LegacyNewDecWithPrec(3333, 3), false, true, false}, + {math.LegacyNewDecWithPrec(0, 0), math.LegacyNewDecWithPrec(100, 0), false, true, false}, + {math.LegacyNewDecWithPrec(-1, 0), math.LegacyNewDecWithPrec(3333, 3), false, true, false}, + {math.LegacyNewDecWithPrec(-1, 0), math.LegacyNewDecWithPrec(100, 0), false, true, false}, + {math.LegacyNewDecWithPrec(1111, 3), math.LegacyNewDecWithPrec(100, 0), false, true, false}, + {math.LegacyNewDecWithPrec(1111, 3), math.LegacyNewDecWithPrec(3333, 3), false, true, false}, + {math.LegacyNewDecWithPrec(-3333, 3), math.LegacyNewDecWithPrec(-1111, 3), false, true, false}, + + {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(0, 0), true, false, false}, + {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(0, 0), true, false, false}, + {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(-1, 0), true, false, false}, + {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(-1, 0), true, false, false}, + {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(1111, 3), true, false, false}, + {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(1111, 3), true, false, false}, + {math.LegacyNewDecWithPrec(-1111, 3), math.LegacyNewDecWithPrec(-3333, 3), true, false, false}, + } + + for tcIndex, tc := range tests { + s.Require().Equal(tc.gt, tc.d1.GT(tc.d2), "GT result is incorrect, tc %d", tcIndex) + s.Require().Equal(tc.lt, tc.d1.LT(tc.d2), "LT result is incorrect, tc %d", tcIndex) + s.Require().Equal(tc.eq, tc.d1.Equal(tc.d2), "equality result is incorrect, tc %d", tcIndex) + } +} + +func (s *decimalTestSuite) TestDecsEqual() { + tests := []struct { + d1s, d2s []math.LegacyDec + eq bool + }{ + {[]math.LegacyDec{math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(0)}, true}, + {[]math.LegacyDec{math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(1)}, false}, + {[]math.LegacyDec{math.LegacyNewDec(0)}, []math.LegacyDec{}, false}, + {[]math.LegacyDec{math.LegacyNewDec(0), math.LegacyNewDec(1)}, []math.LegacyDec{math.LegacyNewDec(0), math.LegacyNewDec(1)}, true}, + {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, true}, + {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(0), math.LegacyNewDec(1)}, false}, + {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(1)}, false}, + {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(2)}, []math.LegacyDec{math.LegacyNewDec(2), math.LegacyNewDec(4)}, false}, + {[]math.LegacyDec{math.LegacyNewDec(3), math.LegacyNewDec(18)}, []math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(6)}, false}, + } + + for tcIndex, tc := range tests { + s.Require().Equal(tc.eq, math.LegacyDecsEqual(tc.d1s, tc.d2s), "equality of decional arrays is incorrect, tc %d", tcIndex) + s.Require().Equal(tc.eq, math.LegacyDecsEqual(tc.d2s, tc.d1s), "equality of decional arrays is incorrect (converse), tc %d", tcIndex) + } +} + +func (s *decimalTestSuite) TestArithmetic() { + tests := []struct { + d1, d2 math.LegacyDec + expMul, expMulTruncate, expMulRoundUp math.LegacyDec + expQuo, expQuoRoundUp, expQuoTruncate math.LegacyDec + expAdd, expSub math.LegacyDec + }{ + // d1 d2 MUL MulTruncate MulRoundUp QUO QUORoundUp QUOTrunctate ADD SUB + {math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0)}, + {math.LegacyNewDec(1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(1), math.LegacyNewDec(1)}, + {math.LegacyNewDec(0), math.LegacyNewDec(1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(1), math.LegacyNewDec(-1)}, + {math.LegacyNewDec(0), math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(-1), math.LegacyNewDec(1)}, + {math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(-1), math.LegacyNewDec(-1)}, + + {math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(2), math.LegacyNewDec(0)}, + {math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(-2), math.LegacyNewDec(0)}, + {math.LegacyNewDec(1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(2)}, + {math.LegacyNewDec(-1), math.LegacyNewDec(1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(-2)}, + + { + math.LegacyNewDec(3), math.LegacyNewDec(7), math.LegacyNewDec(21), math.LegacyNewDec(21), math.LegacyNewDec(21), + math.LegacyNewDecWithPrec(428571428571428571, 18), math.LegacyNewDecWithPrec(428571428571428572, 18), math.LegacyNewDecWithPrec(428571428571428571, 18), + math.LegacyNewDec(10), math.LegacyNewDec(-4), + }, + { + math.LegacyNewDec(2), math.LegacyNewDec(4), math.LegacyNewDec(8), math.LegacyNewDec(8), math.LegacyNewDec(8), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), + math.LegacyNewDec(6), math.LegacyNewDec(-2), + }, + + {math.LegacyNewDec(100), math.LegacyNewDec(100), math.LegacyNewDec(10000), math.LegacyNewDec(10000), math.LegacyNewDec(10000), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(200), math.LegacyNewDec(0)}, + + { + math.LegacyNewDecWithPrec(15, 1), math.LegacyNewDecWithPrec(15, 1), math.LegacyNewDecWithPrec(225, 2), math.LegacyNewDecWithPrec(225, 2), math.LegacyNewDecWithPrec(225, 2), + math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(3), math.LegacyNewDec(0), + }, + { + math.LegacyNewDecWithPrec(3333, 4), math.LegacyNewDecWithPrec(333, 4), math.LegacyNewDecWithPrec(1109889, 8), math.LegacyNewDecWithPrec(1109889, 8), math.LegacyNewDecWithPrec(1109889, 8), + math.LegacyMustNewDecFromStr("10.009009009009009009"), math.LegacyMustNewDecFromStr("10.009009009009009010"), math.LegacyMustNewDecFromStr("10.009009009009009009"), + math.LegacyNewDecWithPrec(3666, 4), math.LegacyNewDecWithPrec(3, 1), + }, + } + + for tcIndex, tc := range tests { + + resAdd := tc.d1.Add(tc.d2) + resSub := tc.d1.Sub(tc.d2) + resMul := tc.d1.Mul(tc.d2) + resMulTruncate := tc.d1.MulTruncate(tc.d2) + resMulRoundUp := tc.d1.MulRoundUp(tc.d2) + s.Require().True(tc.expAdd.Equal(resAdd), "expTruncated %v, res %v, tc %d", tc.expAdd, resAdd, tcIndex) + s.Require().True(tc.expSub.Equal(resSub), "expTruncated %v, res %v, tc %d", tc.expSub, resSub, tcIndex) + s.Require().True(tc.expMul.Equal(resMul), "expTruncated %v, res %v, tc %d", tc.expMul, resMul, tcIndex) + s.Require().True(tc.expMulTruncate.Equal(resMulTruncate), "expTruncated %v, res %v, tc %d", tc.expMulTruncate, resMulTruncate, tcIndex) + s.Require().True(tc.expMulRoundUp.Equal(resMulRoundUp), "expTruncated %v, res %v, tc %d", tc.expMulRoundUp, resMulRoundUp, tcIndex) + + if tc.d2.IsZero() { // panic for divide by zero + s.Require().Panics(func() { tc.d1.Quo(tc.d2) }) + } else { + resQuo := tc.d1.Quo(tc.d2) + s.Require().True(tc.expQuo.Equal(resQuo), "expTruncated %v, res %v, tc %d", tc.expQuo.String(), resQuo.String(), tcIndex) + + resQuoRoundUp := tc.d1.QuoRoundUp(tc.d2) + s.Require().True(tc.expQuoRoundUp.Equal(resQuoRoundUp), "expTruncated %v, res %v, tc %d", + tc.expQuoRoundUp.String(), resQuoRoundUp.String(), tcIndex) + + resQuoTruncate := tc.d1.QuoTruncate(tc.d2) + s.Require().True(tc.expQuoTruncate.Equal(resQuoTruncate), "expTruncated %v, res %v, tc %d", + tc.expQuoTruncate.String(), resQuoTruncate.String(), tcIndex) + } + } +} + +func (s *decimalTestSuite) TestMulRoundUp_RoundingAtPrecisionEnd() { + var ( + a = math.LegacyMustNewDecFromStr("0.000000000000000009") + b = math.LegacyMustNewDecFromStr("0.000000000000000009") + expectedRoundUp = math.LegacyMustNewDecFromStr("0.000000000000000001") + expectedTruncate = math.LegacyMustNewDecFromStr("0.000000000000000000") + ) + + actualRoundUp := a.MulRoundUp(b) + s.Require().Equal(expectedRoundUp.String(), actualRoundUp.String(), "expTruncated %v, res %v", expectedRoundUp, actualRoundUp) + + actualTruncate := a.MulTruncate(b) + s.Require().Equal(expectedTruncate.String(), actualTruncate.String(), "expTruncated %v, res %v", expectedRoundUp, actualTruncate) +} + +func (s *decimalTestSuite) TestBankerRoundChop() { + tests := []struct { + d1 math.LegacyDec + exp int64 + }{ + {s.mustNewDecFromStr("0.25"), 0}, + {s.mustNewDecFromStr("0"), 0}, + {s.mustNewDecFromStr("1"), 1}, + {s.mustNewDecFromStr("0.75"), 1}, + {s.mustNewDecFromStr("0.5"), 0}, + {s.mustNewDecFromStr("7.5"), 8}, + {s.mustNewDecFromStr("1.5"), 2}, + {s.mustNewDecFromStr("2.5"), 2}, + {s.mustNewDecFromStr("0.545"), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even + {s.mustNewDecFromStr("1.545"), 2}, + } + + for tcIndex, tc := range tests { + resNeg := tc.d1.Neg().RoundInt64() + s.Require().Equal(-1*tc.exp, resNeg, "negative tc %d", tcIndex) + + resPos := tc.d1.RoundInt64() + s.Require().Equal(tc.exp, resPos, "positive tc %d", tcIndex) + } +} + +func (s *decimalTestSuite) TestTruncate() { + tests := []struct { + d1 math.LegacyDec + exp int64 + }{ + {s.mustNewDecFromStr("0"), 0}, + {s.mustNewDecFromStr("0.25"), 0}, + {s.mustNewDecFromStr("0.75"), 0}, + {s.mustNewDecFromStr("1"), 1}, + {s.mustNewDecFromStr("1.5"), 1}, + {s.mustNewDecFromStr("7.5"), 7}, + {s.mustNewDecFromStr("7.6"), 7}, + {s.mustNewDecFromStr("7.4"), 7}, + {s.mustNewDecFromStr("100.1"), 100}, + {s.mustNewDecFromStr("1000.1"), 1000}, + } + + for tcIndex, tc := range tests { + resNeg := tc.d1.Neg().TruncateInt64() + s.Require().Equal(-1*tc.exp, resNeg, "negative tc %d", tcIndex) + + resPos := tc.d1.TruncateInt64() + s.Require().Equal(tc.exp, resPos, "positive tc %d", tcIndex) + } +} + +func (s *decimalTestSuite) TestStringOverflow() { + // two random 64 bit primes + dec1, err := math.LegacyNewDecFromStr("51643150036226787134389711697696177267") + s.Require().NoError(err) + dec2, err := math.LegacyNewDecFromStr("-31798496660535729618459429845579852627") + s.Require().NoError(err) + dec3 := dec1.Add(dec2) + s.Require().Equal( + "19844653375691057515930281852116324640.000000000000000000", + dec3.String(), + ) +} + +func (s *decimalTestSuite) TestDecMulInt() { + tests := []struct { + sdkDec math.LegacyDec + sdkInt math.Int + want math.LegacyDec + }{ + {math.LegacyNewDec(10), math.NewInt(2), math.LegacyNewDec(20)}, + {math.LegacyNewDec(1000000), math.NewInt(100), math.LegacyNewDec(100000000)}, + {math.LegacyNewDecWithPrec(1, 1), math.NewInt(10), math.LegacyNewDec(1)}, + {math.LegacyNewDecWithPrec(1, 5), math.NewInt(20), math.LegacyNewDecWithPrec(2, 4)}, + } + for i, tc := range tests { + got := tc.sdkDec.MulInt(tc.sdkInt) + s.Require().Equal(tc.want, got, "Incorrect result on test case %d", i) + } +} + +func (s *decimalTestSuite) TestDecCeil() { + testCases := []struct { + input math.LegacyDec + expected math.LegacyDec + }{ + {math.LegacyNewDecWithPrec(1000000000000000, math.LegacyPrecision), math.LegacyNewDec(1)}, // 0.001 => 1.0 + {math.LegacyNewDecWithPrec(-1000000000000000, math.LegacyPrecision), math.LegacyZeroDec()}, // -0.001 => 0.0 + {math.LegacyZeroDec(), math.LegacyZeroDec()}, // 0.0 => 0.0 + {math.LegacyNewDecWithPrec(900000000000000000, math.LegacyPrecision), math.LegacyNewDec(1)}, // 0.9 => 1.0 + {math.LegacyNewDecWithPrec(4001000000000000000, math.LegacyPrecision), math.LegacyNewDec(5)}, // 4.001 => 5.0 + {math.LegacyNewDecWithPrec(-4001000000000000000, math.LegacyPrecision), math.LegacyNewDec(-4)}, // -4.001 => -4.0 + {math.LegacyNewDecWithPrec(4700000000000000000, math.LegacyPrecision), math.LegacyNewDec(5)}, // 4.7 => 5.0 + {math.LegacyNewDecWithPrec(-4700000000000000000, math.LegacyPrecision), math.LegacyNewDec(-4)}, // -4.7 => -4.0 + } + + for i, tc := range testCases { + res := tc.input.Ceil() + s.Require().Equal(tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input) + } +} + +func (s *decimalTestSuite) TestCeilOverflow() { + // (2^256 * 10^18 -1) / 10^18 + d, err := math.LegacyNewDecFromStr("115792089237316195423570985008687907853269984665640564039457584007913129639935.999999999999999999") + s.Require().NoError(err) + s.Require().True(d.IsInValidRange()) + // this call panics because the value is too large + s.Require().Panics(func() { d.Ceil() }, "Ceil should panic on overflow") +} + +func (s *decimalTestSuite) TestPower() { + testCases := []struct { + input math.LegacyDec + power uint64 + expected math.LegacyDec + }{ + {math.LegacyNewDec(100), 0, math.LegacyOneDec()}, // 10 ^ (0) => 1.0 + {math.LegacyOneDec(), 10, math.LegacyOneDec()}, // 1.0 ^ (10) => 1.0 + {math.LegacyNewDecWithPrec(5, 1), 2, math.LegacyNewDecWithPrec(25, 2)}, // 0.5 ^ 2 => 0.25 + {math.LegacyNewDecWithPrec(2, 1), 2, math.LegacyNewDecWithPrec(4, 2)}, // 0.2 ^ 2 => 0.04 + {math.LegacyNewDecFromInt(math.NewInt(3)), 3, math.LegacyNewDecFromInt(math.NewInt(27))}, // 3 ^ 3 => 27 + {math.LegacyNewDecFromInt(math.NewInt(-3)), 4, math.LegacyNewDecFromInt(math.NewInt(81))}, // -3 ^ 4 = 81 + {math.LegacyNewDecWithPrec(1414213562373095049, 18), 2, math.LegacyNewDecFromInt(math.NewInt(2))}, // 1.414213562373095049 ^ 2 = 2 + } + + for i, tc := range testCases { + res := tc.input.Power(tc.power) + s.Require().True(tc.expected.Sub(res).Abs().LTE(math.LegacySmallestDec()), "unexpected result for test case %d, normal power, input: %v", i, tc.input) + + mutableInput := tc.input + mutableInput.PowerMut(tc.power) + s.Require().True(tc.expected.Sub(mutableInput).Abs().LTE(math.LegacySmallestDec()), + "unexpected result for test case %d, input %v", i, tc.input) + s.Require().True(res.Equal(tc.input), "unexpected result for test case %d, mutable power, input: %v", i, tc.input) + } +} + +func (s *decimalTestSuite) TestApproxRoot() { + testCases := []struct { + input math.LegacyDec + root uint64 + expected math.LegacyDec + }{ + {math.LegacyNewDecFromInt(math.NewInt(2)), 0, math.LegacyOneDec()}, // 2 ^ 0 => 1.0 + {math.LegacyNewDecWithPrec(4, 2), 0, math.LegacyOneDec()}, // 0.04 ^ 0 => 1.0 + {math.LegacyNewDec(0), 1, math.LegacyNewDec(0)}, // 0 ^ 1 => 0 + {math.LegacyOneDec(), 10, math.LegacyOneDec()}, // 1.0 ^ (0.1) => 1.0 + {math.LegacyNewDecWithPrec(25, 2), 2, math.LegacyNewDecWithPrec(5, 1)}, // 0.25 ^ (0.5) => 0.5 + {math.LegacyNewDecWithPrec(4, 2), 2, math.LegacyNewDecWithPrec(2, 1)}, // 0.04 ^ (0.5) => 0.2 + {math.LegacyNewDecFromInt(math.NewInt(27)), 3, math.LegacyNewDecFromInt(math.NewInt(3))}, // 27 ^ (1/3) => 3 + {math.LegacyNewDecFromInt(math.NewInt(-81)), 4, math.LegacyNewDecFromInt(math.NewInt(-3))}, // -81 ^ (0.25) => -3 + {math.LegacyNewDecFromInt(math.NewInt(2)), 2, math.LegacyNewDecWithPrec(1414213562373095049, 18)}, // 2 ^ (0.5) => 1.414213562373095049 + {math.LegacyNewDecWithPrec(1005, 3), 31536000, math.LegacyMustNewDecFromStr("1.000000000158153904")}, // 1.005 ^ (1/31536000) ≈ 1.00000000016 + {math.LegacySmallestDec(), 2, math.LegacyNewDecWithPrec(1, 9)}, // 1e-18 ^ (0.5) => 1e-9 + {math.LegacySmallestDec(), 3, math.LegacyMustNewDecFromStr("0.000000999999999997")}, // 1e-18 ^ (1/3) => 1e-6 + {math.LegacyNewDecWithPrec(1, 8), 3, math.LegacyMustNewDecFromStr("0.002154434690031900")}, // 1e-8 ^ (1/3) ≈ 0.00215443469 + {math.LegacyMustNewDecFromStr("9000002314687921634000000000000000000021394871242000000000000000"), 2, math.LegacyMustNewDecFromStr("94868342004527103646332858502867.899477053226766107")}, + } + + // In the case of 1e-8 ^ (1/3), the result repeats every 5 iterations starting from iteration 24 + // (i.e. 24, 29, 34, ... give the same result) and never converges enough. The maximum number of + // iterations (300) causes the result at iteration 300 to be returned, regardless of convergence. + + for i, tc := range testCases { + res, err := tc.input.ApproxRoot(tc.root) + s.Require().NoError(err) + s.Require().True(tc.expected.Sub(res).Abs().LTE(math.LegacySmallestDec()), "unexpected result for test case %d, input: %v", i, tc.input) + } +} + +func (s *decimalTestSuite) TestApproxSqrt() { + testCases := []struct { + input math.LegacyDec + expected math.LegacyDec + }{ + {math.LegacyOneDec(), math.LegacyOneDec()}, // 1.0 => 1.0 + {math.LegacyNewDecWithPrec(25, 2), math.LegacyNewDecWithPrec(5, 1)}, // 0.25 => 0.5 + {math.LegacyNewDecWithPrec(4, 2), math.LegacyNewDecWithPrec(2, 1)}, // 0.09 => 0.3 + {math.LegacyNewDec(9), math.LegacyNewDecFromInt(math.NewInt(3))}, // 9 => 3 + {math.LegacyNewDec(-9), math.LegacyNewDecFromInt(math.NewInt(-3))}, // -9 => -3 + {math.LegacyNewDec(2), math.LegacyNewDecWithPrec(1414213562373095049, 18)}, // 2 => 1.414213562373095049 + { // 2^127 - 1 => 13043817825332782212.3495718062525083688 which rounds to 13043817825332782212.3495718062525083689 + math.LegacyNewDec(2).Power(127).Sub(math.LegacyOneDec()), + math.LegacyMustNewDecFromStr("13043817825332782212.349571806252508369"), + }, + {math.LegacyMustNewDecFromStr("1.000000011823380862"), math.LegacyMustNewDecFromStr("1.000000005911690414")}, + } + + for i, tc := range testCases { + res, err := tc.input.ApproxSqrt() + s.Require().NoError(err) + s.Require().Equal(tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input) + } +} + +func (s *decimalTestSuite) TestDecSortableBytes() { + tests := []struct { + d math.LegacyDec + want []byte + }{ + {math.LegacyNewDec(0), []byte("000000000000000000.000000000000000000")}, + {math.LegacyNewDec(1), []byte("000000000000000001.000000000000000000")}, + {math.LegacyNewDec(10), []byte("000000000000000010.000000000000000000")}, + {math.LegacyNewDec(12340), []byte("000000000000012340.000000000000000000")}, + {math.LegacyNewDecWithPrec(12340, 4), []byte("000000000000000001.234000000000000000")}, + {math.LegacyNewDecWithPrec(12340, 5), []byte("000000000000000000.123400000000000000")}, + {math.LegacyNewDecWithPrec(12340, 8), []byte("000000000000000000.000123400000000000")}, + {math.LegacyNewDecWithPrec(1009009009009009009, 17), []byte("000000000000000010.090090090090090090")}, + {math.LegacyNewDecWithPrec(-1009009009009009009, 17), []byte("-000000000000000010.090090090090090090")}, + {math.LegacyNewDec(1000000000000000000), []byte("max")}, + {math.LegacyNewDec(-1000000000000000000), []byte("--")}, + } + for tcIndex, tc := range tests { + s.Require().Equal(tc.want, math.LegacySortableDecBytes(tc.d), "bad String(), index: %v", tcIndex) + } + + s.Require().Panics(func() { math.LegacySortableDecBytes(math.LegacyNewDec(1000000000000000001)) }) + s.Require().Panics(func() { math.LegacySortableDecBytes(math.LegacyNewDec(-1000000000000000001)) }) +} + +func (s *decimalTestSuite) TestDecEncoding() { + largestBigInt, ok := new(big.Int).SetString("33499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535", 10) + s.Require().True(ok) + + smallestBigInt, ok := new(big.Int).SetString("-33499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535", 10) + s.Require().True(ok) + + const maxDecBitLen = 315 + maxInt, ok := new(big.Int).SetString(strings.Repeat("1", maxDecBitLen), 2) + s.Require().True(ok) + + testCases := []struct { + input math.LegacyDec + rawBz string + jsonStr string + yamlStr string + }{ + { + math.LegacyNewDec(0), "30", + "\"0.000000000000000000\"", + "\"0.000000000000000000\"\n", + }, + { + math.LegacyNewDecWithPrec(4, 2), + "3430303030303030303030303030303030", + "\"0.040000000000000000\"", + "\"0.040000000000000000\"\n", + }, + { + math.LegacyNewDecWithPrec(-4, 2), + "2D3430303030303030303030303030303030", + "\"-0.040000000000000000\"", + "\"-0.040000000000000000\"\n", + }, + { + math.LegacyNewDecWithPrec(1414213562373095049, 18), + "31343134323133353632333733303935303439", + "\"1.414213562373095049\"", + "\"1.414213562373095049\"\n", + }, + { + math.LegacyNewDecWithPrec(-1414213562373095049, 18), + "2D31343134323133353632333733303935303439", + "\"-1.414213562373095049\"", + "\"-1.414213562373095049\"\n", + }, + { + math.LegacyNewDecFromBigIntWithPrec(largestBigInt, 18), + "3333343939313839373435303536383830313439363838383536363335353937303037313632363639303332363437323930373938313231363930313030343838383838373332383631323930303334333736343335313330343333353335", + "\"33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"", + "\"33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"\n", + }, + { + math.LegacyNewDecFromBigIntWithPrec(smallestBigInt, 18), + "2D3333343939313839373435303536383830313439363838383536363335353937303037313632363639303332363437323930373938313231363930313030343838383838373332383631323930303334333736343335313330343333353335", + "\"-33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"", + "\"-33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"\n", + }, + { + math.LegacyNewDecFromBigIntWithPrec(maxInt, 18), + "3636373439353934383732353238343430303734383434343238333137373938353033353831333334353136333233363435333939303630383435303530323434343434333636343330363435303137313838323137353635323136373637", + "\"66749594872528440074844428317798503581334516323645399060845050244444366430645.017188217565216767\"", + "\"66749594872528440074844428317798503581334516323645399060845050244444366430645.017188217565216767\"\n", + }, + } + + for _, tc := range testCases { + bz, err := tc.input.Marshal() + s.Require().NoError(err) + s.Require().Equal(tc.rawBz, fmt.Sprintf("%X", bz)) + + var other math.LegacyDec + s.Require().NoError((&other).Unmarshal(bz)) + s.Require().True(tc.input.Equal(other)) + + bz, err = json.Marshal(tc.input) + s.Require().NoError(err) + s.Require().Equal(tc.jsonStr, string(bz)) + s.Require().NoError(json.Unmarshal(bz, &other)) + s.Require().True(tc.input.Equal(other)) + + bz, err = yaml.Marshal(tc.input) + s.Require().NoError(err) + s.Require().Equal(tc.yamlStr, string(bz)) + } +} + +// Showcase that different orders of operations causes different results. +func (s *decimalTestSuite) TestOperationOrders() { + n1 := math.LegacyNewDec(10) + n2 := math.LegacyNewDec(1000000010) + s.Require().Equal(n1.Mul(n2).Quo(n2), math.LegacyNewDec(10)) + s.Require().NotEqual(n1.Mul(n2).Quo(n2), n1.Quo(n2).Mul(n2)) +} + +func BenchmarkMarshalTo(b *testing.B) { + b.ReportAllocs() + bis := []struct { + in math.LegacyDec + want []byte + }{ + { + math.LegacyNewDec(1e8), []byte{ + 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + }, + }, + {math.LegacyNewDec(0), []byte{0x30}}, + } + data := make([]byte, 100) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, bi := range bis { + if n, err := bi.in.MarshalTo(data); err != nil { + b.Fatal(err) + } else if !bytes.Equal(data[:n], bi.want) { + b.Fatalf("Mismatch\nGot: % x\nWant: % x\n", data[:n], bi.want) + } + } + } +} + +var sink interface{} + +func BenchmarkLegacyQuoMut(b *testing.B) { + b1 := math.LegacyNewDec(17e2 + 8371) + b2 := math.LegacyNewDec(4371) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sink = b1.QuoMut(b2) + } + + if sink == nil { + b.Fatal("Benchmark did not run") + } + sink = (interface{})(nil) +} + +func BenchmarkLegacyQuoTruncateMut(b *testing.B) { + b1 := math.LegacyNewDec(17e2 + 8371) + baseArr := make([]math.LegacyDec, b.N) + for i := 0; i < b.N; i++ { + baseArr[i] = b1.Clone() + } + b2 := math.LegacyNewDec(4371) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sink = baseArr[i].QuoTruncateMut(b2) + } + + if sink == nil { + b.Fatal("Benchmark did not run") + } + sink = (interface{})(nil) +} + +func BenchmarkLegacySqrtOnMersennePrime(b *testing.B) { + b1 := math.LegacyNewDec(2).Power(127).Sub(math.LegacyOneDec()) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sink, _ = b1.ApproxSqrt() + } + + if sink == nil { + b.Fatal("Benchmark did not run") + } + sink = (interface{})(nil) +} + +func BenchmarkLegacyQuoRoundupMut(b *testing.B) { + b1 := math.LegacyNewDec(17e2 + 8371) + baseArr := make([]math.LegacyDec, b.N) + for i := 0; i < b.N; i++ { + baseArr[i] = b1.Clone() + } + b2 := math.LegacyNewDec(4371) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sink = baseArr[i].QuoRoundupMut(b2) + } + + if sink == nil { + b.Fatal("Benchmark did not run") + } + sink = (interface{})(nil) +} + +func TestFormatDec(t *testing.T) { + type decimalTest []string + var testcases []decimalTest + raw, err := os.ReadFile("./testdata/decimals.json") + require.NoError(t, err) + err = json.Unmarshal(raw, &testcases) + require.NoError(t, err) + + for _, tc := range testcases { + t.Run(tc[0], func(t *testing.T) { + out, err := math.FormatDec(tc[0]) + require.NoError(t, err) + require.Equal(t, tc[1], out) + }) + } +} + +func TestFormatDecNonDigits(t *testing.T) { + badCases := []string{ + "10.a", + "1a.10", + "p1a10.", + "0.10p", + "--10", + "12.😎😎", + "11111111111133333333333333333333333333333a", + "11111111111133333333333333333333333333333 192892", + } + + for _, value := range badCases { + value := value + t.Run(value, func(t *testing.T) { + s, err := math.FormatDec(value) + if err == nil { + t.Fatal("Expected an error") + } + if g, w := err.Error(), "non-digits"; !strings.Contains(g, w) { + t.Errorf("Error mismatch\nGot: %q\nWant substring: %q", g, w) + } + if s != "" { + t.Fatalf("Got a non-empty string: %q", s) + } + }) + } +} + +func TestNegativePrecisionPanic(t *testing.T) { + require.Panics(t, func() { + math.LegacyNewDecWithPrec(10, -1) + }) +} + +func (s *decimalTestSuite) TestConvertToBigIntMutativeForLegacyDec() { + r := big.NewInt(30) + i := math.LegacyNewDecFromBigInt(r) + + // Compare value of BigInt & BigIntMut + s.Require().Equal(i.BigInt(), i.BigIntMut()) + + // Modify BigIntMut() pointer and ensure i.BigIntMut() & i.BigInt() change + p1 := i.BigIntMut() + p1.SetInt64(40) + s.Require().Equal(big.NewInt(40), i.BigIntMut()) + s.Require().Equal(big.NewInt(40), i.BigInt()) + + // Modify big.Int() pointer and ensure i.BigIntMut() & i.BigInt() don't change + p2 := i.BigInt() + p2.SetInt64(50) + s.Require().NotEqual(big.NewInt(50), i.BigIntMut()) + s.Require().NotEqual(big.NewInt(50), i.BigInt()) +} + +func TestQuoMut(t *testing.T) { + specs := map[string]struct { + dividend, divisor math.LegacyDec + expTruncated, expRoundedUp string + expPanic bool + }{ + "0.0000000000000000001": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("10"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000002": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("5"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000003": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("3.333333333333333"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000004": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("2.5"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000005": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("2"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000006": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("1.666666666666666666"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000007": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("1.428571428571429"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000008": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("1.25"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000009": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("1.111111111111111"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000001": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("10"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000002": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("5"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000003": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("3.333333333333333"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000004": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("2.5"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000005": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("2"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000006": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("1.666666666666666666"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000007": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("1.428571428571429"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000008": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("1.25"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000009": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("1.111111111111111"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000001": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-10"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000002": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-5"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000003": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-3.333333333333333"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000004": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-2.5"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000005": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-2"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000006": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-1.666666666666666666"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000007": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-1.428571428571429"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000008": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-1.25"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000009": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-1.111111111111111"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "big / small": { + dividend: math.LegacyMustNewDecFromStr("999999999999999999"), + divisor: math.LegacyNewDecWithPrec(1, 18), + expRoundedUp: "999999999999999999000000000000000000.000000000000000000", + expTruncated: "999999999999999999000000000000000000.000000000000000000", + }, + "divide by dividend": { + dividend: math.LegacyNewDecWithPrec(123, 0), + divisor: math.LegacyMustNewDecFromStr("123"), + expRoundedUp: "1.000000000000000000", + expTruncated: "1.000000000000000000", + }, + "zero divided": { + dividend: math.LegacyNewDecWithPrec(0, 0), + divisor: math.LegacyMustNewDecFromStr("1"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "zero divided by negative value": { + dividend: math.LegacyNewDecWithPrec(0, 0), + divisor: math.LegacyMustNewDecFromStr("-1"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "zero divided by zero": { + dividend: math.LegacyNewDecWithPrec(0, 0), + divisor: math.LegacyMustNewDecFromStr("0"), + expPanic: true, + }, + "divide by zero": { + dividend: math.LegacyNewDecWithPrec(1, 0), + divisor: math.LegacyMustNewDecFromStr("0"), + expPanic: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + t.Run("round up", func(t *testing.T) { + t.Parallel() + if !spec.expPanic { + got := spec.dividend.Clone().QuoRoundupMut(spec.divisor.Clone()) + require.Equal(t, spec.expRoundedUp, got.String()) + return + } + require.Panics(t, func() { + _ = spec.dividend.Clone().QuoRoundupMut(spec.divisor.Clone()) + }) + }) + t.Run("truncate", func(t *testing.T) { + t.Parallel() + if !spec.expPanic { + got := spec.dividend.Clone().QuoTruncateMut(spec.divisor.Clone()) + require.Equal(t, spec.expTruncated, got.String()) + return + } + require.Panics(t, func() { + _ = spec.dividend.Clone().QuoTruncateMut(spec.divisor.Clone()) + }) + }) + }) + } +} + +func Test_DocumentLegacyAsymmetry(t *testing.T) { + zeroDec := math.LegacyZeroDec() + emptyDec := math.LegacyDec{} + + zeroDecBz, err := zeroDec.Marshal() + require.NoError(t, err) + zeroDecJSON, err := zeroDec.MarshalJSON() + require.NoError(t, err) + + emptyDecBz, err := emptyDec.Marshal() + require.NoError(t, err) + emptyDecJSON, err := emptyDec.MarshalJSON() + require.NoError(t, err) + + // makes sense, zero and empty are semantically different and render differently + require.NotEqual(t, zeroDecJSON, emptyDecJSON) + // but on the proto wire they encode to the same bytes + require.Equal(t, zeroDecBz, emptyDecBz) + + // zero values are symmetrical + zeroDecRoundTrip := math.LegacyDec{} + err = zeroDecRoundTrip.Unmarshal(zeroDecBz) + require.NoError(t, err) + zeroDecRoundTripJSON, err := zeroDecRoundTrip.MarshalJSON() + require.NoError(t, err) + require.Equal(t, zeroDecJSON, zeroDecRoundTripJSON) + require.Equal(t, zeroDec, zeroDecRoundTrip) + + // empty values are not + emptyDecRoundTrip := math.LegacyDec{} + err = emptyDecRoundTrip.Unmarshal(emptyDecBz) + require.NoError(t, err) + emptyDecRoundTripJSON, err := emptyDecRoundTrip.MarshalJSON() + require.NoError(t, err) + + // !!! this is the key point, they are not equal, it looks like a bug + require.NotEqual(t, emptyDecJSON, emptyDecRoundTripJSON) + require.NotEqual(t, emptyDec, emptyDecRoundTrip) +} + +// 2^256 * 10^18 -1 +const maxValidDecNumber = "115792089237316195423570985008687907853269984665640564039457584007913129639935999999999999999999" + +func TestDecOpsWithinLimits(t *testing.T) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(t, ok) + minValid := new(big.Int).Neg(maxValid) + specs := map[string]struct { + src *big.Int + expErr bool + }{ + "max": { + src: maxValid, + }, + "max + 1": { + src: new(big.Int).Add(maxValid, big.NewInt(1)), + expErr: true, + }, + "min": { + src: minValid, + }, + "min - 1": { + src: new(big.Int).Sub(minValid, big.NewInt(1)), + expErr: true, + }, + "max Int": { + // max Int is 2^256 -1 + src: math.NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1))).BigIntMut(), + }, + "min Int": { + // max Int is -1 *(2^256 -1) + src: math.NewIntFromBigInt(new(big.Int).Neg(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1)))).BigIntMut(), + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + src := math.LegacyNewDecFromBigIntWithPrec(spec.src, 18) + + ops := map[string]struct { + fn func(src math.LegacyDec) math.LegacyDec + }{ + "AddMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.AddMut(math.LegacyNewDec(0)) }, + }, + "SubMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.SubMut(math.LegacyNewDec(0)) }, + }, + "MulMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulMut(math.LegacyNewDec(1)) }, + }, + "MulTruncateMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulTruncateMut(math.LegacyNewDec(1)) }, + }, + "MulRoundUpMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulRoundUpMut(math.LegacyNewDec(1)) }, + }, + "MulIntMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulIntMut(math.NewInt(1)) }, + }, + "MulInt64Mut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulInt64Mut(1) }, + }, + "QuoMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.QuoMut(math.LegacyNewDec(1)) }, + }, + "QuoTruncateMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.QuoTruncateMut(math.LegacyNewDec(1)) }, + }, + "QuoRoundupMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.QuoRoundupMut(math.LegacyNewDec(1)) }, + }, + } + for name, op := range ops { + t.Run(name, func(t *testing.T) { + if spec.expErr { + assert.Panics(t, func() { + got := op.fn(src) + t.Log(got.String()) + }) + return + } + exp := src.String() + // exp no panics + got := op.fn(src) + assert.Equal(t, exp, got.String()) + }) + } + }) + } +} + +func TestDecCeilLimits(t *testing.T) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(t, ok) + minValid := new(big.Int).Neg(maxValid) + + specs := map[string]struct { + src *big.Int + exp string + expErr bool + }{ + "max": { + src: maxValid, + expErr: true, + }, + "max + 1": { + src: new(big.Int).Add(maxValid, big.NewInt(1)), + expErr: true, + }, + "max - 1e18, previous full number": { + src: new(big.Int).Sub(maxValid, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)), + exp: "115792089237316195423570985008687907853269984665640564039457584007913129639935.000000000000000000", + }, + "min": { + src: minValid, + exp: "-115792089237316195423570985008687907853269984665640564039457584007913129639935.000000000000000000", + }, + "min - 1": { + src: new(big.Int).Sub(minValid, big.NewInt(1)), + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + src := math.LegacyNewDecFromBigIntWithPrec(spec.src, 18) + if spec.expErr { + assert.Panics(t, func() { + got := src.Ceil() + t.Log(got.String()) + }) + return + } + got := src.Ceil() + assert.Equal(t, spec.exp, got.String()) + }) + } +} + +func TestTruncateIntLimits(t *testing.T) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(t, ok) + minValid := new(big.Int).Neg(maxValid) + + specs := map[string]struct { + src *big.Int + exp string + expErr bool + }{ + "max": { + src: maxValid, + exp: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + "max + 1": { + src: new(big.Int).Add(maxValid, big.NewInt(1)), + expErr: true, + }, + "min": { + src: minValid, + exp: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + "min - 1": { + src: new(big.Int).Sub(minValid, big.NewInt(1)), + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + src := math.LegacyNewDecFromBigIntWithPrec(spec.src, 18) + if spec.expErr { + assert.Panics(t, func() { + got := src.TruncateInt() + t.Log(got.String()) + }) + return + } + got := src.TruncateInt() + assert.Equal(t, spec.exp, got.String()) + }) + } +} + +func TestRoundIntLimits(t *testing.T) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(t, ok) + minValid := new(big.Int).Neg(maxValid) + oneE18 := new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil) + + specs := map[string]struct { + src *big.Int + exp string + expErr bool + }{ + "max -1e18; previous full number": { + src: new(big.Int).Sub(maxValid, oneE18), + exp: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + "max": { + src: maxValid, + expErr: true, + }, + "max + 1": { + src: new(big.Int).Add(maxValid, big.NewInt(1)), + expErr: true, + }, + "min + 1e18; previous full number": { + src: new(big.Int).Add(minValid, oneE18), + exp: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + "min": { + src: minValid, + expErr: true, + }, + "min - 1": { + src: new(big.Int).Sub(minValid, big.NewInt(1)), + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + src := math.LegacyNewDecFromBigIntWithPrec(spec.src, 18) + t.Log(src.String()) + if spec.expErr { + assert.Panics(t, func() { + got := src.RoundInt() + t.Log(got.String()) + }) + return + } + got := src.RoundInt() + assert.Equal(t, spec.exp, got.String()) + }) + } +} + +func BenchmarkIsInValidRange(b *testing.B) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(b, ok) + souceMax := math.LegacyNewDecFromBigIntWithPrec(maxValid, 18) + b.ResetTimer() + specs := map[string]math.LegacyDec{ + "max": souceMax, + "greater max": math.LegacyNewDecFromBigIntWithPrec(maxValid, 16), + "min": souceMax.Neg(), + "lower min": math.LegacyNewDecFromBigIntWithPrec(new(big.Int).Neg(maxValid), 16), + "zero": math.LegacyZeroDec(), + "one": math.LegacyOneDec(), + } + for name, source := range specs { + b.Run(name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = source.IsInValidRange() + } + }) + } +} diff --git a/schema/appdata/async.go b/schema/appdata/async.go new file mode 100644 index 000000000000..d16216b56dd7 --- /dev/null +++ b/schema/appdata/async.go @@ -0,0 +1,144 @@ +package appdata + +import ( + "context" + "sync" +) + +// AsyncListenerOptions are options for async listeners and listener mux's. +type AsyncListenerOptions struct { + // Context is the context whose Done() channel listeners use will use to listen for completion to close their + // goroutine. If it is nil, then context.Background() will be used and goroutines may be leaked. + Context context.Context + + // BufferSize is the buffer size of the channels to use. It defaults to 0. + BufferSize int + + // DoneWaitGroup is an optional wait-group that listener goroutines will notify via Add(1) when they are started + // and Done() after they are canceled and completed. + DoneWaitGroup *sync.WaitGroup +} + +// AsyncListenerMux is a convenience function that calls AsyncListener for each listener +// with the provided options and combines them using ListenerMux. +func AsyncListenerMux(opts AsyncListenerOptions, listeners ...Listener) Listener { + asyncListeners := make([]Listener, len(listeners)) + for i, l := range listeners { + asyncListeners[i] = AsyncListener(opts, l) + } + return ListenerMux(asyncListeners...) +} + +// AsyncListener returns a listener that forwards received events to the provided listener listening in asynchronously +// in a separate go routine. The listener that is returned will return nil for all methods including Commit and +// an error or nil will only be returned when the callback returned by Commit is called. +// Thus Commit() can be used as a synchronization and error checking mechanism. The go routine +// that is being used for listening will exit when context.Done() returns and no more events will be received by the listener. +// bufferSize is the size of the buffer for the channel that is used to send events to the listener. +func AsyncListener(opts AsyncListenerOptions, listener Listener) Listener { + commitChan := make(chan error) + packetChan := make(chan Packet, opts.BufferSize) + res := Listener{} + ctx := opts.Context + if ctx == nil { + ctx = context.Background() + } + done := ctx.Done() + + go func() { + defer func() { + close(packetChan) + if opts.DoneWaitGroup != nil { + opts.DoneWaitGroup.Done() + } + }() + + if opts.DoneWaitGroup != nil { + opts.DoneWaitGroup.Add(1) + } + + var err error + for { + select { + case packet := <-packetChan: + if err != nil { + // if we have an error, don't process any more packets + // and return the error and finish when it's time to commit + if _, ok := packet.(CommitData); ok { + commitChan <- err + return + } + } else { + // process the packet + err = listener.SendPacket(packet) + // if it's a commit + if _, ok := packet.(CommitData); ok { + commitChan <- err + if err != nil { + return + } + } + } + + case <-done: + return + } + } + }() + + if listener.InitializeModuleData != nil { + res.InitializeModuleData = func(data ModuleInitializationData) error { + packetChan <- data + return nil + } + } + + if listener.StartBlock != nil { + res.StartBlock = func(data StartBlockData) error { + packetChan <- data + return nil + } + } + + if listener.OnTx != nil { + res.OnTx = func(data TxData) error { + packetChan <- data + return nil + } + } + + if listener.OnEvent != nil { + res.OnEvent = func(data EventData) error { + packetChan <- data + return nil + } + } + + if listener.OnKVPair != nil { + res.OnKVPair = func(data KVPairData) error { + packetChan <- data + return nil + } + } + + if listener.OnObjectUpdate != nil { + res.OnObjectUpdate = func(data ObjectUpdateData) error { + packetChan <- data + return nil + } + } + + res.Commit = func(data CommitData) (func() error, error) { + packetChan <- data + return func() error { + return <-commitChan + }, nil + } + + res.onBatch = func(batch PacketBatch) error { + packetChan <- batch + return nil + } + + return res +} diff --git a/schema/field.go b/schema/field.go new file mode 100644 index 000000000000..d76e91b56792 --- /dev/null +++ b/schema/field.go @@ -0,0 +1,101 @@ +package schema + +import ( + "fmt" +) + +// Field represents a field in an object type. +type Field struct { + // Name is the name of the field. It must conform to the NameFormat regular expression. + Name string `json:"name"` + + // Kind is the basic type of the field. + Kind Kind `json:"kind"` + + // Nullable indicates whether null values are accepted for the field. Key fields CANNOT be nullable. + Nullable bool `json:"nullable,omitempty"` + + // ReferencedType is the referenced type name when Kind is EnumKind, StructKind or OneOfKind. + ReferencedType string `json:"referenced_type,omitempty"` + + // ElementKind is the element type when Kind is ListKind. + // Support for this is currently UNIMPLEMENTED, this notice will be removed when it is added. + ElementKind Kind `json:"element_kind,omitempty"` + + // Size specifies the size or max-size of a field. + // Support for this is currently UNIMPLEMENTED, this notice will be removed when it is added. + // Its specific meaning may vary depending on the field kind. + // For IntNKind and UintNKind fields, it specifies the bit width of the field. + // For StringKind, BytesKind, AddressKind, and JSONKind, fields it specifies the maximum length rather than a fixed length. + // If it is 0, such fields have no maximum length. + // It is invalid to have a non-zero Size for other kinds. + Size uint32 `json:"size,omitempty"` +} + +// Validate validates the field. +func (c Field) Validate(typeSet TypeSet) error { + // valid name + if c.Name == "" { + return fmt.Errorf("field name cannot be empty, might be missing the named key codec") + } + + if !ValidateName(c.Name) { + return fmt.Errorf("invalid field name %q", c.Name) + } + + // valid kind + if err := c.Kind.Validate(); err != nil { + return fmt.Errorf("invalid field kind for %q: %v", c.Name, err) //nolint:errorlint // false positive due to using go1.12 + } + + // enum definition only valid with EnumKind + switch c.Kind { + case EnumKind: + if c.ReferencedType == "" { + return fmt.Errorf("enum field %q must have a referenced type", c.Name) + } + + _, ok := typeSet.LookupEnumType(c.ReferencedType) + if !ok { + return fmt.Errorf("can't find enum type %q referenced by field %q", c.ReferencedType, c.Name) + } + + default: + if c.ReferencedType != "" { + return fmt.Errorf("field %q with kind %q cannot have a referenced type", c.Name, c.Kind) + } + } + + return nil +} + +// ValidateValue validates that the value conforms to the field's kind and nullability. +// Unlike Kind.ValidateValue, it also checks that the value conforms to the EnumType +// if the field is an EnumKind. +func (c Field) ValidateValue(value interface{}, typeSet TypeSet) error { + if value == nil { + if !c.Nullable { + return fmt.Errorf("field %q cannot be null", c.Name) + } + return nil + } + err := c.Kind.ValidateValueType(value) + if err != nil { + return fmt.Errorf("invalid value for field %q: %v", c.Name, err) //nolint:errorlint // false positive due to using go1.12 + } + + switch c.Kind { + case EnumKind: + enumType, ok := typeSet.LookupEnumType(c.ReferencedType) + if !ok { + return fmt.Errorf("enum field %q references unknown type %q", c.Name, c.ReferencedType) + } + err := enumType.ValidateValue(value.(string)) + if err != nil { + return fmt.Errorf("invalid value for enum field %q: %v", c.Name, err) //nolint:errorlint // false positive due to using go1.12 + } + default: + } + + return nil +} diff --git a/schema/field_test.go b/schema/field_test.go new file mode 100644 index 000000000000..1fa6413b790c --- /dev/null +++ b/schema/field_test.go @@ -0,0 +1,231 @@ +package schema + +import ( + "encoding/json" + "reflect" + "strings" + "testing" +) + +func TestField_Validate(t *testing.T) { + tests := []struct { + name string + field Field + errContains string + }{ + { + name: "valid field", + field: Field{ + Name: "field1", + Kind: StringKind, + }, + errContains: "", + }, + { + name: "empty name", + field: Field{ + Name: "", + Kind: StringKind, + }, + errContains: "name cannot be empty, might be missing the named key codec", + }, + { + name: "invalid kind", + field: Field{ + Name: "field1", + Kind: InvalidKind, + }, + errContains: "invalid field kind", + }, + { + name: "missing enum type", + field: Field{ + Name: "field1", + Kind: EnumKind, + }, + errContains: `enum field "field1" must have a referenced type`, + }, + { + name: "enum definition with non-EnumKind", + field: Field{ + Name: "field1", + Kind: StringKind, + ReferencedType: "enum", + }, + errContains: `field "field1" with kind "string" cannot have a referenced type`, + }, + { + name: "valid enum", + field: Field{ + Name: "field1", + Kind: EnumKind, + ReferencedType: "enum", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.field.Validate(testEnumSchema) + if tt.errContains == "" { + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + } else { + if err == nil { + t.Errorf("expected error, got nil") + } else if !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("expected error contains: %s, got: %v", tt.errContains, err) + } + } + }) + } +} + +func TestField_ValidateValue(t *testing.T) { + tests := []struct { + name string + field Field + value interface{} + errContains string + }{ + { + name: "valid field", + field: Field{ + Name: "field1", + Kind: StringKind, + }, + value: "value", + errContains: "", + }, + { + name: "null non-nullable field", + field: Field{ + Name: "field1", + Kind: StringKind, + Nullable: false, + }, + value: nil, + errContains: "cannot be null", + }, + { + name: "null nullable field", + field: Field{ + Name: "field1", + Kind: StringKind, + Nullable: true, + }, + value: nil, + errContains: "", + }, + { + name: "invalid value", + field: Field{ + Name: "field1", + Kind: StringKind, + }, + value: 1, + errContains: "invalid value for field \"field1\"", + }, + { + name: "valid enum", + field: Field{ + Name: "field1", + Kind: EnumKind, + ReferencedType: "enum", + }, + value: "a", + errContains: "", + }, + { + name: "invalid enum", + field: Field{ + Name: "field1", + Kind: EnumKind, + ReferencedType: "enum", + }, + value: "c", + errContains: "not a valid enum value", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.field.ValidateValue(tt.value, testEnumSchema) + if tt.errContains == "" { + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + } else { + if err == nil { + t.Errorf("expected error, got nil") + } else if !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("expected error contains: %s, got: %v", tt.errContains, err) + } + } + }) + } +} + +func TestFieldJSON(t *testing.T) { + tt := []struct { + field Field + json string + expectErr bool + }{ + { + field: Field{ + Name: "field1", + Kind: StringKind, + }, + json: `{"name":"field1","kind":"string"}`, + }, + { + field: Field{ + Name: "field1", + Kind: Int32Kind, + Nullable: true, + }, + json: `{"name":"field1","kind":"int32","nullable":true}`, + }, + { + field: Field{ + Name: "field1", + Kind: EnumKind, + ReferencedType: "enum", + }, + json: `{"name":"field1","kind":"enum","referenced_type":"enum"}`, + }, + } + + for _, tc := range tt { + t.Run(tc.json, func(t *testing.T) { + b, err := json.Marshal(tc.field) + if tc.expectErr { + if err == nil { + t.Fatalf("expected error, got nil") + } + } else { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(b) != tc.json { + t.Fatalf("expected %s, got %s", tc.json, string(b)) + } + var field Field + err = json.Unmarshal(b, &field) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(field, tc.field) { + t.Fatalf("expected %v, got %v", tc.field, field) + } + } + }) + } +} + +var testEnumSchema = MustCompileModuleSchema(EnumType{ + Name: "enum", + Values: []EnumValueDefinition{{Name: "a", Value: 1}, {Name: "b", Value: 2}}, +}) diff --git a/schema/state_object.go b/schema/state_object.go new file mode 100644 index 000000000000..3367d8337b7f --- /dev/null +++ b/schema/state_object.go @@ -0,0 +1,106 @@ +package schema + +import "fmt" + +// StateObjectType describes an object type a module schema. +type StateObjectType struct { + // Name is the name of the object type. It must be unique within the module schema amongst all object and enum + // types and conform to the NameFormat regular expression. + Name string `json:"name"` + + // KeyFields is a list of fields that make up the primary key of the object. + // It can be empty, in which case, indexers should assume that this object is + // a singleton and only has one value. Field names must be unique within the + // object between both key and value fields. + // Key fields CANNOT be nullable and Float32Kind, Float64Kind, JSONKind, StructKind, + // OneOfKind, RepeatedKind, ListKind or ObjectKind + // are NOT ALLOWED. + // It is an INCOMPATIBLE change to add, remove or change fields in the key as this + // changes the underlying primary key of the object. + KeyFields []Field `json:"key_fields,omitempty"` + + // ValueFields is a list of fields that are not part of the primary key of the object. + // It can be empty in the case where all fields are part of the primary key. + // Field names must be unique within the object between both key and value fields. + // ObjectKind fields are not allowed. + // It is a COMPATIBLE change to add new value fields to an object type because + // this does not affect the primary key of the object. + // Existing value fields should not be removed or modified. + ValueFields []Field `json:"value_fields,omitempty"` + + // RetainDeletions is a flag that indicates whether the indexer should retain + // deleted rows in the database and flag them as deleted rather than actually + // deleting the row. For many types of data in state, the data is deleted even + // though it is still valid in order to save space. Indexers will want to have + // the option of retaining such data and distinguishing from other "true" deletions. + RetainDeletions bool `json:"retain_deletions,omitempty"` +} + +// TypeName implements the Type interface. +func (o StateObjectType) TypeName() string { + return o.Name +} + +func (StateObjectType) isType() {} + +// Validate validates the object type. +func (o StateObjectType) Validate(typeSet TypeSet) error { + if !ValidateName(o.Name) { + return fmt.Errorf("invalid object type name %q", o.Name) + } + + fieldNames := map[string]bool{} + + for _, field := range o.KeyFields { + if err := field.Validate(typeSet); err != nil { + return fmt.Errorf("invalid key field %q in type %q: %v", field.Name, o.Name, err) //nolint:errorlint // false positive due to using go1.12 + } + + if !field.Kind.ValidKeyKind() { + return fmt.Errorf("key field %q of kind %q uses an invalid key field kind", field.Name, field.Kind) + } + + if field.Nullable { + return fmt.Errorf("key field %q cannot be nullable", field.Name) + } + + if fieldNames[field.Name] { + return fmt.Errorf("duplicate key field name %q for stateObjectType: %s", field.Name, o.Name) + } + fieldNames[field.Name] = true + } + + for _, field := range o.ValueFields { + if err := field.Validate(typeSet); err != nil { + return fmt.Errorf("invalid value field %q: %v", field.Name, err) //nolint:errorlint // false positive due to using go1.12 + } + + if fieldNames[field.Name] { + return fmt.Errorf("duplicate field name %q for stateObjectType: %s", field.Name, o.Name) + } + fieldNames[field.Name] = true + } + + if len(o.KeyFields) == 0 && len(o.ValueFields) == 0 { + return fmt.Errorf("object type %q has no key or value fields", o.Name) + } + + return nil +} + +// ValidateObjectUpdate validates that the update conforms to the object type. +func (o StateObjectType) ValidateObjectUpdate(update StateObjectUpdate, typeSet TypeSet) error { + if o.Name != update.TypeName { + return fmt.Errorf("object type name %q does not match update type name %q", o.Name, update.TypeName) + } + + if err := ValidateObjectKey(o.KeyFields, update.Key, typeSet); err != nil { + return fmt.Errorf("invalid key for object type %q: %v", update.TypeName, err) //nolint:errorlint // false positive due to using go1.12 + } + + if update.Delete { + return nil + } + + return ValidateObjectValue(o.ValueFields, update.Value, typeSet) +} diff --git a/schema/state_object_test.go b/schema/state_object_test.go new file mode 100644 index 000000000000..03bb402adf46 --- /dev/null +++ b/schema/state_object_test.go @@ -0,0 +1,282 @@ +package schema + +import ( + "strings" + "testing" +) + +var object1Type = StateObjectType{ + Name: "object1", + KeyFields: []Field{ + { + Name: "field1", + Kind: StringKind, + }, + }, +} + +var object2Type = StateObjectType{ + KeyFields: []Field{ + { + Name: "field1", + Kind: StringKind, + }, + { + Name: "field2", + Kind: Int32Kind, + }, + }, +} + +var object3Type = StateObjectType{ + Name: "object3", + ValueFields: []Field{ + { + Name: "field1", + Kind: StringKind, + }, + { + Name: "field2", + Kind: Int32Kind, + }, + }, +} + +var object4Type = StateObjectType{ + Name: "object4", + KeyFields: []Field{ + { + Name: "field1", + Kind: Int32Kind, + }, + }, + ValueFields: []Field{ + { + Name: "field2", + Kind: StringKind, + }, + }, +} + +func TestObjectType_Validate(t *testing.T) { + tests := []struct { + name string + objectType StateObjectType + errContains string + }{ + { + name: "valid object type", + objectType: object1Type, + errContains: "", + }, + { + name: "empty object type name", + objectType: StateObjectType{ + Name: "", + KeyFields: []Field{ + { + Name: "field1", + Kind: StringKind, + }, + }, + }, + errContains: "invalid object type name", + }, + { + name: "invalid key field", + objectType: StateObjectType{ + Name: "object1", + KeyFields: []Field{ + { + Name: "", + Kind: StringKind, + }, + }, + }, + errContains: "field name cannot be empty, might be missing the named key codec", + }, + { + name: "invalid value field", + objectType: StateObjectType{ + Name: "object1", + ValueFields: []Field{ + { + Kind: StringKind, + }, + }, + }, + errContains: "field name cannot be empty, might be missing the named key codec", + }, + { + name: "no fields", + objectType: StateObjectType{Name: "object0"}, + errContains: "has no key or value fields", + }, + { + name: "duplicate field", + objectType: StateObjectType{ + Name: "object1", + KeyFields: []Field{ + { + Name: "field1", + Kind: StringKind, + }, + }, + ValueFields: []Field{ + { + Name: "field1", + Kind: StringKind, + }, + }, + }, + errContains: "duplicate field name", + }, + { + name: "duplicate field 22", + objectType: StateObjectType{ + Name: "object1", + KeyFields: []Field{ + { + Name: "field1", + Kind: StringKind, + }, + { + Name: "field1", + Kind: StringKind, + }, + }, + }, + errContains: "duplicate key field name \"field1\" for stateObjectType: object1", + }, + { + name: "nullable key field", + objectType: StateObjectType{ + Name: "objectNullKey", + KeyFields: []Field{ + { + Name: "field1", + Kind: StringKind, + Nullable: true, + }, + }, + }, + errContains: "key field \"field1\" cannot be nullable", + }, + { + name: "float32 key field", + objectType: StateObjectType{ + Name: "o1", + KeyFields: []Field{ + { + Name: "field1", + Kind: Float32Kind, + }, + }, + }, + errContains: "invalid key field kind", + }, + { + name: "float64 key field", + objectType: StateObjectType{ + Name: "o1", + KeyFields: []Field{ + { + Name: "field1", + Kind: Float64Kind, + }, + }, + }, + errContains: "invalid key field kind", + }, + { + name: "json key field", + objectType: StateObjectType{ + Name: "o1", + KeyFields: []Field{ + { + Name: "field1", + Kind: JSONKind, + }, + }, + }, + errContains: "invalid key field kind", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.objectType.Validate(EmptyTypeSet()) + if tt.errContains == "" { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } else { + if err == nil || !strings.Contains(err.Error(), tt.errContains) { + t.Fatalf("expected error to contain %q, got: %v", tt.errContains, err) + } + } + }) + } +} + +func TestObjectType_ValidateObjectUpdate(t *testing.T) { + tests := []struct { + name string + objectType StateObjectType + object StateObjectUpdate + errContains string + }{ + { + name: "wrong name", + objectType: object1Type, + object: StateObjectUpdate{ + TypeName: "object2", + Key: "hello", + }, + errContains: "does not match update type name", + }, + { + name: "invalid value", + objectType: object1Type, + object: StateObjectUpdate{ + TypeName: "object1", + Key: 123, + }, + errContains: "invalid value", + }, + { + name: "valid update", + objectType: object4Type, + object: StateObjectUpdate{ + TypeName: "object4", + Key: int32(123), + Value: "hello", + }, + }, + { + name: "valid deletion", + objectType: object4Type, + object: StateObjectUpdate{ + TypeName: "object4", + Key: int32(123), + Value: "ignored!", + Delete: true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.objectType.ValidateObjectUpdate(tt.object, EmptyTypeSet()) + if tt.errContains == "" { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } else { + if err == nil || !strings.Contains(err.Error(), tt.errContains) { + t.Fatalf("expected error to contain %q, got: %v", tt.errContains, err) + } + } + }) + } +} diff --git a/server/v2/cometbft/go.mod b/server/v2/cometbft/go.mod index b400359f0490..58b8538cca58 100644 --- a/server/v2/cometbft/go.mod +++ b/server/v2/cometbft/go.mod @@ -1,12 +1,23 @@ module cosmossdk.io/server/v2/cometbft -go 1.23.1 +go 1.23.2 replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241108140525-43e28b43ad7a // main +======= + cosmossdk.io/api => ../../../api + cosmossdk.io/collections => ../../../collections + cosmossdk.io/core/testing => ../../../core/testing + cosmossdk.io/server/v2 => ../ + cosmossdk.io/server/v2/appmanager => ../appmanager + cosmossdk.io/server/v2/stf => ../stf + cosmossdk.io/store => ../../../store + cosmossdk.io/store/v2 => ../../../store/v2 +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../../../x/bank cosmossdk.io/x/consensus => ../../../x/consensus cosmossdk.io/x/staking => ../../../x/staking diff --git a/server/v2/cometbft/go.sum b/server/v2/cometbft/go.sum index 02e7e5b56cff..09cd4384a7ba 100644 --- a/server/v2/cometbft/go.sum +++ b/server/v2/cometbft/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20241108153815-606544c7be7e h1:F+ScucYxwrrDJU8guJXQXpGhdpziYSbxW6HMP2wCNxs= diff --git a/simapp/go.mod b/simapp/go.mod index eb5ee84f6228..0c37d6da45bf 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/simapp -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main @@ -252,8 +252,13 @@ replace ( // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20241127063259-f296a5005ce8 // main cosmossdk.io/client/v2 => ../client/v2 +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/collections => ../collections + cosmossdk.io/store => ../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/tools/confix => ../tools/confix cosmossdk.io/x/accounts => ../x/accounts cosmossdk.io/x/accounts/defaults/base => ../x/accounts/defaults/base @@ -280,6 +285,8 @@ replace ( // Below are the long-lived replace of the SimApp replace ( + cosmossdk.io/indexer/postgres => ../indexer/postgres + cosmossdk.io/schema => ../schema // use cosmos fork of keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 // Simapp always use the latest version of the cosmos-sdk diff --git a/simapp/go.sum b/simapp/go.sum index 8f367b584cf0..c73c32195914 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -210,6 +210,7 @@ cosmossdk.io/log v1.5.0 h1:dVdzPJW9kMrnAYyMf1duqacoidB9uZIl+7c6z0mnq0g= cosmossdk.io/log v1.5.0/go.mod h1:Tr46PUJjiUthlwQ+hxYtUtPn4D/oCZXAkYevBeh5+FI= cosmossdk.io/math v1.4.0 h1:XbgExXFnXmF/CccPPEto40gOO7FpWu9yWNAZPN3nkNQ= cosmossdk.io/math v1.4.0/go.mod h1:O5PkD4apz2jZs4zqFdTr16e1dcaQCc5z6lkEnrrppuk= +<<<<<<< HEAD cosmossdk.io/schema v0.3.0/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ= cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac h1:3joNZZWZ3k7fMsrBDL1ktuQ2xQwYLZOaDhkruadDFmc= cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ= @@ -217,6 +218,8 @@ cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 h1:p69ThBO2dqCHKA3G cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3/go.mod h1:uaysXSQHWUykekFPvS1JqQ7HM58Zuqcby1DNlLQPWSg= cosmossdk.io/x/tx v1.0.0-alpha.2 h1:UW80FMm7B0fiAMsrfe5+HabSJ3XBg+tQa6/GK9prqWk= cosmossdk.io/x/tx v1.0.0-alpha.2/go.mod h1:r4yTKSJ7ZCCR95YbBfY3nfvbgNw6m9F6f25efWYYQWo= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= diff --git a/simapp/v2/go.mod b/simapp/v2/go.mod index 9ca2a618de0f..fe9b01827766 100644 --- a/simapp/v2/go.mod +++ b/simapp/v2/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/simapp/v2 -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main diff --git a/store/cachekv/branch_bench_test.go b/store/cachekv/branch_bench_test.go new file mode 100644 index 000000000000..0001d416585c --- /dev/null +++ b/store/cachekv/branch_bench_test.go @@ -0,0 +1,119 @@ +package cachekv_test + +import ( + "encoding/binary" + "fmt" + "testing" + + coretesting "cosmossdk.io/core/testing" + "cosmossdk.io/store/cachekv" + "cosmossdk.io/store/dbadapter" +) + +var ( + stackSizes = []int{1, 10, 100} + elemsInStack = 10 +) + +func Benchmark_CacheStack_Set(b *testing.B) { + for _, stackSize := range stackSizes { + b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { + bs := makeBranchStack(b, stackSize) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + bs.Set([]byte{0}, []byte{0}) + } + }) + } +} + +// Gets the same key from the branch store. +func Benchmark_Get(b *testing.B) { + for _, stackSize := range stackSizes { + b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { + bs := makeBranchStack(b, stackSize) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sink = bs.Get([]byte{0}) + } + }) + } + if sink == nil { + b.Fatal("prevent compiler optimization") + } + sink = nil +} + +// Gets always different keys. +func Benchmark_GetSparse(b *testing.B) { + for _, stackSize := range stackSizes { + b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { + bs := makeBranchStack(b, stackSize) + keys := func() [][]byte { + var keys [][]byte + for i := 0; i < b.N; i++ { + keys = append(keys, numToBytes(i)) + } + return keys + }() + b.ResetTimer() + b.ReportAllocs() + for _, key := range keys { + sink = bs.Get(key) + } + }) + } + if sink == nil { + b.Fatal("Benchmark did not run") + } + sink = nil +} + +var keySink, valueSink any + +func Benchmark_Iterate(b *testing.B) { + for _, stackSize := range stackSizes { + b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { + bs := makeBranchStack(b, stackSize) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + iter := bs.Iterator(nil, nil) + for iter.Valid() { + keySink = iter.Key() + valueSink = iter.Value() + iter.Next() + } + _ = iter.Close() + } + }) + } + + if keySink == nil || valueSink == nil { + b.Fatal("Benchmark did not run") + } + keySink = nil + valueSink = nil +} + +// makeBranchStack creates a branch stack of the given size and initializes it with unique key-value pairs. +func makeBranchStack(_ *testing.B, stackSize int) *cachekv.Store { + parent := dbadapter.Store{DB: coretesting.NewMemDB()} + branch := cachekv.NewStore(parent) + for i := 1; i < stackSize; i++ { + branch = cachekv.NewStore(branch) + for j := 0; j < elemsInStack; j++ { + // create unique keys by including the branch index. + key := append(numToBytes(i), numToBytes(j)...) + value := []byte{byte(j)} + branch.Set(key, value) + } + } + return branch +} + +func numToBytes[T ~int](n T) []byte { + return binary.BigEndian.AppendUint64(nil, uint64(n)) +} diff --git a/tests/go.mod b/tests/go.mod index c1d5861505b5..b8adfb15a8ce 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -1,6 +1,6 @@ module github.com/cosmos/cosmos-sdk/tests -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main diff --git a/tests/integration/v2/auth/app_test.go b/tests/integration/v2/auth/app_test.go new file mode 100644 index 000000000000..5c3c5a7e95cf --- /dev/null +++ b/tests/integration/v2/auth/app_test.go @@ -0,0 +1,133 @@ +package auth + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/core/router" + "cosmossdk.io/core/transaction" + "cosmossdk.io/depinject" + "cosmossdk.io/log" + "cosmossdk.io/runtime/v2" + "cosmossdk.io/x/accounts" + basedepinject "cosmossdk.io/x/accounts/defaults/base/depinject" + accountsv1 "cosmossdk.io/x/accounts/v1" + _ "cosmossdk.io/x/bank" // import as blank for app wiring + bankkeeper "cosmossdk.io/x/bank/keeper" + banktypes "cosmossdk.io/x/bank/types" + _ "cosmossdk.io/x/consensus" // import as blank for app wiring + _ "cosmossdk.io/x/staking" // import as blank for app wirings + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/tests/integration/v2" + "github.com/cosmos/cosmos-sdk/testutil/configurator" + _ "github.com/cosmos/cosmos-sdk/x/auth" // import as blank for app wiring + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import as blank for app wiring`` + _ "github.com/cosmos/cosmos-sdk/x/auth/vesting" // import as blank for app wiring + _ "github.com/cosmos/cosmos-sdk/x/genutil" // import as blank for app wiring +) + +type suite struct { + app *integration.App + + cdc codec.Codec + ctx context.Context + + authKeeper authkeeper.AccountKeeper + accountsKeeper accounts.Keeper + bankKeeper bankkeeper.Keeper +} + +func (s suite) mustAddr(address []byte) string { + str, _ := s.authKeeper.AddressCodec().BytesToString(address) + return str +} + +func createTestSuite(t *testing.T) *suite { + t.Helper() + res := suite{} + + moduleConfigs := []configurator.ModuleOption{ + configurator.AccountsModule(), + configurator.AuthModule(), + configurator.BankModule(), + configurator.VestingModule(), + configurator.StakingModule(), + configurator.TxModule(), + configurator.ValidateModule(), + configurator.ConsensusModule(), + configurator.GenutilModule(), + } + + var err error + startupCfg := integration.DefaultStartUpConfig(t) + + msgRouterService := integration.NewRouterService() + res.registerMsgRouterService(msgRouterService) + + var routerFactory runtime.RouterServiceFactory = func(_ []byte) router.Service { + return msgRouterService + } + + queryRouterService := integration.NewRouterService() + res.registerQueryRouterService(queryRouterService) + + serviceBuilder := runtime.NewRouterBuilder(routerFactory, queryRouterService) + + startupCfg.BranchService = &integration.BranchService{} + startupCfg.RouterServiceBuilder = serviceBuilder + + res.app, err = integration.NewApp( + depinject.Configs(configurator.NewAppV2Config(moduleConfigs...), depinject.Provide( + // inject desired account types: + basedepinject.ProvideAccount, + + // provide base account options + basedepinject.ProvideSecp256K1PubKey, + + // provide extra accounts + ProvideMockRetroCompatAccountValid, + ProvideMockRetroCompatAccountNoInfo, + ProvideMockRetroCompatAccountNoImplement, + ), depinject.Supply(log.NewNopLogger())), + startupCfg, + &res.bankKeeper, &res.accountsKeeper, &res.authKeeper) + require.NoError(t, err) + + res.ctx = res.app.StateLatestContext(t) + + return &res +} + +func (s *suite) registerMsgRouterService(router *integration.RouterService) { + // register custom router service + bankSendHandler := func(ctx context.Context, req transaction.Msg) (transaction.Msg, error) { + msg, ok := req.(*banktypes.MsgSend) + if !ok { + return nil, integration.ErrInvalidMsgType + } + msgServer := bankkeeper.NewMsgServerImpl(s.bankKeeper) + resp, err := msgServer.Send(ctx, msg) + return resp, err + } + + router.RegisterHandler(bankSendHandler, "cosmos.bank.v1beta1.MsgSend") +} + +func (s *suite) registerQueryRouterService(router *integration.RouterService) { + // register custom router service + queryHandler := func(ctx context.Context, msg transaction.Msg) (transaction.Msg, error) { + req, ok := msg.(*accountsv1.AccountNumberRequest) + if !ok { + return nil, integration.ErrInvalidMsgType + } + qs := accounts.NewQueryServer(s.accountsKeeper) + resp, err := qs.AccountNumber(ctx, req) + return resp, err + } + + router.RegisterHandler(queryHandler, "cosmos.accounts.v1.AccountNumberRequest") +} diff --git a/tests/integration/v2/services.go b/tests/integration/v2/services.go index f69aa70574a0..2a76cc21bf53 100644 --- a/tests/integration/v2/services.go +++ b/tests/integration/v2/services.go @@ -117,3 +117,78 @@ func (e *eventManager) Emit(event transaction.Msg) error { func (e *eventManager) EmitKV(eventType string, attrs ...event.Attribute) error { return nil } +<<<<<<< HEAD +======= + +var _ branch.Service = &BranchService{} + +// custom branch service for integration tests +type BranchService struct{} + +func (bs *BranchService) Execute(ctx context.Context, f func(ctx context.Context) error) error { + _, ok := ctx.Value(contextKey).(*integrationContext) + if !ok { + return errors.New("context is not an integration context") + } + + return f(ctx) +} + +func (bs *BranchService) ExecuteWithGasLimit( + ctx context.Context, + gasLimit uint64, + f func(ctx context.Context) error, +) (gasUsed uint64, err error) { + iCtx, ok := ctx.Value(contextKey).(*integrationContext) + if !ok { + return 0, errors.New("context is not an integration context") + } + + // execute branched, with predefined gas limit. + err = f(ctx) + // restore original context + gasUsed = iCtx.gasMeter.Limit() - iCtx.gasMeter.Remaining() + _ = iCtx.gasMeter.Consume(gasUsed, "execute-with-gas-limit") + + return gasUsed, err +} + +// msgTypeURL returns the TypeURL of a proto message. +func msgTypeURL(msg gogoproto.Message) string { + return gogoproto.MessageName(msg) +} + +type routerHandler func(context.Context, transaction.Msg) (transaction.Msg, error) + +var _ router.Service = &RouterService{} + +// custom router service for integration tests +type RouterService struct { + handlers map[string]routerHandler +} + +func NewRouterService() *RouterService { + return &RouterService{ + handlers: make(map[string]routerHandler), + } +} + +func (rs *RouterService) RegisterHandler(handler routerHandler, typeUrl string) { + rs.handlers[typeUrl] = handler +} + +func (rs RouterService) CanInvoke(ctx context.Context, typeUrl string) error { + if rs.handlers[typeUrl] == nil { + return fmt.Errorf("no handler for typeURL %s", typeUrl) + } + return nil +} + +func (rs RouterService) Invoke(ctx context.Context, req transaction.Msg) (transaction.Msg, error) { + typeUrl := msgTypeURL(req) + if rs.handlers[typeUrl] == nil { + return nil, fmt.Errorf("no handler for typeURL %s", typeUrl) + } + return rs.handlers[typeUrl](ctx, req) +} +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) diff --git a/types/collections.go b/types/collections.go index 47d975e89bf3..cb7490af137b 100644 --- a/types/collections.go +++ b/types/collections.go @@ -43,7 +43,7 @@ var ( // Deprecated: exists only for state compatibility reasons, should not // be used for new storage keys using time. Please use the time KeyCodec // provided in the collections package. - TimeKey collcodec.KeyCodec[time.Time] = timeKeyCodec{} + TimeKey collcodec.NameableKeyCodec[time.Time] = timeKeyCodec{} // LEUint64Key is a collections KeyCodec that encodes uint64 using little endian. // NOTE: it MUST NOT be used by other modules, distribution relies on this only for @@ -55,7 +55,7 @@ var ( // Deprecated: exists only for state compatibility reasons, should not be // used for new storage keys using []byte. Please use the BytesKey provided // in the collections package. - LengthPrefixedBytesKey collcodec.KeyCodec[[]byte] = lengthPrefixedBytesKey{collections.BytesKey} + LengthPrefixedBytesKey collcodec.NameableKeyCodec[[]byte] = lengthPrefixedBytesKey{collections.BytesKey} ) const ( @@ -138,6 +138,10 @@ func (g lengthPrefixedAddressKey[T]) Size(key T) int { return g.SizeNonTerminal( func (g lengthPrefixedAddressKey[T]) KeyType() string { return "index_key/" + g.KeyCodec.KeyType() } +func (g lengthPrefixedAddressKey[T]) WithName(name string) collcodec.KeyCodec[T] { + return collcodec.NamedKeyCodec[T]{KeyCodec: g, Name: name} +} + // Deprecated: LengthPrefixedAddressKey implements an SDK backwards compatible indexing key encoder // for addresses. // The status quo in the SDK is that address keys are length prefixed even when they're the @@ -147,7 +151,7 @@ func (g lengthPrefixedAddressKey[T]) KeyType() string { return "index_key/" + g. // byte to the string, then when you know when the string part finishes, it's logical that the // part which remains is the address key. In the SDK instead we prepend to the address key its // length too. -func LengthPrefixedAddressKey[T addressUnion](keyCodec collcodec.KeyCodec[T]) collcodec.KeyCodec[T] { +func LengthPrefixedAddressKey[T addressUnion](keyCodec collcodec.KeyCodec[T]) collcodec.NameableKeyCodec[T] { return lengthPrefixedAddressKey[T]{ keyCodec, } @@ -175,6 +179,10 @@ func (g lengthPrefixedBytesKey) KeyType() string { return "index_key/" + g.KeyCodec.KeyType() } +func (g lengthPrefixedBytesKey) WithName(name string) collcodec.KeyCodec[[]byte] { + return collcodec.NamedKeyCodec[[]byte]{KeyCodec: g, Name: name} +} + // Collection Codecs type intValueCodec struct{} @@ -328,6 +336,10 @@ func (t timeKeyCodec) DecodeNonTerminal(buffer []byte) (int, time.Time, error) { } func (t timeKeyCodec) SizeNonTerminal(key time.Time) int { return t.Size(key) } +func (t timeKeyCodec) WithName(name string) collcodec.KeyCodec[time.Time] { + return collcodec.NamedKeyCodec[time.Time]{KeyCodec: t, Name: name} +} + type leUint64Key struct{} func (l leUint64Key) Encode(buffer []byte, key uint64) (int, error) { diff --git a/x/accounts/defaults/base/go.mod b/x/accounts/defaults/base/go.mod index 5dec500e8300..06e3d70ae204 100644 --- a/x/accounts/defaults/base/go.mod +++ b/x/accounts/defaults/base/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/accounts/defaults/base -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 diff --git a/x/accounts/defaults/lockup/go.mod b/x/accounts/defaults/lockup/go.mod index 7cb243bba870..f3049203f454 100644 --- a/x/accounts/defaults/lockup/go.mod +++ b/x/accounts/defaults/lockup/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/accounts/defaults/lockup -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee // main diff --git a/x/accounts/defaults/multisig/go.mod b/x/accounts/defaults/multisig/go.mod index 4b62d35a4202..085a0f2a5d39 100644 --- a/x/accounts/defaults/multisig/go.mod +++ b/x/accounts/defaults/multisig/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/accounts/defaults/multisig -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee // main diff --git a/x/accounts/go.mod b/x/accounts/go.mod index 2a3c178f1e48..d9e52c45bb1e 100644 --- a/x/accounts/go.mod +++ b/x/accounts/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/accounts -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main diff --git a/x/auth/module.go b/x/auth/module.go index b959fcde5408..45e845a02527 100644 --- a/x/auth/module.go +++ b/x/auth/module.go @@ -8,10 +8,12 @@ import ( gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc" + "cosmossdk.io/collections" "cosmossdk.io/core/appmodule" appmodulev2 "cosmossdk.io/core/appmodule/v2" "cosmossdk.io/core/registry" "cosmossdk.io/core/transaction" + "cosmossdk.io/schema" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -198,3 +200,9 @@ func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.accountKeeper.Schema) } + +// ModuleCodec implements schema.HasModuleCodec. +// It allows the indexer to decode the module's KVPairUpdate. +func (am AppModule) ModuleCodec() (schema.ModuleCodec, error) { + return am.accountKeeper.Schema.ModuleCodec(collections.IndexingOptions{}) +} diff --git a/x/authz/go.mod b/x/authz/go.mod index ecc0c84c3a20..101e36d5f78e 100644 --- a/x/authz/go.mod +++ b/x/authz/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/authz -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main diff --git a/x/bank/go.mod b/x/bank/go.mod index bf4982b1de52..ce8d6407fc07 100644 --- a/x/bank/go.mod +++ b/x/bank/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/bank -go 1.23.1 +go 1.23.2 require ( cosmossdk.io/api v0.8.0 // main diff --git a/x/bank/keeper/view.go b/x/bank/keeper/view.go index bd43ee7be09c..7ea846986a0e 100644 --- a/x/bank/keeper/view.go +++ b/x/bank/keeper/view.go @@ -75,10 +75,18 @@ func NewBaseViewKeeper(env appmodule.Environment, cdc codec.BinaryCodec, ak type Environment: env, cdc: cdc, ak: ak, +<<<<<<< HEAD Supply: collections.NewMap(sb, types.SupplyKey, "supply", collections.StringKey.WithName("supply"), sdk.IntValue), DenomMetadata: collections.NewMap(sb, types.DenomMetadataPrefix, "denom_metadata", collections.StringKey.WithName("denom_metadata"), codec.CollValue[types.Metadata](cdc)), SendEnabled: collections.NewMap(sb, types.SendEnabledPrefix, "send_enabled", collections.StringKey.WithName("send_enabled"), codec.BoolValue), // NOTE: we use a bool value which uses protobuf to retain state backwards compat Balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.NamedPairKeyCodec("address", sdk.AccAddressKey, "balances", collections.StringKey), types.BalanceValueCodec, newBalancesIndexes(sb)), +======= + addrCdc: ak.AddressCodec(), + Supply: collections.NewMap(sb, types.SupplyKey, "supply", collections.StringKey.WithName("denom"), sdk.IntValue), + DenomMetadata: collections.NewMap(sb, types.DenomMetadataPrefix, "denom_metadata", collections.StringKey.WithName("denom"), codec.CollValue[types.Metadata](cdc)), + SendEnabled: collections.NewMap(sb, types.SendEnabledPrefix, "send_enabled", collections.StringKey.WithName("denom"), codec.BoolValue), // NOTE: we use a bool value which uses protobuf to retain state backwards compat + Balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.NamedPairKeyCodec("address", sdk.AccAddressKey, "denom", collections.StringKey), types.BalanceValueCodec, newBalancesIndexes(sb)), +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)), } diff --git a/x/bank/module.go b/x/bank/module.go index f387903c5886..cc459f23b340 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -39,6 +39,7 @@ var ( _ appmodule.HasMigrations = AppModule{} _ appmodule.HasGenesis = AppModule{} _ appmodule.HasRegisterInterfaces = AppModule{} + _ schema.HasModuleCodec = AppModule{} ) // AppModule implements an application module for the bank module. diff --git a/x/circuit/go.mod b/x/circuit/go.mod index 190d8ceca821..d6deb841420d 100644 --- a/x/circuit/go.mod +++ b/x/circuit/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/circuit -go 1.23.1 +go 1.23.2 require ( cosmossdk.io/api v0.8.0 // main @@ -9,8 +9,14 @@ require ( cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 // main cosmossdk.io/depinject v1.1.0 cosmossdk.io/errors v1.0.1 +<<<<<<< HEAD cosmossdk.io/store v1.1.1-0.20240909133312-50288938d1b6 // main github.com/cosmos/cosmos-sdk v0.52.0 +======= + cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 + cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc + github.com/cosmos/cosmos-sdk v0.53.0 +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) github.com/cosmos/gogoproto v1.7.0 github.com/golang/protobuf v1.5.4 github.com/grpc-ecosystem/grpc-gateway v1.16.0 @@ -24,7 +30,6 @@ require ( buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1 // indirect cosmossdk.io/log v1.5.0 // indirect cosmossdk.io/math v1.4.0 // indirect - cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 // indirect cosmossdk.io/x/bank v0.0.0-20240226161501-23359a0b6d91 // indirect cosmossdk.io/x/staking v0.0.0-00010101000000-000000000000 // indirect cosmossdk.io/x/tx v1.0.0-alpha.2 // indirect; main @@ -179,10 +184,17 @@ replace github.com/cosmos/cosmos-sdk => ../../. // TODO remove post spinning out all modules replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/schema => ../../schema + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/staking => ../staking ) diff --git a/x/circuit/go.sum b/x/circuit/go.sum index a8d314e5cfb9..3ee79e9def2f 100644 --- a/x/circuit/go.sum +++ b/x/circuit/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= @@ -20,12 +23,15 @@ cosmossdk.io/log v1.5.0 h1:dVdzPJW9kMrnAYyMf1duqacoidB9uZIl+7c6z0mnq0g= cosmossdk.io/log v1.5.0/go.mod h1:Tr46PUJjiUthlwQ+hxYtUtPn4D/oCZXAkYevBeh5+FI= cosmossdk.io/math v1.4.0 h1:XbgExXFnXmF/CccPPEto40gOO7FpWu9yWNAZPN3nkNQ= cosmossdk.io/math v1.4.0/go.mod h1:O5PkD4apz2jZs4zqFdTr16e1dcaQCc5z6lkEnrrppuk= +<<<<<<< HEAD cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 h1:DmOoS/1PeY6Ih0hAVlJ69kLMUrLV+TCbfICrZtB1vdU= cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ= cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 h1:p69ThBO2dqCHKA3GcVoGr18Q4oH04asl1TsDPloxtSI= cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3/go.mod h1:uaysXSQHWUykekFPvS1JqQ7HM58Zuqcby1DNlLQPWSg= cosmossdk.io/x/tx v1.0.0-alpha.2 h1:UW80FMm7B0fiAMsrfe5+HabSJ3XBg+tQa6/GK9prqWk= cosmossdk.io/x/tx v1.0.0-alpha.2/go.mod h1:r4yTKSJ7ZCCR95YbBfY3nfvbgNw6m9F6f25efWYYQWo= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= diff --git a/x/circuit/module.go b/x/circuit/module.go index 91645aaee6cc..da69333716bf 100644 --- a/x/circuit/module.go +++ b/x/circuit/module.go @@ -8,10 +8,12 @@ import ( gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc" + "cosmossdk.io/collections" "cosmossdk.io/core/appmodule" appmodulev2 "cosmossdk.io/core/appmodule/v2" "cosmossdk.io/core/registry" "cosmossdk.io/core/transaction" + "cosmossdk.io/schema" "cosmossdk.io/x/circuit/ante" "cosmossdk.io/x/circuit/keeper" "cosmossdk.io/x/circuit/types" @@ -117,3 +119,9 @@ func (am AppModule) TxValidator(ctx context.Context, tx transaction.Tx) error { validator := ante.NewCircuitBreakerDecorator(&am.keeper) return validator.ValidateTx(ctx, tx) } + +// ModuleCodec implements schema.HasModuleCodec. +// It allows the indexer to decode the module's KVPairUpdate. +func (am AppModule) ModuleCodec() (schema.ModuleCodec, error) { + return am.keeper.Schema.ModuleCodec(collections.IndexingOptions{}) +} diff --git a/x/consensus/go.mod b/x/consensus/go.mod index 26fe241de32f..6c8563a9dd45 100644 --- a/x/consensus/go.mod +++ b/x/consensus/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/consensus -go 1.23.1 +go 1.23.2 require ( cosmossdk.io/api v0.8.0 // main @@ -8,7 +8,12 @@ require ( cosmossdk.io/core v1.0.0-alpha.6 // main cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 // main cosmossdk.io/depinject v1.1.0 +<<<<<<< HEAD cosmossdk.io/store v1.1.1-0.20240909133312-50288938d1b6 +======= + cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 + cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) github.com/cometbft/cometbft v1.0.0-rc1.0.20240908111210-ab0be101882f github.com/cometbft/cometbft/api v1.0.0-rc.1 github.com/cosmos/cosmos-proto v1.0.0-beta.5 @@ -27,7 +32,6 @@ require ( cosmossdk.io/errors v1.0.1 // indirect cosmossdk.io/log v1.5.0 // indirect cosmossdk.io/math v1.4.0 // indirect - cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 // indirect cosmossdk.io/x/bank v0.0.0-20240226161501-23359a0b6d91 // indirect cosmossdk.io/x/staking v0.0.0-00010101000000-000000000000 // indirect cosmossdk.io/x/tx v1.0.0-alpha.2 // indirect; main @@ -177,10 +181,16 @@ require ( replace github.com/cosmos/cosmos-sdk => ../../. replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/staking => ../staking ) diff --git a/x/consensus/go.sum b/x/consensus/go.sum index 264abe6d0f73..93c04b975390 100644 --- a/x/consensus/go.sum +++ b/x/consensus/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= diff --git a/x/consensus/keeper/keeper.go b/x/consensus/keeper/keeper.go index 28f94270a38e..53858707f9fe 100644 --- a/x/consensus/keeper/keeper.go +++ b/x/consensus/keeper/keeper.go @@ -26,6 +26,7 @@ type Keeper struct { authority string ParamsStore collections.Item[cmtproto.ConsensusParams] + Schema collections.Schema } var _ exported.ConsensusParamSetter = Keeper{}.ParamsStore @@ -33,11 +34,19 @@ var _ exported.ConsensusParamSetter = Keeper{}.ParamsStore // NewKeeper creates a new Keeper instance. func NewKeeper(cdc codec.BinaryCodec, env appmodule.Environment, authority string) Keeper { sb := collections.NewSchemaBuilder(env.KVStoreService) - return Keeper{ + k := Keeper{ Environment: env, authority: authority, ParamsStore: collections.NewItem(sb, collections.NewPrefix("Consensus"), "params", codec.CollValue[cmtproto.ConsensusParams](cdc)), } + + var err error + k.Schema, err = sb.Build() + if err != nil { + panic(fmt.Sprintf("failed to build schema: %v", err)) + } + + return k } // GetAuthority returns the authority address for the consensus module. diff --git a/x/consensus/module.go b/x/consensus/module.go index 298a80bad428..7a21619980be 100644 --- a/x/consensus/module.go +++ b/x/consensus/module.go @@ -7,8 +7,10 @@ import ( gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc" + "cosmossdk.io/collections" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/registry" + "cosmossdk.io/schema" "cosmossdk.io/x/consensus/keeper" "cosmossdk.io/x/consensus/types" @@ -96,3 +98,9 @@ func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { // ConsensusVersion implements HasConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } + +// ModuleCodec implements schema.HasModuleCodec. +// It allows the indexer to decode the module's KVPairUpdate. +func (am AppModule) ModuleCodec() (schema.ModuleCodec, error) { + return am.keeper.Schema.ModuleCodec(collections.IndexingOptions{}) +} diff --git a/x/distribution/go.mod b/x/distribution/go.mod index 880d4501a6fc..adce58e3a11f 100644 --- a/x/distribution/go.mod +++ b/x/distribution/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/distribution -go 1.23.1 +go 1.23.2 require ( cosmossdk.io/api v0.8.0 // main @@ -10,7 +10,12 @@ require ( cosmossdk.io/depinject v1.1.0 cosmossdk.io/errors v1.0.1 cosmossdk.io/math v1.4.0 +<<<<<<< HEAD cosmossdk.io/store v1.1.1-0.20240909133312-50288938d1b6 +======= + cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 + cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/staking v0.0.0-00010101000000-000000000000 github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/cosmos-sdk v0.52.0 @@ -30,7 +35,6 @@ require ( buf.build/gen/go/cometbft/cometbft/protocolbuffers/go v1.35.2-20241120201313-68e42a58b301.1 // indirect buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1 // indirect cosmossdk.io/log v1.5.0 // indirect - cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 // indirect cosmossdk.io/x/bank v0.0.0-20240226161501-23359a0b6d91 // indirect cosmossdk.io/x/tx v1.0.0-alpha.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect diff --git a/x/distribution/keeper/keeper.go b/x/distribution/keeper/keeper.go index 6ee76320f982..a289898b8ed3 100644 --- a/x/distribution/keeper/keeper.go +++ b/x/distribution/keeper/keeper.go @@ -101,7 +101,12 @@ func NewKeeper( sb, types.DelegatorStartingInfoPrefix, "delegators_starting_info", - collections.PairKeyCodec(sdk.ValAddressKey, sdk.LengthPrefixedAddressKey(sdk.AccAddressKey)), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + collections.NamedPairKeyCodec( + "validator_address", + sdk.ValAddressKey, + "delegator_address", + sdk.LengthPrefixedAddressKey(sdk.AccAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + ), codec.CollValue[types.DelegatorStartingInfo](cdc), ), ValidatorsAccumulatedCommission: collections.NewMap( @@ -123,14 +128,26 @@ func NewKeeper( sb, types.ValidatorHistoricalRewardsPrefix, "validator_historical_rewards", - collections.PairKeyCodec(sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), sdk.LEUint64Key), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + collections.NamedPairKeyCodec( + "validator_address", + sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + "period", + sdk.LEUint64Key, + ), codec.CollValue[types.ValidatorHistoricalRewards](cdc), ), ValidatorSlashEvents: collections.NewMap( sb, types.ValidatorSlashEventPrefix, "validator_slash_events", - collections.TripleKeyCodec(sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), collections.Uint64Key, collections.Uint64Key), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + collections.NamedTripleKeyCodec( + "validator_address", + sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + "height", + collections.Uint64Key, + "period", + collections.Uint64Key, + ), codec.CollValue[types.ValidatorSlashEvent](cdc), ), } diff --git a/x/distribution/module.go b/x/distribution/module.go index eb7de978c6d1..ae1ae85e82c9 100644 --- a/x/distribution/module.go +++ b/x/distribution/module.go @@ -9,8 +9,10 @@ import ( "github.com/spf13/cobra" "google.golang.org/grpc" + "cosmossdk.io/collections" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/registry" + "cosmossdk.io/schema" "cosmossdk.io/x/distribution/client/cli" "cosmossdk.io/x/distribution/keeper" "cosmossdk.io/x/distribution/simulation" @@ -182,3 +184,9 @@ func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Re reg.Add(weights.Get("msg_withdraw_delegation_reward", 50), simulation.MsgWithdrawDelegatorRewardFactory(am.keeper, am.stakingKeeper)) reg.Add(weights.Get("msg_withdraw_validator_commission", 50), simulation.MsgWithdrawValidatorCommissionFactory(am.keeper, am.stakingKeeper)) } + +// ModuleCodec implements schema.HasModuleCodec. +// It allows the indexer to decode the module's KVPairUpdate. +func (am AppModule) ModuleCodec() (schema.ModuleCodec, error) { + return am.keeper.Schema.ModuleCodec(collections.IndexingOptions{}) +} diff --git a/x/epochs/go.mod b/x/epochs/go.mod index d327150d9d51..17632fc123cf 100644 --- a/x/epochs/go.mod +++ b/x/epochs/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/epochs -go 1.23.1 +go 1.23.2 require ( cosmossdk.io/api v0.8.0 // main @@ -20,7 +20,7 @@ require ( google.golang.org/grpc v1.68.0 ) -require cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 // indirect +require cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 require ( buf.build/gen/go/cometbft/cometbft/protocolbuffers/go v1.35.2-20241120201313-68e42a58b301.1 // indirect @@ -179,10 +179,17 @@ replace github.com/cosmos/cosmos-sdk => ../../. // TODO remove post spinning out all modules replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/schema => ../../schema + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/staking => ../staking ) diff --git a/x/epochs/go.sum b/x/epochs/go.sum index 264abe6d0f73..0dd910f706bb 100644 --- a/x/epochs/go.sum +++ b/x/epochs/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= @@ -20,12 +23,15 @@ cosmossdk.io/log v1.5.0 h1:dVdzPJW9kMrnAYyMf1duqacoidB9uZIl+7c6z0mnq0g= cosmossdk.io/log v1.5.0/go.mod h1:Tr46PUJjiUthlwQ+hxYtUtPn4D/oCZXAkYevBeh5+FI= cosmossdk.io/math v1.4.0 h1:XbgExXFnXmF/CccPPEto40gOO7FpWu9yWNAZPN3nkNQ= cosmossdk.io/math v1.4.0/go.mod h1:O5PkD4apz2jZs4zqFdTr16e1dcaQCc5z6lkEnrrppuk= +<<<<<<< HEAD cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 h1:DmOoS/1PeY6Ih0hAVlJ69kLMUrLV+TCbfICrZtB1vdU= cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ= cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 h1:p69ThBO2dqCHKA3GcVoGr18Q4oH04asl1TsDPloxtSI= cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3/go.mod h1:uaysXSQHWUykekFPvS1JqQ7HM58Zuqcby1DNlLQPWSg= cosmossdk.io/x/tx v1.0.0-alpha.2 h1:UW80FMm7B0fiAMsrfe5+HabSJ3XBg+tQa6/GK9prqWk= cosmossdk.io/x/tx v1.0.0-alpha.2/go.mod h1:r4yTKSJ7ZCCR95YbBfY3nfvbgNw6m9F6f25efWYYQWo= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= diff --git a/x/epochs/module.go b/x/epochs/module.go index 2fba7c0de141..aeb7947b4ec1 100644 --- a/x/epochs/module.go +++ b/x/epochs/module.go @@ -8,8 +8,10 @@ import ( gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc" + "cosmossdk.io/collections" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/registry" + "cosmossdk.io/schema" "cosmossdk.io/x/epochs/keeper" "cosmossdk.io/x/epochs/simulation" "cosmossdk.io/x/epochs/types" @@ -125,3 +127,9 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.Schema) } + +// ModuleCodec implements schema.HasModuleCodec. +// It allows the indexer to decode the module's KVPairUpdate. +func (am AppModule) ModuleCodec() (schema.ModuleCodec, error) { + return am.keeper.Schema.ModuleCodec(collections.IndexingOptions{}) +} diff --git a/x/evidence/go.mod b/x/evidence/go.mod index 0401ba1bfe35..5133a67ffaa9 100644 --- a/x/evidence/go.mod +++ b/x/evidence/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/evidence -go 1.23.1 +go 1.23.2 require ( cosmossdk.io/api v0.8.0 // main @@ -179,10 +179,16 @@ replace github.com/cosmos/cosmos-sdk => ../../. // TODO remove post spinning out all modules replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/staking => ../staking ) diff --git a/x/evidence/go.sum b/x/evidence/go.sum index a8d314e5cfb9..6d1728738c1c 100644 --- a/x/evidence/go.sum +++ b/x/evidence/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= diff --git a/x/feegrant/go.mod b/x/feegrant/go.mod index ca1682c9531b..b395b9b4df1a 100644 --- a/x/feegrant/go.mod +++ b/x/feegrant/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/feegrant -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main diff --git a/x/gov/go.mod b/x/gov/go.mod index bfafc6307c91..e0ea677dce2c 100644 --- a/x/gov/go.mod +++ b/x/gov/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/gov -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main diff --git a/x/group/go.mod b/x/group/go.mod index 5239e6d5529a..733579241126 100644 --- a/x/group/go.mod +++ b/x/group/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/group -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main diff --git a/x/mint/go.mod b/x/mint/go.mod index 3e2739c73885..7c5f4f8d6e21 100644 --- a/x/mint/go.mod +++ b/x/mint/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/mint -go 1.23.1 +go 1.23.2 require ( cosmossdk.io/api v0.8.0 // main @@ -29,7 +29,7 @@ require ( require ( buf.build/gen/go/cometbft/cometbft/protocolbuffers/go v1.35.2-20241120201313-68e42a58b301.1 // indirect buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1 // indirect - cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 // indirect + cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 cosmossdk.io/x/bank v0.0.0-20240226161501-23359a0b6d91 // indirect cosmossdk.io/x/staking v0.0.0-00010101000000-000000000000 // indirect cosmossdk.io/x/tx v1.0.0-alpha.2 // indirect; main @@ -180,10 +180,17 @@ replace github.com/cosmos/cosmos-sdk => ../../. // TODO remove post spinning out all modules replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/schema => ../../schema + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/consensus => ../consensus cosmossdk.io/x/epochs => ../epochs diff --git a/x/mint/go.sum b/x/mint/go.sum index a8d314e5cfb9..3ee79e9def2f 100644 --- a/x/mint/go.sum +++ b/x/mint/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= @@ -20,12 +23,15 @@ cosmossdk.io/log v1.5.0 h1:dVdzPJW9kMrnAYyMf1duqacoidB9uZIl+7c6z0mnq0g= cosmossdk.io/log v1.5.0/go.mod h1:Tr46PUJjiUthlwQ+hxYtUtPn4D/oCZXAkYevBeh5+FI= cosmossdk.io/math v1.4.0 h1:XbgExXFnXmF/CccPPEto40gOO7FpWu9yWNAZPN3nkNQ= cosmossdk.io/math v1.4.0/go.mod h1:O5PkD4apz2jZs4zqFdTr16e1dcaQCc5z6lkEnrrppuk= +<<<<<<< HEAD cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 h1:DmOoS/1PeY6Ih0hAVlJ69kLMUrLV+TCbfICrZtB1vdU= cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ= cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 h1:p69ThBO2dqCHKA3GcVoGr18Q4oH04asl1TsDPloxtSI= cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3/go.mod h1:uaysXSQHWUykekFPvS1JqQ7HM58Zuqcby1DNlLQPWSg= cosmossdk.io/x/tx v1.0.0-alpha.2 h1:UW80FMm7B0fiAMsrfe5+HabSJ3XBg+tQa6/GK9prqWk= cosmossdk.io/x/tx v1.0.0-alpha.2/go.mod h1:r4yTKSJ7ZCCR95YbBfY3nfvbgNw6m9F6f25efWYYQWo= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= diff --git a/x/mint/module.go b/x/mint/module.go index 826dba01f6fc..6a11db35c5d8 100644 --- a/x/mint/module.go +++ b/x/mint/module.go @@ -8,8 +8,10 @@ import ( gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc" + "cosmossdk.io/collections" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/registry" + "cosmossdk.io/schema" "cosmossdk.io/x/mint/keeper" "cosmossdk.io/x/mint/simulation" "cosmossdk.io/x/mint/types" @@ -178,3 +180,9 @@ func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.Schema) } + +// ModuleCodec implements schema.HasModuleCodec. +// It allows the indexer to decode the module's KVPairUpdate. +func (am AppModule) ModuleCodec() (schema.ModuleCodec, error) { + return am.keeper.Schema.ModuleCodec(collections.IndexingOptions{}) +} diff --git a/x/nft/go.mod b/x/nft/go.mod index c340f951d442..fa19ec9a4db4 100644 --- a/x/nft/go.mod +++ b/x/nft/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/nft -go 1.23.1 +go 1.23.2 require ( cosmossdk.io/api v0.8.0 // main @@ -179,10 +179,16 @@ replace github.com/cosmos/cosmos-sdk => ../../. // TODO remove post spinning out all modules replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/staking => ../staking ) diff --git a/x/nft/go.sum b/x/nft/go.sum index a8d314e5cfb9..6d1728738c1c 100644 --- a/x/nft/go.sum +++ b/x/nft/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= diff --git a/x/params/go.mod b/x/params/go.mod index 7bad950dcb54..1a9f558a8c9e 100644 --- a/x/params/go.mod +++ b/x/params/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/params -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main @@ -171,10 +171,16 @@ replace github.com/cosmos/cosmos-sdk => ../.. // TODO remove post spinning out all modules replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/distribution => ../distribution cosmossdk.io/x/gov => ../gov diff --git a/x/params/go.sum b/x/params/go.sum index 3fb3598e3644..b982079693a8 100644 --- a/x/params/go.sum +++ b/x/params/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= diff --git a/x/protocolpool/go.mod b/x/protocolpool/go.mod index 1960d80aa0cb..5e15c67f752a 100644 --- a/x/protocolpool/go.mod +++ b/x/protocolpool/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/protocolpool -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main @@ -179,10 +179,16 @@ replace github.com/cosmos/cosmos-sdk => ../../. // TODO remove post spinning out all modules replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/staking => ../staking ) diff --git a/x/protocolpool/go.sum b/x/protocolpool/go.sum index a8d314e5cfb9..6d1728738c1c 100644 --- a/x/protocolpool/go.sum +++ b/x/protocolpool/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= diff --git a/x/slashing/go.mod b/x/slashing/go.mod index 9db8f8d8ed04..dfed8a0a9eca 100644 --- a/x/slashing/go.mod +++ b/x/slashing/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/slashing -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 // main @@ -177,10 +177,16 @@ replace github.com/cosmos/cosmos-sdk => ../../. // TODO remove post spinning out all modules replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/staking => ../staking ) diff --git a/x/slashing/go.sum b/x/slashing/go.sum index dcc8bcaf3b87..4cc10950ca87 100644 --- a/x/slashing/go.sum +++ b/x/slashing/go.sum @@ -4,10 +4,13 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88e buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.35.2-20240130113600-88ef6483f90f.1/go.mod h1:17Ax38yd8pg56din4ecwSDBRCSX0qLcif5Cdf8ayto4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= diff --git a/x/staking/go.mod b/x/staking/go.mod index 9b348060451d..7c981941a091 100644 --- a/x/staking/go.mod +++ b/x/staking/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/staking -go 1.23.1 +go 1.23.2 require ( cosmossdk.io/api v0.8.0 // main @@ -165,6 +165,22 @@ require ( ) require ( +<<<<<<< HEAD +======= + buf.build/gen/go/cometbft/cometbft/protocolbuffers/go v1.35.2-20241120201313-68e42a58b301.1 // indirect + cosmossdk.io/log v1.5.0 + github.com/dgraph-io/badger/v4 v4.3.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/flatbuffers v2.0.8+incompatible // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/supranational/blst v0.3.13 // indirect + go.opencensus.io v0.24.0 // indirect +) + +require cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 + +require ( +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) github.com/bytedance/sonic v1.12.4 // indirect github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect diff --git a/x/staking/keeper/keeper.go b/x/staking/keeper/keeper.go index b85bab1bbc8b..876c9ae97df3 100644 --- a/x/staking/keeper/keeper.go +++ b/x/staking/keeper/keeper.go @@ -159,8 +159,10 @@ func NewKeeper( LastTotalPower: collections.NewItem(sb, types.LastTotalPowerKey, "last_total_power", sdk.IntValue), Delegations: collections.NewMap( sb, types.DelegationKey, "delegations", - collections.PairKeyCodec( + collections.NamedPairKeyCodec( + "delegator", sdk.LengthPrefixedAddressKey(sdk.AccAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + "validator_address_key", sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility ), codec.CollValue[types.Delegation](cdc), @@ -168,67 +170,94 @@ func NewKeeper( DelegationsByValidator: collections.NewMap( sb, types.DelegationByValIndexKey, "delegations_by_validator", - collections.PairKeyCodec(sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), sdk.AccAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + collections.NamedPairKeyCodec( + "validator_address", + sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + "delegator", + sdk.AccAddressKey, + ), collections.BytesValue, ), UnbondingID: collections.NewSequence(sb, types.UnbondingIDKey, "unbonding_id"), ValidatorByConsensusAddress: collections.NewMap( sb, types.ValidatorsByConsAddrKey, "validator_by_cons_addr", - sdk.LengthPrefixedAddressKey(sdk.ConsAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + sdk.LengthPrefixedAddressKey(sdk.ConsAddressKey).WithName("cons_address"), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility collcodec.KeyToValueCodec(sdk.ValAddressKey), ), - UnbondingType: collections.NewMap(sb, types.UnbondingTypeKey, "unbonding_type", collections.Uint64Key, collections.Uint64Value), + UnbondingType: collections.NewMap(sb, types.UnbondingTypeKey, "unbonding_type", collections.Uint64Key.WithName("unbonding_id"), collections.Uint64Value), // key format is: 52 | lengthPrefixedBytes(AccAddr) | lengthPrefixedBytes(SrcValAddr) | lengthPrefixedBytes(DstValAddr) Redelegations: collections.NewMap( sb, types.RedelegationKey, "redelegations", - collections.TripleKeyCodec( + collections.NamedTripleKeyCodec( + "delegator", collections.BytesKey, + "src_validator", collections.BytesKey, + "dst_validator", sdk.LengthPrefixedBytesKey, // sdk.LengthPrefixedBytesKey is needed to retain state compatibility ), codec.CollValue[types.Redelegation](cdc), ), - UnbondingIndex: collections.NewMap(sb, types.UnbondingIndexKey, "unbonding_index", collections.Uint64Key, collections.BytesValue), + UnbondingIndex: collections.NewMap(sb, types.UnbondingIndexKey, "unbonding_index", collections.Uint64Key.WithName("index"), collections.BytesValue.WithName("ubd_key")), UnbondingDelegationByValIndex: collections.NewMap( sb, types.UnbondingDelegationByValIndexKey, "unbonding_delegation_by_val_index", - collections.PairKeyCodec(sdk.LengthPrefixedBytesKey, sdk.LengthPrefixedBytesKey), // sdk.LengthPrefixedBytesKey is needed to retain state compatibility + collections.NamedPairKeyCodec( + "validator_address", + sdk.LengthPrefixedBytesKey, + "delegator", + sdk.LengthPrefixedBytesKey, + ), // sdk.LengthPrefixedBytesKey is needed to retain state compatibility collections.BytesValue, ), - UnbondingQueue: collections.NewMap(sb, types.UnbondingQueueKey, "unbonidng_queue", sdk.TimeKey, codec.CollValue[types.DVPairs](cdc)), + UnbondingQueue: collections.NewMap(sb, types.UnbondingQueueKey, "unbonidng_queue", sdk.TimeKey.WithName("unbonding_time"), codec.CollValue[types.DVPairs](cdc)), // key format is: 53 | lengthPrefixedBytes(SrcValAddr) | lengthPrefixedBytes(AccAddr) | lengthPrefixedBytes(DstValAddr) RedelegationsByValSrc: collections.NewMap( sb, types.RedelegationByValSrcIndexKey, "redelegations_by_val_src", - collections.TripleKeyCodec( + collections.NamedTripleKeyCodec( + "src_validator", collections.BytesKey, + "delegator", collections.BytesKey, + "dst_validator", sdk.LengthPrefixedBytesKey, // sdk.LengthPrefixedBytesKey is needed to retain state compatibility ), collections.BytesValue, ), // key format is: 17 | lengthPrefixedBytes(valAddr) | power - LastValidatorPower: collections.NewMap(sb, types.LastValidatorPowerKey, "last_validator_power", sdk.LengthPrefixedBytesKey, codec.CollValue[gogotypes.Int64Value](cdc)), // sdk.LengthPrefixedBytesKey is needed to retain state compatibility + LastValidatorPower: collections.NewMap(sb, types.LastValidatorPowerKey, "last_validator_power", sdk.LengthPrefixedBytesKey.WithName("validator_address"), codec.CollValue[gogotypes.Int64Value](cdc)), // sdk.LengthPrefixedBytesKey is needed to retain state compatibility // key format is: 54 | lengthPrefixedBytes(DstValAddr) | lengthPrefixedBytes(AccAddr) | lengthPrefixedBytes(SrcValAddr) RedelegationsByValDst: collections.NewMap( sb, types.RedelegationByValDstIndexKey, "redelegations_by_val_dst", - collections.TripleKeyCodec( + collections.NamedTripleKeyCodec( + "dst_validator", collections.BytesKey, + "delegator", collections.BytesKey, + "src_validator", sdk.LengthPrefixedBytesKey, // sdk.LengthPrefixedBytesKey is needed to retain state compatibility ), collections.BytesValue, ), - RedelegationQueue: collections.NewMap(sb, types.RedelegationQueueKey, "redelegation_queue", sdk.TimeKey, codec.CollValue[types.DVVTriplets](cdc)), - Validators: collections.NewMap(sb, types.ValidatorsKey, "validators", sdk.LengthPrefixedBytesKey, codec.CollValue[types.Validator](cdc)), // sdk.LengthPrefixedBytesKey is needed to retain state compatibility + RedelegationQueue: collections.NewMap(sb, types.RedelegationQueueKey, "redelegation_queue", sdk.TimeKey.WithName("completion_time"), codec.CollValue[types.DVVTriplets](cdc)), + Validators: collections.NewMap( + sb, + types.ValidatorsKey, + "validators", + sdk.LengthPrefixedBytesKey.WithName("validator_address"), // sdk.LengthPrefixedBytesKey is needed to retain state compatibility + codec.CollValue[types.Validator](cdc), + ), UnbondingDelegations: collections.NewMap( sb, types.UnbondingDelegationKey, "unbonding_delegation", - collections.PairKeyCodec( + collections.NamedPairKeyCodec( + "delegator", collections.BytesKey, + "validator", sdk.LengthPrefixedBytesKey, // sdk.LengthPrefixedBytesKey is needed to retain state compatibility ), codec.CollValue[types.UnbondingDelegation](cdc), @@ -238,9 +267,12 @@ func NewKeeper( ValidatorQueue: collections.NewMap( sb, types.ValidatorQueueKey, "validator_queue", - collections.TripleKeyCodec( + collections.NamedTripleKeyCodec( + "ts_length", collections.Uint64Key, + "timestamp", sdk.TimeKey, + "height", collections.Uint64Key, ), codec.CollValue[types.ValAddresses](cdc), @@ -252,7 +284,11 @@ func NewKeeper( ValidatorConsensusKeyRotationRecordIndexKey: collections.NewKeySet( sb, types.ValidatorConsensusKeyRotationRecordIndexKey, "cons_pub_rotation_index", - collections.PairKeyCodec(collections.BytesKey, sdk.TimeKey), + collections.NamedPairKeyCodec( + "validator_address", + collections.BytesKey, + "time", + sdk.TimeKey), ), // key format is: 103 | time @@ -285,7 +321,11 @@ func NewKeeper( sb, types.ValidatorConsPubKeyRotationHistoryKey, "cons_pub_rotation_history", - collections.PairKeyCodec(collections.BytesKey, collections.Uint64Key), + collections.NamedPairKeyCodec( + "validator_address", + collections.BytesKey, + "height_key", + collections.Uint64Key), codec.CollValue[types.ConsPubKeyRotationHistory](cdc), NewRotationHistoryIndexes(sb), ), diff --git a/x/staking/module.go b/x/staking/module.go index 52b5313ed15b..041906ad1c03 100644 --- a/x/staking/module.go +++ b/x/staking/module.go @@ -9,9 +9,11 @@ import ( "github.com/spf13/cobra" "google.golang.org/grpc" + "cosmossdk.io/collections" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/registry" "cosmossdk.io/depinject" + "cosmossdk.io/schema" "cosmossdk.io/x/staking/client/cli" "cosmossdk.io/x/staking/keeper" "cosmossdk.io/x/staking/types" @@ -37,6 +39,7 @@ var ( _ appmodule.AppModule = AppModule{} _ appmodule.HasMigrations = AppModule{} _ appmodule.HasRegisterInterfaces = AppModule{} + _ schema.HasModuleCodec = AppModule{} _ depinject.OnePerModuleType = AppModule{} ) @@ -163,3 +166,9 @@ func (AppModule) ConsensusVersion() uint64 { return consensusVersion } func (am AppModule) EndBlock(ctx context.Context) ([]appmodule.ValidatorUpdate, error) { return am.keeper.EndBlocker(ctx) } + +// ModuleCodec implements schema.HasModuleCodec. +// It allows the indexer to decode the module's KVPairUpdate. +func (am AppModule) ModuleCodec() (schema.ModuleCodec, error) { + return am.keeper.Schema.ModuleCodec(collections.IndexingOptions{}) +} diff --git a/x/upgrade/go.mod b/x/upgrade/go.mod index c0fb50b639fc..bd8bd33d688a 100644 --- a/x/upgrade/go.mod +++ b/x/upgrade/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/x/upgrade -go 1.23.1 +go 1.23.3 require ( cosmossdk.io/api v0.8.0 @@ -210,10 +210,16 @@ require ( replace github.com/cosmos/cosmos-sdk => ../../. replace ( +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20241106093505-9611c5a0e6e3 // main +======= + cosmossdk.io/api => ../../api + cosmossdk.io/collections => ../../collections + cosmossdk.io/store => ../../store +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/x/bank => ../bank cosmossdk.io/x/gov => ../gov cosmossdk.io/x/staking => ../staking diff --git a/x/upgrade/go.sum b/x/upgrade/go.sum index 2bd3b8aad105..d6a9006ba31b 100644 --- a/x/upgrade/go.sum +++ b/x/upgrade/go.sum @@ -192,10 +192,13 @@ 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= +<<<<<<< HEAD cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf h1:CttA/mEIxGm4E7vwrjUpju7/Iespns08d9bOza70cIc= cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf/go.mod h1:YMfx2ATpgITsoydD3hIBa8IkDHtyXp/14rmG0d3sEew= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee h1:OLqvi9ekfShobmdgr4Q/8pu+LjzYJSrNl9tiadPg2xY= cosmossdk.io/collections v0.4.1-0.20241107084833-00f3065e70ee/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= +======= +>>>>>>> bd76b47e1 (feat(indexer): add to modules and implement proto fields (#22544)) cosmossdk.io/core v1.0.0-alpha.6 h1:5ukC4JcQKmemLQXcAgu/QoOvJI50hpBkIIg4ZT2EN8E= cosmossdk.io/core v1.0.0-alpha.6/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY=