From 0113b4d2c0156972aed041b43123f6877228343e Mon Sep 17 00:00:00 2001 From: yihuang Date: Wed, 30 Jun 2021 17:35:11 +0800 Subject: [PATCH] fix: return ethereum-formatted tx hash to client (#202) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * return eth tx hash to client Closes #67 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> change GetTxByEthHash to method add entry to changelog * use eth tx hash internally --- CHANGELOG.md | 1 + app/app.go | 5 ++-- ethereum/rpc/backend/backend.go | 19 +++++------- ethereum/rpc/namespaces/eth/api.go | 48 +++++++++++++++++------------- x/evm/keeper/keeper.go | 27 +++++++++++++---- x/evm/keeper/state_transition.go | 5 ++-- x/evm/keeper/statedb.go | 24 ++------------- x/evm/keeper/statedb_test.go | 13 ++------ x/evm/types/key.go | 2 ++ 9 files changed, 69 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3c4dcbf..6716a57a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * The `ContractAddress`, `Bloom` have been removed from the `MsgEthereumTxResponse` and the response now contains the ethereum-formatted `Hash` in hex format. * (eth) [\#845](https://github.com/cosmos/ethermint/pull/845) The `eth` namespace must be included in the list of API's as default to run the rpc server without error. +* (evm) [#202](https://github.com/tharsis/ethermint/pull/202) Web3 api `SendTransaction`/`SendRawTransaction` returns ethereum compatible transaction hash, and query api `GetTransaction*` also accept that. ### Improvements diff --git a/app/app.go b/app/app.go index 6a1ffbfc..1432c380 100644 --- a/app/app.go +++ b/app/app.go @@ -247,14 +247,13 @@ func NewEthermintApp( appCodec := encodingConfig.Marshaler cdc := encodingConfig.Amino interfaceRegistry := encodingConfig.InterfaceRegistry - txDecoder := encodingConfig.TxConfig.TxDecoder() // NOTE we use custom transaction decoder that supports the sdk.Tx interface instead of sdk.StdTx bApp := baseapp.NewBaseApp( appName, logger, db, - txDecoder, + encodingConfig.TxConfig.TxDecoder(), baseAppOptions..., ) bApp.SetCommitMultiStoreTracer(traceStore) @@ -337,7 +336,7 @@ func NewEthermintApp( // Create Ethermint keepers app.EvmKeeper = evmkeeper.NewKeeper( - appCodec, txDecoder, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], app.GetSubspace(evmtypes.ModuleName), + appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], app.GetSubspace(evmtypes.ModuleName), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, bApp.Trace(), // debug EVM based on Baseapp options ) diff --git a/ethereum/rpc/backend/backend.go b/ethereum/rpc/backend/backend.go index 075ed581..b4025389 100644 --- a/ethereum/rpc/backend/backend.go +++ b/ethereum/rpc/backend/backend.go @@ -138,23 +138,20 @@ func (e *EVMBackend) EthBlockFromTendermint( ethRPCTxs := []interface{}{} for i, txBz := range block.Txs { - // TODO: use msg.AsTransaction.Hash() for txHash once hashing is fixed on Tendermint - // https://github.com/tendermint/tendermint/issues/6539 - hash := common.BytesToHash(txBz.Hash()) - tx, gas := types.DecodeTx(e.clientCtx, txBz) gasUsed += gas msg, isEthTx := tx.(*evmtypes.MsgEthereumTx) - if fullTx { - if !isEthTx { - // TODO: eventually support Cosmos txs in the block - continue - } + if !isEthTx { + // TODO: eventually support Cosmos txs in the block + continue + } - tx, err := types.NewTransactionFromData( + hash := msg.AsTransaction().Hash() + if fullTx { + ethTx, err := types.NewTransactionFromData( msg.Data, common.HexToAddress(msg.From), hash, @@ -163,7 +160,7 @@ func (e *EVMBackend) EthBlockFromTendermint( uint64(i), ) - ethRPCTxs = append(ethRPCTxs, tx) + ethRPCTxs = append(ethRPCTxs, ethTx) if err != nil { e.logger.WithError(err).Debugln("NewTransactionFromData for receipt failed", "hash", hash.Hex) diff --git a/ethereum/rpc/namespaces/eth/api.go b/ethereum/rpc/namespaces/eth/api.go index 70538bfd..746aa5f6 100644 --- a/ethereum/rpc/namespaces/eth/api.go +++ b/ethereum/rpc/namespaces/eth/api.go @@ -21,7 +21,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" abci "github.com/tendermint/tendermint/abci/types" - tmtypes "github.com/tendermint/tendermint/types" + tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" @@ -415,10 +415,7 @@ func (e *PublicAPI) SendTransaction(args rpctypes.SendTxArgs) (common.Hash, erro return common.Hash{}, err } - // TODO: use msg.AsTransaction.Hash() for txHash once hashing is fixed on Tendermint - // https://github.com/tendermint/tendermint/issues/6539 - tmTx := tmtypes.Tx(txBytes) - txHash := common.BytesToHash(tmTx.Hash()) + txHash := msg.AsTransaction().Hash() // Broadcast transaction in sync mode (default) // NOTE: If error is encountered on the node, the broadcast will not return an error @@ -488,10 +485,7 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) return common.Hash{}, err } - // TODO: use msg.AsTransaction.Hash() for txHash once hashing is fixed on Tendermint - // https://github.com/tendermint/tendermint/issues/6539 - tmTx := tmtypes.Tx(txBytes) - txHash := common.BytesToHash(tmTx.Hash()) + txHash := ethereumTx.AsTransaction().Hash() syncCtx := e.clientCtx.WithBroadcastMode(flags.BroadcastSync) rsp, err := syncCtx.BroadcastTx(txBytes) @@ -682,11 +676,26 @@ func (e *PublicAPI) GetBlockByNumber(ethBlockNum rpctypes.BlockNumber, fullTx bo return e.backend.GetBlockByNumber(ethBlockNum, fullTx) } +// GetTxByEthHash uses `/tx_query` to find transaction by ethereum tx hash +// TODO: Don't need to convert once hashing is fixed on Tendermint +// https://github.com/tendermint/tendermint/issues/6539 +func (e *PublicAPI) GetTxByEthHash(hash common.Hash) (*tmrpctypes.ResultTx, error) { + query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex()) + resTxs, err := e.clientCtx.Client.TxSearch(e.ctx, query, false, nil, nil, "") + if err != nil { + return nil, err + } + if len(resTxs.Txs) == 0 { + return nil, errors.Errorf("ethereum tx not found for hash %s", hash.Hex()) + } + return resTxs.Txs[0], nil +} + // GetTransactionByHash returns the transaction identified by hash. func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransaction, error) { e.logger.Debugln("eth_getTransactionByHash", "hash", hash.Hex()) - res, err := e.clientCtx.Client.Tx(e.ctx, hash.Bytes(), false) + res, err := e.GetTxByEthHash(hash) if err != nil { e.logger.WithError(err).Debugln("tx not found", "hash", hash.Hex()) return nil, nil @@ -714,9 +723,6 @@ func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransac return nil, fmt.Errorf("invalid tx type: %T", tx) } - // TODO: use msg.AsTransaction.Hash() for txHash once hashing is fixed on Tendermint - // https://github.com/tendermint/tendermint/issues/6539 - return rpctypes.NewTransactionFromData( msg.Data, common.HexToAddress(msg.From), @@ -750,15 +756,17 @@ func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexu return nil, fmt.Errorf("failed to decode tx: %w", err) } - msg, ok := tx.(*evmtypes.MsgEthereumTx) + 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) } - // TODO: use msg.AsTransaction.Hash() for txHash once hashing is fixed on Tendermint - // https://github.com/tendermint/tendermint/issues/6539 - txHash := common.BytesToHash(txBz.Hash()) + txHash := msg.AsTransaction().Hash() return rpctypes.NewTransactionFromData( msg.Data, @@ -803,9 +811,7 @@ func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockN return nil, fmt.Errorf("invalid tx type: %T", tx) } - // TODO: use msg.AsTransaction.Hash() for txHash once hashing is fixed on Tendermint - // https://github.com/tendermint/tendermint/issues/6539 - txHash := common.BytesToHash(txBz.Hash()) + txHash := msg.AsTransaction().Hash() return rpctypes.NewTransactionFromData( msg.Data, @@ -821,7 +827,7 @@ func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockN func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { e.logger.Debugln("eth_getTransactionReceipt", "hash", hash.Hex()) - res, err := e.clientCtx.Client.Tx(e.ctx, hash.Bytes(), false) + res, err := e.GetTxByEthHash(hash) if err != nil { e.logger.WithError(err).Debugln("tx not found", "hash", hash.Hex()) return nil, nil diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index e6c72189..a73e2118 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -21,8 +21,7 @@ import ( // to the StateDB interface. type Keeper struct { // Protobuf codec - cdc codec.BinaryCodec - txDecoder sdk.TxDecoder + cdc codec.BinaryCodec // Store key required for the EVM Prefix KVStore. It is required by: // - storing Account's Storage State // - storing Account's Code @@ -47,7 +46,7 @@ type Keeper struct { // NewKeeper generates new evm module keeper func NewKeeper( - cdc codec.BinaryCodec, txDecoder sdk.TxDecoder, + cdc codec.BinaryCodec, storeKey, transientKey sdk.StoreKey, paramSpace paramtypes.Subspace, ak types.AccountKeeper, bankKeeper types.BankKeeper, sk types.StakingKeeper, debug bool, @@ -66,7 +65,6 @@ func NewKeeper( // NOTE: we pass in the parameter space to the CommitStateDB in order to use custom denominations for the EVM operations return &Keeper{ cdc: cdc, - txDecoder: txDecoder, paramSpace: paramSpace, accountKeeper: ak, bankKeeper: bankKeeper, @@ -152,10 +150,27 @@ func (k Keeper) SetBlockBloomTransient(bloom *big.Int) { // Tx // ---------------------------------------------------------------------------- +// GetTxHashTransient returns the hash of current processing transaction +func (k Keeper) GetTxHashTransient() common.Hash { + store := k.ctx.TransientStore(k.transientKey) + bz := store.Get(types.KeyPrefixTransientTxHash) + if len(bz) == 0 { + return common.Hash{} + } + + return common.BytesToHash(bz) +} + +// SetTxHashTransient set the hash of processing transaction +func (k Keeper) SetTxHashTransient(hash common.Hash) { + store := k.ctx.TransientStore(k.transientKey) + store.Set(types.KeyPrefixTransientTxHash, hash.Bytes()) +} + // GetTxIndexTransient returns EVM transaction index on the current block. func (k Keeper) GetTxIndexTransient() uint64 { store := k.ctx.TransientStore(k.transientKey) - bz := store.Get(types.KeyPrefixTransientBloom) + bz := store.Get(types.KeyPrefixTransientTxIndex) if len(bz) == 0 { return 0 } @@ -168,7 +183,7 @@ func (k Keeper) GetTxIndexTransient() uint64 { func (k Keeper) IncreaseTxIndexTransient() { txIndex := k.GetTxIndexTransient() store := k.ctx.TransientStore(k.transientKey) - store.Set(types.KeyPrefixTransientBloom, sdk.Uint64ToBigEndian(txIndex+1)) + store.Set(types.KeyPrefixTransientTxIndex, sdk.Uint64ToBigEndian(txIndex+1)) } // ResetRefundTransient resets the available refund amount to 0 diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index c57b983e..4fbcc344 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -128,6 +128,7 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT evm := k.NewEVM(msg, ethCfg) + k.SetTxHashTransient(tx.Hash()) k.IncreaseTxIndexTransient() // create an ethereum StateTransition instance and run TransitionDb @@ -136,9 +137,7 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT return nil, stacktrace.Propagate(err, "failed to apply ethereum core message") } - // NOTE: we set up the transaction hash from tendermint as it is the format expected by the application: - // Remove once hashing is fixed on Tendermint. See https://github.com/tendermint/tendermint/issues/6539 - txHash := common.BytesToHash(tmtypes.Tx(k.ctx.TxBytes()).Hash()) + txHash := tx.Hash() res.Hash = txHash.Hex() logs := k.GetTxLogs(txHash) diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index 63cf4706..0d909b8e 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -10,8 +10,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - tmtypes "github.com/tendermint/tendermint/types" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -588,35 +586,19 @@ func (k *Keeper) RevertToSnapshot(_ int) {} // context. This function also fills in the tx hash, block hash, tx index and log index fields before setting the log // to store. func (k *Keeper) AddLog(log *ethtypes.Log) { - tx, err := k.txDecoder(k.ctx.TxBytes()) - if err != nil { - // safety check, should be checked when processing the tx - panic(err) - } - // NOTE: tx length checked on AnteHandler - ethTx, ok := tx.GetMsgs()[0].(*types.MsgEthereumTx) - if !ok { - panic("invalid ethereum tx") - } - - // NOTE: we set up the transaction hash from tendermint as it is the format expected by the application: - // Remove once hashing is fixed on Tendermint. See https://github.com/tendermint/tendermint/issues/6539 - key := common.BytesToHash(tmtypes.Tx(k.ctx.TxBytes()).Hash()) - log.TxHash = common.HexToHash(ethTx.Hash) - log.BlockHash = common.BytesToHash(k.ctx.HeaderHash()) log.TxIndex = uint(k.GetTxIndexTransient()) + log.TxHash = k.GetTxHashTransient() - logs := k.GetTxLogs(key) + logs := k.GetTxLogs(log.TxHash) log.Index = uint(len(logs)) logs = append(logs, log) - k.SetLogs(key, logs) + k.SetLogs(log.TxHash, logs) k.Logger(k.ctx).Debug( "log added", - "tx-hash-tendermint", key.Hex(), "tx-hash-ethereum", log.TxHash.Hex(), "log-index", int(log.Index), ) diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index ce49315c..8496f7ab 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -10,7 +10,6 @@ import ( authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - tmtypes "github.com/tendermint/tendermint/types" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -483,15 +482,8 @@ func (suite *KeeperTestSuite) TestAddLog() { msg.From = addr.Hex() tx := suite.CreateTestTx(msg, privKey) - txBz, err := suite.clientCtx.TxConfig.TxEncoder()(tx) - suite.Require().NoError(err) - tmHash := common.BytesToHash(tmtypes.Tx(txBz).Hash()) - msg, _ = tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - ethTx := msg.AsTransaction() - txHash := ethTx.Hash() - - suite.app.EvmKeeper.WithContext(suite.ctx.WithTxBytes(txBz)) + txHash := msg.AsTransaction().Hash() testCases := []struct { name string @@ -501,7 +493,7 @@ func (suite *KeeperTestSuite) TestAddLog() { }{ { "tx hash from message", - tmHash, + txHash, ðtypes.Log{ Address: addr, }, @@ -518,6 +510,7 @@ func (suite *KeeperTestSuite) TestAddLog() { tc.malleate() prev := suite.app.EvmKeeper.GetTxLogs(tc.hash) + suite.app.EvmKeeper.SetTxHashTransient(tc.hash) suite.app.EvmKeeper.AddLog(tc.log) post := suite.app.EvmKeeper.GetTxLogs(tc.hash) diff --git a/x/evm/types/key.go b/x/evm/types/key.go index 1e7d51d8..a0f52a89 100644 --- a/x/evm/types/key.go +++ b/x/evm/types/key.go @@ -42,6 +42,7 @@ const ( prefixTransientRefund prefixTransientAccessListAddress prefixTransientAccessListSlot + prefixTransientTxHash ) // KVStore key prefixes @@ -63,6 +64,7 @@ var ( KeyPrefixTransientRefund = []byte{prefixTransientRefund} KeyPrefixTransientAccessListAddress = []byte{prefixTransientAccessListAddress} KeyPrefixTransientAccessListSlot = []byte{prefixTransientAccessListSlot} + KeyPrefixTransientTxHash = []byte{prefixTransientTxHash} ) // BloomKey defines the store key for a block Bloom