From dee623def696be3eb18c076fb784692119c2b255 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 15 Nov 2024 16:18:35 -0500 Subject: [PATCH 01/13] add basic structure for outbound vote message --- simulation/state.go | 29 ++- testutil/sample/crosschain.go | 76 ++++++- testutil/sample/crypto.go | 2 +- x/crosschain/simulation/operations.go | 245 +++++++++++++++++++-- x/crosschain/simulation/operations_test.go | 45 ++++ 5 files changed, 369 insertions(+), 28 deletions(-) create mode 100644 x/crosschain/simulation/operations_test.go diff --git a/simulation/state.go b/simulation/state.go index 40162fc0a6..3367d8ed01 100644 --- a/simulation/state.go +++ b/simulation/state.go @@ -22,6 +22,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + chains2 "github.com/zeta-chain/node/pkg/chains" zetaapp "github.com/zeta-chain/node/app" "github.com/zeta-chain/node/testutil/sample" @@ -127,11 +128,31 @@ func updateObserverState(t *testing.T, rawState map[string]json.RawMessage, cdc observerState.Observers.ObserverList = observers observerState.CrosschainFlags.IsInboundEnabled = true observerState.CrosschainFlags.IsOutboundEnabled = true - tss := sample.TSSRandom(t, r) tss.OperatorAddressList = observers observerState.Tss = &tss + chains := chains2.DefaultChainsList() + var chainsNonces []observertypes.ChainNonces + var pendingNonces []observertypes.PendingNonces + for _, chain := range chains { + chainNonce := observertypes.ChainNonces{ + ChainId: chain.ChainId, + Nonce: 0, + } + chainsNonces = append(chainsNonces, chainNonce) + pendingNonce := observertypes.PendingNonces{ + NonceLow: 0, + NonceHigh: 0, + ChainId: chain.ChainId, + Tss: tss.TssPubkey, + } + pendingNonces = append(pendingNonces, pendingNonce) + } + + observerState.ChainNonces = chainsNonces + observerState.PendingNonces = pendingNonces + return observerState } @@ -171,9 +192,9 @@ func updateFungibleState(t *testing.T, rawState map[string]json.RawMessage, cdc fungibleState := new(fungibletypes.GenesisState) cdc.MustUnmarshalJSON(fungibleStateBz, fungibleState) fungibleState.SystemContract = &fungibletypes.SystemContract{ - SystemContract: sample.EthAddressRandom(r).String(), - ConnectorZevm: sample.EthAddressRandom(r).String(), - Gateway: sample.EthAddressRandom(r).String(), + SystemContract: sample.EthAddressFromRand(r).String(), + ConnectorZevm: sample.EthAddressFromRand(r).String(), + Gateway: sample.EthAddressFromRand(r).String(), } return fungibleState diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index b6af0f9012..760121e273 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -302,9 +302,9 @@ func InboundVoteSim(coinType coin.CoinType, from, to int64, r *rand.Rand) types. EthAddress() return types.MsgVoteInbound{ Creator: "", - Sender: EthAddressRandom(r).String(), + Sender: EthAddressFromRand(r).String(), SenderChainId: from, - Receiver: EthAddressRandom(r).String(), + Receiver: EthAddressFromRand(r).String(), ReceiverChain: to, Amount: math.NewUint(r.Uint64()), Message: base64.StdEncoding.EncodeToString(RandomBytes(r)), @@ -314,12 +314,82 @@ func InboundVoteSim(coinType coin.CoinType, from, to int64, r *rand.Rand) types. }, InboundHash: ethcommon.BytesToHash(RandomBytes(r)).String(), CoinType: coinType, - TxOrigin: EthAddressRandom(r).String(), + TxOrigin: EthAddressFromRand(r).String(), Asset: StringRandom(r, 32), EventIndex: r.Uint64(), } } +func OutboundVoteSim(r *rand.Rand, + creator string, + index string, + to int64, + from int64, + tssPubkey string, +) (types.CrossChainTx, types.MsgVoteOutbound) { + coinType := coin.CoinType_Gas + + amount := math.NewUint(uint64(r.Int63())) + inbound := &types.InboundParams{ + Sender: EthAddressFromRand(r).String(), + SenderChainId: from, + TxOrigin: EthAddressFromRand(r).String(), + CoinType: coinType, + Asset: StringRandom(r, 32), + Amount: amount, + ObservedHash: StringRandom(r, 32), + ObservedExternalHeight: r.Uint64(), + BallotIndex: StringRandom(r, 32), + FinalizedZetaHeight: r.Uint64(), + } + + outbound := &types.OutboundParams{ + Receiver: EthAddressFromRand(r).String(), + ReceiverChainId: to, + CoinType: coinType, + Amount: math.NewUint(uint64(r.Int63())), + TssNonce: 0, + TssPubkey: tssPubkey, + CallOptions: &types.CallOptions{ + GasLimit: r.Uint64(), + }, + GasPrice: math.NewUint(uint64(r.Int63())).String(), + Hash: StringRandom(r, 32), + BallotIndex: StringRandom(r, 32), + ObservedExternalHeight: r.Uint64(), + GasUsed: 100, + EffectiveGasPrice: math.NewInt(r.Int63()), + EffectiveGasLimit: 100, + } + + cctx := types.CrossChainTx{ + Creator: creator, + Index: index, + ZetaFees: sdk.NewUint(1), + RelayedMessage: base64.StdEncoding.EncodeToString(RandomBytes(r)), + CctxStatus: &types.Status{Status: types.CctxStatus_PendingInbound}, + InboundParams: inbound, + OutboundParams: []*types.OutboundParams{outbound}, + } + + msg := types.MsgVoteOutbound{ + CctxHash: cctx.Index, + OutboundTssNonce: cctx.GetCurrentOutboundParam().TssNonce, + OutboundChain: cctx.GetCurrentOutboundParam().ReceiverChainId, + Status: chains.ReceiveStatus_success, + Creator: creator, + ObservedOutboundHash: ethcommon.BytesToHash(EthAddressFromRand(r).Bytes()).String(), + ValueReceived: cctx.GetCurrentOutboundParam().Amount, + ObservedOutboundBlockHeight: cctx.GetCurrentOutboundParam().ObservedExternalHeight, + ObservedOutboundEffectiveGasPrice: cctx.GetCurrentOutboundParam().EffectiveGasPrice, + ObservedOutboundGasUsed: cctx.GetCurrentOutboundParam().GasUsed, + CoinType: cctx.InboundParams.CoinType, + } + + return cctx, msg + +} + func ZRC20Withdrawal(to []byte, value *big.Int) *zrc20.ZRC20Withdrawal { return &zrc20.ZRC20Withdrawal{ From: EthAddress(), diff --git a/testutil/sample/crypto.go b/testutil/sample/crypto.go index 364585f687..9e643fa123 100644 --- a/testutil/sample/crypto.go +++ b/testutil/sample/crypto.go @@ -60,7 +60,7 @@ func EthAddress() ethcommon.Address { return ethcommon.BytesToAddress(sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).Bytes()) } -func EthAddressRandom(r *rand.Rand) ethcommon.Address { +func EthAddressFromRand(r *rand.Rand) ethcommon.Address { return ethcommon.BytesToAddress(sdk.AccAddress(PubKey(r).Address()).Bytes()) } diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index 6b2cdb7387..526cd80a2c 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -12,6 +12,7 @@ import ( moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" + ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/zeta-chain/node/pkg/authz" "github.com/zeta-chain/node/pkg/chains" @@ -145,6 +146,45 @@ func WeightedOperations( weightVoteInbound, SimulateVoteInbound(k), ), + simulation.NewWeightedOperation( + weightVoteOutbound, + SimulateVoteOutbound(k), + ), + } +} + +func operationSimulateVoteOutbound( + k keeper.Keeper, + msg types.MsgVoteOutbound, + 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) } } @@ -184,27 +224,143 @@ func operationSimulateVoteInbound( } } -func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { - // 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 - 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}, - }) +func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { - statePercentageArray := []float64{1, .9, .75, .4, .15, 0} - curNumVotesState := 1 + observerVotesTransitionMatrix, statePercentageArray, curNumVotesState := ObserverVotesSimulationMatrix() + ballotVotesTransitionMatrix, yesVotePercentageArray, ballotVotesState := BallotVoteSimulationMatrix() + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + to, from := int64(1337), int64(101) + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + for _, chain := range supportedChains { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { + to = chain.ChainId + } + if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { + from = chain.ChainId + } + } + + _, creator, err := GetRandomAccountAndObserver(r, ctx, k, accs) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + index := ethcrypto.Keccak256Hash([]byte(fmt.Sprintf("%d", r.Int63()))).Hex() + tss, found := k.GetObserverKeeper().GetTSS(ctx) + if !found { + return simtypes.OperationMsg{}, nil, fmt.Errorf("tss not found") + } + + cctx, msg := sample.OutboundVoteSim(r, creator, index, to, from, tss.TssPubkey) + err = k.SetObserverOutboundInfo(ctx, to, &cctx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to set observer outbound info"), nil, err + } + msg.OutboundTssNonce = cctx.GetCurrentOutboundParam().TssNonce + k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx, tss.TssPubkey) + + // 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 := GetRandomAccountAndObserver(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 + + err = firstMsg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first inbound 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) + + // 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 + } + + // 1) Schedule operations for votes + // 1.1) first pick a number of people to vote. + curNumVotesState = observerVotesTransitionMatrix.NextState(r, curNumVotesState) + numVotes := int(math.Ceil(float64(len(observerSet.ObserverList)) * statePercentageArray[curNumVotesState])) + + // 1.2) select who votes + whoVotes := r.Perm(len(observerSet.ObserverList)) + whoVotes = whoVotes[:numVotes] + + var fops []simtypes.FutureOperation + + ballotVotesState = ballotVotesTransitionMatrix.NextState(r, ballotVotesState) + yesVotePercentage := yesVotePercentageArray[ballotVotesState] + numberOfYesVotes := int(math.Ceil(float64(numVotes) * yesVotePercentage)) + vote := chains.ReceiveStatus_success + for _, observerIdx := range whoVotes { + observerAddress := observerSet.ObserverList[observerIdx] + // firstVoter has already voted. + if observerAddress == firstVoter { + continue + } + observerAccount, err := GetObserverAccount(observerAddress, accs) + if err != nil { + continue + } + // 1.3) schedule the vote + votingMsg := msg + votingMsg.Creator = observerAddress + + 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: operationSimulateVoteOutbound(k, votingMsg, observerAccount), + }) + } + return opMsg, fops, nil + + } +} + +func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { + + observerVotesTransitionMatrix, statePercentageArray, curNumVotesState := ObserverVotesSimulationMatrix() return func( r *rand.Rand, app *baseapp.BaseApp, @@ -229,6 +385,14 @@ func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { } msg := sample.InboundVoteSim(0, from, to, r) + // Return early if inbound is not enabled. + cf, found := k.GetObserverKeeper().GetCrosschainFlags(ctx) + if !found { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "crosschain flags not found"), nil, nil + } + if !cf.IsInboundEnabled { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "inbound is not enabled"), nil, nil + } // Pick a random observer to create the ballot // If this returns an error, it is likely that the entire observer set has been removed @@ -279,7 +443,7 @@ func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { // 1) Schedule operations for votes // 1.1) first pick a number of people to vote. - curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) + curNumVotesState = observerVotesTransitionMatrix.NextState(r, curNumVotesState) numVotes := int(math.Ceil(float64(len(observerSet.ObserverList)) * statePercentageArray[curNumVotesState])) // 1.2) select who votes @@ -484,3 +648,44 @@ func GenAndDeliverTx( return simtypes.NewOperationMsg(txCtx.Msg, true, "", txCtx.Cdc), nil, nil } + +func ObserverVotesSimulationMatrix() (simtypes.TransitionMatrix, []float64, int) { + // 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 + 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}, + }) + + statePercentageArray := []float64{1, .9, .75, .4, .15, 0} + curNumVotesState := 1 + return observerVotesTransitionMatrix, statePercentageArray, curNumVotesState +} + +func BallotVoteSimulationMatrix() (simtypes.TransitionMatrix, []float64, int) { + // The states are: + // column 1: 100% vote yes + // column 2: 50% vote yes + // column 3: 0% vote yes + // For all conditions we assume if the the vote is not a yes. + // then it is a no .Not voting condtion is handled by the ObserverVotesSimulationMatrix matrix + ballotTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ + {20, 50, 50}, + {55, 50, 20}, + {25, 0, 30}, + }) + + yesVoteArray := []float64{1, .5, 0} + ballotVotesState := 1 + return ballotTransitionMatrix, yesVoteArray, ballotVotesState +} diff --git a/x/crosschain/simulation/operations_test.go b/x/crosschain/simulation/operations_test.go new file mode 100644 index 0000000000..bffa63a7d9 --- /dev/null +++ b/x/crosschain/simulation/operations_test.go @@ -0,0 +1,45 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +func Test_Matrix(t *testing.T) { + 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} + yesVoteArray := []float64{1, .5, 0} + curNumVotesState := 1 + + ballotTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ + {20, 50, 50}, + {55, 50, 20}, + {25, 0, 30}, + }) + ballotVotesState := 1 + + r := rand.New(rand.NewSource(1)) + + for i := 0; i < 10; i++ { + curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) + ballotVotesState = ballotTransitionMatrix.NextState(r, ballotVotesState) + percentageVote := statePercentageArray[curNumVotesState] + percentageBallotYest := yesVoteArray[ballotVotesState] + + t.Logf("iteration %d ,curNumVotesState: %d,"+ + " ballotVotesState: %d, "+ + "percentageVote: %f, "+ + "percentageBallotYest: %f", i, curNumVotesState, ballotVotesState, percentageVote, percentageBallotYest) + + } +} From 111b0d3e76edf5219a345da8e227d84a046c9cc2 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 19 Nov 2024 12:52:13 -0500 Subject: [PATCH 02/13] add randomised outbound message --- simulation/state.go | 2 +- testutil/sample/crosschain.go | 16 ++++--- .../keeper/msg_server_vote_inbound_tx.go | 3 ++ .../keeper/msg_server_vote_outbound_tx.go | 1 + x/crosschain/simulation/operations.go | 42 +++++++++++-------- x/crosschain/simulation/operations_test.go | 40 ++++-------------- 6 files changed, 50 insertions(+), 54 deletions(-) diff --git a/simulation/state.go b/simulation/state.go index 3367d8ed01..90a51c9540 100644 --- a/simulation/state.go +++ b/simulation/state.go @@ -119,7 +119,7 @@ func updateObserverState(t *testing.T, rawState map[string]json.RawMessage, cdc observers[i], observers[j] = observers[j], observers[i] }) - numObservers := r.Intn(11) + 5 + numObservers := r.Intn(21) + 5 if numObservers > len(observers) { numObservers = len(observers) } diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index 760121e273..1adaf6422b 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -297,9 +297,15 @@ func InboundVote(coinType coin.CoinType, from, to int64) types.MsgVoteInbound { } } +func CoinTypeFromRand(r *rand.Rand) coin.CoinType { + coinTypes := []coin.CoinType{coin.CoinType_Gas, coin.CoinType_ERC20, coin.CoinType_Zeta} + coinType := coinTypes[r.Intn(len(coinTypes))] + return coinType +} + // InboundVoteSim creates a simulated inbound vote message. This function uses the provided source of randomness to generate -func InboundVoteSim(coinType coin.CoinType, from, to int64, r *rand.Rand) types.MsgVoteInbound { - EthAddress() +func InboundVoteSim(_ coin.CoinType, from, to int64, r *rand.Rand) types.MsgVoteInbound { + coinType := CoinTypeFromRand(r) return types.MsgVoteInbound{ Creator: "", Sender: EthAddressFromRand(r).String(), @@ -307,7 +313,7 @@ func InboundVoteSim(coinType coin.CoinType, from, to int64, r *rand.Rand) types. Receiver: EthAddressFromRand(r).String(), ReceiverChain: to, Amount: math.NewUint(r.Uint64()), - Message: base64.StdEncoding.EncodeToString(RandomBytes(r)), + Message: "95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", InboundBlockHeight: r.Uint64(), CallOptions: &types.CallOptions{ GasLimit: 1000000000, @@ -327,8 +333,8 @@ func OutboundVoteSim(r *rand.Rand, from int64, tssPubkey string, ) (types.CrossChainTx, types.MsgVoteOutbound) { - coinType := coin.CoinType_Gas + coinType := CoinTypeFromRand(r) amount := math.NewUint(uint64(r.Int63())) inbound := &types.InboundParams{ Sender: EthAddressFromRand(r).String(), @@ -367,7 +373,7 @@ func OutboundVoteSim(r *rand.Rand, Index: index, ZetaFees: sdk.NewUint(1), RelayedMessage: base64.StdEncoding.EncodeToString(RandomBytes(r)), - CctxStatus: &types.Status{Status: types.CctxStatus_PendingInbound}, + CctxStatus: &types.Status{Status: types.CctxStatus_PendingOutbound}, InboundParams: inbound, OutboundParams: []*types.OutboundParams{outbound}, } diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index 263b7b23bc..5b4ccf4ced 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "fmt" sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -106,6 +107,8 @@ func (k msgServer) VoteInbound( // Save the inbound CCTX to the store. This is called irrespective of the status of the CCTX or the outcome of the process function. k.SaveObservedInboundInformation(ctx, cctx, msg.EventIndex) + fmt.Println("Successfully voted on inbound transaction", cctx.CctxStatus.Status) + return &types.MsgVoteInboundResponse{}, nil } diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx.go b/x/crosschain/keeper/msg_server_vote_outbound_tx.go index 4bb65a3c8f..36125626d7 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx.go @@ -119,6 +119,7 @@ func (k msgServer) VoteOutbound( return &types.MsgVoteOutboundResponse{}, nil } k.SaveSuccessfulOutbound(ctx, &cctx, tss.TssPubkey) + fmt.Println("Outbound transaction processed successfully", cctx.CctxStatus.Status) return &types.MsgVoteOutboundResponse{}, nil } diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index 526cd80a2c..40e491b505 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -226,6 +226,8 @@ func operationSimulateVoteInbound( func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { + defaultVote := chains.ReceiveStatus_success + alternativeVote := chains.ReceiveStatus_failed observerVotesTransitionMatrix, statePercentageArray, curNumVotesState := ObserverVotesSimulationMatrix() ballotVotesTransitionMatrix, yesVotePercentageArray, ballotVotesState := BallotVoteSimulationMatrix() return func( @@ -258,6 +260,8 @@ func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { } cctx, msg := sample.OutboundVoteSim(r, creator, index, to, from, tss.TssPubkey) + msg.Status = defaultVote + err = k.SetObserverOutboundInfo(ctx, to, &cctx) if err != nil { return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to set observer outbound info"), nil, err @@ -326,8 +330,12 @@ func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { ballotVotesState = ballotVotesTransitionMatrix.NextState(r, ballotVotesState) yesVotePercentage := yesVotePercentageArray[ballotVotesState] numberOfYesVotes := int(math.Ceil(float64(numVotes) * yesVotePercentage)) - vote := chains.ReceiveStatus_success - for _, observerIdx := range whoVotes { + vote := defaultVote + + for voteCount, observerIdx := range whoVotes { + if voteCount == numberOfYesVotes { + vote = alternativeVote + } observerAddress := observerSet.ObserverList[observerIdx] // firstVoter has already voted. if observerAddress == firstVoter { @@ -340,6 +348,7 @@ func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { // 1.3) schedule the vote votingMsg := msg votingMsg.Creator = observerAddress + votingMsg.Status = vote e := votingMsg.ValidateBasic() if e != nil { @@ -650,14 +659,7 @@ func GenAndDeliverTx( } func ObserverVotesSimulationMatrix() (simtypes.TransitionMatrix, []float64, int) { - // 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 + observerVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ {20, 10, 0, 0, 0, 0}, {55, 50, 20, 10, 0, 0}, @@ -666,25 +668,31 @@ func ObserverVotesSimulationMatrix() (simtypes.TransitionMatrix, []float64, int) {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, 20}, + {20, 30, 30}, + {10, 60, 50}, + }) // The states are: // column 1: 100% vote yes // column 2: 50% vote yes // column 3: 0% vote yes // For all conditions we assume if the the vote is not a yes. // then it is a no .Not voting condtion is handled by the ObserverVotesSimulationMatrix matrix - ballotTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ - {20, 50, 50}, - {55, 50, 20}, - {25, 0, 30}, - }) - yesVoteArray := []float64{1, .5, 0} ballotVotesState := 1 return ballotTransitionMatrix, yesVoteArray, ballotVotesState diff --git a/x/crosschain/simulation/operations_test.go b/x/crosschain/simulation/operations_test.go index bffa63a7d9..40ee2dd534 100644 --- a/x/crosschain/simulation/operations_test.go +++ b/x/crosschain/simulation/operations_test.go @@ -1,45 +1,23 @@ package simulation_test import ( + "math" "math/rand" "testing" - "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/zeta-chain/node/x/crosschain/simulation" ) func Test_Matrix(t *testing.T) { - 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} - yesVoteArray := []float64{1, .5, 0} - curNumVotesState := 1 - - ballotTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ - {20, 50, 50}, - {55, 50, 20}, - {25, 0, 30}, - }) - ballotVotesState := 1 - - r := rand.New(rand.NewSource(1)) - + r := rand.New(rand.NewSource(42)) + numVotes := 10 for i := 0; i < 10; i++ { - curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) - ballotVotesState = ballotTransitionMatrix.NextState(r, ballotVotesState) - percentageVote := statePercentageArray[curNumVotesState] - percentageBallotYest := yesVoteArray[ballotVotesState] - t.Logf("iteration %d ,curNumVotesState: %d,"+ - " ballotVotesState: %d, "+ - "percentageVote: %f, "+ - "percentageBallotYest: %f", i, curNumVotesState, ballotVotesState, percentageVote, percentageBallotYest) + ballotVotesTransitionMatrix, yesVotePercentageArray, ballotVotesState := simulation.BallotVoteSimulationMatrix() + ballotVotesState = ballotVotesTransitionMatrix.NextState(r, ballotVotesState) + yesVotePercentage := yesVotePercentageArray[ballotVotesState] + numberOfYesVotes := int(math.Ceil(float64(numVotes) * yesVotePercentage)) + t.Logf("Yes Vote Percentage: %v, Number of Yes votes %d", yesVotePercentage, numberOfYesVotes) } } From 5c958d0f85fe48718392f03534273b702644f0c7 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 19 Nov 2024 13:17:16 -0500 Subject: [PATCH 03/13] add cointype randomisation to inbound message --- testutil/sample/crosschain.go | 17 +++++++++-------- .../keeper/msg_server_vote_inbound_tx.go | 4 ---- x/crosschain/simulation/operations.go | 2 +- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index 1adaf6422b..bc112816cb 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -304,16 +304,17 @@ func CoinTypeFromRand(r *rand.Rand) coin.CoinType { } // InboundVoteSim creates a simulated inbound vote message. This function uses the provided source of randomness to generate -func InboundVoteSim(_ coin.CoinType, from, to int64, r *rand.Rand) types.MsgVoteInbound { +func InboundVoteSim(from, to int64, r *rand.Rand) types.MsgVoteInbound { coinType := CoinTypeFromRand(r) return types.MsgVoteInbound{ - Creator: "", - Sender: EthAddressFromRand(r).String(), - SenderChainId: from, - Receiver: EthAddressFromRand(r).String(), - ReceiverChain: to, - Amount: math.NewUint(r.Uint64()), - Message: "95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + Creator: "", + Sender: EthAddressFromRand(r).String(), + SenderChainId: from, + Receiver: EthAddressFromRand(r).String(), + ReceiverChain: to, + Amount: math.NewUint(r.Uint64()), + Message: "95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", // Refactor this + // to use ProtocolContractVersion_V2 format InboundBlockHeight: r.Uint64(), CallOptions: &types.CallOptions{ GasLimit: 1000000000, diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index 5b4ccf4ced..7a47ec537b 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -2,7 +2,6 @@ package keeper import ( "context" - "fmt" sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -106,9 +105,6 @@ func (k msgServer) VoteInbound( } // Save the inbound CCTX to the store. This is called irrespective of the status of the CCTX or the outcome of the process function. k.SaveObservedInboundInformation(ctx, cctx, msg.EventIndex) - - fmt.Println("Successfully voted on inbound transaction", cctx.CctxStatus.Status) - return &types.MsgVoteInboundResponse{}, nil } diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index 40e491b505..b4a0bde86d 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -393,7 +393,7 @@ func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { } } - msg := sample.InboundVoteSim(0, from, to, r) + msg := sample.InboundVoteSim(from, to, r) // Return early if inbound is not enabled. cf, found := k.GetObserverKeeper().GetCrosschainFlags(ctx) if !found { From a1940b99a21c6e74dccb3e5f297d8f689e796781 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 22 Nov 2024 13:36:26 -0500 Subject: [PATCH 04/13] add cointype gas --- Makefile | 2 +- simulation/state.go | 17 ++++ testutil/sample/crosschain.go | 7 +- testutil/sample/crypto.go | 5 ++ x/crosschain/keeper/evm_deposit.go | 2 + .../keeper/msg_server_vote_inbound_tx.go | 2 +- .../keeper/msg_server_vote_outbound_tx.go | 1 - x/crosschain/keeper/refund.go | 1 + .../operation_add_inbound_tracker.go | 79 +++++++++++++++++++ x/crosschain/simulation/operations.go | 20 ++++- x/fungible/keeper/deposits.go | 2 + 11 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 x/crosschain/simulation/operation_add_inbound_tracker.go diff --git a/Makefile b/Makefile index e6a75d3eff..8358885b1b 100644 --- a/Makefile +++ b/Makefile @@ -401,7 +401,7 @@ test-sim-nondeterminism: $(call run-sim-test,"non-determinism test",TestAppStateDeterminism,100,200,30m) test-sim-fullappsimulation: - $(call run-sim-test,"TestFullAppSimulation",TestFullAppSimulation,100,200,30m) + $(call run-sim-test,"TestFullAppSimulation",TestFullAppSimulation,10,20,30m) test-sim-import-export: $(call run-sim-test,"test-import-export",TestAppImportExport,50,100,30m) diff --git a/simulation/state.go b/simulation/state.go index 90a51c9540..6b40d8af72 100644 --- a/simulation/state.go +++ b/simulation/state.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/require" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" chains2 "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/coin" zetaapp "github.com/zeta-chain/node/app" "github.com/zeta-chain/node/testutil/sample" @@ -197,6 +198,22 @@ func updateFungibleState(t *testing.T, rawState map[string]json.RawMessage, cdc Gateway: sample.EthAddressFromRand(r).String(), } + foreignCoins := make([]fungibletypes.ForeignCoins, 0) + chains := chains2.DefaultChainsList() + + for _, chain := range chains { + foreignCoin := fungibletypes.ForeignCoins{ + ForeignChainId: chain.ChainId, + Asset: sample.EthAddressFromRand(r).String(), + Zrc20ContractAddress: sample.EthAddressFromRand(r).String(), + Decimals: 18, + Paused: false, + CoinType: coin.CoinType_Gas, + LiquidityCap: math.ZeroUint(), + } + foreignCoins = append(foreignCoins, foreignCoin) + } + return fungibleState } diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index bc112816cb..e52535ea48 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -304,8 +304,9 @@ func CoinTypeFromRand(r *rand.Rand) coin.CoinType { } // InboundVoteSim creates a simulated inbound vote message. This function uses the provided source of randomness to generate -func InboundVoteSim(from, to int64, r *rand.Rand) types.MsgVoteInbound { - coinType := CoinTypeFromRand(r) +func InboundVoteSim(from, to int64, r *rand.Rand, asset string) types.MsgVoteInbound { + //coinType := CoinTypeFromRand(r) + coinType := coin.CoinType_Gas return types.MsgVoteInbound{ Creator: "", Sender: EthAddressFromRand(r).String(), @@ -322,7 +323,7 @@ func InboundVoteSim(from, to int64, r *rand.Rand) types.MsgVoteInbound { InboundHash: ethcommon.BytesToHash(RandomBytes(r)).String(), CoinType: coinType, TxOrigin: EthAddressFromRand(r).String(), - Asset: StringRandom(r, 32), + Asset: asset, EventIndex: r.Uint64(), } } diff --git a/testutil/sample/crypto.go b/testutil/sample/crypto.go index 9e643fa123..c8cc555dfd 100644 --- a/testutil/sample/crypto.go +++ b/testutil/sample/crypto.go @@ -113,6 +113,11 @@ func Hash() ethcommon.Hash { return ethcommon.BytesToHash(EthAddress().Bytes()) } +// Hash returns a sample hash +func HashFromRand(r *rand.Rand) ethcommon.Hash { + return ethcommon.BytesToHash(EthAddressFromRand(r).Bytes()) +} + // BtcHash returns a sample btc hash func BtcHash() chainhash.Hash { return chainhash.Hash(Hash()) diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index e57dc16d1b..cdb6636325 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -95,6 +95,8 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo } } + fmt.Println("executing ZRC20DepositAndCallContract", cctx.InboundParams.Asset, inboundSenderChainID) + from, err := chains.DecodeAddressFromChainID(inboundSenderChainID, inboundSender, k.GetAuthorityKeeper().GetAdditionalChainList(ctx)) if err != nil { return false, fmt.Errorf("HandleEVMDeposit: unable to decode address: %w", err) diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index 7a47ec537b..99f18ff1f6 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -5,7 +5,6 @@ import ( sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/zeta-chain/node/x/crosschain/types" ) @@ -64,6 +63,7 @@ func (k msgServer) VoteInbound( // vote on inbound ballot // use a temporary context to not commit any ballot state change in case of error tmpCtx, commit := ctx.CacheContext() + finalized, isNew, err := k.zetaObserverKeeper.VoteOnInboundBallot( tmpCtx, msg.SenderChainId, diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx.go b/x/crosschain/keeper/msg_server_vote_outbound_tx.go index 36125626d7..4bb65a3c8f 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx.go @@ -119,7 +119,6 @@ func (k msgServer) VoteOutbound( return &types.MsgVoteOutboundResponse{}, nil } k.SaveSuccessfulOutbound(ctx, &cctx, tss.TssPubkey) - fmt.Println("Outbound transaction processed successfully", cctx.CctxStatus.Status) return &types.MsgVoteOutboundResponse{}, nil } diff --git a/x/crosschain/keeper/refund.go b/x/crosschain/keeper/refund.go index 6c9deef32f..a658b242eb 100644 --- a/x/crosschain/keeper/refund.go +++ b/x/crosschain/keeper/refund.go @@ -47,6 +47,7 @@ func (k Keeper) RefundAmountOnZetaChainGas( // get the zrc20 contract address fcSenderChain, found := k.fungibleKeeper.GetGasCoinForForeignCoin(ctx, chainID) if !found { + fmt.Println("chainID", chainID, "RefundAmountOnZetaChainGas") return types.ErrForeignCoinNotFound } zrc20 := ethcommon.HexToAddress(fcSenderChain.Zrc20ContractAddress) diff --git a/x/crosschain/simulation/operation_add_inbound_tracker.go b/x/crosschain/simulation/operation_add_inbound_tracker.go new file mode 100644 index 0000000000..648b833a72 --- /dev/null +++ b/x/crosschain/simulation/operation_add_inbound_tracker.go @@ -0,0 +1,79 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + 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/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" +) + +// SimulateMsgAddInboundTracker generates a MsgAddInboundTracker with random values +func SimulateMsgAddInboundTracker(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) { + // Get a random account and observer + // If this returns an error, it is likely that the entire observer set has been removed + simAccount, randomObserver, err := GetRandomAccountAndObserver(r, ctx, k, accounts) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + authAccount := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + if len(supportedChains) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAddInboundTracker, + "no supported chains found", + ), nil, nil + } + randomChainID := GetRandomChainID(r, supportedChains) + txHash := sample.Hash() + coinType := sample.CoinTypeFromRand(r) + + // Add a new inbound Tracker + msg := types.MsgAddInboundTracker{ + Creator: randomObserver, + ChainId: randomChainID, + TxHash: txHash.String(), + CoinType: coinType, + Proof: nil, + BlockHash: "", + TxIndex: 0, + } + + // System contracts are deployed on the first block, so we cannot vote on gas prices before that + if ctx.BlockHeight() <= 1 { + return simtypes.NewOperationMsg(&msg, true, "block height less than 1", nil), nil, nil + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate vote gas price msg"), nil, err + } + + 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, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index b4a0bde86d..2b9dc5f8e7 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -150,6 +150,10 @@ func WeightedOperations( weightVoteOutbound, SimulateVoteOutbound(k), ), + simulation.NewWeightedOperation( + weightAddInboundTracker, + SimulateMsgAddInboundTracker(k), + ), } } @@ -368,7 +372,6 @@ func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { } func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { - observerVotesTransitionMatrix, statePercentageArray, curNumVotesState := ObserverVotesSimulationMatrix() return func( r *rand.Rand, @@ -393,7 +396,15 @@ func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { } } - msg := sample.InboundVoteSim(from, to, r) + foriegnCoins := k.GetFungibleKeeper().GetAllForeignCoins(ctx) + asset := "" + for _, coin := range foriegnCoins { + if coin.ForeignChainId == from { + asset = coin.Asset + } + } + + msg := sample.InboundVoteSim(from, to, r, asset) // Return early if inbound is not enabled. cf, found := k.GetObserverKeeper().GetCrosschainFlags(ctx) if !found { @@ -461,6 +472,7 @@ func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { var fops []simtypes.FutureOperation + votingBlock := int64(0) for _, observerIdx := range whoVotes { observerAddress := observerSet.ObserverList[observerIdx] // firstVoter has already voted. @@ -479,13 +491,13 @@ func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { 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), + BlockHeight: int(ctx.BlockHeight() + votingBlock), Op: operationSimulateVoteInbound(k, votingMsg, observerAccount), }) + votingBlock++ } return opMsg, fops, nil } diff --git a/x/fungible/keeper/deposits.go b/x/fungible/keeper/deposits.go index b6a0c72c4e..f82fce78a7 100644 --- a/x/fungible/keeper/deposits.go +++ b/x/fungible/keeper/deposits.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" @@ -37,6 +38,7 @@ func (k Keeper) ZRC20DepositAndCallContract( protocolContractVersion crosschaintypes.ProtocolContractVersion, isCrossChainCall bool, ) (*evmtypes.MsgEthereumTxResponse, bool, error) { + fmt.Println("executing ZRC20DepositAndCallContract", asset, senderChainID) // get ZRC20 contract zrc20Contract, _, err := k.getAndCheckZRC20(ctx, amount, senderChainID, coinType, asset) if err != nil { From 734fcde9745dc404cab5c470a1697227ea1c3650 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 22 Nov 2024 17:39:19 -0500 Subject: [PATCH 05/13] add cointype erc20 --- Makefile | 2 +- simulation/state.go | 1 + testutil/sample/crosschain.go | 4 ++-- x/crosschain/keeper/evm_deposit.go | 2 -- x/crosschain/keeper/msg_server_vote_inbound_tx.go | 2 ++ x/crosschain/simulation/operations.go | 5 +++-- x/fungible/keeper/deposits.go | 4 ++-- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 8358885b1b..e6a75d3eff 100644 --- a/Makefile +++ b/Makefile @@ -401,7 +401,7 @@ test-sim-nondeterminism: $(call run-sim-test,"non-determinism test",TestAppStateDeterminism,100,200,30m) test-sim-fullappsimulation: - $(call run-sim-test,"TestFullAppSimulation",TestFullAppSimulation,10,20,30m) + $(call run-sim-test,"TestFullAppSimulation",TestFullAppSimulation,100,200,30m) test-sim-import-export: $(call run-sim-test,"test-import-export",TestAppImportExport,50,100,30m) diff --git a/simulation/state.go b/simulation/state.go index 6b40d8af72..7b534b4684 100644 --- a/simulation/state.go +++ b/simulation/state.go @@ -213,6 +213,7 @@ func updateFungibleState(t *testing.T, rawState map[string]json.RawMessage, cdc } foreignCoins = append(foreignCoins, foreignCoin) } + fungibleState.ForeignCoinsList = foreignCoins return fungibleState } diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index e52535ea48..127a37dc6e 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -305,8 +305,7 @@ func CoinTypeFromRand(r *rand.Rand) coin.CoinType { // InboundVoteSim creates a simulated inbound vote message. This function uses the provided source of randomness to generate func InboundVoteSim(from, to int64, r *rand.Rand, asset string) types.MsgVoteInbound { - //coinType := CoinTypeFromRand(r) - coinType := coin.CoinType_Gas + coinType := CoinTypeFromRand(r) return types.MsgVoteInbound{ Creator: "", Sender: EthAddressFromRand(r).String(), @@ -337,6 +336,7 @@ func OutboundVoteSim(r *rand.Rand, ) (types.CrossChainTx, types.MsgVoteOutbound) { coinType := CoinTypeFromRand(r) + amount := math.NewUint(uint64(r.Int63())) inbound := &types.InboundParams{ Sender: EthAddressFromRand(r).String(), diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index cdb6636325..e57dc16d1b 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -95,8 +95,6 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo } } - fmt.Println("executing ZRC20DepositAndCallContract", cctx.InboundParams.Asset, inboundSenderChainID) - from, err := chains.DecodeAddressFromChainID(inboundSenderChainID, inboundSender, k.GetAuthorityKeeper().GetAdditionalChainList(ctx)) if err != nil { return false, fmt.Errorf("HandleEVMDeposit: unable to decode address: %w", err) diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index 99f18ff1f6..fabdc964ea 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "fmt" sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -101,6 +102,7 @@ func (k msgServer) VoteInbound( cctx, err := k.ValidateInbound(ctx, msg, true) if err != nil { + fmt.Println("Error validating inbound", err) return nil, sdkerrors.Wrap(err, voteInboundID) } // Save the inbound CCTX to the store. This is called irrespective of the status of the CCTX or the outcome of the process function. diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index 2b9dc5f8e7..249022fd11 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -398,6 +398,7 @@ func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { foriegnCoins := k.GetFungibleKeeper().GetAllForeignCoins(ctx) asset := "" + for _, coin := range foriegnCoins { if coin.ForeignChainId == from { asset = coin.Asset @@ -472,7 +473,7 @@ func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { var fops []simtypes.FutureOperation - votingBlock := int64(0) + votingBlock := int64(1) for _, observerIdx := range whoVotes { observerAddress := observerSet.ObserverList[observerIdx] // firstVoter has already voted. @@ -497,7 +498,7 @@ func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { BlockHeight: int(ctx.BlockHeight() + votingBlock), Op: operationSimulateVoteInbound(k, votingMsg, observerAccount), }) - votingBlock++ + //votingBlock++ } return opMsg, fops, nil } diff --git a/x/fungible/keeper/deposits.go b/x/fungible/keeper/deposits.go index f82fce78a7..60e8f5e6af 100644 --- a/x/fungible/keeper/deposits.go +++ b/x/fungible/keeper/deposits.go @@ -1,7 +1,6 @@ package keeper import ( - "fmt" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" @@ -38,7 +37,7 @@ func (k Keeper) ZRC20DepositAndCallContract( protocolContractVersion crosschaintypes.ProtocolContractVersion, isCrossChainCall bool, ) (*evmtypes.MsgEthereumTxResponse, bool, error) { - fmt.Println("executing ZRC20DepositAndCallContract", asset, senderChainID) + //fmt.Println("start ZRC20DepositAndCallContract", asset, senderChainID) // get ZRC20 contract zrc20Contract, _, err := k.getAndCheckZRC20(ctx, amount, senderChainID, coinType, asset) if err != nil { @@ -101,6 +100,7 @@ func (k Keeper) getAndCheckZRC20( // this simplify the current workflow and allow to pause calls by pausing the gas token // TODO: refactor this logic and create specific workflow for no asset call // https://github.com/zeta-chain/node/issues/2627 + if coinType == coin.CoinType_Gas || coinType == coin.CoinType_NoAssetCall { foreignCoin, found = k.GetGasCoinForForeignCoin(ctx, chainID) if !found { From a3fb140964bf307dcd5844e789304ab76bacdb21 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Sat, 23 Nov 2024 11:09:04 -0500 Subject: [PATCH 06/13] add outbound tracker --- x/crosschain/keeper/cctx_utils.go | 1 - .../keeper/msg_server_add_outbound_tracker.go | 4 +- .../keeper/msg_server_vote_inbound_tx.go | 3 +- .../operation_add_inbound_tracker.go | 9 +- .../operation_add_outbound_tracker.go | 117 +++++ .../simulation/operation_gas_price_voter.go | 78 ++++ .../simulation/operation_vote_inbound.go | 186 ++++++++ .../simulation/operation_vote_outbound.go | 199 +++++++++ x/crosschain/simulation/operations.go | 422 +----------------- x/crosschain/types/keys.go | 3 +- x/crosschain/types/outbound_tracker.go | 5 + 11 files changed, 595 insertions(+), 432 deletions(-) create mode 100644 x/crosschain/simulation/operation_add_outbound_tracker.go create mode 100644 x/crosschain/simulation/operation_gas_price_voter.go create mode 100644 x/crosschain/simulation/operation_vote_inbound.go create mode 100644 x/crosschain/simulation/operation_vote_outbound.go create mode 100644 x/crosschain/types/outbound_tracker.go diff --git a/x/crosschain/keeper/cctx_utils.go b/x/crosschain/keeper/cctx_utils.go index 3f62202cb7..3fedf03cef 100644 --- a/x/crosschain/keeper/cctx_utils.go +++ b/x/crosschain/keeper/cctx_utils.go @@ -30,7 +30,6 @@ func (k Keeper) SetObserverOutboundInfo(ctx sdk.Context, receiveChainID int64, c "identifiers: %s (chain %q)", cctx.LogIdentifierForCCTX(), chain.Name, ) } - // SET nonce cctx.GetCurrentOutboundParam().TssNonce = nonce.Nonce tss, found := k.GetObserverKeeper().GetTSS(ctx) diff --git a/x/crosschain/keeper/msg_server_add_outbound_tracker.go b/x/crosschain/keeper/msg_server_add_outbound_tracker.go index 39002d3155..c8dc917602 100644 --- a/x/crosschain/keeper/msg_server_add_outbound_tracker.go +++ b/x/crosschain/keeper/msg_server_add_outbound_tracker.go @@ -15,7 +15,6 @@ import ( ) // MaxOutboundTrackerHashes is the maximum number of hashes that can be stored in the outbound transaction tracker -const MaxOutboundTrackerHashes = 5 // AddOutboundTracker adds a new record to the outbound transaction tracker. // only the admin policy account and the observer validators are authorized to broadcast this message without proof. @@ -47,7 +46,6 @@ func (k msgServer) AddOutboundTracker( msg.Nonce, ) } - // tracker submission is only allowed when the cctx is pending if !IsPending(cctx.CrossChainTx) { // garbage tracker (for any reason) is harmful to outTx observation and should be removed if it exists @@ -112,7 +110,7 @@ func (k msgServer) AddOutboundTracker( } // check if max hashes are reached - if len(tracker.HashList) >= MaxOutboundTrackerHashes { + if len(tracker.HashList) >= types.MaxOutboundTrackerHashes { return nil, types.ErrMaxTxOutTrackerHashesReached.Wrapf( "max hashes reached for chain %d, nonce %d, hash number: %d", msg.ChainId, diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index fabdc964ea..43667415ae 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -2,7 +2,6 @@ package keeper import ( "context" - "fmt" sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -102,11 +101,11 @@ func (k msgServer) VoteInbound( cctx, err := k.ValidateInbound(ctx, msg, true) if err != nil { - fmt.Println("Error validating inbound", err) return nil, sdkerrors.Wrap(err, voteInboundID) } // Save the inbound CCTX to the store. This is called irrespective of the status of the CCTX or the outcome of the process function. k.SaveObservedInboundInformation(ctx, cctx, msg.EventIndex) + return &types.MsgVoteInboundResponse{}, nil } diff --git a/x/crosschain/simulation/operation_add_inbound_tracker.go b/x/crosschain/simulation/operation_add_inbound_tracker.go index 648b833a72..2c36a9bf82 100644 --- a/x/crosschain/simulation/operation_add_inbound_tracker.go +++ b/x/crosschain/simulation/operation_add_inbound_tracker.go @@ -35,7 +35,7 @@ func SimulateMsgAddInboundTracker(k keeper.Keeper) simtypes.Operation { ), nil, nil } randomChainID := GetRandomChainID(r, supportedChains) - txHash := sample.Hash() + txHash := sample.HashFromRand(r) coinType := sample.CoinTypeFromRand(r) // Add a new inbound Tracker @@ -49,14 +49,9 @@ func SimulateMsgAddInboundTracker(k keeper.Keeper) simtypes.Operation { TxIndex: 0, } - // System contracts are deployed on the first block, so we cannot vote on gas prices before that - if ctx.BlockHeight() <= 1 { - return simtypes.NewOperationMsg(&msg, true, "block height less than 1", nil), nil, nil - } - err = msg.ValidateBasic() if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate vote gas price msg"), nil, err + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate MsgAddInboundTracker"), nil, err } txCtx := simulation.OperationInput{ diff --git a/x/crosschain/simulation/operation_add_outbound_tracker.go b/x/crosschain/simulation/operation_add_outbound_tracker.go new file mode 100644 index 0000000000..edaaefe8e1 --- /dev/null +++ b/x/crosschain/simulation/operation_add_outbound_tracker.go @@ -0,0 +1,117 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + 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/pkg/chains" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" +) + +// SimulateMsgAddOutboundTracker generates a MsgAddOutboundTracker with random values +func SimulateMsgAddOutboundTracker(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) { + + chainID := int64(1337) + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + for _, chain := range supportedChains { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { + chainID = chain.ChainId + } + + } + // Get a random account and observer + // If this returns an error, it is likely that the entire observer set has been removed + simAccount, randomObserver, err := GetRandomAccountAndObserver(r, ctx, k, accounts) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + authAccount := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + txHash := sample.HashFromRand(r) + tss, found := k.GetObserverKeeper().GetTSS(ctx) + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAddOutboundTracker, + "no TSS found", + ), nil, nil + } + + pendingNonces, found := k.GetObserverKeeper().GetPendingNonces(ctx, tss.TssPubkey, chainID) + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAddOutboundTracker, + "no pending nonces found", + ), nil, nil + } + + // pick a random nonce from the pending nonces between 0 and nonceLow + //fmt.Printf("pendingNonces.NonceLow: %d | pendingNonces.NonceHigh: %d \n", + // pendingNonces.NonceLow, pendingNonces.NonceHigh) + if pendingNonces.NonceLow == pendingNonces.NonceHigh { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAddOutboundTracker, + "no pending nonces found", + ), nil, nil + } + + nonce := pendingNonces.NonceLow + + tracker, found := k.GetOutboundTracker(ctx, chainID, uint64(nonce)) + if found && tracker.IsMaxed() { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAddOutboundTracker, + "tracker is maxed", + ), nil, nil + } + // Add a new inbound Tracker + msg := types.MsgAddOutboundTracker{ + Creator: randomObserver, + ChainId: chainID, + Nonce: uint64(nonce), + TxHash: txHash.String(), + Proof: nil, + BlockHash: "", + TxIndex: 0, + } + + // System contracts are deployed on the first block, so we cannot vote on gas prices before that + if ctx.BlockHeight() <= 2 { + return simtypes.NewOperationMsg(&msg, true, "block height less than 1", nil), nil, nil + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate MsgAddOutboundTracker msg"), nil, err + } + + 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, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/crosschain/simulation/operation_gas_price_voter.go b/x/crosschain/simulation/operation_gas_price_voter.go new file mode 100644 index 0000000000..89aeac1213 --- /dev/null +++ b/x/crosschain/simulation/operation_gas_price_voter.go @@ -0,0 +1,78 @@ +package simulation + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + 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/pkg/authz" + "github.com/zeta-chain/node/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" +) + +// SimulateMsgVoteGasPrice generates a MsgVoteGasPrice and delivers it +func SimulateMsgVoteGasPrice(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) { + // Get a random account and observer + // If this returns an error, it is likely that the entire observer set has been removed + simAccount, randomObserver, err := GetRandomAccountAndObserver(r, ctx, k, accounts) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + authAccount := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + if len(supportedChains) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + authz.GasPriceVoter.String(), + "no supported chains found", + ), nil, nil + } + randomChainID := GetRandomChainID(r, supportedChains) + + // Vote for random gas price. Gas prices do not use a ballot system, so we can vote directly without having to schedule future operations. + // The random nature of the price might create weird gas prices for the chain, but it is fine for now. We can remove the randomness if needed + msg := types.MsgVoteGasPrice{ + Creator: randomObserver, + ChainId: randomChainID, + Price: r.Uint64(), + PriorityFee: r.Uint64(), + BlockNumber: r.Uint64(), + Supply: fmt.Sprintf("%d", r.Int63()), + } + + // System contracts are deployed on the first block, so we cannot vote on gas prices before that + if ctx.BlockHeight() <= 1 { + return simtypes.NewOperationMsg(&msg, true, "block height less than 1", nil), nil, nil + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate vote gas price msg"), nil, err + } + + 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, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/crosschain/simulation/operation_vote_inbound.go b/x/crosschain/simulation/operation_vote_inbound.go new file mode 100644 index 0000000000..6258d17f67 --- /dev/null +++ b/x/crosschain/simulation/operation_vote_inbound.go @@ -0,0 +1,186 @@ +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/pkg/authz" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" +) + +// operationSimulateVoteInbound generates a MsgVoteInbound with a random vote and delivers it. +func operationSimulateVoteInbound( + k keeper.Keeper, + msg types.MsgVoteInbound, + 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) + } +} + +func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { + observerVotesTransitionMatrix, statePercentageArray, curNumVotesState := ObserverVotesSimulationMatrix() + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // TODO : randomize these values + // Right now we use a constant value for cctx creation , this is the same as the one used in unit tests for the successful condition. + // TestKeeper_VoteInbound/successfully vote on evm deposit + // But this can improved by adding more randomization + + to, from := int64(1337), int64(101) + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + for _, chain := range supportedChains { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { + from = chain.ChainId + } + if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { + to = chain.ChainId + } + } + + foriegnCoins := k.GetFungibleKeeper().GetAllForeignCoins(ctx) + asset := "" + + for _, coin := range foriegnCoins { + if coin.ForeignChainId == from { + asset = coin.Asset + } + } + + msg := sample.InboundVoteSim(from, to, r, asset) + // Return early if inbound is not enabled. + cf, found := k.GetObserverKeeper().GetCrosschainFlags(ctx) + if !found { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "crosschain flags not found"), nil, nil + } + if !cf.IsInboundEnabled { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "inbound is not enabled"), nil, nil + } + + // 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 := GetRandomAccountAndObserver(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 + + err = firstMsg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first inbound 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) + + // 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 + } + + // 1) Schedule operations for votes + // 1.1) first pick a number of people to vote. + curNumVotesState = observerVotesTransitionMatrix.NextState(r, curNumVotesState) + numVotes := int(math.Ceil(float64(len(observerSet.ObserverList)) * statePercentageArray[curNumVotesState])) + + // 1.2) select who votes + whoVotes := r.Perm(len(observerSet.ObserverList)) + whoVotes = whoVotes[:numVotes] + + var fops []simtypes.FutureOperation + + for _, observerIdx := range whoVotes { + observerAddress := observerSet.ObserverList[observerIdx] + // firstVoter has already voted. + if observerAddress == firstVoter { + continue + } + observerAccount, err := GetObserverAccount(observerAddress, accs) + if err != nil { + continue + } + // 1.3) schedule the vote + votingMsg := msg + votingMsg.Creator = observerAddress + + 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: operationSimulateVoteInbound(k, votingMsg, observerAccount), + }) + + } + return opMsg, fops, nil + } +} diff --git a/x/crosschain/simulation/operation_vote_outbound.go b/x/crosschain/simulation/operation_vote_outbound.go new file mode 100644 index 0000000000..13ddc35222 --- /dev/null +++ b/x/crosschain/simulation/operation_vote_outbound.go @@ -0,0 +1,199 @@ +package simulation + +import ( + "fmt" + "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" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/zeta-chain/node/pkg/authz" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" +) + +func operationSimulateVoteOutbound( + k keeper.Keeper, + msg types.MsgVoteOutbound, + 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) + } +} + +func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { + + defaultVote := chains.ReceiveStatus_success + alternativeVote := chains.ReceiveStatus_failed + observerVotesTransitionMatrix, statePercentageArray, curNumVotesState := ObserverVotesSimulationMatrix() + ballotVotesTransitionMatrix, yesVotePercentageArray, ballotVotesState := BallotVoteSimulationMatrix() + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + to, from := int64(1337), int64(101) + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + for _, chain := range supportedChains { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { + to = chain.ChainId + } + if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { + from = chain.ChainId + } + } + + _, creator, err := GetRandomAccountAndObserver(r, ctx, k, accs) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + index := ethcrypto.Keccak256Hash([]byte(fmt.Sprintf("%d", r.Int63()))).Hex() + + tss, found := k.GetObserverKeeper().GetTSS(ctx) + if !found { + return simtypes.OperationMsg{}, nil, fmt.Errorf("tss not found") + } + + cctx, msg := sample.OutboundVoteSim(r, creator, index, to, from, tss.TssPubkey) + msg.Status = defaultVote + + err = k.SetObserverOutboundInfo(ctx, to, &cctx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to set observer outbound info"), nil, err + } + + msg.OutboundTssNonce = cctx.GetCurrentOutboundParam().TssNonce + k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx, tss.TssPubkey) + + // 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 := GetRandomAccountAndObserver(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 + + err = firstMsg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first inbound 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) + + // 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 + } + + // 1) Schedule operations for votes + // 1.1) first pick a number of people to vote. + curNumVotesState = observerVotesTransitionMatrix.NextState(r, curNumVotesState) + numVotes := int(math.Ceil(float64(len(observerSet.ObserverList)) * statePercentageArray[curNumVotesState])) + + // 1.2) select who votes + whoVotes := r.Perm(len(observerSet.ObserverList)) + whoVotes = whoVotes[:numVotes] + + var fops []simtypes.FutureOperation + + ballotVotesState = ballotVotesTransitionMatrix.NextState(r, ballotVotesState) + yesVotePercentage := yesVotePercentageArray[ballotVotesState] + numberOfYesVotes := int(math.Ceil(float64(numVotes) * yesVotePercentage)) + vote := defaultVote + + for voteCount, observerIdx := range whoVotes { + if voteCount == numberOfYesVotes { + vote = alternativeVote + } + observerAddress := observerSet.ObserverList[observerIdx] + // firstVoter has already voted. + if observerAddress == firstVoter { + continue + } + observerAccount, err := GetObserverAccount(observerAddress, accs) + if err != nil { + continue + } + // 1.3) schedule the vote + votingMsg := msg + votingMsg.Creator = observerAddress + 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: operationSimulateVoteOutbound(k, votingMsg, observerAccount), + }) + } + return opMsg, fops, nil + + } +} diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index 249022fd11..4354f5aac4 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -2,23 +2,15 @@ package simulation import ( "fmt" - "math" "math/rand" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" 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" - ethcrypto "github.com/ethereum/go-ethereum/crypto" - - "github.com/zeta-chain/node/pkg/authz" "github.com/zeta-chain/node/pkg/chains" - "github.com/zeta-chain/node/testutil/sample" "github.com/zeta-chain/node/x/crosschain/keeper" - "github.com/zeta-chain/node/x/crosschain/types" observerTypes "github.com/zeta-chain/node/x/observer/types" ) @@ -154,416 +146,10 @@ func WeightedOperations( weightAddInboundTracker, SimulateMsgAddInboundTracker(k), ), - } -} - -func operationSimulateVoteOutbound( - k keeper.Keeper, - msg types.MsgVoteOutbound, - 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) - } -} - -// operationSimulateVoteInbound generates a MsgVoteInbound with a random vote and delivers it. -func operationSimulateVoteInbound( - k keeper.Keeper, - msg types.MsgVoteInbound, - 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) - } -} - -func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { - - defaultVote := chains.ReceiveStatus_success - alternativeVote := chains.ReceiveStatus_failed - observerVotesTransitionMatrix, statePercentageArray, curNumVotesState := ObserverVotesSimulationMatrix() - ballotVotesTransitionMatrix, yesVotePercentageArray, ballotVotesState := BallotVoteSimulationMatrix() - return func( - r *rand.Rand, - app *baseapp.BaseApp, - ctx sdk.Context, - accs []simtypes.Account, - chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - to, from := int64(1337), int64(101) - supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) - for _, chain := range supportedChains { - if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { - to = chain.ChainId - } - if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { - from = chain.ChainId - } - } - - _, creator, err := GetRandomAccountAndObserver(r, ctx, k, accs) - if err != nil { - return simtypes.OperationMsg{}, nil, nil - } - index := ethcrypto.Keccak256Hash([]byte(fmt.Sprintf("%d", r.Int63()))).Hex() - - tss, found := k.GetObserverKeeper().GetTSS(ctx) - if !found { - return simtypes.OperationMsg{}, nil, fmt.Errorf("tss not found") - } - - cctx, msg := sample.OutboundVoteSim(r, creator, index, to, from, tss.TssPubkey) - msg.Status = defaultVote - - err = k.SetObserverOutboundInfo(ctx, to, &cctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to set observer outbound info"), nil, err - } - msg.OutboundTssNonce = cctx.GetCurrentOutboundParam().TssNonce - k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx, tss.TssPubkey) - - // 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 := GetRandomAccountAndObserver(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 - - err = firstMsg.ValidateBasic() - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first inbound 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) - - // 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 - } - - // 1) Schedule operations for votes - // 1.1) first pick a number of people to vote. - curNumVotesState = observerVotesTransitionMatrix.NextState(r, curNumVotesState) - numVotes := int(math.Ceil(float64(len(observerSet.ObserverList)) * statePercentageArray[curNumVotesState])) - - // 1.2) select who votes - whoVotes := r.Perm(len(observerSet.ObserverList)) - whoVotes = whoVotes[:numVotes] - - var fops []simtypes.FutureOperation - - ballotVotesState = ballotVotesTransitionMatrix.NextState(r, ballotVotesState) - yesVotePercentage := yesVotePercentageArray[ballotVotesState] - numberOfYesVotes := int(math.Ceil(float64(numVotes) * yesVotePercentage)) - vote := defaultVote - - for voteCount, observerIdx := range whoVotes { - if voteCount == numberOfYesVotes { - vote = alternativeVote - } - observerAddress := observerSet.ObserverList[observerIdx] - // firstVoter has already voted. - if observerAddress == firstVoter { - continue - } - observerAccount, err := GetObserverAccount(observerAddress, accs) - if err != nil { - continue - } - // 1.3) schedule the vote - votingMsg := msg - votingMsg.Creator = observerAddress - 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: operationSimulateVoteOutbound(k, votingMsg, observerAccount), - }) - } - return opMsg, fops, nil - - } -} - -func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { - observerVotesTransitionMatrix, statePercentageArray, curNumVotesState := ObserverVotesSimulationMatrix() - return func( - r *rand.Rand, - app *baseapp.BaseApp, - ctx sdk.Context, - accs []simtypes.Account, - chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // TODO : randomize these values - // Right now we use a constant value for cctx creation , this is the same as the one used in unit tests for the successful condition. - // TestKeeper_VoteInbound/successfully vote on evm deposit - // But this can improved by adding more randomization - - to, from := int64(1337), int64(101) - supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) - for _, chain := range supportedChains { - if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { - from = chain.ChainId - } - if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { - to = chain.ChainId - } - } - - foriegnCoins := k.GetFungibleKeeper().GetAllForeignCoins(ctx) - asset := "" - - for _, coin := range foriegnCoins { - if coin.ForeignChainId == from { - asset = coin.Asset - } - } - - msg := sample.InboundVoteSim(from, to, r, asset) - // Return early if inbound is not enabled. - cf, found := k.GetObserverKeeper().GetCrosschainFlags(ctx) - if !found { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "crosschain flags not found"), nil, nil - } - if !cf.IsInboundEnabled { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "inbound is not enabled"), nil, nil - } - - // 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 := GetRandomAccountAndObserver(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 - - err = firstMsg.ValidateBasic() - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first inbound 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) - - // 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 - } - - // 1) Schedule operations for votes - // 1.1) first pick a number of people to vote. - curNumVotesState = observerVotesTransitionMatrix.NextState(r, curNumVotesState) - numVotes := int(math.Ceil(float64(len(observerSet.ObserverList)) * statePercentageArray[curNumVotesState])) - - // 1.2) select who votes - whoVotes := r.Perm(len(observerSet.ObserverList)) - whoVotes = whoVotes[:numVotes] - - var fops []simtypes.FutureOperation - - votingBlock := int64(1) - for _, observerIdx := range whoVotes { - observerAddress := observerSet.ObserverList[observerIdx] - // firstVoter has already voted. - if observerAddress == firstVoter { - continue - } - observerAccount, err := GetObserverAccount(observerAddress, accs) - if err != nil { - continue - } - // 1.3) schedule the vote - votingMsg := msg - votingMsg.Creator = observerAddress - - 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() + votingBlock), - Op: operationSimulateVoteInbound(k, votingMsg, observerAccount), - }) - //votingBlock++ - } - return opMsg, fops, nil - } -} - -// SimulateMsgVoteGasPrice generates a MsgVoteGasPrice and delivers it -func SimulateMsgVoteGasPrice(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) { - // Get a random account and observer - // If this returns an error, it is likely that the entire observer set has been removed - simAccount, randomObserver, err := GetRandomAccountAndObserver(r, ctx, k, accounts) - if err != nil { - return simtypes.OperationMsg{}, nil, nil - } - authAccount := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) - spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) - - supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) - if len(supportedChains) == 0 { - return simtypes.NoOpMsg( - types.ModuleName, - authz.GasPriceVoter.String(), - "no supported chains found", - ), nil, nil - } - randomChainID := GetRandomChainID(r, supportedChains) - - // Vote for random gas price. Gas prices do not use a ballot system, so we can vote directly without having to schedule future operations. - // The random nature of the price might create weird gas prices for the chain, but it is fine for now. We can remove the randomness if needed - msg := types.MsgVoteGasPrice{ - Creator: randomObserver, - ChainId: randomChainID, - Price: r.Uint64(), - PriorityFee: r.Uint64(), - BlockNumber: r.Uint64(), - Supply: fmt.Sprintf("%d", r.Int63()), - } - - // System contracts are deployed on the first block, so we cannot vote on gas prices before that - if ctx.BlockHeight() <= 1 { - return simtypes.NewOperationMsg(&msg, true, "block height less than 1", nil), nil, nil - } - - err = msg.ValidateBasic() - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate vote gas price msg"), nil, err - } - - 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, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) + simulation.NewWeightedOperation( + weightMsgAddOutboundTracker, + SimulateMsgAddOutboundTracker(k), + ), } } diff --git a/x/crosschain/types/keys.go b/x/crosschain/types/keys.go index 76d343dd3e..26422c6101 100644 --- a/x/crosschain/types/keys.go +++ b/x/crosschain/types/keys.go @@ -26,7 +26,8 @@ const ( ProtocolFee = 2000000000000000000 // CCTXIndexLength is the length of a crosschain transaction index - CCTXIndexLength = 66 + CCTXIndexLength = 66 + MaxOutboundTrackerHashes = 5 ) func GetProtocolFee() math.Uint { diff --git a/x/crosschain/types/outbound_tracker.go b/x/crosschain/types/outbound_tracker.go new file mode 100644 index 0000000000..821f90598e --- /dev/null +++ b/x/crosschain/types/outbound_tracker.go @@ -0,0 +1,5 @@ +package types + +func (o *OutboundTracker) IsMaxed() bool { + return len(o.HashList) >= MaxOutboundTrackerHashes +} From 1dc5135216ab5d2f192cb7e563851fc03c93f12c Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 25 Nov 2024 12:51:20 -0500 Subject: [PATCH 07/13] improve outbound tracker nonce selection --- .../simulation/operation_add_outbound_tracker.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/x/crosschain/simulation/operation_add_outbound_tracker.go b/x/crosschain/simulation/operation_add_outbound_tracker.go index edaaefe8e1..000877d3bb 100644 --- a/x/crosschain/simulation/operation_add_outbound_tracker.go +++ b/x/crosschain/simulation/operation_add_outbound_tracker.go @@ -56,8 +56,7 @@ func SimulateMsgAddOutboundTracker(k keeper.Keeper) simtypes.Operation { } // pick a random nonce from the pending nonces between 0 and nonceLow - //fmt.Printf("pendingNonces.NonceLow: %d | pendingNonces.NonceHigh: %d \n", - // pendingNonces.NonceLow, pendingNonces.NonceHigh) + // Ih nonce low is the same as nonce high, it means that there are no pending nonces to add trackers for if pendingNonces.NonceLow == pendingNonces.NonceHigh { return simtypes.NoOpMsg( types.ModuleName, @@ -65,8 +64,16 @@ func SimulateMsgAddOutboundTracker(k keeper.Keeper) simtypes.Operation { "no pending nonces found", ), nil, nil } - - nonce := pendingNonces.NonceLow + // Pick a random pending nonce + nonce := 0 + switch { + case pendingNonces.NonceHigh <= 1: + nonce = int(pendingNonces.NonceLow) + case pendingNonces.NonceLow == 0: + nonce = r.Intn(int(pendingNonces.NonceHigh)) + default: + nonce = r.Intn(int(pendingNonces.NonceHigh)-int(pendingNonces.NonceLow)) + int(pendingNonces.NonceLow) + } tracker, found := k.GetOutboundTracker(ctx, chainID, uint64(nonce)) if found && tracker.IsMaxed() { From bbb2e0b2da80bdfa4936d7ef092249ddca0c017a Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 26 Nov 2024 10:09:43 -0500 Subject: [PATCH 08/13] remove block limit for outbound tracker --- x/crosschain/keeper/msg_server_add_outbound_tracker.go | 2 +- x/crosschain/simulation/operation_add_outbound_tracker.go | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/x/crosschain/keeper/msg_server_add_outbound_tracker.go b/x/crosschain/keeper/msg_server_add_outbound_tracker.go index c8dc917602..e531206a38 100644 --- a/x/crosschain/keeper/msg_server_add_outbound_tracker.go +++ b/x/crosschain/keeper/msg_server_add_outbound_tracker.go @@ -110,7 +110,7 @@ func (k msgServer) AddOutboundTracker( } // check if max hashes are reached - if len(tracker.HashList) >= types.MaxOutboundTrackerHashes { + if tracker.IsMaxed() { return nil, types.ErrMaxTxOutTrackerHashesReached.Wrapf( "max hashes reached for chain %d, nonce %d, hash number: %d", msg.ChainId, diff --git a/x/crosschain/simulation/operation_add_outbound_tracker.go b/x/crosschain/simulation/operation_add_outbound_tracker.go index 000877d3bb..b7adba640b 100644 --- a/x/crosschain/simulation/operation_add_outbound_tracker.go +++ b/x/crosschain/simulation/operation_add_outbound_tracker.go @@ -75,6 +75,7 @@ func SimulateMsgAddOutboundTracker(k keeper.Keeper) simtypes.Operation { nonce = r.Intn(int(pendingNonces.NonceHigh)-int(pendingNonces.NonceLow)) + int(pendingNonces.NonceLow) } + // Verify if the tracker is maxed tracker, found := k.GetOutboundTracker(ctx, chainID, uint64(nonce)) if found && tracker.IsMaxed() { return simtypes.NoOpMsg( @@ -94,11 +95,6 @@ func SimulateMsgAddOutboundTracker(k keeper.Keeper) simtypes.Operation { TxIndex: 0, } - // System contracts are deployed on the first block, so we cannot vote on gas prices before that - if ctx.BlockHeight() <= 2 { - return simtypes.NewOperationMsg(&msg, true, "block height less than 1", nil), nil, nil - } - err = msg.ValidateBasic() if err != nil { return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate MsgAddOutboundTracker msg"), nil, err From f6f02c697c88b8e58ce0bc8ff2c29d7160747c42 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 26 Nov 2024 11:22:28 -0500 Subject: [PATCH 09/13] add operation remove outbound tracker --- .../operation_remove_outbound_tracker.go | 62 +++++++++++++++++++ x/crosschain/simulation/operations.go | 32 +++++++++- x/crosschain/types/expected_keepers.go | 2 + 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 x/crosschain/simulation/operation_remove_outbound_tracker.go diff --git a/x/crosschain/simulation/operation_remove_outbound_tracker.go b/x/crosschain/simulation/operation_remove_outbound_tracker.go new file mode 100644 index 0000000000..013110ca3b --- /dev/null +++ b/x/crosschain/simulation/operation_remove_outbound_tracker.go @@ -0,0 +1,62 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + 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/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" +) + +func SimulateMsgRemoveOutboundTracker(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) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveOutboundTracker, err.Error()), nil, nil + } + + authAccount := k.GetAuthKeeper().GetAccount(ctx, policyAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + trackers := k.GetAllOutboundTracker(ctx) + + if len(trackers) == 0 { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveOutboundTracker, "no outbound trackers found"), nil, nil + } + + randomTracker := trackers[r.Intn(len(trackers))] + + msg := types.MsgRemoveOutboundTracker{ + ChainId: randomTracker.ChainId, + Nonce: randomTracker.Nonce, + Creator: policyAccount.Address.String(), + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate MsgAddOutboundTracker msg"), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: policyAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index 4354f5aac4..a1cb8cf20b 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" observerTypes "github.com/zeta-chain/node/x/observer/types" ) @@ -21,11 +22,11 @@ import ( // TODO Add more details to comment based on what the number represents in terms of percentage of operations in a block // https://github.com/zeta-chain/node/issues/3100 const ( - DefaultWeightMsgAddOutboundTracker = 50 - DefaultWeightAddInboundTracker = 50 + DefaultWeightMsgAddOutboundTracker = 100 + DefaultWeightAddInboundTracker = 20 DefaultWeightRemoveOutboundTracker = 5 DefaultWeightVoteGasPrice = 100 - DefaultWeightVoteOutbound = 50 + DefaultWeightVoteOutbound = 100 DefaultWeightVoteInbound = 100 DefaultWeightWhitelistERC20 = 1 DefaultWeightMigrateTssFunds = 1 @@ -150,6 +151,10 @@ func WeightedOperations( weightMsgAddOutboundTracker, SimulateMsgAddOutboundTracker(k), ), + simulation.NewWeightedOperation( + weightRemoveOutboundTracker, + SimulateMsgRemoveOutboundTracker(k), + ), } } @@ -296,3 +301,24 @@ func BallotVoteSimulationMatrix() (simtypes.TransitionMatrix, []float64, int) { ballotVotesState := 1 return ballotTransitionMatrix, yesVoteArray, ballotVotesState } + +func GetPolicyAccount(ctx sdk.Context, k types.AuthorityKeeper, accounts []simtypes.Account) (simtypes.Account, error) { + policies, found := k.GetPolicies(ctx) + if !found { + return simtypes.Account{}, fmt.Errorf("policies object not found") + } + if len(policies.Items) == 0 { + return simtypes.Account{}, fmt.Errorf("no policies found") + } + + admin := policies.Items[0].Address + address, err := observerTypes.GetOperatorAddressFromAccAddress(admin) + if err != nil { + return simtypes.Account{}, err + } + simAccount, found := simtypes.FindAccount(accounts, address) + if !found { + return simtypes.Account{}, fmt.Errorf("admin account not found in list of simulation accounts") + } + return simAccount, nil +} diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 2fee9cb31a..7ca9f4b46e 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -9,6 +9,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ethcommon "github.com/ethereum/go-ethereum/common" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + authoritytypes "github.com/zeta-chain/node/x/authority/types" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" @@ -224,6 +225,7 @@ type FungibleKeeper interface { type AuthorityKeeper interface { CheckAuthorization(ctx sdk.Context, msg sdk.Msg) error GetAdditionalChainList(ctx sdk.Context) (list []chains.Chain) + GetPolicies(ctx sdk.Context) (val authoritytypes.Policies, found bool) } type LightclientKeeper interface { From 1b1331b34c3375a4deccae8bc06aee468766550e Mon Sep 17 00:00:00 2001 From: Tanmay Date: Wed, 27 Nov 2024 10:00:25 -0500 Subject: [PATCH 10/13] add operation whitelist erc20 --- pkg/chains/chain_filters.go | 5 ++ simulation/state.go | 24 +++++- testutil/sample/crosschain.go | 14 ++++ testutil/sample/crypto.go | 15 ++++ .../keeper/msg_server_whitelist_erc20.go | 1 + .../operation_add_outbound_tracker.go | 9 +- .../simulation/operation_gas_price_voter.go | 7 +- .../operation_remove_outbound_tracker.go | 2 +- .../simulation/operation_whitelist_erc20.go | 82 +++++++++++++++++++ x/crosschain/simulation/operations.go | 6 +- 10 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 x/crosschain/simulation/operation_whitelist_erc20.go diff --git a/pkg/chains/chain_filters.go b/pkg/chains/chain_filters.go index 3235fb98b1..c7961860e1 100644 --- a/pkg/chains/chain_filters.go +++ b/pkg/chains/chain_filters.go @@ -18,6 +18,11 @@ func FilterByConsensus(cs Consensus) ChainFilter { return func(chain Chain) bool { return chain.Consensus == cs } } +// FilterByVM filters chains by VM type +func FilterByVM(vm Vm) ChainFilter { + return func(chain Chain) bool { return chain.Vm == vm } +} + // FilterChains applies a list of filters to a list of chains func FilterChains(chainList []Chain, filters ...ChainFilter) []Chain { // Apply each filter to the list of supported chains diff --git a/simulation/state.go b/simulation/state.go index 7b534b4684..79a3a60ff1 100644 --- a/simulation/state.go +++ b/simulation/state.go @@ -22,8 +22,9 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" - chains2 "github.com/zeta-chain/node/pkg/chains" + zetachains "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" zetaapp "github.com/zeta-chain/node/app" "github.com/zeta-chain/node/testutil/sample" @@ -133,7 +134,7 @@ func updateObserverState(t *testing.T, rawState map[string]json.RawMessage, cdc tss.OperatorAddressList = observers observerState.Tss = &tss - chains := chains2.DefaultChainsList() + chains := zetachains.DefaultChainsList() var chainsNonces []observertypes.ChainNonces var pendingNonces []observertypes.PendingNonces for _, chain := range chains { @@ -186,6 +187,23 @@ func updateAuthorityState(t *testing.T, rawState map[string]json.RawMessage, cdc return authorityState } +func updateCrossChainState(t *testing.T, rawState map[string]json.RawMessage, cdc codec.Codec, r *rand.Rand) *crosschaintypes.GenesisState { + crossChainStateBz, ok := rawState[crosschaintypes.ModuleName] + require.True(t, ok, "crosschain genesis state is missing") + + crossChainState := new(crosschaintypes.GenesisState) + cdc.MustUnmarshalJSON(crossChainStateBz, crossChainState) + + gasPriceList := []crosschaintypes.GasPrice{} + + chains := zetachains.DefaultChainsList() + for _, chain := range chains { + gasPriceList = append(gasPriceList, sample.GasPriceFromRand(r, chain.ChainId)) + } + + return crossChainState +} + func updateFungibleState(t *testing.T, rawState map[string]json.RawMessage, cdc codec.Codec, r *rand.Rand) *fungibletypes.GenesisState { fungibleStateBz, ok := rawState[fungibletypes.ModuleName] require.True(t, ok, "fungible genesis state is missing") @@ -199,7 +217,7 @@ func updateFungibleState(t *testing.T, rawState map[string]json.RawMessage, cdc } foreignCoins := make([]fungibletypes.ForeignCoins, 0) - chains := chains2.DefaultChainsList() + chains := zetachains.DefaultChainsList() for _, chain := range chains { foreignCoin := fungibletypes.ForeignCoins{ diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index 127a37dc6e..cfc10cfc89 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -118,6 +118,20 @@ func GasPriceWithChainID(t *testing.T, chainID int64) types.GasPrice { } } +func GasPriceFromRand(r *rand.Rand, chainID int64) types.GasPrice { + price := r.Uint64() + priorityFee := r.Uint64() % price + return types.GasPrice{ + Creator: "", + ChainId: chainID, + Signers: []string{AccAddressFromRand(r), AccAddressFromRand(r)}, + BlockNums: []uint64{r.Uint64()}, + Prices: []uint64{price}, + MedianIndex: 0, + PriorityFees: []uint64{priorityFee}, + } +} + func InboundParams(r *rand.Rand) *types.InboundParams { return &types.InboundParams{ Sender: EthAddress().String(), diff --git a/testutil/sample/crypto.go b/testutil/sample/crypto.go index c8cc555dfd..7abecc15bf 100644 --- a/testutil/sample/crypto.go +++ b/testutil/sample/crypto.go @@ -90,6 +90,14 @@ func SolanaAddress(t *testing.T) string { return privKey.PublicKey().String() } +func SolAddressFromRand(t *testing.T, r *rand.Rand) string { + privKey, err := solana.NewRandomPrivateKey() + if err != nil { + panic(err) + } + return privKey.PublicKey().String() +} + // SolanaSignature returns a sample solana signature func SolanaSignature(t *testing.T) solana.Signature { // Generate a random keypair @@ -143,6 +151,13 @@ func AccAddress() string { return sdk.AccAddress(addr).String() } +// AccAddressFromRand returns a sample account address in string +func AccAddressFromRand(r *rand.Rand) string { + pk := PubKey(r) + addr := pk.Address() + return sdk.AccAddress(addr).String() +} + // ValAddress returns a sample validator operator address func ValAddress(r *rand.Rand) sdk.ValAddress { return sdk.ValAddress(PubKey(r).Address()) diff --git a/x/crosschain/keeper/msg_server_whitelist_erc20.go b/x/crosschain/keeper/msg_server_whitelist_erc20.go index 197310e16c..2b35d22e56 100644 --- a/x/crosschain/keeper/msg_server_whitelist_erc20.go +++ b/x/crosschain/keeper/msg_server_whitelist_erc20.go @@ -107,6 +107,7 @@ func (k msgServer) WhitelistERC20( msg.ChainId, ) } + if zrc20Addr == (ethcommon.Address{}) { return nil, errorsmod.Wrapf( types.ErrDeployContract, diff --git a/x/crosschain/simulation/operation_add_outbound_tracker.go b/x/crosschain/simulation/operation_add_outbound_tracker.go index b7adba640b..744fed3435 100644 --- a/x/crosschain/simulation/operation_add_outbound_tracker.go +++ b/x/crosschain/simulation/operation_add_outbound_tracker.go @@ -21,11 +21,18 @@ func SimulateMsgAddOutboundTracker(k keeper.Keeper) simtypes.Operation { chainID := int64(1337) supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + if len(supportedChains) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAddOutboundTracker, + "no supported chains found", + ), nil, nil + } + for _, chain := range supportedChains { if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { chainID = chain.ChainId } - } // Get a random account and observer // If this returns an error, it is likely that the entire observer set has been removed diff --git a/x/crosschain/simulation/operation_gas_price_voter.go b/x/crosschain/simulation/operation_gas_price_voter.go index 89aeac1213..013cb4df22 100644 --- a/x/crosschain/simulation/operation_gas_price_voter.go +++ b/x/crosschain/simulation/operation_gas_price_voter.go @@ -39,11 +39,14 @@ func SimulateMsgVoteGasPrice(k keeper.Keeper) simtypes.Operation { // Vote for random gas price. Gas prices do not use a ballot system, so we can vote directly without having to schedule future operations. // The random nature of the price might create weird gas prices for the chain, but it is fine for now. We can remove the randomness if needed + price := r.Uint64() + // Select priority fee between 0 and price + priorityFee := r.Uint64() % price msg := types.MsgVoteGasPrice{ Creator: randomObserver, ChainId: randomChainID, - Price: r.Uint64(), - PriorityFee: r.Uint64(), + Price: price, + PriorityFee: priorityFee, BlockNumber: r.Uint64(), Supply: fmt.Sprintf("%d", r.Int63()), } diff --git a/x/crosschain/simulation/operation_remove_outbound_tracker.go b/x/crosschain/simulation/operation_remove_outbound_tracker.go index 013110ca3b..dd9bbb92db 100644 --- a/x/crosschain/simulation/operation_remove_outbound_tracker.go +++ b/x/crosschain/simulation/operation_remove_outbound_tracker.go @@ -39,7 +39,7 @@ func SimulateMsgRemoveOutboundTracker(k keeper.Keeper) simtypes.Operation { err = msg.ValidateBasic() if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate MsgAddOutboundTracker msg"), nil, err + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate MsgRemoveOutboundTracker msg"), nil, err } txCtx := simulation.OperationInput{ diff --git a/x/crosschain/simulation/operation_whitelist_erc20.go b/x/crosschain/simulation/operation_whitelist_erc20.go new file mode 100644 index 0000000000..7776ee79cb --- /dev/null +++ b/x/crosschain/simulation/operation_whitelist_erc20.go @@ -0,0 +1,82 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + 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/pkg/chains" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" +) + +func SimulateMsgWhitelistERC20(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) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWhitelistERC20, err.Error()), nil, nil + } + + authAccount := k.GetAuthKeeper().GetAccount(ctx, policyAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + if len(supportedChains) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgWhitelistERC20, + "no supported chains found", + ), nil, nil + } + + filteredChains := chains.FilterChains(supportedChains, chains.FilterByVM(chains.Vm_evm)) + + //pick a random chain + randomChain := supportedChains[r.Intn(len(filteredChains))] + var tokenAddress string + switch { + case randomChain.IsEVMChain(): + tokenAddress = sample.EthAddressFromRand(r).String() + //updatedTokenAddress = sample.EthAddressFromRand(r).String() + default: + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWhitelistERC20, "unsupported chain"), nil, nil + } + + msg := types.MsgWhitelistERC20{ + Creator: policyAccount.Address.String(), + ChainId: randomChain.ChainId, + Erc20Address: tokenAddress, + GasLimit: 100000, + Decimals: 18, + Name: "Test", + Symbol: "TST", + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate MsgRemoveOutboundTracker msg"), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: policyAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index a1cb8cf20b..21c6983578 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -28,7 +28,7 @@ const ( DefaultWeightVoteGasPrice = 100 DefaultWeightVoteOutbound = 100 DefaultWeightVoteInbound = 100 - DefaultWeightWhitelistERC20 = 1 + DefaultWeightWhitelistERC20 = 10 DefaultWeightMigrateTssFunds = 1 DefaultWeightUpdateTssAddress = 1 DefaultWeightAbortStuckCCTX = 10 @@ -155,6 +155,10 @@ func WeightedOperations( weightRemoveOutboundTracker, SimulateMsgRemoveOutboundTracker(k), ), + simulation.NewWeightedOperation( + weightWhitelistERC20, + SimulateMsgWhitelistERC20(k), + ), } } From a8adac623b11a32be87c00158fbe6bf619a86106 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Wed, 27 Nov 2024 10:14:49 -0500 Subject: [PATCH 11/13] fix unit tests --- simulation/state.go | 5 +-- testutil/keeper/mocks/crosschain/authority.go | 32 ++++++++++++++++++- testutil/sample/crosschain.go | 2 -- .../msg_server_add_outbound_tracker_test.go | 4 +-- zetaclient/chains/evm/observer/outbound.go | 3 +- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/simulation/state.go b/simulation/state.go index 79a3a60ff1..ef707af306 100644 --- a/simulation/state.go +++ b/simulation/state.go @@ -135,8 +135,8 @@ func updateObserverState(t *testing.T, rawState map[string]json.RawMessage, cdc observerState.Tss = &tss chains := zetachains.DefaultChainsList() - var chainsNonces []observertypes.ChainNonces - var pendingNonces []observertypes.PendingNonces + chainsNonces := make([]observertypes.ChainNonces, 0) + pendingNonces := make([]observertypes.PendingNonces, 0) for _, chain := range chains { chainNonce := observertypes.ChainNonces{ ChainId: chain.ChainId, @@ -250,6 +250,7 @@ func updateRawState(t *testing.T, rawState map[string]json.RawMessage, cdc codec rawState[observertypes.ModuleName] = cdc.MustMarshalJSON(observerState) rawState[authoritytypes.ModuleName] = cdc.MustMarshalJSON(authorityState) rawState[fungibletypes.ModuleName] = cdc.MustMarshalJSON(fungibleState) + rawState[crosschaintypes.ModuleName] = cdc.MustMarshalJSON(updateCrossChainState(t, rawState, cdc, r)) } // AppStateFn returns the initial application state using a genesis or the simulation parameters. diff --git a/testutil/keeper/mocks/crosschain/authority.go b/testutil/keeper/mocks/crosschain/authority.go index 7dafb1331b..9f2a9a8837 100644 --- a/testutil/keeper/mocks/crosschain/authority.go +++ b/testutil/keeper/mocks/crosschain/authority.go @@ -3,8 +3,10 @@ package mocks import ( - mock "github.com/stretchr/testify/mock" chains "github.com/zeta-chain/node/pkg/chains" + authoritytypes "github.com/zeta-chain/node/x/authority/types" + + mock "github.com/stretchr/testify/mock" types "github.com/cosmos/cosmos-sdk/types" ) @@ -52,6 +54,34 @@ func (_m *CrosschainAuthorityKeeper) GetAdditionalChainList(ctx types.Context) [ return r0 } +// GetPolicies provides a mock function with given fields: ctx +func (_m *CrosschainAuthorityKeeper) GetPolicies(ctx types.Context) (authoritytypes.Policies, bool) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetPolicies") + } + + var r0 authoritytypes.Policies + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context) (authoritytypes.Policies, bool)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(types.Context) authoritytypes.Policies); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(authoritytypes.Policies) + } + + if rf, ok := ret.Get(1).(func(types.Context) bool); ok { + r1 = rf(ctx) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // NewCrosschainAuthorityKeeper creates a new instance of CrosschainAuthorityKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewCrosschainAuthorityKeeper(t interface { diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index cfc10cfc89..5d86a41203 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -348,7 +348,6 @@ func OutboundVoteSim(r *rand.Rand, from int64, tssPubkey string, ) (types.CrossChainTx, types.MsgVoteOutbound) { - coinType := CoinTypeFromRand(r) amount := math.NewUint(uint64(r.Int63())) @@ -409,7 +408,6 @@ func OutboundVoteSim(r *rand.Rand, } return cctx, msg - } func ZRC20Withdrawal(to []byte, value *big.Int) *zrc20.ZRC20Withdrawal { diff --git a/x/crosschain/keeper/msg_server_add_outbound_tracker_test.go b/x/crosschain/keeper/msg_server_add_outbound_tracker_test.go index d243a3a7e6..171c81fd71 100644 --- a/x/crosschain/keeper/msg_server_add_outbound_tracker_test.go +++ b/x/crosschain/keeper/msg_server_add_outbound_tracker_test.go @@ -249,8 +249,8 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) { observerMock.On("IsNonTombstonedObserver", mock.Anything, mock.Anything).Return(false) keepertest.MockCctxByNonce(t, ctx, *k, observerMock, types.CctxStatus_PendingOutbound, false) - hashes := make([]*types.TxHash, keeper.MaxOutboundTrackerHashes) - for i := 0; i < keeper.MaxOutboundTrackerHashes; i++ { + hashes := make([]*types.TxHash, types.MaxOutboundTrackerHashes) + for i := 0; i < types.MaxOutboundTrackerHashes; i++ { hashes[i] = &types.TxHash{ TxHash: sample.Hash().Hex(), } diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go index f8ce8f32ba..d6526e4289 100644 --- a/zetaclient/chains/evm/observer/outbound.go +++ b/zetaclient/chains/evm/observer/outbound.go @@ -20,7 +20,6 @@ import ( "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" - crosschainkeeper "github.com/zeta-chain/node/x/crosschain/keeper" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" "github.com/zeta-chain/node/zetaclient/chains/evm" "github.com/zeta-chain/node/zetaclient/chains/interfaces" @@ -126,7 +125,7 @@ func (ob *Observer) ProcessOutboundTrackers(ctx context.Context) error { // should not happen. We can't tell which txHash is true. It might happen (e.g. bug, glitchy/hacked endpoint) ob.Logger().Outbound.Error().Msgf("WatchOutbound: confirmed multiple (%d) outbound for chain %d nonce %d", txCount, chainID, nonce) } else { - if len(tracker.HashList) == crosschainkeeper.MaxOutboundTrackerHashes { + if len(tracker.HashList) == crosschaintypes.MaxOutboundTrackerHashes { ob.Logger().Outbound.Error().Msgf("WatchOutbound: outbound tracker is full of hashes for chain %d nonce %d", chainID, nonce) } } From de55103223d9a9e8aa2b3df84a1dbcd5bf9c67b1 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Wed, 27 Nov 2024 16:03:28 -0500 Subject: [PATCH 12/13] fix unit tests --- testutil/sample/crosschain.go | 12 +- .../keeper/msg_server_abort_stuck_cctx.go | 3 + .../simulation/operation_abort_stuck_cctx.go | 165 ++++++++++++++++++ .../simulation/operation_vote_outbound.go | 17 +- x/crosschain/simulation/operations.go | 14 +- 5 files changed, 201 insertions(+), 10 deletions(-) create mode 100644 x/crosschain/simulation/operation_abort_stuck_cctx.go diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index 5d86a41203..630282c770 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -341,13 +341,13 @@ func InboundVoteSim(from, to int64, r *rand.Rand, asset string) types.MsgVoteInb } } -func OutboundVoteSim(r *rand.Rand, +func CCTXfromRand(r *rand.Rand, creator string, index string, to int64, from int64, tssPubkey string, -) (types.CrossChainTx, types.MsgVoteOutbound) { +) types.CrossChainTx { coinType := CoinTypeFromRand(r) amount := math.NewUint(uint64(r.Int63())) @@ -392,13 +392,19 @@ func OutboundVoteSim(r *rand.Rand, InboundParams: inbound, OutboundParams: []*types.OutboundParams{outbound}, } + return cctx +} + +func OutboundVoteSim(r *rand.Rand, + cctx types.CrossChainTx, +) (types.CrossChainTx, types.MsgVoteOutbound) { msg := types.MsgVoteOutbound{ CctxHash: cctx.Index, OutboundTssNonce: cctx.GetCurrentOutboundParam().TssNonce, OutboundChain: cctx.GetCurrentOutboundParam().ReceiverChainId, Status: chains.ReceiveStatus_success, - Creator: creator, + Creator: cctx.Creator, ObservedOutboundHash: ethcommon.BytesToHash(EthAddressFromRand(r).Bytes()).String(), ValueReceived: cctx.GetCurrentOutboundParam().Amount, ObservedOutboundBlockHeight: cctx.GetCurrentOutboundParam().ObservedExternalHeight, diff --git a/x/crosschain/keeper/msg_server_abort_stuck_cctx.go b/x/crosschain/keeper/msg_server_abort_stuck_cctx.go index 6ba922e804..fd2e9aba7d 100644 --- a/x/crosschain/keeper/msg_server_abort_stuck_cctx.go +++ b/x/crosschain/keeper/msg_server_abort_stuck_cctx.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "fmt" "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -40,6 +41,8 @@ func (k msgServer) AbortStuckCCTX( cctx.CctxStatus.Status == types.CctxStatus_PendingInbound || cctx.CctxStatus.Status == types.CctxStatus_PendingRevert if !isPending { + fmt.Println("CCTX not in pending", cctx.Index, cctx.CctxStatus.Status) + return nil, types.ErrStatusNotPending } diff --git a/x/crosschain/simulation/operation_abort_stuck_cctx.go b/x/crosschain/simulation/operation_abort_stuck_cctx.go new file mode 100644 index 0000000000..55eb05000a --- /dev/null +++ b/x/crosschain/simulation/operation_abort_stuck_cctx.go @@ -0,0 +1,165 @@ +package simulation + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + 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/pkg/chains" + "github.com/zeta-chain/node/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" +) + +func SimulateMsgAbortStuckCCTX(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) { + chainID := int64(1337) + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + if len(supportedChains) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAbortStuckCCTX, + "no supported chains found", + ), nil, nil + } + + for _, chain := range supportedChains { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { + chainID = chain.ChainId + } + } + + policyAccount, err := GetPolicyAccount(ctx, k.GetAuthorityKeeper(), accounts) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAbortStuckCCTX, err.Error()), nil, nil + } + + authAccount := k.GetAuthKeeper().GetAccount(ctx, policyAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + //_, creator, err := GetRandomAccountAndObserver(r, ctx, k, accounts) + //if err != nil { + // return simtypes.OperationMsg{}, nil, nil + //} + //index := ethcrypto.Keccak256Hash([]byte(fmt.Sprintf("%d", r.Int63()))).Hex() + // + tss, found := k.GetObserverKeeper().GetTSS(ctx) + if !found { + return simtypes.OperationMsg{}, nil, fmt.Errorf("tss not found") + } + // + //cctx := sample.CCTXfromRand(r, creator, index, to, from, tss.TssPubkey) + //cctx.CctxStatus = &types.Status{ + // Status: types.CctxStatus_Aborted, + // StatusMessage: "testing SimulateMsgAbortStuckCCTX", + // ErrorMessage: "SimulateMsgAbortStuckCCTX", + // LastUpdateTimestamp: r.Int63(), + // IsAbortRefunded: false, + // CreatedTimestamp: r.Int63(), + //} + + fmt.Println("\n------------------------------------------------------") + fmt.Println("Block height:", ctx.BlockHeight()) + + pendingNonces, found := k.GetObserverKeeper().GetPendingNonces(ctx, tss.TssPubkey, chainID) + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAbortStuckCCTX, + "no pending nonces found", + ), nil, nil + } + + // pick a random nonce from the pending nonces between 0 and nonceLow + // If nonce low is the same as nonce high, it means that there are no pending nonces to add trackers for + if pendingNonces.NonceLow == pendingNonces.NonceHigh { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAddOutboundTracker, + "no pending nonces found", + ), nil, nil + } + fmt.Println("Pending nonces:", pendingNonces.NonceLow, pendingNonces.NonceHigh) + for i := pendingNonces.NonceLow; i < pendingNonces.NonceHigh; i++ { + fmt.Println("Checking nonce:", i) + nonceToCctx, found := k.GetObserverKeeper().GetNonceToCctx(ctx, tss.TssPubkey, chainID, int64(i)) + if !found { + fmt.Println("NonceToCctx not found:", chainID, i) + continue + } + + cctx, found := k.GetCrossChainTx(ctx, nonceToCctx.CctxIndex) + if !found { + fmt.Println("CCTX not found:", chainID, i) + continue + } + fmt.Println("CCTX found:", cctx.Index, cctx.CctxStatus.Status, cctx.GetCurrentOutboundParam().TssNonce) + //if cctx.CctxStatus.Status == types.CctxStatus_Aborted { + // fmt.Println("CCTX already aborted:", cctx.Index) + // continue + //} + } + + // Pick a random pending nonce + nonce := 0 + switch { + case pendingNonces.NonceHigh <= 1: + nonce = int(pendingNonces.NonceLow) + case pendingNonces.NonceLow == 0: + nonce = r.Intn(int(pendingNonces.NonceHigh)) + default: + nonce = r.Intn(int(pendingNonces.NonceHigh)-int(pendingNonces.NonceLow)) + int(pendingNonces.NonceLow) + } + + nonceToCctx, found := k.GetObserverKeeper().GetNonceToCctx(ctx, tss.TssPubkey, chainID, int64(nonce)) + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAbortStuckCCTX, + "no cctx found", + ), nil, nil + } + + cctx, found := k.GetCrossChainTx(ctx, nonceToCctx.CctxIndex) + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAbortStuckCCTX, + "no cctx found", + ), nil, nil + } + fmt.Println("CCTX picked for abort:", cctx.Index, cctx.CctxStatus.Status, cctx.GetCurrentOutboundParam().TssNonce) + + msg := types.MsgAbortStuckCCTX{ + Creator: policyAccount.Address.String(), + CctxIndex: cctx.Index, + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate MsgAbortStuckCCTX msg"), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: policyAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/crosschain/simulation/operation_vote_outbound.go b/x/crosschain/simulation/operation_vote_outbound.go index 13ddc35222..ecc86208a2 100644 --- a/x/crosschain/simulation/operation_vote_outbound.go +++ b/x/crosschain/simulation/operation_vote_outbound.go @@ -11,6 +11,7 @@ import ( moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" + ethcommon "github.com/ethereum/go-ethereum/common" ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/zeta-chain/node/pkg/authz" "github.com/zeta-chain/node/pkg/chains" @@ -89,8 +90,20 @@ func SimulateVoteOutbound(k keeper.Keeper) simtypes.Operation { return simtypes.OperationMsg{}, nil, fmt.Errorf("tss not found") } - cctx, msg := sample.OutboundVoteSim(r, creator, index, to, from, tss.TssPubkey) - msg.Status = defaultVote + cctx := sample.CCTXfromRand(r, creator, index, to, from, tss.TssPubkey) + msg := types.MsgVoteOutbound{ + CctxHash: cctx.Index, + OutboundTssNonce: cctx.GetCurrentOutboundParam().TssNonce, + OutboundChain: cctx.GetCurrentOutboundParam().ReceiverChainId, + Status: defaultVote, + Creator: cctx.Creator, + ObservedOutboundHash: ethcommon.BytesToHash(sample.EthAddressFromRand(r).Bytes()).String(), + ValueReceived: cctx.GetCurrentOutboundParam().Amount, + ObservedOutboundBlockHeight: cctx.GetCurrentOutboundParam().ObservedExternalHeight, + ObservedOutboundEffectiveGasPrice: cctx.GetCurrentOutboundParam().EffectiveGasPrice, + ObservedOutboundGasUsed: cctx.GetCurrentOutboundParam().GasUsed, + CoinType: cctx.InboundParams.CoinType, + } err = k.SetObserverOutboundInfo(ctx, to, &cctx) if err != nil { diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index 21c6983578..93bb347c15 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -22,7 +22,7 @@ import ( // TODO Add more details to comment based on what the number represents in terms of percentage of operations in a block // https://github.com/zeta-chain/node/issues/3100 const ( - DefaultWeightMsgAddOutboundTracker = 100 + DefaultWeightAddOutboundTracker = 100 DefaultWeightAddInboundTracker = 20 DefaultWeightRemoveOutboundTracker = 5 DefaultWeightVoteGasPrice = 100 @@ -51,7 +51,7 @@ const ( func WeightedOperations( appParams simtypes.AppParams, cdc codec.JSONCodec, k keeper.Keeper) simulation.WeightedOperations { var ( - weightMsgAddOutboundTracker int + weightAddOutboundTracker int weightAddInboundTracker int weightRemoveOutboundTracker int weightVoteGasPrice int @@ -64,9 +64,9 @@ func WeightedOperations( weightUpdateRateLimiterFlags int ) - appParams.GetOrGenerate(cdc, OpWeightMsgAddOutboundTracker, &weightMsgAddOutboundTracker, nil, + appParams.GetOrGenerate(cdc, OpWeightMsgAddOutboundTracker, &weightAddOutboundTracker, nil, func(_ *rand.Rand) { - weightMsgAddOutboundTracker = DefaultWeightMsgAddOutboundTracker + weightAddOutboundTracker = DefaultWeightAddOutboundTracker }, ) @@ -148,7 +148,7 @@ func WeightedOperations( SimulateMsgAddInboundTracker(k), ), simulation.NewWeightedOperation( - weightMsgAddOutboundTracker, + weightAddOutboundTracker, SimulateMsgAddOutboundTracker(k), ), simulation.NewWeightedOperation( @@ -159,6 +159,10 @@ func WeightedOperations( weightWhitelistERC20, SimulateMsgWhitelistERC20(k), ), + simulation.NewWeightedOperation( + weightAbortStuckCCTX, + SimulateMsgAbortStuckCCTX(k), + ), } } From e5c5fc706cd1568c86a552f68103ae7525619007 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Wed, 27 Nov 2024 17:42:38 -0500 Subject: [PATCH 13/13] fix Abort CCTX test --- .../simulation/operation_abort_stuck_cctx.go | 49 +++++++++++-------- x/crosschain/simulation/operations.go | 9 ++++ x/crosschain/types/status.go | 8 +++ 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/x/crosschain/simulation/operation_abort_stuck_cctx.go b/x/crosschain/simulation/operation_abort_stuck_cctx.go index 55eb05000a..ab8a0655fd 100644 --- a/x/crosschain/simulation/operation_abort_stuck_cctx.go +++ b/x/crosschain/simulation/operation_abort_stuck_cctx.go @@ -84,26 +84,26 @@ func SimulateMsgAbortStuckCCTX(k keeper.Keeper) simtypes.Operation { "no pending nonces found", ), nil, nil } - fmt.Println("Pending nonces:", pendingNonces.NonceLow, pendingNonces.NonceHigh) - for i := pendingNonces.NonceLow; i < pendingNonces.NonceHigh; i++ { - fmt.Println("Checking nonce:", i) - nonceToCctx, found := k.GetObserverKeeper().GetNonceToCctx(ctx, tss.TssPubkey, chainID, int64(i)) - if !found { - fmt.Println("NonceToCctx not found:", chainID, i) - continue - } - - cctx, found := k.GetCrossChainTx(ctx, nonceToCctx.CctxIndex) - if !found { - fmt.Println("CCTX not found:", chainID, i) - continue - } - fmt.Println("CCTX found:", cctx.Index, cctx.CctxStatus.Status, cctx.GetCurrentOutboundParam().TssNonce) - //if cctx.CctxStatus.Status == types.CctxStatus_Aborted { - // fmt.Println("CCTX already aborted:", cctx.Index) - // continue - //} - } + //fmt.Println("Pending nonces:", pendingNonces.NonceLow, pendingNonces.NonceHigh) + //for i := pendingNonces.NonceLow; i < pendingNonces.NonceHigh; i++ { + // fmt.Println("Checking nonce:", i) + // nonceToCctx, found := k.GetObserverKeeper().GetNonceToCctx(ctx, tss.TssPubkey, chainID, int64(i)) + // if !found { + // fmt.Println("NonceToCctx not found:", chainID, i) + // continue + // } + // + // cctx, found := k.GetCrossChainTx(ctx, nonceToCctx.CctxIndex) + // if !found { + // fmt.Println("CCTX not found:", chainID, i) + // continue + // } + // fmt.Println("CCTX found:", cctx.Index, cctx.CctxStatus.Status, cctx.GetCurrentOutboundParam().TssNonce) + // //if cctx.CctxStatus.Status == types.CctxStatus_Aborted { + // // fmt.Println("CCTX already aborted:", cctx.Index) + // // continue + // //} + //} // Pick a random pending nonce nonce := 0 @@ -133,7 +133,14 @@ func SimulateMsgAbortStuckCCTX(k keeper.Keeper) simtypes.Operation { "no cctx found", ), nil, nil } - fmt.Println("CCTX picked for abort:", cctx.Index, cctx.CctxStatus.Status, cctx.GetCurrentOutboundParam().TssNonce) + + if !cctx.CctxStatus.Status.IsPendingStatus() { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgAbortStuckCCTX, + "cctx not in pending status", + ), nil, nil + } msg := types.MsgAbortStuckCCTX{ Creator: policyAccount.Address.String(), diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go index 93bb347c15..4d74019a95 100644 --- a/x/crosschain/simulation/operations.go +++ b/x/crosschain/simulation/operations.go @@ -33,6 +33,7 @@ const ( DefaultWeightUpdateTssAddress = 1 DefaultWeightAbortStuckCCTX = 10 DefaultWeightUpdateRateLimiterFlags = 1 + DefaultWeightRefundAbortedCCTX = 1 OpWeightMsgAddOutboundTracker = "op_weight_msg_add_outbound_tracker" // #nosec G101 not a hardcoded credential OpWeightAddInboundTracker = "op_weight_msg_add_inbound_tracker" // #nosec G101 not a hardcoded credential @@ -45,6 +46,7 @@ const ( OpWeightUpdateTssAddress = "op_weight_msg_update_tss_address" // #nosec G101 not a hardcoded credential OpWeightAbortStuckCCTX = "op_weight_msg_abort_stuck_cctx" // #nosec G101 not a hardcoded credential OpWeightUpdateRateLimiterFlags = "op_weight_msg_update_rate_limiter_flags" // #nosec G101 not a hardcoded credential + OpWeightRefundAbortedCCTX = "op_weight_msg_refund_aborted_cctx" // #nosec G101 not a hardcoded credential ) @@ -62,6 +64,7 @@ func WeightedOperations( weightUpdateTssAddress int weightAbortStuckCCTX int weightUpdateRateLimiterFlags int + weightRefundAbortedCCTX int ) appParams.GetOrGenerate(cdc, OpWeightMsgAddOutboundTracker, &weightAddOutboundTracker, nil, @@ -130,6 +133,12 @@ func WeightedOperations( }, ) + appParams.GetOrGenerate(cdc, OpWeightRefundAbortedCCTX, &weightRefundAbortedCCTX, nil, + func(_ *rand.Rand) { + weightRefundAbortedCCTX = DefaultWeightRefundAbortedCCTX + }, + ) + return simulation.WeightedOperations{ simulation.NewWeightedOperation( weightVoteGasPrice, diff --git a/x/crosschain/types/status.go b/x/crosschain/types/status.go index 00c08bf6f7..d5eb303ab9 100644 --- a/x/crosschain/types/status.go +++ b/x/crosschain/types/status.go @@ -86,3 +86,11 @@ func stateTransitionMap() map[CctxStatus][]CctxStatus { } return stateTransitionMap } + +func (c CctxStatus) IsTerminalStatus() bool { + return c == CctxStatus_Aborted || c == CctxStatus_Reverted || c == CctxStatus_OutboundMined +} + +func (c CctxStatus) IsPendingStatus() bool { + return c == CctxStatus_PendingInbound || c == CctxStatus_PendingOutbound || c == CctxStatus_PendingRevert +}