diff --git a/simulation/simulation_test.go b/simulation/simulation_test.go index 30dc4fcf95..c0e25f6067 100644 --- a/simulation/simulation_test.go +++ b/simulation/simulation_test.go @@ -250,7 +250,7 @@ func TestFullAppSimulation(t *testing.T) { zetasimulation.PrintStats(db) } -// TestFullAppSimulationAfterImport tests the application simulation after importing the state exported from a previous.At a high level,it does the following +// TestAppImportExport tests the application simulation after importing the state exported from a previous.At a high level,it does the following // 1. It runs a full simulation and exports the state // 2. It creates a new app, and db // 3. It imports the exported state into the new app diff --git a/x/crosschain/simulation/operation_vote_outbound.go b/x/crosschain/simulation/operation_vote_outbound.go index c9205f2b23..001cbcfafb 100644 --- a/x/crosschain/simulation/operation_vote_outbound.go +++ b/x/crosschain/simulation/operation_vote_outbound.go @@ -140,7 +140,7 @@ func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { err = firstMsg.ValidateBasic() if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first inbound vote"), nil, err + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first outbound vote"), nil, err } tx, err := simtestutil.GenSignedMockTx( @@ -170,7 +170,7 @@ func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { // Add subsequent votes observerSet, found := k.GetObserverKeeper().GetObserverSet(ctx) if !found { - return simtypes.NoOpMsg(types.ModuleName, authz.InboundVoter.String(), "observer set not found"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, authz.OutboundVoter.String(), "observer set not found"), nil, nil } // 1) Schedule operations for votes diff --git a/x/observer/keeper/msg_server_vote_tss.go b/x/observer/keeper/msg_server_vote_tss.go index 9b39174fd4..9c796ba5bb 100644 --- a/x/observer/keeper/msg_server_vote_tss.go +++ b/x/observer/keeper/msg_server_vote_tss.go @@ -27,8 +27,7 @@ const voteTSSid = "Vote TSS" func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types.MsgVoteTSSResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - // Checks whether a signer is authorized to sign, by checking their address against the observer mapper - // which contains the observer list for the chain and type. + // Checks whether a signer is authorized to sign, by checking if the signer has a node account. _, found := k.GetNodeAccount(ctx, msg.Creator) if !found { return nil, errorsmod.Wrapf( @@ -80,6 +79,12 @@ func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types return &types.MsgVoteTSSResponse{}, errorsmod.Wrap(err, voteTSSid) } + //if ctx.BlockHeight() == 3 || ctx.BlockHeight() == 4 { + // fmt.Println("Vote added", ctx.BlockHeight(), msg.TssPubkey) + // fmt.Println("Votes", ballot.Votes) + // fmt.Println("VoterList Length", len(ballot.VoterList)) + //} + ballot, isFinalized := k.CheckIfFinalizingVote(ctx, ballot) if !isFinalized { return &types.MsgVoteTSSResponse{ @@ -89,6 +94,8 @@ func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types }, nil } + //fmt.Println("Ballot finalized", ballot.BallotStatus) + // The ballot is finalized, we check if this is the correct ballot for updating the TSS // The requirements are // 1. The keygen is still pending @@ -104,7 +111,7 @@ func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types }, nil } - // For cases when an observer tries to vote for an older pending ballot, associated with a keygen that was discarded , we would return at this check while still adding the vote to the ballot + // For cases when an observer tries to vote for an older pending ballot, associated with a keygen that was discarded, we would return at this check while still adding the vote to the ballot if msg.KeygenZetaHeight != keygen.BlockNumber { return &types.MsgVoteTSSResponse{ VoteFinalized: isFinalized, diff --git a/x/observer/simulation/operation_add_observer.go b/x/observer/simulation/operation_add_observer.go index ea1fbb274c..ca88d3bc32 100644 --- a/x/observer/simulation/operation_add_observer.go +++ b/x/observer/simulation/operation_add_observer.go @@ -14,8 +14,8 @@ import ( "github.com/zeta-chain/node/x/observer/types" ) -// SimulateMsgAddObserver generates a TypeMsgAddObserver and delivers it. The message adds an observer to the observer set -func SimulateMsgAddObserver(k keeper.Keeper) simtypes.Operation { +// SimulateAddObserver generates a TypeMsgAddObserver and delivers it. The message adds an observer to the observer set +func SimulateAddObserver(k keeper.Keeper) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) @@ -44,14 +44,13 @@ func SimulateMsgAddObserver(k keeper.Keeper) simtypes.Operation { // Pick a random observer which part of the node account but not in the observer set var newObserver string - foundNA := false - for i := 0; i < 10; i++ { + foundNA := RepeatCheck(func() bool { newObserver = nodeAccounts[r.Intn(len(nodeAccounts))].Operator if _, found := observerMap[newObserver]; !found { - foundNA = true - break + return true } - } + return false + }) if !foundNA { return simtypes.NoOpMsg( types.ModuleName, diff --git a/x/observer/simulation/operation_add_observer_node_account.go b/x/observer/simulation/operation_add_observer_node_account.go index 4335182ab3..079665b88d 100644 --- a/x/observer/simulation/operation_add_observer_node_account.go +++ b/x/observer/simulation/operation_add_observer_node_account.go @@ -14,8 +14,8 @@ import ( "github.com/zeta-chain/node/x/observer/types" ) -// SimulateMsgAddObserverNodeAccount generates a TypeMsgAddObserver and delivers it. -func SimulateMsgAddObserverNodeAccount(k keeper.Keeper) simtypes.Operation { +// SimulateAddObserverNodeAccount generates a TypeMsgAddObserver and delivers it. +func SimulateAddObserverNodeAccount(k keeper.Keeper) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) @@ -49,22 +49,29 @@ func SimulateMsgAddObserverNodeAccount(k keeper.Keeper) simtypes.Operation { ), nil, nil } newObserver := "" - for { + foundNewObserver := RepeatCheck(func() bool { randomValidator := validators[r.Intn(len(validators))] - randomValidatorOperatorAddress, err := types.GetAccAddressFromOperatorAddress( - randomValidator.OperatorAddress, - ) + randomValidatorAddress, err := types.GetAccAddressFromOperatorAddress(randomValidator.OperatorAddress) if err != nil { - continue + return false } - newObserver = randomValidatorOperatorAddress.String() + newObserver = randomValidatorAddress.String() err = k.IsValidator(ctx, newObserver) if err != nil { - continue + return false } if _, ok := observerMap[newObserver]; !ok { - break + return true } + return false + }) + + if !foundNewObserver { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAddObserver, + "no new observer found", + ), nil, nil } msg := types.MsgAddObserver{ diff --git a/x/observer/simulation/operation_disable_cctx.go b/x/observer/simulation/operation_disable_cctx.go index 235b7d2947..324a6ead76 100644 --- a/x/observer/simulation/operation_disable_cctx.go +++ b/x/observer/simulation/operation_disable_cctx.go @@ -13,8 +13,8 @@ import ( "github.com/zeta-chain/node/x/observer/types" ) -// SimulateMsgDisableCCTX generates a MsgDisableCCTX and delivers it. -func SimulateMsgDisableCCTX(k keeper.Keeper) simtypes.Operation { +// SimulateDisableCCTX generates a MsgDisableCCTX and delivers it. +func SimulateDisableCCTX(k keeper.Keeper) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) diff --git a/x/observer/simulation/operation_enable_cctx.go b/x/observer/simulation/operation_enable_cctx.go index 491f88c45d..3e963caa15 100644 --- a/x/observer/simulation/operation_enable_cctx.go +++ b/x/observer/simulation/operation_enable_cctx.go @@ -13,8 +13,8 @@ import ( "github.com/zeta-chain/node/x/observer/types" ) -// SimulateMsgEnableCCTX generates a MsgEnableCCTX and delivers it. -func SimulateMsgEnableCCTX(k keeper.Keeper) simtypes.Operation { +// SimulateEnableCCTX generates a MsgEnableCCTX and delivers it. +func SimulateEnableCCTX(k keeper.Keeper) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) diff --git a/x/observer/simulation/operation_remove_chain_params.go b/x/observer/simulation/operation_remove_chain_params.go index f4ae45c204..3f81e11324 100644 --- a/x/observer/simulation/operation_remove_chain_params.go +++ b/x/observer/simulation/operation_remove_chain_params.go @@ -34,19 +34,27 @@ func SimulateMsgRemoveChainParams(k keeper.Keeper) simtypes.Operation { ), nil, nil } - randomChainId := int64(0) - // remove zeta chain from the supported chains - for { + randomExternalChain := int64(0) + foundExternalChain := RepeatCheck(func() bool { c := supportedChains[r.Intn(len(supportedChains))] if !c.IsZetaChain() { - randomChainId = c.ChainId - break + randomExternalChain = c.ChainId + return true } + return false + }) + + if !foundExternalChain { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgRemoveChainParams, + "no external chain found", + ), nil, nil } msg := types.MsgRemoveChainParams{ Creator: policyAccount.Address.String(), - ChainId: randomChainId, + ChainId: randomExternalChain, } err = msg.ValidateBasic() diff --git a/x/observer/simulation/operation_reset_chain_nonces.go b/x/observer/simulation/operation_reset_chain_nonces.go index b48ead0528..709490aafa 100644 --- a/x/observer/simulation/operation_reset_chain_nonces.go +++ b/x/observer/simulation/operation_reset_chain_nonces.go @@ -14,8 +14,8 @@ import ( "github.com/zeta-chain/node/x/observer/types" ) -// SimulateMsgResetChainNonces generates a MsgResetChainNonces and delivers it. -func SimulateMsgResetChainNonces(k keeper.Keeper) simtypes.Operation { +// SimulateResetChainNonces generates a MsgResetChainNonces and delivers it. +func SimulateResetChainNonces(k keeper.Keeper) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) @@ -26,7 +26,7 @@ func SimulateMsgResetChainNonces(k keeper.Keeper) simtypes.Operation { authAccount := k.GetAuthKeeper().GetAccount(ctx, policyAccount.Address) spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) - randomChain, err := GetExternalChain(ctx, k, r, 10) + randomChain, err := GetExternalChain(ctx, k, r) if err != nil { return simtypes.NoOpMsg( types.ModuleName, diff --git a/x/observer/simulation/operation_update_chain_params.go b/x/observer/simulation/operation_update_chain_params.go index 936895e2a3..ad4beb768c 100644 --- a/x/observer/simulation/operation_update_chain_params.go +++ b/x/observer/simulation/operation_update_chain_params.go @@ -14,8 +14,8 @@ import ( "github.com/zeta-chain/node/x/observer/types" ) -// SimulateMsgUpdateChainParams generates a MsgUpdateChainParams and delivers it. -func SimulateMsgUpdateChainParams(k keeper.Keeper) simtypes.Operation { +// SimulateUpdateChainParams generates a MsgUpdateChainParams and delivers it. +func SimulateUpdateChainParams(k keeper.Keeper) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) @@ -26,7 +26,7 @@ func SimulateMsgUpdateChainParams(k keeper.Keeper) simtypes.Operation { authAccount := k.GetAuthKeeper().GetAccount(ctx, policyAccount.Address) spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) - randomChain, err := GetExternalChain(ctx, k, r, 10) + randomChain, err := GetExternalChain(ctx, k, r) if err != nil { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateChainParams, err.Error()), nil, nil } diff --git a/x/observer/simulation/operation_update_gas_price_increase_flags.go b/x/observer/simulation/operation_update_gas_price_increase_flags.go index d9932c5146..fdb59d789d 100644 --- a/x/observer/simulation/operation_update_gas_price_increase_flags.go +++ b/x/observer/simulation/operation_update_gas_price_increase_flags.go @@ -14,8 +14,8 @@ import ( "github.com/zeta-chain/node/x/observer/types" ) -// SimulateMsgUpdateGasPriceIncreaseFlags generates a MsgUpdateGasPriceIncreaseFlags with random values -func SimulateMsgUpdateGasPriceIncreaseFlags(k keeper.Keeper) simtypes.Operation { +// SimulateUpdateGasPriceIncreaseFlags generates a MsgUpdateGasPriceIncreaseFlags with random values +func SimulateUpdateGasPriceIncreaseFlags(k keeper.Keeper) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) diff --git a/x/observer/simulation/operation_update_keygen.go b/x/observer/simulation/operation_update_keygen.go index bba3c0511c..c6718b0dcc 100644 --- a/x/observer/simulation/operation_update_keygen.go +++ b/x/observer/simulation/operation_update_keygen.go @@ -14,8 +14,8 @@ import ( "github.com/zeta-chain/node/x/observer/types" ) -// SimulateMsgUpdateKeygen generates a MsgUpdateKeygen and delivers it. -func SimulateMsgUpdateKeygen(k keeper.Keeper) simtypes.Operation { +// SimulateUpdateKeygen generates a MsgUpdateKeygen and delivers it. +func SimulateUpdateKeygen(k keeper.Keeper) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) diff --git a/x/observer/simulation/operation_update_observer.go b/x/observer/simulation/operation_update_observer.go index 75f6cec847..9b90fdae83 100644 --- a/x/observer/simulation/operation_update_observer.go +++ b/x/observer/simulation/operation_update_observer.go @@ -13,8 +13,8 @@ import ( "github.com/zeta-chain/node/x/observer/types" ) -// SimulateMsgUpdateObserver generates a TypeMsgUpdateObserver and delivers it. -func SimulateMsgUpdateObserver(k keeper.Keeper) simtypes.Operation { +// SimulateUpdateObserver generates a TypeMsgUpdateObserver and delivers it. +func SimulateUpdateObserver(k keeper.Keeper) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) @@ -40,21 +40,31 @@ func SimulateMsgUpdateObserver(k keeper.Keeper) simtypes.Operation { "no validators found", ), nil, nil } + newObserver := "" - for { + foundNewObserver := RepeatCheck(func() bool { randomValidator := validators[r.Intn(len(validators))] - nO, err := types.GetAccAddressFromOperatorAddress(randomValidator.OperatorAddress) + randomValidatorAddress, err := types.GetAccAddressFromOperatorAddress(randomValidator.OperatorAddress) if err != nil { - continue + return false } - newObserver = nO.String() + newObserver = randomValidatorAddress.String() err = k.IsValidator(ctx, newObserver) if err != nil { - continue + return false } if _, ok := observerMap[newObserver]; !ok { - break + return true } + return false + }) + + if !foundNewObserver { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgUpdateObserver, + "no new observer found", + ), nil, nil } lastBlockCount, found := k.GetLastObserverCount(ctx) diff --git a/x/observer/simulation/operation_vote_tss.go b/x/observer/simulation/operation_vote_tss.go new file mode 100644 index 0000000000..63002c7b72 --- /dev/null +++ b/x/observer/simulation/operation_vote_tss.go @@ -0,0 +1,178 @@ +package simulation + +import ( + "math" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/zeta-chain/node/testutil/sample" + + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/x/observer/keeper" + "github.com/zeta-chain/node/x/observer/types" +) + +func operationSimulateVoteTss( + k keeper.Keeper, + msg types.MsgVoteTSS, + simAccount simtypes.Account, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, _ []simtypes.Account, _ string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + // Fetch the account from the auth keeper which can then be used to fetch spendable coins + authAccount := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + // Generate a transaction with a random fee and deliver it + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + // Generate and deliver the transaction using the function defined by us instead of using the default function provided by the cosmos-sdk + // The main difference between the two functions is that the one defined by us does not error out if the vote fails. + // We need this behaviour as the votes are assigned to future operations, i.e., they are scheduled to be executed in a future block. We do not know at the time of scheduling if the vote will be successful or not. + // There might be multiple reasons for a vote to fail , like the observer not being present in the observer set, the observer not being an observer, etc. + return GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateVoteOutbound generates a MsgVoteOutbound with random values +// This is the only operation which saves a cctx directly to the store. +func SimulateMsgVoteTSS(k keeper.Keeper) simtypes.Operation { + + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + yesVote := chains.ReceiveStatus_success + noVote := chains.ReceiveStatus_failed + ballotVotesTransitionMatrix, yesVotePercentageArray, ballotVotesState := BallotVoteSimulationMatrix() + nodeAccounts := k.GetAllNodeAccount(ctx) + numVotes := len(nodeAccounts) + ballotVotesState = ballotVotesTransitionMatrix.NextState(r, ballotVotesState) + yesVotePercentage := yesVotePercentageArray[ballotVotesState] + numberOfYesVotes := int(math.Ceil(float64(numVotes) * yesVotePercentage)) + + vote := yesVote + if numberOfYesVotes == 0 { + vote = noVote + } + + newTss, err := sample.TSSFromRand(r) + if err != nil { + return simtypes.OperationMsg{}, nil, err + } + + keygen, found := k.GetKeygen(ctx) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgVoteTSS, "keygen not found"), nil, nil + } + + msg := types.MsgVoteTSS{ + Creator: "", + TssPubkey: newTss.TssPubkey, + KeygenZetaHeight: keygen.BlockNumber, + Status: vote, + } + + // Pick a random observer to create the ballot + // If this returns an error, it is likely that the entire observer set has been removed + simAccount, firstVoter, err := GetRandomNodeAccount(r, ctx, k, accs) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig + account := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + + firstMsg := msg + firstMsg.Creator = firstVoter + + // THe first vote should always create a new ballot + _, found = k.GetBallot(ctx, firstMsg.Digest()) + if found { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "ballot already exists"), nil, nil + } + + err = firstMsg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first tss vote"), nil, err + } + + tx, err := simtestutil.GenSignedMockTx( + r, + txGen, + []sdk.Msg{&firstMsg}, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, + simtestutil.DefaultGenTxGas, + chainID, + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + simAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + } + + // We can return error here as we can guarantee that the first vote will be successful. + // Since we query the observer set before adding votes + _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + opMsg := simtypes.NewOperationMsg(&msg, true, "", nil) + + var fops []simtypes.FutureOperation + + for voteCount, nodeAccount := range nodeAccounts { + if vote == yesVote && voteCount == numberOfYesVotes { + vote = noVote + } + // firstVoter has already voted. + if nodeAccount.Operator == firstVoter { + continue + } + observerAccount, err := GetSimAccount(nodeAccount.Operator, accs) + if err != nil { + continue + } + // 1.3) schedule the vote + votingMsg := msg + votingMsg.Creator = nodeAccount.Operator + votingMsg.Status = vote + + e := votingMsg.ValidateBasic() + if e != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate voting msg"), nil, e + } + + fops = append(fops, simtypes.FutureOperation{ + // Submit all subsequent votes in the next block. + // We can consider adding a random block height between 1 and ballot maturity blocks in the future. + BlockHeight: int(ctx.BlockHeight() + 1), + Op: operationSimulateVoteTss(k, votingMsg, observerAccount), + }) + } + return opMsg, fops, nil + } +} diff --git a/x/observer/simulation/operations.go b/x/observer/simulation/operations.go index f610695e85..558bb485f2 100644 --- a/x/observer/simulation/operations.go +++ b/x/observer/simulation/operations.go @@ -5,6 +5,7 @@ import ( "math/rand" "github.com/cosmos/cosmos-sdk/codec" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -46,6 +47,8 @@ const ( DefaultWeightMsgTypeMsgResetChainNonces = 5 DefaultWeightMsgTypeMsgUpdateGasPriceIncreaseFlags = 10 DefaultWeightMsgTypeMsgAddObserver = 5 + + DefaultRetryCount = 10 ) // WeightedOperations for observer module @@ -123,22 +126,22 @@ func WeightedOperations( return simulation.WeightedOperations{ simulation.NewWeightedOperation( weightMsgTypeMsgEnableCCTX, - SimulateMsgEnableCCTX(k), + SimulateEnableCCTX(k), ), simulation.NewWeightedOperation( weightMsgTypeMsgDisableCCTX, - SimulateMsgDisableCCTX(k), + SimulateDisableCCTX(k), ), simulation.NewWeightedOperation( weightMsgTypeMsgUpdateKeygen, - SimulateMsgUpdateKeygen(k), + SimulateUpdateKeygen(k), ), // simulation.NewWeightedOperation( weightMsgTypeMsgUpdateChainParams, - SimulateMsgUpdateChainParams(k), + SimulateUpdateChainParams(k), ), // //simulation.NewWeightedOperation( @@ -148,27 +151,32 @@ func WeightedOperations( simulation.NewWeightedOperation( weightMsgTypeMsgResetChainNonces, - SimulateMsgResetChainNonces(k), + SimulateResetChainNonces(k), ), simulation.NewWeightedOperation( weightMsgTypeMsgUpdateGasPriceIncreaseFlags, - SimulateMsgUpdateGasPriceIncreaseFlags(k), + SimulateUpdateGasPriceIncreaseFlags(k), ), simulation.NewWeightedOperation( weightMsgTypeMsgAddObserver, - SimulateMsgUpdateObserver(k), + SimulateUpdateObserver(k), ), simulation.NewWeightedOperation( weightMsgTypeMsgAddObserver, - SimulateMsgAddObserverNodeAccount(k), + SimulateAddObserverNodeAccount(k), ), simulation.NewWeightedOperation( weightMsgTypeMsgAddObserver, - SimulateMsgAddObserver(k), + SimulateAddObserver(k), + ), + + simulation.NewWeightedOperation( + weightMsgTypeMsgVoteTSS, + SimulateMsgVoteTSS(k), ), } } @@ -194,19 +202,25 @@ func GetPolicyAccount(ctx sdk.Context, k types.AuthorityKeeper, accounts []simty return simAccount, nil } -func GetExternalChain(ctx sdk.Context, k keeper.Keeper, r *rand.Rand, retryCount int) (chains.Chain, error) { +func GetExternalChain(ctx sdk.Context, k keeper.Keeper, r *rand.Rand) (chains.Chain, error) { supportedChains := k.GetSupportedChains(ctx) if len(supportedChains) == 0 { return chains.Chain{}, fmt.Errorf("no supported chains found") } - // remove zeta chain from the supported chains - for i := 0; i < retryCount; i++ { + externalChain := chains.Chain{} + foundExternalChain := RepeatCheck(func() bool { c := supportedChains[r.Intn(len(supportedChains))] if !c.IsZetaChain() { - return c, nil + externalChain = c + return true } + return false + }) + + if !foundExternalChain { + return chains.Chain{}, fmt.Errorf("no external chain found") } - return chains.Chain{}, fmt.Errorf("no external chain found") + return externalChain, nil } // GetRandomAccountAndObserver returns a random account and the associated observer address @@ -229,42 +243,62 @@ func GetRandomAccountAndObserver( } randomObserver := "" - foundObserver := false - for i := 0; i < 10; i++ { - randomObserver = GetRandomObserver(r, observers.ObserverList) + foundObserver := RepeatCheck(func() bool { + randomObserver = GetRandomObserver(r, observerList) _, foundNodeAccount := k.GetNodeAccount(ctx, randomObserver) if !foundNodeAccount { - continue + return false } ok := k.IsNonTombstonedObserver(ctx, randomObserver) if ok { - foundObserver = true - break + return true } - } + return false + }) if !foundObserver { return simtypes.Account{}, "no observer found", nil, nil } - simAccount, err := GetObserverAccount(randomObserver, accounts) + simAccount, err := GetSimAccount(randomObserver, accounts) if err != nil { return simtypes.Account{}, "", observerList, err } return simAccount, randomObserver, observerList, nil } +func GetRandomNodeAccount( + r *rand.Rand, + ctx sdk.Context, + k keeper.Keeper, + accounts []simtypes.Account, +) (simtypes.Account, string, error) { + nodeAccounts := k.GetAllNodeAccount(ctx) + + if len(nodeAccounts) == 0 { + return simtypes.Account{}, "", fmt.Errorf("no node accounts present") + } + + randomNodeAccount := nodeAccounts[r.Intn(len(nodeAccounts))].Operator + + simAccount, err := GetSimAccount(randomNodeAccount, accounts) + if err != nil { + return simtypes.Account{}, "", err + } + return simAccount, randomNodeAccount, nil +} + func GetRandomObserver(r *rand.Rand, observerList []string) string { idx := r.Intn(len(observerList)) return observerList[idx] } -// GetObserverAccount returns the account associated with the observer address from the list of accounts provided -// GetObserverAccount can fail if all the observers are removed from the observer set ,this can happen +// GetSimAccount returns the account associated with the observer address from the list of accounts provided +// GetSimAccount can fail if all the observers are removed from the observer set ,this can happen //if the other modules create transactions which affect the validator //and triggers any of the staking hooks defined in the observer modules -func GetObserverAccount(observerAddress string, accounts []simtypes.Account) (simtypes.Account, error) { +func GetSimAccount(observerAddress string, accounts []simtypes.Account) (simtypes.Account, error) { operatorAddress, err := types.GetOperatorAddressFromAccAddress(observerAddress) if err != nil { return simtypes.Account{}, fmt.Errorf("validator not found for observer ") @@ -276,3 +310,101 @@ func GetObserverAccount(observerAddress string, accounts []simtypes.Account) (si } return simAccount, nil } + +func RepeatCheck(fn func() bool) bool { + for i := 0; i < DefaultRetryCount; i++ { + if fn() { + return true + } + } + return false +} + +func ObserverVotesSimulationMatrix() (simtypes.TransitionMatrix, []float64, int) { + observerVotesTransitionMatrix, _ := 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}, + }) + // The states are: + // column 1: All observers 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, but this is arbitrary and can be changed + statePercentageArray := []float64{1, .9, .75, .4, .15, 0} + curNumVotesState := 1 + return observerVotesTransitionMatrix, statePercentageArray, curNumVotesState +} + +func BallotVoteSimulationMatrix() (simtypes.TransitionMatrix, []float64, int) { + ballotTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ + {70, 10}, + {30, 10}, + }) + // The states are: + // column 1: 100% vote yes + // column 2: 0% vote yes + // For all conditions we assume if the vote is not a yes + // then it is a no . + yesVoteArray := []float64{1, 0} + ballotVotesState := 1 + return ballotTransitionMatrix, yesVoteArray, ballotVotesState +} + +// GenAndDeliverTxWithRandFees generates a transaction with a random fee and delivers it. +func GenAndDeliverTxWithRandFees( + txCtx simulation.OperationInput, +) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + spendable := txCtx.Bankkeeper.SpendableCoins(txCtx.Context, account.GetAddress()) + + var fees sdk.Coins + var err error + + coins, hasNeg := spendable.SafeSub(txCtx.CoinsSpentInMsg...) + if hasNeg { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "message doesn't leave room for fees"), nil, err + } + + fees, err = simtypes.RandomFees(txCtx.R, txCtx.Context, coins) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate fees"), nil, err + } + return GenAndDeliverTx(txCtx, fees) +} + +// GenAndDeliverTx generates a transactions and delivers it with the provided fees. +// This function does not return an error if the transaction fails to deliver. +func GenAndDeliverTx( + txCtx simulation.OperationInput, + fees sdk.Coins, +) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + tx, err := simtestutil.GenSignedMockTx( + txCtx.R, + txCtx.TxGen, + []sdk.Msg{txCtx.Msg}, + fees, + simtestutil.DefaultGenTxGas, + txCtx.Context.ChainID(), + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + txCtx.SimAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate mock tx"), nil, err + } + + _, _, err = txCtx.App.SimDeliver(txCtx.TxGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to deliver tx"), nil, nil + } + + return simtypes.NewOperationMsg(txCtx.Msg, true, "", txCtx.Cdc), nil, nil +}