874 lines
30 KiB
Go
874 lines
30 KiB
Go
package simulation
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math/rand"
|
|
|
|
"cosmossdk.io/math"
|
|
"cosmossdk.io/x/staking/keeper"
|
|
"cosmossdk.io/x/staking/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/testutil"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
|
"github.com/cosmos/cosmos-sdk/x/simulation"
|
|
)
|
|
|
|
// Simulation operation weights constants
|
|
const (
|
|
DefaultWeightMsgCreateValidator int = 100
|
|
DefaultWeightMsgEditValidator int = 5
|
|
DefaultWeightMsgDelegate int = 100
|
|
DefaultWeightMsgUndelegate int = 100
|
|
DefaultWeightMsgBeginRedelegate int = 100
|
|
DefaultWeightMsgCancelUnbondingDelegation int = 100
|
|
DefaultWeightMsgRotateConsPubKey int = 100
|
|
|
|
OpWeightMsgCreateValidator = "op_weight_msg_create_validator"
|
|
OpWeightMsgEditValidator = "op_weight_msg_edit_validator"
|
|
OpWeightMsgDelegate = "op_weight_msg_delegate"
|
|
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
|
|
func WeightedOperations(
|
|
appParams simtypes.AppParams,
|
|
cdc codec.JSONCodec,
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
) simulation.WeightedOperations {
|
|
var (
|
|
weightMsgCreateValidator int
|
|
weightMsgEditValidator int
|
|
weightMsgDelegate int
|
|
weightMsgUndelegate int
|
|
weightMsgBeginRedelegate int
|
|
weightMsgCancelUnbondingDelegation int
|
|
weightMsgRotateConsPubKey int
|
|
)
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgCreateValidator, &weightMsgCreateValidator, nil, func(_ *rand.Rand) {
|
|
weightMsgCreateValidator = DefaultWeightMsgCreateValidator
|
|
})
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgEditValidator, &weightMsgEditValidator, nil, func(_ *rand.Rand) {
|
|
weightMsgEditValidator = DefaultWeightMsgEditValidator
|
|
})
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgDelegate, &weightMsgDelegate, nil, func(_ *rand.Rand) {
|
|
weightMsgDelegate = DefaultWeightMsgDelegate
|
|
})
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgUndelegate, &weightMsgUndelegate, nil, func(_ *rand.Rand) {
|
|
weightMsgUndelegate = DefaultWeightMsgUndelegate
|
|
})
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgBeginRedelegate, &weightMsgBeginRedelegate, nil, func(_ *rand.Rand) {
|
|
weightMsgBeginRedelegate = DefaultWeightMsgBeginRedelegate
|
|
})
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgCancelUnbondingDelegation, &weightMsgCancelUnbondingDelegation, nil, func(_ *rand.Rand) {
|
|
weightMsgCancelUnbondingDelegation = DefaultWeightMsgCancelUnbondingDelegation
|
|
})
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgRotateConsPubKey, &weightMsgRotateConsPubKey, nil, func(_ *rand.Rand) {
|
|
weightMsgRotateConsPubKey = DefaultWeightMsgRotateConsPubKey
|
|
})
|
|
|
|
return simulation.WeightedOperations{
|
|
simulation.NewWeightedOperation(
|
|
weightMsgCreateValidator,
|
|
SimulateMsgCreateValidator(txGen, ak, bk, k),
|
|
),
|
|
simulation.NewWeightedOperation(
|
|
weightMsgEditValidator,
|
|
SimulateMsgEditValidator(txGen, ak, bk, k),
|
|
),
|
|
simulation.NewWeightedOperation(
|
|
weightMsgDelegate,
|
|
SimulateMsgDelegate(txGen, ak, bk, k),
|
|
),
|
|
simulation.NewWeightedOperation(
|
|
weightMsgUndelegate,
|
|
SimulateMsgUndelegate(txGen, ak, bk, k),
|
|
),
|
|
simulation.NewWeightedOperation(
|
|
weightMsgBeginRedelegate,
|
|
SimulateMsgBeginRedelegate(txGen, ak, bk, k),
|
|
),
|
|
simulation.NewWeightedOperation(
|
|
weightMsgCancelUnbondingDelegation,
|
|
SimulateMsgCancelUnbondingDelegate(txGen, ak, bk, k),
|
|
),
|
|
simulation.NewWeightedOperation(
|
|
weightMsgRotateConsPubKey,
|
|
SimulateMsgRotateConsPubKey(txGen, ak, bk, k),
|
|
),
|
|
}
|
|
}
|
|
|
|
// SimulateMsgCreateValidator generates a MsgCreateValidator with random values
|
|
func SimulateMsgCreateValidator(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
msgType := sdk.MsgTypeURL(&types.MsgCreateValidator{})
|
|
|
|
simAccount, _ := simtypes.RandomAcc(r, accs)
|
|
address := sdk.ValAddress(simAccount.Address)
|
|
|
|
// ensure the validator doesn't exist already
|
|
_, err := k.GetValidator(ctx, address)
|
|
if err == nil {
|
|
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
|
|
}
|
|
|
|
balance := bk.GetBalance(ctx, simAccount.Address, denom).Amount
|
|
if !balance.IsPositive() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "balance is negative"), nil, nil
|
|
}
|
|
|
|
amount, err := simtypes.RandPositiveInt(r, balance)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err
|
|
}
|
|
|
|
selfDelegation := sdk.NewCoin(denom, amount)
|
|
|
|
account := ak.GetAccount(ctx, simAccount.Address)
|
|
spendable := bk.SpendableCoins(ctx, account.GetAddress())
|
|
|
|
var fees sdk.Coins
|
|
|
|
coins, hasNeg := spendable.SafeSub(selfDelegation)
|
|
if !hasNeg {
|
|
fees, err = simtypes.RandomFees(r, coins)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate fees"), nil, err
|
|
}
|
|
}
|
|
|
|
description := types.NewDescription(
|
|
simtypes.RandStringOfLength(r, 10),
|
|
simtypes.RandStringOfLength(r, 10),
|
|
simtypes.RandStringOfLength(r, 10),
|
|
simtypes.RandStringOfLength(r, 10),
|
|
simtypes.RandStringOfLength(r, 10),
|
|
)
|
|
|
|
maxCommission := math.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 100)), 2)
|
|
commission := types.NewCommissionRates(
|
|
simtypes.RandomDecAmount(r, maxCommission),
|
|
maxCommission,
|
|
simtypes.RandomDecAmount(r, maxCommission),
|
|
)
|
|
|
|
addr, err := k.ValidatorAddressCodec().BytesToString(address)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate validator address"), nil, err
|
|
}
|
|
|
|
msg, err := types.NewMsgCreateValidator(addr, simAccount.ConsKey.PubKey(), selfDelegation, description, commission, math.OneInt())
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to create CreateValidator message"), nil, err
|
|
}
|
|
|
|
// check if there's another key rotation for this same key in the same block
|
|
allRotations, err := k.GetBlockConsPubKeyRotationHistory(ctx)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get block cons key rotation history"), nil, err
|
|
}
|
|
for _, r := range allRotations {
|
|
if r.NewConsPubkey.Compare(msg.Pubkey) == 0 {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "cons key already used in this block"), nil, nil
|
|
}
|
|
}
|
|
|
|
txCtx := simulation.OperationInput{
|
|
R: r,
|
|
App: app,
|
|
TxGen: txGen,
|
|
Cdc: nil,
|
|
Msg: msg,
|
|
Context: ctx,
|
|
SimAccount: simAccount,
|
|
AccountKeeper: ak,
|
|
ModuleName: types.ModuleName,
|
|
}
|
|
|
|
return simulation.GenAndDeliverTx(txCtx, fees)
|
|
}
|
|
}
|
|
|
|
// SimulateMsgEditValidator generates a MsgEditValidator with random values
|
|
func SimulateMsgEditValidator(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
msgType := sdk.MsgTypeURL(&types.MsgEditValidator{})
|
|
|
|
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
|
|
}
|
|
|
|
address := val.GetOperator()
|
|
newCommissionRate := simtypes.RandomDecAmount(r, val.Commission.MaxRate)
|
|
|
|
if err := val.Commission.ValidateNewRate(newCommissionRate, ctx.HeaderInfo().Time); err != nil {
|
|
// skip as the commission is invalid
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid commission rate"), nil, nil
|
|
}
|
|
|
|
bz, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator())
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
|
|
}
|
|
|
|
simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(bz))
|
|
if !found {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find account"), nil, fmt.Errorf("validator %s not found", val.GetOperator())
|
|
}
|
|
|
|
account := ak.GetAccount(ctx, simAccount.Address)
|
|
spendable := bk.SpendableCoins(ctx, account.GetAddress())
|
|
|
|
description := types.NewDescription(
|
|
simtypes.RandStringOfLength(r, 10),
|
|
simtypes.RandStringOfLength(r, 10),
|
|
simtypes.RandStringOfLength(r, 10),
|
|
simtypes.RandStringOfLength(r, 10),
|
|
simtypes.RandStringOfLength(r, 10),
|
|
)
|
|
|
|
msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil)
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// SimulateMsgDelegate generates a MsgDelegate with random values
|
|
func SimulateMsgDelegate(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
msgType := sdk.MsgTypeURL(&types.MsgDelegate{})
|
|
denom, err := k.BondDenom(ctx)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
simAccount, _ := simtypes.RandomAcc(r, accs)
|
|
val, ok := testutil.RandSliceElem(r, vals)
|
|
if !ok {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick a validator"), nil, nil
|
|
}
|
|
|
|
if val.InvalidExRate() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator's invalid echange rate"), nil, nil
|
|
}
|
|
|
|
amount := bk.GetBalance(ctx, simAccount.Address, denom).Amount
|
|
if !amount.IsPositive() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "balance is negative"), nil, nil
|
|
}
|
|
|
|
amount, err = simtypes.RandPositiveInt(r, amount)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err
|
|
}
|
|
|
|
bondAmt := sdk.NewCoin(denom, amount)
|
|
|
|
account := ak.GetAccount(ctx, simAccount.Address)
|
|
spendable := bk.SpendableCoins(ctx, account.GetAddress())
|
|
|
|
var fees sdk.Coins
|
|
|
|
coins, hasNeg := spendable.SafeSub(bondAmt)
|
|
if !hasNeg {
|
|
fees, err = simtypes.RandomFees(r, coins)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate fees"), nil, err
|
|
}
|
|
}
|
|
|
|
accAddr, err := ak.AddressCodec().BytesToString(simAccount.Address)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting account string address"), nil, err
|
|
}
|
|
msg := types.NewMsgDelegate(accAddr, val.GetOperator(), bondAmt)
|
|
|
|
txCtx := simulation.OperationInput{
|
|
R: r,
|
|
App: app,
|
|
TxGen: txGen,
|
|
Cdc: nil,
|
|
Msg: msg,
|
|
Context: ctx,
|
|
SimAccount: simAccount,
|
|
AccountKeeper: ak,
|
|
ModuleName: types.ModuleName,
|
|
}
|
|
|
|
return simulation.GenAndDeliverTx(txCtx, fees)
|
|
}
|
|
}
|
|
|
|
// SimulateMsgUndelegate generates a MsgUndelegate with random values
|
|
func SimulateMsgUndelegate(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
msgType := sdk.MsgTypeURL(&types.MsgUndelegate{})
|
|
|
|
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, "validator is not ok"), nil, nil
|
|
}
|
|
|
|
valAddr, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator())
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
|
|
}
|
|
delegations, err := k.GetValidatorDelegations(ctx, valAddr)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator delegations"), nil, nil
|
|
}
|
|
|
|
if delegations == nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have any delegation entries"), nil, nil
|
|
}
|
|
|
|
// get random delegator from validator
|
|
delegation := delegations[r.Intn(len(delegations))]
|
|
delAddr := delegation.GetDelegatorAddr()
|
|
|
|
delAddrBz, err := ak.AddressCodec().StringToBytes(delAddr)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting delegator address bytes"), nil, err
|
|
}
|
|
|
|
hasMaxUD, err := k.HasMaxUnbondingDelegationEntries(ctx, delAddrBz, valAddr)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting max unbonding delegation entries"), nil, err
|
|
}
|
|
|
|
if hasMaxUD {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have a max unbonding delegation entries"), nil, nil
|
|
}
|
|
|
|
totalBond := val.TokensFromShares(delegation.GetShares()).TruncateInt()
|
|
if !totalBond.IsPositive() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "total bond is negative"), nil, nil
|
|
}
|
|
|
|
unbondAmt, err := simtypes.RandPositiveInt(r, totalBond)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid unbond amount"), nil, err
|
|
}
|
|
|
|
if unbondAmt.IsZero() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unbond amount is zero"), nil, nil
|
|
}
|
|
|
|
bondDenom, err := k.BondDenom(ctx)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
|
|
}
|
|
|
|
msg := types.NewMsgUndelegate(
|
|
delAddr, val.GetOperator(), sdk.NewCoin(bondDenom, unbondAmt),
|
|
)
|
|
|
|
if !bk.IsSendEnabledDenom(ctx, bondDenom) {
|
|
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "bond denom send not enabled"), nil, nil
|
|
}
|
|
|
|
// need to retrieve the simulation account associated with delegation to retrieve PrivKey
|
|
var simAccount simtypes.Account
|
|
|
|
for _, simAcc := range accs {
|
|
if simAcc.Address.Equals(sdk.AccAddress(delAddrBz)) {
|
|
simAccount = simAcc
|
|
break
|
|
}
|
|
}
|
|
// if simaccount.PrivKey == nil, delegation address does not exist in accs. However, since smart contracts and module accounts can stake, we can ignore the error
|
|
if simAccount.PrivKey == nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "account private key is nil"), nil, nil
|
|
}
|
|
|
|
account := ak.GetAccount(ctx, delAddrBz)
|
|
spendable := bk.SpendableCoins(ctx, account.GetAddress())
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// SimulateMsgCancelUnbondingDelegate generates a MsgCancelUnbondingDelegate with random values
|
|
func SimulateMsgCancelUnbondingDelegate(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
msgType := sdk.MsgTypeURL(&types.MsgCancelUnbondingDelegation{})
|
|
|
|
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
|
|
}
|
|
|
|
simAccount, _ := simtypes.RandomAcc(r, accs)
|
|
val, ok := testutil.RandSliceElem(r, vals)
|
|
if !ok {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is not ok"), nil, nil
|
|
}
|
|
|
|
if val.IsJailed() || val.InvalidExRate() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is jailed"), nil, nil
|
|
}
|
|
|
|
valAddr, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator())
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
|
|
}
|
|
unbondingDelegation, err := k.GetUnbondingDelegation(ctx, simAccount.Address, valAddr)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "account does have any unbonding delegation"), nil, nil
|
|
}
|
|
|
|
// This is a temporary fix to make staking simulation pass. We should fetch
|
|
// the first unbondingDelegationEntry that matches the creationHeight, because
|
|
// currently the staking msgServer chooses the first unbondingDelegationEntry
|
|
// with the matching creationHeight.
|
|
//
|
|
// ref: https://github.com/cosmos/cosmos-sdk/issues/12932
|
|
creationHeight := unbondingDelegation.Entries[r.Intn(len(unbondingDelegation.Entries))].CreationHeight
|
|
|
|
var unbondingDelegationEntry types.UnbondingDelegationEntry
|
|
|
|
for _, entry := range unbondingDelegation.Entries {
|
|
if entry.CreationHeight == creationHeight {
|
|
unbondingDelegationEntry = entry
|
|
break
|
|
}
|
|
}
|
|
|
|
if unbondingDelegationEntry.CompletionTime.Before(ctx.HeaderInfo().Time) {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unbonding delegation is already processed"), nil, nil
|
|
}
|
|
|
|
if !unbondingDelegationEntry.Balance.IsPositive() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "delegator receiving balance is negative"), nil, nil
|
|
}
|
|
|
|
cancelBondAmt := simtypes.RandomAmount(r, unbondingDelegationEntry.Balance)
|
|
|
|
if cancelBondAmt.IsZero() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "cancelBondAmt amount is zero"), nil, nil
|
|
}
|
|
|
|
bondDenom, err := k.BondDenom(ctx)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
|
|
}
|
|
|
|
accAddr, err := ak.AddressCodec().BytesToString(simAccount.Address)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting account string address"), nil, err
|
|
}
|
|
msg := types.NewMsgCancelUnbondingDelegation(
|
|
accAddr, val.GetOperator(), unbondingDelegationEntry.CreationHeight, sdk.NewCoin(bondDenom, cancelBondAmt),
|
|
)
|
|
|
|
spendable := bk.SpendableCoins(ctx, simAccount.Address)
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values
|
|
func SimulateMsgBeginRedelegate(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
msgType := sdk.MsgTypeURL(&types.MsgBeginRedelegate{})
|
|
|
|
allVals, err := k.GetAllValidators(ctx)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err
|
|
}
|
|
|
|
if len(allVals) == 0 {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil
|
|
}
|
|
|
|
srcVal, ok := testutil.RandSliceElem(r, allVals)
|
|
if !ok {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick validator"), nil, nil
|
|
}
|
|
|
|
srcAddr, err := k.ValidatorAddressCodec().StringToBytes(srcVal.GetOperator())
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
|
|
}
|
|
delegations, err := k.GetValidatorDelegations(ctx, srcAddr)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator delegations"), nil, nil
|
|
}
|
|
|
|
if delegations == nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have any delegation entries"), nil, nil
|
|
}
|
|
|
|
// get random delegator from src validator
|
|
delegation := delegations[r.Intn(len(delegations))]
|
|
delAddr := delegation.GetDelegatorAddr()
|
|
|
|
delAddrBz, err := ak.AddressCodec().StringToBytes(delAddr)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting delegator address bytes"), nil, err
|
|
}
|
|
|
|
hasRecRedel, err := k.HasReceivingRedelegation(ctx, delAddrBz, srcAddr)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting receiving redelegation"), nil, err
|
|
}
|
|
|
|
if hasRecRedel {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "receveing redelegation is not allowed"), nil, nil // skip
|
|
}
|
|
|
|
// get random destination validator
|
|
destVal, ok := testutil.RandSliceElem(r, allVals)
|
|
if !ok {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick validator"), nil, nil
|
|
}
|
|
|
|
destAddr, err := k.ValidatorAddressCodec().StringToBytes(destVal.GetOperator())
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
|
|
}
|
|
hasMaxRedel, err := k.HasMaxRedelegationEntries(ctx, delAddrBz, srcAddr, destAddr)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting max redelegation entries"), nil, err
|
|
}
|
|
|
|
if bytes.Equal(srcAddr, destAddr) || destVal.InvalidExRate() || hasMaxRedel {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "checks failed"), nil, nil
|
|
}
|
|
|
|
totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt()
|
|
if !totalBond.IsPositive() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "total bond is negative"), nil, nil
|
|
}
|
|
|
|
redAmt, err := simtypes.RandPositiveInt(r, totalBond)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err
|
|
}
|
|
|
|
if redAmt.IsZero() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "amount is zero"), nil, nil
|
|
}
|
|
|
|
// check if the shares truncate to zero
|
|
shares, err := srcVal.SharesFromTokens(redAmt)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid shares"), nil, err
|
|
}
|
|
|
|
if srcVal.TokensFromShares(shares).TruncateInt().IsZero() {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "shares truncate to zero"), nil, nil // skip
|
|
}
|
|
|
|
// need to retrieve the simulation account associated with delegation to retrieve PrivKey
|
|
var simAccount simtypes.Account
|
|
|
|
for _, simAcc := range accs {
|
|
if simAcc.Address.Equals(sdk.AccAddress(delAddrBz)) {
|
|
simAccount = simAcc
|
|
break
|
|
}
|
|
}
|
|
|
|
// if simaccount.PrivKey == nil, delegation address does not exist in accs. However, since smart contracts and module accounts can stake, we can ignore the error
|
|
if simAccount.PrivKey == nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "account private key is nil"), nil, nil
|
|
}
|
|
|
|
account := ak.GetAccount(ctx, delAddrBz)
|
|
spendable := bk.SpendableCoins(ctx, account.GetAddress())
|
|
|
|
bondDenom, err := k.BondDenom(ctx)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
|
|
}
|
|
|
|
if !bk.IsSendEnabledDenom(ctx, bondDenom) {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom send not enabled"), nil, nil
|
|
}
|
|
|
|
msg := types.NewMsgBeginRedelegate(
|
|
delAddr, srcVal.GetOperator(), destVal.GetOperator(),
|
|
sdk.NewCoin(bondDenom, redAmt),
|
|
)
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
func SimulateMsgRotateConsPubKey(txGen client.TxConfig, ak types.AccountKeeper, bk types.BankKeeper, k *keeper.Keeper) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, 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
|
|
}
|
|
consAddress, err := k.ConsensusAddressCodec().BytesToString(cons)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting consensus address"), nil, err
|
|
}
|
|
|
|
acc, _ := simtypes.RandomAcc(r, accs)
|
|
accAddress, err := k.ConsensusAddressCodec().BytesToString(acc.ConsKey.PubKey().Address())
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting consensus address"), nil, err
|
|
}
|
|
if consAddress == accAddress {
|
|
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
|
|
}
|
|
|
|
// check if there's another key rotation for this same key in the same block
|
|
allRotations, err := k.GetBlockConsPubKeyRotationHistory(ctx)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get block cons key rotation history"), nil, err
|
|
}
|
|
for _, r := range allRotations {
|
|
if r.NewConsPubkey.Compare(msg.NewPubkey) == 0 {
|
|
return simtypes.NoOpMsg(types.ModuleName, msgType, "cons key already used in this block"), nil, nil
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|