cosmos-sdk/x/staking/simulation/operations.go

406 lines
12 KiB
Go
Raw Normal View History

update simulation operations to use BaseApp (#4946) * update operations to use baseapp * updates and cleanup operations * update operations * restructure sim ops params * rename sim /operations/msg.go to /operations.go * move GenTx to a helper pkg to avoid circle deps * rm msg.ValidateBasic * changelog * random fees; delete auth's DeductFees sim operation * add chain-id for sig verification * Update x/simulation/account.go Co-Authored-By: colin axner <colinaxner@berkeley.edu> * fix bank, gov and distr errors * fix staking and slashing errors; increase prob for send enabled * increase gas x10 * make format * fix some distr and staking edge cases * fix all edge cases * golang ci * rename acc vars; default no fees to 0stake * cleanup; check for exchange rate and skip invalid ops * fixes * check for max entries * add pubkey to genaccounts * fix gov bug * update staking sim ops * fix small redelegation error * fix small self delegation on unjail * rm inf loop on random val/accs * copy array * add ok boolean to RandomValidator return values * format * Update x/bank/simulation/operations.go Co-Authored-By: colin axner <colinaxner@berkeley.edu> * Update simapp/helpers/test_helpers.go Co-Authored-By: colin axner <colinaxner@berkeley.edu> * address @colin-axner comments * add genaccount pubkey validation * fix test * update operations and move RandomFees to x/simulation * update gov ops * address @alexanderbez comments * avoid modifications to config * reorder params * changelog * Update x/distribution/simulation/genesis.go Co-Authored-By: Alexander Bezobchuk <alexanderbez@users.noreply.github.com> * remove named return values * ensure all operations are simulated * golangci * add nolint * disable whitespace and funlen linter * disable godox * add TODO on unjail * update ops weights * remove dup * update godoc * x/slashing/simulation/operations.go linting * x/staking/simulation/operations.go linting * update operations format * x/bank/simulation/operations.go linting * x/distribution/simulation/operations.go linting * x/staking/simulation/operations.go linting * start changes: make bank simulate send multiple coins, code cleanup * fix nondeterminism bug * fix txsiglimit err * fix multisend bug * simplify simulation, cleanup opt privkey args * make slashing test invalid unjail msgs * Update simapp/state.go * golangCI changes
2019-10-23 02:14:45 -07:00
package simulation
import (
"errors"
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
// SimulateMsgCreateValidator generates a MsgCreateValidator with random values
// nolint: funlen
func SimulateMsgCreateValidator(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
simAccount, _ := simulation.RandomAcc(r, accs)
address := sdk.ValAddress(simAccount.Address)
// ensure the validator doesn't exist already
_, found := k.GetValidator(ctx, address)
if found {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
denom := k.GetParams(ctx).BondDenom
amount := ak.GetAccount(ctx, simAccount.Address).GetCoins().AmountOf(denom)
if !amount.IsPositive() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
amount, err := simulation.RandPositiveInt(r, amount)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
selfDelegation := sdk.NewCoin(denom, amount)
account := ak.GetAccount(ctx, simAccount.Address)
coins := account.SpendableCoins(ctx.BlockTime())
var fees sdk.Coins
coins, hasNeg := coins.SafeSub(sdk.Coins{selfDelegation})
if !hasNeg {
fees, err = simulation.RandomFees(r, ctx, coins)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
}
description := types.NewDescription(
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
)
maxCommission := sdk.NewDecWithPrec(int64(simulation.RandIntBetween(r, 0, 100)), 2)
commission := types.NewCommissionRates(
simulation.RandomDecAmount(r, maxCommission),
maxCommission,
simulation.RandomDecAmount(r, maxCommission),
)
msg := types.NewMsgCreateValidator(address, simAccount.PubKey,
selfDelegation, description, commission, sdk.OneInt())
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgEditValidator generates a MsgEditValidator with random values
// nolint: funlen
func SimulateMsgEditValidator(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
if len(k.GetAllValidators(ctx)) == 0 {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
val, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
address := val.GetOperator()
newCommissionRate := simulation.RandomDecAmount(r, val.Commission.MaxRate)
if err := val.Commission.ValidateNewRate(newCommissionRate, ctx.BlockHeader().Time); err != nil {
// skip as the commission is invalid
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
simAccount, found := simulation.FindAccount(accs, sdk.AccAddress(val.GetOperator()))
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("validator %s not found", val.GetOperator())
}
account := ak.GetAccount(ctx, simAccount.Address)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
description := types.NewDescription(
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
)
msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil)
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgDelegate generates a MsgDelegate with random values
// nolint: funlen
func SimulateMsgDelegate(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
denom := k.GetParams(ctx).BondDenom
if len(k.GetAllValidators(ctx)) == 0 {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
simAccount, _ := simulation.RandomAcc(r, accs)
val, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
if val.InvalidExRate() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
amount := ak.GetAccount(ctx, simAccount.Address).GetCoins().AmountOf(denom)
if !amount.IsPositive() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
amount, err := simulation.RandPositiveInt(r, amount)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
bondAmt := sdk.NewCoin(denom, amount)
account := ak.GetAccount(ctx, simAccount.Address)
coins := account.SpendableCoins(ctx.BlockTime())
var fees sdk.Coins
coins, hasNeg := coins.SafeSub(sdk.Coins{bondAmt})
if !hasNeg {
fees, err = simulation.RandomFees(r, ctx, coins)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
}
msg := types.NewMsgDelegate(simAccount.Address, val.GetOperator(), bondAmt)
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgUndelegate generates a MsgUndelegate with random values
// nolint: funlen
func SimulateMsgUndelegate(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
// get random validator
validator, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
valAddr := validator.GetOperator()
delegations := k.GetValidatorDelegations(ctx, validator.OperatorAddress)
// get random delegator from validator
delegation := delegations[r.Intn(len(delegations))]
delAddr := delegation.GetDelegatorAddr()
if k.HasMaxUnbondingDelegationEntries(ctx, delAddr, valAddr) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
totalBond := validator.TokensFromShares(delegation.GetShares()).TruncateInt()
if !totalBond.IsPositive() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
unbondAmt, err := simulation.RandPositiveInt(r, totalBond)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
if unbondAmt.IsZero() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
msg := types.NewMsgUndelegate(
delAddr, valAddr, sdk.NewCoin(k.BondDenom(ctx), unbondAmt),
)
// need to retrieve the simulation account associated with delegation to retrieve PrivKey
var simAccount simulation.Account
for _, simAcc := range accs {
if simAcc.Address.Equals(delAddr) {
simAccount = simAcc
break
}
}
// if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error
if simAccount.PrivKey == nil {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("Delegation addr: %s does not exist in simulation accounts", delAddr)
}
account := ak.GetAccount(ctx, delAddr)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values
// nolint: funlen
func SimulateMsgBeginRedelegate(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
// get random source validator
srcVal, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
srcAddr := srcVal.GetOperator()
delegations := k.GetValidatorDelegations(ctx, srcAddr)
// get random delegator from src validator
delegation := delegations[r.Intn(len(delegations))]
delAddr := delegation.GetDelegatorAddr()
if k.HasReceivingRedelegation(ctx, delAddr, srcAddr) {
return simulation.NoOpMsg(types.ModuleName), nil, nil // skip
}
// get random destination validator
destVal, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
destAddr := destVal.GetOperator()
if srcAddr.Equals(destAddr) ||
destVal.InvalidExRate() ||
k.HasMaxRedelegationEntries(ctx, delAddr, srcAddr, destAddr) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt()
if !totalBond.IsPositive() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
redAmt, err := simulation.RandPositiveInt(r, totalBond)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
if redAmt.IsZero() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
// check if the shares truncate to zero
shares, err := srcVal.SharesFromTokens(redAmt)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
if srcVal.TokensFromShares(shares).TruncateInt().IsZero() {
return simulation.NoOpMsg(types.ModuleName), nil, nil // skip
}
// need to retrieve the simulation account associated with delegation to retrieve PrivKey
var simAccount simulation.Account
for _, simAcc := range accs {
if simAcc.Address.Equals(delAddr) {
simAccount = simAcc
break
}
}
// if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error
if simAccount.PrivKey == nil {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("Delegation addr: %s does not exist in simulation accounts", delAddr)
}
// get tx fees
account := ak.GetAccount(ctx, delAddr)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
msg := types.NewMsgBeginRedelegate(
delAddr, srcAddr, destAddr,
sdk.NewCoin(k.BondDenom(ctx), redAmt),
)
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}