From 8bb79d12ca0d905f34d734e59479cfba5e6775d1 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Thu, 16 Aug 2018 14:45:07 -0700 Subject: [PATCH] Fix non-deterministic map iteration in fuzzer (#2069) * This demonstrates that the state machine is non-deterministic if there are more than two txs in a block. * fix non-deterministic map iteration * (squash this) fix build errors * (squash this) iterate using range --- cmd/gaia/app/sim_test.go | 40 ++++++++++++--------- x/bank/simulation/sim_test.go | 1 + x/gov/simulation/sim_test.go | 1 + x/mock/simulation/random_simulate_blocks.go | 25 ++++++++++--- x/stake/simulation/sim_test.go | 1 + 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 5d0edcba2..3b85a89cf 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -130,17 +130,13 @@ func TestFullGaiaSimulation(t *testing.T) { }, numBlocks, blockSize, + false, ) } -// TODO: Make this not depend on Gaia or any of the modules, -// and place it in random_simulation_test.go -// -// Test doesn't use `app.ExportAppStateAndValidators` as that panics with the following: -// panic: Stored pool should not have been nil [recovered] -// panic: Stored pool should not have been nil -// Change to `app.ExportAppStateAndValidators` once it is fixed +// TODO: Make another test for the fuzzer itself, which just has noOp txs +// and doesn't depend on gaia func TestAppStateDeterminism(t *testing.T) { numTimesToRun := 5 appHashList := make([]json.RawMessage, numTimesToRun) @@ -152,27 +148,39 @@ func TestAppStateDeterminism(t *testing.T) { app := NewGaiaApp(logger, db, nil) noOpInvariant := func(t *testing.T, baseapp *baseapp.BaseApp, log string) {} - noOpTestAndRunTx := func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - privKeys []crypto.PrivKey, log string, event func(string), - ) (action string, err sdk.Error) { - return "", nil - } + // noOpTestAndRunTx := func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + // privKeys []crypto.PrivKey, log string, event func(string), + // ) (action string, err sdk.Error) { + // return "", nil + // } // Run randomized simulation simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, []simulation.TestAndRunTx{ - noOpTestAndRunTx, + banksim.TestAndRunSingleInputMsgSend(app.accountMapper), + govsim.SimulateMsgSubmitProposal(app.govKeeper, app.stakeKeeper), + govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper), + govsim.SimulateMsgVote(app.govKeeper, app.stakeKeeper), + stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgEditValidator(app.stakeKeeper), + stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper), + stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper), + slashingsim.SimulateMsgUnrevoke(app.slashingKeeper), }, []simulation.RandSetup{}, []simulation.Invariant{noOpInvariant}, - 0, - 10, + 20, + 20, + true, ) appHash := app.LastCommitID().Hash appHashList[i] = appHash } for i := 1; i < numTimesToRun; i++ { - require.Equal(t, appHashList[0], appHashList[i]) + require.Equal(t, appHashList[0], appHashList[i], "appHashes: %v", appHashList) } } diff --git a/x/bank/simulation/sim_test.go b/x/bank/simulation/sim_test.go index 49e3dfa92..8fedeca79 100644 --- a/x/bank/simulation/sim_test.go +++ b/x/bank/simulation/sim_test.go @@ -42,5 +42,6 @@ func TestBankWithRandomMessages(t *testing.T) { TotalCoinsInvariant(mapper, func() sdk.Coins { return mapp.TotalCoinsSupply }), }, 30, 30, + false, ) } diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index e7131f8fc..691aa1cd2 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -64,5 +64,6 @@ func TestGovWithRandomMessages(t *testing.T) { }, []simulation.Invariant{ AllInvariants(), }, 10, 100, + false, ) } diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 1b6953631..ffc00500e 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "math/rand" + "sort" "testing" "time" @@ -20,17 +21,17 @@ import ( // Simulate tests application by sending random messages. func Simulate( t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, ops []TestAndRunTx, setups []RandSetup, - invariants []Invariant, numBlocks int, blockSize int, + invariants []Invariant, numBlocks int, blockSize int, commit bool, ) { time := time.Now().UnixNano() - SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize) + SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) } // SimulateFromSeed tests an application by running the provided // operations, testing the provided invariants, but using the provided seed. func SimulateFromSeed( t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []TestAndRunTx, setups []RandSetup, - invariants []Invariant, numBlocks int, blockSize int, + invariants []Invariant, numBlocks int, blockSize int, commit bool, ) { log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed)) fmt.Printf("%s\n", log) @@ -104,6 +105,9 @@ func SimulateFromSeed( } res := app.EndBlock(abci.RequestEndBlock{}) + if commit { + app.Commit() + } header.Height++ header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) @@ -123,6 +127,17 @@ func SimulateFromSeed( DisplayEvents(events) } +func getKeys(validators map[string]mockValidator) []string { + keys := make([]string, len(validators)) + i := 0 + for key := range validators { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + // RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64, pastTimes []time.Time, event func(string), header abci.Header, log string) abci.RequestBeginBlock { @@ -131,7 +146,9 @@ func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]m } signingValidators := make([]abci.SigningValidator, len(validators)) i := 0 - for _, mVal := range validators { + + for _, key := range getKeys(validators) { + mVal := validators[key] mVal.livenessState = livenessTransitions.NextState(r, mVal.livenessState) signed := true diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 3e80e1eb3..84745cd1d 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -57,5 +57,6 @@ func TestStakeWithRandomMessages(t *testing.T) { }, []simulation.Invariant{ AllInvariants(coinKeeper, stakeKeeper, mapp.AccountMapper), }, 10, 100, + false, ) }