From 1a48e09e78a62cb8cf081513eb118d782db90536 Mon Sep 17 00:00:00 2001 From: yihuang Date: Tue, 13 Jul 2021 01:42:53 +0800 Subject: [PATCH] rpc: implement `eth_getPendingTransactions` (#259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement `eth_getPendingTransactions` Closes #244 * refactor repeatitive code * Update ethereum/rpc/namespaces/eth/api.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update ethereum/rpc/namespaces/eth/api.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update ethereum/rpc/namespaces/eth/api.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update ethereum/rpc/namespaces/eth/api.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * test UnwrapEthereumMsg Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- ethereum/rpc/namespaces/eth/api.go | 164 +++++++++++------------------ ethereum/rpc/types/utils.go | 21 ++++ tests/rpc/rpc_pending_test.go | 6 ++ x/evm/types/msg.go | 1 + x/evm/types/utils.go | 19 ++++ x/evm/types/utils_test.go | 37 ++++++- 6 files changed, 138 insertions(+), 110 deletions(-) diff --git a/ethereum/rpc/namespaces/eth/api.go b/ethereum/rpc/namespaces/eth/api.go index 729484e8..52d94dc2 100644 --- a/ethereum/rpc/namespaces/eth/api.go +++ b/ethereum/rpc/namespaces/eth/api.go @@ -612,40 +612,18 @@ func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransac } for _, tx := range txs { - if tx == nil { - return nil, fmt.Errorf("invalid tx in mempool") - } - - if len((*tx).GetMsgs()) != 1 { - continue - } - msg, ok := (*tx).GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - continue - } - - txhash := msg.AsTransaction().Hash() - if txhash != hash { - continue - } - - from, err := msg.GetSender(e.chainIDEpoch) + msg, err := evmtypes.UnwrapEthereumMsg(tx) if err != nil { - return nil, err + // not ethereum tx + continue } - data, err := evmtypes.UnpackTxData(msg.Data) - if err != nil { - return nil, fmt.Errorf("failed to unpack tx data: %w", err) - } - - rpctx, err := rpctypes.NewTransactionFromData( - data, - from, - hash, + rpctx, err := rpctypes.NewTransactionFromMsg( + msg, common.Hash{}, uint64(0), uint64(0), + e.chainIDEpoch, ) if err != nil { return nil, err @@ -666,33 +644,18 @@ func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransac return nil, fmt.Errorf("failed to decode tx: %w", err) } - if len(tx.GetMsgs()) != 1 { - e.logger.Debugln("invalid tx") - return nil, fmt.Errorf("invalid tx type: %T", tx) - } - msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - e.logger.Debugln("invalid tx") - return nil, fmt.Errorf("invalid tx type: %T", tx) - } - - from, err := msg.GetSender(e.chainIDEpoch) + msg, err := evmtypes.UnwrapEthereumMsg(&tx) if err != nil { + e.logger.WithError(err).Debugln("invalid tx") return nil, err } - data, err := evmtypes.UnpackTxData(msg.Data) - if err != nil { - return nil, fmt.Errorf("failed to unpack tx data: %w", err) - } - - return rpctypes.NewTransactionFromData( - data, - from, - hash, + return rpctypes.NewTransactionFromMsg( + msg, common.BytesToHash(resBlock.Block.Hash()), uint64(res.Height), uint64(res.Index), + e.chainIDEpoch, ) } @@ -719,31 +682,18 @@ func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexu return nil, fmt.Errorf("failed to decode tx: %w", err) } - if len(tx.GetMsgs()) != 1 { - e.logger.Debugln("invalid tx") - return nil, fmt.Errorf("invalid tx type: %T", tx) - } - msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - e.logger.Debugln("invalid tx") - return nil, fmt.Errorf("invalid tx type: %T", tx) - } - - txHash := msg.AsTransaction().Hash() - - txData, err := evmtypes.UnpackTxData(msg.Data) + msg, err := evmtypes.UnwrapEthereumMsg(&tx) if err != nil { - e.logger.WithError(err).Debugln("decoding failed") - return nil, fmt.Errorf("failed to unpack tx data: %w", err) + e.logger.WithError(err).Debugln("invalid tx") + return nil, err } - return rpctypes.NewTransactionFromData( - txData, - common.HexToAddress(msg.From), - txHash, + return rpctypes.NewTransactionFromMsg( + msg, hash, uint64(resBlock.Block.Height), uint64(idx), + e.chainIDEpoch, ) } @@ -770,31 +720,18 @@ func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockN return nil, fmt.Errorf("failed to decode tx: %w", err) } - if len(tx.GetMsgs()) != 1 { - e.logger.Debugln("invalid tx") - return nil, fmt.Errorf("invalid tx type: %T", tx) - } - msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - e.logger.Debugln("invalid tx") - return nil, fmt.Errorf("invalid tx type: %T", tx) - } - - txHash := msg.AsTransaction().Hash() - - txData, err := evmtypes.UnpackTxData(msg.Data) + msg, err := evmtypes.UnwrapEthereumMsg(&tx) if err != nil { - e.logger.WithError(err).Debugln("decoding failed") - return nil, fmt.Errorf("failed to unpack tx data: %w", err) + e.logger.WithError(err).Debugln("invalid tx") + return nil, err } - return rpctypes.NewTransactionFromData( - txData, - common.HexToAddress(msg.From), - txHash, + return rpctypes.NewTransactionFromMsg( + msg, common.BytesToHash(resBlock.Block.Hash()), uint64(resBlock.Block.Height), uint64(idx), + e.chainIDEpoch, ) } @@ -820,15 +757,10 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac return nil, fmt.Errorf("failed to decode tx: %w", err) } - if len(tx.GetMsgs()) != 1 { - e.logger.Debugln("invalid tx") - return nil, fmt.Errorf("invalid tx type: %T", tx) - } - - msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - e.logger.Debugln("invalid tx") - return nil, fmt.Errorf("invalid tx type: %T", tx) + msg, err := evmtypes.UnwrapEthereumMsg(&tx) + if err != nil { + e.logger.WithError(err).Debugln("invalid tx") + return nil, err } txData, err := evmtypes.UnpackTxData(msg.Data) @@ -911,8 +843,34 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac func (e *PublicAPI) PendingTransactions() ([]*rpctypes.RPCTransaction, error) { e.logger.Debugln("eth_getPendingTransactions") - // FIXME https://github.com/tharsis/ethermint/issues/244 - return []*rpctypes.RPCTransaction{}, nil + txs, err := e.backend.PendingTransactions() + if err != nil { + return nil, err + } + + result := make([]*rpctypes.RPCTransaction, 0, len(txs)) + for _, tx := range txs { + msg, err := evmtypes.UnwrapEthereumMsg(tx) + if err != nil { + // not valid ethereum tx + continue + } + + rpctx, err := rpctypes.NewTransactionFromMsg( + msg, + common.Hash{}, + uint64(0), + uint64(0), + e.chainIDEpoch, + ) + if err != nil { + return nil, err + } + + result = append(result, rpctx) + } + + return result, nil } // GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil. @@ -1095,16 +1053,12 @@ func (e *PublicAPI) getAccountNonce(accAddr common.Address, pending bool, height // add the uncommitted txs to the nonce counter // only supports `MsgEthereumTx` style tx for _, tx := range pendingTxs { - if tx == nil { - continue - } - if len((*tx).GetMsgs()) != 1 { - continue - } - msg, ok := (*tx).GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { + msg, err := evmtypes.UnwrapEthereumMsg(tx) + if err != nil { + // not ethereum tx continue } + sender, err := msg.GetSender(e.chainIDEpoch) if err != nil { continue diff --git a/ethereum/rpc/types/utils.go b/ethereum/rpc/types/utils.go index 7c96f027..21ed4e12 100644 --- a/ethereum/rpc/types/utils.go +++ b/ethereum/rpc/types/utils.go @@ -236,6 +236,27 @@ func ErrRevertedWith(data []byte) DataError { } } +// NewTransactionFromMsg returns a transaction that will serialize to the RPC +// representation, with the given location metadata set (if available). +func NewTransactionFromMsg( + msg *evmtypes.MsgEthereumTx, + blockHash common.Hash, + blockNumber, index uint64, + chainID *big.Int, +) (*RPCTransaction, error) { + from, err := msg.GetSender(chainID) + if err != nil { + return nil, err + } + + data, err := evmtypes.UnpackTxData(msg.Data) + if err != nil { + return nil, fmt.Errorf("failed to unpack tx data: %w", err) + } + + return NewTransactionFromData(data, from, msg.AsTransaction().Hash(), blockHash, blockNumber, index) +} + // NewTransactionFromData returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func NewTransactionFromData( diff --git a/tests/rpc/rpc_pending_test.go b/tests/rpc/rpc_pending_test.go index 656e0051..1645f382 100644 --- a/tests/rpc/rpc_pending_test.go +++ b/tests/rpc/rpc_pending_test.go @@ -282,6 +282,12 @@ func TestEth_Pending_GetTransactionByHash(t *testing.T) { err = json.Unmarshal(rpcRes.Result, &pendingBlockTx) require.NoError(t, err) + txsRes := Call(t, "eth_getPendingTransactions", []interface{}{}) + var pendingTxs []map[string]interface{} + err = json.Unmarshal(txsRes.Result, &pendingTxs) + require.NoError(t, err) + require.NotEmpty(t, pendingTxs) + // verify the pending tx has all the correct fields from the tx sent. require.NotEmpty(t, pendingBlockTx) require.NotEmpty(t, pendingBlockTx["hash"]) diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index 20b86eb0..f45584f8 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -37,6 +37,7 @@ func NewTx( gasLimit uint64, gasPrice *big.Int, input []byte, accesses *ethtypes.AccessList, ) *MsgEthereumTx { return newMsgEthereumTx(chainID, nonce, to, amount, gasLimit, gasPrice, input, accesses) + } // NewTxContract returns a reference to a new Ethereum transaction diff --git a/x/evm/types/utils.go b/x/evm/types/utils.go index 0d87480a..b673e4ef 100644 --- a/x/evm/types/utils.go +++ b/x/evm/types/utils.go @@ -1,6 +1,8 @@ package types import ( + "fmt" + log "github.com/xlab/suplog" "github.com/gogo/protobuf/proto" @@ -50,3 +52,20 @@ func DecodeTransactionLogs(data []byte) (TransactionLogs, error) { } return logs, nil } + +// UnwrapEthereumMsg extract MsgEthereumTx from wrapping sdk.Tx +func UnwrapEthereumMsg(tx *sdk.Tx) (*MsgEthereumTx, error) { + if tx == nil { + return nil, fmt.Errorf("invalid tx: nil") + } + + if len((*tx).GetMsgs()) != 1 { + return nil, fmt.Errorf("invalid tx type: %T", tx) + } + msg, ok := (*tx).GetMsgs()[0].(*MsgEthereumTx) + if !ok { + return nil, fmt.Errorf("invalid tx type: %T", tx) + } + + return msg, nil +} diff --git a/x/evm/types/utils_test.go b/x/evm/types/utils_test.go index df4dcd70..9a0d2680 100644 --- a/x/evm/types/utils_test.go +++ b/x/evm/types/utils_test.go @@ -1,11 +1,17 @@ -package types +package types_test import ( + "math/big" "testing" + "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" proto "github.com/gogo/protobuf/proto" + "github.com/tharsis/ethermint/app" "github.com/tharsis/ethermint/crypto/ethsecp256k1" + "github.com/tharsis/ethermint/encoding" + evmtypes "github.com/tharsis/ethermint/x/evm/types" "github.com/stretchr/testify/require" @@ -27,9 +33,9 @@ func GenerateEthAddress() ethcmn.Address { func TestEvmDataEncoding(t *testing.T) { ret := []byte{0x5, 0x8} - data := &MsgEthereumTxResponse{ + data := &evmtypes.MsgEthereumTxResponse{ Hash: common.BytesToHash([]byte("hash")).String(), - Logs: []*Log{{ + Logs: []*evmtypes.Log{{ Data: []byte{1, 2, 3, 4}, BlockNumber: 17, }}, @@ -40,15 +46,36 @@ func TestEvmDataEncoding(t *testing.T) { require.NoError(t, err) txData := &sdk.TxMsgData{ - Data: []*sdk.MsgData{{MsgType: TypeMsgEthereumTx, Data: enc}}, + Data: []*sdk.MsgData{{MsgType: evmtypes.TypeMsgEthereumTx, Data: enc}}, } txDataBz, err := proto.Marshal(txData) require.NoError(t, err) - res, err := DecodeTxResponse(txDataBz) + res, err := evmtypes.DecodeTxResponse(txDataBz) require.NoError(t, err) require.NotNil(t, res) require.Equal(t, data.Logs, res.Logs) require.Equal(t, ret, res.Ret) } + +func TestUnwrapEthererumMsg(t *testing.T) { + _, err := evmtypes.UnwrapEthereumMsg(nil) + require.NotNil(t, err) + + encodingConfig := encoding.MakeConfig(app.ModuleBasics) + clientCtx := client.Context{}.WithTxConfig(encodingConfig.TxConfig) + builder, _ := clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) + + tx := builder.GetTx().(sdk.Tx) + _, err = evmtypes.UnwrapEthereumMsg(&tx) + require.NotNil(t, err) + + msg := evmtypes.NewTx(big.NewInt(1), 0, &common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte{}, nil) + err = builder.SetMsgs(msg) + + tx = builder.GetTx().(sdk.Tx) + msg_, err := evmtypes.UnwrapEthereumMsg(&tx) + require.Nil(t, err) + require.Equal(t, msg_, msg) +}