package backend import ( "fmt" "math/big" "github.com/cerc-io/laconicd/indexer" "github.com/cerc-io/laconicd/rpc/backend/mocks" rpctypes "github.com/cerc-io/laconicd/rpc/types" ethermint "github.com/cerc-io/laconicd/types" evmtypes "github.com/cerc-io/laconicd/x/evm/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" abci "github.com/tendermint/tendermint/abci/types" tmlog "github.com/tendermint/tendermint/libs/log" tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" "google.golang.org/grpc/metadata" ) 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: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())}, {Key: []byte("txIndex"), Value: []byte("0")}, {Key: []byte("amount"), Value: []byte("1000")}, {Key: []byte("txGasUsed"), Value: []byte("21000")}, {Key: []byte("txHash"), Value: []byte("")}, {Key: []byte("recipient"), Value: []byte("")}, }}, }, }, } rpcTransaction, _ := rpctypes.NewRPCTransaction(msgEthereumTx.AsTransaction(), common.Hash{}, 0, 0, big.NewInt(1), suite.backend.chainID) 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, _ := rpctypes.NewRPCTransaction(msgEthereumTx.AsTransaction(), common.Hash{}, 0, 0, big.NewInt(1), suite.backend.chainID) 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, _ := rpctypes.NewRPCTransaction(msgEthereumTx.AsTransaction(), common.Hash{}, 0, 0, big.NewInt(1), suite.backend.chainID) 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: []byte("ethereumTxHash"), Value: []byte(common.HexToHash(msgEthTx.Hash).Hex())}, {Key: []byte("txIndex"), Value: []byte("0")}, {Key: []byte("amount"), Value: []byte("1000")}, {Key: []byte("txGasUsed"), Value: []byte("21000")}, {Key: []byte("txHash"), Value: []byte("")}, {Key: []byte("recipient"), Value: []byte("")}, }}, }, }, } txFromMsg, _ := rpctypes.NewTransactionFromMsg( msgEthTx, common.BytesToHash(defaultBlock.Hash().Bytes()), 1, 0, big.NewInt(1), suite.backend.chainID, ) 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, _ := rpctypes.NewTransactionFromMsg( msgEthTx, common.BytesToHash(defaultBlock.Hash().Bytes()), 1, 0, big.NewInt(1), suite.backend.chainID, ) 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: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())}, {Key: []byte("txIndex"), Value: []byte("0")}, {Key: []byte("amount"), Value: []byte("1000")}, {Key: []byte("txGasUsed"), Value: []byte("21000")}, {Key: []byte("txHash"), Value: []byte("")}, {Key: []byte("recipient"), Value: []byte("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) } }) } }