From 0a3b8dc7235fc5fd962aacca43324e499c3b41bd Mon Sep 17 00:00:00 2001 From: skosito Date: Fri, 31 May 2024 19:37:05 +0100 Subject: [PATCH] feat: modify rpc methods to include synthetic txs (#2282) --- changelog.md | 1 + go.mod | 13 ++-- go.sum | 5 +- rpc/backend/blocks.go | 90 +++++++++++++++++++++------ rpc/backend/tracing.go | 89 +++++---------------------- rpc/backend/tx_info.go | 129 +++++++++++++++++++-------------------- rpc/types/events.go | 88 ++++++++++++++++++++++---- rpc/types/types.go | 2 + rpc/types/utils.go | 6 +- x/fungible/keeper/evm.go | 8 ++- 10 files changed, 243 insertions(+), 188 deletions(-) diff --git a/changelog.md b/changelog.md index 4dda5f671e..d4595d2891 100644 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,7 @@ * [2152](https://github.com/zeta-chain/node/pull/2152) - custom priority nonce mempool * [2113](https://github.com/zeta-chain/node/pull/2113) - add zetaclientd-supervisor process * [2154](https://github.com/zeta-chain/node/pull/2154) - add `ibccrosschain` module +* [2282](https://github.com/zeta-chain/node/pull/2282) - modify rpc methods to support synthetic txs * [2258](https://github.com/zeta-chain/node/pull/2258) - add Optimism and Base in static chain information * [2287](https://github.com/zeta-chain/node/pull/2287) - implement `MsgUpdateChainInfo` message * [2279](https://github.com/zeta-chain/node/pull/2279) - add a CCTXGateway field to chain static data diff --git a/go.mod b/go.mod index 5cba25e884..709983e8cc 100644 --- a/go.mod +++ b/go.mod @@ -59,11 +59,13 @@ require ( cosmossdk.io/tools/rosetta v0.2.1 github.com/binance-chain/tss-lib v0.0.0-20201118045712-70b2cb4bf916 github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/cockroachdb/errors v1.10.0 github.com/cometbft/cometbft v0.37.4 github.com/cometbft/cometbft-db v0.8.0 + github.com/golang/mock v1.6.0 + github.com/huandu/skiplist v1.2.0 github.com/nanmu42/etherscan-api v1.10.0 github.com/onrik/ethrpc v1.2.0 - github.com/tendermint/tendermint v0.34.12 go.nhat.io/grpcmock v0.25.0 ) @@ -77,7 +79,6 @@ require ( github.com/agl/ed25519 v0.0.0-20200225211852-fd4d107ace12 // indirect github.com/bool64/shared v0.1.5 // indirect github.com/cespare/xxhash v1.1.0 // indirect - github.com/cockroachdb/errors v1.10.0 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677 // indirect github.com/cockroachdb/redact v1.1.5 // indirect @@ -95,10 +96,8 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/golang/glog v1.1.2 // indirect - github.com/golang/mock v1.6.0 // indirect github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/huandu/skiplist v1.2.0 // indirect github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/ipfs/boxo v0.10.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect @@ -216,7 +215,7 @@ require ( github.com/gtank/ristretto255 v0.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.4 // indirect + github.com/hashicorp/go-getter v1.7.4 github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect @@ -321,7 +320,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.17.0 - golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect + golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.19.0 golang.org/x/oauth2 v0.15.0 // indirect @@ -354,4 +353,4 @@ replace ( replace github.com/cometbft/cometbft-db => github.com/notional-labs/cometbft-db v0.0.0-20230321185329-6dc7c0ca6345 -replace github.com/evmos/ethermint => github.com/zeta-chain/ethermint v0.0.0-20240429123701-35f3f79bf83f +replace github.com/evmos/ethermint => github.com/zeta-chain/ethermint v0.0.0-20240531172701-61d040058c94 diff --git a/go.sum b/go.sum index 21f0ee7415..ac3eb6cc0b 100644 --- a/go.sum +++ b/go.sum @@ -1639,7 +1639,6 @@ github.com/tendermint/tendermint v0.34.0-rc6/go.mod h1:ugzyZO5foutZImv0Iyx/gOFCX github.com/tendermint/tendermint v0.34.0/go.mod h1:Aj3PIipBFSNO21r+Lq3TtzQ+uKESxkbA3yo/INM4QwQ= github.com/tendermint/tendermint v0.34.10/go.mod h1:aeHL7alPh4uTBIJQ8mgFEE8VwJLXI1VD3rVOmH2Mcy0= github.com/tendermint/tendermint v0.34.11/go.mod h1:aeHL7alPh4uTBIJQ8mgFEE8VwJLXI1VD3rVOmH2Mcy0= -github.com/tendermint/tendermint v0.34.12 h1:m+kUYNhONedhJfHmHG8lqsdZvbR5t6vmhaok1yXjpKg= github.com/tendermint/tendermint v0.34.12/go.mod h1:aeHL7alPh4uTBIJQ8mgFEE8VwJLXI1VD3rVOmH2Mcy0= github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI= github.com/tendermint/tm-db v0.6.3/go.mod h1:lfA1dL9/Y/Y8wwyPp2NMLyn5P5Ptr/gvDFNWtrCWSf8= @@ -1733,8 +1732,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeta-chain/ethermint v0.0.0-20240429123701-35f3f79bf83f h1:joafCsPgohPEn93VCbNXi9IAl6kNvKy8u+kv5amEvUk= -github.com/zeta-chain/ethermint v0.0.0-20240429123701-35f3f79bf83f/go.mod h1:s1zA6OpXv3Tb5I0M6M6j5fo/AssaZL/pgkc7G0W2kN8= +github.com/zeta-chain/ethermint v0.0.0-20240531172701-61d040058c94 h1:M54ljayJvy+WlEVdUmX8pgo1nA+XguB3mLhm3wi2z9o= +github.com/zeta-chain/ethermint v0.0.0-20240531172701-61d040058c94/go.mod h1:s1zA6OpXv3Tb5I0M6M6j5fo/AssaZL/pgkc7G0W2kN8= github.com/zeta-chain/go-tss v0.1.1-0.20240430111318-1785e48eb127 h1:AGQepvsMIVHAHPlplzNcSCyMoGBY1DfO4WHG/QHUSIU= github.com/zeta-chain/go-tss v0.1.1-0.20240430111318-1785e48eb127/go.mod h1:bVpAoSlRYYCY/R34horVU3cheeHqhSVxygelc++emIU= github.com/zeta-chain/keystone/keys v0.0.0-20231105174229-903bc9405da2 h1:gd2uE0X+ZbdFJ8DubxNqLbOVlCB12EgWdzSNRAR82tM= diff --git a/rpc/backend/blocks.go b/rpc/backend/blocks.go index 361c53e318..d28b8345da 100644 --- a/rpc/backend/blocks.go +++ b/rpc/backend/blocks.go @@ -22,8 +22,10 @@ import ( "math/big" "strconv" + abci "github.com/cometbft/cometbft/abci/types" tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" "github.com/ethereum/go-ethereum/common" @@ -272,14 +274,14 @@ func (b *Backend) BlockNumberFromTendermintByHash(blockHash common.Hash) (*big.I return big.NewInt(resBlock.Block.Height), nil } -// EthMsgsFromTendermintBlock returns all real MsgEthereumTxs from a +// EthMsgsFromTendermintBlock returns all real and synthetic MsgEthereumTxs from a // Tendermint block. It also ensures consistency over the correct txs indexes // across RPC endpoints func (b *Backend) EthMsgsFromTendermintBlock( resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults, ) ([]*evmtypes.MsgEthereumTx, []*rpctypes.TxResultAdditionalFields) { - var result []*evmtypes.MsgEthereumTx + var ethMsgs []*evmtypes.MsgEthereumTx var txsAdditional []*rpctypes.TxResultAdditionalFields block := resBlock.Block @@ -294,30 +296,78 @@ func (b *Backend) EthMsgsFromTendermintBlock( } tx, err := b.clientCtx.TxConfig.TxDecoder()(tx) - if err != nil { + // assumption is that if regular ethermint msg is found in tx + // there should not be synthetic one as well + shouldCheckForSyntheticTx := true + // if tx can be decoded, try to find MsgEthereumTx inside + if err == nil { + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if ok { + shouldCheckForSyntheticTx = false + ethMsg.Hash = ethMsg.AsTransaction().Hash().Hex() + ethMsgs = append(ethMsgs, ethMsg) + txsAdditional = append(txsAdditional, nil) + } + } + } else { b.logger.Debug("failed to decode transaction in block", "height", block.Height, "error", err.Error()) - continue } - for _, msg := range tx.GetMsgs() { - ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - res, additional, err := rpctypes.ParseTxBlockResult(txResults[i], tx, i, block.Height) - if err != nil || additional == nil || res == nil { - continue - } - ethMsg = &evmtypes.MsgEthereumTx{ - From: additional.Sender.Hex(), - Hash: additional.Hash.Hex(), - } + + // if tx can not be decoded or MsgEthereumTx was not found, try to parse it from block results + if shouldCheckForSyntheticTx { + ethMsg, additional := b.parseSyntheticTxFromBlockResults(txResults, i, tx, block) + if ethMsg != nil { + ethMsgs = append(ethMsgs, ethMsg) txsAdditional = append(txsAdditional, additional) - } else { - ethMsg.Hash = ethMsg.AsTransaction().Hash().Hex() - txsAdditional = append(txsAdditional, nil) } - result = append(result, ethMsg) } } - return result, txsAdditional + return ethMsgs, txsAdditional +} + +func (b *Backend) parseSyntheticTxFromBlockResults( + txResults []*abci.ResponseDeliverTx, + i int, + tx sdk.Tx, + block *tmtypes.Block, +) (*evmtypes.MsgEthereumTx, *rpctypes.TxResultAdditionalFields) { + res, additional, err := rpctypes.ParseTxBlockResult(txResults[i], tx, i, block.Height) + // just skip tx if it can not be parsed, so remaining txs from the block are parsed + if err != nil { + b.logger.Error(err.Error()) + return nil, nil + } + if additional == nil || res == nil { + return nil, nil + } + return b.parseSyntethicTxFromAdditionalFields(additional), additional +} + +func (b *Backend) parseSyntethicTxFromAdditionalFields( + additional *rpctypes.TxResultAdditionalFields, +) *evmtypes.MsgEthereumTx { + recipient := additional.Recipient + t := ethtypes.NewTx(ðtypes.LegacyTx{ + Nonce: additional.Nonce, + Data: additional.Data, + Gas: additional.GasUsed, + To: &recipient, + GasPrice: nil, + Value: additional.Value, + V: big.NewInt(0), + R: big.NewInt(0), + S: big.NewInt(0), + }) + ethMsg := &evmtypes.MsgEthereumTx{} + err := ethMsg.FromEthereumTx(t) + if err != nil { + b.logger.Error("can not create eth msg", err.Error()) + return nil + } + ethMsg.Hash = additional.Hash.Hex() + ethMsg.From = additional.Sender.Hex() + return ethMsg } // HeaderByNumber returns the block header identified by height. diff --git a/rpc/backend/tracing.go b/rpc/backend/tracing.go index e86d2ed8a4..84a78dc6c7 100644 --- a/rpc/backend/tracing.go +++ b/rpc/backend/tracing.go @@ -22,7 +22,6 @@ import ( tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/pkg/errors" @@ -50,62 +49,28 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi return nil, err } - // check tx index is not out of bound - // #nosec G701 txs number in block is always less than MaxUint32 - if uint32(len(blk.Block.Txs)) < transaction.TxIndex { - b.logger.Debug( - "tx index out of bounds", - "index", - transaction.TxIndex, - "hash", - hash.String(), - "height", - blk.Block.Height, - ) - return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height) - } - - var predecessors []*evmtypes.MsgEthereumTx - for _, txBz := range blk.Block.Txs[:transaction.TxIndex] { - tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz) - if err != nil { - b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error()) - continue - } - for _, msg := range tx.GetMsgs() { - ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - continue - } - - predecessors = append(predecessors, ethMsg) - } - } - - tx, err := b.clientCtx.TxConfig.TxDecoder()(blk.Block.Txs[transaction.TxIndex]) + blockResult, err := b.TendermintBlockResultByNumber(&blk.Block.Height) if err != nil { - b.logger.Debug("tx not found", "hash", hash) - return nil, err + return nil, fmt.Errorf("block result not found for height %d", blk.Block.Height) } - // add predecessor messages in current cosmos tx - // #nosec G701 always in range - for i := 0; i < int(transaction.MsgIndex); i++ { - ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx) - if !ok { - continue + predecessors := []*evmtypes.MsgEthereumTx{} + msgs, _ := b.EthMsgsFromTendermintBlock(blk, blockResult) + var ethMsg *evmtypes.MsgEthereumTx + for _, m := range msgs { + if m.Hash == hash.Hex() { + ethMsg = m + break } - predecessors = append(predecessors, ethMsg) + predecessors = append(predecessors, m) } - ethMessage, ok := tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx) - if !ok { - b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx)) - return nil, fmt.Errorf("invalid transaction type %T", tx) + if ethMsg == nil { + return nil, fmt.Errorf("tx not found in block %d", blk.Block.Height) } traceTxRequest := evmtypes.QueryTraceTxRequest{ - Msg: ethMessage, + Msg: ethMsg, Predecessors: predecessors, BlockNumber: blk.Block.Height, BlockTime: blk.Block.Time, @@ -160,31 +125,7 @@ func (b *Backend) TraceBlock(height rpctypes.BlockNumber, b.logger.Debug("block result not found", "height", block.Block.Height, "error", err.Error()) return nil, nil } - - txDecoder := b.clientCtx.TxConfig.TxDecoder() - - var txsMessages []*evmtypes.MsgEthereumTx - for i, tx := range txs { - if !rpctypes.TxSuccessOrExceedsBlockGasLimit(blockRes.TxsResults[i]) { - b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash())) - continue - } - - decodedTx, err := txDecoder(tx) - if err != nil { - b.logger.Error("failed to decode transaction", "hash", txs[i].Hash(), "error", err.Error()) - continue - } - - for _, msg := range decodedTx.GetMsgs() { - ethMessage, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - // Just considers Ethereum transactions - continue - } - txsMessages = append(txsMessages, ethMessage) - } - } + msgs, _ := b.EthMsgsFromTendermintBlock(block, blockRes) // minus one to get the context at the beginning of the block contextHeight := height - 1 @@ -195,7 +136,7 @@ func (b *Backend) TraceBlock(height rpctypes.BlockNumber, ctxWithHeight := rpctypes.ContextWithHeight(int64(contextHeight)) traceBlockRequest := &evmtypes.QueryTraceBlockRequest{ - Txs: txsMessages, + Txs: msgs, TraceConfig: config, BlockNumber: block.Block.Height, BlockTime: block.Block.Time, diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index 612097b0a1..e08f36db42 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -41,39 +41,48 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac return b.getTransactionByHashPending(txHash) } - block, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(res.Height)) - if err != nil { - return nil, err - } - - tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex]) + resBlock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(res.Height)) if err != nil { - return nil, err + b.logger.Debug("block not found", "height", res.Height, "error", err.Error()) + return nil, nil } - blockRes, err := b.TendermintBlockResultByNumber(&block.Block.Height) + blockRes, err := b.TendermintBlockResultByNumber(&res.Height) if err != nil { - b.logger.Debug("block result not found", "height", block.Block.Height, "error", err.Error()) + b.logger.Debug("failed to retrieve block results", "height", res.Height, "error", err.Error()) return nil, nil } - // the `res.MsgIndex` is inferred from tx index, should be within the bound. - msg, ok := tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) - if !ok { - if additional == nil { - return nil, err + var ethMsg *evmtypes.MsgEthereumTx + // if additional fields are empty we can try to get MsgEthereumTx from sdk.Msg array + if additional == nil { + // #nosec G701 always in range + if int(res.TxIndex) >= len(resBlock.Block.Txs) { + b.logger.Error("tx out of bounds") + return nil, fmt.Errorf("tx out of bounds") } - msg = &evmtypes.MsgEthereumTx{ - Hash: hexTx, - From: additional.Sender.Hex(), + tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex]) + if err != nil { + b.logger.Debug("decoding failed", "error", err.Error()) + return nil, fmt.Errorf("failed to decode tx: %w", err) + } + ethMsg = tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) + if ethMsg == nil { + b.logger.Error("failed to get eth msg from sdk.Msgs") + return nil, fmt.Errorf("failed to get eth msg from sdk.Msgs") } } else { - additional = nil + // if additional fields are not empty try to parse synthetic tx from them + ethMsg = b.parseSyntethicTxFromAdditionalFields(additional) + if ethMsg == nil { + b.logger.Error("failed to get synthetic eth msg from additional fields") + return nil, fmt.Errorf("failed to get synthetic eth msg from additional fields") + } } if res.EthTxIndex == -1 { // Fallback to find tx index by iterating all valid eth transactions - msgs, _ := b.EthMsgsFromTendermintBlock(block, blockRes) + msgs, _ := b.EthMsgsFromTendermintBlock(resBlock, blockRes) for i := range msgs { if msgs[i].Hash == hexTx { // #nosec G701 always in range @@ -82,6 +91,7 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac } } } + // if we still unable to find the eth tx index, return error, shouldn't happen. if res.EthTxIndex == -1 && additional == nil { return nil, errors.New("can't find index of ethereum tx") @@ -103,8 +113,8 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac } return rpctypes.NewTransactionFromMsg( - msg, - common.BytesToHash(block.BlockID.Hash.Bytes()), + ethMsg, + common.BytesToHash(resBlock.BlockID.Hash.Bytes()), // #nosec G701 always positive uint64(res.Height), // #nosec G701 always positive @@ -158,7 +168,6 @@ func (b *Backend) getTransactionByHashPending(txHash common.Hash) (*rpctypes.RPC func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { hexTx := hash.Hex() b.logger.Debug("eth_getTransactionReceipt", "hash", hexTx) - res, additional, err := b.GetTxByEthHash(hash) if err != nil { b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error()) @@ -170,16 +179,26 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ b.logger.Debug("block not found", "height", res.Height, "error", err.Error()) return nil, nil } - tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex]) - if err != nil { - b.logger.Debug("decoding failed", "error", err.Error()) - return nil, fmt.Errorf("failed to decode tx: %w", err) - } var txData evmtypes.TxData var ethMsg *evmtypes.MsgEthereumTx + // if additional fields are empty we can try to get MsgEthereumTx from sdk.Msg array if additional == nil { + // #nosec G701 always in range + if int(res.TxIndex) >= len(resBlock.Block.Txs) { + b.logger.Error("tx out of bounds") + return nil, fmt.Errorf("tx out of bounds") + } + tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex]) + if err != nil { + b.logger.Debug("decoding failed", "error", err.Error()) + return nil, fmt.Errorf("failed to decode tx: %w", err) + } ethMsg = tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) + if ethMsg == nil { + b.logger.Error("failed to get eth msg") + return nil, fmt.Errorf("failed to get eth msg") + } txData, err = evmtypes.UnpackTxData(ethMsg.Data) if err != nil { @@ -187,9 +206,11 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ return nil, err } } else { - ethMsg = &evmtypes.MsgEthereumTx{ - From: additional.Sender.Hex(), - Hash: additional.Hash.Hex(), + // if additional fields are not empty try to parse synthetic tx from them + ethMsg = b.parseSyntethicTxFromAdditionalFields(additional) + if ethMsg == nil { + b.logger.Error("failed to parse tx") + return nil, fmt.Errorf("failed to parse tx") } } @@ -218,13 +239,13 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ } var from common.Address - if ethMsg.Data != nil { + if additional != nil { + from = common.HexToAddress(ethMsg.From) + } else if ethMsg.Data != nil { from, err = ethMsg.GetSender(chainID.ToInt()) if err != nil { return nil, err } - } else if additional != nil { - from = common.HexToAddress(ethMsg.From) } else { return nil, errors.New("failed to parse receipt") } @@ -247,6 +268,7 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ } } } + // return error if still unable to find the eth tx index if res.EthTxIndex == -1 { if additional != nil { @@ -321,7 +343,6 @@ func (b *Backend) GetTransactionByBlockHashAndIndex( idx hexutil.Uint, ) (*rpctypes.RPCTransaction, error) { b.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx) - sc, ok := b.clientCtx.Client.(tmrpcclient.SignClient) if !ok { return nil, errors.New("invalid rpc client") @@ -450,42 +471,16 @@ func (b *Backend) GetTransactionByBlockAndIndex( return nil, nil } - var msg *evmtypes.MsgEthereumTx - // find in tx indexer // #nosec G701 always in range - res, additional, err := b.GetTxByTxIndex(block.Block.Height, uint(idx)) - if err == nil { - tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex]) - if err != nil { - b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) - return nil, nil - } - - var ok bool - // msgIndex is inferred from tx events, should be within bound. - msg, ok = tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) - if !ok { - if additional == nil { - b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) - return nil, nil - } - msg = &evmtypes.MsgEthereumTx{ - Hash: additional.Hash.Hex(), - From: additional.Sender.Hex(), - } - } - } else { - // #nosec G701 always in range - i := int(idx) - ethMsgs, _ := b.EthMsgsFromTendermintBlock(block, blockRes) - if i >= len(ethMsgs) { - b.logger.Debug("block txs index out of bound", "index", i) - return nil, nil - } - - msg = ethMsgs[i] + i := int(idx) + ethMsgs, additionals := b.EthMsgsFromTendermintBlock(block, blockRes) + if i >= len(ethMsgs) { + b.logger.Debug("block txs index out of bound", "index", i) + return nil, nil } + msg := ethMsgs[i] + additional := additionals[i] baseFee, err := b.BaseFee(blockRes) if err != nil { // handle the error for pruned node. diff --git a/rpc/types/events.go b/rpc/types/events.go index e6e9a3c994..3839fb84df 100644 --- a/rpc/types/events.go +++ b/rpc/types/events.go @@ -16,14 +16,18 @@ package types import ( + "encoding/base64" "fmt" + "math" "math/big" "strconv" + "strings" abci "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" + "github.com/ethereum/go-ethereum/common/hexutil" ethermint "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" ) @@ -68,6 +72,7 @@ const ( // ParsedTx is the tx infos parsed from events. type ParsedTx struct { + // max uint32 means there is no sdk.Msg corresponding to eth tx MsgIndex int // the following fields are parsed from events @@ -83,6 +88,8 @@ type ParsedTx struct { Amount *big.Int Recipient common.Address Sender common.Address + Nonce uint64 + Data []byte } // NewParsedTx initialize a ParsedTx @@ -177,6 +184,18 @@ func ParseTxResult(result *abci.ResponseDeliverTx, tx sdk.Tx) (*ParsedTxs, error p.Txs[i].GasUsed = gasLimit } } + + // fix msg indexes, because some eth txs indexed here don't have corresponding sdk.Msg + currMsgIndex := 0 + for _, tx := range p.Txs { + if tx.Type == CosmosEVMTxType { + tx.MsgIndex = math.MaxUint32 + // todo: fix mapping as well + } else { + tx.MsgIndex = currMsgIndex + currMsgIndex++ + } + } return p, nil } @@ -204,7 +223,7 @@ func ParseTxIndexerResult( txResult.Index, ) } - if parsedTx.Type == 88 { + if parsedTx.Type == CosmosEVMTxType { return ðermint.TxResult{ Height: txResult.Height, TxIndex: txResult.Index, @@ -251,7 +270,7 @@ func ParseTxBlockResult( return nil, nil, fmt.Errorf("ethereum tx not found in msgs: block %d, index %d", height, txIndex) } parsedTx := txs.Txs[0] - if parsedTx.Type == 88 { + if parsedTx.Type == CosmosEVMTxType { return ðermint.TxResult{ Height: height, // #nosec G701 always in range @@ -270,6 +289,8 @@ func ParseTxBlockResult( Recipient: parsedTx.Recipient, Sender: parsedTx.Sender, GasUsed: parsedTx.GasUsed, + Data: parsedTx.Data, + Nonce: parsedTx.Nonce, }, nil } return ðermint.TxResult{ @@ -353,19 +374,19 @@ func (p *ParsedTxs) AccumulativeGasUsed(msgIndex int) (result uint64) { // fillTxAttribute parse attributes by name, less efficient than hardcode the index, but more stable against event // format changes. -func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error { - switch string(key) { +func fillTxAttribute(tx *ParsedTx, key, value string) error { + switch key { case evmtypes.AttributeKeyEthereumTxHash: - tx.Hash = common.HexToHash(string(value)) + tx.Hash = common.HexToHash(value) case evmtypes.AttributeKeyTxIndex: - txIndex, err := strconv.ParseUint(string(value), 10, 31) + txIndex, err := strconv.ParseUint(value, 10, 31) if err != nil { return err } // #nosec G701 always in range tx.EthTxIndex = int32(txIndex) case evmtypes.AttributeKeyTxGasUsed: - gasUsed, err := strconv.ParseUint(string(value), 10, 64) + gasUsed, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } @@ -373,32 +394,73 @@ func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error { case evmtypes.AttributeKeyEthereumTxFailed: tx.Failed = len(value) > 0 case SenderType: - tx.Sender = common.HexToAddress(string(value)) + tx.Sender = common.HexToAddress(value) case evmtypes.AttributeKeyRecipient: - tx.Recipient = common.HexToAddress(string(value)) + tx.Recipient = common.HexToAddress(value) case evmtypes.AttributeKeyTxHash: - tx.TxHash = string(value) + tx.TxHash = value case evmtypes.AttributeKeyTxType: - txType, err := strconv.ParseUint(string(value), 10, 31) + txType, err := strconv.ParseUint(value, 10, 31) if err != nil { return err } tx.Type = txType case AmountType: var success bool - tx.Amount, success = big.NewInt(0).SetString(string(value), 10) + tx.Amount, success = big.NewInt(0).SetString(value, 10) if !success { return nil } + case evmtypes.AttributeKeyTxNonce: + nonce, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + tx.Nonce = nonce + + case evmtypes.AttributeKeyTxData: + hexBytes, err := hexutil.Decode(value) + if err != nil { + return err + } + tx.Data = hexBytes } return nil } func fillTxAttributes(tx *ParsedTx, attrs []abci.EventAttribute) error { + // before cosmos upgrade to 0.47, attributes are base64 encoded + // purpose of this is to support older txs as well + isLegacyAttrs := isLegacyAttrEncoding(attrs) for _, attr := range attrs { - if err := fillTxAttribute(tx, []byte(attr.Key), []byte(attr.Value)); err != nil { + if isLegacyAttrs { + // only decode if value can be decoded + // (error should not happen because at this point it is determined it is legacy attr) + decKey, err := base64.StdEncoding.DecodeString(attr.Key) + if err != nil { + return err + } + attr.Key = string(decKey) + decValue, err := base64.StdEncoding.DecodeString(attr.Value) + if err != nil { + return err + } + attr.Value = string(decValue) + } + + if err := fillTxAttribute(tx, attr.Key, attr.Value); err != nil { return err } } return nil } + +func isLegacyAttrEncoding(attrs []abci.EventAttribute) bool { + for _, attr := range attrs { + if strings.Contains(attr.Key, "==") || strings.Contains(attr.Value, "==") { + return true + } + } + + return false +} diff --git a/rpc/types/types.go b/rpc/types/types.go index 62d4b343a9..907eff4740 100644 --- a/rpc/types/types.go +++ b/rpc/types/types.go @@ -53,6 +53,8 @@ type TxResultAdditionalFields struct { Recipient common.Address `json:"recipient"` Sender common.Address `json:"sender"` GasUsed uint64 `json:"gasUsed"` + Nonce uint64 `json:"nonce"` + Data []byte `json:"data"` } // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction diff --git a/rpc/types/utils.go b/rpc/types/utils.go index 650a0d52ff..dfda689557 100644 --- a/rpc/types/utils.go +++ b/rpc/types/utils.go @@ -167,10 +167,10 @@ func NewTransactionFromMsg( chainID *big.Int, txAdditional *TxResultAdditionalFields, ) (*RPCTransaction, error) { - tx := msg.AsTransaction() - if tx == nil { + if txAdditional != nil { return NewRPCTransactionFromIncompleteMsg(msg, blockHash, blockNumber, index, baseFee, chainID, txAdditional) } + tx := msg.AsTransaction() return NewRPCTransaction(tx, blockHash, blockNumber, index, baseFee, chainID) } @@ -253,7 +253,7 @@ func NewRPCTransactionFromIncompleteMsg( GasPrice: (*hexutil.Big)(baseFee), Hash: common.HexToHash(msg.Hash), Input: []byte{}, - Nonce: 0, // TODO: get nonce for "from" from ethermint + Nonce: hexutil.Uint64(txAdditional.Nonce), // TODO: get nonce for "from" from ethermint To: to, Value: (*hexutil.Big)(txAdditional.Value), V: nil, diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index bb7a037c07..9a7ed00d5b 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -680,6 +680,9 @@ func (k Keeper) CallEVM( // value is the amount of wei to send; gaslimit is the custom gas limit, if nil EstimateGas is used // to bisect the correct gas limit (this may sometimes result in insufficient gas limit; not sure why) // +// noEthereumTxEvent flag is used to control if ethereum_tx eventsshould emitted +// which will mean these txs are indexed and available in rpc methods +// // returns (msg,err) the EVM execution result if there is any, even if error is non-nil due to contract reverts // Furthermore, err!=nil && msg!=nil && msg.Failed() means the contract call reverted; in which case // msg.Ret gives the RET code if contract revert with REVERT opcode with parameters. @@ -721,7 +724,6 @@ func (k Keeper) CallEVMWithData( if gasLimit != nil { gasCap = gasLimit.Uint64() } - msg := ethtypes.NewMessage( from, contract, @@ -789,6 +791,10 @@ func (k Keeper) CallEVMWithData( } if !noEthereumTxEvent { + // adding txData for more info in rpc methods in order to parse synthetic txs + attrs = append(attrs, sdk.NewAttribute(evmtypes.AttributeKeyTxData, hexutil.Encode(msg.Data()))) + // adding nonce for more info in rpc methods in order to parse synthetic txs + attrs = append(attrs, sdk.NewAttribute(evmtypes.AttributeKeyTxNonce, fmt.Sprint(nonce))) ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( evmtypes.EventTypeEthereumTx,