diff --git a/changelog.md b/changelog.md index 6f0fa7e632..47ab793048 100644 --- a/changelog.md +++ b/changelog.md @@ -51,6 +51,7 @@ * [2199](https://github.com/zeta-chain/node/pull/2199) - custom priority mempool unit tests * [2240](https://github.com/zeta-chain/node/pull/2240) - removed hard-coded Bitcoin regnet chainID in E2E withdraw tests * [2266](https://github.com/zeta-chain/node/pull/2266) - try fixing E2E test `crosschain_swap` failure `btc transaction not signed` +* [2294](https://github.com/zeta-chain/node/pull/2294) - add and fix existing ethermint rpc unit test * [2299](https://github.com/zeta-chain/node/pull/2299) - add `zetae2e` command to deploy test contracts ### Fixes diff --git a/codecov.yml b/codecov.yml index 99fe33e09e..5646dcdf96 100644 --- a/codecov.yml +++ b/codecov.yml @@ -68,7 +68,6 @@ ignore: - "cmd/**/*" - "contrib/**/*" - "docs/**/*" - - "rpc/**/*" - "proto/**/*" - "scripts/**/*" - "server/**/*" diff --git a/rpc/backend/account_info_test.go b/rpc/backend/account_info_test.go new file mode 100644 index 0000000000..f54b743d5f --- /dev/null +++ b/rpc/backend/account_info_test.go @@ -0,0 +1,460 @@ +package backend + +import ( + "fmt" + "math/big" + + tmrpcclient "github.com/cometbft/cometbft/rpc/client" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" + "google.golang.org/grpc/metadata" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + rpctypes "github.com/zeta-chain/zetacore/rpc/types" +) + +func (suite *BackendTestSuite) TestGetCode() { + blockNr := rpctypes.NewBlockNumber(big.NewInt(1)) + contractCode := []byte( + "0xef616c92f3cfc9e92dc270d6acff9cea213cecc7020a76ee4395af09bdceb4837a1ebdb5735e11e7d3adb6104e0c3ac55180b4ddf5e54d022cc5e8837f6a4f971b", + ) + + testCases := []struct { + name string + addr common.Address + blockNrOrHash rpctypes.BlockNumberOrHash + registerMock func(common.Address) + expPass bool + expCode hexutil.Bytes + }{ + { + "fail - BlockHash and BlockNumber are both nil ", + tests.GenerateAddress(), + rpctypes.BlockNumberOrHash{}, + func(addr common.Address) {}, + false, + nil, + }, + { + "fail - query client errors on getting Code", + tests.GenerateAddress(), + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(addr common.Address) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterCodeError(queryClient, addr) + }, + false, + nil, + }, + { + "pass", + tests.GenerateAddress(), + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(addr common.Address) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterCode(queryClient, addr, contractCode) + }, + true, + contractCode, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset + tc.registerMock(tc.addr) + + code, err := suite.backend.GetCode(tc.addr, tc.blockNrOrHash) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expCode, code) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetProof() { + blockNrInvalid := rpctypes.NewBlockNumber(big.NewInt(1)) + blockNr := rpctypes.NewBlockNumber(big.NewInt(4)) + address1 := tests.GenerateAddress() + + testCases := []struct { + name string + addr common.Address + storageKeys []string + blockNrOrHash rpctypes.BlockNumberOrHash + registerMock func(rpctypes.BlockNumber, common.Address) + expPass bool + expAccRes *rpctypes.AccountResult + }{ + { + "fail - BlockNumeber = 1 (invalidBlockNumber)", + address1, + []string{}, + rpctypes.BlockNumberOrHash{BlockNumber: &blockNrInvalid}, + func(bn rpctypes.BlockNumber, addr common.Address) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, bn.Int64(), nil) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterAccount(queryClient, addr, blockNrInvalid.Int64()) + }, + false, + &rpctypes.AccountResult{}, + }, + { + "fail - Block doesn't exist)", + address1, + []string{}, + rpctypes.BlockNumberOrHash{BlockNumber: &blockNrInvalid}, + func(bn rpctypes.BlockNumber, addr common.Address) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, bn.Int64()) + }, + false, + &rpctypes.AccountResult{}, + }, + { + "pass", + address1, + []string{"0x0"}, + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(bn rpctypes.BlockNumber, addr common.Address) { + suite.backend.ctx = rpctypes.ContextWithHeight(bn.Int64()) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, bn.Int64(), nil) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterAccount(queryClient, addr, bn.Int64()) + + // Use the IAVL height if a valid tendermint height is passed in. + iavlHeight := bn.Int64() + RegisterABCIQueryWithOptions( + client, + bn.Int64(), + "store/evm/key", + evmtypes.StateKey(address1, common.HexToHash("0x0").Bytes()), + tmrpcclient.ABCIQueryOptions{Height: iavlHeight, Prove: true}, + ) + RegisterABCIQueryWithOptions( + client, + bn.Int64(), + "store/acc/key", + authtypes.AddressStoreKey(sdk.AccAddress(address1.Bytes())), + tmrpcclient.ABCIQueryOptions{Height: iavlHeight, Prove: true}, + ) + }, + true, + &rpctypes.AccountResult{ + Address: address1, + AccountProof: []string{""}, + Balance: (*hexutil.Big)(big.NewInt(0)), + CodeHash: common.HexToHash(""), + Nonce: 0x0, + StorageHash: common.Hash{}, + StorageProof: []rpctypes.StorageResult{ + { + Key: "0x0", + Value: (*hexutil.Big)(big.NewInt(2)), + Proof: []string{""}, + }, + }, + }, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() + tc.registerMock(*tc.blockNrOrHash.BlockNumber, tc.addr) + + accRes, err := suite.backend.GetProof(tc.addr, tc.storageKeys, tc.blockNrOrHash) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expAccRes, accRes) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetStorageAt() { + blockNr := rpctypes.NewBlockNumber(big.NewInt(1)) + + testCases := []struct { + name string + addr common.Address + key string + blockNrOrHash rpctypes.BlockNumberOrHash + registerMock func(common.Address, string, string) + expPass bool + expStorage hexutil.Bytes + }{ + { + "fail - BlockHash and BlockNumber are both nil", + tests.GenerateAddress(), + "0x0", + rpctypes.BlockNumberOrHash{}, + func(addr common.Address, key string, storage string) {}, + false, + nil, + }, + { + "fail - query client errors on getting Storage", + tests.GenerateAddress(), + "0x0", + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(addr common.Address, key string, storage string) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterStorageAtError(queryClient, addr, key) + }, + false, + nil, + }, + { + "pass", + tests.GenerateAddress(), + "0x0", + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(addr common.Address, key string, storage string) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterStorageAt(queryClient, addr, key, storage) + }, + true, + hexutil.Bytes{ + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + }, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() + tc.registerMock(tc.addr, tc.key, tc.expStorage.String()) + + storage, err := suite.backend.GetStorageAt(tc.addr, tc.key, tc.blockNrOrHash) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expStorage, storage) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetBalance() { + blockNr := rpctypes.NewBlockNumber(big.NewInt(1)) + + testCases := []struct { + name string + addr common.Address + blockNrOrHash rpctypes.BlockNumberOrHash + registerMock func(rpctypes.BlockNumber, common.Address) + expPass bool + expBalance *hexutil.Big + }{ + { + "fail - BlockHash and BlockNumber are both nil", + tests.GenerateAddress(), + rpctypes.BlockNumberOrHash{}, + func(bn rpctypes.BlockNumber, addr common.Address) { + }, + false, + nil, + }, + { + "fail - tendermint client failed to get block", + tests.GenerateAddress(), + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(bn rpctypes.BlockNumber, addr common.Address) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, bn.Int64()) + }, + false, + nil, + }, + { + "fail - query client failed to get balance", + tests.GenerateAddress(), + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(bn rpctypes.BlockNumber, addr common.Address) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, bn.Int64(), nil) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBalanceError(queryClient, addr, bn.Int64()) + }, + false, + nil, + }, + { + "fail - invalid balance", + tests.GenerateAddress(), + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(bn rpctypes.BlockNumber, addr common.Address) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, bn.Int64(), nil) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBalanceInvalid(queryClient, addr, bn.Int64()) + }, + false, + nil, + }, + { + "fail - pruned node state", + tests.GenerateAddress(), + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(bn rpctypes.BlockNumber, addr common.Address) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, bn.Int64(), nil) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBalanceNegative(queryClient, addr, bn.Int64()) + }, + false, + nil, + }, + { + "pass", + tests.GenerateAddress(), + rpctypes.BlockNumberOrHash{BlockNumber: &blockNr}, + func(bn rpctypes.BlockNumber, addr common.Address) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, bn.Int64(), nil) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBalance(queryClient, addr, bn.Int64()) + }, + true, + (*hexutil.Big)(big.NewInt(1)), + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() + + // avoid nil pointer reference + if tc.blockNrOrHash.BlockNumber != nil { + tc.registerMock(*tc.blockNrOrHash.BlockNumber, tc.addr) + } + + balance, err := suite.backend.GetBalance(tc.addr, tc.blockNrOrHash) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expBalance, balance) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTransactionCount() { + testCases := []struct { + name string + accExists bool + blockNum rpctypes.BlockNumber + registerMock func(common.Address, rpctypes.BlockNumber) + expPass bool + expTxCount hexutil.Uint64 + }{ + { + "pass - account doesn't exist", + false, + rpctypes.NewBlockNumber(big.NewInt(1)), + func(addr common.Address, bn rpctypes.BlockNumber) { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParams(queryClient, &header, 1) + }, + true, + hexutil.Uint64(0), + }, + { + "fail - block height is in the future", + false, + rpctypes.NewBlockNumber(big.NewInt(10000)), + func(addr common.Address, bn rpctypes.BlockNumber) { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParams(queryClient, &header, 1) + }, + false, + hexutil.Uint64(0), + }, + // TODO (https://github.com/zeta-chain/node/issues/2302): Error mocking the GetAccount call - problem with Any type + //{ + // "pass - returns the number of transactions at the given address up to the given block number", + // true, + // rpctypes.NewBlockNumber(big.NewInt(1)), + // func(addr common.Address, bn rpctypes.BlockNumber) { + // client := suite.backend.clientCtx.Client.(*mocks.Client) + // account, err := suite.backend.clientCtx.AccountRetriever.GetAccount(suite.backend.clientCtx, suite.acc) + // suite.Require().NoError(err) + // request := &authtypes.QueryAccountRequest{Address: sdk.AccAddress(suite.acc.Bytes()).String()} + // requestMarshal, _ := request.Marshal() + // RegisterABCIQueryAccount( + // client, + // requestMarshal, + // tmrpcclient.ABCIQueryOptions{Height: int64(1), Prove: false}, + // account, + // ) + // }, + // true, + // hexutil.Uint64(0), + //}, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() + + addr := tests.GenerateAddress() + if tc.accExists { + addr = common.BytesToAddress(suite.acc.Bytes()) + } + + tc.registerMock(addr, tc.blockNum) + + txCount, err := suite.backend.GetTransactionCount(addr, tc.blockNum) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expTxCount, *txCount) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/rpc/backend/backend_suite_test.go b/rpc/backend/backend_suite_test.go new file mode 100644 index 0000000000..0194d5902f --- /dev/null +++ b/rpc/backend/backend_suite_test.go @@ -0,0 +1,199 @@ +package backend + +import ( + "bufio" + "math/big" + "os" + "path/filepath" + "testing" + + dbm "github.com/cometbft/cometbft-db" + tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/evmos/ethermint/app" + "github.com/evmos/ethermint/crypto/ethsecp256k1" + "github.com/evmos/ethermint/crypto/hd" + "github.com/evmos/ethermint/encoding" + "github.com/evmos/ethermint/indexer" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" + "github.com/stretchr/testify/suite" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + rpctypes "github.com/zeta-chain/zetacore/rpc/types" +) + +type BackendTestSuite struct { + suite.Suite + backend *Backend + acc sdk.AccAddress + signer keyring.Signer +} + +func TestBackendTestSuite(t *testing.T) { + suite.Run(t, new(BackendTestSuite)) +} + +const ChainID = "zetachain_7001-1" + +// SetupTest is executed before every BackendTestSuite test +func (suite *BackendTestSuite) SetupTest() { + ctx := server.NewDefaultContext() + ctx.Viper.Set("telemetry.global-labels", []interface{}{}) + + baseDir := suite.T().TempDir() + nodeDirName := "node" + clientDir := filepath.Join(baseDir, nodeDirName, "evmoscli") + keyRing, err := suite.generateTestKeyring(clientDir) + if err != nil { + panic(err) + } + + // Create Account with set sequence + suite.acc = sdk.AccAddress(tests.GenerateAddress().Bytes()) + accounts := map[string]client.TestAccount{} + accounts[suite.acc.String()] = client.TestAccount{ + Address: suite.acc, + Num: uint64(1), + Seq: uint64(1), + } + + priv, err := ethsecp256k1.GenerateKey() + suite.signer = tests.NewSigner(priv) + suite.Require().NoError(err) + + encodingConfig := encoding.MakeConfig(app.ModuleBasics) + clientCtx := client.Context{}.WithChainID(ChainID). + WithHeight(1). + WithTxConfig(encodingConfig.TxConfig). + WithKeyringDir(clientDir). + WithKeyring(keyRing). + WithAccountRetriever(client.TestAccountRetriever{Accounts: accounts}) + + allowUnprotectedTxs := false + idxer := indexer.NewKVIndexer(dbm.NewMemDB(), ctx.Logger, clientCtx) + + suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, idxer) + suite.backend.queryClient.QueryClient = mocks.NewEVMQueryClient(suite.T()) + suite.backend.clientCtx.Client = mocks.NewClient(suite.T()) + suite.backend.queryClient.FeeMarket = mocks.NewFeeMarketQueryClient(suite.T()) + suite.backend.ctx = rpctypes.ContextWithHeight(1) + + // Add codec + encCfg := encoding.MakeConfig(app.ModuleBasics) + suite.backend.clientCtx.Codec = encCfg.Codec +} + +// buildEthereumTx returns an example legacy Ethereum transaction +func (suite *BackendTestSuite) buildEthereumTx() (*evmtypes.MsgEthereumTx, []byte) { + msgEthereumTx := evmtypes.NewTx( + suite.backend.chainID, + uint64(0), + &common.Address{}, + big.NewInt(0), + 100000, + big.NewInt(1), + nil, + nil, + nil, + nil, + ) + suite.signAndEncodeEthTx(msgEthereumTx) + // A valid msg should have empty `From` + msgEthereumTx.From = "" + + txBuilder := suite.backend.clientCtx.TxConfig.NewTxBuilder() + txBuilder.SetSignatures() + err := txBuilder.SetMsgs(msgEthereumTx) + suite.Require().NoError(err) + + bz, err := suite.backend.clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) + suite.Require().NoError(err) + return msgEthereumTx, bz +} + +// buildFormattedBlock returns a formatted block for testing +func (suite *BackendTestSuite) buildFormattedBlock( + blockRes *tmrpctypes.ResultBlockResults, + resBlock *tmrpctypes.ResultBlock, + fullTx bool, + tx *evmtypes.MsgEthereumTx, + validator sdk.AccAddress, + baseFee *big.Int, +) map[string]interface{} { + header := resBlock.Block.Header + gasLimit := int64(^uint32(0)) // for `MaxGas = -1` (DefaultConsensusParams) + gasUsed := new(big.Int).SetUint64(uint64(blockRes.TxsResults[0].GasUsed)) + + root := common.Hash{}.Bytes() + receipt := ethtypes.NewReceipt(root, false, gasUsed.Uint64()) + bloom := ethtypes.CreateBloom(ethtypes.Receipts{receipt}) + + ethRPCTxs := []interface{}{} + if tx != nil { + if fullTx { + rpcTx, err := rpctypes.NewRPCTransaction( + tx.AsTransaction(), + common.BytesToHash(header.Hash()), + uint64(header.Height), + uint64(0), + baseFee, + suite.backend.chainID, + ) + suite.Require().NoError(err) + ethRPCTxs = []interface{}{rpcTx} + } else { + ethRPCTxs = []interface{}{common.HexToHash(tx.Hash)} + } + } + + return rpctypes.FormatBlock( + header, + resBlock.Block.Size(), + gasLimit, + gasUsed, + ethRPCTxs, + bloom, + common.BytesToAddress(validator.Bytes()), + baseFee, + ) +} + +func (suite *BackendTestSuite) generateTestKeyring(clientDir string) (keyring.Keyring, error) { + buf := bufio.NewReader(os.Stdin) + encCfg := encoding.MakeConfig(app.ModuleBasics) + return keyring.New( + sdk.KeyringServiceName(), + keyring.BackendTest, + clientDir, + buf, + encCfg.Codec, + []keyring.Option{hd.EthSecp256k1Option()}...) +} + +func (suite *BackendTestSuite) signAndEncodeEthTx(msgEthereumTx *evmtypes.MsgEthereumTx) []byte { + from, priv := tests.NewAddrKey() + signer := tests.NewSigner(priv) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsWithoutHeader(queryClient, 1) + + ethSigner := ethtypes.LatestSigner(suite.backend.ChainConfig()) + msgEthereumTx.From = from.String() + err := msgEthereumTx.Sign(ethSigner, signer) + suite.Require().NoError(err) + + tx, err := msgEthereumTx.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), "azeta") + suite.Require().NoError(err) + + txEncoder := suite.backend.clientCtx.TxConfig.TxEncoder() + txBz, err := txEncoder(tx) + suite.Require().NoError(err) + + return txBz +} diff --git a/rpc/backend/blocks_test.go b/rpc/backend/blocks_test.go new file mode 100644 index 0000000000..218680ebe5 --- /dev/null +++ b/rpc/backend/blocks_test.go @@ -0,0 +1,1615 @@ +package backend + +import ( + "fmt" + "math/big" + + sdkmath "cosmossdk.io/math" + "github.com/cometbft/cometbft/abci/types" + tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + tmtypes "github.com/cometbft/cometbft/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" + "google.golang.org/grpc/metadata" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + ethrpc "github.com/zeta-chain/zetacore/rpc/types" +) + +func (suite *BackendTestSuite) TestBlockNumber() { + testCases := []struct { + name string + registerMock func() + expBlockNumber hexutil.Uint64 + expPass bool + }{ + { + "fail - invalid block header height", + func() { + var header metadata.MD + height := int64(1) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsInvalidHeight(queryClient, &header, int64(height)) + }, + 0x0, + false, + }, + { + "fail - invalid block header", + func() { + var header metadata.MD + height := int64(1) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsInvalidHeader(queryClient, &header, int64(height)) + }, + 0x0, + false, + }, + { + "pass - app state header height 1", + func() { + var header metadata.MD + height := int64(1) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParams(queryClient, &header, int64(height)) + }, + 0x1, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + blockNumber, err := suite.backend.BlockNumber() + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expBlockNumber, blockNumber) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetBlockByNumber() { + var ( + blockRes *tmrpctypes.ResultBlockResults + resBlock *tmrpctypes.ResultBlock + ) + msgEthereumTx, bz := suite.buildEthereumTx() + testCases := []struct { + name string + blockNumber ethrpc.BlockNumber + fullTx bool + baseFee *big.Int + validator sdk.AccAddress + tx *evmtypes.MsgEthereumTx + txBz []byte + registerMock func(ethrpc.BlockNumber, sdkmath.Int, sdk.AccAddress, []byte) + expNoop bool + expPass bool + }{ + { + "pass - tendermint block not found", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(blockNum ethrpc.BlockNumber, _ sdkmath.Int, _ sdk.AccAddress, _ []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, height) + }, + true, + true, + }, + { + "pass - block not found (e.g. request block height that is greater than current one)", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockNotFound(client, height) + }, + true, + true, + }, + { + "pass - block results error", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlock(client, height, txBz) + RegisterBlockResultsError(client, blockNum.Int64()) + }, + true, + true, + }, + { + "pass - without tx", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlock(client, height, txBz) + blockRes, _ = RegisterBlockResults(client, blockNum.Int64()) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + }, + false, + true, + }, + { + "pass - with tx", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + msgEthereumTx, + bz, + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlock(client, height, txBz) + blockRes, _ = RegisterBlockResults(client, blockNum.Int64()) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + }, + false, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(tc.blockNumber, sdk.NewIntFromBigInt(tc.baseFee), tc.validator, tc.txBz) + + block, err := suite.backend.GetBlockByNumber(tc.blockNumber, tc.fullTx) + + if tc.expPass { + if tc.expNoop { + suite.Require().Nil(block) + } else { + expBlock := suite.buildFormattedBlock( + blockRes, + resBlock, + tc.fullTx, + tc.tx, + tc.validator, + tc.baseFee, + ) + suite.Require().Equal(expBlock, block) + } + suite.Require().NoError(err) + + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetBlockByHash() { + var ( + blockRes *tmrpctypes.ResultBlockResults + resBlock *tmrpctypes.ResultBlock + ) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsWithoutHeader(queryClient, 1) + msgEthereumTx, bz := suite.buildEthereumTx() + + block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) + + testCases := []struct { + name string + hash common.Hash + fullTx bool + baseFee *big.Int + validator sdk.AccAddress + tx *evmtypes.MsgEthereumTx + txBz []byte + registerMock func(common.Hash, sdkmath.Int, sdk.AccAddress, []byte) + expNoop bool + expPass bool + }{ + { + "fail - tendermint failed to get block", + common.BytesToHash(block.Hash()), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(hash common.Hash, baseFee sdkmath.Int, validator sdk.AccAddress, txBz []byte) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashError(client, hash, txBz) + }, + false, + false, + }, + { + "noop - tendermint blockres not found", + common.BytesToHash(block.Hash()), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(hash common.Hash, baseFee sdkmath.Int, validator sdk.AccAddress, txBz []byte) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashNotFound(client, hash, txBz) + }, + true, + true, + }, + { + "noop - tendermint failed to fetch block result", + common.BytesToHash(block.Hash()), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(hash common.Hash, baseFee sdkmath.Int, validator sdk.AccAddress, txBz []byte) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, hash, txBz) + + RegisterBlockResultsError(client, height) + }, + true, + true, + }, + { + "pass - without tx", + common.BytesToHash(block.Hash()), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(hash common.Hash, baseFee sdkmath.Int, validator sdk.AccAddress, txBz []byte) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, hash, txBz) + + blockRes, _ = RegisterBlockResults(client, height) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + }, + false, + true, + }, + { + "pass - with tx", + common.BytesToHash(block.Hash()), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + msgEthereumTx, + bz, + func(hash common.Hash, baseFee sdkmath.Int, validator sdk.AccAddress, txBz []byte) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, hash, txBz) + + blockRes, _ = RegisterBlockResults(client, height) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + }, + false, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(tc.hash, sdk.NewIntFromBigInt(tc.baseFee), tc.validator, tc.txBz) + + block, err := suite.backend.GetBlockByHash(tc.hash, tc.fullTx) + + if tc.expPass { + if tc.expNoop { + suite.Require().Nil(block) + } else { + expBlock := suite.buildFormattedBlock( + blockRes, + resBlock, + tc.fullTx, + tc.tx, + tc.validator, + tc.baseFee, + ) + suite.Require().Equal(expBlock, block) + } + suite.Require().NoError(err) + + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetBlockTransactionCountByHash() { + _, bz := suite.buildEthereumTx() + block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) + emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + + testCases := []struct { + name string + hash common.Hash + registerMock func(common.Hash) + expCount hexutil.Uint + expPass bool + }{ + { + "fail - block not found", + common.BytesToHash(emptyBlock.Hash()), + func(hash common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashError(client, hash, nil) + }, + hexutil.Uint(0), + false, + }, + { + "fail - tendermint client failed to get block result", + common.BytesToHash(emptyBlock.Hash()), + func(hash common.Hash) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHash(client, hash, nil) + RegisterBlockResultsError(client, height) + }, + hexutil.Uint(0), + false, + }, + { + "pass - block without tx", + common.BytesToHash(emptyBlock.Hash()), + func(hash common.Hash) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHash(client, hash, nil) + RegisterBlockResults(client, height) + }, + hexutil.Uint(0), + true, + }, + { + "pass - block with tx", + common.BytesToHash(block.Hash()), + func(hash common.Hash) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHash(client, hash, bz) + RegisterBlockResults(client, height) + }, + hexutil.Uint(1), + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + + tc.registerMock(tc.hash) + count := suite.backend.GetBlockTransactionCountByHash(tc.hash) + if tc.expPass { + suite.Require().Equal(tc.expCount, *count) + } else { + suite.Require().Nil(count) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetBlockTransactionCountByNumber() { + _, bz := suite.buildEthereumTx() + block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) + emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + + testCases := []struct { + name string + blockNum ethrpc.BlockNumber + registerMock func(ethrpc.BlockNumber) + expCount hexutil.Uint + expPass bool + }{ + { + "fail - block not found", + ethrpc.BlockNumber(emptyBlock.Height), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, height) + }, + hexutil.Uint(0), + false, + }, + { + "fail - tendermint client failed to get block result", + ethrpc.BlockNumber(emptyBlock.Height), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, height, nil) + RegisterBlockResultsError(client, height) + }, + hexutil.Uint(0), + false, + }, + { + "pass - block without tx", + ethrpc.BlockNumber(emptyBlock.Height), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, height, nil) + RegisterBlockResults(client, height) + }, + hexutil.Uint(0), + true, + }, + { + "pass - block with tx", + ethrpc.BlockNumber(block.Height), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, height, bz) + RegisterBlockResults(client, height) + }, + hexutil.Uint(1), + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + + tc.registerMock(tc.blockNum) + count := suite.backend.GetBlockTransactionCountByNumber(tc.blockNum) + if tc.expPass { + suite.Require().Equal(tc.expCount, *count) + } else { + suite.Require().Nil(count) + } + }) + } +} + +func (suite *BackendTestSuite) TestTendermintBlockByNumber() { + var expResultBlock *tmrpctypes.ResultBlock + + testCases := []struct { + name string + blockNumber ethrpc.BlockNumber + registerMock func(ethrpc.BlockNumber) + found bool + expPass bool + }{ + { + "fail - client error", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, height) + }, + false, + false, + }, + { + "noop - block not found", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockNotFound(client, height) + }, + false, + true, + }, + { + "fail - blockNum < 0 with app state height error", + ethrpc.BlockNumber(-1), + func(_ ethrpc.BlockNumber) { + var header metadata.MD + appHeight := int64(1) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsError(queryClient, &header, appHeight) + }, + false, + false, + }, + { + "pass - blockNum < 0 with app state height >= 1", + ethrpc.BlockNumber(-1), + func(blockNum ethrpc.BlockNumber) { + var header metadata.MD + appHeight := int64(1) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParams(queryClient, &header, appHeight) + + tmHeight := appHeight + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlock(client, tmHeight, nil) + }, + true, + true, + }, + { + "pass - blockNum = 0 (defaults to blockNum = 1 due to a difference between tendermint heights and geth heights", + ethrpc.BlockNumber(0), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlock(client, height, nil) + }, + true, + true, + }, + { + "pass - blockNum = 1", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlock(client, height, nil) + }, + true, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + + tc.registerMock(tc.blockNumber) + resultBlock, err := suite.backend.TendermintBlockByNumber(tc.blockNumber) + + if tc.expPass { + suite.Require().NoError(err) + + if !tc.found { + suite.Require().Nil(resultBlock) + } else { + suite.Require().Equal(expResultBlock, resultBlock) + suite.Require().Equal(expResultBlock.Block.Header.Height, resultBlock.Block.Header.Height) + } + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestTendermintBlockResultByNumber() { + var expBlockRes *tmrpctypes.ResultBlockResults + + testCases := []struct { + name string + blockNumber int64 + registerMock func(int64) + expPass bool + }{ + { + "fail", + 1, + func(blockNum int64) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockResultsError(client, blockNum) + }, + false, + }, + { + "pass", + 1, + func(blockNum int64) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockResults(client, blockNum) + + expBlockRes = &tmrpctypes.ResultBlockResults{ + Height: blockNum, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + } + }, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(tc.blockNumber) + + blockRes, err := suite.backend.TendermintBlockResultByNumber(&tc.blockNumber) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(expBlockRes, blockRes) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestBlockNumberFromTendermint() { + var resBlock *tmrpctypes.ResultBlock + + _, bz := suite.buildEthereumTx() + block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) + blockNum := ethrpc.NewBlockNumber(big.NewInt(block.Height)) + blockHash := common.BytesToHash(block.Hash()) + + testCases := []struct { + name string + blockNum *ethrpc.BlockNumber + hash *common.Hash + registerMock func(*common.Hash) + expPass bool + }{ + { + "error - without blockHash or blockNum", + nil, + nil, + func(hash *common.Hash) {}, + false, + }, + { + "error - with blockHash, tendermint client failed to get block", + nil, + &blockHash, + func(hash *common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashError(client, *hash, bz) + }, + false, + }, + { + "pass - with blockHash", + nil, + &blockHash, + func(hash *common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, *hash, bz) + }, + true, + }, + { + "pass - without blockHash & with blockNumber", + &blockNum, + nil, + func(hash *common.Hash) {}, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + + blockNrOrHash := ethrpc.BlockNumberOrHash{ + BlockNumber: tc.blockNum, + BlockHash: tc.hash, + } + + tc.registerMock(tc.hash) + blockNum, err := suite.backend.BlockNumberFromTendermint(blockNrOrHash) + + if tc.expPass { + suite.Require().NoError(err) + if tc.hash == nil { + suite.Require().Equal(*tc.blockNum, blockNum) + } else { + expHeight := ethrpc.NewBlockNumber(big.NewInt(resBlock.Block.Height)) + suite.Require().Equal(expHeight, blockNum) + } + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestBlockNumberFromTendermintByHash() { + var resBlock *tmrpctypes.ResultBlock + + _, bz := suite.buildEthereumTx() + block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) + emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + + testCases := []struct { + name string + hash common.Hash + registerMock func(common.Hash) + expPass bool + }{ + { + "fail - tendermint client failed to get block", + common.BytesToHash(block.Hash()), + func(hash common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashError(client, hash, bz) + }, + false, + }, + { + "pass - block without tx", + common.BytesToHash(emptyBlock.Hash()), + func(hash common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, hash, bz) + }, + true, + }, + { + "pass - block with tx", + common.BytesToHash(block.Hash()), + func(hash common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, hash, bz) + }, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + + tc.registerMock(tc.hash) + blockNum, err := suite.backend.BlockNumberFromTendermintByHash(tc.hash) + if tc.expPass { + expHeight := big.NewInt(resBlock.Block.Height) + suite.Require().NoError(err) + suite.Require().Equal(expHeight, blockNum) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestBlockBloom() { + testCases := []struct { + name string + blockRes *tmrpctypes.ResultBlockResults + expBlockBloom ethtypes.Bloom + expPass bool + }{ + { + "fail - empty block result", + &tmrpctypes.ResultBlockResults{}, + ethtypes.Bloom{}, + false, + }, + { + "fail - non block bloom event type", + &tmrpctypes.ResultBlockResults{ + EndBlockEvents: []types.Event{{Type: evmtypes.EventTypeEthereumTx}}, + }, + ethtypes.Bloom{}, + false, + }, + { + "fail - nonblock bloom attribute key", + &tmrpctypes.ResultBlockResults{ + EndBlockEvents: []types.Event{ + { + Type: evmtypes.EventTypeBlockBloom, + Attributes: []types.EventAttribute{ + {Key: string(evmtypes.AttributeKeyEthereumTxHash)}, + }, + }, + }, + }, + ethtypes.Bloom{}, + false, + }, + { + "pass - block bloom attribute key", + &tmrpctypes.ResultBlockResults{ + EndBlockEvents: []types.Event{ + { + Type: evmtypes.EventTypeBlockBloom, + Attributes: []types.EventAttribute{ + {Key: string(bAttributeKeyEthereumBloom)}, + }, + }, + }, + }, + ethtypes.Bloom{}, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + blockBloom, err := suite.backend.BlockBloom(tc.blockRes) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expBlockBloom, blockBloom) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { + msgEthereumTx, bz := suite.buildEthereumTx() + emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + + testCases := []struct { + name string + baseFee *big.Int + validator sdk.AccAddress + height int64 + resBlock *tmrpctypes.ResultBlock + blockRes *tmrpctypes.ResultBlockResults + fullTx bool + registerMock func(sdkmath.Int, sdk.AccAddress, int64) + expTxs bool + expPass bool + }{ + { + "pass - block without tx", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(common.Address{}.Bytes()), + int64(1), + &tmrpctypes.ResultBlock{Block: emptyBlock}, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + false, + func(baseFee sdkmath.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + false, + true, + }, + { + "pass - block with tx - with BaseFee error", + nil, + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + true, + func(baseFee sdkmath.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFeeError(queryClient) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + true, + true, + }, + { + "pass - block with tx - with ValidatorAccount error", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(common.Address{}.Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + true, + func(baseFee sdkmath.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccountError(queryClient) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + true, + true, + }, + { + "pass - block with tx - with ConsensusParams error - BlockMaxGas defaults to max uint32", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + true, + func(baseFee sdkmath.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParamsError(client, height) + }, + true, + true, + }, + { + "pass - block with tx - with ShouldIgnoreGasUsed - empty txs", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{ + { + Code: 11, + GasUsed: 0, + Log: "no block gas left to run tx: out of gas", + }, + }, + }, + true, + func(baseFee sdkmath.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + false, + true, + }, + { + "pass - block with tx - non fullTx", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + false, + func(baseFee sdkmath.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + true, + true, + }, + { + "pass - block with tx", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + true, + func(baseFee sdkmath.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + true, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(sdk.NewIntFromBigInt(tc.baseFee), tc.validator, tc.height) + + block, err := suite.backend.RPCBlockFromTendermintBlock(tc.resBlock, tc.blockRes, tc.fullTx) + + var expBlock map[string]interface{} + header := tc.resBlock.Block.Header + gasLimit := int64(^uint32(0)) // for `MaxGas = -1` (DefaultConsensusParams) + gasUsed := new(big.Int).SetUint64(uint64(tc.blockRes.TxsResults[0].GasUsed)) + + root := common.Hash{}.Bytes() + receipt := ethtypes.NewReceipt(root, false, gasUsed.Uint64()) + bloom := ethtypes.CreateBloom(ethtypes.Receipts{receipt}) + + ethRPCTxs := []interface{}{} + + if tc.expTxs { + if tc.fullTx { + rpcTx, err := ethrpc.NewRPCTransaction( + msgEthereumTx.AsTransaction(), + common.BytesToHash(header.Hash()), + uint64(header.Height), + uint64(0), + tc.baseFee, + suite.backend.chainID, + ) + suite.Require().NoError(err) + ethRPCTxs = []interface{}{rpcTx} + } else { + ethRPCTxs = []interface{}{common.HexToHash(msgEthereumTx.Hash)} + } + } + + expBlock = ethrpc.FormatBlock( + header, + tc.resBlock.Block.Size(), + gasLimit, + gasUsed, + ethRPCTxs, + bloom, + common.BytesToAddress(tc.validator.Bytes()), + tc.baseFee, + ) + + if tc.expPass { + suite.Require().Equal(expBlock, block) + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestEthMsgsFromTendermintBlock() { + msgEthereumTx, bz := suite.buildEthereumTx() + + testCases := []struct { + name string + resBlock *tmrpctypes.ResultBlock + blockRes *tmrpctypes.ResultBlockResults + expMsgs []*evmtypes.MsgEthereumTx + }{ + { + "tx in not included in block - unsuccessful tx without ExceedBlockGasLimit error", + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + TxsResults: []*types.ResponseDeliverTx{ + { + Code: 1, + }, + }, + }, + []*evmtypes.MsgEthereumTx(nil), + }, + { + "tx included in block - unsuccessful tx with ExceedBlockGasLimit error", + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + TxsResults: []*types.ResponseDeliverTx{ + { + Code: 1, + Log: ethrpc.ExceedBlockGasLimitError, + }, + }, + }, + []*evmtypes.MsgEthereumTx{msgEthereumTx}, + }, + { + "pass", + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + TxsResults: []*types.ResponseDeliverTx{ + { + Code: 0, + Log: ethrpc.ExceedBlockGasLimitError, + }, + }, + }, + []*evmtypes.MsgEthereumTx{msgEthereumTx}, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + + msgs, _ := suite.backend.EthMsgsFromTendermintBlock(tc.resBlock, tc.blockRes) + suite.Require().Equal(tc.expMsgs, msgs) + }) + } +} + +func (suite *BackendTestSuite) TestHeaderByNumber() { + var expResultBlock *tmrpctypes.ResultBlock + + _, bz := suite.buildEthereumTx() + + testCases := []struct { + name string + blockNumber ethrpc.BlockNumber + baseFee *big.Int + registerMock func(ethrpc.BlockNumber, sdkmath.Int) + expPass bool + }{ + { + "fail - tendermint client failed to get block", + ethrpc.BlockNumber(1), + sdk.NewInt(1).BigInt(), + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, height) + }, + false, + }, + { + "fail - block not found for height", + ethrpc.BlockNumber(1), + sdk.NewInt(1).BigInt(), + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockNotFound(client, height) + }, + false, + }, + { + "fail - block not found for height", + ethrpc.BlockNumber(1), + sdk.NewInt(1).BigInt(), + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, height, nil) + RegisterBlockResultsError(client, height) + }, + false, + }, + { + "pass - without Base Fee, failed to fetch from prunned block", + ethrpc.BlockNumber(1), + nil, + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlock(client, height, nil) + RegisterBlockResults(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFeeError(queryClient) + }, + true, + }, + { + "pass - blockNum = 1, without tx", + ethrpc.BlockNumber(1), + sdk.NewInt(1).BigInt(), + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlock(client, height, nil) + RegisterBlockResults(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + }, + true, + }, + { + "pass - blockNum = 1, with tx", + ethrpc.BlockNumber(1), + sdk.NewInt(1).BigInt(), + func(blockNum ethrpc.BlockNumber, baseFee sdkmath.Int) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlock(client, height, bz) + RegisterBlockResults(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + }, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + + tc.registerMock(tc.blockNumber, sdk.NewIntFromBigInt(tc.baseFee)) + header, err := suite.backend.HeaderByNumber(tc.blockNumber) + + if tc.expPass { + expHeader := ethrpc.EthHeaderFromTendermint(expResultBlock.Block.Header, ethtypes.Bloom{}, tc.baseFee) + suite.Require().NoError(err) + suite.Require().Equal(expHeader, header) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestHeaderByHash() { + var expResultBlock *tmrpctypes.ResultBlock + + _, bz := suite.buildEthereumTx() + block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) + emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + + testCases := []struct { + name string + hash common.Hash + baseFee *big.Int + registerMock func(common.Hash, sdkmath.Int) + expPass bool + }{ + { + "fail - tendermint client failed to get block", + common.BytesToHash(block.Hash()), + sdk.NewInt(1).BigInt(), + func(hash common.Hash, baseFee sdkmath.Int) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashError(client, hash, bz) + }, + false, + }, + { + "fail - block not found for height", + common.BytesToHash(block.Hash()), + sdk.NewInt(1).BigInt(), + func(hash common.Hash, baseFee sdkmath.Int) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashNotFound(client, hash, bz) + }, + false, + }, + { + "fail - block not found for height", + common.BytesToHash(block.Hash()), + sdk.NewInt(1).BigInt(), + func(hash common.Hash, baseFee sdkmath.Int) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHash(client, hash, bz) + RegisterBlockResultsError(client, height) + }, + false, + }, + { + "pass - without Base Fee, failed to fetch from prunned block", + common.BytesToHash(block.Hash()), + nil, + func(hash common.Hash, baseFee sdkmath.Int) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlockByHash(client, hash, bz) + RegisterBlockResults(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFeeError(queryClient) + }, + true, + }, + { + "pass - blockNum = 1, without tx", + common.BytesToHash(emptyBlock.Hash()), + sdk.NewInt(1).BigInt(), + func(hash common.Hash, baseFee sdkmath.Int) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlockByHash(client, hash, nil) + RegisterBlockResults(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + }, + true, + }, + { + "pass - with tx", + common.BytesToHash(block.Hash()), + sdk.NewInt(1).BigInt(), + func(hash common.Hash, baseFee sdkmath.Int) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlockByHash(client, hash, bz) + RegisterBlockResults(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + }, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + + tc.registerMock(tc.hash, sdk.NewIntFromBigInt(tc.baseFee)) + header, err := suite.backend.HeaderByHash(tc.hash) + + if tc.expPass { + expHeader := ethrpc.EthHeaderFromTendermint(expResultBlock.Block.Header, ethtypes.Bloom{}, tc.baseFee) + suite.Require().NoError(err) + suite.Require().Equal(expHeader, header) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestEthBlockByNumber() { + msgEthereumTx, bz := suite.buildEthereumTx() + emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + + testCases := []struct { + name string + blockNumber ethrpc.BlockNumber + registerMock func(ethrpc.BlockNumber) + expEthBlock *ethtypes.Block + expPass bool + }{ + { + "fail - tendermint client failed to get block", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, height) + }, + nil, + false, + }, + { + "fail - block result not found for height", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, height, nil) + RegisterBlockResultsError(client, blockNum.Int64()) + }, + nil, + false, + }, + { + "pass - block without tx", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, height, nil) + + RegisterBlockResults(client, blockNum.Int64()) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + baseFee := sdk.NewInt(1) + RegisterBaseFee(queryClient, baseFee) + }, + ethtypes.NewBlock( + ethrpc.EthHeaderFromTendermint( + emptyBlock.Header, + ethtypes.Bloom{}, + sdk.NewInt(1).BigInt(), + ), + []*ethtypes.Transaction{}, + nil, + nil, + nil, + ), + true, + }, + { + "pass - block with tx", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, height, bz) + + RegisterBlockResults(client, blockNum.Int64()) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + baseFee := sdk.NewInt(1) + RegisterBaseFee(queryClient, baseFee) + }, + ethtypes.NewBlock( + ethrpc.EthHeaderFromTendermint( + emptyBlock.Header, + ethtypes.Bloom{}, + sdk.NewInt(1).BigInt(), + ), + []*ethtypes.Transaction{msgEthereumTx.AsTransaction()}, + nil, + nil, + trie.NewStackTrie(nil), + ), + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(tc.blockNumber) + + ethBlock, err := suite.backend.EthBlockByNumber(tc.blockNumber) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expEthBlock.Header(), ethBlock.Header()) + suite.Require().Equal(tc.expEthBlock.Uncles(), ethBlock.Uncles()) + suite.Require().Equal(tc.expEthBlock.ReceiptHash(), ethBlock.ReceiptHash()) + for i, tx := range tc.expEthBlock.Transactions() { + suite.Require().Equal(tx.Data(), ethBlock.Transactions()[i].Data()) + } + + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestEthBlockFromTendermintBlock() { + msgEthereumTx, bz := suite.buildEthereumTx() + emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + + testCases := []struct { + name string + baseFee *big.Int + resBlock *tmrpctypes.ResultBlock + blockRes *tmrpctypes.ResultBlockResults + registerMock func(sdkmath.Int, int64) + expEthBlock *ethtypes.Block + expPass bool + }{ + { + "pass - block without tx", + sdk.NewInt(1).BigInt(), + &tmrpctypes.ResultBlock{ + Block: emptyBlock, + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + func(baseFee sdkmath.Int, blockNum int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + }, + ethtypes.NewBlock( + ethrpc.EthHeaderFromTendermint( + emptyBlock.Header, + ethtypes.Bloom{}, + sdk.NewInt(1).BigInt(), + ), + []*ethtypes.Transaction{}, + nil, + nil, + nil, + ), + true, + }, + { + "pass - block with tx", + sdk.NewInt(1).BigInt(), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + EndBlockEvents: []types.Event{ + { + Type: evmtypes.EventTypeBlockBloom, + Attributes: []types.EventAttribute{ + {Key: string(bAttributeKeyEthereumBloom)}, + }, + }, + }, + }, + func(baseFee sdkmath.Int, blockNum int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + }, + ethtypes.NewBlock( + ethrpc.EthHeaderFromTendermint( + emptyBlock.Header, + ethtypes.Bloom{}, + sdk.NewInt(1).BigInt(), + ), + []*ethtypes.Transaction{msgEthereumTx.AsTransaction()}, + nil, + nil, + trie.NewStackTrie(nil), + ), + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(sdk.NewIntFromBigInt(tc.baseFee), tc.blockRes.Height) + + ethBlock, err := suite.backend.EthBlockFromTendermintBlock(tc.resBlock, tc.blockRes) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expEthBlock.Header(), ethBlock.Header()) + suite.Require().Equal(tc.expEthBlock.Uncles(), ethBlock.Uncles()) + suite.Require().Equal(tc.expEthBlock.ReceiptHash(), ethBlock.ReceiptHash()) + for i, tx := range tc.expEthBlock.Transactions() { + suite.Require().Equal(tx.Data(), ethBlock.Transactions()[i].Data()) + } + + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/rpc/backend/call_tx_test.go b/rpc/backend/call_tx_test.go new file mode 100644 index 0000000000..e0c7158ceb --- /dev/null +++ b/rpc/backend/call_tx_test.go @@ -0,0 +1,504 @@ +package backend + +import ( + "encoding/json" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rlp" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" + "google.golang.org/grpc/metadata" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + rpctypes "github.com/zeta-chain/zetacore/rpc/types" +) + +func (suite *BackendTestSuite) TestResend() { + txNonce := (hexutil.Uint64)(1) + baseFee := sdk.NewInt(1) + gasPrice := new(hexutil.Big) + toAddr := tests.GenerateAddress() + chainID := (*hexutil.Big)(suite.backend.chainID) + callArgs := evmtypes.TransactionArgs{ + From: nil, + To: &toAddr, + Gas: nil, + GasPrice: nil, + MaxFeePerGas: gasPrice, + MaxPriorityFeePerGas: gasPrice, + Value: gasPrice, + Nonce: &txNonce, + Input: nil, + Data: nil, + AccessList: nil, + ChainID: chainID, + } + + testCases := []struct { + name string + registerMock func() + args evmtypes.TransactionArgs + gasPrice *hexutil.Big + gasLimit *hexutil.Uint64 + expHash common.Hash + expPass bool + }{ + { + "fail - Missing transaction nonce ", + func() {}, + evmtypes.TransactionArgs{ + Nonce: nil, + }, + nil, + nil, + common.Hash{}, + false, + }, + { + "pass - Can't set Tx defaults BaseFee disabled", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParams(queryClient, &header, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFeeDisabled(queryClient) + }, + evmtypes.TransactionArgs{ + Nonce: &txNonce, + ChainID: callArgs.ChainID, + }, + nil, + nil, + common.Hash{}, + true, + }, + { + "pass - Can't set Tx defaults ", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + feeMarketClient := suite.backend.queryClient.FeeMarket.(*mocks.FeeMarketQueryClient) + RegisterParams(queryClient, &header, 1) + RegisterFeeMarketParams(feeMarketClient, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, baseFee) + }, + evmtypes.TransactionArgs{ + Nonce: &txNonce, + }, + nil, + nil, + common.Hash{}, + true, + }, + { + "pass - MaxFeePerGas is nil", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParams(queryClient, &header, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFeeDisabled(queryClient) + }, + evmtypes.TransactionArgs{ + Nonce: &txNonce, + MaxPriorityFeePerGas: nil, + GasPrice: nil, + MaxFeePerGas: nil, + }, + nil, + nil, + common.Hash{}, + true, + }, + { + "fail - GasPrice and (MaxFeePerGas or MaxPriorityPerGas specified", + func() {}, + evmtypes.TransactionArgs{ + Nonce: &txNonce, + MaxPriorityFeePerGas: nil, + GasPrice: gasPrice, + MaxFeePerGas: gasPrice, + }, + nil, + nil, + common.Hash{}, + false, + }, + { + "fail - Block error", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParams(queryClient, &header, 1) + RegisterBlockError(client, 1) + }, + evmtypes.TransactionArgs{ + Nonce: &txNonce, + }, + nil, + nil, + common.Hash{}, + false, + }, + { + "pass - MaxFeePerGas is nil", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParams(queryClient, &header, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, baseFee) + }, + evmtypes.TransactionArgs{ + Nonce: &txNonce, + GasPrice: nil, + MaxPriorityFeePerGas: gasPrice, + MaxFeePerGas: gasPrice, + ChainID: callArgs.ChainID, + }, + nil, + nil, + common.Hash{}, + true, + }, + { + "pass - Chain Id is nil", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParams(queryClient, &header, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, baseFee) + }, + evmtypes.TransactionArgs{ + Nonce: &txNonce, + MaxPriorityFeePerGas: gasPrice, + ChainID: nil, + }, + nil, + nil, + common.Hash{}, + true, + }, + { + "fail - Pending transactions error", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, baseFee) + RegisterEstimateGas(queryClient, callArgs) + RegisterParams(queryClient, &header, 1) + RegisterParamsWithoutHeader(queryClient, 1) + RegisterUnconfirmedTxsError(client, nil) + }, + evmtypes.TransactionArgs{ + Nonce: &txNonce, + To: &toAddr, + MaxFeePerGas: gasPrice, + MaxPriorityFeePerGas: gasPrice, + Value: gasPrice, + Gas: nil, + ChainID: callArgs.ChainID, + }, + gasPrice, + nil, + common.Hash{}, + false, + }, + { + "fail - Not Ethereum txs", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, baseFee) + RegisterEstimateGas(queryClient, callArgs) + RegisterParams(queryClient, &header, 1) + RegisterParamsWithoutHeader(queryClient, 1) + RegisterUnconfirmedTxsEmpty(client, nil) + }, + evmtypes.TransactionArgs{ + Nonce: &txNonce, + To: &toAddr, + MaxFeePerGas: gasPrice, + MaxPriorityFeePerGas: gasPrice, + Value: gasPrice, + Gas: nil, + ChainID: callArgs.ChainID, + }, + gasPrice, + nil, + common.Hash{}, + false, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + hash, err := suite.backend.Resend(tc.args, tc.gasPrice, tc.gasLimit) + + if tc.expPass { + suite.Require().Equal(tc.expHash, hash) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestSendRawTransaction() { + ethTx, bz := suite.buildEthereumTx() + rlpEncodedBz, err := rlp.EncodeToBytes(ethTx.AsTransaction()) + suite.Require().NoError(err) + cosmosTx, err := ethTx.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), "azeta") + suite.Require().NoError(err) + txBytes, err := suite.backend.clientCtx.TxConfig.TxEncoder()(cosmosTx) + suite.Require().NoError(err) + + testCases := []struct { + name string + registerMock func() + rawTx []byte + expHash common.Hash + expPass bool + }{ + { + "fail - empty bytes", + func() {}, + []byte{}, + common.Hash{}, + false, + }, + { + "fail - no RLP encoded bytes", + func() {}, + bz, + common.Hash{}, + false, + }, + { + "fail - unprotected transactions", + func() { + suite.backend.allowUnprotectedTxs = false + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsWithoutHeaderError(queryClient, 1) + }, + rlpEncodedBz, + common.Hash{}, + false, + }, + { + "fail - failed to get evm params", + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + suite.backend.allowUnprotectedTxs = true + RegisterParamsWithoutHeaderError(queryClient, 1) + }, + rlpEncodedBz, + common.Hash{}, + false, + }, + { + "fail - failed to broadcast transaction", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + suite.backend.allowUnprotectedTxs = true + RegisterParamsWithoutHeader(queryClient, 1) + RegisterBroadcastTxError(client, txBytes) + }, + rlpEncodedBz, + common.HexToHash(ethTx.Hash), + false, + }, + { + "pass - Gets the correct transaction hash of the eth transaction", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + suite.backend.allowUnprotectedTxs = true + RegisterParamsWithoutHeader(queryClient, 1) + RegisterBroadcastTx(client, txBytes) + }, + rlpEncodedBz, + common.HexToHash(ethTx.Hash), + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + hash, err := suite.backend.SendRawTransaction(tc.rawTx) + + if tc.expPass { + suite.Require().Equal(tc.expHash, hash) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestDoCall() { + _, bz := suite.buildEthereumTx() + gasPrice := (*hexutil.Big)(big.NewInt(1)) + toAddr := tests.GenerateAddress() + chainID := (*hexutil.Big)(suite.backend.chainID) + callArgs := evmtypes.TransactionArgs{ + From: nil, + To: &toAddr, + Gas: nil, + GasPrice: nil, + MaxFeePerGas: gasPrice, + MaxPriorityFeePerGas: gasPrice, + Value: gasPrice, + Input: nil, + Data: nil, + AccessList: nil, + ChainID: chainID, + } + argsBz, err := json.Marshal(callArgs) + suite.Require().NoError(err) + + testCases := []struct { + name string + registerMock func() + blockNum rpctypes.BlockNumber + callArgs evmtypes.TransactionArgs + expEthTx *evmtypes.MsgEthereumTxResponse + expPass bool + }{ + { + "fail - Invalid request", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBlock(client, 1, bz) + RegisterEthCallError( + queryClient, + &evmtypes.EthCallRequest{Args: argsBz, ChainId: suite.backend.chainID.Int64()}, + ) + }, + rpctypes.BlockNumber(1), + callArgs, + &evmtypes.MsgEthereumTxResponse{}, + false, + }, + { + "pass - Returned transaction response", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBlock(client, 1, bz) + RegisterEthCall( + queryClient, + &evmtypes.EthCallRequest{Args: argsBz, ChainId: suite.backend.chainID.Int64()}, + ) + }, + rpctypes.BlockNumber(1), + callArgs, + &evmtypes.MsgEthereumTxResponse{}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + msgEthTx, err := suite.backend.DoCall(tc.callArgs, tc.blockNum) + + if tc.expPass { + suite.Require().Equal(tc.expEthTx, msgEthTx) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGasPrice() { + defaultGasPrice := (*hexutil.Big)(big.NewInt(1)) + + testCases := []struct { + name string + registerMock func() + expGas *hexutil.Big + expPass bool + }{ + { + "pass - get the default gas price", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + feeMarketClient := suite.backend.queryClient.FeeMarket.(*mocks.FeeMarketQueryClient) + RegisterFeeMarketParams(feeMarketClient, 1) + RegisterParams(queryClient, &header, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, sdk.NewInt(1)) + }, + defaultGasPrice, + true, + }, + { + "fail - can't get gasFee, FeeMarketParams error", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + feeMarketClient := suite.backend.queryClient.FeeMarket.(*mocks.FeeMarketQueryClient) + RegisterFeeMarketParamsError(feeMarketClient, 1) + RegisterParams(queryClient, &header, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, sdk.NewInt(1)) + }, + defaultGasPrice, + false, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + gasPrice, err := suite.backend.GasPrice() + if tc.expPass { + suite.Require().Equal(tc.expGas, gasPrice) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/rpc/backend/chain_info_test.go b/rpc/backend/chain_info_test.go new file mode 100644 index 0000000000..c39215d1bf --- /dev/null +++ b/rpc/backend/chain_info_test.go @@ -0,0 +1,454 @@ +package backend + +import ( + "fmt" + "math/big" + + "github.com/cometbft/cometbft/abci/types" + tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common/hexutil" + ethrpc "github.com/ethereum/go-ethereum/rpc" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" + feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" + "google.golang.org/grpc/metadata" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + rpc "github.com/zeta-chain/zetacore/rpc/types" +) + +func (suite *BackendTestSuite) TestBaseFee() { + baseFee := sdk.NewInt(1) + + testCases := []struct { + name string + blockRes *tmrpctypes.ResultBlockResults + registerMock func() + expBaseFee *big.Int + expPass bool + }{ + { + "fail - grpc BaseFee error", + &tmrpctypes.ResultBlockResults{Height: 1}, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFeeError(queryClient) + }, + nil, + false, + }, + { + "fail - grpc BaseFee error - with non feemarket block event", + &tmrpctypes.ResultBlockResults{ + Height: 1, + BeginBlockEvents: []types.Event{ + { + Type: evmtypes.EventTypeBlockBloom, + }, + }, + }, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFeeError(queryClient) + }, + nil, + false, + }, + { + "fail - grpc BaseFee error - with feemarket block event", + &tmrpctypes.ResultBlockResults{ + Height: 1, + BeginBlockEvents: []types.Event{ + { + Type: feemarkettypes.EventTypeFeeMarket, + }, + }, + }, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFeeError(queryClient) + }, + nil, + false, + }, + { + "fail - grpc BaseFee error - with feemarket block event with wrong attribute value", + &tmrpctypes.ResultBlockResults{ + Height: 1, + BeginBlockEvents: []types.Event{ + { + Type: feemarkettypes.EventTypeFeeMarket, + Attributes: []types.EventAttribute{ + {Value: "/1"}, + }, + }, + }, + }, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFeeError(queryClient) + }, + nil, + false, + }, + { + "fail - grpc baseFee error - with feemarket block event with baseFee attribute value", + &tmrpctypes.ResultBlockResults{ + Height: 1, + BeginBlockEvents: []types.Event{ + { + Type: feemarkettypes.EventTypeFeeMarket, + Attributes: []types.EventAttribute{ + {Value: baseFee.String()}, + }, + }, + }, + }, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFeeError(queryClient) + }, + baseFee.BigInt(), + true, + }, + { + "fail - base fee or london fork not enabled", + &tmrpctypes.ResultBlockResults{Height: 1}, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFeeDisabled(queryClient) + }, + nil, + true, + }, + { + "pass", + &tmrpctypes.ResultBlockResults{Height: 1}, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + }, + baseFee.BigInt(), + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + baseFee, err := suite.backend.BaseFee(tc.blockRes) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expBaseFee, baseFee) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestChainId() { + expChainId := (*hexutil.Big)(big.NewInt(7001)) + testCases := []struct { + name string + registerMock func() + expChainId *hexutil.Big + expPass bool + }{ + { + "pass - block is at or past the EIP-155 replay-protection fork block, return chainID from config ", + func() { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsInvalidHeight(queryClient, &header, int64(1)) + }, + expChainId, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + chainId, err := suite.backend.ChainID() + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expChainId, chainId) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetCoinbase() { + validatorAcc := sdk.AccAddress(tests.GenerateAddress().Bytes()) + testCases := []struct { + name string + registerMock func() + accAddr sdk.AccAddress + expPass bool + }{ + { + "fail - Can't retrieve status from node", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterStatusError(client) + }, + validatorAcc, + false, + }, + { + "fail - Can't query validator account", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterStatus(client) + RegisterValidatorAccountError(queryClient) + }, + validatorAcc, + false, + }, + { + "pass - Gets coinbase account", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterStatus(client) + RegisterValidatorAccount(queryClient, validatorAcc) + }, + validatorAcc, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + accAddr, err := suite.backend.GetCoinbase() + + if tc.expPass { + suite.Require().Equal(tc.accAddr, accAddr) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestSuggestGasTipCap() { + testCases := []struct { + name string + registerMock func() + baseFee *big.Int + expGasTipCap *big.Int + expPass bool + }{ + { + "pass - London hardfork not enabled or feemarket not enabled ", + func() {}, + nil, + big.NewInt(0), + true, + }, + { + "pass - Gets the suggest gas tip cap ", + func() {}, + nil, + big.NewInt(0), + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + maxDelta, err := suite.backend.SuggestGasTipCap(tc.baseFee) + + if tc.expPass { + suite.Require().Equal(tc.expGasTipCap, maxDelta) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGlobalMinGasPrice() { + testCases := []struct { + name string + registerMock func() + expMinGasPrice sdk.Dec + expPass bool + }{ + { + "fail - Can't get FeeMarket params", + func() { + feeMarketCleint := suite.backend.queryClient.FeeMarket.(*mocks.FeeMarketQueryClient) + RegisterFeeMarketParamsError(feeMarketCleint, int64(1)) + }, + sdk.ZeroDec(), + false, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + globalMinGasPrice, err := suite.backend.GlobalMinGasPrice() + + if tc.expPass { + suite.Require().Equal(tc.expMinGasPrice, globalMinGasPrice) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestFeeHistory() { + testCases := []struct { + name string + registerMock func(validator sdk.AccAddress) + userBlockCount ethrpc.DecimalOrHex + latestBlock ethrpc.BlockNumber + expFeeHistory *rpc.FeeHistoryResult + validator sdk.AccAddress + expPass bool + }{ + { + "fail - can't get params ", + func(validator sdk.AccAddress) { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + suite.backend.cfg.JSONRPC.FeeHistoryCap = 0 + RegisterParamsError(queryClient, &header, ethrpc.BlockNumber(1).Int64()) + }, + 1, + -1, + nil, + nil, + false, + }, + { + "fail - user block count higher than max block count ", + func(validator sdk.AccAddress) { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + suite.backend.cfg.JSONRPC.FeeHistoryCap = 0 + RegisterParams(queryClient, &header, ethrpc.BlockNumber(1).Int64()) + }, + 1, + -1, + nil, + nil, + false, + }, + { + "fail - Tendermint block fetching error ", + func(validator sdk.AccAddress) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + suite.backend.cfg.JSONRPC.FeeHistoryCap = 2 + RegisterBlockError(client, ethrpc.BlockNumber(1).Int64()) + }, + 1, + 1, + nil, + nil, + false, + }, + { + "fail - Eth block fetching error", + func(validator sdk.AccAddress) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + suite.backend.cfg.JSONRPC.FeeHistoryCap = 2 + RegisterBlock(client, ethrpc.BlockNumber(1).Int64(), nil) + RegisterBlockResultsError(client, 1) + }, + 1, + 1, + nil, + nil, + true, + }, + { + "fail - Invalid base fee", + func(validator sdk.AccAddress) { + // baseFee := sdk.NewInt(1) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + suite.backend.cfg.JSONRPC.FeeHistoryCap = 2 + RegisterBlock(client, ethrpc.BlockNumber(1).Int64(), nil) + RegisterBlockResults(client, 1) + RegisterBaseFeeError(queryClient) + RegisterValidatorAccount(queryClient, validator) + RegisterConsensusParams(client, 1) + }, + 1, + 1, + nil, + sdk.AccAddress(tests.GenerateAddress().Bytes()), + false, + }, + { + "pass - Valid FeeHistoryResults object", + func(validator sdk.AccAddress) { + var header metadata.MD + baseFee := sdk.NewInt(1) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + suite.backend.cfg.JSONRPC.FeeHistoryCap = 2 + RegisterBlock(client, ethrpc.BlockNumber(1).Int64(), nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + RegisterConsensusParams(client, 1) + RegisterParams(queryClient, &header, 1) + RegisterParamsWithoutHeader(queryClient, 1) + }, + 1, + 1, + &rpc.FeeHistoryResult{ + OldestBlock: (*hexutil.Big)(big.NewInt(1)), + BaseFee: []*hexutil.Big{(*hexutil.Big)(big.NewInt(1)), (*hexutil.Big)(big.NewInt(1))}, + GasUsedRatio: []float64{0}, + Reward: [][]*hexutil.Big{ + { + (*hexutil.Big)(big.NewInt(0)), + (*hexutil.Big)(big.NewInt(0)), + (*hexutil.Big)(big.NewInt(0)), + (*hexutil.Big)(big.NewInt(0)), + }, + }, + }, + sdk.AccAddress(tests.GenerateAddress().Bytes()), + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(tc.validator) + + feeHistory, err := suite.backend.FeeHistory(tc.userBlockCount, tc.latestBlock, []float64{25, 50, 75, 100}) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(feeHistory, tc.expFeeHistory) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/rpc/backend/client_test.go b/rpc/backend/client_test.go new file mode 100644 index 0000000000..74d6ae0bfd --- /dev/null +++ b/rpc/backend/client_test.go @@ -0,0 +1,319 @@ +package backend + +import ( + "context" + "testing" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/libs/bytes" + tmrpcclient "github.com/cometbft/cometbft/rpc/client" + tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/client" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + evmtypes "github.com/evmos/ethermint/x/evm/types" + mock "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + rpc "github.com/zeta-chain/zetacore/rpc/types" +) + +// Client defines a mocked object that implements the Tendermint JSON-RPC Client +// interface. It allows for performing Client queries without having to run a +// Tendermint RPC Client server. +// +// To use a mock method it has to be registered in a given test. +var _ tmrpcclient.Client = &mocks.Client{} + +// Tx Search +func RegisterTxSearch(client *mocks.Client, query string, txBz []byte) { + resulTxs := []*tmrpctypes.ResultTx{{Tx: txBz}} + client.On("TxSearch", rpc.ContextWithHeight(1), query, false, (*int)(nil), (*int)(nil), ""). + Return(&tmrpctypes.ResultTxSearch{Txs: resulTxs, TotalCount: 1}, nil) +} + +func RegisterTxSearchEmpty(client *mocks.Client, query string) { + client.On("TxSearch", rpc.ContextWithHeight(1), query, false, (*int)(nil), (*int)(nil), ""). + Return(&tmrpctypes.ResultTxSearch{}, nil) +} + +func RegisterTxSearchError(client *mocks.Client, query string) { + client.On("TxSearch", rpc.ContextWithHeight(1), query, false, (*int)(nil), (*int)(nil), ""). + Return(nil, errortypes.ErrInvalidRequest) +} + +// Broadcast Tx +func RegisterBroadcastTx(client *mocks.Client, tx types.Tx) { + client.On("BroadcastTxSync", context.Background(), tx). + Return(&tmrpctypes.ResultBroadcastTx{}, nil) +} + +func RegisterBroadcastTxError(client *mocks.Client, tx types.Tx) { + client.On("BroadcastTxSync", context.Background(), tx). + Return(nil, errortypes.ErrInvalidRequest) +} + +// Unconfirmed Transactions +func RegisterUnconfirmedTxs(client *mocks.Client, limit *int, txs []types.Tx) { + client.On("UnconfirmedTxs", rpc.ContextWithHeight(1), limit). + Return(&tmrpctypes.ResultUnconfirmedTxs{Txs: txs}, nil) +} + +func RegisterUnconfirmedTxsEmpty(client *mocks.Client, limit *int) { + client.On("UnconfirmedTxs", rpc.ContextWithHeight(1), limit). + Return(&tmrpctypes.ResultUnconfirmedTxs{ + Txs: make([]types.Tx, 2), + }, nil) +} + +func RegisterUnconfirmedTxsError(client *mocks.Client, limit *int) { + client.On("UnconfirmedTxs", rpc.ContextWithHeight(1), limit). + Return(nil, errortypes.ErrInvalidRequest) +} + +// Status +func RegisterStatus(client *mocks.Client) { + client.On("Status", rpc.ContextWithHeight(1)). + Return(&tmrpctypes.ResultStatus{}, nil) +} + +func RegisterStatusError(client *mocks.Client) { + client.On("Status", rpc.ContextWithHeight(1)). + Return(nil, errortypes.ErrInvalidRequest) +} + +// Block +func RegisterBlockMultipleTxs( + client *mocks.Client, + height int64, + txs []types.Tx, +) (*tmrpctypes.ResultBlock, error) { + block := types.MakeBlock(height, txs, nil, nil) + block.ChainID = ChainID + resBlock := &tmrpctypes.ResultBlock{Block: block} + client.On("Block", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).Return(resBlock, nil) + return resBlock, nil +} + +func RegisterBlock( + client *mocks.Client, + height int64, + tx []byte, +) (*tmrpctypes.ResultBlock, error) { + // without tx + if tx == nil { + emptyBlock := types.MakeBlock(height, []types.Tx{}, nil, nil) + emptyBlock.ChainID = ChainID + resBlock := &tmrpctypes.ResultBlock{Block: emptyBlock} + client.On("Block", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).Return(resBlock, nil) + return resBlock, nil + } + + // with tx + block := types.MakeBlock(height, []types.Tx{tx}, nil, nil) + block.ChainID = ChainID + resBlock := &tmrpctypes.ResultBlock{Block: block} + client.On("Block", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).Return(resBlock, nil) + return resBlock, nil +} + +// Block returns error +func RegisterBlockError(client *mocks.Client, height int64) { + client.On("Block", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")). + Return(nil, errortypes.ErrInvalidRequest) +} + +// Block not found +func RegisterBlockNotFound( + client *mocks.Client, + height int64, +) (*tmrpctypes.ResultBlock, error) { + client.On("Block", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")). + Return(&tmrpctypes.ResultBlock{Block: nil}, nil) + + return &tmrpctypes.ResultBlock{Block: nil}, nil +} + +func TestRegisterBlock(t *testing.T) { + client := mocks.NewClient(t) + height := rpc.BlockNumber(1).Int64() + RegisterBlock(client, height, nil) + + res, err := client.Block(rpc.ContextWithHeight(height), &height) + + emptyBlock := types.MakeBlock(height, []types.Tx{}, nil, nil) + emptyBlock.ChainID = ChainID + resBlock := &tmrpctypes.ResultBlock{Block: emptyBlock} + require.Equal(t, resBlock, res) + require.NoError(t, err) +} + +// ConsensusParams +func RegisterConsensusParams(client *mocks.Client, height int64) { + consensusParams := types.DefaultConsensusParams() + client.On("ConsensusParams", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")). + Return(&tmrpctypes.ResultConsensusParams{ConsensusParams: *consensusParams}, nil) +} + +func RegisterConsensusParamsError(client *mocks.Client, height int64) { + client.On("ConsensusParams", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")). + Return(nil, errortypes.ErrInvalidRequest) +} + +func TestRegisterConsensusParams(t *testing.T) { + client := mocks.NewClient(t) + height := int64(1) + RegisterConsensusParams(client, height) + + res, err := client.ConsensusParams(rpc.ContextWithHeight(height), &height) + consensusParams := types.DefaultConsensusParams() + require.Equal(t, &tmrpctypes.ResultConsensusParams{ConsensusParams: *consensusParams}, res) + require.NoError(t, err) +} + +// BlockResults + +func RegisterBlockResultsWithEventLog(client *mocks.Client, height int64) (*tmrpctypes.ResultBlockResults, error) { + res := &tmrpctypes.ResultBlockResults{ + Height: height, + TxsResults: []*abci.ResponseDeliverTx{ + {Code: 0, GasUsed: 0, Events: []abci.Event{{ + Type: evmtypes.EventTypeTxLog, + Attributes: []abci.EventAttribute{{ + Key: evmtypes.AttributeKeyTxLog, + Value: "{\"test\": \"hello\"}", + Index: true, + }}, + }}}, + }, + } + client.On("BlockResults", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")). + Return(res, nil) + return res, nil +} + +func RegisterBlockResults( + client *mocks.Client, + height int64, +) (*tmrpctypes.ResultBlockResults, error) { + res := &tmrpctypes.ResultBlockResults{ + Height: height, + TxsResults: []*abci.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + } + + client.On("BlockResults", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")). + Return(res, nil) + return res, nil +} + +func RegisterBlockResultsWithTxResults( + client *mocks.Client, + height int64, + txResults []*abci.ResponseDeliverTx, +) (*tmrpctypes.ResultBlockResults, error) { + res := &tmrpctypes.ResultBlockResults{ + Height: height, + TxsResults: txResults, + } + + client.On("BlockResults", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")). + Return(res, nil) + return res, nil +} + +func RegisterBlockResultsError(client *mocks.Client, height int64) { + client.On("BlockResults", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")). + Return(nil, errortypes.ErrInvalidRequest) +} + +func TestRegisterBlockResults(t *testing.T) { + client := mocks.NewClient(t) + height := int64(1) + RegisterBlockResults(client, height) + + res, err := client.BlockResults(rpc.ContextWithHeight(height), &height) + expRes := &tmrpctypes.ResultBlockResults{ + Height: height, + TxsResults: []*abci.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + } + require.Equal(t, expRes, res) + require.NoError(t, err) +} + +// BlockByHash +func RegisterBlockByHash( + client *mocks.Client, + hash common.Hash, + tx []byte, +) (*tmrpctypes.ResultBlock, error) { + block := types.MakeBlock(1, []types.Tx{tx}, nil, nil) + resBlock := &tmrpctypes.ResultBlock{Block: block} + + client.On("BlockByHash", rpc.ContextWithHeight(1), []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}). + Return(resBlock, nil) + return resBlock, nil +} + +func RegisterBlockByHashError(client *mocks.Client, hash common.Hash, tx []byte) { + client.On("BlockByHash", rpc.ContextWithHeight(1), []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}). + Return(nil, errortypes.ErrInvalidRequest) +} + +func RegisterBlockByHashNotFound(client *mocks.Client, hash common.Hash, tx []byte) { + client.On("BlockByHash", rpc.ContextWithHeight(1), []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}). + Return(nil, nil) +} + +func RegisterABCIQueryWithOptions( + client *mocks.Client, + height int64, + path string, + data bytes.HexBytes, + opts tmrpcclient.ABCIQueryOptions, +) { + client.On("ABCIQueryWithOptions", context.Background(), path, data, opts). + Return(&tmrpctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + Value: []byte{2}, // TODO (https://github.com/zeta-chain/node/issues/2302) replace with data.Bytes(), + Height: height, + }, + }, nil) +} + +func RegisterABCIQueryWithOptionsError( + clients *mocks.Client, + path string, + data bytes.HexBytes, + opts tmrpcclient.ABCIQueryOptions, +) { + clients.On("ABCIQueryWithOptions", context.Background(), path, data, opts). + Return(nil, errortypes.ErrInvalidRequest) +} + +func RegisterABCIQueryAccount( + clients *mocks.Client, + data bytes.HexBytes, + opts tmrpcclient.ABCIQueryOptions, + acc client.Account, +) { + baseAccount := authtypes.NewBaseAccount( + acc.GetAddress(), + acc.GetPubKey(), + acc.GetAccountNumber(), + acc.GetSequence(), + ) + accAny, _ := codectypes.NewAnyWithValue(baseAccount) + accResponse := authtypes.QueryAccountResponse{Account: accAny} + respBz, _ := accResponse.Marshal() + clients.On("ABCIQueryWithOptions", context.Background(), "/cosmos.auth.v1beta1.Query/Account", data, opts). + Return(&tmrpctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + Value: respBz, + Height: 1, + }, + }, nil) +} diff --git a/rpc/backend/evm_query_client_test.go b/rpc/backend/evm_query_client_test.go new file mode 100644 index 0000000000..24b32b1328 --- /dev/null +++ b/rpc/backend/evm_query_client_test.go @@ -0,0 +1,291 @@ +package backend + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" + "github.com/ethereum/go-ethereum/common" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" + mock "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + rpc "github.com/zeta-chain/zetacore/rpc/types" +) + +// QueryClient defines a mocked object that implements the ethermint GRPC +// QueryClient interface. It allows for performing QueryClient queries without having +// to run a ethermint GRPC server. +// +// To use a mock method it has to be registered in a given test. +var _ evmtypes.QueryClient = &mocks.EVMQueryClient{} + +// TraceTransaction +func RegisterTraceTransactionWithPredecessors( + queryClient *mocks.EVMQueryClient, + msgEthTx *evmtypes.MsgEthereumTx, + predecessors []*evmtypes.MsgEthereumTx, +) { + data := []byte{0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x7d} + queryClient.On("TraceTx", rpc.ContextWithHeight(1), + &evmtypes.QueryTraceTxRequest{Msg: msgEthTx, BlockNumber: 1, Predecessors: predecessors, ChainId: 7001}). + Return(&evmtypes.QueryTraceTxResponse{Data: data}, nil) +} + +func RegisterTraceTransaction(queryClient *mocks.EVMQueryClient, msgEthTx *evmtypes.MsgEthereumTx) { + data := []byte{0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x7d} + queryClient.On("TraceTx", rpc.ContextWithHeight(1), &evmtypes.QueryTraceTxRequest{Msg: msgEthTx, BlockNumber: 1, Predecessors: []*evmtypes.MsgEthereumTx{}, ChainId: 7001}). + Return(&evmtypes.QueryTraceTxResponse{Data: data}, nil) +} + +func RegisterTraceTransactionError(queryClient *mocks.EVMQueryClient, msgEthTx *evmtypes.MsgEthereumTx) { + queryClient.On("TraceTx", rpc.ContextWithHeight(1), &evmtypes.QueryTraceTxRequest{Msg: msgEthTx, BlockNumber: 1, ChainId: 7001}). + Return(nil, errortypes.ErrInvalidRequest) +} + +// TraceBlock +func RegisterTraceBlock(queryClient *mocks.EVMQueryClient, txs []*evmtypes.MsgEthereumTx) { + data := []byte{0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x7d} + queryClient.On( + "TraceBlock", + rpc.ContextWithHeight(1), + &evmtypes.QueryTraceBlockRequest{ + Txs: txs, + BlockNumber: 1, + TraceConfig: &evmtypes.TraceConfig{}, + ChainId: 7001, + }, + ). + Return(&evmtypes.QueryTraceBlockResponse{Data: data}, nil) +} + +func RegisterTraceBlockError(queryClient *mocks.EVMQueryClient) { + queryClient.On("TraceBlock", rpc.ContextWithHeight(1), &evmtypes.QueryTraceBlockRequest{}). + Return(nil, errortypes.ErrInvalidRequest) +} + +// Params +func RegisterParams(queryClient *mocks.EVMQueryClient, header *metadata.MD, height int64) { + queryClient.On("Params", rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}, grpc.Header(header)). + Return(&evmtypes.QueryParamsResponse{}, nil). + Run(func(args mock.Arguments) { + // If Params call is successful, also update the header height + arg := args.Get(2).(grpc.HeaderCallOption) + h := metadata.MD{} + h.Set(grpctypes.GRPCBlockHeightHeader, fmt.Sprint(height)) + *arg.HeaderAddr = h + }) +} + +func RegisterParamsWithoutHeader(queryClient *mocks.EVMQueryClient, height int64) { + params := evmtypes.DefaultParams() + params.EvmDenom = "azeta" + queryClient.On("Params", rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}). + Return(&evmtypes.QueryParamsResponse{Params: params}, nil) +} + +func RegisterParamsInvalidHeader(queryClient *mocks.EVMQueryClient, header *metadata.MD, height int64) { + queryClient.On("Params", rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}, grpc.Header(header)). + Return(&evmtypes.QueryParamsResponse{}, nil). + Run(func(args mock.Arguments) { + // If Params call is successful, also update the header height + arg := args.Get(2).(grpc.HeaderCallOption) + h := metadata.MD{} + *arg.HeaderAddr = h + }) +} + +func RegisterParamsInvalidHeight(queryClient *mocks.EVMQueryClient, header *metadata.MD, height int64) { + queryClient.On("Params", rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}, grpc.Header(header)). + Return(&evmtypes.QueryParamsResponse{}, nil). + Run(func(args mock.Arguments) { + // If Params call is successful, also update the header height + arg := args.Get(2).(grpc.HeaderCallOption) + h := metadata.MD{} + h.Set(grpctypes.GRPCBlockHeightHeader, "invalid") + *arg.HeaderAddr = h + }) +} + +func RegisterParamsWithoutHeaderError(queryClient *mocks.EVMQueryClient, height int64) { + queryClient.On("Params", rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}). + Return(nil, errortypes.ErrInvalidRequest) +} + +// Params returns error +func RegisterParamsError(queryClient *mocks.EVMQueryClient, header *metadata.MD, height int64) { + queryClient.On("Params", rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}, grpc.Header(header)). + Return(nil, errortypes.ErrInvalidRequest) +} + +func TestRegisterParams(t *testing.T) { + var header metadata.MD + queryClient := mocks.NewEVMQueryClient(t) + + height := int64(1) + RegisterParams(queryClient, &header, height) + + _, err := queryClient.Params(rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}, grpc.Header(&header)) + require.NoError(t, err) + blockHeightHeader := header.Get(grpctypes.GRPCBlockHeightHeader) + headerHeight, err := strconv.ParseInt(blockHeightHeader[0], 10, 64) + require.NoError(t, err) + require.Equal(t, height, headerHeight) +} + +func TestRegisterParamsError(t *testing.T) { + queryClient := mocks.NewEVMQueryClient(t) + RegisterBaseFeeError(queryClient) + _, err := queryClient.BaseFee(rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}) + require.Error(t, err) +} + +// ETH Call +func RegisterEthCall(queryClient *mocks.EVMQueryClient, request *evmtypes.EthCallRequest) { + ctx, _ := context.WithCancel(rpc.ContextWithHeight(1)) + queryClient.On("EthCall", ctx, request). + Return(&evmtypes.MsgEthereumTxResponse{}, nil) +} + +func RegisterEthCallError(queryClient *mocks.EVMQueryClient, request *evmtypes.EthCallRequest) { + ctx, _ := context.WithCancel(rpc.ContextWithHeight(1)) + queryClient.On("EthCall", ctx, request). + Return(nil, errortypes.ErrInvalidRequest) +} + +// Estimate Gas +func RegisterEstimateGas(queryClient *mocks.EVMQueryClient, args evmtypes.TransactionArgs) { + bz, _ := json.Marshal(args) + queryClient.On("EstimateGas", rpc.ContextWithHeight(1), &evmtypes.EthCallRequest{Args: bz, ChainId: args.ChainID.ToInt().Int64()}). + Return(&evmtypes.EstimateGasResponse{}, nil) +} + +// BaseFee +func RegisterBaseFee(queryClient *mocks.EVMQueryClient, baseFee sdkmath.Int) { + queryClient.On("BaseFee", rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}). + Return(&evmtypes.QueryBaseFeeResponse{BaseFee: &baseFee}, nil) +} + +// Base fee returns error +func RegisterBaseFeeError(queryClient *mocks.EVMQueryClient) { + queryClient.On("BaseFee", rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}). + Return(&evmtypes.QueryBaseFeeResponse{}, evmtypes.ErrInvalidBaseFee) +} + +// Base fee not enabled +func RegisterBaseFeeDisabled(queryClient *mocks.EVMQueryClient) { + queryClient.On("BaseFee", rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}). + Return(&evmtypes.QueryBaseFeeResponse{}, nil) +} + +func TestRegisterBaseFee(t *testing.T) { + baseFee := sdk.NewInt(1) + queryClient := mocks.NewEVMQueryClient(t) + RegisterBaseFee(queryClient, baseFee) + res, err := queryClient.BaseFee(rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}) + require.Equal(t, &evmtypes.QueryBaseFeeResponse{BaseFee: &baseFee}, res) + require.NoError(t, err) +} + +func TestRegisterBaseFeeError(t *testing.T) { + queryClient := mocks.NewEVMQueryClient(t) + RegisterBaseFeeError(queryClient) + res, err := queryClient.BaseFee(rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}) + require.Equal(t, &evmtypes.QueryBaseFeeResponse{}, res) + require.Error(t, err) +} + +func TestRegisterBaseFeeDisabled(t *testing.T) { + queryClient := mocks.NewEVMQueryClient(t) + RegisterBaseFeeDisabled(queryClient) + res, err := queryClient.BaseFee(rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}) + require.Equal(t, &evmtypes.QueryBaseFeeResponse{}, res) + require.NoError(t, err) +} + +// ValidatorAccount +func RegisterValidatorAccount(queryClient *mocks.EVMQueryClient, validator sdk.AccAddress) { + queryClient.On("ValidatorAccount", rpc.ContextWithHeight(1), &evmtypes.QueryValidatorAccountRequest{}). + Return(&evmtypes.QueryValidatorAccountResponse{AccountAddress: validator.String()}, nil) +} + +func RegisterValidatorAccountError(queryClient *mocks.EVMQueryClient) { + queryClient.On("ValidatorAccount", rpc.ContextWithHeight(1), &evmtypes.QueryValidatorAccountRequest{}). + Return(nil, status.Error(codes.InvalidArgument, "empty request")) +} + +func TestRegisterValidatorAccount(t *testing.T) { + queryClient := mocks.NewEVMQueryClient(t) + + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) + RegisterValidatorAccount(queryClient, validator) + res, err := queryClient.ValidatorAccount(rpc.ContextWithHeight(1), &evmtypes.QueryValidatorAccountRequest{}) + require.Equal(t, &evmtypes.QueryValidatorAccountResponse{AccountAddress: validator.String()}, res) + require.NoError(t, err) +} + +// Code +func RegisterCode(queryClient *mocks.EVMQueryClient, addr common.Address, code []byte) { + queryClient.On("Code", rpc.ContextWithHeight(1), &evmtypes.QueryCodeRequest{Address: addr.String()}). + Return(&evmtypes.QueryCodeResponse{Code: code}, nil) +} + +func RegisterCodeError(queryClient *mocks.EVMQueryClient, addr common.Address) { + queryClient.On("Code", rpc.ContextWithHeight(1), &evmtypes.QueryCodeRequest{Address: addr.String()}). + Return(nil, errortypes.ErrInvalidRequest) +} + +// Storage +func RegisterStorageAt(queryClient *mocks.EVMQueryClient, addr common.Address, key string, storage string) { + queryClient.On("Storage", rpc.ContextWithHeight(1), &evmtypes.QueryStorageRequest{Address: addr.String(), Key: key}). + Return(&evmtypes.QueryStorageResponse{Value: storage}, nil) +} + +func RegisterStorageAtError(queryClient *mocks.EVMQueryClient, addr common.Address, key string) { + queryClient.On("Storage", rpc.ContextWithHeight(1), &evmtypes.QueryStorageRequest{Address: addr.String(), Key: key}). + Return(nil, errortypes.ErrInvalidRequest) +} + +func RegisterAccount(queryClient *mocks.EVMQueryClient, addr common.Address, height int64) { + queryClient.On("Account", rpc.ContextWithHeight(height), &evmtypes.QueryAccountRequest{Address: addr.String()}). + Return(&evmtypes.QueryAccountResponse{ + Balance: "0", + CodeHash: "", + Nonce: 0, + }, + nil, + ) +} + +// Balance +func RegisterBalance(queryClient *mocks.EVMQueryClient, addr common.Address, height int64) { + queryClient.On("Balance", rpc.ContextWithHeight(height), &evmtypes.QueryBalanceRequest{Address: addr.String()}). + Return(&evmtypes.QueryBalanceResponse{Balance: "1"}, nil) +} + +func RegisterBalanceInvalid(queryClient *mocks.EVMQueryClient, addr common.Address, height int64) { + queryClient.On("Balance", rpc.ContextWithHeight(height), &evmtypes.QueryBalanceRequest{Address: addr.String()}). + Return(&evmtypes.QueryBalanceResponse{Balance: "invalid"}, nil) +} + +func RegisterBalanceNegative(queryClient *mocks.EVMQueryClient, addr common.Address, height int64) { + queryClient.On("Balance", rpc.ContextWithHeight(height), &evmtypes.QueryBalanceRequest{Address: addr.String()}). + Return(&evmtypes.QueryBalanceResponse{Balance: "-1"}, nil) +} + +func RegisterBalanceError(queryClient *mocks.EVMQueryClient, addr common.Address, height int64) { + queryClient.On("Balance", rpc.ContextWithHeight(height), &evmtypes.QueryBalanceRequest{Address: addr.String()}). + Return(nil, errortypes.ErrInvalidRequest) +} diff --git a/rpc/backend/feemarket_query_client_test.go b/rpc/backend/feemarket_query_client_test.go new file mode 100644 index 0000000000..d24507eae4 --- /dev/null +++ b/rpc/backend/feemarket_query_client_test.go @@ -0,0 +1,22 @@ +package backend + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + rpc "github.com/zeta-chain/zetacore/rpc/types" +) + +var _ feemarkettypes.QueryClient = &mocks.FeeMarketQueryClient{} + +// Params +func RegisterFeeMarketParams(feeMarketClient *mocks.FeeMarketQueryClient, height int64) { + feeMarketClient.On("Params", rpc.ContextWithHeight(height), &feemarkettypes.QueryParamsRequest{}). + Return(&feemarkettypes.QueryParamsResponse{Params: feemarkettypes.DefaultParams()}, nil) +} + +func RegisterFeeMarketParamsError(feeMarketClient *mocks.FeeMarketQueryClient, height int64) { + feeMarketClient.On("Params", rpc.ContextWithHeight(height), &feemarkettypes.QueryParamsRequest{}). + Return(nil, sdkerrors.ErrInvalidRequest) +} diff --git a/rpc/backend/filters_test.go b/rpc/backend/filters_test.go new file mode 100644 index 0000000000..486e51ebf2 --- /dev/null +++ b/rpc/backend/filters_test.go @@ -0,0 +1,121 @@ +package backend + +import ( + "encoding/json" + + tmtypes "github.com/cometbft/cometbft/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + evmtypes "github.com/evmos/ethermint/x/evm/types" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + ethrpc "github.com/zeta-chain/zetacore/rpc/types" +) + +func (suite *BackendTestSuite) TestGetLogs() { + _, bz := suite.buildEthereumTx() + block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) + logs := make([]*evmtypes.Log, 0, 1) + var log evmtypes.Log + json.Unmarshal( + []byte{0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x7d}, + &log, + ) + logs = append(logs, &log) + + testCases := []struct { + name string + registerMock func(hash common.Hash) + blockHash common.Hash + expLogs [][]*ethtypes.Log + expPass bool + }{ + { + "fail - no block with that hash", + func(hash common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashNotFound(client, hash, bz) + }, + common.Hash{}, + nil, + false, + }, + { + "fail - error fetching block by hash", + func(hash common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashError(client, hash, bz) + }, + common.Hash{}, + nil, + false, + }, + { + "fail - error getting block results", + func(hash common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHash(client, hash, bz) + RegisterBlockResultsError(client, 1) + }, + common.Hash{}, + nil, + false, + }, + { + "success - getting logs with block hash", + func(hash common.Hash) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHash(client, hash, bz) + RegisterBlockResultsWithEventLog(client, ethrpc.BlockNumber(1).Int64()) + }, + common.BytesToHash(block.Hash()), + [][]*ethtypes.Log{evmtypes.LogsToEthereum(logs)}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.registerMock(tc.blockHash) + logs, err := suite.backend.GetLogs(tc.blockHash) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expLogs, logs) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestBloomStatus() { + testCases := []struct { + name string + registerMock func() + expResult uint64 + expPass bool + }{ + { + "pass - returns the BloomBitsBlocks and the number of processed sections maintained", + func() {}, + 4096, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.registerMock() + bloom, _ := suite.backend.BloomStatus() + + if tc.expPass { + suite.Require().Equal(tc.expResult, bloom) + } + }) + } +} diff --git a/rpc/backend/mocks/client.go b/rpc/backend/mocks/client.go index dc4e2428cd..16090fcc69 100644 --- a/rpc/backend/mocks/client.go +++ b/rpc/backend/mocks/client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package mocks @@ -459,6 +459,52 @@ func (_m *Client) GenesisChunked(_a0 context.Context, _a1 uint) (*coretypes.Resu return r0, r1 } +// Header provides a mock function with given fields: ctx, height +func (_m *Client) Header(ctx context.Context, height *int64) (*coretypes.ResultHeader, error) { + ret := _m.Called(ctx, height) + + var r0 *coretypes.ResultHeader + if rf, ok := ret.Get(0).(func(context.Context, *int64) *coretypes.ResultHeader); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultHeader) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HeaderByHash provides a mock function with given fields: ctx, hash +func (_m *Client) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) { + ret := _m.Called(ctx, hash) + + var r0 *coretypes.ResultHeader + if rf, ok := ret.Get(0).(func(context.Context, bytes.HexBytes) *coretypes.ResultHeader); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultHeader) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, bytes.HexBytes) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Health provides a mock function with given fields: _a0 func (_m *Client) Health(_a0 context.Context) (*coretypes.ResultHealth, error) { ret := _m.Called(_a0) @@ -838,4 +884,4 @@ func NewClient(t mockConstructorTestingTNewClient) *Client { t.Cleanup(func() { mock.AssertExpectations(t) }) return mock -} +} \ No newline at end of file diff --git a/rpc/backend/node_info_test.go b/rpc/backend/node_info_test.go new file mode 100644 index 0000000000..a98c433f11 --- /dev/null +++ b/rpc/backend/node_info_test.go @@ -0,0 +1,359 @@ +package backend + +import ( + "fmt" + "math/big" + + tmrpcclient "github.com/cometbft/cometbft/rpc/client" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/evmos/ethermint/crypto/ethsecp256k1" + ethermint "github.com/evmos/ethermint/types" + "github.com/spf13/viper" + "google.golang.org/grpc/metadata" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" +) + +func (suite *BackendTestSuite) TestRPCMinGasPrice() { + testCases := []struct { + name string + registerMock func() + expMinGasPrice int64 + expPass bool + }{ + { + "pass - default gas price", + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsWithoutHeaderError(queryClient, 1) + }, + ethermint.DefaultGasPrice, + true, + }, + { + "pass - min gas price is 0", + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsWithoutHeader(queryClient, 1) + }, + ethermint.DefaultGasPrice, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + minPrice := suite.backend.RPCMinGasPrice() + if tc.expPass { + suite.Require().Equal(tc.expMinGasPrice, minPrice) + } else { + suite.Require().NotEqual(tc.expMinGasPrice, minPrice) + } + }) + } +} + +func (suite *BackendTestSuite) TestSetGasPrice() { + defaultGasPrice := (*hexutil.Big)(big.NewInt(1)) + testCases := []struct { + name string + registerMock func() + gasPrice hexutil.Big + expOutput bool + }{ + { + "pass - cannot get server config", + func() { + suite.backend.clientCtx.Viper = viper.New() + }, + *defaultGasPrice, + false, + }, + { + "pass - cannot find coin denom", + func() { + suite.backend.clientCtx.Viper = viper.New() + suite.backend.clientCtx.Viper.Set("telemetry.global-labels", []interface{}{}) + }, + *defaultGasPrice, + false, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + output := suite.backend.SetGasPrice(tc.gasPrice) + suite.Require().Equal(tc.expOutput, output) + }) + } +} + +// TODO (https://github.com/zeta-chain/node/issues/2302): Combine these 2 into one test since the code is identical +func (suite *BackendTestSuite) TestListAccounts() { + testCases := []struct { + name string + registerMock func() + expAddr []common.Address + expPass bool + }{ + { + "pass - returns empty address", + func() {}, + []common.Address{}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + output, err := suite.backend.ListAccounts() + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expAddr, output) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestAccounts() { + testCases := []struct { + name string + registerMock func() + expAddr []common.Address + expPass bool + }{ + { + "pass - returns empty address", + func() {}, + []common.Address{}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + output, err := suite.backend.Accounts() + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expAddr, output) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestSyncing() { + testCases := []struct { + name string + registerMock func() + expResponse interface{} + expPass bool + }{ + { + "fail - Can't get status", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterStatusError(client) + }, + false, + false, + }, + { + "pass - Node not catching up", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterStatus(client) + }, + false, + true, + }, + { + "pass - Node is catching up", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterStatus(client) + status, _ := client.Status(suite.backend.ctx) + status.SyncInfo.CatchingUp = true + }, + map[string]interface{}{ + "startingBlock": hexutil.Uint64(0), + "currentBlock": hexutil.Uint64(0), + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + output, err := suite.backend.Syncing() + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expResponse, output) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestSetEtherbase() { + testCases := []struct { + name string + registerMock func() + etherbase common.Address + expResult bool + }{ + { + "pass - Failed to get coinbase address", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterStatusError(client) + }, + common.Address{}, + false, + }, + { + "pass - the minimum fee is not set", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterStatus(client) + RegisterValidatorAccount(queryClient, suite.acc) + }, + common.Address{}, + false, + }, + { + "fail - error querying for account ", + func() { + var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterStatus(client) + RegisterValidatorAccount(queryClient, suite.acc) + RegisterParams(queryClient, &header, 1) + c := sdk.NewDecCoin("azeta", sdk.NewIntFromBigInt(big.NewInt(1))) + suite.backend.cfg.SetMinGasPrices(sdk.DecCoins{c}) + delAddr, _ := suite.backend.GetCoinbase() + // account, _ := suite.backend.clientCtx.AccountRetriever.GetAccount(suite.backend.clientCtx, delAddr) + delCommonAddr := common.BytesToAddress(delAddr.Bytes()) + request := &authtypes.QueryAccountRequest{Address: sdk.AccAddress(delCommonAddr.Bytes()).String()} + requestMarshal, _ := request.Marshal() + RegisterABCIQueryWithOptionsError( + client, + "/cosmos.auth.v1beta1.Query/Account", + requestMarshal, + tmrpcclient.ABCIQueryOptions{Height: int64(1), Prove: false}, + ) + }, + common.Address{}, + false, + }, + // TODO (https://github.com/zeta-chain/node/issues/2302): Finish this test case once ABCIQuery GetAccount is fixed + //{ + // "pass - set the etherbase for the miner", + // func() { + // client := suite.backend.clientCtx.Client.(*mocks.Client) + // queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + // RegisterStatus(client) + // RegisterValidatorAccount(queryClient, suite.acc) + // c := sdk.NewDecCoin("azeta", sdk.NewIntFromBigInt(big.NewInt(1))) + // suite.backend.cfg.SetMinGasPrices(sdk.DecCoins{c}) + // delAddr, _ := suite.backend.GetCoinbase() + // account, _ := suite.backend.clientCtx.AccountRetriever.GetAccount(suite.backend.clientCtx, delAddr) + // delCommonAddr := common.BytesToAddress(delAddr.Bytes()) + // request := &authtypes.QueryAccountRequest{Address: sdk.AccAddress(delCommonAddr.Bytes()).String()} + // requestMarshal, _ := request.Marshal() + // RegisterABCIQueryAccount( + // client, + // requestMarshal, + // tmrpcclient.ABCIQueryOptions{Height: int64(1), Prove: false}, + // account, + // ) + // }, + // common.Address{}, + // false, + //}, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + output := suite.backend.SetEtherbase(tc.etherbase) + + suite.Require().Equal(tc.expResult, output) + }) + } +} + +func (suite *BackendTestSuite) TestImportRawKey() { + priv, _ := ethsecp256k1.GenerateKey() + privHex := common.Bytes2Hex(priv.Bytes()) + pubAddr := common.BytesToAddress(priv.PubKey().Address().Bytes()) + + testCases := []struct { + name string + registerMock func() + privKey string + password string + expAddr common.Address + expPass bool + }{ + { + "fail - not a valid private key", + func() {}, + "", + "", + common.Address{}, + false, + }, + { + "pass - returning correct address", + func() {}, + privHex, + "", + pubAddr, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + output, err := suite.backend.ImportRawKey(tc.privKey, tc.password) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expAddr, output) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/rpc/backend/sign_tx_test.go b/rpc/backend/sign_tx_test.go new file mode 100644 index 0000000000..153e326cd6 --- /dev/null +++ b/rpc/backend/sign_tx_test.go @@ -0,0 +1,274 @@ +package backend + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + goethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/evmos/ethermint/crypto/ethsecp256k1" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" + "google.golang.org/grpc/metadata" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" +) + +func (suite *BackendTestSuite) TestSendTransaction() { + gasPrice := new(hexutil.Big) + gas := hexutil.Uint64(1) + zeroGas := hexutil.Uint64(0) + toAddr := tests.GenerateAddress() + priv, err := ethsecp256k1.GenerateKey() + suite.Require().NoError(err) + from := common.BytesToAddress(priv.PubKey().Address().Bytes()) + nonce := hexutil.Uint64(1) + baseFee := sdk.NewInt(1) + callArgsDefault := evmtypes.TransactionArgs{ + From: &from, + To: &toAddr, + GasPrice: gasPrice, + Gas: &gas, + Nonce: &nonce, + } + + hash := common.Hash{} + + testCases := []struct { + name string + registerMock func() + args evmtypes.TransactionArgs + expHash common.Hash + expPass bool + }{ + { + "fail - Can't find account in Keyring", + func() {}, + evmtypes.TransactionArgs{}, + hash, + false, + }, + { + "fail - Block error can't set Tx defaults", + func() { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + RegisterParams(queryClient, &header, 1) + RegisterBlockError(client, 1) + }, + callArgsDefault, + hash, + false, + }, + { + "fail - Cannot validate transaction gas set to 0", + func() { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + RegisterParams(queryClient, &header, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, baseFee) + }, + evmtypes.TransactionArgs{ + From: &from, + To: &toAddr, + GasPrice: gasPrice, + Gas: &zeroGas, + Nonce: &nonce, + }, + hash, + false, + }, + { + "fail - Cannot broadcast transaction", + func() { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + RegisterParams(queryClient, &header, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, baseFee) + RegisterParamsWithoutHeader(queryClient, 1) + ethSigner := ethtypes.LatestSigner(suite.backend.ChainConfig()) + msg := callArgsDefault.ToTransaction() + msg.Sign(ethSigner, suite.backend.clientCtx.Keyring) + tx, err := msg.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), "azeta") + suite.Require().NoError(err) + txEncoder := suite.backend.clientCtx.TxConfig.TxEncoder() + txBytes, err := txEncoder(tx) + suite.Require().NoError(err) + RegisterBroadcastTxError(client, txBytes) + }, + callArgsDefault, + common.Hash{}, + false, + }, + { + "pass - Return the transaction hash", + func() { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + RegisterParams(queryClient, &header, 1) + RegisterBlock(client, 1, nil) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, baseFee) + RegisterParamsWithoutHeader(queryClient, 1) + ethSigner := ethtypes.LatestSigner(suite.backend.ChainConfig()) + msg := callArgsDefault.ToTransaction() + msg.Sign(ethSigner, suite.backend.clientCtx.Keyring) + tx, err := msg.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), "azeta") + suite.Require().NoError(err) + txEncoder := suite.backend.clientCtx.TxConfig.TxEncoder() + txBytes, err := txEncoder(tx) + suite.Require().NoError(err) + RegisterBroadcastTx(client, txBytes) + }, + callArgsDefault, + hash, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + if tc.expPass { + // Sign the transaction and get the hash + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsWithoutHeader(queryClient, 1) + ethSigner := ethtypes.LatestSigner(suite.backend.ChainConfig()) + msg := callArgsDefault.ToTransaction() + msg.Sign(ethSigner, suite.backend.clientCtx.Keyring) + tc.expHash = msg.AsTransaction().Hash() + } + responseHash, err := suite.backend.SendTransaction(tc.args) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expHash, responseHash) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestSign() { + from, priv := tests.NewAddrKey() + testCases := []struct { + name string + registerMock func() + fromAddr common.Address + inputBz hexutil.Bytes + expPass bool + }{ + { + "fail - can't find key in Keyring", + func() {}, + from, + nil, + false, + }, + { + "pass - sign nil data", + func() { + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + }, + from, + nil, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + responseBz, err := suite.backend.Sign(tc.fromAddr, tc.inputBz) + if tc.expPass { + signature, _, err := suite.backend.clientCtx.Keyring.SignByAddress( + (sdk.AccAddress)(from.Bytes()), + tc.inputBz, + ) + signature[goethcrypto.RecoveryIDOffset] += 27 + suite.Require().NoError(err) + suite.Require().Equal((hexutil.Bytes)(signature), responseBz) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestSignTypedData() { + from, priv := tests.NewAddrKey() + testCases := []struct { + name string + registerMock func() + fromAddr common.Address + inputTypedData apitypes.TypedData + expPass bool + }{ + { + "fail - can't find key in Keyring", + func() {}, + from, + apitypes.TypedData{}, + false, + }, + { + "fail - empty TypeData", + func() { + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + }, + from, + apitypes.TypedData{}, + false, + }, + // TODO (https://github.com/zeta-chain/node/issues/2302): Generate a TypedData msg + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + responseBz, err := suite.backend.SignTypedData(tc.fromAddr, tc.inputTypedData) + + if tc.expPass { + sigHash, _, err := apitypes.TypedDataAndHash(tc.inputTypedData) + suite.Require().NoError(err) + signature, _, err := suite.backend.clientCtx.Keyring.SignByAddress( + (sdk.AccAddress)(from.Bytes()), + sigHash, + ) + signature[goethcrypto.RecoveryIDOffset] += 27 + suite.Require().NoError(err) + suite.Require().Equal((hexutil.Bytes)(signature), responseBz) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/rpc/backend/tracing_test.go b/rpc/backend/tracing_test.go new file mode 100644 index 0000000000..feb6ca2c42 --- /dev/null +++ b/rpc/backend/tracing_test.go @@ -0,0 +1,316 @@ +package backend + +import ( + "fmt" + + dbm "github.com/cometbft/cometbft-db" + abci "github.com/cometbft/cometbft/abci/types" + tmlog "github.com/cometbft/cometbft/libs/log" + tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/crypto" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/evmos/ethermint/crypto/ethsecp256k1" + "github.com/evmos/ethermint/indexer" + evmtypes "github.com/evmos/ethermint/x/evm/types" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" +) + +func (suite *BackendTestSuite) TestTraceTransaction() { + msgEthereumTx, _ := suite.buildEthereumTx() + msgEthereumTx2, _ := suite.buildEthereumTx() + + txHash := msgEthereumTx.AsTransaction().Hash() + txHash2 := msgEthereumTx2.AsTransaction().Hash() + + priv, err := ethsecp256k1.GenerateKey() + suite.Require().NoError(err) + from := common.BytesToAddress(priv.PubKey().Address().Bytes()) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterParamsWithoutHeader(queryClient, 1) + + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + ethSigner := ethtypes.LatestSigner(suite.backend.ChainConfig()) + + txEncoder := suite.backend.clientCtx.TxConfig.TxEncoder() + + msgEthereumTx.From = from.String() + msgEthereumTx.Sign(ethSigner, suite.signer) + tx, err := msgEthereumTx.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), "azeta") + suite.Require().NoError(err) + txBz, err := txEncoder(tx) + suite.Require().NoError(err) + + msgEthereumTx2.From = from.String() + msgEthereumTx2.Sign(ethSigner, suite.signer) + tx2, err := msgEthereumTx2.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), "azeta") + suite.Require().NoError(err) + txBz2, err := txEncoder(tx2) + suite.Require().NoError(err) + + testCases := []struct { + name string + txHash common.Hash + registerMock func() + block *types.Block + responseBlock []*abci.ResponseDeliverTx + expResult interface{} + expPass bool + }{ + { + "fail - tx not found", + txHash, + func() {}, + &types.Block{Header: types.Header{Height: 1}, Data: types.Data{Txs: []types.Tx{}}}, + []*abci.ResponseDeliverTx{ + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash.Hex()}, + {Key: "txIndex", Value: "0"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, + }}, + }, + }, + }, + nil, + false, + }, + { + "fail - block not found", + txHash, + func() { + // var header metadata.MD + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, 1) + }, + &types.Block{Header: types.Header{Height: 1}, Data: types.Data{Txs: []types.Tx{txBz}}}, + []*abci.ResponseDeliverTx{ + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash.Hex()}, + {Key: "txIndex", Value: "0"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, + }}, + }, + }, + }, + map[string]interface{}{"test": "hello"}, + false, + }, + { + "pass - transaction found in a block with multiple transactions", + txHash2, // tx1 is predecessor of tx2 + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockMultipleTxs(client, 1, []types.Tx{txBz, txBz2}) + RegisterTraceTransactionWithPredecessors( + queryClient, + msgEthereumTx2, + []*evmtypes.MsgEthereumTx{msgEthereumTx}, + ) + txResults := []*abci.ResponseDeliverTx{ + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash.Hex()}, + {Key: "txIndex", Value: "0"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, + }}, + }, + }, + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash2.Hex()}, + {Key: "txIndex", Value: "1"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, + }}, + }, + }, + } + + RegisterBlockResultsWithTxResults(client, 1, txResults) + }, + &types.Block{ + Header: types.Header{Height: 1, ChainID: ChainID}, + Data: types.Data{Txs: []types.Tx{txBz, txBz2}}, + }, + []*abci.ResponseDeliverTx{ + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash.Hex()}, + {Key: "txIndex", Value: "0"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, + }}, + }, + }, + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash2.Hex()}, + {Key: "txIndex", Value: "1"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, + }}, + }, + }, + }, + map[string]interface{}{"test": "hello"}, + true, + }, + { + "pass - transaction found", + txHash, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, 1, txBz) + RegisterTraceTransaction(queryClient, msgEthereumTx) + txResults := []*abci.ResponseDeliverTx{ + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash.Hex()}, + {Key: "txIndex", Value: "0"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, + }}, + }, + }, + } + RegisterBlockResultsWithTxResults(client, 1, txResults) + + }, + &types.Block{Header: types.Header{Height: 1}, Data: types.Data{Txs: []types.Tx{txBz}}}, + []*abci.ResponseDeliverTx{ + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash.Hex()}, + {Key: "txIndex", Value: "0"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, + }}, + }, + }, + }, + map[string]interface{}{"test": "hello"}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + db := dbm.NewMemDB() + suite.backend.indexer = indexer.NewKVIndexer(db, tmlog.NewNopLogger(), suite.backend.clientCtx) + + err := suite.backend.indexer.IndexBlock(tc.block, tc.responseBlock) + suite.Require().NoError(err) + txResult, err := suite.backend.TraceTransaction(tc.txHash, nil) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expResult, txResult) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestTraceBlock() { + msgEthTx, bz := suite.buildEthereumTx() + emptyBlock := types.MakeBlock(1, []types.Tx{}, nil, nil) + emptyBlock.ChainID = ChainID + filledBlock := types.MakeBlock(1, []types.Tx{bz}, nil, nil) + filledBlock.ChainID = ChainID + resBlockEmpty := tmrpctypes.ResultBlock{Block: emptyBlock, BlockID: emptyBlock.LastBlockID} + resBlockFilled := tmrpctypes.ResultBlock{Block: filledBlock, BlockID: filledBlock.LastBlockID} + + testCases := []struct { + name string + registerMock func() + expTraceResults []*evmtypes.TxTraceResult + resBlock *tmrpctypes.ResultBlock + config *evmtypes.TraceConfig + expPass bool + }{ + { + "pass - no transaction returning empty array", + func() {}, + []*evmtypes.TxTraceResult{}, + &resBlockEmpty, + &evmtypes.TraceConfig{}, + true, + }, + { + "fail - cannot unmarshal data", + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterTraceBlock(queryClient, []*evmtypes.MsgEthereumTx{msgEthTx}) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockResults(client, 1) + }, + []*evmtypes.TxTraceResult{}, + &resBlockFilled, + &evmtypes.TraceConfig{}, + false, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + traceResults, err := suite.backend.TraceBlock(1, tc.config, tc.resBlock) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expTraceResults, traceResults) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index e08f36db42..d43dc26177 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -44,7 +44,7 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac resBlock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(res.Height)) if err != nil { b.logger.Debug("block not found", "height", res.Height, "error", err.Error()) - return nil, nil + return nil, err } blockRes, err := b.TendermintBlockResultByNumber(&res.Height) @@ -388,9 +388,10 @@ func (b *Backend) GetTransactionByBlockNumberAndIndex( func (b *Backend) GetTxByEthHash(hash common.Hash) (*ethermint.TxResult, *rpctypes.TxResultAdditionalFields, error) { if b.indexer != nil { txRes, err := b.indexer.GetByTxHash(hash) - if err == nil { - return txRes, nil, nil + if err != nil { + return nil, nil, err } + return txRes, nil, nil } // fallback to tendermint tx indexer diff --git a/rpc/backend/tx_info_test.go b/rpc/backend/tx_info_test.go new file mode 100644 index 0000000000..9827c6eff1 --- /dev/null +++ b/rpc/backend/tx_info_test.go @@ -0,0 +1,639 @@ +package backend + +import ( + "fmt" + "math/big" + + dbm "github.com/cometbft/cometbft-db" + abci "github.com/cometbft/cometbft/abci/types" + tmlog "github.com/cometbft/cometbft/libs/log" + tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/cometbft/cometbft/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/evmos/ethermint/indexer" + ethermint "github.com/evmos/ethermint/types" + evmtypes "github.com/evmos/ethermint/x/evm/types" + "google.golang.org/grpc/metadata" + + "github.com/zeta-chain/zetacore/rpc/backend/mocks" + rpctypes "github.com/zeta-chain/zetacore/rpc/types" +) + +func (suite *BackendTestSuite) TestGetTransactionByHash() { + msgEthereumTx, _ := suite.buildEthereumTx() + txHash := msgEthereumTx.AsTransaction().Hash() + + txBz := suite.signAndEncodeEthTx(msgEthereumTx) + block := &types.Block{Header: types.Header{Height: 1, ChainID: "test"}, Data: types.Data{Txs: []types.Tx{txBz}}} + responseDeliver := []*abci.ResponseDeliverTx{ + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash.Hex()}, + {Key: "txIndex", Value: "0"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: ""}, + }}, + }, + }, + } + + rpcTransaction, err := rpctypes.NewRPCTransaction( + msgEthereumTx.AsTransaction(), + common.Hash{}, + 0, + 0, + big.NewInt(1), + suite.backend.chainID, + ) + suite.Require().NoError(err) + + testCases := []struct { + name string + registerMock func() + tx *evmtypes.MsgEthereumTx + expRPCTx *rpctypes.RPCTransaction + expPass bool + }{ + { + "fail - Block error", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, 1) + }, + msgEthereumTx, + rpcTransaction, + false, + }, + { + "fail - Block Result error", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlock(client, 1, txBz) + RegisterBlockResultsError(client, 1) + }, + msgEthereumTx, + nil, + true, + }, + { + "pass - Base fee error", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBlock(client, 1, txBz) + RegisterBlockResults(client, 1) + RegisterBaseFeeError(queryClient) + }, + msgEthereumTx, + rpcTransaction, + true, + }, + { + "pass - Transaction found and returned", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBlock(client, 1, txBz) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, sdk.NewInt(1)) + }, + msgEthereumTx, + rpcTransaction, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + db := dbm.NewMemDB() + suite.backend.indexer = indexer.NewKVIndexer(db, tmlog.NewNopLogger(), suite.backend.clientCtx) + err := suite.backend.indexer.IndexBlock(block, responseDeliver) + suite.Require().NoError(err) + + rpcTx, err := suite.backend.GetTransactionByHash(common.HexToHash(tc.tx.Hash)) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(rpcTx, tc.expRPCTx) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTransactionsByHashPending() { + msgEthereumTx, bz := suite.buildEthereumTx() + rpcTransaction, err := rpctypes.NewRPCTransaction( + msgEthereumTx.AsTransaction(), + common.Hash{}, + 0, + 0, + big.NewInt(1), + suite.backend.chainID, + ) + suite.Require().NoError(err) + + testCases := []struct { + name string + registerMock func() + tx *evmtypes.MsgEthereumTx + expRPCTx *rpctypes.RPCTransaction + expPass bool + }{ + { + "fail - Pending transactions returns error", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterUnconfirmedTxsError(client, nil) + }, + msgEthereumTx, + nil, + true, + }, + { + "fail - Tx not found return nil", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterUnconfirmedTxs(client, nil, nil) + }, + msgEthereumTx, + nil, + true, + }, + { + "pass - Tx found and returned", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterUnconfirmedTxs(client, nil, types.Txs{bz}) + }, + msgEthereumTx, + rpcTransaction, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + rpcTx, err := suite.backend.getTransactionByHashPending(common.HexToHash(tc.tx.Hash)) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(rpcTx, tc.expRPCTx) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTxByEthHash() { + msgEthereumTx, bz := suite.buildEthereumTx() + rpcTransaction, err := rpctypes.NewRPCTransaction( + msgEthereumTx.AsTransaction(), + common.Hash{}, + 0, + 0, + big.NewInt(1), + suite.backend.chainID, + ) + suite.Require().NoError(err) + + testCases := []struct { + name string + registerMock func() + tx *evmtypes.MsgEthereumTx + expRPCTx *rpctypes.RPCTransaction + expPass bool + }{ + { + "fail - Indexer disabled can't find transaction", + func() { + suite.backend.indexer = nil + client := suite.backend.clientCtx.Client.(*mocks.Client) + query := fmt.Sprintf( + "%s.%s='%s'", + evmtypes.TypeMsgEthereumTx, + evmtypes.AttributeKeyEthereumTxHash, + common.HexToHash(msgEthereumTx.Hash).Hex(), + ) + RegisterTxSearch(client, query, bz) + }, + msgEthereumTx, + rpcTransaction, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + rpcTx, _, err := suite.backend.GetTxByEthHash(common.HexToHash(tc.tx.Hash)) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(rpcTx, tc.expRPCTx) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTransactionByBlockHashAndIndex() { + _, bz := suite.buildEthereumTx() + + testCases := []struct { + name string + registerMock func() + blockHash common.Hash + expRPCTx *rpctypes.RPCTransaction + expPass bool + }{ + { + "pass - block not found", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHashError(client, common.Hash{}, bz) + }, + common.Hash{}, + nil, + true, + }, + { + "pass - Block results error", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockByHash(client, common.Hash{}, bz) + RegisterBlockResultsError(client, 1) + }, + common.Hash{}, + nil, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + rpcTx, err := suite.backend.GetTransactionByBlockHashAndIndex(tc.blockHash, 1) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(rpcTx, tc.expRPCTx) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTransactionByBlockAndIndex() { + msgEthTx, bz := suite.buildEthereumTx() + + defaultBlock := types.MakeBlock(1, []types.Tx{bz}, nil, nil) + defaultResponseDeliverTx := []*abci.ResponseDeliverTx{ + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: common.HexToHash(msgEthTx.Hash).Hex()}, + {Key: "txIndex", Value: "0"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: ""}, + }}, + }, + }, + } + + txFromMsg, err := rpctypes.NewTransactionFromMsg( + msgEthTx, + common.BytesToHash(defaultBlock.Hash().Bytes()), + 1, + 0, + big.NewInt(1), + suite.backend.chainID, + nil, + ) + suite.Require().NoError(err) + + testCases := []struct { + name string + registerMock func() + block *tmrpctypes.ResultBlock + idx hexutil.Uint + expRPCTx *rpctypes.RPCTransaction + expPass bool + }{ + { + "pass - block txs index out of bound ", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockResults(client, 1) + }, + &tmrpctypes.ResultBlock{Block: types.MakeBlock(1, []types.Tx{bz}, nil, nil)}, + 1, + nil, + true, + }, + { + "pass - Can't fetch base fee", + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockResults(client, 1) + RegisterBaseFeeError(queryClient) + }, + &tmrpctypes.ResultBlock{Block: defaultBlock}, + 0, + txFromMsg, + true, + }, + { + "pass - Gets Tx by transaction index", + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + db := dbm.NewMemDB() + suite.backend.indexer = indexer.NewKVIndexer(db, tmlog.NewNopLogger(), suite.backend.clientCtx) + txBz := suite.signAndEncodeEthTx(msgEthTx) + block := &types.Block{ + Header: types.Header{Height: 1, ChainID: "test"}, + Data: types.Data{Txs: []types.Tx{txBz}}, + } + err := suite.backend.indexer.IndexBlock(block, defaultResponseDeliverTx) + suite.Require().NoError(err) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, sdk.NewInt(1)) + }, + &tmrpctypes.ResultBlock{Block: defaultBlock}, + 0, + txFromMsg, + true, + }, + { + "pass - returns the Ethereum format transaction by the Ethereum hash", + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, sdk.NewInt(1)) + }, + &tmrpctypes.ResultBlock{Block: defaultBlock}, + 0, + txFromMsg, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + rpcTx, err := suite.backend.GetTransactionByBlockAndIndex(tc.block, tc.idx) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(rpcTx, tc.expRPCTx) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTransactionByBlockNumberAndIndex() { + msgEthTx, bz := suite.buildEthereumTx() + defaultBlock := types.MakeBlock(1, []types.Tx{bz}, nil, nil) + txFromMsg, err := rpctypes.NewTransactionFromMsg( + msgEthTx, + common.BytesToHash(defaultBlock.Hash().Bytes()), + 1, + 0, + big.NewInt(1), + suite.backend.chainID, + nil, + ) + suite.Require().NoError(err) + + testCases := []struct { + name string + registerMock func() + blockNum rpctypes.BlockNumber + idx hexutil.Uint + expRPCTx *rpctypes.RPCTransaction + expPass bool + }{ + { + "fail - block not found return nil", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, 1) + }, + 0, + 0, + nil, + true, + }, + { + "pass - returns the transaction identified by block number and index", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBlock(client, 1, bz) + RegisterBlockResults(client, 1) + RegisterBaseFee(queryClient, sdk.NewInt(1)) + }, + 0, + 0, + txFromMsg, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + rpcTx, err := suite.backend.GetTransactionByBlockNumberAndIndex(tc.blockNum, tc.idx) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(rpcTx, tc.expRPCTx) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTransactionByTxIndex() { + _, bz := suite.buildEthereumTx() + + testCases := []struct { + name string + registerMock func() + height int64 + index uint + expTxResult *ethermint.TxResult + expPass bool + }{ + { + "fail - Ethereum tx with query not found", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + suite.backend.indexer = nil + RegisterTxSearch(client, "tx.height=0 AND ethereum_tx.txIndex=0", bz) + }, + 0, + 0, + ðermint.TxResult{}, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + txResults, _, err := suite.backend.GetTxByTxIndex(tc.height, tc.index) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(txResults, tc.expTxResult) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestQueryTendermintTxIndexer() { + testCases := []struct { + name string + registerMock func() + txGetter func(*rpctypes.ParsedTxs) *rpctypes.ParsedTx + query string + expTxResult *ethermint.TxResult + expPass bool + }{ + { + "fail - Ethereum tx with query not found", + func() { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterTxSearchEmpty(client, "") + }, + func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx { + return &rpctypes.ParsedTx{} + }, + "", + ðermint.TxResult{}, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + txResults, _, err := suite.backend.queryTendermintTxIndexer(tc.query, tc.txGetter) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(txResults, tc.expTxResult) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTransactionReceipt() { + msgEthereumTx, _ := suite.buildEthereumTx() + txHash := msgEthereumTx.AsTransaction().Hash() + + txBz := suite.signAndEncodeEthTx(msgEthereumTx) + + testCases := []struct { + name string + registerMock func() + tx *evmtypes.MsgEthereumTx + block *types.Block + blockResult []*abci.ResponseDeliverTx + expTxReceipt map[string]interface{} + expPass bool + }{ + { + "fail - Receipts do not match ", + func() { + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterParams(queryClient, &header, 1) + RegisterParamsWithoutHeader(queryClient, 1) + RegisterBlock(client, 1, txBz) + RegisterBlockResults(client, 1) + }, + msgEthereumTx, + &types.Block{Header: types.Header{Height: 1}, Data: types.Data{Txs: []types.Tx{txBz}}}, + []*abci.ResponseDeliverTx{ + { + Code: 0, + Events: []abci.Event{ + {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: "ethereumTxHash", Value: txHash.Hex()}, + {Key: "txIndex", Value: "0"}, + {Key: "amount", Value: "1000"}, + {Key: "txGasUsed", Value: "21000"}, + {Key: "txHash", Value: ""}, + {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, + }}, + }, + }, + }, + map[string]interface{}(nil), + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + db := dbm.NewMemDB() + suite.backend.indexer = indexer.NewKVIndexer(db, tmlog.NewNopLogger(), suite.backend.clientCtx) + err := suite.backend.indexer.IndexBlock(tc.block, tc.blockResult) + suite.Require().NoError(err) + + txReceipt, err := suite.backend.GetTransactionReceipt(common.HexToHash(tc.tx.Hash)) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(txReceipt, tc.expTxReceipt) + } else { + suite.Require().NotEqual(txReceipt, tc.expTxReceipt) + } + }) + } +} diff --git a/rpc/backend/utils_test.go b/rpc/backend/utils_test.go new file mode 100644 index 0000000000..a3b3c2dd5f --- /dev/null +++ b/rpc/backend/utils_test.go @@ -0,0 +1,52 @@ +package backend + +import ( + "fmt" + + "github.com/cometbft/cometbft/proto/tendermint/crypto" +) + +func mockProofs(num int, withData bool) *crypto.ProofOps { + var proofOps *crypto.ProofOps + if num > 0 { + proofOps = new(crypto.ProofOps) + for i := 0; i < num; i++ { + proof := crypto.ProofOp{} + if withData { + proof.Data = []byte("\n\031\n\003KEY\022\005VALUE\032\013\010\001\030\001 \001*\003\000\002\002") + } + proofOps.Ops = append(proofOps.Ops, proof) + } + } + return proofOps +} + +func (suite *BackendTestSuite) TestGetHexProofs() { + defaultRes := []string{""} + testCases := []struct { + name string + proof *crypto.ProofOps + exp []string + }{ + { + "no proof provided", + mockProofs(0, false), + defaultRes, + }, + { + "no proof data provided", + mockProofs(1, false), + defaultRes, + }, + { + "valid proof provided", + mockProofs(1, true), + []string{"0x0a190a034b4559120556414c55451a0b0801180120012a03000202"}, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.Require().Equal(tc.exp, GetHexProofs(tc.proof)) + }) + } +}