From a37fb583b7cae9366996cad74f2cca5f08860a77 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Wed, 11 Sep 2024 17:17:22 -0400 Subject: [PATCH 1/4] test: skip precompiles test for tss migration (#2867) * fix e2e test for tss migration * add changelog --- changelog.md | 3 ++- contrib/localnet/orchestrator/start-zetae2e.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 2d0edbe190..c1a4ef1acd 100644 --- a/changelog.md +++ b/changelog.md @@ -34,7 +34,8 @@ * [2726](https://github.com/zeta-chain/node/pull/2726) - add e2e tests for deposit and call, deposit and revert * [2703](https://github.com/zeta-chain/node/pull/2703) - add e2e tests for stateful precompiled contracts * [2763](https://github.com/zeta-chain/node/pull/2763) - add V2 contracts migration test -* [2830] (https://github.com/zeta-chain/node/pull/2830) - extend staking precompile tests +* [2830](https://github.com/zeta-chain/node/pull/2830) - extend staking precompile tests +* [2867](https://github.com/zeta-chain/node/pull/2867) - skip precompiles test for tss migration ### Fixes diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index b26a1c7544..69cff1532a 100644 --- a/contrib/localnet/orchestrator/start-zetae2e.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -181,7 +181,7 @@ if [ "$LOCALNET_MODE" == "tss-migrate" ]; then echo "waiting 10 seconds for node to restart" sleep 10 - zetae2e local --skip-setup --config deployed.yml --skip-bitcoin-setup --light --skip-header-proof + zetae2e local --skip-setup --config deployed.yml --skip-bitcoin-setup --light --skip-header-proof --skip-precompiles ZETAE2E_EXIT_CODE=$? if [ $ZETAE2E_EXIT_CODE -eq 0 ]; then echo "E2E passed after migration" From 1929ff660aac572b27e94729bf9e055cc31b32c0 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Wed, 11 Sep 2024 14:26:34 -0700 Subject: [PATCH 2/4] fix(`zetaclient`): add zetaclient evm outbound tx index by nonce to supplement outtx tracker (#2735) * zetaclient: cache outbound tx while scanning blocks * PoC: no outbound tracker to validate the local indexing * Update zetaclient/chains/evm/observer/inbound.go Co-authored-by: Lucas Bertrand * fixed rename * fixed rename * revert the manual test in localnet e2e * fix unit test * fix some logs/comments due to rename * Update zetaclient/chains/evm/observer/inbound.go Co-authored-by: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> * check cache before calling RPC * use a separate function FilterTSSOutbound to scan TSS outbounds and supplement to outbound trackers * replace manual evm rpc mock with mockery generated mock --------- Co-authored-by: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Co-authored-by: Lucas Bertrand Co-authored-by: Charlie Chen --- changelog.md | 1 + zetaclient/chains/evm/observer/inbound.go | 5 + .../chains/evm/observer/inbound_test.go | 85 ++- .../chains/evm/observer/observer_gas_test.go | 20 +- .../chains/evm/observer/observer_test.go | 50 +- zetaclient/chains/evm/observer/outbound.go | 36 ++ .../chains/evm/observer/outbound_test.go | 74 +++ zetaclient/chains/evm/signer/sign_test.go | 5 +- zetaclient/chains/evm/signer/signer.go | 10 +- .../chains/evm/signer/signer_admin_test.go | 5 +- zetaclient/chains/evm/signer/signer_test.go | 17 +- zetaclient/orchestrator/bootstap_test.go | 5 +- zetaclient/testutils/constant.go | 3 + zetaclient/testutils/mocks/evm_rpc.go | 576 +++++++++++++----- zetaclient/testutils/testdata.go | 26 +- zetaclient/testutils/types/ethrpc.go | 168 +++++ 16 files changed, 870 insertions(+), 216 deletions(-) create mode 100644 zetaclient/testutils/types/ethrpc.go diff --git a/changelog.md b/changelog.md index c1a4ef1acd..0e51250604 100644 --- a/changelog.md +++ b/changelog.md @@ -42,6 +42,7 @@ * [2654](https://github.com/zeta-chain/node/pull/2654) - add validation for authorization list in when validating genesis state for authorization module * [2674](https://github.com/zeta-chain/node/pull/2674) - allow operators to vote on ballots associated with discarded keygen without affecting the status of the current keygen. * [2672](https://github.com/zeta-chain/node/pull/2672) - check observer set for duplicates when adding a new observer or updating an existing one +* [2735](https://github.com/zeta-chain/node/pull/2735) - fix the outbound tracker blocking confirmation and outbound processing on EVM chains by locally index outbound txs in zetaclient * [2787](https://github.com/zeta-chain/node/pull/2787) - ask for 3 accounts (signer, pda, system_program) on solana gateway deposit * [2842](https://github.com/zeta-chain/node/pull/2842) - fix: move interval assignment out of cctx loop in EVM outbound tx scheduler * [2853](https://github.com/zeta-chain/node/pull/2853) - calling precompile through sc with sc state update diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index d412c9b0e0..0e1cb6b84d 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -223,6 +223,11 @@ func (ob *Observer) ObserveInbound(ctx context.Context, sampledLogger zerolog.Lo return errors.Wrap(err, "unable to observe TSSReceive") } + // task 4: filter the outbounds from TSS address to supplement outbound trackers + // TODO: make this a separate go routine in outbound.go after switching to smart contract V2 + // + ob.FilterTSSOutbound(ctx, startBlock, toBlock) + // query the gateway logs // TODO: refactor in a more declarative design. Example: storing the list of contract and events to listen in an array // https://github.com/zeta-chain/node/issues/2493 diff --git a/zetaclient/chains/evm/observer/inbound_test.go b/zetaclient/chains/evm/observer/inbound_test.go index 861d31928c..e3612678da 100644 --- a/zetaclient/chains/evm/observer/inbound_test.go +++ b/zetaclient/chains/evm/observer/inbound_test.go @@ -3,12 +3,14 @@ package observer_test import ( "context" "encoding/hex" + "errors" "testing" ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/onrik/ethrpc" "github.com/rs/zerolog" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" zctx "github.com/zeta-chain/node/zetaclient/context" "github.com/zeta-chain/node/zetaclient/keys" @@ -17,6 +19,7 @@ import ( "github.com/zeta-chain/node/pkg/coin" "github.com/zeta-chain/node/pkg/constant" "github.com/zeta-chain/node/zetaclient/chains/evm" + "github.com/zeta-chain/node/zetaclient/chains/interfaces" "github.com/zeta-chain/node/zetaclient/config" "github.com/zeta-chain/node/zetaclient/testutils" "github.com/zeta-chain/node/zetaclient/testutils/mocks" @@ -461,42 +464,70 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { blockNumber := receipt.BlockNumber.Uint64() block := testutils.LoadEVMBlock(t, TestDataDir, chainID, blockNumber, true) - // create mock client - evmClient := mocks.NewMockEvmClient() - evmJSONRPC := mocks.NewMockJSONRPCClient() + // create mock zetacore client tss := mocks.NewTSSMainnet() lastBlock := receipt.BlockNumber.Uint64() + confirmation - zetacoreClient := mocks.NewZetacoreClient(t). WithKeys(&keys.Keys{}). WithZetaChain(). WithPostVoteInbound("", ""). WithPostVoteInbound("", "") - ctx := context.Background() - - t.Run("should observe TSS receive in block", func(t *testing.T) { - ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) + // test cases + tests := []struct { + name string + evmClient interfaces.EVMRPCClient + jsonClient interfaces.EVMJSONRPCClient + errMsg string + }{ + { + name: "should observe TSS receive in block", + evmClient: func() interfaces.EVMRPCClient { + // feed block number and receipt to mock client + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) + evmClient.On("TransactionReceipt", mock.Anything, mock.Anything).Return(receipt, nil) + return evmClient + }(), + jsonClient: mocks.NewMockJSONRPCClient().WithBlock(block), + errMsg: "", + }, + { + name: "should not observe on error getting block", + evmClient: func() interfaces.EVMRPCClient { + // feed block number to allow construction of observer + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) + return evmClient + }(), + jsonClient: mocks.NewMockJSONRPCClient(), // no block + errMsg: "error getting block", + }, + { + name: "should not observe on error getting receipt", + evmClient: func() interfaces.EVMRPCClient { + // feed block number but RPC error on getting receipt + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) + evmClient.On("TransactionReceipt", mock.Anything, mock.Anything).Return(nil, errors.New("RPC error")) + return evmClient + }(), + jsonClient: mocks.NewMockJSONRPCClient().WithBlock(block), + errMsg: "error getting receipt", + }, + } - // feed archived block and receipt - evmJSONRPC.WithBlock(block) - evmClient.WithReceipt(receipt) - err := ob.ObserveTSSReceiveInBlock(ctx, blockNumber) - require.NoError(t, err) - }) - t.Run("should not observe on error getting block", func(t *testing.T) { - ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) - err := ob.ObserveTSSReceiveInBlock(ctx, blockNumber) - // error getting block is expected because the mock JSONRPC contains no block - require.ErrorContains(t, err, "error getting block") - }) - t.Run("should not observe on error getting receipt", func(t *testing.T) { - ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) - evmJSONRPC.WithBlock(block) - err := ob.ObserveTSSReceiveInBlock(ctx, blockNumber) - // error getting block is expected because the mock evmClient contains no receipt - require.ErrorContains(t, err, "error getting receipt") - }) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ob, _ := MockEVMObserver(t, chain, tt.evmClient, tt.jsonClient, zetacoreClient, tss, lastBlock, chainParam) + err := ob.ObserveTSSReceiveInBlock(context.Background(), blockNumber) + if tt.errMsg != "" { + require.ErrorContains(t, err, tt.errMsg) + } else { + require.NoError(t, err) + } + }) + } } func makeAppContext(t *testing.T) (context.Context, *zctx.AppContext) { diff --git a/zetaclient/chains/evm/observer/observer_gas_test.go b/zetaclient/chains/evm/observer/observer_gas_test.go index 26de2fd46e..da64d32972 100644 --- a/zetaclient/chains/evm/observer/observer_gas_test.go +++ b/zetaclient/chains/evm/observer/observer_gas_test.go @@ -24,7 +24,8 @@ func TestPostGasPrice(t *testing.T) { t.Run("Pre EIP-1559 doesn't support priorityFee", func(t *testing.T) { // ARRANGE // Given ETH rpc mock - ethRPC := mocks.NewMockEvmClient().WithBlockNumber(blockNumber) + ethRPC := mocks.NewEVMRPCClient(t) + ethRPC.On("BlockNumber", mock.Anything).Return(uint64(blockNumber), nil) // Given zetacore client mock zetacoreClient := mocks.NewZetacoreClient(t).WithZetaChain() @@ -37,10 +38,11 @@ func TestPostGasPrice(t *testing.T) { observer, _ := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) // Given empty baseFee from RPC - ethRPC.WithHeader(ðtypes.Header{BaseFee: nil}) + ethRPC.On("HeaderByNumber", anything, anything).Return(ðtypes.Header{BaseFee: nil}, nil) - // Given gas price from RPC - ethRPC.WithSuggestGasPrice(big.NewInt(3 * gwei)) + // Given gasPrice and priorityFee from RPC + ethRPC.On("SuggestGasPrice", anything).Return(big.NewInt(3*gwei), nil) + ethRPC.On("SuggestGasTipCap", anything).Return(big.NewInt(0), nil) // Given mock collector for zetacore call // PostVoteGasPrice(ctx, chain, gasPrice, priorityFee, blockNum) @@ -69,7 +71,8 @@ func TestPostGasPrice(t *testing.T) { t.Run("Post EIP-1559 supports priorityFee", func(t *testing.T) { // ARRANGE // Given ETH rpc mock - ethRPC := mocks.NewMockEvmClient().WithBlockNumber(blockNumber) + ethRPC := mocks.NewEVMRPCClient(t) + ethRPC.On("BlockNumber", mock.Anything).Return(uint64(blockNumber), nil) // Given zetacore client mock zetacoreClient := mocks.NewZetacoreClient(t).WithZetaChain() @@ -82,12 +85,11 @@ func TestPostGasPrice(t *testing.T) { observer, _ := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) // Given 1 gwei baseFee from RPC - ethRPC.WithHeader(ðtypes.Header{BaseFee: big.NewInt(gwei)}) + ethRPC.On("HeaderByNumber", anything, anything).Return(ðtypes.Header{BaseFee: big.NewInt(gwei)}, nil) // Given gasPrice and priorityFee from RPC - ethRPC. - WithSuggestGasPrice(big.NewInt(3 * gwei)). - WithSuggestGasTipCap(big.NewInt(2 * gwei)) + ethRPC.On("SuggestGasPrice", anything).Return(big.NewInt(3*gwei), nil) + ethRPC.On("SuggestGasTipCap", anything).Return(big.NewInt(2*gwei), nil) // Given mock collector for zetacore call // PostVoteGasPrice(ctx, chain, gasPrice, priorityFee, blockNum) diff --git a/zetaclient/chains/evm/observer/observer_test.go b/zetaclient/chains/evm/observer/observer_test.go index f89715022a..049845b79e 100644 --- a/zetaclient/chains/evm/observer/observer_test.go +++ b/zetaclient/chains/evm/observer/observer_test.go @@ -12,6 +12,7 @@ import ( lru "github.com/hashicorp/golang-lru" "github.com/onrik/ethrpc" "github.com/rs/zerolog" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/pkg/ptr" zctx "github.com/zeta-chain/node/zetaclient/context" @@ -97,7 +98,9 @@ func MockEVMObserver( // use default mock evm client if not provided if evmClient == nil { - evmClient = mocks.NewMockEvmClient().WithBlockNumber(1000) + evmClientDefault := mocks.NewEVMRPCClient(t) + evmClientDefault.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) + evmClient = evmClientDefault } // use default mock evm client if not provided @@ -152,6 +155,10 @@ func Test_NewObserver(t *testing.T) { chain := chains.Ethereum params := mocks.MockChainParams(chain.ChainId, 10) + // create evm client with mocked block number 1000 + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) + // test cases tests := []struct { name string @@ -174,7 +181,7 @@ func Test_NewObserver(t *testing.T) { Endpoint: "http://localhost:8545", }, chainParams: params, - evmClient: mocks.NewMockEvmClient().WithBlockNumber(1000), + evmClient: evmClient, evmJSONRPC: mocks.NewMockJSONRPCClient(), tss: mocks.NewTSSMainnet(), logger: base.Logger{}, @@ -188,13 +195,18 @@ func Test_NewObserver(t *testing.T) { Endpoint: "http://localhost:8545", }, chainParams: params, - evmClient: mocks.NewMockEvmClient().WithError(fmt.Errorf("error RPC")), - evmJSONRPC: mocks.NewMockJSONRPCClient(), - tss: mocks.NewTSSMainnet(), - logger: base.Logger{}, - ts: nil, - fail: true, - message: "error RPC", + evmClient: func() interfaces.EVMRPCClient { + // create mock evm client with RPC error + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(uint64(0), fmt.Errorf("error RPC")) + return evmClient + }(), + evmJSONRPC: mocks.NewMockJSONRPCClient(), + tss: mocks.NewTSSMainnet(), + logger: base.Logger{}, + ts: nil, + fail: true, + message: "error RPC", }, { name: "should fail on invalid ENV var", @@ -203,7 +215,7 @@ func Test_NewObserver(t *testing.T) { Endpoint: "http://localhost:8545", }, chainParams: params, - evmClient: mocks.NewMockEvmClient().WithBlockNumber(1000), + evmClient: evmClient, evmJSONRPC: mocks.NewMockJSONRPCClient(), tss: mocks.NewTSSMainnet(), before: func() { @@ -224,8 +236,7 @@ func Test_NewObserver(t *testing.T) { // run tests for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // create AppContext, client and tss - //zetacoreCtx, _ := getAppContext(tt.evmCfg.Chain, tt.evmCfg.Endpoint, ¶ms) + // create mock zetacore client zetacoreClient := mocks.NewZetacoreClient(t) database, err := db.NewFromSqliteInMemory(true) @@ -272,7 +283,8 @@ func Test_LoadLastBlockScanned(t *testing.T) { params := mocks.MockChainParams(chain.ChainId, 10) // create observer using mock evm client - evmClient := mocks.NewMockEvmClient().WithBlockNumber(100) + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(uint64(100), nil) ob, _ := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) t.Run("should load last block scanned", func(t *testing.T) { @@ -301,8 +313,12 @@ func Test_LoadLastBlockScanned(t *testing.T) { // reset last block scanned to 0 so that it will be loaded from RPC obOther.WithLastBlockScanned(0) - // set RPC error - evmClient.WithError(fmt.Errorf("error RPC")) + // create mock evm client with RPC error + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(uint64(0), fmt.Errorf("error RPC")) + + // attach mock evm client to observer + obOther.WithEvmClient(evmClient) // load last block scanned err := obOther.LoadLastBlockScanned(ctx) @@ -389,12 +405,12 @@ func Test_HeaderCache(t *testing.T) { ob.WithHeaderCache(headerCache) // create mock evm client - evmClient := mocks.NewMockEvmClient() + evmClient := mocks.NewEVMRPCClient(t) ob.WithEvmClient(evmClient) // feed block header to evm client header := ðtypes.Header{Number: big.NewInt(100)} - evmClient.WithHeader(header) + evmClient.On("HeaderByNumber", mock.Anything, mock.Anything).Return(header, nil) // get block header from observer resHeader, err := ob.GetBlockHeaderCached(ctx, uint64(100)) diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go index fbecca5b4c..2534c47aab 100644 --- a/zetaclient/chains/evm/observer/outbound.go +++ b/zetaclient/chains/evm/observer/outbound.go @@ -427,6 +427,42 @@ func ParseAndCheckWithdrawnEvent( return nil, errors.New("no ERC20 Withdrawn event found") } +// FilterTSSOutbound filters the outbounds from TSS address to supplement outbound trackers +func (ob *Observer) FilterTSSOutbound(ctx context.Context, startBlock, toBlock uint64) { + // filters the outbounds from TSS address block by block + for bn := startBlock; bn <= toBlock; bn++ { + ob.FilterTSSOutboundInBlock(ctx, bn) + } +} + +// FilterTSSOutboundInBlock filters the outbounds in a single block to supplement outbound trackers +func (ob *Observer) FilterTSSOutboundInBlock(ctx context.Context, blockNumber uint64) { + // query block and ignore error (we don't rescan as we are only supplementing outbound trackers) + block, err := ob.GetBlockByNumberCached(blockNumber) + if err != nil { + ob.Logger(). + Outbound.Error(). + Err(err). + Msgf("error getting block %d for chain %d", blockNumber, ob.Chain().ChainId) + return + } + + for i := range block.Transactions { + tx := block.Transactions[i] + if ethcommon.HexToAddress(tx.From) == ob.TSS().EVMAddress() { + nonce := uint64(tx.Nonce) + if !ob.IsTxConfirmed(nonce) { + if receipt, txx, ok := ob.checkConfirmedTx(ctx, tx.Hash, nonce); ok { + ob.SetTxNReceipt(nonce, receipt, txx) + ob.Logger(). + Outbound.Info(). + Msgf("TSS outbound detected on chain %d nonce %d tx %s", ob.Chain().ChainId, nonce, tx.Hash) + } + } + } + } +} + // checkConfirmedTx checks if a txHash is confirmed // returns (receipt, transaction, true) if confirmed or (nil, nil, false) otherwise func (ob *Observer) checkConfirmedTx( diff --git a/zetaclient/chains/evm/observer/outbound_test.go b/zetaclient/chains/evm/observer/outbound_test.go index 400f589fff..7b8e47f40f 100644 --- a/zetaclient/chains/evm/observer/outbound_test.go +++ b/zetaclient/chains/evm/observer/outbound_test.go @@ -5,7 +5,9 @@ import ( "testing" ethcommon "github.com/ethereum/go-ethereum/common" + lru "github.com/hashicorp/golang-lru" "github.com/rs/zerolog" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" @@ -413,6 +415,78 @@ func Test_ParseERC20WithdrawnEvent(t *testing.T) { }) } +func Test_FilterTSSOutbound(t *testing.T) { + // load archived evm block + // https://etherscan.io/block/19363323 + chain := chains.Ethereum + chainID := chain.ChainId + chainParam := mocks.MockChainParams(chainID, 1) + + // load archived evm block + // https://etherscan.io/block/19363323 + blockNumber := uint64(19363323) + block := testutils.LoadEVMBlock(t, TestDataDir, chainID, blockNumber, true) + + // the outbound to be tested + outboundNonce := uint64(7260) + outboundHash := ethcommon.HexToHash("0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3") + tx, receipt := testutils.LoadEVMOutboundNReceipt(t, TestDataDir, chainID, outboundHash.Hex(), coin.CoinType_Gas) + + ctx := context.Background() + + t.Run("should filter TSS outbound", func(t *testing.T) { + // create mock evm client with preloaded block, tx and receipt + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(blockNumber+1, nil) // +1 confirmations + evmClient.On("TransactionByHash", mock.Anything, outboundHash).Return(tx, false, nil) + evmClient.On("TransactionReceipt", mock.Anything, outboundHash).Return(receipt, nil) + + // create evm observer for testing + tss := mocks.NewTSSMainnet() + ob, _ := MockEVMObserver(t, chain, evmClient, nil, nil, tss, 1, chainParam) + + // feed archived block to observer cache + blockCache, err := lru.New(1000) + require.NoError(t, err) + blockCache.Add(blockNumber, block) + ob.WithBlockCache(blockCache) + + // filter TSS outbound + ob.FilterTSSOutbound(ctx, blockNumber, blockNumber) + + // tx should be confirmed after filtering + found := ob.IsTxConfirmed(outboundNonce) + require.True(t, found) + + // retrieve tx and receipt + receipt, tx := ob.GetTxNReceipt(outboundNonce) + require.NotNil(t, tx) + require.NotNil(t, receipt) + require.Equal(t, outboundHash, tx.Hash()) + require.Equal(t, outboundNonce, tx.Nonce()) + }) + + t.Run("should filter nothing on RPC error", func(t *testing.T) { + // create mock evm client block number + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(blockNumber+1, nil) + + // create evm JSON-RPC client without block to simulate RPC error + evmJSONRPC := mocks.NewMockJSONRPCClient() + + // create evm observer for testing + tss := mocks.NewTSSMainnet() + ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, nil, tss, 1, chainParam) + + // filter TSS outbound + ob.FilterTSSOutbound(ctx, blockNumber, blockNumber) + + // tx should be confirmed after filtering + found := ob.IsTxConfirmed(outboundNonce) + require.False(t, found) + }) +} + // TODO: create mocks for gateway and ERC20CustodyV2 and uncomment these tests // https://github.com/zeta-chain/node/issues/2669 // diff --git a/zetaclient/chains/evm/signer/sign_test.go b/zetaclient/chains/evm/signer/sign_test.go index d3dc206341..5123968573 100644 --- a/zetaclient/chains/evm/signer/sign_test.go +++ b/zetaclient/chains/evm/signer/sign_test.go @@ -1,13 +1,14 @@ package signer import ( + "math/big" + "testing" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/zetaclient/testutils/mocks" - "math/big" - "testing" ) func TestSigner_SignConnectorOnReceive(t *testing.T) { diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index 0f63554e8a..5e046107fb 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -30,6 +30,7 @@ import ( "github.com/zeta-chain/node/zetaclient/logs" "github.com/zeta-chain/node/zetaclient/metrics" "github.com/zeta-chain/node/zetaclient/outboundprocessor" + "github.com/zeta-chain/node/zetaclient/testutils" "github.com/zeta-chain/node/zetaclient/testutils/mocks" "github.com/zeta-chain/node/zetaclient/zetacore" ) @@ -100,6 +101,11 @@ func NewSigner( }, nil } +// WithEvmClient attaches a new client to the signer +func (signer *Signer) WithEvmClient(client interfaces.EVMRPCClient) { + signer.client = client +} + // SetZetaConnectorAddress sets the zeta connector address func (signer *Signer) SetZetaConnectorAddress(addr ethcommon.Address) { signer.Lock() @@ -545,10 +551,10 @@ func ErrorMsg(cctx *crosschaintypes.CrossChainTx) string { // getEVMRPC is a helper function to set up the client and signer, also initializes a mock client for unit tests func getEVMRPC(ctx context.Context, endpoint string) (interfaces.EVMRPCClient, ethtypes.Signer, error) { - if endpoint == mocks.EVMRPCEnabled { + if endpoint == testutils.MockEVMRPCEndpoint { chainID := big.NewInt(chains.BscMainnet.ChainId) ethSigner := ethtypes.NewLondonSigner(chainID) - client := &mocks.MockEvmClient{} + client := &mocks.EVMRPCClient{} return client, ethSigner, nil } httpClient, err := metrics.GetInstrumentedHTTPClient(endpoint) diff --git a/zetaclient/chains/evm/signer/signer_admin_test.go b/zetaclient/chains/evm/signer/signer_admin_test.go index 7a19df94d7..6b43653ffb 100644 --- a/zetaclient/chains/evm/signer/signer_admin_test.go +++ b/zetaclient/chains/evm/signer/signer_admin_test.go @@ -2,13 +2,14 @@ package signer import ( "fmt" + "math/big" + "testing" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/pkg/constant" "github.com/zeta-chain/node/testutil/sample" "github.com/zeta-chain/node/zetaclient/testutils/mocks" - "math/big" - "testing" ) func TestSigner_SignAdminTx(t *testing.T) { diff --git a/zetaclient/chains/evm/signer/signer_test.go b/zetaclient/chains/evm/signer/signer_test.go index 807d6b7819..a128354a7f 100644 --- a/zetaclient/chains/evm/signer/signer_test.go +++ b/zetaclient/chains/evm/signer/signer_test.go @@ -10,6 +10,7 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" observertypes "github.com/zeta-chain/node/x/observer/types" zctx "github.com/zeta-chain/node/zetaclient/context" @@ -54,7 +55,7 @@ func getNewEvmSigner(tss interfaces.TSSSigner) (*Signer, error) { tss, nil, logger, - mocks.EVMRPCEnabled, + testutils.MockEVMRPCEndpoint, connectorAddress, erc20CustodyAddress, sample.EthAddress(), @@ -71,7 +72,8 @@ func getNewEvmChainObserver(t *testing.T, tss interfaces.TSSSigner) (*observer.O } // prepare mock arguments to create observer - evmClient := mocks.NewMockEvmClient().WithBlockNumber(1000) + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("BlockNumber", mock.Anything).Return(uint64(1000), nil) evmJSONRPCClient := mocks.NewMockJSONRPCClient() params := mocks.MockChainParams(chains.BscMainnet.ChainId, 10) logger := base.Logger{} @@ -160,6 +162,7 @@ func TestSigner_SetGetERC20CustodyAddress(t *testing.T) { func TestSigner_TryProcessOutbound(t *testing.T) { ctx := makeCtx(t) + // Setup evm signer evmSigner, err := getNewEvmSigner(nil) require.NoError(t, err) cctx := getCCTX(t) @@ -167,6 +170,11 @@ func TestSigner_TryProcessOutbound(t *testing.T) { mockObserver, err := getNewEvmChainObserver(t, nil) require.NoError(t, err) + // Attach mock EVM client to the signer + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("SendTransaction", mock.Anything, mock.Anything).Return(nil) + evmSigner.WithEvmClient(evmClient) + // Test with mock client that has keys client := mocks.NewZetacoreClient(t). WithKeys(&keys.Keys{}). @@ -193,6 +201,11 @@ func TestSigner_BroadcastOutbound(t *testing.T) { require.NoError(t, err) require.False(t, skip) + // Attach mock EVM evmClient to the signer + evmClient := mocks.NewEVMRPCClient(t) + evmClient.On("SendTransaction", mock.Anything, mock.Anything).Return(nil) + evmSigner.WithEvmClient(evmClient) + t.Run("BroadcastOutbound - should successfully broadcast", func(t *testing.T) { // Call SignERC20Withdraw tx, err := evmSigner.SignERC20Withdraw(ctx, txData) diff --git a/zetaclient/orchestrator/bootstap_test.go b/zetaclient/orchestrator/bootstap_test.go index 0d7d27c347..6153961183 100644 --- a/zetaclient/orchestrator/bootstap_test.go +++ b/zetaclient/orchestrator/bootstap_test.go @@ -16,6 +16,7 @@ import ( zctx "github.com/zeta-chain/node/zetaclient/context" "github.com/zeta-chain/node/zetaclient/db" "github.com/zeta-chain/node/zetaclient/metrics" + "github.com/zeta-chain/node/zetaclient/testutils" "github.com/zeta-chain/node/zetaclient/testutils/mocks" "github.com/zeta-chain/node/zetaclient/testutils/testrpc" ) @@ -40,12 +41,12 @@ func TestCreateSignerMap(t *testing.T) { cfg.EVMChainConfigs[chains.Ethereum.ChainId] = config.EVMConfig{ Chain: chains.Ethereum, - Endpoint: mocks.EVMRPCEnabled, + Endpoint: testutils.MockEVMRPCEndpoint, } cfg.EVMChainConfigs[chains.Polygon.ChainId] = config.EVMConfig{ Chain: chains.Polygon, - Endpoint: mocks.EVMRPCEnabled, + Endpoint: testutils.MockEVMRPCEndpoint, } cfg.BitcoinConfig = btcConfig diff --git a/zetaclient/testutils/constant.go b/zetaclient/testutils/constant.go index 38a5aae275..e9874a75b5 100644 --- a/zetaclient/testutils/constant.go +++ b/zetaclient/testutils/constant.go @@ -7,6 +7,9 @@ import ( ) const ( + // MockEVMRPCEndpoint is the endpoint to enable the mock EVM RPC client + MockEVMRPCEndpoint = "MockEVMRPCEnabled" + // TSSAddressEVMMainnet the EVM TSS address for test purposes // Note: public key is zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc TSSAddressEVMMainnet = "0x70e967acFcC17c3941E87562161406d41676FD83" diff --git a/zetaclient/testutils/mocks/evm_rpc.go b/zetaclient/testutils/mocks/evm_rpc.go index d004d88b71..d1761c4063 100644 --- a/zetaclient/testutils/mocks/evm_rpc.go +++ b/zetaclient/testutils/mocks/evm_rpc.go @@ -1,228 +1,504 @@ +// Code generated by mockery v2.42.2. DO NOT EDIT. + package mocks import ( - "errors" - "math/big" + context "context" + big "math/big" - "github.com/ethereum/go-ethereum" - ethcommon "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "golang.org/x/net/context" + common "github.com/ethereum/go-ethereum/common" - "github.com/zeta-chain/node/zetaclient/chains/interfaces" -) + ethereum "github.com/ethereum/go-ethereum" -const EVMRPCEnabled = "MockEVMRPCEnabled" + mock "github.com/stretchr/testify/mock" -// Subscription interface -var _ ethereum.Subscription = subscription{} + types "github.com/ethereum/go-ethereum/core/types" +) -type subscription struct { +// EVMRPCClient is an autogenerated mock type for the EVMRPCClient type +type EVMRPCClient struct { + mock.Mock } -func (s subscription) Unsubscribe() { -} +// BlockByNumber provides a mock function with given fields: ctx, number +func (_m *EVMRPCClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + ret := _m.Called(ctx, number) -func (s subscription) Err() <-chan error { - return nil -} + if len(ret) == 0 { + panic("no return value specified for BlockByNumber") + } + + var r0 *types.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Block, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Block); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } -// EvmClient interface -var _ interfaces.EVMRPCClient = &MockEvmClient{} + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } -type MockEvmClient struct { - err error - blockNumber uint64 - header *ethtypes.Header - gasPrice *big.Int - priorityFee *big.Int - Receipts []*ethtypes.Receipt + return r0, r1 } -func NewMockEvmClient() *MockEvmClient { - client := &MockEvmClient{} - return client.Reset() -} +// BlockNumber provides a mock function with given fields: ctx +func (_m *EVMRPCClient) BlockNumber(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) -func (e *MockEvmClient) SubscribeFilterLogs( - _ context.Context, - _ ethereum.FilterQuery, - _ chan<- ethtypes.Log, -) (ethereum.Subscription, error) { - if e.err != nil { - return subscription{}, e.err + if len(ret) == 0 { + panic("no return value specified for BlockNumber") } - return subscription{}, nil -} -func (e *MockEvmClient) CodeAt(_ context.Context, _ ethcommon.Address, _ *big.Int) ([]byte, error) { - if e.err != nil { - return nil, e.err + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (uint64, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) uint64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(uint64) } - return []byte{}, nil -} -func (e *MockEvmClient) CallContract(_ context.Context, _ ethereum.CallMsg, _ *big.Int) ([]byte, error) { - if e.err != nil { - return nil, e.err + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) } - return []byte{}, nil + + return r0, r1 } -func (e *MockEvmClient) HeaderByNumber(_ context.Context, _ *big.Int) (*ethtypes.Header, error) { - if e.err != nil { - return nil, e.err +// CallContract provides a mock function with given fields: ctx, call, blockNumber +func (_m *EVMRPCClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, call, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for CallContract") } - return e.header, nil -} -func (e *MockEvmClient) PendingCodeAt(_ context.Context, _ ethcommon.Address) ([]byte, error) { - if e.err != nil { - return nil, e.err + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)); ok { + return rf(ctx, call, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) []byte); ok { + r0 = rf(ctx, call, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } } - return []byte{}, nil -} -func (e *MockEvmClient) PendingNonceAt(_ context.Context, _ ethcommon.Address) (uint64, error) { - if e.err != nil { - return 0, e.err + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg, *big.Int) error); ok { + r1 = rf(ctx, call, blockNumber) + } else { + r1 = ret.Error(1) } - return 0, nil + + return r0, r1 } -func (e *MockEvmClient) SuggestGasPrice(_ context.Context) (*big.Int, error) { - if e.err != nil { - return nil, e.err +// CodeAt provides a mock function with given fields: ctx, contract, blockNumber +func (_m *EVMRPCClient) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, contract, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for CodeAt") } - if e.gasPrice != nil { - return e.gasPrice, nil + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) ([]byte, error)); ok { + return rf(ctx, contract, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) []byte); ok { + r0 = rf(ctx, contract, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, *big.Int) error); ok { + r1 = rf(ctx, contract, blockNumber) + } else { + r1 = ret.Error(1) } - return big.NewInt(0), nil + return r0, r1 } -func (e *MockEvmClient) SuggestGasTipCap(_ context.Context) (*big.Int, error) { - if e.err != nil { - return nil, e.err +// EstimateGas provides a mock function with given fields: ctx, call +func (_m *EVMRPCClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + ret := _m.Called(ctx, call) + + if len(ret) == 0 { + panic("no return value specified for EstimateGas") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) (uint64, error)); ok { + return rf(ctx, call) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) uint64); ok { + r0 = rf(ctx, call) + } else { + r0 = ret.Get(0).(uint64) } - if e.priorityFee != nil { - return e.priorityFee, nil + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg) error); ok { + r1 = rf(ctx, call) + } else { + r1 = ret.Error(1) } - return big.NewInt(0), nil + return r0, r1 } -func (e *MockEvmClient) EstimateGas(_ context.Context, _ ethereum.CallMsg) (gas uint64, err error) { - if e.err != nil { - return 0, e.err +// FilterLogs provides a mock function with given fields: ctx, query +func (_m *EVMRPCClient) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { + ret := _m.Called(ctx, query) + + if len(ret) == 0 { + panic("no return value specified for FilterLogs") } - gas = 0 - err = nil - return -} -func (e *MockEvmClient) SendTransaction(_ context.Context, _ *ethtypes.Transaction) error { - return e.err -} + var r0 []types.Log + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery) ([]types.Log, error)); ok { + return rf(ctx, query) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery) []types.Log); ok { + r0 = rf(ctx, query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Log) + } + } -func (e *MockEvmClient) FilterLogs(_ context.Context, _ ethereum.FilterQuery) ([]ethtypes.Log, error) { - if e.err != nil { - return nil, e.err + if rf, ok := ret.Get(1).(func(context.Context, ethereum.FilterQuery) error); ok { + r1 = rf(ctx, query) + } else { + r1 = ret.Error(1) } - return []ethtypes.Log{}, nil + + return r0, r1 } -func (e *MockEvmClient) BlockNumber(_ context.Context) (uint64, error) { - if e.err != nil { - return 0, e.err +// HeaderByNumber provides a mock function with given fields: ctx, number +func (_m *EVMRPCClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for HeaderByNumber") } - return e.blockNumber, nil -} -func (e *MockEvmClient) BlockByNumber(_ context.Context, _ *big.Int) (*ethtypes.Block, error) { - if e.err != nil { - return nil, e.err + var r0 *types.Header + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Header, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Header); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } } - return ðtypes.Block{}, nil -} -func (e *MockEvmClient) TransactionByHash( - _ context.Context, - _ ethcommon.Hash, -) (tx *ethtypes.Transaction, isPending bool, err error) { - if e.err != nil { - return nil, false, e.err + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) } - return ðtypes.Transaction{}, false, nil + + return r0, r1 } -func (e *MockEvmClient) TransactionReceipt(_ context.Context, _ ethcommon.Hash) (*ethtypes.Receipt, error) { - if e.err != nil { - return nil, e.err +// PendingCodeAt provides a mock function with given fields: ctx, account +func (_m *EVMRPCClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + ret := _m.Called(ctx, account) + + if len(ret) == 0 { + panic("no return value specified for PendingCodeAt") } - // pop a receipt from the list - if len(e.Receipts) > 0 { - receipt := e.Receipts[len(e.Receipts)-1] - e.Receipts = e.Receipts[:len(e.Receipts)-1] - return receipt, nil + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) ([]byte, error)); ok { + return rf(ctx, account) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) []byte); ok { + r0 = rf(ctx, account) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } } - return nil, errors.New("no receipt found") + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(ctx, account) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (e *MockEvmClient) TransactionSender( - _ context.Context, - _ *ethtypes.Transaction, - _ ethcommon.Hash, - _ uint, -) (ethcommon.Address, error) { - if e.err != nil { - return ethcommon.Address{}, e.err +// PendingNonceAt provides a mock function with given fields: ctx, account +func (_m *EVMRPCClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + ret := _m.Called(ctx, account) + + if len(ret) == 0 { + panic("no return value specified for PendingNonceAt") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) (uint64, error)); ok { + return rf(ctx, account) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) uint64); ok { + r0 = rf(ctx, account) + } else { + r0 = ret.Get(0).(uint64) } - return ethcommon.Address{}, nil + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(ctx, account) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (e *MockEvmClient) Reset() *MockEvmClient { - e.Receipts = []*ethtypes.Receipt{} - return e +// SendTransaction provides a mock function with given fields: ctx, tx +func (_m *EVMRPCClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + ret := _m.Called(ctx, tx) + + if len(ret) == 0 { + panic("no return value specified for SendTransaction") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction) error); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Error(0) + } + + return r0 } -// ---------------------------------------------------------------------------- -// Feed data to the mock evm client for testing -// ---------------------------------------------------------------------------- -func (e *MockEvmClient) WithError(err error) *MockEvmClient { - e.err = err - return e +// SubscribeFilterLogs provides a mock function with given fields: ctx, query, ch +func (_m *EVMRPCClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + ret := _m.Called(ctx, query, ch) + + if len(ret) == 0 { + panic("no return value specified for SubscribeFilterLogs") + } + + var r0 ethereum.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error)); ok { + return rf(ctx, query, ch) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) ethereum.Subscription); ok { + r0 = rf(ctx, query, ch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ethereum.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) error); ok { + r1 = rf(ctx, query, ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (e *MockEvmClient) WithBlockNumber(blockNumber uint64) *MockEvmClient { - e.blockNumber = blockNumber - return e +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *EVMRPCClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasPrice") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (e *MockEvmClient) WithHeader(header *ethtypes.Header) *MockEvmClient { - e.header = header - return e +// SuggestGasTipCap provides a mock function with given fields: ctx +func (_m *EVMRPCClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasTipCap") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (e *MockEvmClient) WithReceipt(receipt *ethtypes.Receipt) *MockEvmClient { - e.Receipts = append(e.Receipts, receipt) - return e +// TransactionByHash provides a mock function with given fields: ctx, hash +func (_m *EVMRPCClient) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) { + ret := _m.Called(ctx, hash) + + if len(ret) == 0 { + panic("no return value specified for TransactionByHash") + } + + var r0 *types.Transaction + var r1 bool + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Transaction, bool, error)); ok { + return rf(ctx, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Transaction); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) bool); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(context.Context, common.Hash) error); ok { + r2 = rf(ctx, hash) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 } -func (e *MockEvmClient) WithReceipts(receipts []*ethtypes.Receipt) *MockEvmClient { - e.Receipts = append(e.Receipts, receipts...) - return e +// TransactionReceipt provides a mock function with given fields: ctx, txHash +func (_m *EVMRPCClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + ret := _m.Called(ctx, txHash) + + if len(ret) == 0 { + panic("no return value specified for TransactionReceipt") + } + + var r0 *types.Receipt + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Receipt, error)); ok { + return rf(ctx, txHash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Receipt); ok { + r0 = rf(ctx, txHash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Receipt) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, txHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (e *MockEvmClient) WithSuggestGasPrice(price *big.Int) *MockEvmClient { - e.gasPrice = price - return e +// TransactionSender provides a mock function with given fields: ctx, tx, block, index +func (_m *EVMRPCClient) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) { + ret := _m.Called(ctx, tx, block, index) + + if len(ret) == 0 { + panic("no return value specified for TransactionSender") + } + + var r0 common.Address + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, common.Hash, uint) (common.Address, error)); ok { + return rf(ctx, tx, block, index) + } + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, common.Hash, uint) common.Address); ok { + r0 = rf(ctx, tx, block, index) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Address) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *types.Transaction, common.Hash, uint) error); ok { + r1 = rf(ctx, tx, block, index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -func (e *MockEvmClient) WithSuggestGasTipCap(price *big.Int) *MockEvmClient { - e.priorityFee = price - return e +// NewEVMRPCClient creates a new instance of EVMRPCClient. 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 NewEVMRPCClient(t interface { + mock.TestingT + Cleanup(func()) +}) *EVMRPCClient { + mock := &EVMRPCClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock } diff --git a/zetaclient/testutils/testdata.go b/zetaclient/testutils/testdata.go index d2a3b3dff6..160ec230e5 100644 --- a/zetaclient/testutils/testdata.go +++ b/zetaclient/testutils/testdata.go @@ -16,6 +16,7 @@ import ( "github.com/zeta-chain/node/pkg/coin" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" testcctx "github.com/zeta-chain/node/zetaclient/testdata/cctx" + testtypes "github.com/zeta-chain/node/zetaclient/testutils/types" ) const ( @@ -47,6 +48,20 @@ func LoadObjectFromJSONFile(t *testing.T, obj interface{}, filename string) { require.NoError(t, err) } +// LoadJSONRawMessageFromFile loads a raw JSON message from a file in JSON format +func LoadJSONRawMessageFromFile(t *testing.T, filename string) json.RawMessage { + file, err := os.Open(filepath.Clean(filename)) + require.NoError(t, err) + defer file.Close() + + // read the JSON raw message from the file + decoder := json.NewDecoder(file) + var raw json.RawMessage + err = decoder.Decode(&raw) + require.NoError(t, err) + return raw +} + // LoadCctxByInbound loads archived cctx by inbound func LoadCctxByInbound( t *testing.T, @@ -81,9 +96,14 @@ func LoadCctxByNonce( // LoadEVMBlock loads archived evm block from file func LoadEVMBlock(t *testing.T, dir string, chainID int64, blockNumber uint64, trimmed bool) *ethrpc.Block { name := path.Join(dir, TestDataPathEVM, FileNameEVMBlock(chainID, blockNumber, trimmed)) - block := ðrpc.Block{} - LoadObjectFromJSONFile(t, block, name) - return block + + // load archived block + jsonMessage := LoadJSONRawMessageFromFile(t, name) + blockProxy := new(testtypes.ProxyBlockWithTransactions) + err := json.Unmarshal(jsonMessage, blockProxy) + require.NoError(t, err) + + return blockProxy.ToBlock() } // LoadBTCTxRawResult loads archived Bitcoin tx raw result from file diff --git a/zetaclient/testutils/types/ethrpc.go b/zetaclient/testutils/types/ethrpc.go new file mode 100644 index 0000000000..f968369660 --- /dev/null +++ b/zetaclient/testutils/types/ethrpc.go @@ -0,0 +1,168 @@ +package types + +import ( + "bytes" + "encoding/json" + "math/big" + "strconv" + "strings" + "unsafe" + + "github.com/onrik/ethrpc" +) + +// Transaction - transaction object +type Transaction struct { + Hash string + Nonce int + BlockHash string + BlockNumber *int + TransactionIndex *int + From string + To string + Value big.Int + Gas int + GasPrice big.Int + Input string +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (t *Transaction) UnmarshalJSON(data []byte) error { + proxy := new(ProxyTransaction) + if err := json.Unmarshal(data, proxy); err != nil { + return err + } + + *t = *(*Transaction)(unsafe.Pointer(proxy)) + + return nil +} + +// Block - block object +type Block struct { + Number int + Hash string + ParentHash string + Nonce string + Sha3Uncles string + LogsBloom string + TransactionsRoot string + StateRoot string + Miner string + Difficulty big.Int + TotalDifficulty big.Int + ExtraData string + Size int + GasLimit int + GasUsed int + Timestamp int + Uncles []string + Transactions []Transaction +} + +type ProxyTransaction struct { + Hash string `json:"hash"` + Nonce hexInt `json:"nonce"` + BlockHash string `json:"blockHash"` + BlockNumber *hexInt `json:"blockNumber"` + TransactionIndex *hexInt `json:"transactionIndex"` + From string `json:"from"` + To string `json:"to"` + Value hexBig `json:"value"` + Gas hexInt `json:"gas"` + GasPrice hexBig `json:"gasPrice"` + Input string `json:"input"` +} + +type hexInt int + +// ParseInt parse string value to int +func ParseInt(value string) (int, error) { + i, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64) + if err != nil { + return 0, err + } + + return int(i), nil +} + +func (i *hexInt) UnmarshalJSON(data []byte) error { + result, err := ParseInt(string(bytes.Trim(data, `"`))) + *i = hexInt(result) + + return err +} + +type hexBig big.Int + +func (i *hexBig) UnmarshalJSON(data []byte) error { + result, err := ethrpc.ParseBigInt(string(bytes.Trim(data, `"`))) + *i = hexBig(result) + + return err +} + +type ProxyBlockWithTransactions struct { + Number hexInt `json:"number"` + Hash string `json:"hash"` + ParentHash string `json:"parentHash"` + Nonce string `json:"nonce"` + Sha3Uncles string `json:"sha3Uncles"` + LogsBloom string `json:"logsBloom"` + TransactionsRoot string `json:"transactionsRoot"` + StateRoot string `json:"stateRoot"` + Miner string `json:"miner"` + Difficulty hexBig `json:"difficulty"` + TotalDifficulty hexBig `json:"totalDifficulty"` + ExtraData string `json:"extraData"` + Size hexInt `json:"size"` + GasLimit hexInt `json:"gasLimit"` + GasUsed hexInt `json:"gasUsed"` + Timestamp hexInt `json:"timestamp"` + Uncles []string `json:"uncles"` + Transactions []ProxyTransaction `json:"transactions"` +} + +func (proxy *ProxyBlockWithTransactions) ToBlock() *ethrpc.Block { + block := *(*Block)(unsafe.Pointer(proxy)) + + ethrpcBlock := ðrpc.Block{ + Number: block.Number, + Hash: block.Hash, + ParentHash: block.ParentHash, + Nonce: block.Nonce, + Sha3Uncles: block.Sha3Uncles, + LogsBloom: block.LogsBloom, + TransactionsRoot: block.TransactionsRoot, + StateRoot: block.StateRoot, + Miner: block.Miner, + Difficulty: block.Difficulty, + TotalDifficulty: block.TotalDifficulty, + ExtraData: block.ExtraData, + Size: block.Size, + GasLimit: block.GasLimit, + GasUsed: block.GasUsed, + Timestamp: block.Timestamp, + Uncles: block.Uncles, + Transactions: make([]ethrpc.Transaction, len(block.Transactions)), + } + + // copy transactions + for i, tx := range block.Transactions { + ethrpcBlock.Transactions[i] = ethrpc.Transaction{ + Hash: tx.Hash, + Nonce: tx.Nonce, + BlockHash: tx.BlockHash, + BlockNumber: tx.BlockNumber, + TransactionIndex: tx.TransactionIndex, + From: tx.From, + To: tx.To, + Value: tx.Value, + Gas: tx.Gas, + GasPrice: tx.GasPrice, + Input: tx.Input, + } + } + + return ethrpcBlock +} From 19c929c50ef409ebd858b66767b60413b07de399 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Thu, 12 Sep 2024 13:00:21 -0700 Subject: [PATCH 3/4] fix(zetacore): add default cctx list pagination size (#2871) * fix(zetacore): add default cctx list pagination size * add unit tests --- x/crosschain/keeper/grpc_query_cctx.go | 9 +++++ x/crosschain/keeper/grpc_query_cctx_test.go | 38 +++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/x/crosschain/keeper/grpc_query_cctx.go b/x/crosschain/keeper/grpc_query_cctx.go index a157a95433..636348ea43 100644 --- a/x/crosschain/keeper/grpc_query_cctx.go +++ b/x/crosschain/keeper/grpc_query_cctx.go @@ -20,6 +20,8 @@ const ( // MaxLookbackNonce is the maximum number of nonces to look back to find missed pending cctxs MaxLookbackNonce = 1000 + + DefaultPageSize = 100 ) func (k Keeper) ZetaAccounting( @@ -46,6 +48,13 @@ func (k Keeper) CctxAll(c context.Context, req *types.QueryAllCctxRequest) (*typ store := ctx.KVStore(k.storeKey) sendStore := prefix.NewStore(store, types.KeyPrefix(types.CCTXKey)) + if req.Pagination == nil { + req.Pagination = &query.PageRequest{} + } + if req.Pagination.Limit == 0 { + req.Pagination.Limit = DefaultPageSize + } + pageRes, err := query.Paginate(sendStore, req.Pagination, func(_ []byte, value []byte) error { var send types.CrossChainTx if err := k.cdc.Unmarshal(value, &send); err != nil { diff --git a/x/crosschain/keeper/grpc_query_cctx_test.go b/x/crosschain/keeper/grpc_query_cctx_test.go index 54bffea45d..8a91e9445b 100644 --- a/x/crosschain/keeper/grpc_query_cctx_test.go +++ b/x/crosschain/keeper/grpc_query_cctx_test.go @@ -5,6 +5,7 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" "github.com/stretchr/testify/require" keepertest "github.com/zeta-chain/node/testutil/keeper" @@ -288,3 +289,40 @@ func TestKeeper_CctxByNonce(t *testing.T) { require.Equal(t, res.CrossChainTx.CctxStatus.LastUpdateTimestamp, ctx.BlockTime().Unix()) }) } + +func TestKeeper_CctxAll(t *testing.T) { + t.Run("empty request", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeper(t) + _, err := k.CctxAll(ctx, &types.QueryAllCctxRequest{}) + require.NoError(t, err) + }) + + t.Run("default page size", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeper(t) + chainID := getValidEthChainID() + tss := sample.Tss() + zk.ObserverKeeper.SetTSS(ctx, tss) + _ = createCctxWithNonceRange(t, ctx, *k, 1000, 2000, chainID, tss, zk) + + res, err := k.CctxAll(ctx, &types.QueryAllCctxRequest{}) + require.NoError(t, err) + require.Len(t, res.CrossChainTx, keeper.DefaultPageSize) + }) + + t.Run("page size provided", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeper(t) + chainID := getValidEthChainID() + tss := sample.Tss() + zk.ObserverKeeper.SetTSS(ctx, tss) + _ = createCctxWithNonceRange(t, ctx, *k, 1000, 2000, chainID, tss, zk) + testPageSize := 200 + + res, err := k.CctxAll(ctx, &types.QueryAllCctxRequest{ + Pagination: &query.PageRequest{ + Limit: uint64(testPageSize), + }, + }) + require.NoError(t, err) + require.Len(t, res.CrossChainTx, testPageSize) + }) +} From 75a41892e49f6e542f59c306ebeb4ee9bcf6062e Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Fri, 13 Sep 2024 10:19:33 -0700 Subject: [PATCH 4/4] chore: sync v20 changelog (#2872) --- changelog.md | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/changelog.md b/changelog.md index 0e51250604..2b3667d36d 100644 --- a/changelog.md +++ b/changelog.md @@ -4,16 +4,6 @@ ### Features -* [2578](https://github.com/zeta-chain/node/pull/2578) - add Gateway address in protocol contract list -* [2630](https://github.com/zeta-chain/node/pull/2630) - implement `MsgMigrateERC20CustodyFunds` to migrate the funds from the ERC20Custody to a new contracts (to be used for the new ERC20Custody contract for smart contract V2) -* [2578](https://github.com/zeta-chain/node/pull/2578) - Add Gateway address in protocol contract list -* [2594](https://github.com/zeta-chain/node/pull/2594) - Integrate Protocol Contracts V2 in the protocol -* [2634](https://github.com/zeta-chain/node/pull/2634) - add support for EIP-1559 gas fees -* [2597](https://github.com/zeta-chain/node/pull/2597) - Add generic rpc metrics to zetaclient -* [2538](https://github.com/zeta-chain/node/pull/2538) - add background worker routines to shutdown zetaclientd when needed for tss migration -* [2681](https://github.com/zeta-chain/node/pull/2681) - implement `MsgUpdateERC20CustodyPauseStatus` to pause or unpause ERC20 Custody contract (to be used for the migration process for smart contract V2) -* [2644](https://github.com/zeta-chain/node/pull/2644) - add created_timestamp to cctx status -* [2673](https://github.com/zeta-chain/node/pull/2673) - add relayer key importer, encryption and decryption * [2633](https://github.com/zeta-chain/node/pull/2633) - support for stateful precompiled contracts * [2788](https://github.com/zeta-chain/node/pull/2788) - add common importable zetacored rpc package * [2784](https://github.com/zeta-chain/node/pull/2784) - staking precompiled contract @@ -22,7 +12,6 @@ ### Refactor -* [2615](https://github.com/zeta-chain/node/pull/2615) - Refactor cleanup of outbound trackers * [2749](https://github.com/zeta-chain/node/pull/2749) - fix all lint errors from govet * [2725](https://github.com/zeta-chain/node/pull/2725) - refactor SetCctxAndNonceToCctxAndInboundHashToCctx to receive tsspubkey as an argument * [2802](https://github.com/zeta-chain/node/pull/2802) - set default liquidity cap for new ZRC20s @@ -31,22 +20,50 @@ ### Tests * [2661](https://github.com/zeta-chain/node/pull/2661) - update connector and erc20Custody addresses in tss migration e2e tests -* [2726](https://github.com/zeta-chain/node/pull/2726) - add e2e tests for deposit and call, deposit and revert * [2703](https://github.com/zeta-chain/node/pull/2703) - add e2e tests for stateful precompiled contracts -* [2763](https://github.com/zeta-chain/node/pull/2763) - add V2 contracts migration test * [2830](https://github.com/zeta-chain/node/pull/2830) - extend staking precompile tests * [2867](https://github.com/zeta-chain/node/pull/2867) - skip precompiles test for tss migration ### Fixes -* [2654](https://github.com/zeta-chain/node/pull/2654) - add validation for authorization list in when validating genesis state for authorization module * [2674](https://github.com/zeta-chain/node/pull/2674) - allow operators to vote on ballots associated with discarded keygen without affecting the status of the current keygen. * [2672](https://github.com/zeta-chain/node/pull/2672) - check observer set for duplicates when adding a new observer or updating an existing one * [2735](https://github.com/zeta-chain/node/pull/2735) - fix the outbound tracker blocking confirmation and outbound processing on EVM chains by locally index outbound txs in zetaclient -* [2787](https://github.com/zeta-chain/node/pull/2787) - ask for 3 accounts (signer, pda, system_program) on solana gateway deposit * [2842](https://github.com/zeta-chain/node/pull/2842) - fix: move interval assignment out of cctx loop in EVM outbound tx scheduler * [2853](https://github.com/zeta-chain/node/pull/2853) - calling precompile through sc with sc state update +## v20.0.0 + +### Features + +* [2578](https://github.com/zeta-chain/node/pull/2578) - add Gateway address in protocol contract list +* [2630](https://github.com/zeta-chain/node/pull/2630) - implement `MsgMigrateERC20CustodyFunds` to migrate the funds from the ERC20Custody to a new contracts (to be used for the new ERC20Custody contract for smart contract V2) +* [2578](https://github.com/zeta-chain/node/pull/2578) - Add Gateway address in protocol contract list +* [2594](https://github.com/zeta-chain/node/pull/2594) - Integrate Protocol Contracts V2 in the protocol +* [2634](https://github.com/zeta-chain/node/pull/2634) - add support for EIP-1559 gas fees +* [2597](https://github.com/zeta-chain/node/pull/2597) - Add generic rpc metrics to zetaclient +* [2538](https://github.com/zeta-chain/node/pull/2538) - add background worker routines to shutdown zetaclientd when needed for tss migration +* [2681](https://github.com/zeta-chain/node/pull/2681) - implement `MsgUpdateERC20CustodyPauseStatus` to pause or unpause ERC20 Custody contract (to be used for the migration process for smart contract V2) +* [2644](https://github.com/zeta-chain/node/pull/2644) - add created_timestamp to cctx status +* [2673](https://github.com/zeta-chain/node/pull/2673) - add relayer key importer, encryption and decryption +* [2825](https://github.com/zeta-chain/node/pull/2825) - add Bitcoin inscriptions support + +### Refactor + +* [2615](https://github.com/zeta-chain/node/pull/2615) - Refactor cleanup of outbound trackers +* [2855](https://github.com/zeta-chain/node/pull/2855) - disable Bitcoin witness support for mainnet + +### Tests + +* [2726](https://github.com/zeta-chain/node/pull/2726) - add e2e tests for deposit and call, deposit and revert +* [2821](https://github.com/zeta-chain/node/pull/2821) - V2 protocol contracts migration e2e tests + +### Fixes + +* [2654](https://github.com/zeta-chain/node/pull/2654) - add validation for authorization list in when validating genesis state for authorization module +* [2672](https://github.com/zeta-chain/node/pull/2672) - check observer set for duplicates when adding a new observer or updating an existing one +* [2824](https://github.com/zeta-chain/node/pull/2824) - fix Solana deposit number + ## v19.0.0 ### Breaking Changes