From d8196920aadc64faa85e11d2eda91cfe757d7af6 Mon Sep 17 00:00:00 2001 From: Marie Gauthier Date: Wed, 27 Oct 2021 14:49:37 +0200 Subject: [PATCH] feat: Add Table-Store (aka ORM) package - `AutoUInt64Table` and `PrimaryKeyTable` (#10415) ## Description ref: #9237, #9156 This PR is a follow-up of #9751. It introduces 2 new public table types: `AutoUInt64Table` and `PrimaryKeyTable` based on the parent `table` struct introduced by #9751. Upcoming work will include: - multi-key secondary indexes - iterator and pagination - import/export genesis --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable) --- go.sum | 2 - testutil/testdata/table.go | 4 + testutil/testdata/testdata.pb.go | 146 +++++++++--- testutil/testdata/testdata.proto | 2 + types/errors/errors.go | 2 +- x/group/go.mod | 12 +- x/group/go.sum | 28 ++- x/group/internal/orm/auto_uint64.go | 75 +++++++ x/group/internal/orm/example_test.go | 35 +++ x/group/internal/orm/generators_test.go | 18 ++ x/group/internal/orm/key_codec.go | 75 +++++++ x/group/internal/orm/key_codec_test.go | 46 ++++ x/group/internal/orm/orm_scenario_test.go | 207 ++++++++++++++++++ x/group/internal/orm/primary_key.go | 113 ++++++++++ .../internal/orm/primary_key_property_test.go | 188 ++++++++++++++++ x/group/internal/orm/primary_key_test.go | 67 ++++++ x/group/internal/orm/spec/01_table.md | 38 ++++ x/group/internal/orm/spec/README.md | 27 +-- x/group/internal/orm/testsupport.go | 70 ++++++ x/group/internal/orm/types.go | 2 +- 20 files changed, 1089 insertions(+), 68 deletions(-) create mode 100644 x/group/internal/orm/auto_uint64.go create mode 100644 x/group/internal/orm/example_test.go create mode 100644 x/group/internal/orm/generators_test.go create mode 100644 x/group/internal/orm/key_codec.go create mode 100644 x/group/internal/orm/key_codec_test.go create mode 100644 x/group/internal/orm/orm_scenario_test.go create mode 100644 x/group/internal/orm/primary_key.go create mode 100644 x/group/internal/orm/primary_key_property_test.go create mode 100644 x/group/internal/orm/primary_key_test.go create mode 100644 x/group/internal/orm/spec/01_table.md diff --git a/go.sum b/go.sum index f0a7eae77169..40118db668ec 100644 --- a/go.sum +++ b/go.sum @@ -163,8 +163,6 @@ github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coinbase/rosetta-sdk-go v0.6.10 h1:rgHD/nHjxLh0lMEdfGDqpTtlvtSBwULqrrZ2qPdNaCM= -github.com/coinbase/rosetta-sdk-go v0.6.10/go.mod h1:J/JFMsfcePrjJZkwQFLh+hJErkAmdm9Iyy3D5Y0LfXo= github.com/coinbase/rosetta-sdk-go v0.7.0 h1:lmTO/JEpCvZgpbkOITL95rA80CPKb5CtMzLaqF2mCNg= github.com/coinbase/rosetta-sdk-go v0.7.0/go.mod h1:7nD3oBPIiHqhRprqvMgPoGxe/nyq3yftRmpsy29coWE= github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= diff --git a/testutil/testdata/table.go b/testutil/testdata/table.go index 576b911f3f0d..8faae88a451d 100644 --- a/testutil/testdata/table.go +++ b/testutil/testdata/table.go @@ -6,6 +6,10 @@ var ( ErrTest = errors.Register("table_testdata", 2, "test") ) +func (g TableModel) PrimaryKeyFields() []interface{} { + return []interface{}{g.Id} +} + func (g TableModel) ValidateBasic() error { if g.Name == "" { return errors.Wrap(ErrTest, "name") diff --git a/testutil/testdata/testdata.pb.go b/testutil/testdata/testdata.pb.go index 4894d5ec9fef..07942ea668c5 100644 --- a/testutil/testdata/testdata.pb.go +++ b/testutil/testdata/testdata.pb.go @@ -323,8 +323,10 @@ func (m *BadMultiSignature) GetMaliciousField() []byte { } type TableModel struct { - Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Number uint64 `protobuf:"varint,3,opt,name=number,proto3" json:"number,omitempty"` + Metadata []byte `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` } func (m *TableModel) Reset() { *m = TableModel{} } @@ -374,6 +376,20 @@ func (m *TableModel) GetName() string { return "" } +func (m *TableModel) GetNumber() uint64 { + if m != nil { + return m.Number + } + return 0 +} + +func (m *TableModel) GetMetadata() []byte { + if m != nil { + return m.Metadata + } + return nil +} + func init() { proto.RegisterType((*Dog)(nil), "testdata.Dog") proto.RegisterType((*Cat)(nil), "testdata.Cat") @@ -387,32 +403,34 @@ func init() { func init() { proto.RegisterFile("testdata.proto", fileDescriptor_40c4782d007dfce9) } var fileDescriptor_40c4782d007dfce9 = []byte{ - // 393 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xb1, 0x8e, 0xda, 0x40, - 0x14, 0x45, 0x19, 0x0c, 0x24, 0xbc, 0x58, 0x46, 0x19, 0x51, 0x38, 0x14, 0x0e, 0x72, 0x13, 0x8a, - 0x60, 0x47, 0x41, 0x69, 0xe8, 0x80, 0x28, 0xa1, 0xa1, 0x71, 0x52, 0xa5, 0x41, 0x63, 0x3c, 0xd8, - 0x23, 0xc6, 0x9e, 0x88, 0x19, 0x47, 0x90, 0xaf, 0xd8, 0x5f, 0xd8, 0xbf, 0xd9, 0x92, 0x72, 0xcb, - 0x15, 0xfc, 0xc8, 0xca, 0x63, 0xbc, 0x50, 0x6c, 0x41, 0xe5, 0x7b, 0xef, 0xf3, 0x3d, 0x7a, 0x96, - 0x1f, 0x58, 0x8a, 0x4a, 0x15, 0x11, 0x45, 0xbc, 0xbf, 0x5b, 0xa1, 0x04, 0x7e, 0x5b, 0xf9, 0x5e, - 0x37, 0x16, 0xb1, 0xd0, 0xa1, 0x5f, 0xa8, 0x72, 0xde, 0xfb, 0x10, 0x0b, 0x11, 0x73, 0xea, 0x6b, - 0x17, 0xe6, 0x6b, 0x9f, 0x64, 0xfb, 0x72, 0xe4, 0x0e, 0xc1, 0xf8, 0x2e, 0x62, 0x8c, 0xa1, 0x21, - 0xd9, 0x7f, 0x6a, 0xa3, 0x3e, 0x1a, 0xb4, 0x03, 0xad, 0x8b, 0x2c, 0x23, 0x29, 0xb5, 0xeb, 0x65, - 0x56, 0x68, 0xf7, 0x1b, 0x18, 0x33, 0xa2, 0xb0, 0x0d, 0x6f, 0x52, 0x91, 0xb1, 0x0d, 0xdd, 0x9e, - 0x1b, 0x95, 0xc5, 0x5d, 0x68, 0x72, 0xf6, 0x8f, 0x4a, 0xdd, 0x6a, 0x06, 0xa5, 0x71, 0x7f, 0x42, - 0x7b, 0x4e, 0xe4, 0x24, 0x63, 0x29, 0xe1, 0xf8, 0x33, 0xb4, 0x88, 0x56, 0xba, 0xfb, 0xee, 0x6b, - 0xd7, 0x2b, 0xd7, 0xf3, 0xaa, 0xf5, 0xbc, 0x49, 0xb6, 0x0f, 0xce, 0xef, 0x60, 0x13, 0xd0, 0x4e, - 0xc3, 0x8c, 0x00, 0xed, 0xdc, 0x19, 0x98, 0x73, 0x22, 0x2f, 0xac, 0x11, 0x40, 0x42, 0xe4, 0xf2, - 0x06, 0x5e, 0x3b, 0xa9, 0x4a, 0xee, 0x02, 0x3a, 0x25, 0xe4, 0xc2, 0x19, 0x83, 0x55, 0x70, 0x6e, - 0x64, 0x99, 0xc9, 0x55, 0xd7, 0x0d, 0xe1, 0xfd, 0x94, 0x44, 0x8b, 0x9c, 0x2b, 0xf6, 0x8b, 0xc5, - 0x19, 0x51, 0xf9, 0x96, 0x62, 0x07, 0x40, 0x56, 0x46, 0xda, 0xa8, 0x6f, 0x0c, 0xcc, 0xe0, 0x2a, - 0xc1, 0x9f, 0xa0, 0x93, 0x12, 0xce, 0x56, 0x4c, 0xe4, 0x72, 0xb9, 0x66, 0x94, 0x47, 0x76, 0xb3, - 0x8f, 0x06, 0x66, 0x60, 0xbd, 0xc4, 0x3f, 0x8a, 0x74, 0xdc, 0x38, 0xdc, 0x7f, 0x44, 0xee, 0x17, - 0x80, 0xdf, 0x24, 0xe4, 0x74, 0x21, 0x22, 0xca, 0xb1, 0x05, 0x75, 0x16, 0xe9, 0x0d, 0x1b, 0x41, - 0x9d, 0x45, 0xaf, 0xfd, 0xa9, 0xe9, 0xfc, 0xe1, 0xe8, 0xa0, 0xc3, 0xd1, 0x41, 0x4f, 0x47, 0x07, - 0xdd, 0x9d, 0x9c, 0xda, 0xe1, 0xe4, 0xd4, 0x1e, 0x4f, 0x4e, 0xed, 0x8f, 0x17, 0x33, 0x95, 0xe4, - 0xa1, 0xb7, 0x12, 0xa9, 0xbf, 0x12, 0x32, 0x15, 0xf2, 0xfc, 0x18, 0xca, 0x68, 0xe3, 0x17, 0xa7, - 0x94, 0x2b, 0xc6, 0xfd, 0xea, 0xa6, 0xc2, 0x96, 0xfe, 0xf6, 0xd1, 0x73, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x7a, 0x9a, 0x17, 0x6f, 0x76, 0x02, 0x00, 0x00, + // 419 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0xb1, 0x6e, 0xdb, 0x30, + 0x10, 0x35, 0x2d, 0xd9, 0x8d, 0xaf, 0x82, 0x82, 0x12, 0x46, 0xa1, 0x7a, 0x50, 0x0d, 0x2d, 0xf5, + 0xd0, 0x48, 0x40, 0x83, 0x2e, 0xd9, 0x92, 0x14, 0xad, 0x17, 0x2f, 0x6a, 0xa7, 0x2e, 0x01, 0x65, + 0x32, 0x12, 0x11, 0x52, 0x2c, 0x44, 0xaa, 0x48, 0xfa, 0x15, 0xfd, 0x85, 0xfe, 0x4d, 0x47, 0x8f, + 0x1d, 0x0b, 0xfb, 0x47, 0x0a, 0x51, 0x92, 0xed, 0xa1, 0x83, 0x27, 0xbe, 0xf7, 0xee, 0xde, 0xe3, + 0x81, 0x3c, 0xf0, 0x0d, 0xd3, 0x86, 0x12, 0x43, 0xe2, 0x6f, 0x95, 0x32, 0x0a, 0x9f, 0xf5, 0x7c, + 0x36, 0xcd, 0x55, 0xae, 0xac, 0x98, 0x34, 0xa8, 0xad, 0xcf, 0x5e, 0xe5, 0x4a, 0xe5, 0x82, 0x25, + 0x96, 0x65, 0xf5, 0x7d, 0x42, 0xca, 0xa7, 0xb6, 0x14, 0x5d, 0x80, 0xf3, 0x41, 0xe5, 0x18, 0x83, + 0xab, 0xf9, 0x0f, 0x16, 0xa0, 0x39, 0x5a, 0x4c, 0x52, 0x8b, 0x1b, 0xad, 0x24, 0x92, 0x05, 0xc3, + 0x56, 0x6b, 0x70, 0xf4, 0x1e, 0x9c, 0x5b, 0x62, 0x70, 0x00, 0xcf, 0xa4, 0x2a, 0xf9, 0x03, 0xab, + 0x3a, 0x47, 0x4f, 0xf1, 0x14, 0x46, 0x82, 0x7f, 0x67, 0xda, 0xba, 0x46, 0x69, 0x4b, 0xa2, 0x4f, + 0x30, 0x59, 0x12, 0x7d, 0x5d, 0x72, 0x49, 0x04, 0x7e, 0x0b, 0x63, 0x62, 0x91, 0xf5, 0x3e, 0x7f, + 0x37, 0x8d, 0xdb, 0xf1, 0xe2, 0x7e, 0xbc, 0xf8, 0xba, 0x7c, 0x4a, 0xbb, 0x1e, 0xec, 0x01, 0x7a, + 0xb4, 0x61, 0x4e, 0x8a, 0x1e, 0xa3, 0x5b, 0xf0, 0x96, 0x44, 0x1f, 0xb2, 0x2e, 0x01, 0x0a, 0xa2, + 0xef, 0x4e, 0xc8, 0x9b, 0x14, 0xbd, 0x29, 0x5a, 0xc1, 0x79, 0x1b, 0x72, 0xc8, 0xb9, 0x02, 0xbf, + 0xc9, 0x39, 0x31, 0xcb, 0x2b, 0x8e, 0xbc, 0x51, 0x06, 0x2f, 0x6e, 0x08, 0x5d, 0xd5, 0xc2, 0xf0, + 0xcf, 0x3c, 0x2f, 0x89, 0xa9, 0x2b, 0x86, 0x43, 0x00, 0xdd, 0x13, 0x1d, 0xa0, 0xb9, 0xb3, 0xf0, + 0xd2, 0x23, 0x05, 0xbf, 0x81, 0x73, 0x49, 0x04, 0x5f, 0x73, 0x55, 0xeb, 0xbb, 0x7b, 0xce, 0x04, + 0x0d, 0x46, 0x73, 0xb4, 0xf0, 0x52, 0x7f, 0x2f, 0x7f, 0x6c, 0xd4, 0x2b, 0x77, 0xf3, 0xeb, 0x35, + 0x8a, 0x28, 0xc0, 0x17, 0x92, 0x09, 0xb6, 0x52, 0x94, 0x09, 0xec, 0xc3, 0x90, 0x53, 0x3b, 0xa1, + 0x9b, 0x0e, 0x39, 0xfd, 0xdf, 0x4f, 0xe1, 0x97, 0x30, 0x2e, 0x6b, 0x99, 0xb1, 0x2a, 0x70, 0x6c, + 0x5f, 0xc7, 0xf0, 0x0c, 0xce, 0x24, 0x33, 0xa4, 0xd9, 0x96, 0xc0, 0xb5, 0x37, 0xee, 0xf9, 0xcd, + 0xf2, 0xf7, 0x36, 0x44, 0x9b, 0x6d, 0x88, 0xfe, 0x6e, 0x43, 0xf4, 0x73, 0x17, 0x0e, 0x36, 0xbb, + 0x70, 0xf0, 0x67, 0x17, 0x0e, 0xbe, 0xc6, 0x39, 0x37, 0x45, 0x9d, 0xc5, 0x6b, 0x25, 0x93, 0xb5, + 0xd2, 0x52, 0xe9, 0xee, 0xb8, 0xd0, 0xf4, 0x21, 0x69, 0xd6, 0xaf, 0x36, 0x5c, 0x24, 0xfd, 0x1e, + 0x66, 0x63, 0xfb, 0x5e, 0x97, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x8f, 0xef, 0x9c, 0xaa, + 0x02, 0x00, 0x00, } func (m *Dog) Marshal() (dAtA []byte, err error) { @@ -660,6 +678,18 @@ func (m *TableModel) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Metadata) > 0 { + i -= len(m.Metadata) + copy(dAtA[i:], m.Metadata) + i = encodeVarintTestdata(dAtA, i, uint64(len(m.Metadata))) + i-- + dAtA[i] = 0x22 + } + if m.Number != 0 { + i = encodeVarintTestdata(dAtA, i, uint64(m.Number)) + i-- + dAtA[i] = 0x18 + } if len(m.Name) > 0 { i -= len(m.Name) copy(dAtA[i:], m.Name) @@ -796,6 +826,13 @@ func (m *TableModel) Size() (n int) { if l > 0 { n += 1 + l + sovTestdata(uint64(l)) } + if m.Number != 0 { + n += 1 + sovTestdata(uint64(m.Number)) + } + l = len(m.Metadata) + if l > 0 { + n += 1 + l + sovTestdata(uint64(l)) + } return n } @@ -1494,6 +1531,59 @@ func (m *TableModel) Unmarshal(dAtA []byte) error { } m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Number", wireType) + } + m.Number = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTestdata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Number |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTestdata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTestdata + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTestdata + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Metadata = append(m.Metadata[:0], dAtA[iNdEx:postIndex]...) + if m.Metadata == nil { + m.Metadata = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTestdata(dAtA[iNdEx:]) diff --git a/testutil/testdata/testdata.proto b/testutil/testdata/testdata.proto index 1da9aef16e8e..b46cbf031df7 100644 --- a/testutil/testdata/testdata.proto +++ b/testutil/testdata/testdata.proto @@ -39,4 +39,6 @@ message BadMultiSignature { message TableModel { uint64 id = 1; string name = 2; + uint64 number = 3; + bytes metadata = 4; } diff --git a/types/errors/errors.go b/types/errors/errors.go index 29ba3195f146..ece008e10da1 100644 --- a/types/errors/errors.go +++ b/types/errors/errors.go @@ -170,7 +170,7 @@ var ( ErrORMEmptyModel = Register(ormCodespace, 45, "invalid argument") // ErrORMKeyMaxLength defines an error when a key exceeds max length - ErrORMKeyMaxLength = Register(ormCodespace, 46, "index key exceeds max length") + ErrORMKeyMaxLength = Register(ormCodespace, 46, "key exceeds max length") // ErrORMEmptyKey defines an error for an empty key ErrORMEmptyKey = Register(ormCodespace, 47, "cannot use empty key") diff --git a/x/group/go.mod b/x/group/go.mod index be3763bc17b8..5941713b48c5 100644 --- a/x/group/go.mod +++ b/x/group/go.mod @@ -17,7 +17,7 @@ require github.com/tendermint/tm-db v0.6.4 require ( github.com/DataDog/zstd v1.4.5 // indirect - github.com/armon/go-metrics v0.3.9 // indirect + github.com/armon/go-metrics v0.3.10 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd v0.22.0-beta // indirect github.com/cespare/xxhash v1.1.0 // indirect @@ -27,12 +27,13 @@ require ( github.com/cosmos/iavl v0.17.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgraph-io/badger/v2 v2.2007.2 // indirect - github.com/dgraph-io/ristretto v0.0.3 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-kit/kit v0.10.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.3 // indirect github.com/google/btree v1.0.0 // indirect @@ -44,6 +45,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect + github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554 // indirect github.com/libp2p/go-buffer-pool v0.0.2 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect @@ -56,7 +58,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.11.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.31.1 // indirect + github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect github.com/spf13/afero v1.6.0 // indirect @@ -69,7 +71,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tendermint/go-amino v0.16.0 // indirect - github.com/tendermint/tendermint v0.34.13 // indirect + github.com/tendermint/tendermint v0.34.14 // indirect go.etcd.io/bbolt v1.3.5 // indirect golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect @@ -87,3 +89,5 @@ replace google.golang.org/grpc => google.golang.org/grpc v1.33.2 replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 replace github.com/cosmos/cosmos-sdk => ../../ + +replace github.com/cosmos/cosmos-sdk/db => ../../db diff --git a/x/group/go.sum b/x/group/go.sum index 1de48c261123..fdb0cd5ac51a 100644 --- a/x/group/go.sum +++ b/x/group/go.sum @@ -101,8 +101,8 @@ github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1: github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18= -github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= @@ -162,8 +162,8 @@ github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b80 github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coinbase/rosetta-sdk-go v0.6.10 h1:rgHD/nHjxLh0lMEdfGDqpTtlvtSBwULqrrZ2qPdNaCM= -github.com/coinbase/rosetta-sdk-go v0.6.10/go.mod h1:J/JFMsfcePrjJZkwQFLh+hJErkAmdm9Iyy3D5Y0LfXo= +github.com/coinbase/rosetta-sdk-go v0.7.0 h1:lmTO/JEpCvZgpbkOITL95rA80CPKb5CtMzLaqF2mCNg= +github.com/coinbase/rosetta-sdk-go v0.7.0/go.mod h1:7nD3oBPIiHqhRprqvMgPoGxe/nyq3yftRmpsy29coWE= github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/confio/ics23/go v0.6.3/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/confio/ics23/go v0.6.6 h1:pkOy18YxxJ/r0XFDCnrl4Bjv6h4LkBSpLS6F38mrKL8= @@ -217,9 +217,11 @@ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFM github.com/dgraph-io/badger/v2 v2.2007.1/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= +github.com/dgraph-io/badger/v3 v3.2103.1/go.mod h1:dULbq6ehJ5K0cGW/1TQ9iSfUk0gbSiToDWmWmTsJ53E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI= github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= @@ -300,6 +302,7 @@ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -342,6 +345,7 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -480,7 +484,7 @@ github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.10.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= +github.com/jhump/protoreflect v1.10.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= @@ -506,8 +510,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -520,6 +525,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554 h1:nDOkLO7klmnEw1s4AyKt1Arvpgyh33uj1JmkYlJaDsk= +github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554/go.mod h1:9+Pb2/tg1PvEgW7aFx4bFhDE4bvbI03zuJ8kb7nJ9Jc= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -689,8 +696,8 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8 github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.31.1 h1:d18hG4PkHnNAKNMOmFuXFaiY8Us0nird/2m60uS1AMs= -github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -809,8 +816,9 @@ github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoM github.com/tendermint/tendermint v0.34.0-rc4/go.mod h1:yotsojf2C1QBOw4dZrTcxbyxmPUrT4hNuOQWX9XUwB4= github.com/tendermint/tendermint v0.34.0-rc6/go.mod h1:ugzyZO5foutZImv0Iyx/gOFCX6mjJTgbLHTwi17VDVg= github.com/tendermint/tendermint v0.34.0/go.mod h1:Aj3PIipBFSNO21r+Lq3TtzQ+uKESxkbA3yo/INM4QwQ= -github.com/tendermint/tendermint v0.34.13 h1:fu+tsHudbOr5PvepjH0q47Jae59hQAvn3IqAHv2EbC8= github.com/tendermint/tendermint v0.34.13/go.mod h1:6RVVRBqwtKhA+H59APKumO+B7Nye4QXSFc6+TYxAxCI= +github.com/tendermint/tendermint v0.34.14 h1:GCXmlS8Bqd2Ix3TQCpwYLUNHe+Y+QyJsm5YE+S/FkPo= +github.com/tendermint/tendermint v0.34.14/go.mod h1:FrwVm3TvsVicI9Z7FlucHV6Znfd5KBc/Lpp69cCwtk0= github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI= github.com/tendermint/tm-db v0.6.3/go.mod h1:lfA1dL9/Y/Y8wwyPp2NMLyn5P5Ptr/gvDFNWtrCWSf8= github.com/tendermint/tm-db v0.6.4 h1:3N2jlnYQkXNQclQwd/eKV/NzlqPlfK21cpRRIx80XXQ= diff --git a/x/group/internal/orm/auto_uint64.go b/x/group/internal/orm/auto_uint64.go new file mode 100644 index 000000000000..62eb84cbbabc --- /dev/null +++ b/x/group/internal/orm/auto_uint64.go @@ -0,0 +1,75 @@ +package orm + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ Indexable = &AutoUInt64Table{} + +// AutoUInt64Table is the table type with an auto incrementing ID. +type AutoUInt64Table struct { + *table + seq Sequence +} + +// NewAutoUInt64Table creates a new AutoUInt64Table. +func NewAutoUInt64Table(prefixData [2]byte, prefixSeq byte, model codec.ProtoMarshaler, cdc codec.Codec) (*AutoUInt64Table, error) { + table, err := newTable(prefixData, model, cdc) + if err != nil { + return nil, err + } + return &AutoUInt64Table{ + table: table, + seq: NewSequence(prefixSeq), + }, nil +} + +// Create a new persistent object with an auto generated uint64 primary key. The +// key is returned. +// +// Create iterates through the registered callbacks that may add secondary index +// keys. +func (a AutoUInt64Table) Create(store sdk.KVStore, obj codec.ProtoMarshaler) (uint64, error) { + autoIncID := a.seq.NextVal(store) + err := a.table.Create(store, EncodeSequence(autoIncID), obj) + if err != nil { + return 0, err + } + return autoIncID, nil +} + +// Update updates the given object under the rowID key. It expects the key to +// exists already and fails with an `ErrNotFound` otherwise. Any caller must +// therefore make sure that this contract is fulfilled. Parameters must not be +// nil. +// +// Update iterates through the registered callbacks that may add or remove +// secondary index keys. +func (a AutoUInt64Table) Update(store sdk.KVStore, rowID uint64, newValue codec.ProtoMarshaler) error { + return a.table.Update(store, EncodeSequence(rowID), newValue) +} + +// Delete removes the object under the rowID key. It expects the key to exists already +// and fails with a `ErrNotFound` otherwise. Any caller must therefore make sure that this contract +// is fulfilled. +// +// Delete iterates though the registered callbacks and removes secondary index keys by them. +func (a AutoUInt64Table) Delete(store sdk.KVStore, rowID uint64) error { + return a.table.Delete(store, EncodeSequence(rowID)) +} + +// Has checks if a rowID exists. +func (a AutoUInt64Table) Has(store sdk.KVStore, rowID uint64) bool { + return a.table.Has(store, EncodeSequence(rowID)) +} + +// GetOne load the object persisted for the given RowID into the dest parameter. +// If none exists `ErrNotFound` is returned instead. Parameters must not be nil. +func (a AutoUInt64Table) GetOne(store sdk.KVStore, rowID uint64, dest codec.ProtoMarshaler) (RowID, error) { + rawRowID := EncodeSequence(rowID) + if err := a.table.GetOne(store, rawRowID, dest); err != nil { + return nil, err + } + return rawRowID, nil +} diff --git a/x/group/internal/orm/example_test.go b/x/group/internal/orm/example_test.go new file mode 100644 index 000000000000..a6bddf0b855b --- /dev/null +++ b/x/group/internal/orm/example_test.go @@ -0,0 +1,35 @@ +package orm + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/testutil/testdata" +) + +type TestKeeper struct { + autoUInt64Table *AutoUInt64Table + primaryKeyTable *PrimaryKeyTable +} + +var ( + AutoUInt64TableTablePrefix [2]byte = [2]byte{0x0} + PrimaryKeyTablePrefix [2]byte = [2]byte{0x1} + AutoUInt64TableSeqPrefix byte = 0x2 +) + +func NewTestKeeper(cdc codec.Codec) TestKeeper { + k := TestKeeper{} + + autoUInt64Table, err := NewAutoUInt64Table(AutoUInt64TableTablePrefix, AutoUInt64TableSeqPrefix, &testdata.TableModel{}, cdc) + if err != nil { + panic(err.Error()) + } + k.autoUInt64Table = autoUInt64Table + + primaryKeyTable, err := NewPrimaryKeyTable(PrimaryKeyTablePrefix, &testdata.TableModel{}, cdc) + if err != nil { + panic(err.Error()) + } + k.primaryKeyTable = primaryKeyTable + + return k +} diff --git a/x/group/internal/orm/generators_test.go b/x/group/internal/orm/generators_test.go new file mode 100644 index 000000000000..0cc95eedaae0 --- /dev/null +++ b/x/group/internal/orm/generators_test.go @@ -0,0 +1,18 @@ +package orm + +import ( + "pgregory.net/rapid" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" +) + +// genTableModel generates a new table model. At the moment it doesn't +// generate empty strings for Name. +var genTableModel = rapid.Custom(func(t *rapid.T) *testdata.TableModel { + return &testdata.TableModel{ + Id: rapid.Uint64().Draw(t, "id").(uint64), + Name: rapid.StringN(1, 100, 150).Draw(t, "name").(string), + Number: rapid.Uint64().Draw(t, "number ").(uint64), + Metadata: []byte(rapid.StringN(1, 100, 150).Draw(t, "metadata").(string)), + } +}) diff --git a/x/group/internal/orm/key_codec.go b/x/group/internal/orm/key_codec.go new file mode 100644 index 000000000000..099ed1571cf1 --- /dev/null +++ b/x/group/internal/orm/key_codec.go @@ -0,0 +1,75 @@ +package orm + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/types/errors" +) + +// MaxBytesLen is the maximum allowed length for a key part of type []byte +const MaxBytesLen = 255 + +// buildKeyFromParts encodes and concatenates primary key and index parts. +// They can be []byte, string, and integer types. The function will return +// an error if there is a part of any other type. +// Key parts, except the last part, follow these rules: +// - []byte is encoded with a single byte length prefix +// - strings are null-terminated +// - integers are encoded using 8 byte big endian. +func buildKeyFromParts(parts []interface{}) ([]byte, error) { + bytesSlice := make([][]byte, len(parts)) + totalLen := 0 + var err error + for i, part := range parts { + bytesSlice[i], err = keyPartBytes(part, len(parts) > 1 && i == len(parts)-1) + if err != nil { + return nil, err + } + totalLen += len(bytesSlice[i]) + } + key := make([]byte, 0, totalLen) + for _, bs := range bytesSlice { + key = append(key, bs...) + } + return key, nil +} + +func keyPartBytes(part interface{}, last bool) ([]byte, error) { + switch v := part.(type) { + case []byte: + if last || len(v) == 0 { + return v, nil + } + return AddLengthPrefix(v), nil + case string: + if last || len(v) == 0 { + return []byte(v), nil + } + return NullTerminatedBytes(v), nil + case uint64: + return EncodeSequence(v), nil + default: + return nil, fmt.Errorf("type %T not allowed as key part", v) + } +} + +// AddLengthPrefix prefixes the byte array with its length as 8 bytes. The function will panic +// if the bytes length is bigger than 255. +func AddLengthPrefix(bytes []byte) []byte { + byteLen := len(bytes) + if byteLen > MaxBytesLen { + panic(errors.Wrap(errors.ErrORMKeyMaxLength, "Cannot create key part with an []byte of length greater than 255 bytes. Try again with a smaller []byte.")) + } + + prefixedBytes := make([]byte, 1+len(bytes)) + copy(prefixedBytes, []byte{uint8(byteLen)}) + copy(prefixedBytes[1:], bytes) + return prefixedBytes +} + +// NullTerminatedBytes converts string to byte array and null terminate it +func NullTerminatedBytes(s string) []byte { + bytes := make([]byte, len(s)+1) + copy(bytes, s) + return bytes +} diff --git a/x/group/internal/orm/key_codec_test.go b/x/group/internal/orm/key_codec_test.go new file mode 100644 index 000000000000..4c5e8dcfbe8b --- /dev/null +++ b/x/group/internal/orm/key_codec_test.go @@ -0,0 +1,46 @@ +package orm + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAddLengthPrefix(t *testing.T) { + tcs := []struct { + name string + in []byte + expected []byte + }{ + {"empty", []byte{}, []byte{0}}, + {"nil", nil, []byte{0}}, + {"some data", []byte{0, 1, 100, 200}, []byte{4, 0, 1, 100, 200}}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + out := AddLengthPrefix(tc.in) + require.Equal(t, tc.expected, out) + }) + } + + require.Panics(t, func() { + AddLengthPrefix(make([]byte, 256)) + }) +} + +func TestNullTerminatedBytes(t *testing.T) { + tcs := []struct { + name string + in string + expected []byte + }{ + {"empty", "", []byte{0}}, + {"some data", "abc", []byte{0x61, 0x62, 0x63, 0}}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + out := NullTerminatedBytes(tc.in) + require.Equal(t, tc.expected, out) + }) + } +} diff --git a/x/group/internal/orm/orm_scenario_test.go b/x/group/internal/orm/orm_scenario_test.go new file mode 100644 index 000000000000..ad934349ff82 --- /dev/null +++ b/x/group/internal/orm/orm_scenario_test.go @@ -0,0 +1,207 @@ +package orm + +import ( + "encoding/binary" + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" +) + +func TestKeeperEndToEndWithAutoUInt64Table(t *testing.T) { + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + ctx := NewMockContext() + store := ctx.KVStore(sdk.NewKVStoreKey("test")) + + k := NewTestKeeper(cdc) + + tm := testdata.TableModel{ + Id: 1, + Name: "name", + Number: 123, + Metadata: []byte("metadata"), + } + // when stored + rowID, err := k.autoUInt64Table.Create(store, &tm) + require.NoError(t, err) + // then we should find it + exists := k.autoUInt64Table.Has(store, rowID) + require.True(t, exists) + + // and load it + var loaded testdata.TableModel + + binKey, err := k.autoUInt64Table.GetOne(store, rowID, &loaded) + require.NoError(t, err) + + require.Equal(t, rowID, binary.BigEndian.Uint64(binKey)) + require.Equal(t, tm, loaded) + + // when updated + tm.Name = "new name" + err = k.autoUInt64Table.Update(store, rowID, &tm) + require.NoError(t, err) + + binKey, err = k.autoUInt64Table.GetOne(store, rowID, &loaded) + require.NoError(t, err) + + require.Equal(t, rowID, binary.BigEndian.Uint64(binKey)) + require.Equal(t, tm, loaded) + + // when deleted + err = k.autoUInt64Table.Delete(store, rowID) + require.NoError(t, err) + + exists = k.autoUInt64Table.Has(store, rowID) + require.False(t, exists) +} + +func TestKeeperEndToEndWithPrimaryKeyTable(t *testing.T) { + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + ctx := NewMockContext() + store := ctx.KVStore(sdk.NewKVStoreKey("test")) + + k := NewTestKeeper(cdc) + + tm := testdata.TableModel{ + Id: 1, + Name: "name", + Number: 123, + Metadata: []byte("metadata"), + } + // when stored + err := k.primaryKeyTable.Create(store, &tm) + require.NoError(t, err) + // then we should find it by primary key + primaryKey := PrimaryKey(&tm) + exists := k.primaryKeyTable.Has(store, primaryKey) + require.True(t, exists) + + // and load it by primary key + var loaded testdata.TableModel + err = k.primaryKeyTable.GetOne(store, primaryKey, &loaded) + require.NoError(t, err) + + // then values should match expectations + require.Equal(t, tm, loaded) + + // and when we create another entry with the same primary key + err = k.primaryKeyTable.Create(store, &tm) + // then it should fail as the primary key must be unique + require.True(t, errors.ErrORMUniqueConstraint.Is(err), err) + + // and when entity updated with new primary key + updatedMember := &testdata.TableModel{ + Id: 2, + Name: tm.Name, + Number: tm.Number, + Metadata: tm.Metadata, + } + // then it should fail as the primary key is immutable + err = k.primaryKeyTable.Update(store, updatedMember) + require.Error(t, err) + + // and when entity updated with non primary key attribute modified + updatedMember = &testdata.TableModel{ + Id: 1, + Name: "new name", + Number: tm.Number, + Metadata: tm.Metadata, + } + // then it should not fail + err = k.primaryKeyTable.Update(store, updatedMember) + require.NoError(t, err) + + // and when entity deleted + err = k.primaryKeyTable.Delete(store, &tm) + require.NoError(t, err) + + exists = k.primaryKeyTable.Has(store, primaryKey) + require.False(t, exists) +} + +func TestGasCostsPrimaryKeyTable(t *testing.T) { + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + ctx := NewMockContext() + store := ctx.KVStore(sdk.NewKVStoreKey("test")) + + k := NewTestKeeper(cdc) + + tm := testdata.TableModel{ + Id: 1, + Name: "name", + Number: 123, + Metadata: []byte("metadata"), + } + rowID, err := k.autoUInt64Table.Create(store, &tm) + require.NoError(t, err) + require.Equal(t, uint64(1), rowID) + + gCtx := NewGasCountingMockContext() + err = k.primaryKeyTable.Create(gCtx.KVStore(store), &tm) + require.NoError(t, err) + t.Logf("gas consumed on create: %d", gCtx.GasConsumed()) + + // get by primary key + gCtx.ResetGasMeter() + var loaded testdata.TableModel + err = k.primaryKeyTable.GetOne(gCtx.KVStore(store), PrimaryKey(&tm), &loaded) + require.NoError(t, err) + t.Logf("gas consumed on get by primary key: %d", gCtx.GasConsumed()) + + // delete + gCtx.ResetGasMeter() + err = k.primaryKeyTable.Delete(gCtx.KVStore(store), &tm) + require.NoError(t, err) + t.Logf("gas consumed on delete by primary key: %d", gCtx.GasConsumed()) + + // with 3 elements + var tms []testdata.TableModel + for i := 1; i < 4; i++ { + gCtx.ResetGasMeter() + tm := testdata.TableModel{ + Id: uint64(i), + Name: fmt.Sprintf("name%d", i), + Number: 123, + Metadata: []byte("metadata"), + } + err = k.primaryKeyTable.Create(gCtx.KVStore(store), &tm) + require.NoError(t, err) + t.Logf("%d: gas consumed on create: %d", i, gCtx.GasConsumed()) + tms = append(tms, tm) + } + + for i := 1; i < 4; i++ { + gCtx.ResetGasMeter() + tm := testdata.TableModel{ + Id: uint64(i), + Name: fmt.Sprintf("name%d", i), + Number: 123, + Metadata: []byte("metadata"), + } + err = k.primaryKeyTable.GetOne(gCtx.KVStore(store), PrimaryKey(&tm), &loaded) + require.NoError(t, err) + t.Logf("%d: gas consumed on get by primary key: %d", i, gCtx.GasConsumed()) + } + + // delete + for i, m := range tms { + gCtx.ResetGasMeter() + + err = k.primaryKeyTable.Delete(gCtx.KVStore(store), &m) + require.NoError(t, err) + t.Logf("%d: gas consumed on delete: %d", i, gCtx.GasConsumed()) + } +} diff --git a/x/group/internal/orm/primary_key.go b/x/group/internal/orm/primary_key.go new file mode 100644 index 000000000000..8bfeae402dea --- /dev/null +++ b/x/group/internal/orm/primary_key.go @@ -0,0 +1,113 @@ +package orm + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ Indexable = &PrimaryKeyTable{} + +// PrimaryKeyTable provides simpler object style orm methods without passing database RowIDs. +// Entries are persisted and loaded with a reference to their unique primary key. +type PrimaryKeyTable struct { + *table +} + +// NewPrimaryKeyTable creates a new PrimaryKeyTable. +func NewPrimaryKeyTable(prefixData [2]byte, model PrimaryKeyed, cdc codec.Codec) (*PrimaryKeyTable, error) { + table, err := newTable(prefixData, model, cdc) + if err != nil { + return nil, err + } + return &PrimaryKeyTable{ + table: table, + }, nil +} + +// PrimaryKeyed defines an object type that is aware of its immutable primary key. +type PrimaryKeyed interface { + // PrimaryKeyFields returns the fields of the object that will make up + // the primary key. The PrimaryKey function will encode and concatenate + // the fields to build the primary key. + // + // PrimaryKey parts can be []byte, string, and integer types. []byte is + // encoded with a length prefix, strings are null-terminated, and + // integers are encoded using 8 byte big endian. + // + // IMPORTANT: []byte parts are encoded with a single byte length prefix, + // so cannot be longer than 255 bytes. + PrimaryKeyFields() []interface{} + codec.ProtoMarshaler +} + +// PrimaryKey returns the immutable and serialized primary key of this object. +// The primary key has to be unique within it's domain so that not two with same +// value can exist in the same table. This means PrimaryKeyFields() has to +// return a unique value for each object. +func PrimaryKey(obj PrimaryKeyed) []byte { + fields := obj.PrimaryKeyFields() + key, err := buildKeyFromParts(fields) + if err != nil { + panic(err) + } + return key +} + +// Create persists the given object under their primary key. It checks if the +// key already exists and may return an `ErrUniqueConstraint`. +// +// Create iterates through the registered callbacks that may add secondary +// index keys. +func (a PrimaryKeyTable) Create(store sdk.KVStore, obj PrimaryKeyed) error { + rowID := PrimaryKey(obj) + return a.table.Create(store, rowID, obj) +} + +// Update updates the given object under the primary key. It expects the key to +// exists already and fails with an `ErrNotFound` otherwise. Any caller must +// therefore make sure that this contract is fulfilled. Parameters must not be +// nil. +// +// Update iterates through the registered callbacks that may add or remove +// secondary index keys. +func (a PrimaryKeyTable) Update(store sdk.KVStore, newValue PrimaryKeyed) error { + return a.table.Update(store, PrimaryKey(newValue), newValue) +} + +// Set persists the given object under the rowID key. It does not check if the +// key already exists and overwrites the value if it does. +// +// Set iterates through the registered callbacks that may add secondary index +// keys. +func (a PrimaryKeyTable) Set(store sdk.KVStore, newValue PrimaryKeyed) error { + return a.table.Set(store, PrimaryKey(newValue), newValue) +} + +// Delete removes the object. It expects the primary key to exists already and +// fails with a `ErrNotFound` otherwise. Any caller must therefore make sure +// that this contract is fulfilled. +// +// Delete iterates through the registered callbacks that remove secondary index +// keys. +func (a PrimaryKeyTable) Delete(store sdk.KVStore, obj PrimaryKeyed) error { + return a.table.Delete(store, PrimaryKey(obj)) +} + +// Has checks if a key exists. Always returns false on nil or empty key. +func (a PrimaryKeyTable) Has(store sdk.KVStore, primaryKey RowID) bool { + return a.table.Has(store, primaryKey) +} + +// Contains returns true when an object with same type and primary key is persisted in this table. +func (a PrimaryKeyTable) Contains(store sdk.KVStore, obj PrimaryKeyed) bool { + if err := assertCorrectType(a.table.model, obj); err != nil { + return false + } + return a.table.Has(store, PrimaryKey(obj)) +} + +// GetOne loads the object persisted for the given primary Key into the dest parameter. +// If none exists `ErrNotFound` is returned instead. Parameters must not be nil. +func (a PrimaryKeyTable) GetOne(store sdk.KVStore, primKey RowID, dest codec.ProtoMarshaler) error { + return a.table.GetOne(store, primKey, dest) +} diff --git a/x/group/internal/orm/primary_key_property_test.go b/x/group/internal/orm/primary_key_property_test.go new file mode 100644 index 000000000000..083fd2c2944b --- /dev/null +++ b/x/group/internal/orm/primary_key_property_test.go @@ -0,0 +1,188 @@ +package orm + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "pgregory.net/rapid" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" +) + +func TestPrimaryKeyTable(t *testing.T) { + rapid.Check(t, rapid.Run(&primaryKeyMachine{})) +} + +// primaryKeyMachine is a state machine model of the PrimaryKeyTable. The state +// is modelled as a map of strings to TableModels. +type primaryKeyMachine struct { + store sdk.KVStore + table *PrimaryKeyTable + state map[string]*testdata.TableModel +} + +// stateKeys gets all the keys in the model map +func (m *primaryKeyMachine) stateKeys() []string { + keys := make([]string, len(m.state)) + + i := 0 + for k := range m.state { + keys[i] = k + i++ + } + + return keys +} + +// Generate a TableModel that has a 50% chance of being a part of the existing +// state +func (m *primaryKeyMachine) genTableModel() *rapid.Generator { + genStateTableModel := rapid.Custom(func(t *rapid.T) *testdata.TableModel { + pk := rapid.SampledFrom(m.stateKeys()).Draw(t, "key").(string) + return m.state[pk] + }) + + if len(m.stateKeys()) == 0 { + return genTableModel + } else { + return rapid.OneOf(genTableModel, genStateTableModel) + } +} + +// Init creates a new instance of the state machine model by building the real +// table and making the empty model map +func (m *primaryKeyMachine) Init(t *rapid.T) { + // Create context + ctx := NewMockContext() + m.store = ctx.KVStore(sdk.NewKVStoreKey("test")) + + // Create primary key table + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + table, err := NewPrimaryKeyTable( + [2]byte{0x1}, + &testdata.TableModel{}, + cdc, + ) + require.NoError(t, err) + + m.table = table + + // Create model state + m.state = make(map[string]*testdata.TableModel) +} + +// Check that the real values match the state values. +func (m *primaryKeyMachine) Check(t *rapid.T) { + for i := range m.state { + has := m.table.Has(m.store, []byte(i)) + require.Equal(t, true, has) + } +} + +// Create is one of the model commands. It adds an object to the table, creating +// an error if it already exists. +func (m *primaryKeyMachine) Create(t *rapid.T) { + g := genTableModel.Draw(t, "g").(*testdata.TableModel) + pk := string(PrimaryKey(g)) + + t.Logf("pk: %v", pk) + t.Logf("m.state: %v", m.state) + + err := m.table.Create(m.store, g) + + if m.state[pk] != nil { + require.Error(t, err) + } else { + require.NoError(t, err) + m.state[pk] = g + } +} + +// Update is one of the model commands. It updates the value at a given primary +// key and fails if that primary key doesn't already exist in the table. +func (m *primaryKeyMachine) Update(t *rapid.T) { + tm := m.genTableModel().Draw(t, "tm").(*testdata.TableModel) + + newName := rapid.StringN(1, 100, 150).Draw(t, "newName").(string) + tm.Name = newName + + // Perform the real Update + err := m.table.Update(m.store, tm) + + if m.state[string(PrimaryKey(tm))] == nil { + // If there's no value in the model, we expect an error + require.Error(t, err) + } else { + // If we have a value in the model, expect no error + require.NoError(t, err) + + // Update the model with the new value + m.state[string(PrimaryKey(tm))] = tm + } +} + +// Set is one of the model commands. It sets the value at a key in the table +// whether it exists or not. +func (m *primaryKeyMachine) Set(t *rapid.T) { + g := genTableModel.Draw(t, "g").(*testdata.TableModel) + pk := string(PrimaryKey(g)) + + err := m.table.Set(m.store, g) + + require.NoError(t, err) + m.state[pk] = g +} + +// Delete is one of the model commands. It removes the object with the given +// primary key from the table and returns an error if that primary key doesn't +// already exist in the table. +func (m *primaryKeyMachine) Delete(t *rapid.T) { + tm := m.genTableModel().Draw(t, "tm").(*testdata.TableModel) + + // Perform the real Delete + err := m.table.Delete(m.store, tm) + + if m.state[string(PrimaryKey(tm))] == nil { + // If there's no value in the model, we expect an error + require.Error(t, err) + } else { + // If we have a value in the model, expect no error + require.NoError(t, err) + + // Delete the value from the model + delete(m.state, string(PrimaryKey(tm))) + } +} + +// Has is one of the model commands. It checks whether a key already exists in +// the table. +func (m *primaryKeyMachine) Has(t *rapid.T) { + pk := PrimaryKey(m.genTableModel().Draw(t, "g").(*testdata.TableModel)) + + realHas := m.table.Has(m.store, pk) + modelHas := m.state[string(pk)] != nil + + require.Equal(t, realHas, modelHas) +} + +// GetOne is one of the model commands. It fetches an object from the table by +// its primary key and returns an error if that primary key isn't in the table. +func (m *primaryKeyMachine) GetOne(t *rapid.T) { + pk := PrimaryKey(m.genTableModel().Draw(t, "tm").(*testdata.TableModel)) + + var tm testdata.TableModel + + err := m.table.GetOne(m.store, pk, &tm) + t.Logf("tm: %v", tm) + + if m.state[string(pk)] == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, *m.state[string(pk)], tm) + } +} diff --git a/x/group/internal/orm/primary_key_test.go b/x/group/internal/orm/primary_key_test.go new file mode 100644 index 000000000000..6338fd5ce5b8 --- /dev/null +++ b/x/group/internal/orm/primary_key_test.go @@ -0,0 +1,67 @@ +package orm + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" +) + +func TestContains(t *testing.T) { + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + ctx := NewMockContext() + store := ctx.KVStore(sdk.NewKVStoreKey("test")) + + tb, err := NewPrimaryKeyTable([2]byte{0x1}, &testdata.TableModel{}, cdc) + require.NoError(t, err) + + obj := testdata.TableModel{ + Id: 1, + Name: "Some name", + } + err = tb.Create(store, &obj) + require.NoError(t, err) + + specs := map[string]struct { + src PrimaryKeyed + exp bool + }{ + + "same object": {src: &obj, exp: true}, + "clone": { + src: &testdata.TableModel{ + Id: 1, + Name: "Some name", + }, + exp: true, + }, + "different primary key": { + src: &testdata.TableModel{ + Id: 2, + Name: "Some name", + }, + exp: false, + }, + "different type, same key": { + src: mockPrimaryKeyed{&obj}, + exp: false, + }, + } + for msg, spec := range specs { + t.Run(msg, func(t *testing.T) { + got := tb.Contains(store, spec.src) + assert.Equal(t, spec.exp, got) + }) + } +} + +type mockPrimaryKeyed struct { + *testdata.TableModel +} diff --git a/x/group/internal/orm/spec/01_table.md b/x/group/internal/orm/spec/01_table.md new file mode 100644 index 000000000000..6240b06467fc --- /dev/null +++ b/x/group/internal/orm/spec/01_table.md @@ -0,0 +1,38 @@ +# Table + +A table can be built given a `codec.ProtoMarshaler` model type, a prefix to access the underlying prefix store used to store table data as well as a `Codec` for marshalling/unmarshalling. + ++++ https://github.com/cosmos/cosmos-sdk/blob/9f78f16ae75cc42fc5fe636bde18a453ba74831f/x/group/internal/orm/table.go#L24-L30 + +In the prefix store, entities should be stored by an unique identifier called `RowID` which can be based either on an `uint64` auto-increment counter, string or dynamic size bytes. +Regular CRUD operations can be performed on a table, these methods take a `sdk.KVStore` as parameter to get the table prefix store. + +The `table` struct does not: + - enforce uniqueness of the `RowID` + - enforce prefix uniqueness of keys, i.e. not allowing one key to be a prefix + of another + - optimize Gas usage conditions +The `table` struct is private, so that we only have custom tables built on top of it, that do satisfy these requirements. + +## AutoUInt64Table + +`AutoUInt64Table` is a table type with an auto incrementing `uint64` ID. + ++++ https://github.com/cosmos/cosmos-sdk/blob/9f78f16ae75cc42fc5fe636bde18a453ba74831f/x/group/internal/orm/auto_uint64.go#L11-L14 + +It's based on the `Sequence` struct which is a persistent unique key generator based on a counter encoded using 8 byte big endian. + +## PrimaryKeyTable + +`PrimaryKeyTable` provides simpler object style orm methods where are persisted and loaded with a reference to their unique primary key. + +The model provided for creating a `PrimaryKeyTable` should implement the `PrimaryKeyed` interface: + ++++ https://github.com/cosmos/cosmos-sdk/blob/9f78f16ae75cc42fc5fe636bde18a453ba74831f/x/group/internal/orm/primary_key.go#L28-L41 + +`PrimaryKeyFields()` method returns the list of key parts for a given object. +The primary key parts can be []byte, string, and `uint64` types. + Key parts, except the last part, follow these rules: + - []byte is encoded with a single byte length prefix + - strings are null-terminated + - `uint64` are encoded using 8 byte big endian. diff --git a/x/group/internal/orm/spec/README.md b/x/group/internal/orm/spec/README.md index edf57ce74f27..c8eccb01f596 100644 --- a/x/group/internal/orm/spec/README.md +++ b/x/group/internal/orm/spec/README.md @@ -1,26 +1,9 @@ -## Abstract +# Abstract The orm package provides a framework for creating relational database tables with primary and secondary keys. -### Tables +## Contents -```go -type table struct { - model reflect.Type - prefix [2]byte - afterSet []AfterSetInterceptor - afterDelete []AfterDeleteInterceptor - cdc codec.Codec -} -``` - -A table can be built given a `codec.ProtoMarshaler` model type, a prefix to access the underlying prefix store used to store table data as well as a `Codec` for marshalling/unmarshalling. -In the prefix store, entities should be stored by an unique identifier called `RowID` which can be based either on an `uint64` auto-increment counter, string or dynamic size bytes. -Regular CRUD operations can be performed on a table, these methods take a `sdk.KVStore` as parameter to get the table prefix store. - -The `table` struct does not: - - enforce uniqueness of the `RowID` - - enforce prefix uniqueness of keys, i.e. not allowing one key to be a prefix - of another - - optimize Gas usage conditions -The `table` struct is private, so that we only have custom tables built on top of it, that do satisfy these requirements. \ No newline at end of file +1. **[Table](01_table.md)** + - [AutoUInt64Table](01_table.md#autouint64table) + - [PrimaryKeyTable](01_table.md#primarykeytable) diff --git a/x/group/internal/orm/testsupport.go b/x/group/internal/orm/testsupport.go index 07316145404c..df95b0f27414 100644 --- a/x/group/internal/orm/testsupport.go +++ b/x/group/internal/orm/testsupport.go @@ -1,7 +1,10 @@ package orm import ( + "fmt" + "github.com/cosmos/cosmos-sdk/store" + "github.com/cosmos/cosmos-sdk/store/gaskv" "github.com/cosmos/cosmos-sdk/store/types" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -31,3 +34,70 @@ func (m MockContext) KVStore(key storetypes.StoreKey) sdk.KVStore { } return m.store.GetCommitKVStore(key) } + +type debuggingGasMeter struct { + g types.GasMeter +} + +func (d debuggingGasMeter) GasConsumed() types.Gas { + return d.g.GasConsumed() +} + +func (d debuggingGasMeter) GasRemaining() types.Gas { + return d.g.GasRemaining() +} + +func (d debuggingGasMeter) GasConsumedToLimit() types.Gas { + return d.g.GasConsumedToLimit() +} + +func (d debuggingGasMeter) RefundGas(amount uint64, descriptor string) { + d.g.RefundGas(amount, descriptor) +} + +func (d debuggingGasMeter) Limit() types.Gas { + return d.g.Limit() +} + +func (d debuggingGasMeter) ConsumeGas(amount types.Gas, descriptor string) { + fmt.Printf("++ Consuming gas: %q :%d\n", descriptor, amount) + d.g.ConsumeGas(amount, descriptor) +} + +func (d debuggingGasMeter) IsPastLimit() bool { + return d.g.IsPastLimit() +} + +func (d debuggingGasMeter) IsOutOfGas() bool { + return d.g.IsOutOfGas() +} + +func (d debuggingGasMeter) String() string { + return d.g.String() +} + +type GasCountingMockContext struct { + GasMeter sdk.GasMeter +} + +func NewGasCountingMockContext() *GasCountingMockContext { + return &GasCountingMockContext{ + GasMeter: &debuggingGasMeter{sdk.NewInfiniteGasMeter()}, + } +} + +func (g GasCountingMockContext) KVStore(store sdk.KVStore) sdk.KVStore { + return gaskv.NewStore(store, g.GasMeter, types.KVGasConfig()) +} + +func (g GasCountingMockContext) GasConsumed() types.Gas { + return g.GasMeter.GasConsumed() +} + +func (g GasCountingMockContext) GasRemaining() types.Gas { + return g.GasMeter.GasRemaining() +} + +func (g *GasCountingMockContext) ResetGasMeter() { + g.GasMeter = sdk.NewInfiniteGasMeter() +} diff --git a/x/group/internal/orm/types.go b/x/group/internal/orm/types.go index 48e2f631839d..c06bbebd6de0 100644 --- a/x/group/internal/orm/types.go +++ b/x/group/internal/orm/types.go @@ -32,7 +32,7 @@ type Iterator interface { // LoadNext loads the next value in the sequence into the pointer passed as dest and returns the key. If there // are no more items the ErrORMIteratorDone error is returned // The key is the rowID. - LoadNext(dest codec.ProtoMarshaler) (RowID, error) + LoadNext(store sdk.KVStore, dest codec.ProtoMarshaler) (RowID, error) // Close releases the iterator and should be called at the end of iteration io.Closer }