diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 3878166b3add..73e3e2e4a550 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -93,9 +93,8 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json func testAndRunTxs(app *GaiaApp) []simulation.Operation { return []simulation.Operation{ banksim.SimulateSingleInputMsgSend(app.accountMapper), - govsim.SimulateMsgSubmitProposal(app.govKeeper, app.stakeKeeper), + govsim.SimulateSubmittingVotingAndSlashingForProposal(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), diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index eca8accae05b..e89a040d1ef1 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -2,6 +2,7 @@ package simulation import ( "fmt" + "math" "math/rand" "testing" @@ -18,30 +19,89 @@ const ( denom = "steak" ) +// SimulateSubmittingVotingAndSlashingForProposal simulates creating a msg Submit Proposal +// voting on the proposal, and subsequently slashing the proposal. It is implemented using +// future operations. +// TODO: Vote more intelligently, so we can actually do some checks regarding votes passing or failing +// TODO: Actually check that validator slashings happened +func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { + handler := gov.NewHandler(k) + // The states are: + // column 1: All validators vote + // column 2: 90% vote + // column 3: 75% vote + // column 4: 40% vote + // column 5: 15% vote + // column 6: noone votes + // All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily, + // feel free to change. + numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ + {20, 10, 0, 0, 0, 0}, + {55, 50, 20, 10, 0, 0}, + {25, 25, 30, 25, 30, 15}, + {0, 15, 30, 25, 30, 30}, + {0, 0, 20, 30, 30, 30}, + {0, 0, 0, 10, 10, 25}, + }) + statePercentageArray := []float64{1, .9, .75, .4, .15, 0} + curNumVotesState := 1 + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { + // 1) submit proposal now + sender := simulation.RandomKey(r, keys) + msg := simulationCreateMsgSubmitProposal(tb, r, sender, log) + action = simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + proposalID := k.GetLastProposalID(ctx) + // 2) Schedule operations for votes + // 2.1) first pick a number of people to vote. + curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) + numVotes := int(math.Ceil(float64(len(keys)) * statePercentageArray[curNumVotesState])) + // 2.2) select who votes and when + whoVotes := r.Perm(len(keys)) + // didntVote := whoVotes[numVotes:] + whoVotes = whoVotes[:numVotes] + votingPeriod := k.GetVotingProcedure(ctx).VotingPeriod + fops := make([]simulation.FutureOperation, numVotes+1) + for i := 0; i < numVotes; i++ { + whenVote := ctx.BlockHeight() + r.Int63n(votingPeriod) + fops[i] = simulation.FutureOperation{int(whenVote), operationSimulateMsgVote(k, sk, keys[whoVotes[i]], proposalID)} + } + // 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant) + // 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 + } +} + // SimulateMsgSubmitProposal simulates a msg Submit Proposal // Note: Currently doesn't ensure that the proposal txt is in JSON form func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { handler := gov.NewHandler(k) return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { - msg := simulationCreateMsgSubmitProposal(tb, r, keys, log) - ctx, write := ctx.CacheContext() - result := handler(ctx, msg) - if result.IsOK() { - // Update pool to keep invariants - pool := sk.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(msg.InitialDeposit.AmountOf(denom))) - sk.SetPool(ctx, pool) - write() - } - event(fmt.Sprintf("gov/MsgSubmitProposal/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + sender := simulation.RandomKey(r, keys) + msg := simulationCreateMsgSubmitProposal(tb, r, sender, log) + action = simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) return action, nil, nil } } -func simulationCreateMsgSubmitProposal(tb testing.TB, r *rand.Rand, keys []crypto.PrivKey, log string) gov.MsgSubmitProposal { - key := simulation.RandomKey(r, keys) - addr := sdk.AccAddress(key.PubKey().Address()) +func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, sk stake.Keeper, handler sdk.Handler, ctx sdk.Context, event func(string)) (action string) { + ctx, write := ctx.CacheContext() + result := handler(ctx, msg) + if result.IsOK() { + // Update pool to keep invariants + pool := sk.GetPool(ctx) + pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(msg.InitialDeposit.AmountOf(denom))) + sk.SetPool(ctx, pool) + write() + } + event(fmt.Sprintf("gov/MsgSubmitProposal/%v", result.IsOK())) + action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action +} + +func simulationCreateMsgSubmitProposal(tb testing.TB, r *rand.Rand, sender crypto.PrivKey, log string) gov.MsgSubmitProposal { + addr := sdk.AccAddress(sender.PubKey().Address()) deposit := randomDeposit(r) msg := gov.NewMsgSubmitProposal( simulation.RandStringOfLength(r, 5), @@ -87,13 +147,22 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { // SimulateMsgVote func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation { + return operationSimulateMsgVote(k, sk, nil, -1) +} + +func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, key crypto.PrivKey, proposalID int64) simulation.Operation { return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { - key := simulation.RandomKey(r, keys) - addr := sdk.AccAddress(key.PubKey().Address()) - proposalID, ok := randomProposalID(r, k, ctx) - if !ok { - return "no-operation", nil, nil + if key == nil { + key = simulation.RandomKey(r, keys) + } + var ok bool + if proposalID < 0 { + proposalID, ok = randomProposalID(r, k, ctx) + if !ok { + return "no-operation", nil, nil + } } + addr := sdk.AccAddress(key.PubKey().Address()) option := randomVotingOption(r) msg := gov.NewMsgVote(addr, proposalID, option) if msg.ValidateBasic() != nil { diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index 6f0f9830cd32..540dcb4473cc 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -53,6 +53,7 @@ func TestGovWithRandomMessages(t *testing.T) { gov.InitGenesis(ctx, govKeeper, gov.DefaultGenesisState()) } + // Test with unscheduled votes simulation.Simulate( t, mapp.BaseApp, appStateFn, []simulation.Operation{ @@ -66,4 +67,18 @@ func TestGovWithRandomMessages(t *testing.T) { }, 10, 100, false, ) + + // Test with scheduled votes + simulation.Simulate( + t, mapp.BaseApp, appStateFn, + []simulation.Operation{ + SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper), + SimulateMsgDeposit(govKeeper, stakeKeeper), + }, []simulation.RandSetup{ + setup, + }, []simulation.Invariant{ + AllInvariants(), + }, 10, 100, + false, + ) }