Merge PR #3819: Simulation Refactor

This commit is contained in:
frog power 4000 2019-03-14 14:13:15 -04:00 committed by Christopher Goes
parent 48b6b3884f
commit f0d1efa43c
22 changed files with 582 additions and 528 deletions

View File

@ -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)

View File

@ -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

View File

@ -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
}
}

View File

@ -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))

View File

@ -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

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

70
x/simulation/log.go Normal file
View File

@ -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() {}

251
x/simulation/operation.go Normal file
View File

@ -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
}
}

View File

@ -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

View File

@ -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()
}

View File

@ -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.

View File

@ -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
}
}

View File

@ -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
}
}