test: add tests for cons pubkey rotation (#18965)
Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
This commit is contained in:
parent
6a2329fc93
commit
b16aa35d14
@ -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) {
|
||||
|
||||
@ -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)))
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user