-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(collections): Initialise core (Prefix, KeyEncoder, ValueEncoder,…
… Map) (#14134) Co-authored-by: testinginprod <[email protected]> Co-authored-by: Aaron Craelius <[email protected]>
- Loading branch information
1 parent
bd59987
commit fca24b6
Showing
14 changed files
with
932 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -492,6 +492,28 @@ jobs: | |
with: | ||
projectBaseDir: tools/rosetta/ | ||
|
||
test-collections: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-go@v3 | ||
with: | ||
go-version: 1.19.4 | ||
cache: true | ||
cache-dependency-path: collections/go.sum | ||
- uses: technote-space/[email protected] | ||
id: git_diff | ||
with: | ||
PATTERNS: | | ||
collections/**/*.go | ||
collections/go.mod | ||
collections/go.sum | ||
- name: tests | ||
if: env.GIT_DIFF | ||
run: | | ||
cd collections | ||
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock rocksdb_build' ./... | ||
test-cosmovisor: | ||
runs-on: ubuntu-latest | ||
steps: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package collections | ||
|
||
import ( | ||
"errors" | ||
"math" | ||
|
||
storetypes "github.com/cosmos/cosmos-sdk/store/types" | ||
) | ||
|
||
var ( | ||
// ErrNotFound is returned when the provided key is not present in the StorageProvider. | ||
ErrNotFound = errors.New("collections: not found") | ||
// ErrEncoding is returned when something fails during key or value encoding/decoding. | ||
ErrEncoding = errors.New("collections: encoding error") | ||
) | ||
|
||
// StorageProvider is implemented by types | ||
// which provide a KVStore given a StoreKey. | ||
// It represents sdk.Context, it exists to | ||
// reduce dependencies. | ||
type StorageProvider interface { | ||
// KVStore returns a KVStore given its StoreKey. | ||
KVStore(key storetypes.StoreKey) storetypes.KVStore | ||
} | ||
|
||
// Prefix defines a segregation namespace | ||
// for specific collections objects. | ||
type Prefix struct { | ||
raw []byte // TODO(testinginprod): maybe add a humanized prefix field? | ||
} | ||
|
||
// Bytes returns the raw Prefix bytes. | ||
func (n Prefix) Bytes() []byte { return n.raw } | ||
|
||
// NewPrefix returns a Prefix given the provided namespace identifier. | ||
// In the same module, no prefixes should share the same starting bytes | ||
// meaning that having two namespaces whose bytes representation is: | ||
// p1 := []byte("prefix") | ||
// p2 := []byte("prefix1") | ||
// yields to iterations of p1 overlapping over p2. | ||
// If a numeric prefix is provided, it must be between 0 and 255 (uint8). | ||
// If out of bounds this function will panic. | ||
// Reason for which this function is constrained to `int` instead of `uint8` is for | ||
// API ergonomics, golang's type inference will infer int properly but not uint8 | ||
// meaning that developers would need to write NewPrefix(uint8(number)) for numeric | ||
// prefixes. | ||
func NewPrefix[T interface{ int | string | []byte }](identifier T) Prefix { | ||
i := any(identifier) | ||
var prefix []byte | ||
switch c := i.(type) { | ||
case int: | ||
if c > math.MaxUint8 || c < 0 { | ||
panic("invalid integer prefix value: must be between 0 and 255") | ||
} | ||
prefix = []byte{uint8(c)} | ||
case string: | ||
prefix = []byte(c) | ||
case []byte: | ||
identifierCopy := make([]byte, len(c)) | ||
copy(identifierCopy, c) | ||
prefix = identifierCopy | ||
} | ||
return Prefix{raw: prefix} | ||
} | ||
|
||
// KeyCodec defines a generic interface which is implemented | ||
// by types that are capable of encoding and decoding collections keys. | ||
type KeyCodec[T any] interface { | ||
// Encode writes the key bytes into the buffer. Returns the number of | ||
// bytes written. The implementer must expect the buffer to be at least | ||
// of length equal to Size(K) for all encodings. | ||
// It must also return the number of written bytes which must be | ||
// equal to Size(K) for all encodings not involving varints. | ||
// In case of encodings involving varints then the returned | ||
// number of written bytes is allowed to be smaller than Size(K). | ||
Encode(buffer []byte, key T) (int, error) | ||
// Decode reads from the provided bytes buffer to decode | ||
// the key T. Returns the number of bytes read, the type T | ||
// or an error in case of decoding failure. | ||
Decode(buffer []byte) (int, T, error) | ||
// Size returns the buffer size need to encode key T in binary format. | ||
// The returned value must match what is computed by Encode for all | ||
// encodings except the ones involving varints. Varints are expected | ||
// to return the maximum varint bytes buffer length, at the risk of | ||
// over-estimating in order to pick the most performant path. | ||
Size(key T) int | ||
// Stringify returns a string representation of T. | ||
Stringify(key T) string | ||
// KeyType returns a string identifier for the type of the key. | ||
KeyType() string | ||
} | ||
|
||
// ValueCodec defines a generic interface which is implemented | ||
// by types that are capable of encoding and decoding collection values. | ||
type ValueCodec[T any] interface { | ||
// Encode encodes the value T into binary format. | ||
Encode(value T) ([]byte, error) | ||
// Decode returns the type T given its binary representation. | ||
Decode(b []byte) (T, error) | ||
// Stringify returns a string representation of T. | ||
Stringify(value T) string | ||
// ValueType returns the identifier for the type. | ||
ValueType() string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package collections | ||
|
||
import ( | ||
"context" | ||
"math" | ||
"testing" | ||
|
||
"github.com/cosmos/cosmos-sdk/store/mem" | ||
"github.com/cosmos/cosmos-sdk/store/types" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
var _ StorageProvider = (*mockStorageProvider)(nil) | ||
|
||
type mockStorageProvider struct { | ||
context.Context | ||
store types.KVStore | ||
} | ||
|
||
func (m mockStorageProvider) KVStore(key types.StoreKey) types.KVStore { | ||
return m.store | ||
} | ||
|
||
func deps() (types.StoreKey, context.Context) { | ||
kv := mem.NewStore() | ||
key := types.NewKVStoreKey("test") | ||
return key, mockStorageProvider{store: kv} | ||
} | ||
|
||
// checkKeyCodec asserts the correct behaviour of a KeyCodec over the type T. | ||
func checkKeyCodec[T any](t *testing.T, encoder KeyCodec[T], key T) { | ||
buffer := make([]byte, encoder.Size(key)) | ||
written, err := encoder.Encode(buffer, key) | ||
require.NoError(t, err) | ||
require.Equal(t, len(buffer), written) | ||
read, decodedKey, err := encoder.Decode(buffer) | ||
require.NoError(t, err) | ||
require.Equal(t, len(buffer), read, "encoded key and read bytes must have same size") | ||
require.Equal(t, key, decodedKey, "encoding and decoding produces different keys") | ||
} | ||
|
||
// checkValueCodec asserts the correct behaviour of a ValueCodec over the type T. | ||
func checkValueCodec[T any](t *testing.T, encoder ValueCodec[T], value T) { | ||
encodedValue, err := encoder.Encode(value) | ||
require.NoError(t, err) | ||
decodedValue, err := encoder.Decode(encodedValue) | ||
require.NoError(t, err) | ||
require.Equal(t, value, decodedValue, "encoding and decoding produces different values") | ||
} | ||
|
||
func TestPrefix(t *testing.T) { | ||
t.Run("panics on invalid int", func(t *testing.T) { | ||
require.Panics(t, func() { | ||
NewPrefix(math.MaxUint8 + 1) | ||
}) | ||
}) | ||
|
||
t.Run("string", func(t *testing.T) { | ||
require.Equal(t, []byte("prefix"), NewPrefix("prefix").Bytes()) | ||
}) | ||
|
||
t.Run("int", func(t *testing.T) { | ||
require.Equal(t, []byte{0x1}, NewPrefix(1).Bytes()) | ||
}) | ||
|
||
t.Run("[]byte", func(t *testing.T) { | ||
bytes := []byte("prefix") | ||
prefix := NewPrefix(bytes) | ||
require.Equal(t, bytes, prefix.Bytes()) | ||
// assert if modification happen they do not propagate to prefix | ||
bytes[0] = 0x0 | ||
require.Equal(t, []byte("prefix"), prefix.Bytes()) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
module cosmossdk.io/collections | ||
|
||
go 1.19 | ||
|
||
require ( | ||
github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20221207205747-f3be41836f4d | ||
github.com/stretchr/testify v1.8.1 | ||
) | ||
|
||
require ( | ||
cosmossdk.io/errors v1.0.0-beta.7 // indirect | ||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect | ||
github.com/cespare/xxhash v1.1.0 // indirect | ||
github.com/cespare/xxhash/v2 v2.1.2 // indirect | ||
github.com/confio/ics23/go v0.9.0 // indirect | ||
github.com/cosmos/gogoproto v1.4.3 // indirect | ||
github.com/cosmos/gorocksdb v1.2.0 // indirect | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect | ||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect | ||
github.com/dgraph-io/ristretto v0.1.1 // indirect | ||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect | ||
github.com/dustin/go-humanize v1.0.0 // indirect | ||
github.com/go-kit/log v0.2.1 // indirect | ||
github.com/go-logfmt/logfmt v0.5.1 // indirect | ||
github.com/gogo/protobuf v1.3.2 // indirect | ||
github.com/golang/glog v1.0.0 // indirect | ||
github.com/golang/protobuf v1.5.2 // indirect | ||
github.com/golang/snappy v0.0.4 // indirect | ||
github.com/google/btree v1.1.2 // indirect | ||
github.com/jmhodges/levigo v1.0.0 // indirect | ||
github.com/klauspost/compress v1.15.12 // indirect | ||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect | ||
github.com/pkg/errors v0.9.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
github.com/sasha-s/go-deadlock v0.3.1 // indirect | ||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect | ||
github.com/tendermint/go-amino v0.16.0 // indirect | ||
github.com/tendermint/tendermint v0.37.0-rc2 // indirect | ||
github.com/tendermint/tm-db v0.6.7 // indirect | ||
go.etcd.io/bbolt v1.3.6 // indirect | ||
golang.org/x/crypto v0.4.0 // indirect | ||
golang.org/x/exp v0.0.0-20221019170559-20944726eadf // indirect | ||
golang.org/x/net v0.3.0 // indirect | ||
golang.org/x/sys v0.3.0 // indirect | ||
golang.org/x/text v0.5.0 // indirect | ||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect | ||
google.golang.org/grpc v1.51.0 // indirect | ||
google.golang.org/protobuf v1.28.1 // indirect | ||
gopkg.in/yaml.v2 v2.4.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
sigs.k8s.io/yaml v1.3.0 // indirect | ||
) |
Oops, something went wrong.