test: add tests for cons pubkey rotation (#18965)

Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
This commit is contained in:
atheeshp 2024-01-09 10:26:14 +05:30 committed by GitHub
parent 6a2329fc93
commit b16aa35d14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 629 additions and 9 deletions

View File

@ -6,9 +6,10 @@ import (
"strings"
"testing"
"cosmossdk.io/log"
"github.com/rs/zerolog"
"gotest.tools/v3/assert"
"cosmossdk.io/log"
)
func TestLoggerOptionStackTrace(t *testing.T) {

View File

@ -1,6 +1,7 @@
package keeper_test
import (
"bytes"
"context"
"encoding/hex"
"fmt"
@ -28,6 +29,7 @@ import (
"cosmossdk.io/x/evidence/keeper"
evidencetypes "cosmossdk.io/x/evidence/types"
minttypes "cosmossdk.io/x/mint/types"
pooltypes "cosmossdk.io/x/protocolpool/types"
"cosmossdk.io/x/slashing"
slashingkeeper "cosmossdk.io/x/slashing/keeper"
"cosmossdk.io/x/slashing/testutil"
@ -94,6 +96,7 @@ func initFixture(tb testing.TB) *fixture {
authority := authtypes.NewModuleAddress("gov")
maccPerms := map[string][]string{
pooltypes.ModuleName: {},
minttypes.ModuleName: {authtypes.Minter},
stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking},
stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking},
@ -125,7 +128,9 @@ func initFixture(tb testing.TB) *fixture {
slashingKeeper := slashingkeeper.NewKeeper(cdc, codec.NewLegacyAmino(), runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), stakingKeeper, authority.String())
evidenceKeeper := keeper.NewKeeper(cdc, runtime.NewKVStoreService(keys[evidencetypes.StoreKey]), stakingKeeper, slashingKeeper, addresscodec.NewBech32Codec("cosmos"))
stakingKeeper.SetHooks(stakingtypes.NewMultiStakingHooks(slashingKeeper.Hooks()))
evidenceKeeper := keeper.NewKeeper(cdc, runtime.NewKVStoreService(keys[evidencetypes.StoreKey]), stakingKeeper, slashingKeeper, addresscodec.NewBech32Codec(sdk.Bech32PrefixAccAddr))
router := evidencetypes.NewRouter()
router = router.AddRoute(evidencetypes.RouteEquivocation, testEquivocationHandler(evidenceKeeper))
evidenceKeeper.SetRouter(router)
@ -310,6 +315,123 @@ func TestHandleDoubleSign_TooOld(t *testing.T) {
assert.Assert(t, f.slashingKeeper.IsTombstoned(ctx, sdk.ConsAddress(valpubkey.Address())) == false)
}
func TestHandleDoubleSignAfterRotation(t *testing.T) {
t.Parallel()
f := initFixture(t)
ctx := f.sdkCtx.WithIsCheckTx(false).WithBlockHeight(1).WithHeaderInfo(header.Info{Time: time.Now()})
populateValidators(t, f)
power := int64(100)
stakingParams, err := f.stakingKeeper.Params.Get(ctx)
assert.NilError(t, err)
operatorAddr, valpubkey := valAddresses[0], pubkeys[0]
tstaking := stakingtestutil.NewHelper(t, ctx, f.stakingKeeper)
selfDelegation := tstaking.CreateValidatorWithValPower(operatorAddr, valpubkey, power, true)
// execute end-blocker and verify validator attributes
_, err = f.stakingKeeper.EndBlocker(ctx)
assert.NilError(t, err)
assert.DeepEqual(t,
f.bankKeeper.GetAllBalances(ctx, sdk.AccAddress(operatorAddr)).String(),
sdk.NewCoins(sdk.NewCoin(stakingParams.BondDenom, initAmt.Sub(selfDelegation))).String(),
)
valInfo, err := f.stakingKeeper.Validator(ctx, operatorAddr)
assert.NilError(t, err)
consAddrBeforeRotn, err := valInfo.GetConsAddr()
assert.NilError(t, err)
assert.DeepEqual(t, selfDelegation, valInfo.GetBondedTokens())
NewConsPubkey := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53")
msgServer := stakingkeeper.NewMsgServerImpl(f.stakingKeeper)
msg, err := stakingtypes.NewMsgRotateConsPubKey(operatorAddr.String(), NewConsPubkey)
assert.NilError(t, err)
_, err = msgServer.RotateConsPubKey(ctx, msg)
assert.NilError(t, err)
// execute end-blocker and verify validator attributes
_, err = f.stakingKeeper.EndBlocker(ctx)
assert.NilError(t, err)
valInfo, err = f.stakingKeeper.Validator(ctx, operatorAddr)
assert.NilError(t, err)
consAddrAfterRotn, err := valInfo.GetConsAddr()
assert.NilError(t, err)
assert.Equal(t, bytes.Equal(consAddrBeforeRotn, consAddrAfterRotn), false)
// handle a signature to set signing info
err = f.slashingKeeper.HandleValidatorSignature(ctx, NewConsPubkey.Address().Bytes(), selfDelegation.Int64(), comet.BlockIDFlagCommit)
assert.NilError(t, err)
// double sign less than max age
valInfo, err = f.stakingKeeper.Validator(ctx, operatorAddr)
assert.NilError(t, err)
oldTokens := valInfo.GetTokens()
nci := comet.Info{
Evidence: []comet.Evidence{{
Validator: comet.Validator{Address: valpubkey.Address(), Power: power},
Type: comet.MisbehaviorType(abci.MisbehaviorType_DUPLICATE_VOTE),
Time: time.Unix(0, 0),
Height: 0,
}},
}
err = f.evidenceKeeper.BeginBlocker(ctx.WithCometInfo(nci))
assert.NilError(t, err)
// should be jailed and tombstoned
valInfo, err = f.stakingKeeper.Validator(ctx, operatorAddr)
assert.NilError(t, err)
assert.Assert(t, valInfo.IsJailed())
assert.Assert(t, f.slashingKeeper.IsTombstoned(ctx, sdk.ConsAddress(NewConsPubkey.Address())))
// tokens should be decreased
valInfo, err = f.stakingKeeper.Validator(ctx, operatorAddr)
assert.NilError(t, err)
newTokens := valInfo.GetTokens()
assert.Assert(t, newTokens.LT(oldTokens))
// submit duplicate evidence
err = f.evidenceKeeper.BeginBlocker(ctx.WithCometInfo(nci))
assert.NilError(t, err)
// tokens should be the same (capped slash)
valInfo, err = f.stakingKeeper.Validator(ctx, operatorAddr)
assert.NilError(t, err)
assert.Assert(t, valInfo.GetTokens().Equal(newTokens))
// jump to past the unbonding period
ctx = ctx.WithHeaderInfo(header.Info{Time: time.Unix(1, 0).Add(stakingParams.UnbondingTime)})
// require we cannot unjail
assert.Error(t, f.slashingKeeper.Unjail(ctx, operatorAddr), slashingtypes.ErrValidatorJailed.Error())
// require we be able to unbond now
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
del, _ := f.stakingKeeper.Delegations.Get(ctx, collections.Join(sdk.AccAddress(operatorAddr), operatorAddr))
validator, _ := f.stakingKeeper.GetValidator(ctx, operatorAddr)
totalBond := validator.TokensFromShares(del.GetShares()).TruncateInt()
tstaking.Ctx = ctx
tstaking.Denom = stakingParams.BondDenom
tstaking.Undelegate(sdk.AccAddress(operatorAddr), operatorAddr, totalBond, true)
// query evidence from store
var evidences []exported.Evidence
assert.NilError(t, f.evidenceKeeper.Evidences.Walk(ctx, nil, func(key []byte, value exported.Evidence) (stop bool, err error) {
evidences = append(evidences, value)
return false, nil
}))
// evidences, err := f.evidenceKeeper.GetAllEvidence(ctx)
assert.NilError(t, err)
assert.Assert(t, len(evidences) == 1)
}
func populateValidators(t assert.TestingT, f *fixture) {
// add accounts and set total supply
totalSupplyAmt := initAmt.MulRaw(int64(len(valAddresses)))

View File

@ -18,6 +18,7 @@ import (
bankkeeper "cosmossdk.io/x/bank/keeper"
banktypes "cosmossdk.io/x/bank/types"
minttypes "cosmossdk.io/x/mint/types"
pooltypes "cosmossdk.io/x/protocolpool/types"
"cosmossdk.io/x/staking"
stakingkeeper "cosmossdk.io/x/staking/keeper"
"cosmossdk.io/x/staking/testutil"
@ -107,6 +108,7 @@ func initFixture(tb testing.TB) *fixture {
authority := authtypes.NewModuleAddress("gov")
maccPerms := map[string][]string{
pooltypes.ModuleName: {},
minttypes.ModuleName: {authtypes.Minter},
types.ModuleName: {authtypes.Minter},
types.BondedPoolName: {authtypes.Burner, authtypes.Staking},

View File

@ -6,12 +6,15 @@ import (
"gotest.tools/v3/assert"
"cosmossdk.io/core/header"
"cosmossdk.io/math"
"cosmossdk.io/x/bank/testutil"
pooltypes "cosmossdk.io/x/protocolpool/types"
"cosmossdk.io/x/staking/keeper"
"cosmossdk.io/x/staking/types"
"github.com/cosmos/cosmos-sdk/codec/address"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -175,3 +178,217 @@ func TestCancelUnbondingDelegation(t *testing.T) {
})
}
}
func TestRotateConsPubKey(t *testing.T) {
t.Parallel()
f := initFixture(t)
ctx := f.sdkCtx
stakingKeeper := f.stakingKeeper
bankKeeper := f.bankKeeper
accountKeeper := f.accountKeeper
msgServer := keeper.NewMsgServerImpl(stakingKeeper)
bondDenom, err := stakingKeeper.BondDenom(ctx)
assert.NilError(t, err)
params, err := stakingKeeper.Params.Get(ctx)
assert.NilError(t, err)
params.KeyRotationFee = sdk.NewInt64Coin(bondDenom, 10)
err = stakingKeeper.Params.Set(ctx, params)
assert.NilError(t, err)
addrs := simtestutil.AddTestAddrsIncremental(bankKeeper, stakingKeeper, ctx, 5, stakingKeeper.TokensFromConsensusPower(ctx, 100))
valAddrs := simtestutil.ConvertAddrsToValAddrs(addrs)
// create 5 validators
for i := 0; i < 5; i++ {
comm := types.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0))
msg, err := types.NewMsgCreateValidator(valAddrs[i].String(), PKs[i], sdk.NewCoin(sdk.DefaultBondDenom, stakingKeeper.TokensFromConsensusPower(ctx, 30)),
types.Description{Moniker: "NewVal"}, comm, math.OneInt())
assert.NilError(t, err)
_, err = msgServer.CreateValidator(ctx, msg)
assert.NilError(t, err)
}
// call endblocker to update the validator state
_, err = stakingKeeper.EndBlocker(ctx.WithBlockHeight(ctx.BlockHeader().Height + 1))
assert.NilError(t, err)
params, err = stakingKeeper.Params.Get(ctx)
assert.NilError(t, err)
validators, err := stakingKeeper.GetAllValidators(ctx)
assert.NilError(t, err)
assert.Equal(t, len(validators) >= 5, true)
testCases := []struct {
name string
malleate func() sdk.Context
pass bool
validator string
newPubKey cryptotypes.PubKey
expErrMsg string
expHistoryObjs int
fees sdk.Coin
}{
{
name: "successful consensus pubkey rotation",
malleate: func() sdk.Context {
return ctx
},
validator: validators[0].GetOperator(),
newPubKey: PKs[499],
pass: true,
expHistoryObjs: 1,
fees: params.KeyRotationFee,
},
{
name: "non existing validator check",
malleate: func() sdk.Context {
return ctx
},
validator: sdk.ValAddress("non_existing_val").String(),
newPubKey: PKs[498],
pass: false,
expErrMsg: "validator does not exist",
},
{
name: "pubkey already associated with another validator",
malleate: func() sdk.Context {
return ctx
},
validator: validators[0].GetOperator(),
newPubKey: validators[1].ConsensusPubkey.GetCachedValue().(cryptotypes.PubKey),
pass: false,
expErrMsg: "consensus pubkey is already used for a validator",
},
{
name: "consensus pubkey rotation limit check",
malleate: func() sdk.Context {
params, err := stakingKeeper.Params.Get(ctx)
assert.NilError(t, err)
params.KeyRotationFee = sdk.NewInt64Coin(bondDenom, 10)
err = stakingKeeper.Params.Set(ctx, params)
assert.NilError(t, err)
msg, err := types.NewMsgRotateConsPubKey(
validators[1].GetOperator(),
PKs[498],
)
assert.NilError(t, err)
_, err = msgServer.RotateConsPubKey(ctx, msg)
assert.NilError(t, err)
return ctx
},
validator: validators[1].GetOperator(),
newPubKey: PKs[497],
pass: false,
expErrMsg: "exceeding maximum consensus pubkey rotations within unbonding period",
},
{
name: "limit reached, but should rotate after the unbonding period",
malleate: func() sdk.Context {
params, err := stakingKeeper.Params.Get(ctx)
assert.NilError(t, err)
params.KeyRotationFee = sdk.NewInt64Coin(bondDenom, 10)
err = stakingKeeper.Params.Set(ctx, params)
assert.NilError(t, err)
msg, err := types.NewMsgRotateConsPubKey(
validators[3].GetOperator(),
PKs[495],
)
assert.NilError(t, err)
_, err = msgServer.RotateConsPubKey(ctx, msg)
assert.NilError(t, err)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// this shouldn't remove the existing keys from waiting queue since unbonding time isn't reached
_, err = stakingKeeper.EndBlocker(ctx)
assert.NilError(t, err)
// stakingKeeper.UpdateAllMaturedConsKeyRotatedKeys(ctx, ctx.BlockHeader().Time)
msg, err = types.NewMsgRotateConsPubKey(
validators[3].GetOperator(),
PKs[494],
)
assert.NilError(t, err)
_, err = msgServer.RotateConsPubKey(ctx, msg)
assert.Error(t, err, "exceeding maximum consensus pubkey rotations within unbonding period")
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
newCtx := ctx.WithHeaderInfo(header.Info{Time: ctx.HeaderInfo().Time.Add(params.UnbondingTime)})
newCtx = newCtx.WithBlockHeight(newCtx.BlockHeight() + 1)
// this should remove keys from waiting queue since unbonding time is reached
_, err = stakingKeeper.EndBlocker(newCtx)
assert.NilError(t, err)
// stakingKeeper.UpdateAllMaturedConsKeyRotatedKeys(newCtx, newCtx.BlockHeader().Time)
return newCtx
},
validator: validators[3].GetOperator(),
newPubKey: PKs[494],
pass: true,
expErrMsg: "",
expHistoryObjs: 2,
fees: params.KeyRotationFee,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
newCtx := testCase.malleate()
oldDistrBalance := bankKeeper.GetBalance(newCtx, accountKeeper.GetModuleAddress(pooltypes.ModuleName), bondDenom)
msg, err := types.NewMsgRotateConsPubKey(
testCase.validator,
testCase.newPubKey,
)
assert.NilError(t, err)
_, err = msgServer.RotateConsPubKey(newCtx, msg)
if testCase.pass {
assert.NilError(t, err)
_, err = stakingKeeper.EndBlocker(newCtx)
assert.NilError(t, err)
// rotation fee payment from sender to distrtypes
newDistrBalance := bankKeeper.GetBalance(newCtx, accountKeeper.GetModuleAddress(pooltypes.ModuleName), bondDenom)
assert.DeepEqual(t, newDistrBalance, oldDistrBalance.Add(testCase.fees))
valBytes, err := stakingKeeper.ValidatorAddressCodec().StringToBytes(testCase.validator)
assert.NilError(t, err)
// validator consensus pubkey update check
validator, err := stakingKeeper.GetValidator(newCtx, valBytes)
assert.NilError(t, err)
consAddr, err := validator.GetConsAddr()
assert.NilError(t, err)
assert.DeepEqual(t, consAddr, testCase.newPubKey.Address().Bytes())
// consensus rotation history set check
historyObjects, err := stakingKeeper.GetValidatorConsPubKeyRotationHistory(newCtx, valBytes)
assert.NilError(t, err)
assert.Equal(t, len(historyObjects), testCase.expHistoryObjs)
historyObjects, err = stakingKeeper.GetBlockConsPubKeyRotationHistory(newCtx)
assert.NilError(t, err)
assert.Equal(t, len(historyObjects), 1)
} else {
assert.ErrorContains(t, err, testCase.expErrMsg)
}
})
}
}

View File

@ -146,6 +146,7 @@ func (s *SimTestSuite) TestWeightedOperations() {
{simulation.DefaultWeightMsgUndelegate, types.ModuleName, sdk.MsgTypeURL(&types.MsgUndelegate{})},
{simulation.DefaultWeightMsgBeginRedelegate, types.ModuleName, sdk.MsgTypeURL(&types.MsgBeginRedelegate{})},
{simulation.DefaultWeightMsgCancelUnbondingDelegation, types.ModuleName, sdk.MsgTypeURL(&types.MsgCancelUnbondingDelegation{})},
{simulation.DefaultWeightMsgRotateConsPubKey, types.ModuleName, sdk.MsgTypeURL(&types.MsgRotateConsPubKey{})},
}
for i, w := range weightedOps {
@ -367,6 +368,32 @@ func (s *SimTestSuite) TestSimulateMsgBeginRedelegate() {
require.Len(futureOperations, 0)
}
func (s *SimTestSuite) TestSimulateRotateConsPubKey() {
require := s.Require()
blockTime := time.Now().UTC()
ctx := s.ctx.WithHeaderInfo(header.Info{Time: blockTime})
_ = s.getTestingValidator2(ctx)
// begin a new block
_, err := s.app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: s.app.LastBlockHeight() + 1, Hash: s.app.LastCommitID().Hash, Time: blockTime})
require.NoError(err)
// execute operation
op := simulation.SimulateMsgRotateConsPubKey(s.txConfig, s.accountKeeper, s.bankKeeper, s.stakingKeeper)
operationMsg, futureOperations, err := op(s.r, s.app.BaseApp, ctx, s.accounts, "")
require.NoError(err)
var msg types.MsgRotateConsPubKey
err = proto.Unmarshal(operationMsg.Msg, &msg)
require.NoError(err)
require.True(operationMsg.OK)
require.Equal(sdk.MsgTypeURL(&types.MsgRotateConsPubKey{}), sdk.MsgTypeURL(&msg))
require.Equal("cosmosvaloper1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7epjs3u", msg.ValidatorAddress)
require.Len(futureOperations, 0)
}
func (s *SimTestSuite) getTestingValidator0(ctx sdk.Context) types.Validator {
commission0 := types.NewCommission(math.LegacyZeroDec(), math.LegacyOneDec(), math.LegacyOneDec())
return s.getTestingValidator(ctx, commission0, 1)
@ -393,6 +420,14 @@ func (s *SimTestSuite) getTestingValidator(ctx sdk.Context, commission types.Com
return validator
}
func (s *SimTestSuite) getTestingValidator2(ctx sdk.Context) types.Validator {
val := s.getTestingValidator0(ctx)
val.Status = types.Bonded
s.Require().NoError(s.stakingKeeper.SetValidator(ctx, val))
s.Require().NoError(s.stakingKeeper.SetValidatorByConsAddr(ctx, val))
return val
}
func (s *SimTestSuite) setupValidatorRewards(ctx sdk.Context, valAddress sdk.ValAddress) {
decCoins := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyOneDec())}
historicalRewards := distrtypes.NewValidatorHistoricalRewards(decCoins, 2)

View File

@ -8,6 +8,7 @@ import (
"cosmossdk.io/x/slashing/testutil"
slashingtypes "cosmossdk.io/x/slashing/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -111,3 +112,49 @@ func (s *KeeperTestSuite) TestValidatorMissedBlockBitmap_SmallWindow() {
require.Len(missedBlocks, int(params.SignedBlocksWindow)-1)
}
}
func (s *KeeperTestSuite) TestPerformConsensusPubKeyUpdate() {
ctx, slashingKeeper := s.ctx, s.slashingKeeper
require := s.Require()
pks := simtestutil.CreateTestPubKeys(500)
oldConsAddr := sdk.ConsAddress(pks[0].Address())
newConsAddr := sdk.ConsAddress(pks[1].Address())
newInfo := slashingtypes.NewValidatorSigningInfo(
oldConsAddr.String(),
int64(4),
int64(3),
time.Unix(2, 0).UTC(),
false,
int64(10),
)
err := slashingKeeper.ValidatorSigningInfo.Set(ctx, oldConsAddr, newInfo)
require.NoError(err)
s.stakingKeeper.EXPECT().ValidatorIdentifier(gomock.Any(), oldConsAddr).Return(oldConsAddr, nil)
err = slashingKeeper.SetMissedBlockBitmapValue(ctx, oldConsAddr, 10, true)
require.NoError(err)
err = slashingKeeper.Hooks().AfterConsensusPubKeyUpdate(ctx, pks[0], pks[1], sdk.Coin{})
require.NoError(err)
// check pubkey relation is set properly
savedPubKey, err := slashingKeeper.GetPubkey(ctx, newConsAddr.Bytes())
require.NoError(err)
require.Equal(savedPubKey, pks[1])
// check validator SigningInfo is set properly to new consensus pubkey
signingInfo, err := slashingKeeper.ValidatorSigningInfo.Get(ctx, newConsAddr)
require.NoError(err)
require.Equal(signingInfo, newInfo)
// missed blocks maps to old cons key only since there is a identifier added to get the missed blocks using the new cons key.
missedBlocks, err := slashingKeeper.GetValidatorMissedBlocks(ctx, oldConsAddr)
require.NoError(err)
require.Len(missedBlocks, 1)
}

View File

@ -131,8 +131,8 @@ func (k Keeper) ValidatorIdentifier(ctx context.Context, newPk sdk.ConsAddress)
return pk, nil
}
// exceedsMaxRotations returns true if the key rotations exceed the limit, currently we are limiting one rotation for unbonding period.
func (k Keeper) exceedsMaxRotations(ctx context.Context, valAddr sdk.ValAddress) error {
// ExceedsMaxRotations returns true if the key rotations exceed the limit, currently we are limiting one rotation for unbonding period.
func (k Keeper) ExceedsMaxRotations(ctx context.Context, valAddr sdk.ValAddress) error {
count := 0
rng := collections.NewPrefixedPairRange[[]byte, time.Time](valAddr)

View File

@ -655,7 +655,7 @@ func (k msgServer) RotateConsPubKey(ctx context.Context, msg *types.MsgRotateCon
// Check if the validator is exceeding parameter MaxConsPubKeyRotations within the
// unbonding period by iterating ConsPubKeyRotationHistory.
err = k.exceedsMaxRotations(ctx, valAddr)
err = k.ExceedsMaxRotations(ctx, valAddr)
if err != nil {
return nil, err
}

View File

@ -37,18 +37,29 @@ func (k Keeper) GetValidator(ctx context.Context, addr sdk.ValAddress) (validato
// GetValidatorByConsAddr gets a single validator by consensus address
func (k Keeper) GetValidatorByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (validator types.Validator, err error) {
opAddr, err := k.ValidatorByConsensusAddress.Get(ctx, consAddr)
if err != nil && !errors.Is(err, collections.ErrNotFound) {
// if the validator not found try to find it in the map of `OldToNewConsKeyMap`` because validator may've rotated it's key.
if err != nil {
// if the validator not found try to find it in the map of `OldToNewConsKeyMap` because validator may've rotated it's key.
if !errors.Is(err, collections.ErrNotFound) {
return types.Validator{}, err
}
newConsAddr, err := k.OldToNewConsKeyMap.Get(ctx, consAddr)
newConsAddr, err := k.OldToNewConsKeyMap.Get(ctx, consAddr.Bytes())
if err != nil {
if errors.Is(err, collections.ErrNotFound) {
return types.Validator{}, types.ErrNoValidatorFound
}
return types.Validator{}, err
}
opAddr = newConsAddr
operatorAddr, err := k.ValidatorByConsensusAddress.Get(ctx, newConsAddr)
if err != nil {
if errors.Is(err, collections.ErrNotFound) {
return types.Validator{}, types.ErrNoValidatorFound
}
return types.Validator{}, err
}
opAddr = operatorAddr
}
if opAddr == nil {

View File

@ -9,6 +9,7 @@ import (
"cosmossdk.io/collections"
"cosmossdk.io/core/header"
"cosmossdk.io/math"
authtypes "cosmossdk.io/x/auth/types"
stakingkeeper "cosmossdk.io/x/staking/keeper"
"cosmossdk.io/x/staking/testutil"
stakingtypes "cosmossdk.io/x/staking/types"
@ -442,3 +443,71 @@ func (s *KeeperTestSuite) TestUnbondingValidator() {
require.NoError(err)
require.Equal(stakingtypes.Unbonded, validator.Status)
}
func (s *KeeperTestSuite) TestValidatorConsPubKeyUpdate() {
ctx, keeper, msgServer, bk, ak := s.ctx, s.stakingKeeper, s.msgServer, s.bankKeeper, s.accountKeeper
require := s.Require()
powers := []int64{10, 20}
var validators [2]stakingtypes.Validator
bonedPool := authtypes.NewEmptyModuleAccount(stakingtypes.BondedPoolName)
ak.EXPECT().GetModuleAccount(gomock.Any(), stakingtypes.BondedPoolName).Return(bonedPool).AnyTimes()
bk.EXPECT().GetBalance(gomock.Any(), bonedPool.GetAddress(), sdk.DefaultBondDenom).Return(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000000)).AnyTimes()
for i, power := range powers {
valAddr := sdk.ValAddress(PKs[i].Address().Bytes())
validators[i] = testutil.NewValidator(s.T(), valAddr, PKs[i])
tokens := keeper.TokensFromConsensusPower(ctx, power)
validators[i], _ = validators[i].AddTokensFromDel(tokens)
require.NoError(keeper.SetValidator(ctx, validators[i]))
require.NoError(keeper.SetValidatorByPowerIndex(ctx, validators[i]))
require.NoError(keeper.SetValidatorByConsAddr(ctx, validators[i]))
s.bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), stakingtypes.NotBondedPoolName, stakingtypes.BondedPoolName, gomock.Any())
updates := s.applyValidatorSetUpdates(ctx, keeper, 1)
validator, err := keeper.GetValidator(ctx, valAddr)
require.NoError(err)
require.Equal(validator.ABCIValidatorUpdate(keeper.PowerReduction(ctx)), updates[0])
}
params, err := keeper.Params.Get(ctx)
require.NoError(err)
params.KeyRotationFee = sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000)
err = keeper.Params.Set(ctx, params)
require.NoError(err)
valAddr1 := sdk.ValAddress(PKs[0].Address().Bytes())
valStr, err := keeper.ValidatorAddressCodec().BytesToString(valAddr1)
require.NoError(err)
msg, err := stakingtypes.NewMsgRotateConsPubKey(
valStr,
PKs[499], // taking the last element from PKs
)
require.NoError(err)
bk.EXPECT().SendCoinsFromAccountToModule(ctx, sdk.AccAddress(valAddr1), gomock.Any(), gomock.Any()).AnyTimes()
_, err = msgServer.RotateConsPubKey(ctx, msg)
require.NoError(err)
updates := s.applyValidatorSetUpdates(ctx, keeper, 2)
originalPubKey, err := validators[0].CmtConsPublicKey()
require.NoError(err)
validator, err := keeper.GetValidator(ctx, valAddr1)
require.NoError(err)
newPubKey, err := validator.CmtConsPublicKey()
require.NoError(err)
require.Equal(int64(0), updates[0].Power)
require.Equal(originalPubKey, updates[0].PubKey)
require.Equal(int64(10), updates[1].Power)
require.Equal(newPubKey, updates[1].PubKey)
}

View File

@ -26,6 +26,7 @@ const (
DefaultWeightMsgUndelegate int = 100
DefaultWeightMsgBeginRedelegate int = 100
DefaultWeightMsgCancelUnbondingDelegation int = 100
DefaultWeightMsgRotateConsPubKey int = 100
OpWeightMsgCreateValidator = "op_weight_msg_create_validator"
OpWeightMsgEditValidator = "op_weight_msg_edit_validator"
@ -33,6 +34,7 @@ const (
OpWeightMsgUndelegate = "op_weight_msg_undelegate"
OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate"
OpWeightMsgCancelUnbondingDelegation = "op_weight_msg_cancel_unbonding_delegation"
OpWeightMsgRotateConsPubKey = "op_weight_msg_rotate_cons_pubkey"
)
// WeightedOperations returns all the operations from the module with their respective weights
@ -51,6 +53,7 @@ func WeightedOperations(
weightMsgUndelegate int
weightMsgBeginRedelegate int
weightMsgCancelUnbondingDelegation int
weightMsgRotateConsPubKey int
)
appParams.GetOrGenerate(OpWeightMsgCreateValidator, &weightMsgCreateValidator, nil, func(_ *rand.Rand) {
@ -77,6 +80,10 @@ func WeightedOperations(
weightMsgCancelUnbondingDelegation = DefaultWeightMsgCancelUnbondingDelegation
})
appParams.GetOrGenerate(OpWeightMsgRotateConsPubKey, &weightMsgRotateConsPubKey, nil, func(_ *rand.Rand) {
weightMsgRotateConsPubKey = DefaultWeightMsgRotateConsPubKey
})
return simulation.WeightedOperations{
simulation.NewWeightedOperation(
weightMsgCreateValidator,
@ -102,6 +109,10 @@ func WeightedOperations(
weightMsgCancelUnbondingDelegation,
SimulateMsgCancelUnbondingDelegate(txGen, ak, bk, k),
),
simulation.NewWeightedOperation(
weightMsgRotateConsPubKey,
SimulateMsgRotateConsPubKey(txGen, ak, bk, k),
),
}
}
@ -126,6 +137,12 @@ func SimulateMsgCreateValidator(
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator already exists"), nil, nil
}
consPubKey := sdk.GetConsAddress(simAccount.ConsKey.PubKey())
_, err = k.GetValidatorByConsAddr(ctx, consPubKey)
if err == nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "cons key already used"), nil, nil
}
denom, err := k.BondDenom(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
@ -705,3 +722,102 @@ func SimulateMsgBeginRedelegate(
return simulation.GenAndDeliverTxWithRandFees(txCtx)
}
}
func SimulateMsgRotateConsPubKey(txGen client.TxConfig, ak types.AccountKeeper, bk types.BankKeeper, k *keeper.Keeper) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
msgType := sdk.MsgTypeURL(&types.MsgRotateConsPubKey{})
vals, err := k.GetAllValidators(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err
}
if len(vals) == 0 {
return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil
}
val, ok := testutil.RandSliceElem(r, vals)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick a validator"), nil, nil
}
if val.Status != types.Bonded || val.ConsensusPower(sdk.DefaultPowerReduction) == 0 {
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator not bonded."), nil, nil
}
valAddr := val.GetOperator()
valBytes, err := k.ValidatorAddressCodec().StringToBytes(valAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
}
simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(valBytes))
if !found {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find account"), nil, fmt.Errorf("validator %s not found", val.GetOperator())
}
cons, err := val.GetConsAddr()
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get conskey"), nil, err
}
acc, _ := simtypes.RandomAcc(r, accs)
if sdk.ConsAddress(cons).String() == sdk.ConsAddress(acc.ConsKey.PubKey().Address()).String() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "new pubkey and current pubkey should be different"), nil, nil
}
account := ak.GetAccount(ctx, simAccount.Address)
if account == nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find account"), nil, nil
}
spendable := bk.SpendableCoins(ctx, account.GetAddress())
params, err := k.Params.Get(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get params"), nil, err
}
if !spendable.IsAllGTE(sdk.NewCoins(params.KeyRotationFee)) {
return simtypes.NoOpMsg(types.ModuleName, msgType, "not enough balance to pay fee"), nil, nil
}
if err := k.ExceedsMaxRotations(ctx, valBytes); err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "rotations limit reached within unbonding period"), nil, nil
}
_, err = k.GetValidatorByConsAddr(ctx, cons)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get validator"), nil, err
}
// check whether the new cons key associated with another validator
newConsAddr := sdk.ConsAddress(acc.ConsKey.PubKey().Address())
_, err = k.GetValidatorByConsAddr(ctx, newConsAddr)
if err == nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "cons key already used"), nil, nil
}
msg, err := types.NewMsgRotateConsPubKey(valAddr, acc.ConsKey.PubKey())
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to build msg"), nil, err
}
txCtx := simulation.OperationInput{
R: r,
App: app,
TxGen: txGen,
Cdc: nil,
Msg: msg,
Context: ctx,
SimAccount: simAccount,
AccountKeeper: ak,
Bankkeeper: bk,
ModuleName: types.ModuleName,
CoinsSpentInMsg: spendable,
}
return simulation.GenAndDeliverTxWithRandFees(txCtx)
}
}