diff --git a/CHANGELOG.md b/CHANGELOG.md index c3360d8ae415..52a06407172b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/slashing) [#15580](https://github.com/cosmos/cosmos-sdk/pull/15580) The validator slashing window now stores "chunked" bitmap entries for each validator's signing window instead of a single boolean entry per signing window index. * (x/feegrant) [#14294](https://github.com/cosmos/cosmos-sdk/pull/14294) Moved the logic of rejecting duplicate grant from `msg_server` to `keeper` method. * (x/staking) [#14590](https://github.com/cosmos/cosmos-sdk/pull/14590) `MsgUndelegateResponse` now includes undelegated amount. `x/staking` module's `keeper.Undelegate` now returns 3 values (completionTime,undelegateAmount,error) instead of 2. +* (x/staking) (#15731) (https://github.com/cosmos/cosmos-sdk/pull/15731) Introducing a new index to retrieve the delegations by validator efficiently. ### API Breaking Changes diff --git a/tests/integration/staking/keeper/determinstic_test.go b/tests/integration/staking/keeper/determinstic_test.go index a1a97ff36c3f..292a335dfc72 100644 --- a/tests/integration/staking/keeper/determinstic_test.go +++ b/tests/integration/staking/keeper/determinstic_test.go @@ -327,7 +327,7 @@ func TestGRPCValidatorDelegations(t *testing.T) { ValidatorAddr: validator.OperatorAddress, } - testdata.DeterministicIterations(f.ctx, t, req, f.queryClient.ValidatorDelegations, 11985, false) + testdata.DeterministicIterations(f.ctx, t, req, f.queryClient.ValidatorDelegations, 14475, false) } func TestGRPCValidatorUnbondingDelegations(t *testing.T) { diff --git a/tests/integration/staking/keeper/validator_bench_test.go b/tests/integration/staking/keeper/validator_bench_test.go index e7eb60c51216..cf6174c6b213 100644 --- a/tests/integration/staking/keeper/validator_bench_test.go +++ b/tests/integration/staking/keeper/validator_bench_test.go @@ -1,6 +1,15 @@ package keeper_test -import "testing" +import ( + "fmt" + "testing" + + "cosmossdk.io/simapp" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil" + "github.com/cosmos/cosmos-sdk/x/staking/types" +) func BenchmarkGetValidator(b *testing.B) { // 900 is the max number we are allowed to use in order to avoid simtestutil.CreateTestPubKeys @@ -27,3 +36,117 @@ func BenchmarkGetValidator(b *testing.B) { } } } + +func BenchmarkGetValidatorDelegations(b *testing.B) { + var totalPower int64 + powersNumber := 10 + + powers := make([]int64, powersNumber) + for i := range powers { + powers[i] = int64(i) + totalPower += int64(i) + } + + app, ctx, _, valAddrs, vals := initValidators(b, totalPower, len(powers), powers) + for _, validator := range vals { + app.StakingKeeper.SetValidator(ctx, validator) + } + + delegationsNum := 1000 + for _, val := range valAddrs { + for i := 0; i < delegationsNum; i++ { + delegator := sdk.AccAddress(fmt.Sprintf("address%d", i)) + banktestutil.FundAccount(app.BankKeeper, ctx, delegator, + sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(int64(i))))) + NewDel := types.NewDelegation(delegator, val, sdk.NewDec(int64(i))) + app.StakingKeeper.SetDelegation(ctx, NewDel) + } + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + updateValidatorDelegations(ctx, app, valAddrs[0], sdk.ValAddress("val")) + } +} + +func BenchmarkGetValidatorDelegationsLegacy(b *testing.B) { + var totalPower int64 + powersNumber := 10 + + powers := make([]int64, powersNumber) + for i := range powers { + powers[i] = int64(i) + totalPower += int64(i) + } + + app, ctx, _, valAddrs, vals := initValidators(b, totalPower, len(powers), powers) + + for _, validator := range vals { + app.StakingKeeper.SetValidator(ctx, validator) + } + + delegationsNum := 1000 + for _, val := range valAddrs { + for i := 0; i < delegationsNum; i++ { + delegator := sdk.AccAddress(fmt.Sprintf("address%d", i)) + banktestutil.FundAccount(app.BankKeeper, ctx, delegator, + sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(int64(i))))) + NewDel := types.NewDelegation(delegator, val, sdk.NewDec(int64(i))) + app.StakingKeeper.SetDelegation(ctx, NewDel) + } + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + updateValidatorDelegationsLegacy(ctx, app, valAddrs[0], sdk.ValAddress("val")) + } +} + +func updateValidatorDelegationsLegacy(ctx sdk.Context, app *simapp.SimApp, existingValAddr, newValAddr sdk.ValAddress) { + storeKey := app.GetKey(types.StoreKey) + cdc, k := app.AppCodec(), app.StakingKeeper + + store := ctx.KVStore(storeKey) + + iterator := storetypes.KVStorePrefixIterator(store, types.DelegationKey) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + delegation := types.MustUnmarshalDelegation(cdc, iterator.Value()) + if delegation.GetValidatorAddr().Equals(existingValAddr) { + k.RemoveDelegation(ctx, delegation) + delegation.ValidatorAddress = newValAddr.String() + k.SetDelegation(ctx, delegation) + } + } +} + +func updateValidatorDelegations(ctx sdk.Context, app *simapp.SimApp, existingValAddr, newValAddr sdk.ValAddress) { + storeKey := app.GetKey(types.StoreKey) + cdc, k := app.AppCodec(), app.StakingKeeper + + store := ctx.KVStore(storeKey) + + itr := storetypes.KVStorePrefixIterator(store, types.GetDelegationsByValPrefixKey(existingValAddr)) + defer itr.Close() + + for ; itr.Valid(); itr.Next() { + key := itr.Key() + valAddr, delAddr, err := types.ParseDelegationsByValKey(key) + if err != nil { + panic(err) + } + + bz := store.Get(types.GetDelegationKey(delAddr, valAddr)) + delegation := types.MustUnmarshalDelegation(cdc, bz) + + // remove old operator addr from delegation + if err := k.RemoveDelegation(ctx, delegation); err != nil { + panic(err) + } + + delegation.ValidatorAddress = newValAddr.String() + // add with new operator addr + k.SetDelegation(ctx, delegation) + } +} diff --git a/x/staking/keeper/delegation.go b/x/staking/keeper/delegation.go index 3ad718aaffad..7157d92d8646 100644 --- a/x/staking/keeper/delegation.go +++ b/x/staking/keeper/delegation.go @@ -60,14 +60,22 @@ func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegati func (k Keeper) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) (delegations []types.Delegation) { store := ctx.KVStore(k.storeKey) - iterator := storetypes.KVStorePrefixIterator(store, types.DelegationKey) + iterator := storetypes.KVStorePrefixIterator(store, types.GetDelegationsByValPrefixKey(valAddr)) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { - delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) - if delegation.GetValidatorAddr().Equals(valAddr) { - delegations = append(delegations, delegation) + var delegation types.Delegation + valAddr, delAddr, err := types.ParseDelegationsByValKey(iterator.Key()) + if err != nil { + panic(err) + } + + bz := store.Get(types.GetDelegationKey(delAddr, valAddr)) + if err := k.cdc.Unmarshal(bz, &delegation); err != nil { + panic(err) } + + delegations = append(delegations, delegation) } return delegations @@ -103,6 +111,9 @@ func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { store := ctx.KVStore(k.storeKey) b := types.MustMarshalDelegation(k.cdc, delegation) store.Set(types.GetDelegationKey(delegatorAddress, delegation.GetValidatorAddr()), b) + + // set the delegation in validator delegator index + store.Set(types.GetDelegationsByValKey(delegation.GetValidatorAddr(), delegatorAddress), []byte{}) } // RemoveDelegation removes a delegation @@ -119,6 +130,8 @@ func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) e store := ctx.KVStore(k.storeKey) store.Delete(types.GetDelegationKey(delegatorAddress, delegation.GetValidatorAddr())) + store.Delete(types.GetDelegationsByValKey(delegation.GetValidatorAddr(), delegatorAddress)) + return nil } diff --git a/x/staking/keeper/delegation_test.go b/x/staking/keeper/delegation_test.go index fa2cd3351eb5..1eba4d8a0bc3 100644 --- a/x/staking/keeper/delegation_test.go +++ b/x/staking/keeper/delegation_test.go @@ -144,6 +144,83 @@ func (s *KeeperTestSuite) TestDelegation() { require.Equal(0, len(resBonds)) } +func (s *KeeperTestSuite) TestDelegationsByValIndex() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + addrDels, valAddrs := createValAddrs(3) + + for _, addr := range addrDels { + s.accountKeeper.EXPECT().StringToBytes(addr.String()).Return(addr, nil).AnyTimes() + s.accountKeeper.EXPECT().BytesToString(addr).Return(addr.String(), nil).AnyTimes() + s.bankKeeper.EXPECT().DelegateCoinsFromAccountToModule(gomock.Any(), addr, gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + } + + // construct the validators + amts := []math.Int{sdk.NewInt(9), sdk.NewInt(8), sdk.NewInt(7)} + var validators [3]stakingtypes.Validator + for i, amt := range amts { + validators[i] = testutil.NewValidator(s.T(), valAddrs[i], PKs[i]) + validators[i], _ = validators[i].AddTokensFromDel(amt) + + validators[i] = stakingkeeper.TestingUpdateValidator(keeper, ctx, validators[i], true) + } + + // delegate 2 tokens + // + // total delegations after delegating: del1 -> 2stake + _, err := s.msgServer.Delegate(ctx, stakingtypes.NewMsgDelegate(addrDels[0], valAddrs[0], sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(2)))) + require.NoError(err) + + dels := s.stakingKeeper.GetValidatorDelegations(ctx, valAddrs[0]) + require.Len(dels, 1) + + // delegate 4 tokens + // + // total delegations after delegating: del1 -> 2stake, del2 -> 4stake + _, err = s.msgServer.Delegate(ctx, stakingtypes.NewMsgDelegate(addrDels[1], valAddrs[0], sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(4)))) + require.NoError(err) + + dels = s.stakingKeeper.GetValidatorDelegations(ctx, valAddrs[0]) + require.Len(dels, 2) + + // undelegate 1 token from del1 + // + // total delegations after undelegating: del1 -> 1stake, del2 -> 4stake + _, err = s.msgServer.Undelegate(ctx, stakingtypes.NewMsgUndelegate(addrDels[0], valAddrs[0], sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1)))) + require.NoError(err) + + dels = s.stakingKeeper.GetValidatorDelegations(ctx, valAddrs[0]) + require.Len(dels, 2) + + // undelegate 1 token from del1 + // + // total delegations after undelegating: del2 -> 4stake + _, err = s.msgServer.Undelegate(ctx, stakingtypes.NewMsgUndelegate(addrDels[0], valAddrs[0], sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1)))) + require.NoError(err) + + dels = s.stakingKeeper.GetValidatorDelegations(ctx, valAddrs[0]) + require.Len(dels, 1) + + // undelegate 2 tokens from del2 + // + // total delegations after undelegating: del2 -> 2stake + _, err = s.msgServer.Undelegate(ctx, stakingtypes.NewMsgUndelegate(addrDels[1], valAddrs[0], sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(2)))) + require.NoError(err) + + dels = s.stakingKeeper.GetValidatorDelegations(ctx, valAddrs[0]) + require.Len(dels, 1) + + // undelegate 2 tokens from del2 + // + // total delegations after undelegating: [] + _, err = s.msgServer.Undelegate(ctx, stakingtypes.NewMsgUndelegate(addrDels[1], valAddrs[0], sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(2)))) + require.NoError(err) + + dels = s.stakingKeeper.GetValidatorDelegations(ctx, valAddrs[0]) + require.Len(dels, 0) +} + // tests Get/Set/Remove UnbondingDelegation func (s *KeeperTestSuite) TestUnbondingDelegation() { ctx, keeper := s.ctx, s.stakingKeeper diff --git a/x/staking/keeper/grpc_query.go b/x/staking/keeper/grpc_query.go index ea9f29810223..e11bef29dd28 100644 --- a/x/staking/keeper/grpc_query.go +++ b/x/staking/keeper/grpc_query.go @@ -92,31 +92,44 @@ func (k Querier) ValidatorDelegations(c context.Context, req *types.QueryValidat if req.ValidatorAddr == "" { return nil, status.Error(codes.InvalidArgument, "validator address cannot be empty") } + + valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) + if err != nil { + return nil, err + } ctx := sdk.UnwrapSDKContext(c) store := ctx.KVStore(k.storeKey) - valStore := prefix.NewStore(store, types.DelegationKey) - delegations, pageRes, err := query.GenericFilteredPaginate(k.cdc, valStore, req.Pagination, func(key []byte, delegation *types.Delegation) (*types.Delegation, error) { - valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) - if err != nil { - return nil, err - } + delStore := prefix.NewStore(store, types.GetDelegationsByValPrefixKey(valAddr)) - if !delegation.GetValidatorAddr().Equals(valAddr) { - return nil, nil + var ( + dels types.Delegations + pageRes *query.PageResponse + ) + pageRes, err = query.Paginate(delStore, req.Pagination, func(delAddr, value []byte) error { + bz := store.Get(types.GetDelegationKey(delAddr, valAddr)) + + var delegation types.Delegation + err = k.cdc.Unmarshal(bz, &delegation) + if err != nil { + return err } - return delegation, nil - }, func() *types.Delegation { - return &types.Delegation{} + dels = append(dels, delegation) + return nil }) if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } + delegations, pageResponse, err := k.getValidatorDelegationsLegacy(ctx, req) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + dels = types.Delegations{} + for _, d := range delegations { + dels = append(dels, *d) + } - dels := types.Delegations{} - for _, d := range delegations { - dels = append(dels, *d) + pageRes = pageResponse } delResponses, err := DelegationsToDelegationResponses(ctx, k.Keeper, dels) @@ -129,6 +142,26 @@ func (k Querier) ValidatorDelegations(c context.Context, req *types.QueryValidat }, nil } +func (k Querier) getValidatorDelegationsLegacy(ctx sdk.Context, req *types.QueryValidatorDelegationsRequest) ([]*types.Delegation, *query.PageResponse, error) { + store := ctx.KVStore(k.storeKey) + + valStore := prefix.NewStore(store, types.DelegationKey) + return query.GenericFilteredPaginate(k.cdc, valStore, req.Pagination, func(key []byte, delegation *types.Delegation) (*types.Delegation, error) { + valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) + if err != nil { + return nil, err + } + + if !delegation.GetValidatorAddr().Equals(valAddr) { + return nil, nil + } + + return delegation, nil + }, func() *types.Delegation { + return &types.Delegation{} + }) +} + // ValidatorUnbondingDelegations queries unbonding delegations of a validator func (k Querier) ValidatorUnbondingDelegations(c context.Context, req *types.QueryValidatorUnbondingDelegationsRequest) (*types.QueryValidatorUnbondingDelegationsResponse, error) { if req == nil { diff --git a/x/staking/keeper/migrations.go b/x/staking/keeper/migrations.go index 26dd511e2195..8782f5978316 100644 --- a/x/staking/keeper/migrations.go +++ b/x/staking/keeper/migrations.go @@ -40,5 +40,5 @@ func (m Migrator) Migrate3to4(ctx sdk.Context) error { // Migrate4to5 migrates x/staking state from consensus version 4 to 5. func (m Migrator) Migrate4to5(ctx sdk.Context) error { - return v5.MigrateStore(ctx, m.keeper.storeKey) + return v5.MigrateStore(ctx, m.keeper.storeKey, m.keeper.cdc) } diff --git a/x/staking/migrations/v5/keys.go b/x/staking/migrations/v5/keys.go index 3725301f77d2..d62b0a2dc027 100644 --- a/x/staking/migrations/v5/keys.go +++ b/x/staking/migrations/v5/keys.go @@ -1,8 +1,57 @@ package v5 -import "encoding/binary" +import ( + "bytes" + "encoding/binary" + "fmt" -var HistoricalInfoKey = []byte{0x50} // prefix for the historical info + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // ModuleName is the name of the module + ModuleName = "staking" +) + +var ( + DelegationKey = []byte{0x31} // key for a delegation + DelegationByValIndexKey = []byte{0x37} // key for delegations by a validator + HistoricalInfoKey = []byte{0x50} // prefix for the historical info +) + +// ParseDelegationKey parses given key and returns delagator, validator address bytes +func ParseDelegationKey(bz []byte) (sdk.AccAddress, sdk.ValAddress, error) { + prefixLength := len(DelegationKey) + if prefix := bz[:prefixLength]; !bytes.Equal(prefix, DelegationKey) { + return nil, nil, fmt.Errorf("invalid prefix; expected: %X, got: %x", DelegationKey, prefix) + } + + bz = bz[prefixLength:] // remove the prefix byte + if len(bz) == 0 { + return nil, nil, fmt.Errorf("no bytes left to parse: %X", bz) + } + + delAddrLen := bz[0] + bz = bz[1:] // remove the length byte of delegator address. + if len(bz) == 0 { + return nil, nil, fmt.Errorf("no bytes left to parse delegator address: %X", bz) + } + + del := bz[:int(delAddrLen)] + bz = bz[int(delAddrLen):] // remove the length byte of a delegator address + if len(bz) == 0 { + return nil, nil, fmt.Errorf("no bytes left to parse delegator address: %X", bz) + } + + bz = bz[1:] // remove the validator address bytes. + if len(bz) == 0 { + return nil, nil, fmt.Errorf("no bytes left to parse validator address: %X", bz) + } + + val := bz + + return del, val, nil +} // GetHistoricalInfoKey returns a key prefix for indexing HistoricalInfo objects. func GetHistoricalInfoKey(height int64) []byte { diff --git a/x/staking/migrations/v5/migrations_test.go b/x/staking/migrations/v5/migrations_test.go index 67607ffa4e88..406b3e001f7d 100644 --- a/x/staking/migrations/v5/migrations_test.go +++ b/x/staking/migrations/v5/migrations_test.go @@ -8,11 +8,16 @@ import ( storetypes "cosmossdk.io/store/types" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/testutil" + "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/cosmos/cosmos-sdk/x/staking" v2 "github.com/cosmos/cosmos-sdk/x/staking/migrations/v2" v5 "github.com/cosmos/cosmos-sdk/x/staking/migrations/v5" - stackingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -56,7 +61,7 @@ func TestHistoricalKeysMigration(t *testing.T) { } // migrate store to new key format - require.NoErrorf(t, v5.MigrateStore(ctx, storeKey), "v5.MigrateStore failed, seed: %d", seed) + require.NoErrorf(t, v5.MigrateStore(ctx, storeKey, cdc), "v5.MigrateStore failed, seed: %d", seed) // check results for _, tc := range testCases { @@ -66,6 +71,58 @@ func TestHistoricalKeysMigration(t *testing.T) { } } -func createHistoricalInfo(height int64, chainID string) *stackingtypes.HistoricalInfo { - return &stackingtypes.HistoricalInfo{Header: cmtproto.Header{ChainID: chainID, Height: height}} +func createHistoricalInfo(height int64, chainID string) *stakingtypes.HistoricalInfo { + return &stakingtypes.HistoricalInfo{Header: cmtproto.Header{ChainID: chainID, Height: height}} +} + +func TestDelegationsByValidatorMigrations(t *testing.T) { + cdc := moduletestutil.MakeTestEncodingConfig(staking.AppModuleBasic{}).Codec + storeKey := storetypes.NewKVStoreKey(v5.ModuleName) + tKey := storetypes.NewTransientStoreKey("transient_test") + ctx := testutil.DefaultContext(storeKey, tKey) + store := ctx.KVStore(storeKey) + + accAddrs := sims.CreateIncrementalAccounts(11) + valAddrs := sims.ConvertAddrsToValAddrs(accAddrs[0:1]) + var addedDels []stakingtypes.Delegation + + for i := 1; i < 11; i++ { + del1 := stakingtypes.NewDelegation(accAddrs[i], valAddrs[0], sdk.NewDec(100)) + store.Set(stakingtypes.GetDelegationKey(accAddrs[i], valAddrs[0]), stakingtypes.MustMarshalDelegation(cdc, del1)) + addedDels = append(addedDels, del1) + } + + // before migration the state of delegations by val index should be empty + dels := getValDelegations(ctx, cdc, storeKey, valAddrs[0]) + assert.Len(t, dels, 0) + + err := v5.MigrateStore(ctx, storeKey, cdc) + assert.NoError(t, err) + + // after migration the state of delegations by val index should not be empty + dels = getValDelegations(ctx, cdc, storeKey, valAddrs[0]) + assert.Len(t, dels, len(addedDels)) + assert.Equal(t, addedDels, dels) +} + +func getValDelegations(ctx sdk.Context, cdc codec.Codec, storeKey storetypes.StoreKey, valAddr sdk.ValAddress) []stakingtypes.Delegation { + var delegations []stakingtypes.Delegation + + store := ctx.KVStore(storeKey) + iterator := storetypes.KVStorePrefixIterator(store, stakingtypes.GetDelegationsByValPrefixKey(valAddr)) + for ; iterator.Valid(); iterator.Next() { + var delegation stakingtypes.Delegation + valAddr, delAddr, err := stakingtypes.ParseDelegationsByValKey(iterator.Key()) + if err != nil { + panic(err) + } + + bz := store.Get(stakingtypes.GetDelegationKey(delAddr, valAddr)) + + cdc.MustUnmarshal(bz, &delegation) + + delegations = append(delegations, delegation) + } + + return delegations } diff --git a/x/staking/migrations/v5/store.go b/x/staking/migrations/v5/store.go index 4ab2eadece26..f87252da1a7e 100644 --- a/x/staking/migrations/v5/store.go +++ b/x/staking/migrations/v5/store.go @@ -6,13 +6,37 @@ import ( "cosmossdk.io/log" "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) +func migrateDelegationsByValidatorIndex(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.BinaryCodec) error { + store := ctx.KVStore(storeKey) + + iterator := storetypes.KVStorePrefixIterator(store, DelegationKey) + + for ; iterator.Valid(); iterator.Next() { + key := iterator.Key() + del, val, err := ParseDelegationKey(key) + if err != nil { + return err + } + + store.Set(types.GetDelegationsByValKey(val, del), []byte{}) + } + + return nil +} + // MigrateStore performs in-place store migrations from v4 to v5. -func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey) error { +func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.BinaryCodec) error { store := ctx.KVStore(storeKey) + if err := migrateDelegationsByValidatorIndex(ctx, storeKey, cdc); err != nil { + return err + } return migrateHistoricalInfoKeys(store, ctx.Logger()) } diff --git a/x/staking/types/keys.go b/x/staking/types/keys.go index d197d58d1d32..b3e9633233f3 100644 --- a/x/staking/types/keys.go +++ b/x/staking/types/keys.go @@ -40,6 +40,7 @@ var ( RedelegationKey = []byte{0x34} // key for a redelegation RedelegationByValSrcIndexKey = []byte{0x35} // prefix for each key for an redelegation, by source validator operator RedelegationByValDstIndexKey = []byte{0x36} // prefix for each key for an redelegation, by destination validator operator + DelegationByValIndexKey = []byte{0x37} // key for delegations by a validator UnbondingIDKey = []byte{0x37} // key for the counter for the incrementing id for UnbondingOperations UnbondingIndexKey = []byte{0x38} // prefix for an index for looking up unbonding operations by their IDs @@ -210,6 +211,47 @@ func GetDelegationKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte { return append(GetDelegationsKey(delAddr), address.MustLengthPrefix(valAddr)...) } +// GetDelegationsByValKey creates the key for delegations by validator address +// VALUE: staking/Delegation +func GetDelegationsByValKey(valAddr sdk.ValAddress, delAddr sdk.AccAddress) []byte { + return append(GetDelegationsByValPrefixKey(valAddr), delAddr...) +} + +// GetDelegationsByValPrefixKey builds a prefix key bytes with the given validator address bytes. +func GetDelegationsByValPrefixKey(valAddr sdk.ValAddress) []byte { + return append(DelegationByValIndexKey, address.MustLengthPrefix(valAddr)...) +} + +// ParseDelegationsByValKey parses given key and returns validator, delegator address bytes +func ParseDelegationsByValKey(bz []byte) (sdk.ValAddress, sdk.AccAddress, error) { + prefixLength := len(DelegationByValIndexKey) + if prefix := bz[:prefixLength]; !bytes.Equal(prefix, DelegationByValIndexKey) { + return nil, nil, fmt.Errorf("invalid prefix; expected: %X, got: %x", DelegationByValIndexKey, prefix) + } + + bz = bz[prefixLength:] // remove the prefix byte + if len(bz) == 0 { + return nil, nil, fmt.Errorf("no bytes left to parse: %X", bz) + } + + valAddrLen := bz[0] + bz = bz[1:] // remove the length byte of validator address. + if len(bz) == 0 { + return nil, nil, fmt.Errorf("no bytes left to parse validator address: %X", bz) + } + + val := bz[0:int(valAddrLen)] + + bz = bz[int(valAddrLen):] // remove the delegator bytes + if len(bz) == 0 { + return nil, nil, fmt.Errorf("no bytes left to parse delegator address: %X", bz) + } + + del := bz + + return val, del, nil +} + // GetDelegationsKey creates the prefix for a delegator for all validators func GetDelegationsKey(delAddr sdk.AccAddress) []byte { return append(DelegationKey, address.MustLengthPrefix(delAddr)...)