From 945cf3118b1f97e9ecc321f7a0d773d7e9b412e3 Mon Sep 17 00:00:00 2001 From: jason song Date: Mon, 2 Sep 2024 11:12:07 +0900 Subject: [PATCH 1/3] fix: tx receipt returns a reverted status instead of an "invalid chain ID" error in the height where the chain ID was incorrect --- rpc/backend/tx_info.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index 2d25dd0ae4..eedac4ef44 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -194,7 +194,16 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ return nil, err } - from, err := ethMsg.GetSender(chainID.ToInt()) + // NOTE + // This patch applies only to the period when the chain-id was set to 9000 between the v8 and v8.1.1 versions of Canto. + // It is intended to ensure that reverted transaction receipt returns the same results instead of 'invalid chain id' error. + // The upgrade height for v8 is 10848200, and for v8.1.1, it is 10849447. + var from common.Address + if res.Height >= 10848200 && res.Height < 10849447 { + from, err = ethMsg.GetSender(big.NewInt(9000)) // 9000 is the default chain-id that was applied during that period. + } else { + from, err = ethMsg.GetSender(chainID.ToInt()) + } if err != nil { return nil, err } From c09f332442a20fbb7793221025034c545cf6b997 Mon Sep 17 00:00:00 2001 From: jason song Date: Mon, 2 Sep 2024 15:28:20 +0900 Subject: [PATCH 2/3] test: add test for coverage --- rpc/backend/backend_suite_test.go | 26 +++++ rpc/backend/tx_info_test.go | 176 ++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/rpc/backend/backend_suite_test.go b/rpc/backend/backend_suite_test.go index 0a230937e1..04709abc2e 100644 --- a/rpc/backend/backend_suite_test.go +++ b/rpc/backend/backend_suite_test.go @@ -115,6 +115,32 @@ func (suite *BackendTestSuite) buildEthereumTx() (*evmtypes.MsgEthereumTx, []byt return msgEthereumTx, bz } +func (suite *BackendTestSuite) buildEthereumTxWithChainId(chainId *big.Int) (*evmtypes.MsgEthereumTx, []byte) { + msgEthereumTx := evmtypes.NewTx( + chainId, + uint64(0), + &common.Address{}, + big.NewInt(0), + 100000, + big.NewInt(1), + nil, + nil, + nil, + nil, + ) + + // A valid msg should have empty `From` + msgEthereumTx.From = "" + + txBuilder := suite.backend.clientCtx.TxConfig.NewTxBuilder() + 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, diff --git a/rpc/backend/tx_info_test.go b/rpc/backend/tx_info_test.go index 976509470e..5cd0fa46fa 100644 --- a/rpc/backend/tx_info_test.go +++ b/rpc/backend/tx_info_test.go @@ -10,11 +10,14 @@ import ( tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" "github.com/cometbft/cometbft/types" dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/server" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/evmos/ethermint/indexer" "github.com/evmos/ethermint/rpc/backend/mocks" rpctypes "github.com/evmos/ethermint/rpc/types" + "github.com/evmos/ethermint/tests" ethermint "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" "google.golang.org/grpc/metadata" @@ -605,6 +608,179 @@ func (suite *BackendTestSuite) TestGetTransactionReceipt() { } } +func (suite *BackendTestSuite) TestCheckChainIdWithTransactionReceipt() { + + patchedHeight := int64(10848200) + notPatchedHeight := int64(10848100) + + // TX using chain id 9000 + from, pk := tests.NewAddrKey() + msgEthereumTx, _ := suite.buildEthereumTxWithChainId(big.NewInt(9000)) + txHash := msgEthereumTx.AsTransaction().Hash() + + signer := tests.NewSigner(pk) + ethSigner := ethtypes.NewLondonSigner(big.NewInt(9000)) + msgEthereumTx.From = from.String() + err := msgEthereumTx.Sign(ethSigner, signer) + suite.NoError(err) + + tx, err := msgEthereumTx.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), "aphoton") + suite.NoError(err) + txEncoder := suite.backend.clientCtx.TxConfig.TxEncoder() + txBz, err := txEncoder(tx) + suite.NoError(err) + + testCases := []struct { + name string + registerMock func() + tx *evmtypes.MsgEthereumTx + block *types.Block + blockResult *abci.ResponseFinalizeBlock + expTxReceipt map[string]interface{} + expKeyMatchPass bool + expApiPass bool + }{ + { + "success- Receipts with a different chain ID within a patched height", + func() { + + ctx := server.NewDefaultContext() + + // overwrite client conext + suite.backend.clientCtx.WithChainID("canto_7700-1").WithHeight(patchedHeight) + + idxer := indexer.NewKVIndexer(dbm.NewMemDB(), ctx.Logger, suite.backend.clientCtx) + suite.backend = NewBackend(ctx, ctx.Logger, suite.backend.clientCtx, true, 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(patchedHeight) + + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterParams(queryClient, &header, patchedHeight) + RegisterParamsWithoutHeader(queryClient, patchedHeight) + RegisterBlock(client, patchedHeight, txBz) + RegisterBlockResults(client, patchedHeight) + }, + msgEthereumTx, + &types.Block{Header: types.Header{Height: patchedHeight}, Data: types.Data{Txs: []types.Tx{txBz}}}, + &abci.ResponseFinalizeBlock{ + TxResults: []*abci.ExecTxResult{ + { + 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"}, + {Key: "status", Value: "0x0"}, + }}, + }, + }, + }, + }, + map[string]interface{}(nil), + true, + true, + }, + { + "false- Receipts with a different chain ID not in the patched height. expected to return an invalid chain ID", + func() { + + ctx := server.NewDefaultContext() + + // overwrite client conext + suite.backend.clientCtx = suite.backend.clientCtx.WithChainID("canto_7700-1").WithHeight(notPatchedHeight) + + idxer := indexer.NewKVIndexer(dbm.NewMemDB(), ctx.Logger, suite.backend.clientCtx) + suite.backend = NewBackend(ctx, ctx.Logger, suite.backend.clientCtx, true, 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(notPatchedHeight) + + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterParams(queryClient, &header, notPatchedHeight) + RegisterParamsWithoutHeader(queryClient, notPatchedHeight) + RegisterBlock(client, notPatchedHeight, txBz) + RegisterBlockResults(client, notPatchedHeight) + }, + msgEthereumTx, + &types.Block{Header: types.Header{Height: notPatchedHeight}, Data: types.Data{Txs: []types.Tx{txBz}}}, + &abci.ResponseFinalizeBlock{ + TxResults: []*abci.ExecTxResult{ + { + 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"}, + {Key: "status", Value: "0x0"}, + }}, + }, + }, + }, + }, + map[string]interface{}(nil), + false, + false, // invalid chain id error + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.registerMock() + + db := dbm.NewMemDB() + suite.backend.indexer = indexer.NewKVIndexer(db, log.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.expApiPass { + suite.Require().NoError(err) + + if tc.expKeyMatchPass { + + // check all expected receipt keys are exist. + for k, _ := range tc.expTxReceipt { + _, keyExist := txReceipt[k] + suite.Require().True(keyExist) + } + + } else { + + anyKeyNotExist := false + // check the receipt keys are exist. + for k, _ := range tc.expTxReceipt { + _, keyExist := txReceipt[k] + if !keyExist { + anyKeyNotExist = true + } + + } + suite.Require().True(anyKeyNotExist) + } + } else { + suite.Require().Error(err) + } + + }) + } +} + func (suite *BackendTestSuite) TestGetGasUsed() { origin := suite.backend.cfg.JSONRPC.FixRevertGasRefundHeight testCases := []struct { From 1454a72d5c270623dee1d8ed7b0d6cc6b01d34b4 Mon Sep 17 00:00:00 2001 From: jason song Date: Mon, 2 Sep 2024 18:38:55 +0900 Subject: [PATCH 3/3] test: remove unused logic --- rpc/backend/tx_info_test.go | 44 +++++++++++-------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/rpc/backend/tx_info_test.go b/rpc/backend/tx_info_test.go index 5cd0fa46fa..26cb4db942 100644 --- a/rpc/backend/tx_info_test.go +++ b/rpc/backend/tx_info_test.go @@ -631,14 +631,13 @@ func (suite *BackendTestSuite) TestCheckChainIdWithTransactionReceipt() { suite.NoError(err) testCases := []struct { - name string - registerMock func() - tx *evmtypes.MsgEthereumTx - block *types.Block - blockResult *abci.ResponseFinalizeBlock - expTxReceipt map[string]interface{} - expKeyMatchPass bool - expApiPass bool + name string + registerMock func() + tx *evmtypes.MsgEthereumTx + block *types.Block + blockResult *abci.ResponseFinalizeBlock + expTxReceipt map[string]interface{} + expPass bool }{ { "success- Receipts with a different chain ID within a patched height", @@ -686,7 +685,6 @@ func (suite *BackendTestSuite) TestCheckChainIdWithTransactionReceipt() { }, map[string]interface{}(nil), true, - true, }, { "false- Receipts with a different chain ID not in the patched height. expected to return an invalid chain ID", @@ -733,7 +731,6 @@ func (suite *BackendTestSuite) TestCheckChainIdWithTransactionReceipt() { }, }, map[string]interface{}(nil), - false, false, // invalid chain id error }, } @@ -749,30 +746,15 @@ func (suite *BackendTestSuite) TestCheckChainIdWithTransactionReceipt() { suite.Require().NoError(err) txReceipt, err := suite.backend.GetTransactionReceipt(common.HexToHash(tc.tx.Hash)) - if tc.expApiPass { + if tc.expPass { suite.Require().NoError(err) - if tc.expKeyMatchPass { - - // check all expected receipt keys are exist. - for k, _ := range tc.expTxReceipt { - _, keyExist := txReceipt[k] - suite.Require().True(keyExist) - } - - } else { - - anyKeyNotExist := false - // check the receipt keys are exist. - for k, _ := range tc.expTxReceipt { - _, keyExist := txReceipt[k] - if !keyExist { - anyKeyNotExist = true - } - - } - suite.Require().True(anyKeyNotExist) + // if test passes, all expected receipt keys should be exist. + for k, _ := range tc.expTxReceipt { + _, keyExist := txReceipt[k] + suite.Require().True(keyExist) } + } else { suite.Require().Error(err) }