Merge PR #3819: Simulation Refactor
This commit is contained in:
parent
48b6b3884f
commit
f0d1efa43c
|
@ -0,0 +1,7 @@
|
|||
\#3819 Simulation refactor, log output now stored in ~/.gaiad/simulation/
|
||||
* Simulation moved to its own module (not a part of mock)
|
||||
* Logger type instead of passing function variables everywhere
|
||||
* Logger json output (for reloadable simulation running)
|
||||
* Cleanup bank simulation messages / remove dup code in bank simulation
|
||||
* Simulations saved in `~/.gaiad/simulations/`
|
||||
* "Lean" simulation output option to exclude No-ops and !ok functions (`--SimulationLean` flag)
|
|
@ -29,7 +29,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/mint"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
|
@ -43,19 +43,30 @@ var (
|
|||
blockSize int
|
||||
enabled bool
|
||||
verbose bool
|
||||
lean bool
|
||||
commit bool
|
||||
period int
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&genesisFile, "SimulationGenesis", "", "Custom simulation genesis file")
|
||||
flag.Int64Var(&seed, "SimulationSeed", 42, "Simulation random seed")
|
||||
flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "Number of blocks")
|
||||
flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block")
|
||||
flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation")
|
||||
flag.BoolVar(&verbose, "SimulationVerbose", false, "Verbose log output")
|
||||
flag.BoolVar(&commit, "SimulationCommit", false, "Have the simulation commit")
|
||||
flag.IntVar(&period, "SimulationPeriod", 1, "Run slow invariants only once every period assertions")
|
||||
flag.StringVar(&genesisFile, "SimulationGenesis", "", "custom simulation genesis file")
|
||||
flag.Int64Var(&seed, "SimulationSeed", 42, "simulation random seed")
|
||||
flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "number of blocks")
|
||||
flag.IntVar(&blockSize, "SimulationBlockSize", 200, "operations per block")
|
||||
flag.BoolVar(&enabled, "SimulationEnabled", false, "enable the simulation")
|
||||
flag.BoolVar(&verbose, "SimulationVerbose", false, "verbose log output")
|
||||
flag.BoolVar(&lean, "SimulationLean", false, "lean simulation log output")
|
||||
flag.BoolVar(&commit, "SimulationCommit", false, "have the simulation commit")
|
||||
flag.IntVar(&period, "SimulationPeriod", 1, "run slow invariants only once every period assertions")
|
||||
}
|
||||
|
||||
// helper function for populating input for SimulateFromSeed
|
||||
func getSimulateFromSeedInput(tb testing.TB, app *GaiaApp) (
|
||||
testing.TB, *baseapp.BaseApp, simulation.AppStateFn, int64,
|
||||
simulation.WeightedOperations, sdk.Invariants, int, int, bool, bool) {
|
||||
|
||||
return tb, app.BaseApp, appStateFn, seed,
|
||||
testAndRunTxs(app), invariants(app), numBlocks, blockSize, commit, lean
|
||||
}
|
||||
|
||||
func appStateFromGenesisFileFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) {
|
||||
|
@ -265,8 +276,8 @@ func randIntBetween(r *rand.Rand, min, max int) int {
|
|||
func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation {
|
||||
return []simulation.WeightedOperation{
|
||||
{5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)},
|
||||
{100, banksim.SendMsg(app.accountKeeper, app.bankKeeper)},
|
||||
{10, banksim.SingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper)},
|
||||
{100, banksim.SimulateMsgSend(app.accountKeeper, app.bankKeeper)},
|
||||
{10, banksim.SimulateSingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper)},
|
||||
{50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)},
|
||||
{50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)},
|
||||
{50, distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper)},
|
||||
|
@ -314,14 +325,7 @@ func BenchmarkFullGaiaSimulation(b *testing.B) {
|
|||
|
||||
// Run randomized simulation
|
||||
// TODO parameterize numbers, save for a later PR
|
||||
_, err := simulation.SimulateFromSeed(
|
||||
b, app.BaseApp, appStateFn, seed,
|
||||
testAndRunTxs(app),
|
||||
invariants(app), // these shouldn't get ran
|
||||
numBlocks,
|
||||
blockSize,
|
||||
commit,
|
||||
)
|
||||
_, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(b, app))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
b.Fail()
|
||||
|
@ -356,14 +360,7 @@ func TestFullGaiaSimulation(t *testing.T) {
|
|||
require.Equal(t, "GaiaApp", app.Name())
|
||||
|
||||
// Run randomized simulation
|
||||
_, err := simulation.SimulateFromSeed(
|
||||
t, app.BaseApp, appStateFn, seed,
|
||||
testAndRunTxs(app),
|
||||
invariants(app),
|
||||
numBlocks,
|
||||
blockSize,
|
||||
commit,
|
||||
)
|
||||
_, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, app))
|
||||
if commit {
|
||||
// for memdb:
|
||||
// fmt.Println("Database Size", db.Stats()["database.size"])
|
||||
|
@ -397,14 +394,8 @@ func TestGaiaImportExport(t *testing.T) {
|
|||
require.Equal(t, "GaiaApp", app.Name())
|
||||
|
||||
// Run randomized simulation
|
||||
_, err := simulation.SimulateFromSeed(
|
||||
t, app.BaseApp, appStateFn, seed,
|
||||
testAndRunTxs(app),
|
||||
invariants(app),
|
||||
numBlocks,
|
||||
blockSize,
|
||||
commit,
|
||||
)
|
||||
_, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, app))
|
||||
|
||||
if commit {
|
||||
// for memdb:
|
||||
// fmt.Println("Database Size", db.Stats()["database.size"])
|
||||
|
@ -446,7 +437,8 @@ func TestGaiaImportExport(t *testing.T) {
|
|||
storeKeysPrefixes := []StoreKeysPrefixes{
|
||||
{app.keyMain, newApp.keyMain, [][]byte{}},
|
||||
{app.keyAccount, newApp.keyAccount, [][]byte{}},
|
||||
{app.keyStaking, newApp.keyStaking, [][]byte{staking.UnbondingQueueKey, staking.RedelegationQueueKey, staking.ValidatorQueueKey}}, // ordering may change but it doesn't matter
|
||||
{app.keyStaking, newApp.keyStaking, [][]byte{staking.UnbondingQueueKey,
|
||||
staking.RedelegationQueueKey, staking.ValidatorQueueKey}}, // ordering may change but it doesn't matter
|
||||
{app.keySlashing, newApp.keySlashing, [][]byte{}},
|
||||
{app.keyMint, newApp.keyMint, [][]byte{}},
|
||||
{app.keyDistr, newApp.keyDistr, [][]byte{}},
|
||||
|
@ -492,14 +484,8 @@ func TestGaiaSimulationAfterImport(t *testing.T) {
|
|||
require.Equal(t, "GaiaApp", app.Name())
|
||||
|
||||
// Run randomized simulation
|
||||
stopEarly, err := simulation.SimulateFromSeed(
|
||||
t, app.BaseApp, appStateFn, seed,
|
||||
testAndRunTxs(app),
|
||||
invariants(app),
|
||||
numBlocks,
|
||||
blockSize,
|
||||
commit,
|
||||
)
|
||||
stopEarly, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, app))
|
||||
|
||||
if commit {
|
||||
// for memdb:
|
||||
// fmt.Println("Database Size", db.Stats()["database.size"])
|
||||
|
@ -537,14 +523,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) {
|
|||
})
|
||||
|
||||
// Run randomized simulation on imported app
|
||||
_, err = simulation.SimulateFromSeed(
|
||||
t, newApp.BaseApp, appStateFn, seed,
|
||||
testAndRunTxs(newApp),
|
||||
invariants(newApp),
|
||||
numBlocks,
|
||||
blockSize,
|
||||
commit,
|
||||
)
|
||||
_, err = simulation.SimulateFromSeed(getSimulateFromSeedInput(t, newApp))
|
||||
require.Nil(t, err)
|
||||
|
||||
}
|
||||
|
@ -575,6 +554,7 @@ func TestAppStateDeterminism(t *testing.T) {
|
|||
50,
|
||||
100,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
appHash := app.LastCommitID().Hash
|
||||
appHashList[j] = appHash
|
||||
|
|
|
@ -2,29 +2,28 @@ package simulation
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
)
|
||||
|
||||
// SimulateDeductFee
|
||||
func SimulateDeductFee(m auth.AccountKeeper, f auth.FeeCollectionKeeper) simulation.Operation {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (
|
||||
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
account := simulation.RandomAcc(r, accs)
|
||||
stored := m.GetAccount(ctx, account.Address)
|
||||
initCoins := stored.GetCoins()
|
||||
opMsg = simulation.NewOperationMsgBasic("auth", "deduct_fee", "", false, nil)
|
||||
|
||||
if len(initCoins) == 0 {
|
||||
event(fmt.Sprintf("auth/SimulateDeductFee/false"))
|
||||
return action, nil, nil
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
|
||||
denomIndex := r.Intn(len(initCoins))
|
||||
|
@ -32,8 +31,7 @@ func SimulateDeductFee(m auth.AccountKeeper, f auth.FeeCollectionKeeper) simulat
|
|||
|
||||
amt, err := randPositiveInt(r, randCoin.Amount)
|
||||
if err != nil {
|
||||
event(fmt.Sprintf("auth/SimulateDeductFee/false"))
|
||||
return action, nil, nil
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
|
||||
// Create a random fee and verify the fees are within the account's spendable
|
||||
|
@ -41,15 +39,13 @@ func SimulateDeductFee(m auth.AccountKeeper, f auth.FeeCollectionKeeper) simulat
|
|||
fees := sdk.Coins{sdk.NewCoin(randCoin.Denom, amt)}
|
||||
spendableCoins := stored.SpendableCoins(ctx.BlockHeader().Time)
|
||||
if _, hasNeg := spendableCoins.SafeSub(fees); hasNeg {
|
||||
event(fmt.Sprintf("auth/SimulateDeductFee/false"))
|
||||
return action, nil, nil
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
|
||||
// get the new account balance
|
||||
newCoins, hasNeg := initCoins.SafeSub(fees)
|
||||
if hasNeg {
|
||||
event(fmt.Sprintf("auth/SimulateDeductFee/false"))
|
||||
return action, nil, nil
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
|
||||
if err := stored.SetCoins(newCoins); err != nil {
|
||||
|
@ -58,10 +54,9 @@ func SimulateDeductFee(m auth.AccountKeeper, f auth.FeeCollectionKeeper) simulat
|
|||
|
||||
m.SetAccount(ctx, stored)
|
||||
f.AddCollectedFees(ctx, fees)
|
||||
event(fmt.Sprintf("auth/SimulateDeductFee/true"))
|
||||
|
||||
action = "TestDeductFee"
|
||||
return action, nil, nil
|
||||
opMsg.OK = true
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,47 +13,32 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
)
|
||||
|
||||
// SendTx tests and runs a single msg send where both
|
||||
// accounts already exist.
|
||||
func SendMsg(mapper auth.AccountKeeper, bk bank.Keeper) simulation.Operation {
|
||||
func SimulateMsgSend(mapper auth.AccountKeeper, bk bank.Keeper) simulation.Operation {
|
||||
handler := bank.NewHandler(bk)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) {
|
||||
fromAcc, action, msg, abort := createSendMsg(r, ctx, accs, mapper)
|
||||
if abort {
|
||||
return action, nil, nil
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
|
||||
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
fromAcc, comment, msg, ok := createMsgSend(r, ctx, accs, mapper)
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, comment)
|
||||
if !ok {
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
err = sendAndVerifyMsgSend(app, mapper, msg, ctx, []crypto.PrivKey{fromAcc.PrivKey}, handler)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return opMsg, nil, err
|
||||
}
|
||||
event("bank/sendAndVerifyTxSend/ok")
|
||||
|
||||
return action, nil, nil
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SendTx tests and runs a single tx send, with auth where both
|
||||
// accounts already exist.
|
||||
func SendTx(mapper auth.AccountKeeper) simulation.Operation {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) {
|
||||
fromAcc, action, msg, abort := createSendMsg(r, ctx, accs, mapper)
|
||||
if abort {
|
||||
return action, nil, nil
|
||||
}
|
||||
err = sendAndVerifyMsgSend(app, mapper, msg, ctx, []crypto.PrivKey{fromAcc.PrivKey}, nil)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
event("bank/sendAndVerifyTxSend/ok")
|
||||
func createMsgSend(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, mapper auth.AccountKeeper) (
|
||||
fromAcc simulation.Account, comment string, msg bank.MsgSend, ok bool) {
|
||||
|
||||
return action, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func createSendMsg(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, mapper auth.AccountKeeper) (fromAcc simulation.Account, action string, msg bank.MsgSend, abort bool) {
|
||||
fromAcc = simulation.RandomAcc(r, accs)
|
||||
toAcc := simulation.RandomAcc(r, accs)
|
||||
// Disallow sending money to yourself
|
||||
|
@ -63,29 +48,21 @@ func createSendMsg(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, map
|
|||
}
|
||||
toAcc = simulation.RandomAcc(r, accs)
|
||||
}
|
||||
toAddr := toAcc.Address
|
||||
initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).SpendableCoins(ctx.BlockHeader().Time)
|
||||
|
||||
if len(initFromCoins) == 0 {
|
||||
return fromAcc, "skipping, no coins at all", msg, true
|
||||
return fromAcc, "skipping, no coins at all", msg, false
|
||||
}
|
||||
|
||||
denomIndex := r.Intn(len(initFromCoins))
|
||||
amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount)
|
||||
if goErr != nil {
|
||||
return fromAcc, "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, msg, true
|
||||
return fromAcc, "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, msg, false
|
||||
}
|
||||
|
||||
action = fmt.Sprintf("%s is sending %s %s to %s",
|
||||
fromAcc.Address.String(),
|
||||
amt.String(),
|
||||
initFromCoins[denomIndex].Denom,
|
||||
toAddr.String(),
|
||||
)
|
||||
|
||||
coins := sdk.Coins{sdk.NewCoin(initFromCoins[denomIndex].Denom, amt)}
|
||||
msg = bank.NewMsgSend(fromAcc.Address, toAcc.Address, coins)
|
||||
return
|
||||
return fromAcc, "", msg, true
|
||||
}
|
||||
|
||||
// Sends and verifies the transition of a msg send.
|
||||
|
@ -133,44 +110,29 @@ func sendAndVerifyMsgSend(app *baseapp.BaseApp, mapper auth.AccountKeeper, msg b
|
|||
return nil
|
||||
}
|
||||
|
||||
// SingleInputSendTx tests and runs a single msg multisend w/ auth, with one input and one output, where both
|
||||
// accounts already exist.
|
||||
func SingleInputMultiSendTx(mapper auth.AccountKeeper) simulation.Operation {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) {
|
||||
fromAcc, action, msg, abort := createSingleInputMsgMultiSend(r, ctx, accs, mapper)
|
||||
if abort {
|
||||
return action, nil, nil
|
||||
}
|
||||
err = sendAndVerifyMsgMultiSend(app, mapper, msg, ctx, []crypto.PrivKey{fromAcc.PrivKey}, nil)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
event("bank/sendAndVerifyTxMultiSend/ok")
|
||||
|
||||
return action, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SingleInputSendMsg tests and runs a single msg multisend, with one input and one output, where both
|
||||
// accounts already exist.
|
||||
func SingleInputMsgMultiSend(mapper auth.AccountKeeper, bk bank.Keeper) simulation.Operation {
|
||||
func SimulateSingleInputMsgMultiSend(mapper auth.AccountKeeper, bk bank.Keeper) simulation.Operation {
|
||||
handler := bank.NewHandler(bk)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) {
|
||||
fromAcc, action, msg, abort := createSingleInputMsgMultiSend(r, ctx, accs, mapper)
|
||||
if abort {
|
||||
return action, nil, nil
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
|
||||
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
fromAcc, comment, msg, ok := createSingleInputMsgMultiSend(r, ctx, accs, mapper)
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, comment)
|
||||
if !ok {
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
err = sendAndVerifyMsgMultiSend(app, mapper, msg, ctx, []crypto.PrivKey{fromAcc.PrivKey}, handler)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return opMsg, nil, err
|
||||
}
|
||||
event("bank/sendAndVerifyMsgMultiSend/ok")
|
||||
|
||||
return action, nil, nil
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func createSingleInputMsgMultiSend(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, mapper auth.AccountKeeper) (fromAcc simulation.Account, action string, msg bank.MsgMultiSend, abort bool) {
|
||||
func createSingleInputMsgMultiSend(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, mapper auth.AccountKeeper) (
|
||||
fromAcc simulation.Account, comment string, msg bank.MsgMultiSend, ok bool) {
|
||||
|
||||
fromAcc = simulation.RandomAcc(r, accs)
|
||||
toAcc := simulation.RandomAcc(r, accs)
|
||||
// Disallow sending money to yourself
|
||||
|
@ -184,33 +146,28 @@ func createSingleInputMsgMultiSend(r *rand.Rand, ctx sdk.Context, accs []simulat
|
|||
initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).SpendableCoins(ctx.BlockHeader().Time)
|
||||
|
||||
if len(initFromCoins) == 0 {
|
||||
return fromAcc, "skipping, no coins at all", msg, true
|
||||
return fromAcc, "skipping, no coins at all", msg, false
|
||||
}
|
||||
|
||||
denomIndex := r.Intn(len(initFromCoins))
|
||||
amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount)
|
||||
if goErr != nil {
|
||||
return fromAcc, "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, msg, true
|
||||
return fromAcc, "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, msg, false
|
||||
}
|
||||
|
||||
action = fmt.Sprintf("%s is sending %s %s to %s",
|
||||
fromAcc.Address.String(),
|
||||
amt.String(),
|
||||
initFromCoins[denomIndex].Denom,
|
||||
toAddr.String(),
|
||||
)
|
||||
|
||||
coins := sdk.Coins{sdk.NewCoin(initFromCoins[denomIndex].Denom, amt)}
|
||||
msg = bank.MsgMultiSend{
|
||||
Inputs: []bank.Input{bank.NewInput(fromAcc.Address, coins)},
|
||||
Outputs: []bank.Output{bank.NewOutput(toAddr, coins)},
|
||||
}
|
||||
return
|
||||
return fromAcc, "", msg, true
|
||||
}
|
||||
|
||||
// Sends and verifies the transition of a msg multisend. This fails if there are repeated inputs or outputs
|
||||
// pass in handler as nil to handle txs, otherwise handle msgs
|
||||
func sendAndVerifyMsgMultiSend(app *baseapp.BaseApp, mapper auth.AccountKeeper, msg bank.MsgMultiSend, ctx sdk.Context, privkeys []crypto.PrivKey, handler sdk.Handler) error {
|
||||
func sendAndVerifyMsgMultiSend(app *baseapp.BaseApp, mapper auth.AccountKeeper, msg bank.MsgMultiSend,
|
||||
ctx sdk.Context, privkeys []crypto.PrivKey, handler sdk.Handler) error {
|
||||
|
||||
initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs))
|
||||
initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs))
|
||||
AccountNumbers := make([]uint64, len(msg.Inputs))
|
||||
|
|
|
@ -11,11 +11,11 @@ import (
|
|||
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
|
||||
|
||||
// determine the total power signing the block
|
||||
var totalPower, sumPrecommitPower int64
|
||||
var previousTotalPower, sumPreviousPrecommitPower int64
|
||||
for _, voteInfo := range req.LastCommitInfo.GetVotes() {
|
||||
totalPower += voteInfo.Validator.Power
|
||||
previousTotalPower += voteInfo.Validator.Power
|
||||
if voteInfo.SignedLastBlock {
|
||||
sumPrecommitPower += voteInfo.Validator.Power
|
||||
sumPreviousPrecommitPower += voteInfo.Validator.Power
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper)
|
|||
// ref https://github.com/cosmos/cosmos-sdk/issues/3095
|
||||
if ctx.BlockHeight() > 1 {
|
||||
previousProposer := k.GetPreviousProposerConsAddr(ctx)
|
||||
k.AllocateTokens(ctx, sumPrecommitPower, totalPower, previousProposer, req.LastCommitInfo.GetVotes())
|
||||
k.AllocateTokens(ctx, sumPreviousPrecommitPower, previousTotalPower, previousProposer, req.LastCommitInfo.GetVotes())
|
||||
}
|
||||
|
||||
// record the proposer for when we payout on the next block
|
||||
|
|
|
@ -9,42 +9,45 @@ import (
|
|||
)
|
||||
|
||||
// allocate fees handles distribution of the collected fees
|
||||
func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower int64, proposer sdk.ConsAddress, votes []abci.VoteInfo) {
|
||||
func (k Keeper) AllocateTokens(ctx sdk.Context, sumPreviousPrecommitPower, totalPreviousPower int64,
|
||||
previousProposer sdk.ConsAddress, previousVotes []abci.VoteInfo) {
|
||||
|
||||
logger := ctx.Logger().With("module", "x/distribution")
|
||||
|
||||
// fetch collected fees & fee pool
|
||||
// fetch and clear the collected fees for distribution, since this is
|
||||
// called in BeginBlock, collected fees will be from the previous block
|
||||
// (and distributed to the previous proposer)
|
||||
feesCollectedInt := k.feeCollectionKeeper.GetCollectedFees(ctx)
|
||||
feesCollected := sdk.NewDecCoins(feesCollectedInt)
|
||||
feePool := k.GetFeePool(ctx)
|
||||
|
||||
// clear collected fees, which will now be distributed
|
||||
k.feeCollectionKeeper.ClearCollectedFees(ctx)
|
||||
|
||||
// temporary workaround to keep CanWithdrawInvariant happy
|
||||
// general discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634
|
||||
if totalPower == 0 {
|
||||
feePool := k.GetFeePool(ctx)
|
||||
if totalPreviousPower == 0 {
|
||||
feePool.CommunityPool = feePool.CommunityPool.Add(feesCollected)
|
||||
k.SetFeePool(ctx, feePool)
|
||||
return
|
||||
}
|
||||
|
||||
// calculate fraction votes
|
||||
fractionVotes := sdk.NewDec(sumPrecommitPower).Quo(sdk.NewDec(totalPower))
|
||||
previousFractionVotes := sdk.NewDec(sumPreviousPrecommitPower).Quo(sdk.NewDec(totalPreviousPower))
|
||||
|
||||
// calculate proposer reward
|
||||
// calculate previous proposer reward
|
||||
baseProposerReward := k.GetBaseProposerReward(ctx)
|
||||
bonusProposerReward := k.GetBonusProposerReward(ctx)
|
||||
proposerMultiplier := baseProposerReward.Add(bonusProposerReward.MulTruncate(fractionVotes))
|
||||
proposerMultiplier := baseProposerReward.Add(bonusProposerReward.MulTruncate(previousFractionVotes))
|
||||
proposerReward := feesCollected.MulDecTruncate(proposerMultiplier)
|
||||
|
||||
// pay proposer
|
||||
// pay previous proposer
|
||||
remaining := feesCollected
|
||||
proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, proposer)
|
||||
proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, previousProposer)
|
||||
|
||||
if proposerValidator != nil {
|
||||
k.AllocateTokensToValidator(ctx, proposerValidator, proposerReward)
|
||||
remaining = remaining.Sub(proposerReward)
|
||||
} else {
|
||||
// proposer can be unknown if say, the unbonding period is 1 block, so
|
||||
// previous proposer can be unknown if say, the unbonding period is 1 block, so
|
||||
// e.g. a validator undelegates at block X, it's removed entirely by
|
||||
// block X+1's endblock, then X+2 we need to refer to the previous
|
||||
// proposer for X+1, but we've forgotten about them.
|
||||
|
@ -53,7 +56,7 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower in
|
|||
"This should happen only if the proposer unbonded completely within a single block, "+
|
||||
"which generally should not happen except in exceptional circumstances (or fuzz testing). "+
|
||||
"We recommend you investigate immediately.",
|
||||
proposer.String()))
|
||||
previousProposer.String()))
|
||||
}
|
||||
|
||||
// calculate fraction allocated to validators
|
||||
|
@ -62,14 +65,13 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower in
|
|||
|
||||
// allocate tokens proportionally to voting power
|
||||
// TODO consider parallelizing later, ref https://github.com/cosmos/cosmos-sdk/pull/3099#discussion_r246276376
|
||||
for _, vote := range votes {
|
||||
for _, vote := range previousVotes {
|
||||
validator := k.stakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address)
|
||||
|
||||
// TODO consider microslashing for missing votes.
|
||||
// ref https://github.com/cosmos/cosmos-sdk/issues/2525#issuecomment-430838701
|
||||
powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPower))
|
||||
powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPreviousPower))
|
||||
reward := feesCollected.MulDecTruncate(voteMultiplier).MulDecTruncate(powerFraction)
|
||||
reward = reward.Intersect(remaining)
|
||||
k.AllocateTokensToValidator(ctx, validator, reward)
|
||||
remaining = remaining.Sub(reward)
|
||||
}
|
||||
|
|
|
@ -8,34 +8,31 @@ import (
|
|||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/distribution"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
)
|
||||
|
||||
// SimulateMsgSetWithdrawAddress
|
||||
func SimulateMsgSetWithdrawAddress(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation {
|
||||
handler := distribution.NewHandler(k)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
accountOrigin := simulation.RandomAcc(r, accs)
|
||||
accountDestination := simulation.RandomAcc(r, accs)
|
||||
msg := distribution.NewMsgSetWithdrawAddress(accountOrigin.Address, accountDestination.Address)
|
||||
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := handler(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := handler(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
|
||||
event(fmt.Sprintf("distribution/MsgSetWithdrawAddress/%v", result.IsOK()))
|
||||
|
||||
action = fmt.Sprintf("TestMsgSetWithdrawAddress: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,27 +40,24 @@ func SimulateMsgSetWithdrawAddress(m auth.AccountKeeper, k distribution.Keeper)
|
|||
func SimulateMsgWithdrawDelegatorReward(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation {
|
||||
handler := distribution.NewHandler(k)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
delegatorAccount := simulation.RandomAcc(r, accs)
|
||||
validatorAccount := simulation.RandomAcc(r, accs)
|
||||
msg := distribution.NewMsgWithdrawDelegatorReward(delegatorAccount.Address, sdk.ValAddress(validatorAccount.Address))
|
||||
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := handler(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := handler(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
|
||||
event(fmt.Sprintf("distribution/MsgWithdrawDelegatorReward/%v", result.IsOK()))
|
||||
|
||||
action = fmt.Sprintf("TestMsgWithdrawDelegatorReward: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,25 +65,22 @@ func SimulateMsgWithdrawDelegatorReward(m auth.AccountKeeper, k distribution.Kee
|
|||
func SimulateMsgWithdrawValidatorCommission(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation {
|
||||
handler := distribution.NewHandler(k)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
account := simulation.RandomAcc(r, accs)
|
||||
msg := distribution.NewMsgWithdrawValidatorCommission(sdk.ValAddress(account.Address))
|
||||
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := handler(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := handler(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
|
||||
event(fmt.Sprintf("distribution/MsgWithdrawValidatorCommission/%v", result.IsOK()))
|
||||
|
||||
action = fmt.Sprintf("TestMsgWithdrawValidatorCommission: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
)
|
||||
|
||||
// SimulateSubmittingVotingAndSlashingForProposal simulates creating a msg Submit Proposal
|
||||
|
@ -38,17 +38,20 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper) simulation.Ope
|
|||
})
|
||||
statePercentageArray := []float64{1, .9, .75, .4, .15, 0}
|
||||
curNumVotesState := 1
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
|
||||
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
// 1) submit proposal now
|
||||
sender := simulation.RandomAcc(r, accs)
|
||||
msg, err := simulationCreateMsgSubmitProposal(r, sender)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return simulation.NoOpMsg(), nil, err
|
||||
}
|
||||
action, ok := simulateHandleMsgSubmitProposal(msg, handler, ctx, event)
|
||||
ok := simulateHandleMsgSubmitProposal(msg, handler, ctx)
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
// don't schedule votes if proposal failed
|
||||
if !ok {
|
||||
return action, nil, nil
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
proposalID := k.GetLastProposalID(ctx)
|
||||
// 2) Schedule operations for votes
|
||||
|
@ -69,7 +72,7 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper) simulation.Ope
|
|||
// TODO: Find a way to check if a validator was slashed other than just checking their balance a block
|
||||
// before and after.
|
||||
|
||||
return action, fops, nil
|
||||
return opMsg, fops, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,27 +80,27 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper) simulation.Ope
|
|||
// Note: Currently doesn't ensure that the proposal txt is in JSON form
|
||||
func SimulateMsgSubmitProposal(k gov.Keeper) simulation.Operation {
|
||||
handler := gov.NewHandler(k)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
|
||||
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
sender := simulation.RandomAcc(r, accs)
|
||||
msg, err := simulationCreateMsgSubmitProposal(r, sender)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return simulation.NoOpMsg(), nil, err
|
||||
}
|
||||
action, _ = simulateHandleMsgSubmitProposal(msg, handler, ctx, event)
|
||||
return action, nil, nil
|
||||
ok := simulateHandleMsgSubmitProposal(msg, handler, ctx)
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, handler sdk.Handler, ctx sdk.Context, event func(string)) (action string, ok bool) {
|
||||
func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, handler sdk.Handler, ctx sdk.Context) (ok bool) {
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := handler(ctx, msg)
|
||||
ok = result.IsOK()
|
||||
ok = handler(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("gov/MsgSubmitProposal/%v", ok))
|
||||
action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", ok, msg.GetSignBytes())
|
||||
return
|
||||
return ok
|
||||
}
|
||||
|
||||
func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account) (msg gov.MsgSubmitProposal, err error) {
|
||||
|
@ -117,25 +120,27 @@ func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account)
|
|||
|
||||
// SimulateMsgDeposit
|
||||
func SimulateMsgDeposit(k gov.Keeper) simulation.Operation {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
|
||||
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
acc := simulation.RandomAcc(r, accs)
|
||||
proposalID, ok := randomProposalID(r, k, ctx)
|
||||
if !ok {
|
||||
return "no-operation", nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
deposit := randomDeposit(r)
|
||||
msg := gov.NewMsgDeposit(acc.Address, proposalID, deposit)
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := gov.NewHandler(k)(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok = gov.NewHandler(k)(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("gov/MsgDeposit/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgDeposit: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,35 +152,35 @@ func SimulateMsgVote(k gov.Keeper) simulation.Operation {
|
|||
|
||||
// nolint: unparam
|
||||
func operationSimulateMsgVote(k gov.Keeper, acc simulation.Account, proposalID uint64) simulation.Operation {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
|
||||
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
if acc.Equals(simulation.Account{}) {
|
||||
acc = simulation.RandomAcc(r, accs)
|
||||
}
|
||||
|
||||
var ok bool
|
||||
|
||||
if proposalID < 0 {
|
||||
var ok bool
|
||||
proposalID, ok = randomProposalID(r, k, ctx)
|
||||
if !ok {
|
||||
return "no-operation", nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
}
|
||||
option := randomVotingOption(r)
|
||||
|
||||
msg := gov.NewMsgVote(acc.Address, proposalID, option)
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := gov.NewHandler(k)(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := gov.NewHandler(k)(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
|
||||
event(fmt.Sprintf("gov/MsgVote/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgVote: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Operation runs a state machine transition, and ensures the transition
|
||||
// happened as expected. The operation could be running and testing a fuzzed
|
||||
// transaction, or doing the same for a message.
|
||||
//
|
||||
// For ease of debugging, an operation returns a descriptive message "action",
|
||||
// which details what this fuzzed state machine transition actually did.
|
||||
//
|
||||
// Operations can optionally provide a list of "FutureOperations" to run later
|
||||
// These will be ran at the beginning of the corresponding block.
|
||||
type Operation func(r *rand.Rand, app *baseapp.BaseApp,
|
||||
ctx sdk.Context, accounts []Account, event func(string)) (
|
||||
action string, futureOps []FutureOperation, err error)
|
||||
|
||||
// queue of operations
|
||||
type OperationQueue map[int][]Operation
|
||||
|
||||
func newOperationQueue() OperationQueue {
|
||||
operationQueue := make(OperationQueue)
|
||||
return operationQueue
|
||||
}
|
||||
|
||||
// adds all future operations into the operation queue.
|
||||
func queueOperations(queuedOps OperationQueue,
|
||||
queuedTimeOps []FutureOperation, futureOps []FutureOperation) {
|
||||
|
||||
if futureOps == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, futureOp := range futureOps {
|
||||
if futureOp.BlockHeight != 0 {
|
||||
if val, ok := queuedOps[futureOp.BlockHeight]; ok {
|
||||
queuedOps[futureOp.BlockHeight] = append(val, futureOp.Op)
|
||||
} else {
|
||||
queuedOps[futureOp.BlockHeight] = []Operation{futureOp.Op}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: Replace with proper sorted data structure, so don't have the
|
||||
// copy entire slice
|
||||
index := sort.Search(
|
||||
len(queuedTimeOps),
|
||||
func(i int) bool {
|
||||
return queuedTimeOps[i].BlockTime.After(futureOp.BlockTime)
|
||||
},
|
||||
)
|
||||
queuedTimeOps = append(queuedTimeOps, FutureOperation{})
|
||||
copy(queuedTimeOps[index+1:], queuedTimeOps[index:])
|
||||
queuedTimeOps[index] = futureOp
|
||||
}
|
||||
}
|
||||
|
||||
//________________________________________________________________________
|
||||
|
||||
// FutureOperation is an operation which will be ran at the beginning of the
|
||||
// provided BlockHeight. If both a BlockHeight and BlockTime are specified, it
|
||||
// will use the BlockHeight. In the (likely) event that multiple operations
|
||||
// are queued at the same block height, they will execute in a FIFO pattern.
|
||||
type FutureOperation struct {
|
||||
BlockHeight int
|
||||
BlockTime time.Time
|
||||
Op Operation
|
||||
}
|
||||
|
||||
//________________________________________________________________________
|
||||
|
||||
// WeightedOperation is an operation with associated weight.
|
||||
// This is used to bias the selection operation within the simulator.
|
||||
type WeightedOperation struct {
|
||||
Weight int
|
||||
Op Operation
|
||||
}
|
||||
|
||||
// WeightedOperations is the group of all weighted operations to simulate.
|
||||
type WeightedOperations []WeightedOperation
|
||||
|
||||
func (ops WeightedOperations) totalWeight() int {
|
||||
totalOpWeight := 0
|
||||
for _, op := range ops {
|
||||
totalOpWeight += op.Weight
|
||||
}
|
||||
return totalOpWeight
|
||||
}
|
||||
|
||||
type selectOpFn func(r *rand.Rand) Operation
|
||||
|
||||
func (ops WeightedOperations) getSelectOpFn() selectOpFn {
|
||||
totalOpWeight := ops.totalWeight()
|
||||
return func(r *rand.Rand) Operation {
|
||||
x := r.Intn(totalOpWeight)
|
||||
for i := 0; i < len(ops); i++ {
|
||||
if x <= ops[i].Weight {
|
||||
return ops[i].Op
|
||||
}
|
||||
x -= ops[i].Weight
|
||||
}
|
||||
// shouldn't happen
|
||||
return ops[0].Op
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// log writter
|
||||
type LogWriter interface {
|
||||
AddEntry(OperationEntry)
|
||||
PrintLogs()
|
||||
}
|
||||
|
||||
// LogWriter - return a dummy or standard log writer given the testingmode
|
||||
func NewLogWriter(testingmode bool) LogWriter {
|
||||
if !testingmode {
|
||||
return &DummyLogWriter{}
|
||||
}
|
||||
return &StandardLogWriter{}
|
||||
}
|
||||
|
||||
// log writter
|
||||
type StandardLogWriter struct {
|
||||
OpEntries []OperationEntry `json:"op_entries"`
|
||||
}
|
||||
|
||||
// add an entry to the log writter
|
||||
func (lw *StandardLogWriter) AddEntry(opEntry OperationEntry) {
|
||||
lw.OpEntries = append(lw.OpEntries, opEntry)
|
||||
}
|
||||
|
||||
// PrintLogs - print the logs to a simulation file
|
||||
func (lw *StandardLogWriter) PrintLogs() {
|
||||
f := createLogFile()
|
||||
for i := 0; i < len(lw.OpEntries); i++ {
|
||||
writeEntry := fmt.Sprintf("%s\n", (lw.OpEntries[i]).MustMarshal())
|
||||
_, err := f.WriteString(writeEntry)
|
||||
if err != nil {
|
||||
panic("Failed to write logs to file")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createLogFile() *os.File {
|
||||
var f *os.File
|
||||
fileName := fmt.Sprintf("%s.log", time.Now().Format("2006-01-02_15:04:05"))
|
||||
|
||||
folderPath := os.ExpandEnv("$HOME/.gaiad/simulations")
|
||||
filePath := path.Join(folderPath, fileName)
|
||||
|
||||
err := os.MkdirAll(folderPath, os.ModePerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f, _ = os.Create(filePath)
|
||||
fmt.Printf("Logs to writing to %s\n", filePath)
|
||||
return f
|
||||
}
|
||||
|
||||
//_____________________
|
||||
// dummy log writter
|
||||
type DummyLogWriter struct{}
|
||||
|
||||
// do nothing
|
||||
func (lw *DummyLogWriter) AddEntry(_ OperationEntry) {}
|
||||
|
||||
// do nothing
|
||||
func (lw *DummyLogWriter) PrintLogs() {}
|
|
@ -0,0 +1,251 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Operation runs a state machine transition, and ensures the transition
|
||||
// happened as expected. The operation could be running and testing a fuzzed
|
||||
// transaction, or doing the same for a message.
|
||||
//
|
||||
// For ease of debugging, an operation returns a descriptive message "action",
|
||||
// which details what this fuzzed state machine transition actually did.
|
||||
//
|
||||
// Operations can optionally provide a list of "FutureOperations" to run later
|
||||
// These will be ran at the beginning of the corresponding block.
|
||||
type Operation func(r *rand.Rand, app *baseapp.BaseApp,
|
||||
ctx sdk.Context, accounts []Account) (
|
||||
OperationMsg OperationMsg, futureOps []FutureOperation, err error)
|
||||
|
||||
// entry kinds for use within OperationEntry
|
||||
const (
|
||||
BeginBlockEntryKind = "begin_block"
|
||||
EndBlockEntryKind = "end_block"
|
||||
MsgEntryKind = "msg"
|
||||
QueuedsgMsgEntryKind = "queued_msg"
|
||||
)
|
||||
|
||||
// OperationEntry - an operation entry for logging (ex. BeginBlock, EndBlock, XxxMsg, etc)
|
||||
type OperationEntry struct {
|
||||
EntryKind string `json:"entry_kind"`
|
||||
Height int64 `json:"height"`
|
||||
Order int64 `json:"order"`
|
||||
Operation json.RawMessage `json:"operation"`
|
||||
}
|
||||
|
||||
// BeginBlockEntry - operation entry for begin block
|
||||
func BeginBlockEntry(height int64) OperationEntry {
|
||||
return OperationEntry{
|
||||
EntryKind: BeginBlockEntryKind,
|
||||
Height: height,
|
||||
Order: -1,
|
||||
Operation: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// EndBlockEntry - operation entry for end block
|
||||
func EndBlockEntry(height int64) OperationEntry {
|
||||
return OperationEntry{
|
||||
EntryKind: EndBlockEntryKind,
|
||||
Height: height,
|
||||
Order: -1,
|
||||
Operation: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// MsgEntry - operation entry for standard msg
|
||||
func MsgEntry(height int64, opMsg OperationMsg, order int64) OperationEntry {
|
||||
return OperationEntry{
|
||||
EntryKind: MsgEntryKind,
|
||||
Height: height,
|
||||
Order: order,
|
||||
Operation: opMsg.MustMarshal(),
|
||||
}
|
||||
}
|
||||
|
||||
// MsgEntry - operation entry for queued msg
|
||||
func QueuedMsgEntry(height int64, opMsg OperationMsg) OperationEntry {
|
||||
return OperationEntry{
|
||||
EntryKind: QueuedsgMsgEntryKind,
|
||||
Height: height,
|
||||
Order: -1,
|
||||
Operation: opMsg.MustMarshal(),
|
||||
}
|
||||
}
|
||||
|
||||
// OperationEntry - log entry text for this operation entry
|
||||
func (oe OperationEntry) MustMarshal() json.RawMessage {
|
||||
out, err := json.Marshal(oe)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
//_____________________________________________________________________
|
||||
|
||||
// OperationMsg - structure for operation output
|
||||
type OperationMsg struct {
|
||||
Route string `json:"route"`
|
||||
Name string `json:"name"`
|
||||
Comment string `json:"comment"`
|
||||
OK bool `json:"ok"`
|
||||
Msg json.RawMessage `json:"msg"`
|
||||
}
|
||||
|
||||
// OperationMsg - create a new operation message from sdk.Msg
|
||||
func NewOperationMsg(msg sdk.Msg, ok bool, comment string) OperationMsg {
|
||||
|
||||
return OperationMsg{
|
||||
Route: msg.Route(),
|
||||
Name: msg.Type(),
|
||||
Comment: comment,
|
||||
OK: ok,
|
||||
Msg: msg.GetSignBytes(),
|
||||
}
|
||||
}
|
||||
|
||||
// OperationMsg - create a new operation message from raw input
|
||||
func NewOperationMsgBasic(route, name, comment string, ok bool, msg []byte) OperationMsg {
|
||||
return OperationMsg{
|
||||
Route: route,
|
||||
Name: name,
|
||||
Comment: comment,
|
||||
OK: ok,
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// NoOpMsg - create a no-operation message
|
||||
func NoOpMsg() OperationMsg {
|
||||
return OperationMsg{
|
||||
Route: "",
|
||||
Name: "no-operation",
|
||||
Comment: "",
|
||||
OK: false,
|
||||
Msg: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// log entry text for this operation msg
|
||||
func (om OperationMsg) String() string {
|
||||
out, err := json.Marshal(om)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// Marshal the operation msg, panic on error
|
||||
func (om OperationMsg) MustMarshal() json.RawMessage {
|
||||
out, err := json.Marshal(om)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// add event for event stats
|
||||
func (om OperationMsg) LogEvent(eventLogger func(string)) {
|
||||
pass := "ok"
|
||||
if !om.OK {
|
||||
pass = "failure"
|
||||
}
|
||||
eventLogger(fmt.Sprintf("%v/%v/%v", om.Route, om.Name, pass))
|
||||
}
|
||||
|
||||
// queue of operations
|
||||
type OperationQueue map[int][]Operation
|
||||
|
||||
func newOperationQueue() OperationQueue {
|
||||
operationQueue := make(OperationQueue)
|
||||
return operationQueue
|
||||
}
|
||||
|
||||
// adds all future operations into the operation queue.
|
||||
func queueOperations(queuedOps OperationQueue,
|
||||
queuedTimeOps []FutureOperation, futureOps []FutureOperation) {
|
||||
|
||||
if futureOps == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, futureOp := range futureOps {
|
||||
if futureOp.BlockHeight != 0 {
|
||||
if val, ok := queuedOps[futureOp.BlockHeight]; ok {
|
||||
queuedOps[futureOp.BlockHeight] = append(val, futureOp.Op)
|
||||
} else {
|
||||
queuedOps[futureOp.BlockHeight] = []Operation{futureOp.Op}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: Replace with proper sorted data structure, so don't have the
|
||||
// copy entire slice
|
||||
index := sort.Search(
|
||||
len(queuedTimeOps),
|
||||
func(i int) bool {
|
||||
return queuedTimeOps[i].BlockTime.After(futureOp.BlockTime)
|
||||
},
|
||||
)
|
||||
queuedTimeOps = append(queuedTimeOps, FutureOperation{})
|
||||
copy(queuedTimeOps[index+1:], queuedTimeOps[index:])
|
||||
queuedTimeOps[index] = futureOp
|
||||
}
|
||||
}
|
||||
|
||||
//________________________________________________________________________
|
||||
|
||||
// FutureOperation is an operation which will be ran at the beginning of the
|
||||
// provided BlockHeight. If both a BlockHeight and BlockTime are specified, it
|
||||
// will use the BlockHeight. In the (likely) event that multiple operations
|
||||
// are queued at the same block height, they will execute in a FIFO pattern.
|
||||
type FutureOperation struct {
|
||||
BlockHeight int
|
||||
BlockTime time.Time
|
||||
Op Operation
|
||||
}
|
||||
|
||||
//________________________________________________________________________
|
||||
|
||||
// WeightedOperation is an operation with associated weight.
|
||||
// This is used to bias the selection operation within the simulator.
|
||||
type WeightedOperation struct {
|
||||
Weight int
|
||||
Op Operation
|
||||
}
|
||||
|
||||
// WeightedOperations is the group of all weighted operations to simulate.
|
||||
type WeightedOperations []WeightedOperation
|
||||
|
||||
func (ops WeightedOperations) totalWeight() int {
|
||||
totalOpWeight := 0
|
||||
for _, op := range ops {
|
||||
totalOpWeight += op.Weight
|
||||
}
|
||||
return totalOpWeight
|
||||
}
|
||||
|
||||
type selectOpFn func(r *rand.Rand) Operation
|
||||
|
||||
func (ops WeightedOperations) getSelectOpFn() selectOpFn {
|
||||
totalOpWeight := ops.totalWeight()
|
||||
return func(r *rand.Rand) Operation {
|
||||
x := r.Intn(totalOpWeight)
|
||||
for i := 0; i < len(ops); i++ {
|
||||
if x <= ops[i].Weight {
|
||||
return ops[i].Op
|
||||
}
|
||||
x -= ops[i].Weight
|
||||
}
|
||||
// shouldn't happen
|
||||
return ops[0].Op
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -66,15 +65,6 @@ func RandomDecAmount(r *rand.Rand, max sdk.Dec) sdk.Dec {
|
|||
return sdk.NewDecFromBigIntWithPrec(randInt, sdk.Precision)
|
||||
}
|
||||
|
||||
// RandomSetGenesis wraps mock.RandomSetGenesis, but using simulation accounts
|
||||
func RandomSetGenesis(r *rand.Rand, app *mock.App, accs []Account, denoms []string) {
|
||||
addrs := make([]sdk.AccAddress, len(accs))
|
||||
for i := 0; i < len(accs); i++ {
|
||||
addrs[i] = accs[i].Address
|
||||
}
|
||||
mock.RandomSetGenesis(r, app, addrs, denoms)
|
||||
}
|
||||
|
||||
// RandTimestamp generates a random timestamp
|
||||
func RandTimestamp(r *rand.Rand) time.Time {
|
||||
// json.Marshal breaks for timestamps greater with year greater than 9999
|
|
@ -7,7 +7,6 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -24,11 +23,11 @@ type AppStateFn func(r *rand.Rand, accs []Account, genesisTimestamp time.Time) (
|
|||
// Simulate tests application by sending random messages.
|
||||
func Simulate(t *testing.T, app *baseapp.BaseApp,
|
||||
appStateFn AppStateFn, ops WeightedOperations,
|
||||
invariants sdk.Invariants, numBlocks int, blockSize int, commit bool) (bool, error) {
|
||||
invariants sdk.Invariants, numBlocks, blockSize int, commit, lean bool) (bool, error) {
|
||||
|
||||
time := time.Now().UnixNano()
|
||||
return SimulateFromSeed(t, app, appStateFn, time, ops,
|
||||
invariants, numBlocks, blockSize, commit)
|
||||
invariants, numBlocks, blockSize, commit, lean)
|
||||
}
|
||||
|
||||
// initialize the chain for the simulation
|
||||
|
@ -55,7 +54,7 @@ func initChain(
|
|||
func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
|
||||
appStateFn AppStateFn, seed int64, ops WeightedOperations,
|
||||
invariants sdk.Invariants,
|
||||
numBlocks int, blockSize int, commit bool) (stopEarly bool, simError error) {
|
||||
numBlocks, blockSize int, commit, lean bool) (stopEarly bool, simError error) {
|
||||
|
||||
// in case we have to end early, don't os.Exit so that we can run cleanup code.
|
||||
testingMode, t, b := getTestingMode(tb)
|
||||
|
@ -110,16 +109,13 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
|
|||
// These are operations which have been queued by previous operations
|
||||
operationQueue := newOperationQueue()
|
||||
timeOperationQueue := []FutureOperation{}
|
||||
var blockLogBuilders []*strings.Builder
|
||||
|
||||
if testingMode {
|
||||
blockLogBuilders = make([]*strings.Builder, numBlocks)
|
||||
}
|
||||
displayLogs := logPrinter(testingMode, blockLogBuilders)
|
||||
logWriter := NewLogWriter(testingMode)
|
||||
|
||||
blockSimulator := createBlockSimulator(
|
||||
testingMode, tb, t, params, eventStats.tally, invariants,
|
||||
ops, operationQueue, timeOperationQueue,
|
||||
numBlocks, blockSize, displayLogs)
|
||||
numBlocks, blockSize, logWriter, lean)
|
||||
|
||||
if !testingMode {
|
||||
b.ResetTimer()
|
||||
|
@ -130,7 +126,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
|
|||
fmt.Println("Panic with err\n", r)
|
||||
stackTrace := string(debug.Stack())
|
||||
fmt.Println(stackTrace)
|
||||
displayLogs()
|
||||
logWriter.PrintLogs()
|
||||
simError = fmt.Errorf(
|
||||
"Simulation halted due to panic on block %d",
|
||||
header.Height)
|
||||
|
@ -139,46 +135,40 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
|
|||
}
|
||||
|
||||
// TODO split up the contents of this for loop into new functions
|
||||
for i := 0; i < numBlocks && !stopEarly; i++ {
|
||||
for height := 1; height <= numBlocks && !stopEarly; height++ {
|
||||
|
||||
// Log the header time for future lookup
|
||||
pastTimes = append(pastTimes, header.Time)
|
||||
pastVoteInfos = append(pastVoteInfos, request.LastCommitInfo.Votes)
|
||||
|
||||
// Construct log writer
|
||||
logWriter := addLogMessage(testingMode, blockLogBuilders, i)
|
||||
|
||||
// Run the BeginBlock handler
|
||||
logWriter("BeginBlock")
|
||||
logWriter.AddEntry(BeginBlockEntry(int64(height)))
|
||||
app.BeginBlock(request)
|
||||
|
||||
if testingMode {
|
||||
assertAllInvariants(t, app, invariants, "BeginBlock", displayLogs)
|
||||
assertAllInvariants(t, app, invariants, "BeginBlock", logWriter)
|
||||
}
|
||||
|
||||
ctx := app.NewContext(false, header)
|
||||
|
||||
// Run queued operations. Ignores blocksize if blocksize is too small
|
||||
logWriter("Queued operations")
|
||||
numQueuedOpsRan := runQueuedOperations(
|
||||
operationQueue, int(header.Height),
|
||||
tb, r, app, ctx, accs, logWriter,
|
||||
displayLogs, eventStats.tally)
|
||||
tb, r, app, ctx, accs, logWriter, eventStats.tally, lean)
|
||||
|
||||
numQueuedTimeOpsRan := runQueuedTimeOperations(
|
||||
timeOperationQueue, header.Time,
|
||||
tb, r, app, ctx, accs,
|
||||
logWriter, displayLogs, eventStats.tally)
|
||||
timeOperationQueue, int(header.Height), header.Time,
|
||||
tb, r, app, ctx, accs, logWriter, eventStats.tally, lean)
|
||||
|
||||
if testingMode && onOperation {
|
||||
assertAllInvariants(t, app, invariants, "QueuedOperations", displayLogs)
|
||||
assertAllInvariants(t, app, invariants, "QueuedOperations", logWriter)
|
||||
}
|
||||
|
||||
logWriter("Standard operations")
|
||||
operations := blockSimulator(r, app, ctx, accs, header, logWriter)
|
||||
// run standard operations
|
||||
operations := blockSimulator(r, app, ctx, accs, header)
|
||||
opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan
|
||||
if testingMode {
|
||||
assertAllInvariants(t, app, invariants, "StandardOperations", displayLogs)
|
||||
assertAllInvariants(t, app, invariants, "StandardOperations", logWriter)
|
||||
}
|
||||
|
||||
res := app.EndBlock(abci.RequestEndBlock{})
|
||||
|
@ -188,10 +178,10 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
|
|||
header.Time = header.Time.Add(
|
||||
time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second)
|
||||
header.ProposerAddress = validators.randomProposer(r)
|
||||
logWriter("EndBlock")
|
||||
logWriter.AddEntry(EndBlockEntry(int64(height)))
|
||||
|
||||
if testingMode {
|
||||
assertAllInvariants(t, app, invariants, "EndBlock", displayLogs)
|
||||
assertAllInvariants(t, app, invariants, "EndBlock", logWriter)
|
||||
}
|
||||
if commit {
|
||||
app.Commit()
|
||||
|
@ -231,21 +221,21 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
|
|||
//______________________________________________________________________________
|
||||
|
||||
type blockSimFn func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accounts []Account, header abci.Header, logWriter func(string)) (opCount int)
|
||||
accounts []Account, header abci.Header) (opCount int)
|
||||
|
||||
// Returns a function to simulate blocks. Written like this to avoid constant
|
||||
// parameters being passed everytime, to minimize memory overhead.
|
||||
func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params Params,
|
||||
event func(string), invariants sdk.Invariants, ops WeightedOperations,
|
||||
operationQueue OperationQueue, timeOperationQueue []FutureOperation,
|
||||
totalNumBlocks int, avgBlockSize int, displayLogs func()) blockSimFn {
|
||||
totalNumBlocks, avgBlockSize int, logWriter LogWriter, lean bool) blockSimFn {
|
||||
|
||||
var lastBlocksizeState = 0 // state for [4 * uniform distribution]
|
||||
var blocksize int
|
||||
lastBlocksizeState := 0 // state for [4 * uniform distribution]
|
||||
blocksize := 0
|
||||
selectOp := ops.getSelectOpFn()
|
||||
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accounts []Account, header abci.Header, logWriter func(string)) (opCount int) {
|
||||
accounts []Account, header abci.Header) (opCount int) {
|
||||
|
||||
fmt.Printf("\rSimulating... block %d/%d, operation %d/%d. ",
|
||||
header.Height, totalNumBlocks, opCount, blocksize)
|
||||
|
@ -269,10 +259,13 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params
|
|||
// NOTE: the Rand 'r' should not be used here.
|
||||
opAndR := opAndRz[i]
|
||||
op, r2 := opAndR.op, opAndR.rand
|
||||
logUpdate, futureOps, err := op(r2, app, ctx, accounts, event)
|
||||
logWriter(logUpdate)
|
||||
opMsg, futureOps, err := op(r2, app, ctx, accounts)
|
||||
opMsg.LogEvent(event)
|
||||
if !lean || opMsg.OK {
|
||||
logWriter.AddEntry(MsgEntry(header.Height, opMsg, int64(i)))
|
||||
}
|
||||
if err != nil {
|
||||
displayLogs()
|
||||
logWriter.PrintLogs()
|
||||
tb.Fatalf("error on operation %d within block %d, %v",
|
||||
header.Height, opCount, err)
|
||||
}
|
||||
|
@ -280,8 +273,8 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params
|
|||
queueOperations(operationQueue, timeOperationQueue, futureOps)
|
||||
if testingMode {
|
||||
if onOperation {
|
||||
eventStr := fmt.Sprintf("operation: %v", logUpdate)
|
||||
assertAllInvariants(t, app, invariants, eventStr, displayLogs)
|
||||
eventStr := fmt.Sprintf("operation: %v", opMsg.String())
|
||||
assertAllInvariants(t, app, invariants, eventStr, logWriter)
|
||||
}
|
||||
if opCount%50 == 0 {
|
||||
fmt.Printf("\rSimulating... block %d/%d, operation %d/%d. ",
|
||||
|
@ -297,8 +290,7 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params
|
|||
// nolint: errcheck
|
||||
func runQueuedOperations(queueOps map[int][]Operation,
|
||||
height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp,
|
||||
ctx sdk.Context, accounts []Account, logWriter func(string),
|
||||
displayLogs func(), tallyEvent func(string)) (numOpsRan int) {
|
||||
ctx sdk.Context, accounts []Account, logWriter LogWriter, tallyEvent func(string), lean bool) (numOpsRan int) {
|
||||
|
||||
queuedOp, ok := queueOps[height]
|
||||
if !ok {
|
||||
|
@ -311,10 +303,13 @@ func runQueuedOperations(queueOps map[int][]Operation,
|
|||
// For now, queued operations cannot queue more operations.
|
||||
// If a need arises for us to support queued messages to queue more messages, this can
|
||||
// be changed.
|
||||
logUpdate, _, err := queuedOp[i](r, app, ctx, accounts, tallyEvent)
|
||||
logWriter(logUpdate)
|
||||
opMsg, _, err := queuedOp[i](r, app, ctx, accounts)
|
||||
opMsg.LogEvent(tallyEvent)
|
||||
if !lean || opMsg.OK {
|
||||
logWriter.AddEntry((QueuedMsgEntry(int64(height), opMsg)))
|
||||
}
|
||||
if err != nil {
|
||||
displayLogs()
|
||||
logWriter.PrintLogs()
|
||||
tb.FailNow()
|
||||
}
|
||||
}
|
||||
|
@ -323,9 +318,9 @@ func runQueuedOperations(queueOps map[int][]Operation,
|
|||
}
|
||||
|
||||
func runQueuedTimeOperations(queueOps []FutureOperation,
|
||||
currentTime time.Time, tb testing.TB, r *rand.Rand,
|
||||
height int, currentTime time.Time, tb testing.TB, r *rand.Rand,
|
||||
app *baseapp.BaseApp, ctx sdk.Context, accounts []Account,
|
||||
logWriter func(string), displayLogs func(), tallyEvent func(string)) (numOpsRan int) {
|
||||
logWriter LogWriter, tallyEvent func(string), lean bool) (numOpsRan int) {
|
||||
|
||||
numOpsRan = 0
|
||||
for len(queueOps) > 0 && currentTime.After(queueOps[0].BlockTime) {
|
||||
|
@ -333,10 +328,13 @@ func runQueuedTimeOperations(queueOps []FutureOperation,
|
|||
// For now, queued operations cannot queue more operations.
|
||||
// If a need arises for us to support queued messages to queue more messages, this can
|
||||
// be changed.
|
||||
logUpdate, _, err := queueOps[0].Op(r, app, ctx, accounts, tallyEvent)
|
||||
logWriter(logUpdate)
|
||||
opMsg, _, err := queueOps[0].Op(r, app, ctx, accounts)
|
||||
opMsg.LogEvent(tallyEvent)
|
||||
if !lean || opMsg.OK {
|
||||
logWriter.AddEntry(QueuedMsgEntry(int64(height), opMsg))
|
||||
}
|
||||
if err != nil {
|
||||
displayLogs()
|
||||
logWriter.PrintLogs()
|
||||
tb.FailNow()
|
||||
}
|
||||
|
|
@ -3,10 +3,7 @@ package simulation
|
|||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
@ -15,14 +12,14 @@ import (
|
|||
|
||||
// assertAll asserts the all invariants against application state
|
||||
func assertAllInvariants(t *testing.T, app *baseapp.BaseApp, invs sdk.Invariants,
|
||||
event string, displayLogs func()) {
|
||||
event string, logWriter LogWriter) {
|
||||
|
||||
ctx := app.NewContext(false, abci.Header{Height: app.LastBlockHeight() + 1})
|
||||
|
||||
for i := 0; i < len(invs); i++ {
|
||||
if err := invs[i](ctx); err != nil {
|
||||
fmt.Printf("Invariants broken after %s\n%s\n", event, err.Error())
|
||||
displayLogs()
|
||||
logWriter.PrintLogs()
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
@ -36,67 +33,7 @@ func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B
|
|||
} else {
|
||||
b = tb.(*testing.B)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Builds a function to add logs for this particular block
|
||||
func addLogMessage(testingmode bool,
|
||||
blockLogBuilders []*strings.Builder, height int) func(string) {
|
||||
|
||||
if !testingmode {
|
||||
return func(_ string) {}
|
||||
}
|
||||
|
||||
blockLogBuilders[height] = &strings.Builder{}
|
||||
return func(x string) {
|
||||
(*blockLogBuilders[height]).WriteString(x)
|
||||
(*blockLogBuilders[height]).WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a function to print out the logs
|
||||
func logPrinter(testingmode bool, logs []*strings.Builder) func() {
|
||||
if !testingmode {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
return func() {
|
||||
numLoggers := 0
|
||||
for i := 0; i < len(logs); i++ {
|
||||
// We're passed the last created block
|
||||
if logs[i] == nil {
|
||||
numLoggers = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var f *os.File
|
||||
if numLoggers > 10 {
|
||||
fileName := fmt.Sprintf("simulation_log_%s.txt",
|
||||
time.Now().Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("Too many logs to display, instead writing to %s\n",
|
||||
fileName)
|
||||
f, _ = os.Create(fileName)
|
||||
}
|
||||
|
||||
for i := 0; i < numLoggers; i++ {
|
||||
if f == nil {
|
||||
fmt.Printf("Begin block %d\n", i+1)
|
||||
fmt.Println((*logs[i]).String())
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := f.WriteString(fmt.Sprintf("Begin block %d\n", i+1))
|
||||
if err != nil {
|
||||
panic("Failed to write logs to file")
|
||||
}
|
||||
|
||||
_, err = f.WriteString((*logs[i]).String())
|
||||
if err != nil {
|
||||
panic("Failed to write logs to file")
|
||||
}
|
||||
}
|
||||
}
|
||||
return testingMode, t, b
|
||||
}
|
||||
|
||||
// getBlockSize returns a block size as determined from the transition matrix.
|
|
@ -6,29 +6,27 @@ import (
|
|||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
)
|
||||
|
||||
// SimulateMsgUnjail
|
||||
func SimulateMsgUnjail(k slashing.Keeper) simulation.Operation {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
acc := simulation.RandomAcc(r, accs)
|
||||
address := sdk.ValAddress(acc.Address)
|
||||
msg := slashing.NewMsgUnjail(address)
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := slashing.NewHandler(k)(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := slashing.NewHandler(k)(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("slashing/MsgUnjail/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgUnjail: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,21 +7,17 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||
)
|
||||
|
||||
const (
|
||||
noOperation = "no-operation"
|
||||
)
|
||||
|
||||
// SimulateMsgCreateValidator
|
||||
func SimulateMsgCreateValidator(m auth.AccountKeeper, k staking.Keeper) simulation.Operation {
|
||||
handler := staking.NewHandler(k)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (
|
||||
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
denom := k.GetParams(ctx).BondDenom
|
||||
description := staking.Description{
|
||||
|
@ -43,7 +39,7 @@ func SimulateMsgCreateValidator(m auth.AccountKeeper, k staking.Keeper) simulati
|
|||
}
|
||||
|
||||
if amount.Equal(sdk.ZeroInt()) {
|
||||
return noOperation, nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
|
||||
selfDelegation := sdk.NewCoin(denom, amount)
|
||||
|
@ -51,20 +47,17 @@ func SimulateMsgCreateValidator(m auth.AccountKeeper, k staking.Keeper) simulati
|
|||
selfDelegation, description, commission, sdk.OneInt())
|
||||
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := handler(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := handler(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
|
||||
event(fmt.Sprintf("staking/MsgCreateValidator/%v", result.IsOK()))
|
||||
|
||||
// require.True(t, result.IsOK(), "expected OK result but instead got %v", result)
|
||||
action = fmt.Sprintf("TestMsgCreateValidator: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,8 +65,7 @@ func SimulateMsgCreateValidator(m auth.AccountKeeper, k staking.Keeper) simulati
|
|||
func SimulateMsgEditValidator(k staking.Keeper) simulation.Operation {
|
||||
handler := staking.NewHandler(k)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
description := staking.Description{
|
||||
Moniker: simulation.RandStringOfLength(r, 10),
|
||||
|
@ -83,7 +75,7 @@ func SimulateMsgEditValidator(k staking.Keeper) simulation.Operation {
|
|||
}
|
||||
|
||||
if len(k.GetAllValidators(ctx)) == 0 {
|
||||
return noOperation, nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
val := keeper.RandomValidator(r, k, ctx)
|
||||
address := val.GetOperator()
|
||||
|
@ -92,16 +84,15 @@ func SimulateMsgEditValidator(k staking.Keeper) simulation.Operation {
|
|||
msg := staking.NewMsgEditValidator(address, description, &newCommissionRate, nil)
|
||||
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := handler(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := handler(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("staking/MsgEditValidator/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgEditValidator: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,12 +100,11 @@ func SimulateMsgEditValidator(k staking.Keeper) simulation.Operation {
|
|||
func SimulateMsgDelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Operation {
|
||||
handler := staking.NewHandler(k)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
denom := k.GetParams(ctx).BondDenom
|
||||
if len(k.GetAllValidators(ctx)) == 0 {
|
||||
return noOperation, nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
val := keeper.RandomValidator(r, k, ctx)
|
||||
validatorAddress := val.GetOperator()
|
||||
|
@ -125,23 +115,22 @@ func SimulateMsgDelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Oper
|
|||
amount = simulation.RandomAmount(r, amount)
|
||||
}
|
||||
if amount.Equal(sdk.ZeroInt()) {
|
||||
return noOperation, nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
|
||||
msg := staking.NewMsgDelegate(
|
||||
delegatorAddress, validatorAddress, sdk.NewCoin(denom, amount))
|
||||
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := handler(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := handler(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("staking/MsgDelegate/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgDelegate: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,20 +138,19 @@ func SimulateMsgDelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Oper
|
|||
func SimulateMsgUndelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Operation {
|
||||
handler := staking.NewHandler(k)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
delegatorAcc := simulation.RandomAcc(r, accs)
|
||||
delegatorAddress := delegatorAcc.Address
|
||||
delegations := k.GetAllDelegatorDelegations(ctx, delegatorAddress)
|
||||
if len(delegations) == 0 {
|
||||
return noOperation, nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
delegation := delegations[r.Intn(len(delegations))]
|
||||
|
||||
numShares := simulation.RandomDecAmount(r, delegation.Shares)
|
||||
if numShares.Equal(sdk.ZeroDec()) {
|
||||
return noOperation, nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
msg := staking.MsgUndelegate{
|
||||
DelegatorAddress: delegatorAddress,
|
||||
|
@ -170,17 +158,16 @@ func SimulateMsgUndelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Op
|
|||
SharesAmount: numShares,
|
||||
}
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s, got error %v",
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s, got error %v",
|
||||
msg.GetSignBytes(), msg.ValidateBasic())
|
||||
}
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := handler(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := handler(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("staking/MsgUndelegate/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgUndelegate: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,12 +175,11 @@ func SimulateMsgUndelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Op
|
|||
func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Operation {
|
||||
handler := staking.NewHandler(k)
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
accs []simulation.Account, event func(string)) (
|
||||
action string, fOp []simulation.FutureOperation, err error) {
|
||||
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
denom := k.GetParams(ctx).BondDenom
|
||||
if len(k.GetAllValidators(ctx)) == 0 {
|
||||
return noOperation, nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
srcVal := keeper.RandomValidator(r, k, ctx)
|
||||
srcValidatorAddress := srcVal.GetOperator()
|
||||
|
@ -207,7 +193,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k staking.Keeper) simulati
|
|||
amount = simulation.RandomAmount(r, amount)
|
||||
}
|
||||
if amount.Equal(sdk.ZeroInt()) {
|
||||
return noOperation, nil, nil
|
||||
return simulation.NoOpMsg(), nil, nil
|
||||
}
|
||||
msg := staking.MsgBeginRedelegate{
|
||||
DelegatorAddress: delegatorAddress,
|
||||
|
@ -216,15 +202,14 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k staking.Keeper) simulati
|
|||
SharesAmount: amount.ToDec(),
|
||||
}
|
||||
if msg.ValidateBasic() != nil {
|
||||
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := handler(ctx, msg)
|
||||
if result.IsOK() {
|
||||
ok := handler(ctx, msg).IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("staking/MsgBeginRedelegate/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgBeginRedelegate: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil, nil
|
||||
opMsg = simulation.NewOperationMsg(msg, ok, "")
|
||||
return opMsg, nil, nil
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue