cosmos-sdk/x/gov/simulation/msgs.go

218 lines
7.0 KiB
Go

package simulation
import (
"fmt"
"math"
"math/rand"
"time"
"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/simulation"
)
// 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) 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(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 simulation.NoOpMsg(), nil, err
}
ok := simulateHandleMsgSubmitProposal(msg, handler, ctx)
opMsg = simulation.NewOperationMsg(msg, ok, "")
// don't schedule votes if proposal failed
if !ok {
return opMsg, nil, nil
}
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(accs)) * statePercentageArray[curNumVotesState]))
// 2.2) select who votes and when
whoVotes := r.Perm(len(accs))
// didntVote := whoVotes[numVotes:]
whoVotes = whoVotes[:numVotes]
votingPeriod := k.GetVotingParams(ctx).VotingPeriod
fops := make([]simulation.FutureOperation, numVotes+1)
for i := 0; i < numVotes; i++ {
whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second)
fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, accs[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 opMsg, 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) simulation.Operation {
handler := gov.NewHandler(k)
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 simulation.NoOpMsg(), nil, err
}
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) (ok bool) {
ctx, write := ctx.CacheContext()
ok = handler(ctx, msg).IsOK()
if ok {
write()
}
return ok
}
func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account) (msg gov.MsgSubmitProposal, err error) {
deposit := randomDeposit(r)
msg = gov.NewMsgSubmitProposal(
simulation.RandStringOfLength(r, 5),
simulation.RandStringOfLength(r, 5),
gov.ProposalTypeText,
sender.Address,
deposit,
)
if msg.ValidateBasic() != nil {
err = fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
return
}
// SimulateMsgDeposit
func SimulateMsgDeposit(k gov.Keeper) simulation.Operation {
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 simulation.NoOpMsg(), nil, nil
}
deposit := randomDeposit(r)
msg := gov.NewMsgDeposit(acc.Address, proposalID, deposit)
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok = gov.NewHandler(k)(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// SimulateMsgVote
// nolint: unparam
func SimulateMsgVote(k gov.Keeper) simulation.Operation {
return operationSimulateMsgVote(k, simulation.Account{}, 0)
}
// 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) (
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
if acc.Equals(simulation.Account{}) {
acc = simulation.RandomAcc(r, accs)
}
if proposalID < 0 {
var ok bool
proposalID, ok = randomProposalID(r, k, ctx)
if !ok {
return simulation.NoOpMsg(), nil, nil
}
}
option := randomVotingOption(r)
msg := gov.NewMsgVote(acc.Address, proposalID, option)
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := gov.NewHandler(k)(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// Pick a random deposit
func randomDeposit(r *rand.Rand) sdk.Coins {
// TODO Choose based on account balance and min deposit
amount := int64(r.Intn(20)) + 1
return sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, amount)}
}
// Pick a random proposal ID
func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID uint64, ok bool) {
lastProposalID := k.GetLastProposalID(ctx)
if lastProposalID < 1 || lastProposalID == (2<<63-1) {
return 0, false
}
proposalID = uint64(r.Intn(1+int(lastProposalID)) - 1)
return proposalID, true
}
// Pick a random voting option
func randomVotingOption(r *rand.Rand) gov.VoteOption {
switch r.Intn(4) {
case 0:
return gov.OptionYes
case 1:
return gov.OptionAbstain
case 2:
return gov.OptionNo
case 3:
return gov.OptionNoWithVeto
}
panic("should not happen")
}