chore: set collections to main (#23696)

This commit is contained in:
Tyler 2025-02-13 12:46:33 -08:00 committed by GitHub
parent 3120df7ff7
commit 256e3e143b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 3556 additions and 301 deletions

View File

@ -31,16 +31,59 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]
## [v1.1.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv1.1.0)
### Improvements
* [#23515](https://github.com/cosmos/cosmos-sdk/pull/23515) Bring in `collections/protocodec` go module as package within `collections` module.
## [v1.0.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv1.0.0)
### 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.
* [#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.
* [#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/packages/collections) to know how to use the APIs.
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.

View File

@ -23,11 +23,12 @@ go get cosmossdk.io/collections
## Core types
Collections offers 5 different APIs to work with state, which will be explored in the next sections, these APIs are:
- ``Map``: to work with typed arbitrary KV pairings.
- ``KeySet``: to work with just typed keys
- ``Item``: to work with just one typed value
- ``Sequence``: which is a monotonically increasing number.
- ``IndexedMap``: which combines ``Map`` and `KeySet` to provide a `Map` with indexing capabilities.
* ``Map``: to work with typed arbitrary KV pairings.
* ``KeySet``: to work with just typed keys
* ``Item``: to work with just one typed value
* ``Sequence``: which is a monotonically increasing number.
* ``IndexedMap``: which combines ``Map`` and `KeySet` to provide a `Map` with indexing capabilities.
## Preliminary components
@ -82,15 +83,17 @@ The second argument passed to our ``KeySet`` is a `collections.Prefix`, a prefix
where all the state of a specific collection will be saved.
Since a module can have multiple collections, the following is expected:
- module params will become a `collections.Item`
- the `AllowList` is a `collections.KeySet`
* module params will become a `collections.Item`
* the `AllowList` is a `collections.KeySet`
We don't want a collection to write over the state of the other collection so we pass it a prefix, which defines a storage
partition owned by the collection.
If you already built modules, the prefix translates to the items you were creating in your ``types/keys.go`` file, example: https://github.com/cosmos/cosmos-sdk/blob/main/x/feegrant/key.go#L27
If you already built modules, the prefix translates to the items you were creating in your ``types/keys.go`` file, example: https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-rc.1/x/feegrant/key.go#L16~L22
your old:
```go
var (
// FeeAllowanceKeyPrefix is the set of the kvstore for fee allowance data
@ -102,7 +105,9 @@ var (
FeeAllowanceQueueKeyPrefix = []byte{0x01}
)
```
becomes:
```go
var (
// FeeAllowanceKeyPrefix is the set of the kvstore for fee allowance data
@ -130,6 +135,7 @@ prefix2 := collections.NewPrefix("prefix") // THIS IS BAD!
prefix1 := collections.NewPrefix("a")
prefix2 := collections.NewPrefix("aa") // prefix2 starts with the same as prefix1: BAD!!!
```
### Human-Readable Name
The third parameter we pass to a collection is a string, which is a human-readable name.
@ -157,7 +163,7 @@ You might need to implement them only if you're migrating to collections and the
Let's explore an example:
````go
```go
package collections
import (
@ -180,14 +186,14 @@ func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
IDs: collections.NewMap(sb, IDsPrefix, "ids", collections.StringKey, collections.Uint64Value),
}
}
````
```
We're now instantiating a map where the key is string and the value is `uint64`.
We already know the first three arguments of the ``NewMap`` function.
The fourth parameter is our `KeyCodec`, we know that the ``Map`` has `string` as key so we pass it a `KeyCodec` that handles strings as keys.
The fifth parameter is our `ValueCodec`, we know that the `Map` as a `uint64` as value so we pass it a `ValueCodec` that handles uint64.
The fifth parameter is our `ValueCodec`, we know that the `Map` has a `uint64` as value so we pass it a `ValueCodec` that handles uint64.
Collections already comes with all the required implementations for golang primitive types.
@ -390,6 +396,7 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, validator sdk.ValAddress) error
return nil
}
```
The first difference we notice is that `KeySet` needs use to specify only one type parameter: the key (`sdk.ValAddress` in this case).
The second difference we notice is that `KeySet` in its `NewKeySet` function does not require
us to specify a `ValueCodec` but only a `KeyCodec`. This is because a `KeySet` only saves keys and not values.
@ -431,7 +438,7 @@ import (
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
stakingtypes "cosmossdk.io/x/staking/types"
)
var ParamsPrefix = collections.NewPrefix(0)
@ -580,7 +587,7 @@ Let's analyse each method in the example and how it makes use of the `Iterate` a
In `GetAllAccounts` we pass to our `Iterate` a nil `Ranger`. This means that the returned `Iterator` will include
all the existing keys within the collection.
Then we use some the `Values` method from the returned `Iterator` API to collect all the values into a slice.
Then we use the `Values` method from the returned `Iterator` API to collect all the values into a slice.
`Iterator` offers other methods such as `Keys()` to collect only the keys and not the values and `KeyValues` to collect
all the keys and values.
@ -595,9 +602,9 @@ we instruct it to report us results in reverse order through `Descending`
Then we pass the range instruction to `Iterate` and get an `Iterator`, which will contain only the results
we specified in the range.
Then we use again th `Values` method of the `Iterator` to collect all the results.
Then we use again the `Values` method of the `Iterator` to collect all the results.
`collections.Range` also offers a `Prefix` API which is not appliable to all keys types,
`collections.Range` also offers a `Prefix` API which is not applicable to all keys types,
for example uint64 cannot be prefix because it is of constant size, but a `string` key
can be prefixed.
@ -670,7 +677,7 @@ func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
Balances: collections.NewMap(
sb, BalancesPrefix, "balances",
collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey),
math.IntValue,
sdk.IntValue,
),
}
}
@ -679,9 +686,10 @@ func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
#### The Map Key definition
First of all we can see that in order to define a composite key of two elements we use the `collections.Pair` type:
````go
```go
collections.Map[collections.Pair[sdk.AccAddress, string], math.Int]
````
```
`collections.Pair` defines a key composed of two other keys, in our case the first part is `sdk.AccAddress`, the second
part is `string`.
@ -698,7 +706,7 @@ encode the second part of the key.
Let's expand on the example we used before:
````go
```go
var BalancesPrefix = collections.NewPrefix(1)
type Keeper struct {
@ -712,7 +720,7 @@ func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
Balances: collections.NewMap(
sb, BalancesPrefix, "balances",
collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey),
math.IntValue,
sdk.IntValue,
),
}
}
@ -758,7 +766,7 @@ func (k Keeper) GetAllAddressBalancesBetween(ctx sdk.Context, address sdk.AccAdd
}
...
}
````
```
#### SetBalance
@ -769,7 +777,7 @@ We use the `collections.Join` function to generate the composite key.
`collections.Pair` contains the two keys we have joined, it also exposes two methods: `K1` to fetch the 1st part of the
key and `K2` to fetch the second part.
As always, we use the `collections.Map.Set` method to map the composite key to our value (`math.Int`in this case)
As always, we use the `collections.Map.Set` method to map the composite key to our value (`math.Int` in this case)
#### GetBalance
@ -788,7 +796,7 @@ in `Pair` keys iterations.
```
As we can see here we're passing the type parameters of the `collections.Pair` because golang type inference
with respect to generics is not as permissive as other languages, so we need to explitly say what are the types of the pair key.
with respect to generics is not as permissive as other languages, so we need to explicitly say what are the types of the pair key.
#### GetAllAddressesBalancesBetween
@ -811,7 +819,7 @@ type BaseAccount struct {
```
First of all, when we save our accounts in state we map them using a primary key `sdk.AccAddress`.
If it were to be a `collections.Map` it would be `collections.Map[sdk.AccAddres, authtypes.BaseAccount]`.
If it were to be a `collections.Map` it would be `collections.Map[sdk.AccAddress, authtypes.BaseAccount]`.
Then we also want to be able to get an account not only by its `sdk.AccAddress`, but also by its `AccountNumber`.
@ -829,10 +837,6 @@ type AccountsIndexes struct {
Number *indexes.Unique[uint64, sdk.AccAddress, authtypes.BaseAccount]
}
func (a AccountsIndexes) IndexesList() []collections.Index[sdk.AccAddress, authtypes.BaseAccount] {
return []collections.Index[sdk.AccAddress, authtypes.BaseAccount]{a.Number}
}
func NewAccountIndexes(sb *collections.SchemaBuilder) AccountsIndexes {
return AccountsIndexes{
Number: indexes.NewUnique(
@ -856,18 +860,26 @@ Then we can see in our `AccountIndexes` struct the `Number` field is defined as:
```
Where the first type parameter is `uint64`, which is the field type of our index.
The second type parameter is the primary key `sdk.AccAddress`
The second type parameter is the primary key `sdk.AccAddress`.
And the third type parameter is the actual object we're storing `authtypes.BaseAccount`.
Then we implement a function called `IndexesList` on our `AccountIndexes` struct, this will be used
by the `IndexedMap` to keep the underlying map in sync with the indexes, in our case `Number`.
This function just needs to return the slice of indexes contained in the struct.
Then we create a `NewAccountIndexes` function that instantiates and returns the `AccountsIndexes` struct.
The function takes a `SchemaBuilder`. Then we instantiate our `indexes.Unique`, let's analyse the arguments we pass to
`indexes.NewUnique`.
#### NOTE: indexes list
The `AccountsIndexes` struct contains the indexes, the `NewIndexedMap` function will infer the indexes form that struct
using reflection, this happens only at init and is not computationally expensive. In case you want to explicitly declare
indexes: implement the `Indexes` interface in the `AccountsIndexes` struct:
```go
func (a AccountsIndexes) IndexesList() []collections.Index[sdk.AccAddress, authtypes.BaseAccount] {
return []collections.Index[sdk.AccAddress, authtypes.BaseAccount]{a.Number}
}
```
#### Instantiating a `indexes.Unique`
The first three arguments, we already know them, they are: `SchemaBuilder`, `Prefix` which is our index prefix (the partition
@ -1117,3 +1129,82 @@ func (k Keeper) GetAccount(ctx sdk.context, addr sdk.AccAddress) (sdk.AccountI,
return k.Accounts.Get(ctx, addr)
}
```
## Triple key
The `collections.Triple` is a special type of key composed of three keys, it's identical to `collections.Pair`.
Let's see an example.
```go
package example
import (
"context"
"cosmossdk.io/collections"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
)
type AccAddress = string
type ValAddress = string
type Keeper struct {
// let's simulate we have redelegations which are stored as a triple key composed of
// the delegator, the source validator and the destination validator.
Redelegations collections.KeySet[collections.Triple[AccAddress, ValAddress, ValAddress]]
}
func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
sb := collections.NewSchemaBuilder(sdk.OpenKVStore(storeKey))
return Keeper{
Redelegations: collections.NewKeySet(sb, collections.NewPrefix(0), "redelegations", collections.TripleKeyCodec(collections.StringKey, collections.StringKey, collections.StringKey)
}
}
// RedelegationsByDelegator iterates over all the redelegations of a given delegator and calls onResult providing
// each redelegation from source validator towards the destination validator.
func (k Keeper) RedelegationsByDelegator(ctx context.Context, delegator AccAddress, onResult func(src, dst ValAddress) (stop bool, err error)) error {
rng := collections.NewPrefixedTripleRange[AccAddress, ValAddress, ValAddress](delegator)
return k.Redelegations.Walk(ctx, rng, func(key collections.Triple[AccAddress, ValAddress, ValAddress]) (stop bool, err error) {
return onResult(key.K2(), key.K3())
})
}
// RedelegationsByDelegatorAndValidator iterates over all the redelegations of a given delegator and its source validator and calls onResult for each
// destination validator.
func (k Keeper) RedelegationsByDelegatorAndValidator(ctx context.Context, delegator AccAddress, validator ValAddress, onResult func(dst ValAddress) (stop bool, err error)) error {
rng := collections.NewSuperPrefixedTripleRange[AccAddress, ValAddress, ValAddress](delegator, validator)
return k.Redelegations.Walk(ctx, rng, func(key collections.Triple[AccAddress, ValAddress, ValAddress]) (stop bool, err error) {
return onResult(key.K3())
})
}
```
## Advanced Usages
### Alternative Value Codec
The `codec.AltValueCodec` allows a collection to decode values using a different codec than the one used to encode them.
Basically it enables to decode two different byte representations of the same concrete value.
It can be used to lazily migrate values from one bytes representation to another, as long as the new representation is
not able to decode the old one.
A concrete example can be found in `x/bank` where the balance was initially stored as `Coin` and then migrated to `Int`.
```go
var BankBalanceValueCodec = codec.NewAltValueCodec(sdk.IntValue, func(b []byte) (sdk.Int, error) {
coin := sdk.Coin{}
err := coin.Unmarshal(b)
if err != nil {
return sdk.Int{}, err
}
return coin.Amount, nil
})
```
The above example shows how to create an `AltValueCodec` that can decode both `sdk.Int` and `sdk.Coin` values. The provided
decoder function will be used as a fallback in case the default decoder fails. When the value will be encoded back into state
it will use the default encoder. This allows to lazily migrate values to a new bytes representation.

View File

@ -0,0 +1,47 @@
package codec
// NewAltValueCodec returns a new AltValueCodec. canonicalValueCodec is the codec that you want the value
// to be encoded and decoded as, alternativeDecoder is a function that will attempt to decode the value
// in case the canonicalValueCodec fails to decode it.
func NewAltValueCodec[V any](canonicalValueCodec ValueCodec[V], alternativeDecoder func([]byte) (V, error)) ValueCodec[V] {
return AltValueCodec[V]{
canonicalValueCodec: canonicalValueCodec,
alternativeDecoder: alternativeDecoder,
}
}
// AltValueCodec is a codec that can decode a value from state in an alternative format.
// This is useful for migrating data from one format to another. For example, in x/bank
// balances were initially encoded as sdk.Coin, now they are encoded as math.Int.
// The AltValueCodec will be trying to decode the value as math.Int, and if that fails,
// it will attempt to decode it as sdk.Coin.
// NOTE: if the canonical format can also decode the alternative format, then this codec
// will produce undefined and undesirable behavior.
type AltValueCodec[V any] struct {
canonicalValueCodec ValueCodec[V]
alternativeDecoder func([]byte) (V, error)
}
// Decode will attempt to decode the value from state using the canonical value codec.
// If it fails to decode, it will attempt to decode the value using the alternative decoder.
func (a AltValueCodec[V]) Decode(b []byte) (V, error) {
v, err := a.canonicalValueCodec.Decode(b)
if err != nil {
return a.alternativeDecoder(b)
}
return v, nil
}
// Below there is the implementation of ValueCodec relying on the canonical value codec.
func (a AltValueCodec[V]) Encode(value V) ([]byte, error) { return a.canonicalValueCodec.Encode(value) }
func (a AltValueCodec[V]) EncodeJSON(value V) ([]byte, error) {
return a.canonicalValueCodec.EncodeJSON(value)
}
func (a AltValueCodec[V]) DecodeJSON(b []byte) (V, error) { return a.canonicalValueCodec.DecodeJSON(b) }
func (a AltValueCodec[V]) Stringify(value V) string { return a.canonicalValueCodec.Stringify(value) }
func (a AltValueCodec[V]) ValueType() string { return a.canonicalValueCodec.ValueType() }

View File

@ -0,0 +1,53 @@
package codec_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/collections/codec"
"cosmossdk.io/collections/colltest"
)
type altValue struct {
Value uint64 `json:"value"`
}
func TestAltValueCodec(t *testing.T) {
// we assume we want to migrate the value from json(altValue) to just be
// the raw value uint64.
canonical := codec.KeyToValueCodec(codec.NewUint64Key[uint64]())
alternative := func(v []byte) (uint64, error) {
var alt altValue
err := json.Unmarshal(v, &alt)
if err != nil {
return 0, err
}
return alt.Value, nil
}
cdc := codec.NewAltValueCodec(canonical, alternative)
t.Run("decodes alternative value", func(t *testing.T) {
expected := uint64(100)
alternativeEncodedBytes, err := json.Marshal(altValue{Value: expected})
require.NoError(t, err)
got, err := cdc.Decode(alternativeEncodedBytes)
require.NoError(t, err)
require.Equal(t, expected, got)
})
t.Run("decodes canonical value", func(t *testing.T) {
expected := uint64(100)
canonicalEncodedBytes, err := cdc.Encode(expected)
require.NoError(t, err)
got, err := cdc.Decode(canonicalEncodedBytes)
require.NoError(t, err)
require.Equal(t, expected, got)
})
t.Run("conformance", func(t *testing.T) {
colltest.TestValueCodec(t, cdc, uint64(100))
})
}

View File

@ -6,7 +6,7 @@ import (
"strconv"
)
func NewBoolKey[T ~bool]() KeyCodec[T] { return boolKey[T]{} }
func NewBoolKey[T ~bool]() NameableKeyCodec[T] { return boolKey[T]{} }
type boolKey[T ~bool] struct{}
@ -33,7 +33,7 @@ func (b boolKey[T]) Decode(buffer []byte) (int, T, error) {
}
}
func (b boolKey[T]) Size(key T) int { return 1 }
func (b boolKey[T]) Size(_ T) int { return 1 }
func (b boolKey[T]) EncodeJSON(value T) ([]byte, error) {
return json.Marshal(value)
@ -64,3 +64,7 @@ func (b boolKey[T]) DecodeNonTerminal(buffer []byte) (int, T, error) {
func (b boolKey[T]) SizeNonTerminal(key T) int {
return b.Size(key)
}
func (b boolKey[T]) WithName(name string) KeyCodec[T] {
return NamedKeyCodec[T]{KeyCodec: b, Name: name}
}

View File

@ -10,7 +10,7 @@ import (
// using the BytesKey KeyCodec.
const MaxBytesKeyNonTerminalSize = math.MaxUint8
func NewBytesKey[T ~[]byte]() KeyCodec[T] { return bytesKey[T]{} }
func NewBytesKey[T ~[]byte]() NameableKeyCodec[T] { return bytesKey[T]{} }
type bytesKey[T ~[]byte] struct{}
@ -77,3 +77,7 @@ func (bytesKey[T]) DecodeNonTerminal(buffer []byte) (int, T, error) {
func (bytesKey[T]) SizeNonTerminal(key T) int {
return len(key) + 1
}
func (b bytesKey[T]) WithName(name string) KeyCodec[T] {
return NamedKeyCodec[T]{KeyCodec: b, Name: name}
}

View File

@ -125,7 +125,9 @@ type UntypedValueCodec struct {
}
// KeyToValueCodec converts a KeyCodec into a ValueCodec.
func KeyToValueCodec[K any](keyCodec KeyCodec[K]) ValueCodec[K] { return keyToValueCodec[K]{keyCodec} }
func KeyToValueCodec[K any](keyCodec KeyCodec[K]) NameableValueCodec[K] {
return keyToValueCodec[K]{kc: keyCodec}
}
// keyToValueCodec is a ValueCodec that wraps a KeyCodec to make it behave like a ValueCodec.
type keyToValueCodec[K any] struct {
@ -167,3 +169,7 @@ func (k keyToValueCodec[K]) Stringify(value K) string {
func (k keyToValueCodec[K]) ValueType() string {
return k.kc.KeyType()
}
func (k keyToValueCodec[K]) WithName(name string) ValueCodec[K] {
return NamedValueCodec[K]{ValueCodec: k, Name: name}
}

View File

@ -7,7 +7,7 @@ import (
)
func TestUntypedValueCodec(t *testing.T) {
vc := NewUntypedValueCodec(KeyToValueCodec(NewStringKeyCodec[string]()))
vc := NewUntypedValueCodec(ValueCodec[string](KeyToValueCodec(KeyCodec[string](NewStringKeyCodec[string]()))))
t.Run("encode/decode", func(t *testing.T) {
_, err := vc.Encode(0)

View File

@ -0,0 +1,102 @@
package codec
import (
"encoding/json"
"fmt"
"cosmossdk.io/schema"
)
// HasSchemaCodec is an interface that all codec's should implement in order
// to properly support indexing. It is not required by KeyCodec or ValueCodec
// in order to preserve backwards compatibility, but a future version of collections
// may make it required and all codec's should aim to implement it. If it is not
// implemented, fallback defaults will be used for indexing that may be sub-optimal.
//
// Implementations of HasSchemaCodec should test that they are conformant using
// schema.ValidateObjectKey or schema.ValidateObjectValue depending on whether
// the codec is a KeyCodec or ValueCodec respectively.
type HasSchemaCodec[T any] interface {
// SchemaCodec returns the schema codec for the collections codec.
SchemaCodec() (SchemaCodec[T], error)
}
// SchemaCodec is a codec that supports converting collection codec values to and
// from schema codec values.
type SchemaCodec[T any] struct {
// Fields are the schema fields that the codec represents. If this is empty,
// it will be assumed that this codec represents no value (such as an item key
// or key set value).
Fields []schema.Field
// ToSchemaType converts a codec value of type T to a value corresponding to
// a schema object key or value (depending on whether this is a key or value
// codec). The returned value should pass validation with schema.ValidateObjectKey
// or schema.ValidateObjectValue with the fields specified in Fields.
// If this function is nil, it will be assumed that T already represents a
// value that conforms to a schema value without any further conversion.
ToSchemaType func(T) (any, error)
// FromSchemaType converts a schema object key or value to T.
// If this function is nil, it will be assumed that T already represents a
// value that conforms to a schema value without any further conversion.
FromSchemaType func(any) (T, error)
}
// KeySchemaCodec gets the schema codec for the provided KeyCodec either
// by casting to HasSchemaCodec or returning a fallback codec.
func KeySchemaCodec[K any](cdc KeyCodec[K]) (SchemaCodec[K], error) {
if indexable, ok := cdc.(HasSchemaCodec[K]); ok {
return indexable.SchemaCodec()
} else {
return FallbackSchemaCodec[K](), nil
}
}
// ValueSchemaCodec gets the schema codec for the provided ValueCodec either
// by casting to HasSchemaCodec or returning a fallback codec.
func ValueSchemaCodec[V any](cdc ValueCodec[V]) (SchemaCodec[V], error) {
if indexable, ok := cdc.(HasSchemaCodec[V]); ok {
return indexable.SchemaCodec()
} else {
return FallbackSchemaCodec[V](), nil
}
}
// FallbackSchemaCodec returns a fallback schema codec for T when one isn't explicitly
// specified with HasSchemaCodec. It maps all simple types directly to schema kinds
// and converts everything else to JSON String.
func FallbackSchemaCodec[T any]() SchemaCodec[T] {
var t T
kind := schema.KindForGoValue(t)
if err := kind.Validate(); err == nil {
return 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,
}
} else {
// we default to encoding everything to JSON String
return SchemaCodec[T]{
Fields: []schema.Field{{Kind: schema.StringKind}},
ToSchemaType: func(t T) (any, error) {
bz, err := json.Marshal(t)
return string(json.RawMessage(bz)), err
},
FromSchemaType: func(a any) (T, error) {
var t T
sz, ok := a.(string)
if !ok {
return t, fmt.Errorf("expected string, got %T", a)
}
err := json.Unmarshal([]byte(sz), &t)
return t, err
},
}
}
}

View File

@ -7,7 +7,7 @@ import (
"strconv"
)
func NewInt64Key[T ~int64]() KeyCodec[T] { return int64Key[T]{} }
func NewInt64Key[T ~int64]() NameableKeyCodec[T] { return int64Key[T]{} }
type int64Key[T ~int64] struct{}
@ -64,7 +64,11 @@ func (i int64Key[T]) SizeNonTerminal(_ T) int {
return 8
}
func NewInt32Key[T ~int32]() KeyCodec[T] {
func (i int64Key[T]) WithName(name string) KeyCodec[T] {
return NamedKeyCodec[T]{KeyCodec: i, Name: name}
}
func NewInt32Key[T ~int32]() NameableKeyCodec[T] {
return int32Key[T]{}
}
@ -121,3 +125,7 @@ func (i int32Key[T]) DecodeNonTerminal(buffer []byte) (int, T, error) {
func (i int32Key[T]) SizeNonTerminal(_ T) int {
return 4
}
func (i int32Key[T]) WithName(name string) KeyCodec[T] {
return NamedKeyCodec[T]{KeyCodec: i, Name: name}
}

View File

@ -0,0 +1,63 @@
package codec
import "fmt"
// NameableKeyCodec is a KeyCodec that can be named.
type NameableKeyCodec[T any] interface {
KeyCodec[T]
// WithName returns the KeyCodec with the provided name.
WithName(name string) KeyCodec[T]
}
// NameableValueCodec is a ValueCodec that can be named.
type NameableValueCodec[T any] interface {
ValueCodec[T]
// WithName returns the ValueCodec with the provided name.
WithName(name string) ValueCodec[T]
}
// NamedKeyCodec wraps a KeyCodec with a name.
// The underlying key codec MUST have exactly one field in its schema.
type NamedKeyCodec[T any] struct {
KeyCodec[T]
// Name is the name of the KeyCodec in the schema.
Name string
}
// SchemaCodec returns the schema codec for the named key codec.
func (n NamedKeyCodec[T]) SchemaCodec() (SchemaCodec[T], error) {
cdc, err := KeySchemaCodec[T](n.KeyCodec)
if err != nil {
return SchemaCodec[T]{}, err
}
return withName(cdc, n.Name)
}
// NamedValueCodec wraps a ValueCodec with a name.
// The underlying value codec MUST have exactly one field in its schema.
type NamedValueCodec[T any] struct {
ValueCodec[T]
// Name is the name of the ValueCodec in the schema.
Name string
}
// SchemaCodec returns the schema codec for the named value codec.
func (n NamedValueCodec[T]) SchemaCodec() (SchemaCodec[T], error) {
cdc, err := ValueSchemaCodec[T](n.ValueCodec)
if err != nil {
return SchemaCodec[T]{}, err
}
return withName(cdc, n.Name)
}
func withName[T any](cdc SchemaCodec[T], name string) (SchemaCodec[T], error) {
if len(cdc.Fields) != 1 {
return SchemaCodec[T]{}, fmt.Errorf("expected exactly one field to be named, got %d", len(cdc.Fields))
}
cdc.Fields[0].Name = name
return cdc, nil
}

View File

@ -6,7 +6,7 @@ import (
"fmt"
)
func NewStringKeyCodec[T ~string]() KeyCodec[T] { return stringKey[T]{} }
func NewStringKeyCodec[T ~string]() NameableKeyCodec[T] { return stringKey[T]{} }
const (
// StringDelimiter defines the delimiter of a string key when used in non-terminal encodings.
@ -66,3 +66,7 @@ func (stringKey[T]) Stringify(key T) string {
func (stringKey[T]) KeyType() string {
return "string"
}
func (s stringKey[T]) WithName(name string) KeyCodec[T] {
return NamedKeyCodec[T]{KeyCodec: s, Name: name}
}

View File

@ -7,7 +7,7 @@ import (
"strconv"
)
func NewUint64Key[T ~uint64]() KeyCodec[T] { return uint64Key[T]{} }
func NewUint64Key[T ~uint64]() NameableKeyCodec[T] { return uint64Key[T]{} }
type uint64Key[T ~uint64] struct{}
@ -55,7 +55,11 @@ func (uint64Key[T]) KeyType() string {
return "uint64"
}
func NewUint32Key[T ~uint32]() KeyCodec[T] { return uint32Key[T]{} }
func (u uint64Key[T]) WithName(name string) KeyCodec[T] {
return NamedKeyCodec[T]{KeyCodec: u, Name: name}
}
func NewUint32Key[T ~uint32]() NameableKeyCodec[T] { return uint32Key[T]{} }
type uint32Key[T ~uint32] struct{}
@ -95,7 +99,11 @@ func (u uint32Key[T]) DecodeNonTerminal(buffer []byte) (int, T, error) { return
func (uint32Key[T]) SizeNonTerminal(_ T) int { return 4 }
func NewUint16Key[T ~uint16]() KeyCodec[T] { return uint16Key[T]{} }
func (u uint32Key[T]) WithName(name string) KeyCodec[T] {
return NamedKeyCodec[T]{KeyCodec: u, Name: name}
}
func NewUint16Key[T ~uint16]() NameableKeyCodec[T] { return uint16Key[T]{} }
type uint16Key[T ~uint16] struct{}
@ -135,6 +143,10 @@ func (u uint16Key[T]) DecodeNonTerminal(buffer []byte) (int, T, error) { return
func (u uint16Key[T]) SizeNonTerminal(key T) int { return u.Size(key) }
func (u uint16Key[T]) WithName(name string) KeyCodec[T] {
return NamedKeyCodec[T]{KeyCodec: u, Name: name}
}
func uintEncodeJSON(value uint64) ([]byte, error) {
str := `"` + strconv.FormatUint(value, 10) + `"`
return []byte(str), nil

View File

@ -3,10 +3,11 @@ package collections
import (
"context"
"errors"
io "io"
"io"
"math"
"cosmossdk.io/collections/codec"
"cosmossdk.io/schema"
)
var (
@ -29,7 +30,7 @@ var (
Uint64Key = codec.NewUint64Key[uint64]()
// Int32Key can be used to encode int32 keys. Encoding retains ordering by toggling the MSB.
Int32Key = codec.NewInt32Key[int32]()
// Int64Key can be used to encode int64. Encoding retains ordering by toggling the MSB.
// Int64Key can be used to encode int64 keys. Encoding retains ordering by toggling the MSB.
Int64Key = codec.NewInt64Key[int64]()
// StringKey can be used to encode string keys. The encoding just converts the string
// to bytes.
@ -90,6 +91,22 @@ type Collection interface {
ValueCodec() codec.UntypedValueCodec
genesisHandler
// collectionSchemaCodec returns the schema codec for this collection.
schemaCodec() (*collectionSchemaCodec, error)
// isSecondaryIndex indicates that this collection represents a secondary index
// in the schema and should be excluded from the module's user facing schema.
isSecondaryIndex() bool
}
// collectionSchemaCodec maps a collection to a schema object type and provides
// decoders and encoders to and from schema values and raw kv-store bytes.
type collectionSchemaCodec struct {
coll Collection
objectType schema.StateObjectType
keyDecoder func([]byte) (any, error)
valueDecoder func([]byte) (any, error)
}
// Prefix defines a segregation bytes namespace for specific collections objects.
@ -157,3 +174,5 @@ func (c collectionImpl[K, V]) exportGenesis(ctx context.Context, w io.Writer) er
}
func (c collectionImpl[K, V]) defaultGenesis(w io.Writer) error { return c.m.defaultGenesis(w) }
func (c collectionImpl[K, V]) isSecondaryIndex() bool { return c.m.isSecondaryIndex }

View File

@ -5,49 +5,16 @@ import (
"math"
"testing"
db "github.com/cosmos/cosmos-db"
"github.com/stretchr/testify/require"
"cosmossdk.io/core/store"
"cosmossdk.io/core/testing"
)
type testStore struct {
db db.DB
}
func (t testStore) OpenKVStore(ctx context.Context) store.KVStore {
return t
}
func (t testStore) Get(key []byte) ([]byte, error) {
return t.db.Get(key)
}
func (t testStore) Has(key []byte) (bool, error) {
return t.db.Has(key)
}
func (t testStore) Set(key, value []byte) error {
return t.db.Set(key, value)
}
func (t testStore) Delete(key []byte) error {
return t.db.Delete(key)
}
func (t testStore) Iterator(start, end []byte) (store.Iterator, error) {
return t.db.Iterator(start, end)
}
func (t testStore) ReverseIterator(start, end []byte) (store.Iterator, error) {
return t.db.ReverseIterator(start, end)
}
var _ store.KVStore = testStore{}
func deps() (store.KVStoreService, context.Context) {
kv := db.NewMemDB()
return &testStore{kv}, context.Background()
ctx := coretesting.Context()
kv := coretesting.KVStoreService(ctx, "test")
return kv, ctx
}
func TestPrefix(t *testing.T) {

View File

@ -40,6 +40,11 @@ func TestKeyCodec[T any](t *testing.T, keyCodec codec.KeyCodec[T], key T) {
decoded, err := keyCodec.DecodeJSON(keyJSON)
require.NoError(t, err)
require.Equal(t, key, decoded, "json encoding and decoding did not produce the same results")
// check type
require.NotEmpty(t, keyCodec.KeyType())
// check string
_ = keyCodec.Stringify(key)
}
// TestValueCodec asserts the correct behavior of a ValueCodec over the type T.
@ -119,7 +124,7 @@ func (m mockValueCodec[T]) Decode(b []byte) (t T, err error) {
typ, exists := m.seenTypes[wrappedValue.TypeName]
if !exists {
return t, fmt.Errorf("uknown type %s, you're dealing with interfaces... in order to make the interface types known for the MockValueCodec, you need to first encode them", wrappedValue.TypeName)
return t, fmt.Errorf("unknown type %s, you're dealing with interfaces... in order to make the interface types known for the MockValueCodec, you need to first encode them", wrappedValue.TypeName)
}
newT := reflect.New(typ).Interface()

View File

@ -32,7 +32,7 @@ func (m Map[K, V]) importGenesis(ctx context.Context, reader io.Reader) error {
}
func (m Map[K, V]) exportGenesis(ctx context.Context, writer io.Writer) error {
_, err := writer.Write([]byte("["))
_, err := io.WriteString(writer, "[")
if err != nil {
return err
}
@ -48,7 +48,7 @@ func (m Map[K, V]) exportGenesis(ctx context.Context, writer io.Writer) error {
// add a comma before encoding the object
// for all objects besides the first one.
if !first {
_, err = writer.Write([]byte(","))
_, err = io.WriteString(writer, ",")
if err != nil {
return err
}
@ -91,7 +91,7 @@ func (m Map[K, V]) exportGenesis(ctx context.Context, writer io.Writer) error {
}
}
_, err = writer.Write([]byte("]"))
_, err = io.WriteString(writer, "]")
return err
}
@ -148,6 +148,6 @@ func (m Map[K, V]) doDecodeJSON(reader io.Reader, onEntry func(key K, value V) e
}
func (m Map[K, V]) defaultGenesis(writer io.Writer) error {
_, err := writer.Write([]byte(`[]`))
_, err := io.WriteString(writer, `[]`)
return err
}

View File

@ -91,6 +91,7 @@ type testFixture struct {
}
func initFixture(t *testing.T) *testFixture {
t.Helper()
sk, ctx := deps()
schemaBuilder := NewSchemaBuilder(sk)
m := NewMap(schemaBuilder, NewPrefix(1), "map", StringKey, Uint64Value)
@ -110,6 +111,7 @@ func initFixture(t *testing.T) *testFixture {
}
func createTestGenesisSource(t *testing.T) appmodule.GenesisSource {
t.Helper()
expectedOrder := []string{"item", "key_set", "map", "sequence"}
currentIndex := 0
return func(field string) (io.ReadCloser, error) {
@ -149,6 +151,7 @@ func (b *bufCloser) Close() error {
}
func newBufCloser(t *testing.T, str string) *bufCloser {
t.Helper()
b := &bufCloser{
Buffer: bytes.NewBufferString(str),
closed: false,

View File

@ -1,53 +1,52 @@
module cosmossdk.io/collections
go 1.23
go 1.23.2
require (
cosmossdk.io/core v0.11.0
github.com/cosmos/cosmos-db v1.0.2
github.com/stretchr/testify v1.9.0
cosmossdk.io/core v1.0.0
cosmossdk.io/core/testing v0.0.2
cosmossdk.io/schema v1.0.0
github.com/cosmos/cosmos-db v1.1.1
github.com/cosmos/gogoproto v1.7.0
github.com/google/go-cmp v0.6.0
github.com/stretchr/testify v1.10.0
github.com/tidwall/btree v1.7.0
google.golang.org/protobuf v1.36.4
pgregory.net/rapid v1.1.0
)
require (
cosmossdk.io/api v0.7.5 // indirect
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/DataDog/zstd v1.4.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v1.1.0 // indirect
github.com/cockroachdb/pebble v1.1.2 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/linxGnu/grocksdb v1.8.12 // indirect
github.com/onsi/gomega v1.20.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.47.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/protobuf v1.33.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.12.0 // indirect
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -1,218 +1,582 @@
cosmossdk.io/api v0.7.5 h1:eMPTReoNmGUm8DeiQL9DyM8sYDjEhWzL1+nLbI9DqtQ=
cosmossdk.io/api v0.7.5/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38=
cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo=
cosmossdk.io/core v0.11.0/go.mod h1:LaTtayWBSoacF5xNzoF8tmLhehqlA9z1SWiPuNC6X1w=
cosmossdk.io/depinject v1.0.0-alpha.4 h1:PLNp8ZYAMPTUKyG9IK2hsbciDWqna2z1Wsl98okJopc=
cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vMaOmslGFM3yU=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
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=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cosmossdk.io/core v1.0.0 h1:e7XBbISOytLBOXMVwpRPixThXqEkeLGlg8no/qpgS8U=
cosmossdk.io/core v1.0.0/go.mod h1:mKIp3RkoEmtqdEdFHxHwWAULRe+79gfdOvmArrLDbDc=
cosmossdk.io/core/testing v0.0.2 h1:TXAmsnHa1C5lLiVN1z+rnkiyNS6sNwcGJCb6OouaNv8=
cosmossdk.io/core/testing v0.0.2/go.mod h1:6Y3QaoNXsgeHiV4+9oYF/IqMFqCK5cJClonw/OXxHlY=
cosmossdk.io/schema v1.0.0 h1:/diH4XJjpV1JQwuIozwr+A4uFuuwanFdnw2kKeiXwwQ=
cosmossdk.io/schema v1.0.0/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA=
github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/cosmos/cosmos-db v1.0.2 h1:hwMjozuY1OlJs/uh6vddqnk9j7VamLv+0DBlbEXbAKs=
github.com/cosmos/cosmos-db v1.0.2/go.mod h1:Z8IXcFJ9PqKK6BIsVOB3QXtkKoqUOp1vRvPT39kOXEA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/cosmos-db v1.1.1 h1:FezFSU37AlBC8S98NlSagL76oqBRWq/prTPvFcEJNCM=
github.com/cosmos/cosmos-db v1.1.1/go.mod h1:AghjcIPqdhSLP/2Z0yha5xPH3nLnskz81pBx3tcVSAw=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
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=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
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/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/linxGnu/grocksdb v1.8.12 h1:1/pCztQUOa3BX/1gR3jSZDoaKFpeHFvQ1XrqZpSvZVo=
github.com/linxGnu/grocksdb v1.8.12/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k=
github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg=
github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y=
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -2,6 +2,9 @@ package collections
import (
"context"
"errors"
"fmt"
"reflect"
"cosmossdk.io/collections/codec"
)
@ -32,17 +35,77 @@ type Index[PrimaryKey, Value any] interface {
// Internally IndexedMap can be seen as a partitioned collection, one partition
// is a Map[PrimaryKey, Value], that maintains the object, the second
// are the Indexes.
type IndexedMap[PrimaryKey, Value any, Idx Indexes[PrimaryKey, Value]] struct {
Indexes Idx
m Map[PrimaryKey, Value]
type IndexedMap[PrimaryKey, Value, Idx any] struct {
Indexes Idx
computedIndexes []Index[PrimaryKey, Value]
m Map[PrimaryKey, Value]
}
// NewIndexedMapSafe behaves like NewIndexedMap but returns errors.
func NewIndexedMapSafe[K, V, I any](
schema *SchemaBuilder,
prefix Prefix,
name string,
pkCodec codec.KeyCodec[K],
valueCodec codec.ValueCodec[V],
indexes I,
) (im *IndexedMap[K, V, I], err error) {
var indexesList []Index[K, V]
indexesImpl, ok := any(indexes).(Indexes[K, V])
if ok {
indexesList = indexesImpl.IndexesList()
} else {
// if does not implement Indexes, then we try to infer using reflection
indexesList, err = tryInferIndexes[I, K, V](indexes)
if err != nil {
return nil, fmt.Errorf("unable to infer indexes using reflection, consider implementing Indexes interface: %w", err)
}
}
return &IndexedMap[K, V, I]{
computedIndexes: indexesList,
Indexes: indexes,
m: NewMap(schema, prefix, name, pkCodec, valueCodec),
}, nil
}
var (
// testing sentinel errors
errNotStruct = errors.New("wanted struct or pointer to a struct")
errNotIndex = errors.New("field is not an index implementation")
)
func tryInferIndexes[I, K, V any](indexes I) ([]Index[K, V], error) {
typ := reflect.TypeOf(indexes)
v := reflect.ValueOf(indexes)
// check if struct or pointer to a struct
if typ.Kind() != reflect.Struct && (typ.Kind() != reflect.Pointer || typ.Elem().Kind() != reflect.Struct) {
return nil, fmt.Errorf("%w: type %v", errNotStruct, typ)
}
// dereference
if typ.Kind() == reflect.Pointer {
v = v.Elem()
}
indexesImpl := make([]Index[K, V], v.NumField())
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
index, ok := field.Interface().(Index[K, V])
if !ok {
return nil, fmt.Errorf("%w: field number %d", errNotIndex, i)
}
indexesImpl[i] = index
}
return indexesImpl, nil
}
// NewIndexedMap instantiates a new IndexedMap. Accepts a SchemaBuilder, a Prefix,
// a humanized name that defines the name of the collection, the primary key codec
// which is basically what IndexedMap uses to encode the primary key to bytes,
// the value codec which is what the IndexedMap uses to encode the value.
// Then it expects the initialized indexes.
func NewIndexedMap[PrimaryKey, Value any, Idx Indexes[PrimaryKey, Value]](
// Then it expects the initialized indexes. Reflection is used to infer the
// indexes, Indexes can optionally be implemented to be explicit. Panics
// on failure to create indexes. If you want an erroring API use NewIndexedMapSafe.
func NewIndexedMap[PrimaryKey, Value, Idx any](
schema *SchemaBuilder,
prefix Prefix,
name string,
@ -50,10 +113,11 @@ func NewIndexedMap[PrimaryKey, Value any, Idx Indexes[PrimaryKey, Value]](
valueCodec codec.ValueCodec[Value],
indexes Idx,
) *IndexedMap[PrimaryKey, Value, Idx] {
return &IndexedMap[PrimaryKey, Value, Idx]{
Indexes: indexes,
m: NewMap(schema, prefix, name, pkCodec, valueCodec),
im, err := NewIndexedMapSafe(schema, prefix, name, pkCodec, valueCodec, indexes)
if err != nil {
panic(err)
}
return im
}
// Get gets the object given its primary key.
@ -111,7 +175,7 @@ func (m *IndexedMap[PrimaryKey, Value, Idx]) ValueCodec() codec.ValueCodec[Value
}
func (m *IndexedMap[PrimaryKey, Value, Idx]) ref(ctx context.Context, pk PrimaryKey, value Value) error {
for _, index := range m.Indexes.IndexesList() {
for _, index := range m.computedIndexes {
err := index.Reference(ctx, pk, value, cachedGet[PrimaryKey, Value](ctx, m, pk))
if err != nil {
return err
@ -121,7 +185,7 @@ func (m *IndexedMap[PrimaryKey, Value, Idx]) ref(ctx context.Context, pk Primary
}
func (m *IndexedMap[PrimaryKey, Value, Idx]) unref(ctx context.Context, pk PrimaryKey) error {
for _, index := range m.Indexes.IndexesList() {
for _, index := range m.computedIndexes {
err := index.Unreference(ctx, pk, cachedGet[PrimaryKey, Value](ctx, m, pk))
if err != nil {
return err

View File

@ -0,0 +1,30 @@
package collections
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestTryInferIndex(t *testing.T) {
invalidIdx := 5
t.Run("not a pointer to struct", func(t *testing.T) {
_, err := tryInferIndexes[*int, string, string](&invalidIdx)
require.ErrorIs(t, err, errNotStruct)
})
t.Run("not a struct", func(t *testing.T) {
_, err := tryInferIndexes[int, string, string](invalidIdx)
require.ErrorIs(t, err, errNotStruct)
})
t.Run("not an index field", func(t *testing.T) {
type invalidIndex struct {
A int
}
_, err := tryInferIndexes[invalidIndex, string, string](invalidIndex{})
require.ErrorIs(t, err, errNotIndex)
})
}

View File

@ -8,6 +8,7 @@ import (
"cosmossdk.io/collections"
"cosmossdk.io/collections/colltest"
"cosmossdk.io/collections/indexes"
"cosmossdk.io/core/testing"
)
type company struct {
@ -44,7 +45,9 @@ func newTestIndexedMap(schema *collections.SchemaBuilder) *collections.IndexedMa
}
func TestIndexedMap(t *testing.T) {
sk, ctx := colltest.MockStore()
ctx := coretesting.Context()
sk := coretesting.KVStoreService(ctx, "test")
schema := collections.NewSchemaBuilder(sk)
im := newTestIndexedMap(schema)
@ -104,3 +107,27 @@ func TestIndexedMap(t *testing.T) {
require.NoError(t, err)
require.Equal(t, company{"milan", 4}, v)
}
type inferIndex struct {
City *indexes.Multi[string, string, company]
Vat *indexes.Unique[uint64, string, company]
}
func newInferIndex(schema *collections.SchemaBuilder) *inferIndex {
return &inferIndex{
City: indexes.NewMulti(schema, collections.NewPrefix(1), "companies_by_city", collections.StringKey, collections.StringKey, func(pk string, value company) (string, error) {
return value.City, nil
}),
Vat: indexes.NewUnique(schema, collections.NewPrefix(2), "companies_by_vat", collections.Uint64Key, collections.StringKey, func(pk string, value company) (uint64, error) {
return value.Vat, nil
}),
}
}
func TestIndexedMapInfer(t *testing.T) {
sk := coretesting.KVStoreService(coretesting.Context(), "test")
schema := collections.NewSchemaBuilder(sk)
_, err := collections.NewIndexedMapSafe(schema, collections.NewPrefix(0), "im", collections.StringKey, colltest.MockValueCodec[company](), newInferIndex(schema))
require.NoError(t, err)
}

View File

@ -3,50 +3,14 @@ package indexes
import (
"context"
db "github.com/cosmos/cosmos-db"
"cosmossdk.io/core/store"
"cosmossdk.io/core/testing"
)
// TODO remove this when we add testStore to core/store.
type testStore struct {
db db.DB
}
func (t testStore) OpenKVStore(ctx context.Context) store.KVStore {
return t
}
func (t testStore) Get(key []byte) ([]byte, error) {
return t.db.Get(key)
}
func (t testStore) Has(key []byte) (bool, error) {
return t.db.Has(key)
}
func (t testStore) Set(key, value []byte) error {
return t.db.Set(key, value)
}
func (t testStore) Delete(key []byte) error {
return t.db.Delete(key)
}
func (t testStore) Iterator(start, end []byte) (store.Iterator, error) {
return t.db.Iterator(start, end)
}
func (t testStore) ReverseIterator(start, end []byte) (store.Iterator, error) {
return t.db.ReverseIterator(start, end)
}
var _ store.KVStore = testStore{}
func deps() (store.KVStoreService, context.Context) {
kv := db.NewMemDB()
return &testStore{kv}, context.Background()
ctx := coretesting.Context()
kv := coretesting.KVStoreService(ctx, "test")
return kv, ctx
}
type company struct {

View File

@ -8,6 +8,21 @@ import (
"cosmossdk.io/collections/codec"
)
type multiOptions struct {
uncheckedValue bool
}
// WithMultiUncheckedValue is an option that can be passed to NewMulti to
// ignore index values different from '[]byte{}' and continue with the operation.
// This should be used only to behave nicely in case you have used values different
// from '[]byte{}' in your storage before migrating to collections. Refer to
// WithKeySetUncheckedValue for more information.
func WithMultiUncheckedValue() func(*multiOptions) {
return func(o *multiOptions) {
o.uncheckedValue = true
}
}
// Multi defines the most common index. It can be used to create a reference between
// a field of value and its primary key. Multiple primary keys can be mapped to the same
// reference key as the index does not enforce uniqueness constraints.
@ -27,10 +42,35 @@ func NewMulti[ReferenceKey, PrimaryKey, Value any](
refCodec codec.KeyCodec[ReferenceKey],
pkCodec codec.KeyCodec[PrimaryKey],
getRefKeyFunc func(pk PrimaryKey, value Value) (ReferenceKey, error),
options ...func(*multiOptions),
) *Multi[ReferenceKey, PrimaryKey, Value] {
o := new(multiOptions)
for _, opt := range options {
opt(o)
}
if o.uncheckedValue {
return &Multi[ReferenceKey, PrimaryKey, Value]{
getRefKey: getRefKeyFunc,
refKeys: collections.NewKeySet(
schema,
prefix,
name,
collections.PairKeyCodec(refCodec, pkCodec),
collections.WithKeySetUncheckedValue(),
collections.WithKeySetSecondaryIndex(),
),
}
}
return &Multi[ReferenceKey, PrimaryKey, Value]{
getRefKey: getRefKeyFunc,
refKeys: collections.NewKeySet(schema, prefix, name, collections.PairKeyCodec(refCodec, pkCodec)),
refKeys: collections.NewKeySet(
schema,
prefix,
name,
collections.PairKeyCodec(refCodec, pkCodec),
collections.WithKeySetSecondaryIndex(),
),
}
}

View File

@ -16,7 +16,7 @@ func TestMultiIndex(t *testing.T) {
return value.City, nil
})
// we crete two reference keys for primary key 1 and 2 associated with "milan"
// we create two reference keys for primary key 1 and 2 associated with "milan"
require.NoError(t, mi.Reference(ctx, 1, company{City: "milan"}, func() (company, error) { return company{}, collections.ErrNotFound }))
require.NoError(t, mi.Reference(ctx, 2, company{City: "milan"}, func() (company, error) { return company{}, collections.ErrNotFound }))
@ -61,3 +61,52 @@ func TestMultiIndex(t *testing.T) {
require.False(t, iter.Valid())
require.NoError(t, iter.Close())
}
func TestMultiUnchecked(t *testing.T) {
sk, ctx := deps()
schema := collections.NewSchemaBuilder(sk)
uncheckedMi := NewMulti(schema, collections.NewPrefix("prefix"), "multi_index", collections.StringKey, collections.Uint64Key, func(_ uint64, value company) (string, error) {
return value.City, nil
}, WithMultiUncheckedValue())
mi := NewMulti(schema, collections.NewPrefix("prefix"), "multi_index", collections.StringKey, collections.Uint64Key, func(_ uint64, value company) (string, error) {
return value.City, nil
})
rawKey, err := collections.EncodeKeyWithPrefix(
collections.NewPrefix("prefix"),
uncheckedMi.KeyCodec(),
collections.Join("milan", uint64(2)))
require.NoError(t, err)
// set value to be something different from []byte{}
require.NoError(t, sk.OpenKVStore(ctx).Set(rawKey, []byte("something")))
// normal multi index will fail.
err = mi.Walk(ctx, nil, func(indexingKey string, indexedKey uint64) (stop bool, err error) {
return true, err
})
require.ErrorIs(t, err, collections.ErrEncoding)
// unchecked multi index will not fail.
err = uncheckedMi.Walk(ctx, nil, func(indexingKey string, indexedKey uint64) (stop bool, err error) {
require.Equal(t, "milan", indexingKey)
require.Equal(t, uint64(2), indexedKey)
return true, err
})
require.NoError(t, err)
// unchecked multi will also reset the value
err = mi.Reference(ctx, 2, company{City: "milan"}, func() (company, error) {
return company{
City: "milan",
}, nil
})
require.NoError(t, err)
// value reset to []byte{}
rawValue, err := sk.OpenKVStore(ctx).Get(rawKey)
require.NoError(t, err)
require.Equal(t, []byte{}, rawValue)
}

View File

@ -7,6 +7,21 @@ import (
"cosmossdk.io/collections/codec"
)
type reversePairOptions struct {
uncheckedValue bool
}
// WithReversePairUncheckedValue is an option that can be passed to NewReversePair to
// ignore index values different from '[]byte{}' and continue with the operation.
// This should be used only if you are migrating to collections and have used a different
// placeholder value in your storage index keys.
// Refer to WithKeySetUncheckedValue for more information.
func WithReversePairUncheckedValue() func(*reversePairOptions) {
return func(o *reversePairOptions) {
o.uncheckedValue = true
}
}
// ReversePair is an index that is used with collections.Pair keys. It indexes objects by their second part of the key.
// When the value is being indexed by collections.IndexedMap then ReversePair will create a relationship between
// the second part of the primary key and the first part.
@ -31,10 +46,34 @@ func NewReversePair[Value, K1, K2 any](
prefix collections.Prefix,
name string,
pairCodec codec.KeyCodec[collections.Pair[K1, K2]],
options ...func(*reversePairOptions),
) *ReversePair[K1, K2, Value] {
pkc := pairCodec.(pairKeyCodec[K1, K2])
o := new(reversePairOptions)
for _, option := range options {
option(o)
}
if o.uncheckedValue {
return &ReversePair[K1, K2, Value]{
refKeys: collections.NewKeySet(
sb,
prefix,
name,
collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1()),
collections.WithKeySetUncheckedValue(),
collections.WithKeySetSecondaryIndex(),
),
}
}
mi := &ReversePair[K1, K2, Value]{
refKeys: collections.NewKeySet(sb, prefix, name, collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1())),
refKeys: collections.NewKeySet(
sb,
prefix,
name,
collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1()),
collections.WithKeySetSecondaryIndex(),
),
}
return mi

View File

@ -64,6 +64,45 @@ func TestReversePair(t *testing.T) {
// assert if we remove address1 atom balance, we can no longer find it in the index
err = indexedMap.Remove(ctx, collections.Join("address1", "atom"))
require.NoError(t, err)
_, err = indexedMap.Indexes.Denom.MatchExact(ctx, "atom")
require.ErrorIs(t, collections.ErrInvalidIterator, err)
iter, err = indexedMap.Indexes.Denom.MatchExact(ctx, "atom")
require.NoError(t, err)
defer iter.Close()
pks, err = iter.PrimaryKeys()
require.NoError(t, err)
require.Empty(t, pks)
}
func TestUncheckedReversePair(t *testing.T) {
sk, ctx := deps()
sb := collections.NewSchemaBuilder(sk)
prefix := collections.NewPrefix("prefix")
keyCodec := collections.PairKeyCodec(collections.StringKey, collections.StringKey)
uncheckedRp := NewReversePair[Amount](sb, prefix, "denom_index", keyCodec, WithReversePairUncheckedValue())
rp := NewReversePair[Amount](sb, prefix, "denom_index", keyCodec)
rawKey, err := collections.EncodeKeyWithPrefix(prefix, uncheckedRp.KeyCodec(), collections.Join("atom", "address1"))
require.NoError(t, err)
require.NoError(t, sk.OpenKVStore(ctx).Set(rawKey, []byte("i should not be here")))
// normal reverse pair fails
err = rp.Walk(ctx, nil, func(denom, address string) (bool, error) {
return false, nil
})
require.ErrorIs(t, err, collections.ErrEncoding)
// unchecked reverse pair succeeds
err = uncheckedRp.Walk(ctx, nil, func(indexingKey, indexedKey string) (stop bool, err error) {
require.Equal(t, "atom", indexingKey)
return true, nil
})
require.NoError(t, err)
// unchecked reverse pair lazily updates
err = uncheckedRp.Reference(ctx, collections.Join("address1", "atom"), 0, nil)
require.NoError(t, err)
rawValue, err := sk.OpenKVStore(ctx).Get(rawKey)
require.NoError(t, err)
require.Equal(t, []byte{}, rawValue)
}

258
collections/indexing.go Normal file
View File

@ -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
}

View File

@ -1,6 +1,7 @@
package collections
import (
"bytes"
"context"
"errors"
"fmt"
@ -129,39 +130,51 @@ func (r *Range[K]) RangeValues() (start, end *RangeKey[K], order Order, err erro
return r.start, r.end, r.order, nil
}
// iteratorFromRanger generates an Iterator instance, with the proper prefixing and ranging.
// a nil Ranger can be seen as an ascending iteration over all the possible keys.
func iteratorFromRanger[K, V any](ctx context.Context, m Map[K, V], r Ranger[K]) (iter Iterator[K, V], err error) {
// parseRangeInstruction converts a Ranger into start bytes, end bytes and order of a store iteration.
func parseRangeInstruction[K any](prefix []byte, keyCodec codec.KeyCodec[K], r Ranger[K]) ([]byte, []byte, Order, error) {
var (
start *RangeKey[K]
end *RangeKey[K]
order = OrderAscending
err error
)
if r != nil {
start, end, order, err = r.RangeValues()
if err != nil {
return iter, err
return nil, nil, 0, err
}
}
startBytes := m.prefix
startBytes := prefix
if start != nil {
startBytes, err = encodeRangeBound(m.prefix, m.kc, start)
startBytes, err = encodeRangeBound(prefix, keyCodec, start)
if err != nil {
return iter, err
return nil, nil, 0, err
}
}
var endBytes []byte
if end != nil {
endBytes, err = encodeRangeBound(m.prefix, m.kc, end)
endBytes, err = encodeRangeBound(prefix, keyCodec, end)
if err != nil {
return iter, err
return nil, nil, 0, err
}
} else {
endBytes = nextBytesPrefixKey(m.prefix)
endBytes = nextBytesPrefixKey(prefix)
}
if bytes.Compare(startBytes, endBytes) == 1 {
return nil, nil, 0, ErrInvalidIterator
}
return startBytes, endBytes, order, nil
}
// iteratorFromRanger generates an Iterator instance, with the proper prefixing and ranging.
// a nil Ranger can be seen as an ascending iteration over all the possible keys.
func iteratorFromRanger[K, V any](ctx context.Context, m Map[K, V], r Ranger[K]) (iter Iterator[K, V], err error) {
startBytes, endBytes, order, err := parseRangeInstruction(m.prefix, m.kc, r)
if err != nil {
return Iterator[K, V]{}, err
}
return newIterator(ctx, startBytes, endBytes, order, m)
}
@ -182,9 +195,6 @@ func newIterator[K, V any](ctx context.Context, start, end []byte, order Order,
if err != nil {
return Iterator[K, V]{}, err
}
if !iter.Valid() {
return Iterator[K, V]{}, ErrInvalidIterator
}
return Iterator[K, V]{
kc: m.kc,
@ -194,7 +204,7 @@ func newIterator[K, V any](ctx context.Context, start, end []byte, order Order,
}, nil
}
// Iterator defines a generic wrapper around an storetypes.Iterator.
// Iterator defines a generic wrapper around a storetypes.Iterator.
// This iterator provides automatic key and value encoding,
// it assumes all the keys and values contained within the storetypes.Iterator
// range are the same.

View File

@ -1,6 +1,7 @@
package collections
import (
"errors"
"fmt"
"testing"
@ -10,11 +11,13 @@ import (
func TestIteratorBasic(t *testing.T) {
sk, ctx := deps()
// safety check to ensure that iteration does not cross prefix boundaries
sk.OpenKVStore(ctx).Set([]byte{0, 0}, []byte("before prefix"))
sk.OpenKVStore(ctx).Set([]byte{2, 1}, []byte("after prefix"))
err := sk.OpenKVStore(ctx).Set([]byte{0, 0}, []byte("before prefix"))
require.NoError(t, err)
err = sk.OpenKVStore(ctx).Set([]byte{2, 1}, []byte("after prefix"))
require.NoError(t, err)
schemaBuilder := NewSchemaBuilder(sk)
m := NewMap(schemaBuilder, NewPrefix(1), "m", StringKey, Uint64Value)
_, err := schemaBuilder.Build()
_, err = schemaBuilder.Build()
require.NoError(t, err)
for i := uint64(1); i <= 2; i++ {
@ -186,7 +189,7 @@ func TestWalk(t *testing.T) {
})
require.NoError(t, err)
sentinelErr := fmt.Errorf("sentinel error")
sentinelErr := errors.New("sentinel error")
err = m.Walk(ctx, nil, func(key, value uint64) (stop bool, err error) {
require.LessOrEqual(t, key, uint64(3)) // asserts that after the number three we stop
if key == 3 {

58
collections/json.go Normal file
View File

@ -0,0 +1,58 @@
package collections
import (
"encoding/json"
"fmt"
"cosmossdk.io/collections/codec"
)
func NewJSONValueCodec[T any]() codec.ValueCodec[T] {
return jsonValue[T]{
typeName: fmt.Sprintf("%T", new(T)),
}
}
type jsonValue[T any] struct {
typeName string
}
// Decode implements codec.ValueCodec.
func (jsonValue[T]) Decode(b []byte) (T, error) {
var t T
if err := json.Unmarshal(b, &t); err != nil {
return t, err
}
return t, nil
}
// DecodeJSON implements codec.ValueCodec.
func (jsonValue[T]) DecodeJSON(b []byte) (T, error) {
var t T
if err := json.Unmarshal(b, &t); err != nil {
return t, err
}
return t, nil
}
// Encode implements codec.ValueCodec.
func (jsonValue[T]) Encode(value T) ([]byte, error) {
return json.Marshal(value)
}
// EncodeJSON implements codec.ValueCodec.
func (jsonValue[T]) EncodeJSON(value T) ([]byte, error) {
return json.Marshal(value)
}
// Stringify implements codec.ValueCodec.
func (jsonValue[T]) Stringify(value T) string {
return fmt.Sprintf("%v", value)
}
// ValueType implements codec.ValueCodec.
func (jv jsonValue[T]) ValueType() string {
return fmt.Sprintf("json(%s)", jv.typeName)
}

View File

@ -8,14 +8,53 @@ import (
"cosmossdk.io/collections/codec"
)
// WithKeySetUncheckedValue changes the behavior of the KeySet when it encounters
// a value different from '[]byte{}', by default the KeySet errors when this happens.
// This option allows to ignore the value and continue with the operation, in turn
// the value will be cleared out and set to '[]byte{}'.
// You should never use this option if you're creating a new state object from scratch.
// This should be used only to behave nicely in case you have used values different
// from '[]byte{}' in your storage before migrating to collections.
func WithKeySetUncheckedValue() func(opt *keySetOptions) {
return func(opt *keySetOptions) {
opt.uncheckedValue = true
}
}
// WithKeySetSecondaryIndex changes the behavior of the KeySet to be a secondary index.
func WithKeySetSecondaryIndex() func(opt *keySetOptions) {
return func(opt *keySetOptions) {
opt.isSecondaryIndex = true
}
}
type keySetOptions struct {
uncheckedValue bool
isSecondaryIndex bool
}
// KeySet builds on top of a Map and represents a collection retaining only a set
// of keys and no value. It can be used, for example, in an allow list.
type KeySet[K any] Map[K, NoValue]
// NewKeySet returns a KeySet given a Schema, Prefix a human name for the collection
// and a KeyCodec for the key K.
func NewKeySet[K any](schema *SchemaBuilder, prefix Prefix, name string, keyCodec codec.KeyCodec[K]) KeySet[K] {
return (KeySet[K])(NewMap(schema, prefix, name, keyCodec, noValueCodec))
func NewKeySet[K any](
schema *SchemaBuilder,
prefix Prefix,
name string,
keyCodec codec.KeyCodec[K],
options ...func(opt *keySetOptions),
) KeySet[K] {
o := new(keySetOptions)
for _, opt := range options {
opt(o)
}
vc := noValueCodec
if o.uncheckedValue {
vc = codec.NewAltValueCodec(vc, func(_ []byte) (NoValue, error) { return NoValue{}, nil })
}
return (KeySet[K])(NewMap(schema, prefix, name, keyCodec, vc, withMapSecondaryIndex(o.isSecondaryIndex)))
}
// Set adds the key to the KeySet. Errors on encoding problems.
@ -57,6 +96,12 @@ func (k KeySet[K]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key
return (Map[K, NoValue])(k).Walk(ctx, ranger, func(key K, value NoValue) (bool, error) { return walkFunc(key) })
}
// Clear clears the KeySet using the provided Ranger. Refer to Map.Clear for
// behavioral documentation.
func (k KeySet[K]) Clear(ctx context.Context, ranger Ranger[K]) error {
return (Map[K, NoValue])(k).Clear(ctx, ranger)
}
func (k KeySet[K]) KeyCodec() codec.KeyCodec[K] { return (Map[K, NoValue])(k).KeyCodec() }
func (k KeySet[K]) ValueCodec() codec.ValueCodec[NoValue] { return (Map[K, NoValue])(k).ValueCodec() }
@ -73,6 +118,7 @@ var noValueCodec codec.ValueCodec[NoValue] = NoValue{}
const noValueValueType = "no_value"
// NoValue is a type that can be used to represent a non-existing value.
type NoValue struct{}
func (n NoValue) EncodeJSON(_ NoValue) ([]byte, error) {
@ -92,7 +138,7 @@ func (NoValue) Encode(_ NoValue) ([]byte, error) {
func (NoValue) Decode(b []byte) (NoValue, error) {
if !bytes.Equal(b, []byte{}) {
return NoValue{}, fmt.Errorf("%w: invalid value, wanted an empty non-nil byte slice", ErrEncoding)
return NoValue{}, fmt.Errorf("%w: invalid value, wanted an empty non-nil byte slice, got: %x", ErrEncoding, b)
}
return NoValue{}, nil
}

View File

@ -67,3 +67,34 @@ func Test_noValue(t *testing.T) {
_, err = noValueCodec.Decode([]byte("bad"))
require.ErrorIs(t, err, ErrEncoding)
}
func TestUncheckedKeySet(t *testing.T) {
sk, ctx := deps()
schema := NewSchemaBuilder(sk)
uncheckedKs := NewKeySet(schema, NewPrefix("keyset"), "keyset", StringKey, WithKeySetUncheckedValue())
ks := NewKeySet(schema, NewPrefix("keyset"), "keyset", StringKey)
// we set a NoValue unfriendly value.
require.NoError(t, sk.OpenKVStore(ctx).Set([]byte("keyset1"), []byte("A")))
require.NoError(t, sk.OpenKVStore(ctx).Set([]byte("keyset2"), []byte("B")))
// the standard KeySet errors here, because it doesn't like the fact that the value is []byte("A")
// and not []byte{}.
err := ks.Walk(ctx, nil, func(key string) (stop bool, err error) {
return true, nil
})
require.ErrorIs(t, err, ErrEncoding)
// the unchecked KeySet doesn't care about the value, so it works.
err = uncheckedKs.Walk(ctx, nil, func(key string) (stop bool, err error) {
require.Equal(t, "1", key)
return true, nil
})
require.NoError(t, err)
// now we set it again
require.NoError(t, uncheckedKs.Set(ctx, "1"))
// and we will see that the value which was []byte("A") has been cleared to be []byte{}
raw, err := sk.OpenKVStore(ctx).Get([]byte("keyset1"))
require.NoError(t, err)
require.Equal(t, []byte{}, raw)
}

104
collections/lookup_map.go Normal file
View File

@ -0,0 +1,104 @@
package collections
import (
"context"
"fmt"
"cosmossdk.io/collections/codec"
)
// LookupMap represents a map that is not iterable.
type LookupMap[K, V any] Map[K, V]
// NewLookupMap creates a new LookupMap.
func NewLookupMap[K, V any](
schemaBuilder *SchemaBuilder,
prefix Prefix,
name string,
keyCodec codec.KeyCodec[K],
valueCodec codec.ValueCodec[V],
) LookupMap[K, V] {
m := LookupMap[K, V](NewMap[K, V](schemaBuilder, prefix, name, keyCodec, valueCodec))
return m
}
// GetName returns the name of the collection.
func (m LookupMap[K, V]) GetName() string {
return m.name
}
// GetPrefix returns the prefix of the collection.
func (m LookupMap[K, V]) GetPrefix() []byte {
return m.prefix
}
// Set maps the provided value to the provided key in the store.
// Errors with ErrEncoding if key or value encoding fails.
func (m LookupMap[K, V]) Set(ctx context.Context, key K, value V) error {
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return err
}
valueBytes, err := m.vc.Encode(value)
if err != nil {
return fmt.Errorf("%w: value encode: %w", ErrEncoding, err)
}
kvStore := m.sa(ctx)
return kvStore.Set(bytesKey, valueBytes)
}
// Get returns the value associated with the provided key,
// errors with ErrNotFound if the key does not exist, or
// with ErrEncoding if the key or value decoding fails.
func (m LookupMap[K, V]) Get(ctx context.Context, key K) (v V, err error) {
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return v, err
}
kvStore := m.sa(ctx)
valueBytes, err := kvStore.Get(bytesKey)
if err != nil {
return v, err
}
if valueBytes == nil {
return v, fmt.Errorf("%w: key '%s' of type %s", ErrNotFound, m.kc.Stringify(key), m.vc.ValueType())
}
v, err = m.vc.Decode(valueBytes)
if err != nil {
return v, fmt.Errorf("%w: value decode: %w", ErrEncoding, err)
}
return v, nil
}
// Has reports whether the key is present in storage or not.
// Errors with ErrEncoding if key encoding fails.
func (m LookupMap[K, V]) Has(ctx context.Context, key K) (bool, error) {
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return false, err
}
kvStore := m.sa(ctx)
return kvStore.Has(bytesKey)
}
// Remove removes the key from the storage.
// Errors with ErrEncoding if key encoding fails.
// If the key does not exist then this is a no-op.
func (m LookupMap[K, V]) Remove(ctx context.Context, key K) error {
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return err
}
kvStore := m.sa(ctx)
return kvStore.Delete(bytesKey)
}
// KeyCodec returns the Map's KeyCodec.
func (m LookupMap[K, V]) KeyCodec() codec.KeyCodec[K] { return m.kc }
// ValueCodec returns the Map's ValueCodec.
func (m LookupMap[K, V]) ValueCodec() codec.ValueCodec[V] { return m.vc }

View File

@ -0,0 +1,42 @@
package collections_test
import (
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/collections"
"cosmossdk.io/core/testing"
)
func TestLookupMap(t *testing.T) {
ctx := coretesting.Context()
sk := coretesting.KVStoreService(ctx, "test")
schema := collections.NewSchemaBuilder(sk)
lm := collections.NewLookupMap(schema, collections.NewPrefix("hi"), "lm", collections.Uint64Key, collections.Uint64Value)
_, err := schema.Build()
require.NoError(t, err)
// test not has
has, err := lm.Has(ctx, 1)
require.NoError(t, err)
require.False(t, has)
// test get error
_, err = lm.Get(ctx, 1)
require.ErrorIs(t, err, collections.ErrNotFound)
// test set/get
err = lm.Set(ctx, 1, 100)
require.NoError(t, err)
v, err := lm.Get(ctx, 1)
require.NoError(t, err)
require.Equal(t, uint64(100), v)
// test remove
err = lm.Remove(ctx, 1)
require.NoError(t, err)
has, err = lm.Has(ctx, 1)
require.NoError(t, err)
require.False(t, has)
}

View File

@ -1,6 +1,7 @@
package collections
import (
"bytes"
"context"
"fmt"
@ -19,6 +20,22 @@ type Map[K, V any] struct {
sa func(context.Context) store.KVStore
prefix []byte
name string
// isSecondaryIndex indicates that this map represents a secondary index
// on another collection and that it should be skipped when generating
// a user facing schema
isSecondaryIndex bool
}
// withMapSecondaryIndex changes the behavior of the Map to be a secondary index.
func withMapSecondaryIndex(isSecondaryIndex bool) func(opt *mapOptions) {
return func(opt *mapOptions) {
opt.isSecondaryIndex = isSecondaryIndex
}
}
type mapOptions struct {
isSecondaryIndex bool
}
// NewMap returns a Map given a StoreKey, a Prefix, human-readable name and the relative value and key encoders.
@ -30,13 +47,19 @@ func NewMap[K, V any](
name string,
keyCodec codec.KeyCodec[K],
valueCodec codec.ValueCodec[V],
options ...func(opt *mapOptions),
) Map[K, V] {
o := new(mapOptions)
for _, opt := range options {
opt(o)
}
m := Map[K, V]{
kc: keyCodec,
vc: valueCodec,
sa: schemaBuilder.schema.storeAccessor,
prefix: prefix.Bytes(),
name: name,
kc: keyCodec,
vc: valueCodec,
sa: schemaBuilder.schema.storeAccessor,
prefix: prefix.Bytes(),
name: name,
isSecondaryIndex: o.isSecondaryIndex,
}
schemaBuilder.addCollection(collectionImpl[K, V]{m})
return m
@ -60,12 +83,11 @@ func (m Map[K, V]) Set(ctx context.Context, key K, value V) error {
valueBytes, err := m.vc.Encode(value)
if err != nil {
return fmt.Errorf("%w: value encode: %s", ErrEncoding, err) // TODO: use multi err wrapping in go1.20: https://github.com/golang/go/issues/53435
return fmt.Errorf("%w: value encode: %w", ErrEncoding, err)
}
kvStore := m.sa(ctx)
kvStore.Set(bytesKey, valueBytes)
return nil
return kvStore.Set(bytesKey, valueBytes)
}
// Get returns the value associated with the provided key,
@ -88,7 +110,7 @@ func (m Map[K, V]) Get(ctx context.Context, key K) (v V, err error) {
v, err = m.vc.Decode(valueBytes)
if err != nil {
return v, fmt.Errorf("%w: value decode: %s", ErrEncoding, err) // TODO: use multi err wrapping in go1.20: https://github.com/golang/go/issues/53435
return v, fmt.Errorf("%w: value decode: %w", ErrEncoding, err)
}
return v, nil
}
@ -149,6 +171,58 @@ func (m Map[K, V]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key
return nil
}
// Clear clears the collection contained within the provided key range.
// A nil ranger equals to clearing the whole collection.
// NOTE: this API needs to be used with care, considering that as of today
// cosmos-sdk stores the deletion records to be committed in a memory cache,
// clearing a lot of data might make the node go OOM.
func (m Map[K, V]) Clear(ctx context.Context, ranger Ranger[K]) error {
startBytes, endBytes, _, err := parseRangeInstruction(m.prefix, m.kc, ranger)
if err != nil {
return err
}
return deleteDomain(m.sa(ctx), startBytes, endBytes)
}
const clearBatchSize = 10000
// deleteDomain deletes the domain of an iterator, the key difference
// is that it uses batches to clear the store meaning that it will read
// the keys within the domain close the iterator and then delete them.
func deleteDomain(s store.KVStore, start, end []byte) error {
for {
iter, err := s.Iterator(start, end)
if err != nil {
return err
}
keys := make([][]byte, 0, clearBatchSize)
for ; iter.Valid() && len(keys) < clearBatchSize; iter.Next() {
keys = append(keys, iter.Key())
}
// we close the iterator here instead of deferring
err = iter.Close()
if err != nil {
return err
}
for _, key := range keys {
err = s.Delete(key)
if err != nil {
return err
}
}
// If we've retrieved less than the batchSize, we're done.
if len(keys) < clearBatchSize {
break
}
}
return nil
}
// IterateRaw iterates over the collection. The iteration range is untyped, it uses raw
// bytes. The resulting Iterator is typed.
// A nil start iterates from the first key contained in the collection.
@ -164,6 +238,10 @@ func (m Map[K, V]) IterateRaw(ctx context.Context, start, end []byte, order Orde
prefixedEnd = append(m.prefix, end...)
}
if bytes.Compare(prefixedStart, prefixedEnd) == 1 {
return Iterator[K, V]{}, ErrInvalidIterator
}
s := m.sa(ctx)
var (
storeIter store.Iterator
@ -181,9 +259,6 @@ func (m Map[K, V]) IterateRaw(ctx context.Context, start, end []byte, order Orde
return Iterator[K, V]{}, err
}
if !storeIter.Valid() {
return Iterator[K, V]{}, ErrInvalidIterator
}
return Iterator[K, V]{
kc: m.kc,
vc: m.vc,
@ -209,7 +284,7 @@ func EncodeKeyWithPrefix[K any](prefix []byte, kc codec.KeyCodec[K], key K) ([]b
// put key
_, err := kc.Encode(keyBytes[prefixLen:], key)
if err != nil {
return nil, fmt.Errorf("%w: key encode: %s", ErrEncoding, err) // TODO: use multi err wrapping in go1.20: https://github.com/golang/go/issues/53435
return nil, fmt.Errorf("%w: key encode: %w", ErrEncoding, err)
}
return keyBytes, nil
}

View File

@ -1,6 +1,8 @@
package collections
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
@ -36,6 +38,42 @@ func TestMap(t *testing.T) {
require.False(t, has)
}
func TestMap_Clear(t *testing.T) {
makeTest := func() (context.Context, Map[uint64, uint64]) {
sk, ctx := deps()
m := NewMap(NewSchemaBuilder(sk), NewPrefix(0), "test", Uint64Key, Uint64Value)
for i := uint64(0); i < clearBatchSize*2; i++ {
require.NoError(t, m.Set(ctx, i, i))
}
return ctx, m
}
t.Run("nil ranger", func(t *testing.T) {
ctx, m := makeTest()
err := m.Clear(ctx, nil)
require.NoError(t, err)
err = m.Walk(ctx, nil, func(key, value uint64) (bool, error) {
return false, errors.New("should never be called")
})
require.NoError(t, err)
})
t.Run("custom ranger", func(t *testing.T) {
ctx, m := makeTest()
// delete from 0 to 100
err := m.Clear(ctx, new(Range[uint64]).StartInclusive(0).EndInclusive(100))
require.NoError(t, err)
iter, err := m.Iterate(ctx, nil)
require.NoError(t, err)
keys, err := iter.Keys()
require.NoError(t, err)
require.Len(t, keys, clearBatchSize*2-101)
require.Equal(t, keys[0], uint64(101))
require.Equal(t, keys[len(keys)-1], uint64(clearBatchSize*2-1))
})
}
func TestMap_IterateRaw(t *testing.T) {
sk, ctx := deps()
// safety check to ensure prefix boundaries are not crossed
@ -69,6 +107,15 @@ func TestMap_IterateRaw(t *testing.T) {
keys, err = iter.Keys()
require.NoError(t, err)
require.Equal(t, []uint64{2, 1, 0}, keys)
// test invalid iter
_, err = m.IterateRaw(ctx, []byte{0x2, 0x0}, []byte{0x0, 0x0}, OrderAscending)
require.ErrorIs(t, err, ErrInvalidIterator)
// test on empty collection iterating does not error
require.NoError(t, m.Clear(ctx, nil))
_, err = m.IterateRaw(ctx, nil, nil, OrderAscending)
require.NoError(t, err)
}
func Test_encodeKey(t *testing.T) {

View File

@ -0,0 +1,59 @@
package collections
import (
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/collections/codec"
)
func TestNaming(t *testing.T) {
expectKeyCodecName(t, "u16", Uint16Key.WithName("u16"))
expectKeyCodecName(t, "u32", Uint32Key.WithName("u32"))
expectKeyCodecName(t, "u64", Uint64Key.WithName("u64"))
expectKeyCodecName(t, "i32", Int32Key.WithName("i32"))
expectKeyCodecName(t, "i64", Int64Key.WithName("i64"))
expectKeyCodecName(t, "str", StringKey.WithName("str"))
expectKeyCodecName(t, "bytes", BytesKey.WithName("bytes"))
expectKeyCodecName(t, "bool", BoolKey.WithName("bool"))
expectValueCodecName(t, "vu16", Uint16Value.WithName("vu16"))
expectValueCodecName(t, "vu32", Uint32Value.WithName("vu32"))
expectValueCodecName(t, "vu64", Uint64Value.WithName("vu64"))
expectValueCodecName(t, "vi32", Int32Value.WithName("vi32"))
expectValueCodecName(t, "vi64", Int64Value.WithName("vi64"))
expectValueCodecName(t, "vstr", StringValue.WithName("vstr"))
expectValueCodecName(t, "vbytes", BytesValue.WithName("vbytes"))
expectValueCodecName(t, "vbool", BoolValue.WithName("vbool"))
expectKeyCodecNames(t, NamedPairKeyCodec[bool, string]("abc", BoolKey, "def", StringKey), "abc", "def")
expectKeyCodecNames(t, NamedTripleKeyCodec[bool, string, int32]("abc", BoolKey, "def", StringKey, "ghi", Int32Key), "abc", "def", "ghi")
expectKeyCodecNames(t, NamedQuadKeyCodec[bool, string, int32, uint64]("abc", BoolKey, "def", StringKey, "ghi", Int32Key, "jkl", Uint64Key), "abc", "def", "ghi", "jkl")
}
func expectKeyCodecName[T any](t *testing.T, name string, cdc codec.KeyCodec[T]) {
t.Helper()
schema, err := codec.KeySchemaCodec(cdc)
require.NoError(t, err)
require.Equal(t, 1, len(schema.Fields))
require.Equal(t, name, schema.Fields[0].Name)
}
func expectValueCodecName[T any](t *testing.T, name string, cdc codec.ValueCodec[T]) {
t.Helper()
schema, err := codec.ValueSchemaCodec(cdc)
require.NoError(t, err)
require.Equal(t, 1, len(schema.Fields))
require.Equal(t, name, schema.Fields[0].Name)
}
func expectKeyCodecNames[T any](t *testing.T, cdc codec.KeyCodec[T], names ...string) {
t.Helper()
schema, err := codec.KeySchemaCodec(cdc)
require.NoError(t, err)
require.Equal(t, len(names), len(schema.Fields))
for i, name := range names {
require.Equal(t, name, schema.Fields[i].Name)
}
}

View File

@ -6,6 +6,7 @@ import (
"strings"
"cosmossdk.io/collections/codec"
"cosmossdk.io/schema"
)
// Pair defines a key composed of two keys.
@ -54,9 +55,22 @@ func PairKeyCodec[K1, K2 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 codec.KeyC
}
}
// NamedPairKeyCodec instantiates a new KeyCodec instance that can encode the Pair, given the KeyCodec of the
// first part of the key and the KeyCodec of the second part of the key.
// It also provides names for the keys which are used for indexing purposes.
func NamedPairKeyCodec[K1, K2 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2]) codec.KeyCodec[Pair[K1, K2]] {
return pairKeyCodec[K1, K2]{
key1Name: key1Name,
key2Name: key2Name,
keyCodec1: keyCodec1,
keyCodec2: keyCodec2,
}
}
type pairKeyCodec[K1, K2 any] struct {
keyCodec1 codec.KeyCodec[K1]
keyCodec2 codec.KeyCodec[K2]
key1Name, key2Name string
keyCodec1 codec.KeyCodec[K1]
keyCodec2 codec.KeyCodec[K2]
}
func (p pairKeyCodec[K1, K2]) KeyCodec1() codec.KeyCodec[K1] { return p.keyCodec1 }
@ -216,6 +230,94 @@ func (p pairKeyCodec[K1, K2]) DecodeJSON(b []byte) (Pair[K1, K2], error) {
return Join(k1, k2), nil
}
func (p pairKeyCodec[K1, K2]) Name() string {
return fmt.Sprintf("%s,%s", p.key1Name, p.key2Name)
}
func (p pairKeyCodec[K1, K2]) SchemaCodec() (codec.SchemaCodec[Pair[K1, K2]], error) {
field1, err := getNamedKeyField(p.keyCodec1, p.key1Name)
if err != nil {
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key1 field: %w", err)
}
field2, err := getNamedKeyField(p.keyCodec2, p.key2Name)
if err != nil {
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key2 field: %w", err)
}
codec1, err := codec.KeySchemaCodec(p.keyCodec1)
if err != nil {
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key1 schema codec: %w", err)
}
codec2, err := codec.KeySchemaCodec(p.keyCodec2)
if err != nil {
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key2 schema codec: %w", err)
}
return codec.SchemaCodec[Pair[K1, K2]]{
Fields: []schema.Field{field1, field2},
ToSchemaType: func(pair Pair[K1, K2]) (any, error) {
k1, err := toKeySchemaType(codec1, pair.K1())
if err != nil {
return nil, err
}
k2, err := toKeySchemaType(codec2, pair.K2())
if err != nil {
return nil, err
}
return []interface{}{k1, k2}, nil
},
FromSchemaType: func(a any) (Pair[K1, K2], error) {
aSlice, ok := a.([]interface{})
if !ok || len(aSlice) != 2 {
return Pair[K1, K2]{}, fmt.Errorf("expected slice of length 2, got %T", a)
}
k1, err := fromKeySchemaType(codec1, aSlice[0])
if err != nil {
return Pair[K1, K2]{}, err
}
k2, err := fromKeySchemaType(codec2, aSlice[1])
if err != nil {
return Pair[K1, K2]{}, err
}
return Join(k1, k2), nil
},
}, nil
}
func getNamedKeyField[T any](keyCdc codec.KeyCodec[T], name string) (schema.Field, error) {
keySchema, err := codec.KeySchemaCodec(keyCdc)
if err != nil {
return schema.Field{}, err
}
if len(keySchema.Fields) != 1 {
return schema.Field{}, fmt.Errorf("key schema in composite key has more than one field, got %v", keySchema.Fields)
}
field := keySchema.Fields[0]
field.Name = name
return field, nil
}
func toKeySchemaType[T any](cdc codec.SchemaCodec[T], key T) (any, error) {
if cdc.ToSchemaType != nil {
return cdc.ToSchemaType(key)
}
return key, nil
}
func fromKeySchemaType[T any](cdc codec.SchemaCodec[T], key any) (T, error) {
if cdc.FromSchemaType != nil {
return cdc.FromSchemaType(key)
}
tKey, ok := key.(T)
if !ok {
var zero T
return zero, fmt.Errorf("expected type %T, got %T", zero, key)
}
return tKey, nil
}
// NewPrefixUntilPairRange defines a collection query which ranges until the provided Pair prefix.
// Unstable: this API might change in the future.
func NewPrefixUntilPairRange[K1, K2 any](prefix K1) *PairRange[K1, K2] {

View File

@ -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
}

View File

@ -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)
})
}

435
collections/quad.go Normal file
View File

@ -0,0 +1,435 @@
package collections
import (
"encoding/json"
"fmt"
"strings"
"cosmossdk.io/collections/codec"
"cosmossdk.io/schema"
)
// Quad defines a multipart key composed of four keys.
type Quad[K1, K2, K3, K4 any] struct {
k1 *K1
k2 *K2
k3 *K3
k4 *K4
}
// Join4 instantiates a new Quad instance composed of the four provided keys, in order.
func Join4[K1, K2, K3, K4 any](k1 K1, k2 K2, k3 K3, k4 K4) Quad[K1, K2, K3, K4] {
return Quad[K1, K2, K3, K4]{&k1, &k2, &k3, &k4}
}
// K1 returns the first part of the key. If nil, the zero value is returned.
func (t Quad[K1, K2, K3, K4]) K1() (x K1) {
if t.k1 != nil {
return *t.k1
}
return x
}
// K2 returns the second part of the key. If nil, the zero value is returned.
func (t Quad[K1, K2, K3, K4]) K2() (x K2) {
if t.k2 != nil {
return *t.k2
}
return x
}
// K3 returns the third part of the key. If nil, the zero value is returned.
func (t Quad[K1, K2, K3, K4]) K3() (x K3) {
if t.k3 != nil {
return *t.k3
}
return x
}
// K4 returns the fourth part of the key. If nil, the zero value is returned.
func (t Quad[K1, K2, K3, K4]) K4() (x K4) {
if t.k4 != nil {
return *t.k4
}
return x
}
// QuadPrefix creates a new Quad instance composed only of the first part of the key.
func QuadPrefix[K1, K2, K3, K4 any](k1 K1) Quad[K1, K2, K3, K4] {
return Quad[K1, K2, K3, K4]{k1: &k1}
}
// QuadSuperPrefix creates a new Quad instance composed only of the first two parts of the key.
func QuadSuperPrefix[K1, K2, K3, K4 any](k1 K1, k2 K2) Quad[K1, K2, K3, K4] {
return Quad[K1, K2, K3, K4]{k1: &k1, k2: &k2}
}
// QuadSuperPrefix3 creates a new Quad instance composed only of the first three parts of the key.
func QuadSuperPrefix3[K1, K2, K3, K4 any](k1 K1, k2 K2, k3 K3) Quad[K1, K2, K3, K4] {
return Quad[K1, K2, K3, K4]{k1: &k1, k2: &k2, k3: &k3}
}
// QuadKeyCodec instantiates a new KeyCodec instance that can encode the Quad, given
// the KeyCodecs of the four parts of the key, in order.
func QuadKeyCodec[K1, K2, K3, K4 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 codec.KeyCodec[K2], keyCodec3 codec.KeyCodec[K3], keyCodec4 codec.KeyCodec[K4]) codec.KeyCodec[Quad[K1, K2, K3, K4]] {
return quadKeyCodec[K1, K2, K3, K4]{
keyCodec1: keyCodec1,
keyCodec2: keyCodec2,
keyCodec3: keyCodec3,
keyCodec4: keyCodec4,
}
}
// NamedQuadKeyCodec instantiates a new KeyCodec instance that can encode the Quad, given
// the KeyCodecs of the four parts of the key, in order.
// The provided names are used to identify the parts of the key in the schema for indexing.
func NamedQuadKeyCodec[K1, K2, K3, K4 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2], key3Name string, keyCodec3 codec.KeyCodec[K3], key4Name string, keyCodec4 codec.KeyCodec[K4]) codec.KeyCodec[Quad[K1, K2, K3, K4]] {
return quadKeyCodec[K1, K2, K3, K4]{
name1: key1Name,
keyCodec1: keyCodec1,
name2: key2Name,
keyCodec2: keyCodec2,
name3: key3Name,
keyCodec3: keyCodec3,
name4: key4Name,
keyCodec4: keyCodec4,
}
}
type quadKeyCodec[K1, K2, K3, K4 any] struct {
name1, name2, name3, name4 string
keyCodec1 codec.KeyCodec[K1]
keyCodec2 codec.KeyCodec[K2]
keyCodec3 codec.KeyCodec[K3]
keyCodec4 codec.KeyCodec[K4]
}
type jsonQuadKey [4]json.RawMessage
// EncodeJSON encodes Quads to json
func (t quadKeyCodec[K1, K2, K3, K4]) EncodeJSON(value Quad[K1, K2, K3, K4]) ([]byte, error) {
json1, err := t.keyCodec1.EncodeJSON(*value.k1)
if err != nil {
return nil, err
}
json2, err := t.keyCodec2.EncodeJSON(*value.k2)
if err != nil {
return nil, err
}
json3, err := t.keyCodec3.EncodeJSON(*value.k3)
if err != nil {
return nil, err
}
json4, err := t.keyCodec4.EncodeJSON(*value.k4)
if err != nil {
return nil, err
}
return json.Marshal(jsonQuadKey{json1, json2, json3, json4})
}
// DecodeJSON decodes json to Quads
func (t quadKeyCodec[K1, K2, K3, K4]) DecodeJSON(b []byte) (Quad[K1, K2, K3, K4], error) {
var jsonKey jsonQuadKey
err := json.Unmarshal(b, &jsonKey)
if err != nil {
return Quad[K1, K2, K3, K4]{}, err
}
key1, err := t.keyCodec1.DecodeJSON(jsonKey[0])
if err != nil {
return Quad[K1, K2, K3, K4]{}, err
}
key2, err := t.keyCodec2.DecodeJSON(jsonKey[1])
if err != nil {
return Quad[K1, K2, K3, K4]{}, err
}
key3, err := t.keyCodec3.DecodeJSON(jsonKey[2])
if err != nil {
return Quad[K1, K2, K3, K4]{}, err
}
key4, err := t.keyCodec4.DecodeJSON(jsonKey[3])
if err != nil {
return Quad[K1, K2, K3, K4]{}, err
}
return Join4(key1, key2, key3, key4), nil
}
// Stringify converts Quads to string
func (t quadKeyCodec[K1, K2, K3, K4]) Stringify(key Quad[K1, K2, K3, K4]) string {
b := new(strings.Builder)
b.WriteByte('(')
if key.k1 != nil {
b.WriteByte('"')
b.WriteString(t.keyCodec1.Stringify(*key.k1))
b.WriteByte('"')
} else {
b.WriteString("<nil>")
}
b.WriteString(", ")
if key.k2 != nil {
b.WriteByte('"')
b.WriteString(t.keyCodec2.Stringify(*key.k2))
b.WriteByte('"')
} else {
b.WriteString("<nil>")
}
b.WriteString(", ")
if key.k3 != nil {
b.WriteByte('"')
b.WriteString(t.keyCodec3.Stringify(*key.k3))
b.WriteByte('"')
} else {
b.WriteString("<nil>")
}
b.WriteString(", ")
if key.k4 != nil {
b.WriteByte('"')
b.WriteString(t.keyCodec4.Stringify(*key.k4))
b.WriteByte('"')
} else {
b.WriteString("<nil>")
}
b.WriteByte(')')
return b.String()
}
func (t quadKeyCodec[K1, K2, K3, K4]) KeyType() string {
return fmt.Sprintf("Quad[%s,%s,%s,%s]", t.keyCodec1.KeyType(), t.keyCodec2.KeyType(), t.keyCodec3.KeyType(), t.keyCodec4.KeyType())
}
func (t quadKeyCodec[K1, K2, K3, K4]) Encode(buffer []byte, key Quad[K1, K2, K3, K4]) (int, error) {
writtenTotal := 0
if key.k1 != nil {
written, err := t.keyCodec1.EncodeNonTerminal(buffer, *key.k1)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k2 != nil {
written, err := t.keyCodec2.EncodeNonTerminal(buffer[writtenTotal:], *key.k2)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k3 != nil {
written, err := t.keyCodec3.EncodeNonTerminal(buffer[writtenTotal:], *key.k3)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k4 != nil {
written, err := t.keyCodec4.Encode(buffer[writtenTotal:], *key.k4)
if err != nil {
return 0, err
}
writtenTotal += written
}
return writtenTotal, nil
}
func (t quadKeyCodec[K1, K2, K3, K4]) Decode(buffer []byte) (int, Quad[K1, K2, K3, K4], error) {
readTotal := 0
read, key1, err := t.keyCodec1.DecodeNonTerminal(buffer)
if err != nil {
return 0, Quad[K1, K2, K3, K4]{}, err
}
readTotal += read
read, key2, err := t.keyCodec2.DecodeNonTerminal(buffer[readTotal:])
if err != nil {
return 0, Quad[K1, K2, K3, K4]{}, err
}
readTotal += read
read, key3, err := t.keyCodec3.DecodeNonTerminal(buffer[readTotal:])
if err != nil {
return 0, Quad[K1, K2, K3, K4]{}, err
}
readTotal += read
read, key4, err := t.keyCodec4.Decode(buffer[readTotal:])
if err != nil {
return 0, Quad[K1, K2, K3, K4]{}, err
}
readTotal += read
return readTotal, Join4(key1, key2, key3, key4), nil
}
func (t quadKeyCodec[K1, K2, K3, K4]) Size(key Quad[K1, K2, K3, K4]) int {
size := 0
if key.k1 != nil {
size += t.keyCodec1.SizeNonTerminal(*key.k1)
}
if key.k2 != nil {
size += t.keyCodec2.SizeNonTerminal(*key.k2)
}
if key.k3 != nil {
size += t.keyCodec3.SizeNonTerminal(*key.k3)
}
if key.k4 != nil {
size += t.keyCodec4.Size(*key.k4)
}
return size
}
func (t quadKeyCodec[K1, K2, K3, K4]) EncodeNonTerminal(buffer []byte, key Quad[K1, K2, K3, K4]) (int, error) {
writtenTotal := 0
if key.k1 != nil {
written, err := t.keyCodec1.EncodeNonTerminal(buffer, *key.k1)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k2 != nil {
written, err := t.keyCodec2.EncodeNonTerminal(buffer[writtenTotal:], *key.k2)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k3 != nil {
written, err := t.keyCodec3.EncodeNonTerminal(buffer[writtenTotal:], *key.k3)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k4 != nil {
written, err := t.keyCodec4.EncodeNonTerminal(buffer[writtenTotal:], *key.k4)
if err != nil {
return 0, err
}
writtenTotal += written
}
return writtenTotal, nil
}
func (t quadKeyCodec[K1, K2, K3, K4]) DecodeNonTerminal(buffer []byte) (int, Quad[K1, K2, K3, K4], error) {
readTotal := 0
read, key1, err := t.keyCodec1.DecodeNonTerminal(buffer)
if err != nil {
return 0, Quad[K1, K2, K3, K4]{}, err
}
readTotal += read
read, key2, err := t.keyCodec2.DecodeNonTerminal(buffer[readTotal:])
if err != nil {
return 0, Quad[K1, K2, K3, K4]{}, err
}
readTotal += read
read, key3, err := t.keyCodec3.DecodeNonTerminal(buffer[readTotal:])
if err != nil {
return 0, Quad[K1, K2, K3, K4]{}, err
}
readTotal += read
read, key4, err := t.keyCodec4.DecodeNonTerminal(buffer[readTotal:])
if err != nil {
return 0, Quad[K1, K2, K3, K4]{}, err
}
readTotal += read
return readTotal, Join4(key1, key2, key3, key4), nil
}
func (t quadKeyCodec[K1, K2, K3, K4]) SizeNonTerminal(key Quad[K1, K2, K3, K4]) int {
size := 0
if key.k1 != nil {
size += t.keyCodec1.SizeNonTerminal(*key.k1)
}
if key.k2 != nil {
size += t.keyCodec2.SizeNonTerminal(*key.k2)
}
if key.k3 != nil {
size += t.keyCodec3.SizeNonTerminal(*key.k3)
}
if key.k4 != nil {
size += t.keyCodec4.SizeNonTerminal(*key.k4)
}
return size
}
func (t quadKeyCodec[K1, K2, K3, K4]) SchemaCodec() (codec.SchemaCodec[Quad[K1, K2, K3, K4]], error) {
field1, err := getNamedKeyField(t.keyCodec1, t.name1)
if err != nil {
return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{}, fmt.Errorf("error getting key1 field: %w", err)
}
field2, err := getNamedKeyField(t.keyCodec2, t.name2)
if err != nil {
return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{}, fmt.Errorf("error getting key2 field: %w", err)
}
field3, err := getNamedKeyField(t.keyCodec3, t.name3)
if err != nil {
return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{}, fmt.Errorf("error getting key3 field: %w", err)
}
field4, err := getNamedKeyField(t.keyCodec4, t.name4)
if err != nil {
return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{}, fmt.Errorf("error getting key4 field: %w", err)
}
return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{
Fields: []schema.Field{field1, field2, field3, field4},
ToSchemaType: func(q Quad[K1, K2, K3, K4]) (any, error) {
return []interface{}{q.K1(), q.K2(), q.K3(), q.K4()}, nil
},
FromSchemaType: func(a any) (Quad[K1, K2, K3, K4], error) {
aSlice, ok := a.([]interface{})
if !ok || len(aSlice) != 4 {
return Quad[K1, K2, K3, K4]{}, fmt.Errorf("expected slice of length 4, got %T", a)
}
return Join4(aSlice[0].(K1), aSlice[1].(K2), aSlice[2].(K3), aSlice[3].(K4)), nil
},
}, nil
}
// NewPrefixUntilQuadRange defines a collection query which ranges until the provided Quad prefix.
// Unstable: this API might change in the future.
func NewPrefixUntilQuadRange[K1, K2, K3, K4 any](k1 K1) Ranger[Quad[K1, K2, K3, K4]] {
key := QuadPrefix[K1, K2, K3, K4](k1)
return &Range[Quad[K1, K2, K3, K4]]{
end: RangeKeyPrefixEnd(key),
}
}
// NewPrefixedQuadRange provides a Range for all keys prefixed with the given
// first part of the Quad key.
func NewPrefixedQuadRange[K1, K2, K3, K4 any](k1 K1) Ranger[Quad[K1, K2, K3, K4]] {
key := QuadPrefix[K1, K2, K3, K4](k1)
return &Range[Quad[K1, K2, K3, K4]]{
start: RangeKeyExact(key),
end: RangeKeyPrefixEnd(key),
}
}
// NewSuperPrefixedQuadRange provides a Range for all keys prefixed with the given
// first and second parts of the Quad key.
func NewSuperPrefixedQuadRange[K1, K2, K3, K4 any](k1 K1, k2 K2) Ranger[Quad[K1, K2, K3, K4]] {
key := QuadSuperPrefix[K1, K2, K3, K4](k1, k2)
return &Range[Quad[K1, K2, K3, K4]]{
start: RangeKeyExact(key),
end: RangeKeyPrefixEnd(key),
}
}
// NewSuperPrefixedQuadRange3 provides a Range for all keys prefixed with the given
// first, second and third parts of the Quad key.
func NewSuperPrefixedQuadRange3[K1, K2, K3, K4 any](k1 K1, k2 K2, k3 K3) Ranger[Quad[K1, K2, K3, K4]] {
key := QuadSuperPrefix3[K1, K2, K3, K4](k1, k2, k3)
return &Range[Quad[K1, K2, K3, K4]]{
start: RangeKeyExact(key),
end: RangeKeyPrefixEnd(key),
}
}

61
collections/quad_test.go Normal file
View File

@ -0,0 +1,61 @@
package collections_test
import (
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/collections"
"cosmossdk.io/collections/colltest"
coretesting "cosmossdk.io/core/testing"
)
func TestQuad(t *testing.T) {
kc := collections.QuadKeyCodec(collections.Uint64Key, collections.StringKey, collections.BytesKey, collections.BoolKey)
t.Run("conformance", func(t *testing.T) {
colltest.TestKeyCodec(t, kc, collections.Join4(uint64(1), "2", []byte("3"), true))
})
}
func TestQuadRange(t *testing.T) {
ctx := coretesting.Context()
sk := coretesting.KVStoreService(ctx, "test")
schema := collections.NewSchemaBuilder(sk)
// this is a key composed of 4 parts: uint64, string, []byte, bool
kc := collections.QuadKeyCodec(collections.Uint64Key, collections.StringKey, collections.BytesKey, collections.BoolKey)
keySet := collections.NewKeySet(schema, collections.NewPrefix(0), "Quad", kc)
keys := []collections.Quad[uint64, string, []byte, bool]{
collections.Join4(uint64(1), "A", []byte("1"), true),
collections.Join4(uint64(1), "A", []byte("2"), true),
collections.Join4(uint64(1), "B", []byte("3"), false),
collections.Join4(uint64(2), "B", []byte("4"), false),
}
for _, k := range keys {
require.NoError(t, keySet.Set(ctx, k))
}
// we prefix over (1) we expect 3 results
iter, err := keySet.Iterate(ctx, collections.NewPrefixedQuadRange[uint64, string, []byte, bool](uint64(1)))
require.NoError(t, err)
gotKeys, err := iter.Keys()
require.NoError(t, err)
require.Equal(t, keys[:3], gotKeys)
// we super prefix over Join(1, "A") we expect 2 results
iter, err = keySet.Iterate(ctx, collections.NewSuperPrefixedQuadRange[uint64, string, []byte, bool](1, "A"))
require.NoError(t, err)
gotKeys, err = iter.Keys()
require.NoError(t, err)
require.Equal(t, keys[:2], gotKeys)
// we super prefix 3 over Join(1, "A", []byte("1")) we expect 1 result
iter, err = keySet.Iterate(ctx, collections.NewSuperPrefixedQuadRange3[uint64, string, []byte, bool](1, "A", []byte("1")))
require.NoError(t, err)
gotKeys, err = iter.Keys()
require.NoError(t, err)
require.Equal(t, keys[:1], gotKeys)
}

View File

@ -161,6 +161,12 @@ func NewSchemaFromAccessor(accessor func(context.Context) store.KVStore) Schema
}
}
// IsOnePerModuleType implements the depinject.OnePerModuleType interface.
func (s Schema) IsOnePerModuleType() {}
// IsAppModule implements the appmodule.AppModule interface.
func (s Schema) IsAppModule() {}
// DefaultGenesis implements the appmodule.HasGenesis.DefaultGenesis method.
func (s Schema) DefaultGenesis(target appmodule.GenesisTarget) error {
for _, name := range s.collectionsOrdered {

View File

@ -32,7 +32,7 @@ func (s Sequence) Peek(ctx context.Context) (uint64, error) {
}
}
// Next returns the next sequence number, and sets the next expected sequence.
// Next returns the current sequence number, and sets the next expected sequence.
// Errors on encoding issues.
func (s Sequence) Next(ctx context.Context) (uint64, error) {
seq, err := s.Peek(ctx)

View File

@ -0,0 +1,15 @@
sonar.projectKey=cosmos-sdk-collections
sonar.organization=cosmos
sonar.projectName=Cosmos SDK - Collections
sonar.project.monorepo.enabled=true
sonar.sources=.
sonar.exclusions=**/*_test.go,**/*.pb.go,**/*.pulsar.go,**/*.pb.gateway.go
sonar.tests=.
sonar.test.inclusions=**/*_test.go
sonar.go.coverage.reportPaths=coverage.out
sonar.sourceEncoding=UTF-8
sonar.scm.provider=git
sonar.scm.forceReloadAll=true

387
collections/triple.go Normal file
View File

@ -0,0 +1,387 @@
package collections
import (
"encoding/json"
"fmt"
"strings"
"cosmossdk.io/collections/codec"
"cosmossdk.io/schema"
)
// Triple defines a multipart key composed of three keys.
type Triple[K1, K2, K3 any] struct {
k1 *K1
k2 *K2
k3 *K3
}
// Join3 instantiates a new Triple instance composed of the three provided keys, in order.
func Join3[K1, K2, K3 any](k1 K1, k2 K2, k3 K3) Triple[K1, K2, K3] {
return Triple[K1, K2, K3]{&k1, &k2, &k3}
}
// K1 returns the first part of the key. If nil, the zero value is returned.
func (t Triple[K1, K2, K3]) K1() (x K1) {
if t.k1 != nil {
return *t.k1
}
return x
}
// K2 returns the second part of the key. If nil, the zero value is returned.
func (t Triple[K1, K2, K3]) K2() (x K2) {
if t.k2 != nil {
return *t.k2
}
return x
}
// K3 returns the third part of the key. If nil, the zero value is returned.
func (t Triple[K1, K2, K3]) K3() (x K3) {
if t.k3 != nil {
return *t.k3
}
return x
}
// TriplePrefix creates a new Triple instance composed only of the first part of the key.
func TriplePrefix[K1, K2, K3 any](k1 K1) Triple[K1, K2, K3] {
return Triple[K1, K2, K3]{k1: &k1}
}
// TripleSuperPrefix creates a new Triple instance composed only of the first two parts of the key.
func TripleSuperPrefix[K1, K2, K3 any](k1 K1, k2 K2) Triple[K1, K2, K3] {
return Triple[K1, K2, K3]{k1: &k1, k2: &k2}
}
// TripleKeyCodec instantiates a new KeyCodec instance that can encode the Triple, given
// the KeyCodecs of the three parts of the key, in order.
func TripleKeyCodec[K1, K2, K3 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 codec.KeyCodec[K2], keyCodec3 codec.KeyCodec[K3]) codec.KeyCodec[Triple[K1, K2, K3]] {
return tripleKeyCodec[K1, K2, K3]{
keyCodec1: keyCodec1,
keyCodec2: keyCodec2,
keyCodec3: keyCodec3,
}
}
func NamedTripleKeyCodec[K1, K2, K3 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2], key3Name string, keyCodec3 codec.KeyCodec[K3]) codec.KeyCodec[Triple[K1, K2, K3]] {
return tripleKeyCodec[K1, K2, K3]{
key1Name: key1Name,
key2Name: key2Name,
key3Name: key3Name,
keyCodec1: keyCodec1,
keyCodec2: keyCodec2,
keyCodec3: keyCodec3,
}
}
type tripleKeyCodec[K1, K2, K3 any] struct {
key1Name, key2Name, key3Name string
keyCodec1 codec.KeyCodec[K1]
keyCodec2 codec.KeyCodec[K2]
keyCodec3 codec.KeyCodec[K3]
}
type jsonTripleKey [3]json.RawMessage
// EncodeJSON convert triple keys to json
func (t tripleKeyCodec[K1, K2, K3]) EncodeJSON(value Triple[K1, K2, K3]) ([]byte, error) {
json1, err := t.keyCodec1.EncodeJSON(*value.k1)
if err != nil {
return nil, err
}
json2, err := t.keyCodec2.EncodeJSON(*value.k2)
if err != nil {
return nil, err
}
json3, err := t.keyCodec3.EncodeJSON(*value.k3)
if err != nil {
return nil, err
}
return json.Marshal(jsonTripleKey{json1, json2, json3})
}
// DecodeJSON convert json to triple keys
func (t tripleKeyCodec[K1, K2, K3]) DecodeJSON(b []byte) (Triple[K1, K2, K3], error) {
var jsonKey jsonTripleKey
err := json.Unmarshal(b, &jsonKey)
if err != nil {
return Triple[K1, K2, K3]{}, err
}
key1, err := t.keyCodec1.DecodeJSON(jsonKey[0])
if err != nil {
return Triple[K1, K2, K3]{}, err
}
key2, err := t.keyCodec2.DecodeJSON(jsonKey[1])
if err != nil {
return Triple[K1, K2, K3]{}, err
}
key3, err := t.keyCodec3.DecodeJSON(jsonKey[2])
if err != nil {
return Triple[K1, K2, K3]{}, err
}
return Join3(key1, key2, key3), nil
}
// Stringify convert triple keys to string
func (t tripleKeyCodec[K1, K2, K3]) Stringify(key Triple[K1, K2, K3]) string {
b := new(strings.Builder)
b.WriteByte('(')
if key.k1 != nil {
b.WriteByte('"')
b.WriteString(t.keyCodec1.Stringify(*key.k1))
b.WriteByte('"')
} else {
b.WriteString("<nil>")
}
b.WriteString(", ")
if key.k2 != nil {
b.WriteByte('"')
b.WriteString(t.keyCodec2.Stringify(*key.k2))
b.WriteByte('"')
} else {
b.WriteString("<nil>")
}
b.WriteString(", ")
if key.k3 != nil {
b.WriteByte('"')
b.WriteString(t.keyCodec3.Stringify(*key.k3))
b.WriteByte('"')
} else {
b.WriteString("<nil>")
}
b.WriteByte(')')
return b.String()
}
func (t tripleKeyCodec[K1, K2, K3]) KeyType() string {
return fmt.Sprintf("Triple[%s,%s,%s]", t.keyCodec1.KeyType(), t.keyCodec2.KeyType(), t.keyCodec3.KeyType())
}
func (t tripleKeyCodec[K1, K2, K3]) Encode(buffer []byte, key Triple[K1, K2, K3]) (int, error) {
writtenTotal := 0
if key.k1 != nil {
written, err := t.keyCodec1.EncodeNonTerminal(buffer, *key.k1)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k2 != nil {
written, err := t.keyCodec2.EncodeNonTerminal(buffer[writtenTotal:], *key.k2)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k3 != nil {
written, err := t.keyCodec3.Encode(buffer[writtenTotal:], *key.k3)
if err != nil {
return 0, err
}
writtenTotal += written
}
return writtenTotal, nil
}
func (t tripleKeyCodec[K1, K2, K3]) Decode(buffer []byte) (int, Triple[K1, K2, K3], error) {
readTotal := 0
read, key1, err := t.keyCodec1.DecodeNonTerminal(buffer)
if err != nil {
return 0, Triple[K1, K2, K3]{}, err
}
readTotal += read
read, key2, err := t.keyCodec2.DecodeNonTerminal(buffer[readTotal:])
if err != nil {
return 0, Triple[K1, K2, K3]{}, err
}
readTotal += read
read, key3, err := t.keyCodec3.Decode(buffer[readTotal:])
if err != nil {
return 0, Triple[K1, K2, K3]{}, err
}
readTotal += read
return readTotal, Join3(key1, key2, key3), nil
}
func (t tripleKeyCodec[K1, K2, K3]) Size(key Triple[K1, K2, K3]) int {
size := 0
if key.k1 != nil {
size += t.keyCodec1.SizeNonTerminal(*key.k1)
}
if key.k2 != nil {
size += t.keyCodec2.SizeNonTerminal(*key.k2)
}
if key.k3 != nil {
size += t.keyCodec3.Size(*key.k3)
}
return size
}
func (t tripleKeyCodec[K1, K2, K3]) EncodeNonTerminal(buffer []byte, key Triple[K1, K2, K3]) (int, error) {
writtenTotal := 0
if key.k1 != nil {
written, err := t.keyCodec1.EncodeNonTerminal(buffer, *key.k1)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k2 != nil {
written, err := t.keyCodec2.EncodeNonTerminal(buffer[writtenTotal:], *key.k2)
if err != nil {
return 0, err
}
writtenTotal += written
}
if key.k3 != nil {
written, err := t.keyCodec3.EncodeNonTerminal(buffer[writtenTotal:], *key.k3)
if err != nil {
return 0, err
}
writtenTotal += written
}
return writtenTotal, nil
}
func (t tripleKeyCodec[K1, K2, K3]) DecodeNonTerminal(buffer []byte) (int, Triple[K1, K2, K3], error) {
readTotal := 0
read, key1, err := t.keyCodec1.DecodeNonTerminal(buffer)
if err != nil {
return 0, Triple[K1, K2, K3]{}, err
}
readTotal += read
read, key2, err := t.keyCodec2.DecodeNonTerminal(buffer[readTotal:])
if err != nil {
return 0, Triple[K1, K2, K3]{}, err
}
readTotal += read
read, key3, err := t.keyCodec3.DecodeNonTerminal(buffer[readTotal:])
if err != nil {
return 0, Triple[K1, K2, K3]{}, err
}
readTotal += read
return readTotal, Join3(key1, key2, key3), nil
}
func (t tripleKeyCodec[K1, K2, K3]) SizeNonTerminal(key Triple[K1, K2, K3]) int {
size := 0
if key.k1 != nil {
size += t.keyCodec1.SizeNonTerminal(*key.k1)
}
if key.k2 != nil {
size += t.keyCodec2.SizeNonTerminal(*key.k2)
}
if key.k3 != nil {
size += t.keyCodec3.SizeNonTerminal(*key.k3)
}
return size
}
func (t tripleKeyCodec[K1, K2, K3]) Name() string {
return fmt.Sprintf("%s,%s,%s", t.key1Name, t.key2Name, t.key3Name)
}
func (t tripleKeyCodec[K1, K2, K3]) SchemaCodec() (codec.SchemaCodec[Triple[K1, K2, K3]], error) {
field1, err := getNamedKeyField(t.keyCodec1, t.key1Name)
if err != nil {
return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("error getting key1 field: %w", err)
}
field2, err := getNamedKeyField(t.keyCodec2, t.key2Name)
if err != nil {
return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("error getting key2 field: %w", err)
}
field3, err := getNamedKeyField(t.keyCodec3, t.key3Name)
if err != nil {
return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("error getting key3 field: %w", err)
}
return codec.SchemaCodec[Triple[K1, K2, K3]]{
Fields: []schema.Field{field1, field2, field3},
ToSchemaType: func(t Triple[K1, K2, K3]) (any, error) {
return []interface{}{t.K1(), t.K2(), t.K3()}, nil
},
FromSchemaType: func(a any) (Triple[K1, K2, K3], error) {
aSlice, ok := a.([]interface{})
if !ok || len(aSlice) != 3 {
return Triple[K1, K2, K3]{}, fmt.Errorf("expected slice of length 3, got %T", a)
}
return Join3(aSlice[0].(K1), aSlice[1].(K2), aSlice[2].(K3)), nil
},
}, nil
}
// NewPrefixUntilTripleRange defines a collection query which ranges until the provided Pair prefix.
// Unstable: this API might change in the future.
func NewPrefixUntilTripleRange[K1, K2, K3 any](k1 K1) Ranger[Triple[K1, K2, K3]] {
key := TriplePrefix[K1, K2, K3](k1)
return &Range[Triple[K1, K2, K3]]{
end: RangeKeyPrefixEnd(key),
}
}
// NewPrefixedTripleRange provides a Range for all keys prefixed with the given
// first part of the Triple key.
func NewPrefixedTripleRange[K1, K2, K3 any](k1 K1) Ranger[Triple[K1, K2, K3]] {
key := TriplePrefix[K1, K2, K3](k1)
return &Range[Triple[K1, K2, K3]]{
start: RangeKeyExact(key),
end: RangeKeyPrefixEnd(key),
}
}
// NewSuperPrefixedTripleRange provides a Range for all keys prefixed with the given
// first and second parts of the Triple key.
func NewSuperPrefixedTripleRange[K1, K2, K3 any](k1 K1, k2 K2) Ranger[Triple[K1, K2, K3]] {
key := TripleSuperPrefix[K1, K2, K3](k1, k2)
return &Range[Triple[K1, K2, K3]]{
start: RangeKeyExact(key),
end: RangeKeyPrefixEnd(key),
}
}
// NewPrefixUntilTripleRangeReversed defines a collection query which ranges until the provided Pair prefix
// in reverse order.
// Unstable: this API might change in the future.
func NewPrefixUntilTripleRangeReversed[K1, K2, K3 any](k1 K1) Ranger[Triple[K1, K2, K3]] {
key := TriplePrefix[K1, K2, K3](k1)
return &Range[Triple[K1, K2, K3]]{
end: RangeKeyPrefixEnd(key),
order: OrderDescending,
}
}
// NewPrefixedTripleRangeReversed provides a Range for all keys prefixed with the given
// first part of the Triple key in reverse order.
func NewPrefixedTripleRangeReversed[K1, K2, K3 any](k1 K1) Ranger[Triple[K1, K2, K3]] {
key := TriplePrefix[K1, K2, K3](k1)
return &Range[Triple[K1, K2, K3]]{
start: RangeKeyExact(key),
end: RangeKeyPrefixEnd(key),
order: OrderDescending,
}
}
// NewSuperPrefixedTripleRangeReversed provides a Range for all keys prefixed with the given
// first and second parts of the Triple key in reverse order.
func NewSuperPrefixedTripleRangeReversed[K1, K2, K3 any](k1 K1, k2 K2) Ranger[Triple[K1, K2, K3]] {
key := TripleSuperPrefix[K1, K2, K3](k1, k2)
return &Range[Triple[K1, K2, K3]]{
start: RangeKeyExact(key),
end: RangeKeyPrefixEnd(key),
order: OrderDescending,
}
}

View File

@ -0,0 +1,74 @@
package collections_test
import (
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/collections"
"cosmossdk.io/collections/colltest"
coretesting "cosmossdk.io/core/testing"
)
func TestTriple(t *testing.T) {
kc := collections.TripleKeyCodec(collections.Uint64Key, collections.StringKey, collections.BytesKey)
t.Run("conformance", func(t *testing.T) {
colltest.TestKeyCodec(t, kc, collections.Join3(uint64(1), "2", []byte("3")))
})
}
func TestTripleRange(t *testing.T) {
ctx := coretesting.Context()
sk := coretesting.KVStoreService(ctx, "test")
schema := collections.NewSchemaBuilder(sk)
// this is a key composed of 3 parts: uint64, string, []byte
kc := collections.TripleKeyCodec(collections.Uint64Key, collections.StringKey, collections.BytesKey)
keySet := collections.NewKeySet(schema, collections.NewPrefix(0), "triple", kc)
keys := []collections.Triple[uint64, string, []byte]{
collections.Join3(uint64(1), "A", []byte("1")),
collections.Join3(uint64(1), "A", []byte("2")),
collections.Join3(uint64(1), "B", []byte("3")),
collections.Join3(uint64(2), "B", []byte("4")),
}
for _, k := range keys {
require.NoError(t, keySet.Set(ctx, k))
}
// we prefix over (1) we expect 3 results
iter, err := keySet.Iterate(ctx, collections.NewPrefixedTripleRange[uint64, string, []byte](uint64(1)))
require.NoError(t, err)
gotKeys, err := iter.Keys()
require.NoError(t, err)
require.Equal(t, keys[:3], gotKeys)
// we prefix over (1) with "reverse" enabled, we expect 3 results in reverse order
iter, err = keySet.Iterate(ctx, collections.NewPrefixedTripleRangeReversed[uint64, string, []byte](uint64(1)))
require.NoError(t, err)
gotKeys, err = iter.Keys()
require.NoError(t, err)
require.Len(t, gotKeys, 3)
for i := range gotKeys {
require.Equal(t, gotKeys[i], keys[len(gotKeys)-i-1])
}
// we super prefix over Join(1, "A") we expect 2 results
iter, err = keySet.Iterate(ctx, collections.NewSuperPrefixedTripleRange[uint64, string, []byte](1, "A"))
require.NoError(t, err)
gotKeys, err = iter.Keys()
require.NoError(t, err)
require.Equal(t, keys[:2], gotKeys)
// we prefix over Join(1, "A") with "reverse" enabled, we expect 2 results in reverse order
iter, err = keySet.Iterate(ctx, collections.NewSuperPrefixedTripleRangeReversed[uint64, string, []byte](1, "A"))
require.NoError(t, err)
gotKeys, err = iter.Keys()
require.NoError(t, err)
require.Len(t, gotKeys, 2)
for i := range gotKeys {
require.Equal(t, gotKeys[i], keys[len(gotKeys)-i-1])
}
}

141
collections/vec.go Normal file
View File

@ -0,0 +1,141 @@
package collections
import (
"context"
"errors"
"fmt"
"cosmossdk.io/collections/codec"
)
var (
// ErrEmptyVec is returned when trying to pop an element from an empty Vec.
ErrEmptyVec = errors.New("vec is empty")
// ErrOutOfBounds is returned when trying to do an operation on an index that is out of bounds.
ErrOutOfBounds = errors.New("vec index is out of bounds")
)
const (
VecElementsNameSuffix = "_elements"
VecLengthNameSuffix = "_length"
VecElementsPrefixSuffix = 0x0
VecLengthPrefixSuffix = 0x1
)
// NewVec creates a new Vec instance. Since Vec relies on two collections, one for the length
// and the other for the elements, it will register two state objects on the schema builder.
// The first is the length which is an item, whose prefix is the provided prefix with a suffix
// which equals to VecLengthPrefixSuffix, the name is also suffixed with VecLengthNameSuffix.
// The second is the elements which is a map, whose prefix is the provided prefix with a suffix
// which equals to VecElementsPrefixSuffix, the name is also suffixed with VecElementsNameSuffix.
func NewVec[T any](sb *SchemaBuilder, prefix Prefix, name string, vc codec.ValueCodec[T]) Vec[T] {
return Vec[T]{
length: NewItem(sb, append(prefix, VecLengthPrefixSuffix), name+VecLengthNameSuffix, Uint64Value),
elements: NewMap(sb, append(prefix, VecElementsPrefixSuffix), name+VecElementsNameSuffix, Uint64Key, vc),
}
}
// Vec works like a slice sitting on top of a KVStore.
// It relies on two collections, one for the length which is an Item[uint64],
// the other for the elements which is a Map[uint64, T].
type Vec[T any] struct {
length Item[uint64]
elements Map[uint64, T]
}
// Push adds an element to the end of the Vec.
func (v Vec[T]) Push(ctx context.Context, elem T) error {
length, err := v.length.Get(ctx)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
err = v.elements.Set(ctx, length, elem)
if err != nil {
return err
}
err = v.length.Set(ctx, length+1)
if err != nil {
return err
}
return nil
}
// Pop removes an element from the end of the Vec and returns it. Fails
// if the Vec is empty.
func (v Vec[T]) Pop(ctx context.Context) (elem T, err error) {
length, err := v.length.Get(ctx)
if err != nil && !errors.Is(err, ErrNotFound) {
return elem, err
}
if length == 0 {
return elem, ErrEmptyVec
}
length -= 1
elem, err = v.elements.Get(ctx, length)
if err != nil {
return elem, err
}
err = v.elements.Remove(ctx, length)
if err != nil {
return elem, err
}
err = v.length.Set(ctx, length)
if err != nil {
return elem, err
}
return elem, nil
}
// Replace replaces an element at a given index. Fails if the index is out of bounds.
func (v Vec[T]) Replace(ctx context.Context, index uint64, elem T) error {
length, err := v.length.Get(ctx)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
if index >= length {
return fmt.Errorf("%w: length %d", ErrOutOfBounds, length)
}
return v.elements.Set(ctx, index, elem)
}
// Get returns an element at a given index. Returns ErrOutOfBounds
// if the index is out of bounds.
func (v Vec[T]) Get(ctx context.Context, index uint64) (elem T, err error) {
elem, err = v.elements.Get(ctx, index)
switch {
case err == nil:
return elem, nil
case errors.Is(err, ErrNotFound):
return elem, fmt.Errorf("%w: index %d", ErrOutOfBounds, index)
default:
return elem, err
}
}
// Len returns the length of the Vec.
func (v Vec[T]) Len(ctx context.Context) (uint64, error) {
length, err := v.length.Get(ctx)
switch {
// no error, return length as the vec is populated
case err == nil:
return length, nil
// not found, return 0 as the vec is empty
case errors.Is(err, ErrNotFound):
return 0, nil
// something else happened
default:
return 0, err
}
}
// Iterate iterates over the Vec. It returns an Iterator whose key is the index
// and the value is the element at that index.
func (v Vec[T]) Iterate(ctx context.Context, rng Ranger[uint64]) (Iterator[uint64, T], error) {
return v.elements.Iterate(ctx, rng)
}
// Walk walks over the Vec. It calls the walkFn for each element in the Vec,
// where the key is the index and the value is the element at that index.
func (v Vec[T]) Walk(ctx context.Context, rng Ranger[uint64], walkFn func(index uint64, elem T) (stop bool, err error)) error {
return v.elements.Walk(ctx, rng, walkFn)
}

63
collections/vec_test.go Normal file
View File

@ -0,0 +1,63 @@
package collections
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestVec(t *testing.T) {
sk, ctx := deps()
schemaBuilder := NewSchemaBuilder(sk)
vec := NewVec(schemaBuilder, NewPrefix(0), "vec", StringValue)
_, err := schemaBuilder.Build()
require.NoError(t, err)
// length when empty
length, err := vec.Len(ctx)
require.NoError(t, err)
require.Equal(t, uint64(0), length)
// pop when empty should error with an empty vec error
_, err = vec.Pop(ctx)
require.ErrorIs(t, err, ErrEmptyVec)
// replace when out of bounds should error with an out of bounds error
err = vec.Replace(ctx, 0, "foo")
require.ErrorIs(t, err, ErrOutOfBounds)
// get out of bounds should error with an out of bounds error
_, err = vec.Get(ctx, 0)
require.ErrorIs(t, err, ErrOutOfBounds)
// push
err = vec.Push(ctx, "foo")
require.NoError(t, err)
// push more
err = vec.Push(ctx, "bar")
require.NoError(t, err)
// check length
length, err = vec.Len(ctx)
require.NoError(t, err)
require.Equal(t, uint64(2), length)
// get
v, err := vec.Get(ctx, 0)
require.NoError(t, err)
require.Equal(t, "foo", v)
// replace
err = vec.Replace(ctx, 0, "bar")
require.NoError(t, err)
v, err = vec.Get(ctx, 0)
require.NoError(t, err)
require.Equal(t, "bar", v)
// pop
v, err = vec.Pop(ctx)
require.NoError(t, err)
require.Equal(t, "bar", v)
}