diff --git a/.pending/improvements/gaia/3819-Simulation-refa b/.pending/improvements/gaia/3819-Simulation-refa new file mode 100644 index 000000000000..bc4531f962bc --- /dev/null +++ b/.pending/improvements/gaia/3819-Simulation-refa @@ -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) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index beb2647aeecb..d1211a460334 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -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 diff --git a/x/auth/simulation/fake.go b/x/auth/simulation/fake.go index f28c27cd9285..7849bd807848 100644 --- a/x/auth/simulation/fake.go +++ b/x/auth/simulation/fake.go @@ -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 } } diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go index 1e4c529a9e69..69ca7f99e8a8 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -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") - - return action, nil, nil - } -} +func createMsgSend(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, mapper auth.AccountKeeper) ( + fromAcc simulation.Account, comment string, msg bank.MsgSend, ok bool) { -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)) diff --git a/x/distribution/abci_app.go b/x/distribution/abci_app.go index aae399062320..0a2ef5627a90 100644 --- a/x/distribution/abci_app.go +++ b/x/distribution/abci_app.go @@ -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 diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go index 50dcd2e93d79..df5be3b9234b 100644 --- a/x/distribution/keeper/allocation.go +++ b/x/distribution/keeper/allocation.go @@ -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) } diff --git a/x/distribution/simulation/msgs.go b/x/distribution/simulation/msgs.go index 9f979d86d47c..ba610343dacd 100644 --- a/x/distribution/simulation/msgs.go +++ b/x/distribution/simulation/msgs.go @@ -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 } } diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index 4c5eddd372bc..5541fb2bd228 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -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 } } diff --git a/x/mock/simulation/operation.go b/x/mock/simulation/operation.go deleted file mode 100644 index 00237e03ee31..000000000000 --- a/x/mock/simulation/operation.go +++ /dev/null @@ -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 - } -} diff --git a/x/mock/simulation/account.go b/x/simulation/account.go similarity index 100% rename from x/mock/simulation/account.go rename to x/simulation/account.go diff --git a/x/mock/simulation/doc.go b/x/simulation/doc.go similarity index 100% rename from x/mock/simulation/doc.go rename to x/simulation/doc.go diff --git a/x/mock/simulation/event_stats.go b/x/simulation/event_stats.go similarity index 100% rename from x/mock/simulation/event_stats.go rename to x/simulation/event_stats.go diff --git a/x/simulation/log.go b/x/simulation/log.go new file mode 100644 index 000000000000..9bdb76fded8c --- /dev/null +++ b/x/simulation/log.go @@ -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() {} diff --git a/x/mock/simulation/mock_tendermint.go b/x/simulation/mock_tendermint.go similarity index 100% rename from x/mock/simulation/mock_tendermint.go rename to x/simulation/mock_tendermint.go diff --git a/x/simulation/operation.go b/x/simulation/operation.go new file mode 100644 index 000000000000..31bedc758430 --- /dev/null +++ b/x/simulation/operation.go @@ -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 + } +} diff --git a/x/mock/simulation/params.go b/x/simulation/params.go similarity index 100% rename from x/mock/simulation/params.go rename to x/simulation/params.go diff --git a/x/mock/simulation/rand_util.go b/x/simulation/rand_util.go similarity index 89% rename from x/mock/simulation/rand_util.go rename to x/simulation/rand_util.go index 1a5e4566f652..a9b32ca48dd4 100644 --- a/x/mock/simulation/rand_util.go +++ b/x/simulation/rand_util.go @@ -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 diff --git a/x/mock/simulation/simulate.go b/x/simulation/simulate.go similarity index 79% rename from x/mock/simulation/simulate.go rename to x/simulation/simulate.go index e67bddc3c44d..3eb25ccbc626 100644 --- a/x/mock/simulation/simulate.go +++ b/x/simulation/simulate.go @@ -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() } diff --git a/x/mock/simulation/transition_matrix.go b/x/simulation/transition_matrix.go similarity index 100% rename from x/mock/simulation/transition_matrix.go rename to x/simulation/transition_matrix.go diff --git a/x/mock/simulation/util.go b/x/simulation/util.go similarity index 60% rename from x/mock/simulation/util.go rename to x/simulation/util.go index 962dbfe43a4f..14df0f6c648c 100644 --- a/x/mock/simulation/util.go +++ b/x/simulation/util.go @@ -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. diff --git a/x/slashing/simulation/msgs.go b/x/slashing/simulation/msgs.go index 732b3c28c4e9..9aaa8382d204 100644 --- a/x/slashing/simulation/msgs.go +++ b/x/slashing/simulation/msgs.go @@ -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 } } diff --git a/x/staking/simulation/msgs.go b/x/staking/simulation/msgs.go index 559b0ae96dac..05241dbdccab 100644 --- a/x/staking/simulation/msgs.go +++ b/x/staking/simulation/msgs.go @@ -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 } }