Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(bank): make DenomAddressIndex less strict with respect to index values. #16841

Merged
merged 13 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Bug Fixes

* (server) [#16827](https://github.com/cosmos/cosmos-sdk/pull/16827) Properly use `--trace` flag (before it was setting the trace level instead of displaying the stacktraces).
* (x/bank) [#16841](https://github.com/cosmos/cosmos-sdk/pull/16841) correctly process legacy `DenomAddressIndex` values.

### API Breaking Changes

Expand Down
85 changes: 85 additions & 0 deletions x/bank/keeper/collections_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package keeper_test

import (
"testing"

cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
cmttime "github.com/cometbft/cometbft/types/time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"

"cosmossdk.io/collections"
"cosmossdk.io/log"
"cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"

"github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/bank/keeper"
banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

func TestBankStateCompatibility(t *testing.T) {
key := storetypes.NewKVStoreKey(banktypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test"))
ctx := testCtx.Ctx.WithBlockHeader(cmtproto.Header{Time: cmttime.Now()})
encCfg := moduletestutil.MakeTestEncodingConfig()

storeService := runtime.NewKVStoreService(key)

// gomock initializations
ctrl := gomock.NewController(t)
authKeeper := banktestutil.NewMockAccountKeeper(ctrl)
authKeeper.EXPECT().AddressCodec().Return(address.NewBech32Codec("cosmos")).AnyTimes()

k := keeper.NewBaseKeeper(
encCfg.Codec,
storeService,
authKeeper,
map[string]bool{accAddrs[4].String(): true},
authtypes.NewModuleAddress(banktypes.GovModuleName).String(),
log.NewNopLogger(),
)

// test we can decode balances without problems
// using the old value format of the denom to address index
bankDenomAddressLegacyIndexValue := []byte{0} // taken from: https://github.com/cosmos/cosmos-sdk/blob/v0.47.3/x/bank/keeper/send.go#L361
rawKey, err := collections.EncodeKeyWithPrefix(
banktypes.DenomAddressPrefix,
k.Balances.Indexes.Denom.KeyCodec(),
collections.Join("atom", sdk.AccAddress("test")),
)
require.NoError(t, err)
// we set the index key to the old value.
require.NoError(t, storeService.OpenKVStore(ctx).Set(rawKey, bankDenomAddressLegacyIndexValue))

// test walking is ok
err = k.Balances.Indexes.Denom.Walk(ctx, nil, func(indexingKey string, indexedKey sdk.AccAddress) (stop bool, err error) {
require.Equal(t, indexedKey, sdk.AccAddress("test"))
require.Equal(t, indexingKey, "atom")
return true, nil
})
require.NoError(t, err)

// test matching is also ok
iter, err := k.Balances.Indexes.Denom.MatchExact(ctx, "atom")
require.NoError(t, err)
defer iter.Close()
pks, err := iter.PrimaryKeys()
require.NoError(t, err)
require.Len(t, pks, 1)
require.Equal(t, pks[0], collections.Join(sdk.AccAddress("test"), "atom"))

// assert the index value will be updated to the new format
err = k.Balances.Indexes.Denom.Reference(ctx, collections.Join(sdk.AccAddress("test"), "atom"), math.ZeroInt(), nil)
require.NoError(t, err)

newRawValue, err := storeService.OpenKVStore(ctx).Get(rawKey)
require.NoError(t, err)
require.Equal(t, []byte{}, newRawValue)
}
3 changes: 2 additions & 1 deletion x/bank/keeper/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func newBalancesIndexes(sb *collections.SchemaBuilder) BalancesIndexes {
Denom: indexes.NewReversePair[math.Int](
sb, types.DenomAddressPrefix, "address_by_denom_index",
collections.PairKeyCodec(sdk.LengthPrefixedAddressKey(sdk.AccAddressKey), collections.StringKey), // nolint:staticcheck // Note: refer to the LengthPrefixedAddressKey docs to understand why we do this.
indexes.WithReversePairUncheckedValue(), // denom to address indexes were stored as Key: Join(denom, address) Value: []byte{0}, this will migrate the value to []byte{} in a lazy way.
),
}
}
Expand Down Expand Up @@ -81,7 +82,7 @@ func NewBaseViewKeeper(cdc codec.BinaryCodec, storeService store.KVStoreService,
Supply: collections.NewMap(sb, types.SupplyKey, "supply", collections.StringKey, sdk.IntValue),
DenomMetadata: collections.NewMap(sb, types.DenomMetadataPrefix, "denom_metadata", collections.StringKey, codec.CollValue[types.Metadata](cdc)),
SendEnabled: collections.NewMap(sb, types.SendEnabledPrefix, "send_enabled", collections.StringKey, codec.BoolValue), // NOTE: we use a bool value which uses protobuf to retain state backwards compat
Balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey), types.NewBalanceCompatValueCodec(), newBalancesIndexes(sb)),
Balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey), types.BalanceValueCodec, newBalancesIndexes(sb)),
Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)),
}

Expand Down
24 changes: 5 additions & 19 deletions x/bank/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,13 @@ var (
ParamsKey = collections.NewPrefix(5)
)

// NewBalanceCompatValueCodec is a codec for encoding Balances in a backwards compatible way
// with respect to the old format.
func NewBalanceCompatValueCodec() collcodec.ValueCodec[math.Int] {
return balanceCompatValueCodec{
sdk.IntValue,
}
}

type balanceCompatValueCodec struct {
collcodec.ValueCodec[math.Int]
}

func (v balanceCompatValueCodec) Decode(b []byte) (math.Int, error) {
i, err := v.ValueCodec.Decode(b)
if err == nil {
return i, nil
}
// BalanceValueCodec is a codec for encoding bank balances in a backwards compatible way.
// Historically, balances were represented as Coin, now they're represented as a simple math.Int
var BalanceValueCodec = collcodec.NewAltValueCodec(sdk.IntValue, func(bytes []byte) (math.Int, error) {
c := new(sdk.Coin)
err = c.Unmarshal(b)
err := c.Unmarshal(bytes)
if err != nil {
return math.Int{}, err
}
return c.Amount, nil
}
})
5 changes: 2 additions & 3 deletions x/bank/types/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@ import (
)

func TestBalanceValueCodec(t *testing.T) {
c := NewBalanceCompatValueCodec()
t.Run("value codec implementation", func(t *testing.T) {
colltest.TestValueCodec(t, c, math.NewInt(100))
colltest.TestValueCodec(t, BalanceValueCodec, math.NewInt(100))
})

t.Run("legacy coin", func(t *testing.T) {
coin := sdk.NewInt64Coin("coin", 1000)
b, err := coin.Marshal()
require.NoError(t, err)
amt, err := c.Decode(b)
amt, err := BalanceValueCodec.Decode(b)
require.NoError(t, err)
require.Equal(t, coin.Amount, amt)
})
Expand Down