rpc: optimize tx index lookup (#810)

Closes: #760

Solution:
- emit tx index to cosmos events
- rpc side try to use the events, but fallback to heavier approach when fails.

Update rpc/ethereum/namespaces/eth/api.go

changelog

fix lint

fix TxIndexFromEvents

fix

Update rpc/ethereum/backend/backend.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
yihuang 2021-12-17 06:30:22 +08:00 committed by GitHub
parent e752d80e9f
commit 514785bd89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 182 additions and 79 deletions

View File

@ -65,6 +65,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (app) [tharsis#794](https://github.com/tharsis/ethermint/pull/794) Setup in-place store migrators.
* (ci) [tharsis#784](https://github.com/tharsis/ethermint/pull/784) Enable automatic backport of PRs.
* (rpc) [tharsis#786](https://github.com/tharsis/ethermint/pull/786) Improve error message of `SendTransaction`/`SendRawTransaction` JSON-RPC APIs.
* (rpc) [tharsis#810](https://github.com/tharsis/ethermint/pull/810) Optimize tx index lookup in web3 rpc
### Bug Fixes

View File

@ -73,6 +73,7 @@ type Backend interface {
GetCoinbase() (sdk.AccAddress, error)
GetTransactionByHash(txHash common.Hash) (*types.RPCTransaction, error)
GetTxByEthHash(txHash common.Hash) (*tmrpctypes.ResultTx, error)
GetTxByTxIndex(height int64, txIndex uint) (*tmrpctypes.ResultTx, error)
EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error)
BaseFee(height int64) (*big.Int, error)
@ -83,7 +84,7 @@ type Backend interface {
GetFilteredBlocks(from int64, to int64, filter [][]filters.BloomIV, filterAddresses bool) ([]int64, error)
ChainConfig() *params.ChainConfig
SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error)
GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock) []*evmtypes.MsgEthereumTx
GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx
}
var _ Backend = (*EVMBackend)(nil)
@ -688,27 +689,57 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
return nil, nil
}
resBlock, err := e.clientCtx.Client.Block(e.ctx, &res.Height)
if res.TxResult.Code != 0 {
return nil, errors.New("invalid ethereum tx")
}
tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx)
if err != nil {
return nil, err
}
if len(tx.GetMsgs()) != 1 {
return nil, errors.New("invalid ethereum tx")
}
msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
if !ok {
return nil, errors.New("invalid ethereum tx")
}
block, err := e.clientCtx.Client.Block(e.ctx, &res.Height)
if err != nil {
e.logger.Debug("block not found", "height", res.Height, "error", err.Error())
return nil, nil
return nil, err
}
var txIndex uint64
msgs := e.GetEthereumMsgsFromTendermintBlock(resBlock)
for i := range msgs {
if msgs[i].Hash == hexTx {
txIndex = uint64(i)
break
// Try to find txIndex from events
found := false
txIndex, err := types.TxIndexFromEvents(res.TxResult.Events)
if err == nil {
found = true
} else {
// Fallback to find tx index by iterating all valid eth transactions
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
if err != nil {
return nil, nil
}
msgs := e.GetEthereumMsgsFromTendermintBlock(block, blockRes)
for i := range msgs {
if msgs[i].Hash == hexTx {
txIndex = uint64(i)
found = true
break
}
}
}
msg := msgs[txIndex]
if !found {
return nil, errors.New("can't find index of ethereum tx")
}
return types.NewTransactionFromMsg(
msg,
common.BytesToHash(resBlock.Block.Hash()),
common.BytesToHash(block.BlockID.Hash.Bytes()),
uint64(res.Height),
txIndex,
e.chainID,
@ -730,6 +761,22 @@ func (e *EVMBackend) GetTxByEthHash(hash common.Hash) (*tmrpctypes.ResultTx, err
return resTxs.Txs[0], nil
}
// GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs
func (e *EVMBackend) GetTxByTxIndex(height int64, index uint) (*tmrpctypes.ResultTx, error) {
query := fmt.Sprintf("tx.height=%d AND %s.%s=%d",
height, evmtypes.TypeMsgEthereumTx,
evmtypes.AttributeKeyTxIndex, index,
)
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 block %d index %d", height, index)
}
return resTxs.Txs[0], nil
}
func (e *EVMBackend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) {
// Look up the wallet containing the requested signer
_, err := e.clientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.From.Bytes()))
@ -1021,32 +1068,34 @@ BLOCKS:
// GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block.
// It also ensures consistency over the correct txs indexes across RPC endpoints
func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock) []*evmtypes.MsgEthereumTx {
func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx {
// nolint: prealloc
var result []*evmtypes.MsgEthereumTx
for _, tx := range block.Block.Txs {
txResults := blockRes.TxsResults
for i, tx := range block.Block.Txs {
// check tx exists on EVM by cross checking with blockResults
if txResults[i].Code != 0 {
e.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
continue
}
tx, err := e.clientCtx.TxConfig.TxDecoder()(tx)
if err != nil {
e.logger.Debug("failed to decode transaction in block", "height", block.Block.Height, "error", err.Error())
continue
}
for _, msg := range tx.GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
continue
}
hash := ethMsg.AsTransaction().Hash()
// check tx exists on EVM and has the correct block height
ethTx, err := e.GetTxByEthHash(hash)
if err != nil || ethTx.Height != block.Block.Height {
e.logger.Debug("failed to query eth tx hash", "hash", hash.Hex())
continue
}
result = append(result, ethMsg)
if len(tx.GetMsgs()) != 1 {
continue
}
ethMsg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
if !ok {
continue
}
result = append(result, ethMsg)
}
return result

View File

@ -17,6 +17,7 @@ import (
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/log"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
@ -309,18 +310,23 @@ func (e *PublicAPI) GetTransactionCount(address common.Address, blockNrOrHash rp
func (e *PublicAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
e.logger.Debug("eth_getBlockTransactionCountByHash", "hash", hash.Hex())
resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
block, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
if err != nil {
e.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
return nil
}
if resBlock.Block == nil {
if block.Block == nil {
e.logger.Debug("block not found", "hash", hash.Hex())
return nil
}
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock)
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
if err != nil {
return nil
}
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(block, blockRes)
n := hexutil.Uint(len(ethMsgs))
return &n
}
@ -328,18 +334,23 @@ func (e *PublicAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Ui
// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number.
func (e *PublicAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint {
e.logger.Debug("eth_getBlockTransactionCountByNumber", "height", blockNum.Int64())
resBlock, err := e.clientCtx.Client.Block(e.ctx, blockNum.TmHeight())
block, err := e.clientCtx.Client.Block(e.ctx, blockNum.TmHeight())
if err != nil {
e.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
return nil
}
if resBlock.Block == nil {
if block.Block == nil {
e.logger.Debug("block not found", "height", blockNum.Int64())
return nil
}
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock)
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
if err != nil {
return nil
}
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(block, blockRes)
n := hexutil.Uint(len(ethMsgs))
return &n
}
@ -664,75 +675,86 @@ func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransac
return e.backend.GetTransactionByHash(hash)
}
// getTransactionByBlockAndIndex is the common code shared by `GetTransactionByBlockNumberAndIndex` and `GetTransactionByBlockHashAndIndex`.
func (e *PublicAPI) getTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
var msg *evmtypes.MsgEthereumTx
// try /tx_search first
res, err := e.backend.GetTxByTxIndex(block.Block.Height, uint(idx))
if err == nil {
tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx)
if err != nil {
e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
return nil, nil
}
if len(tx.GetMsgs()) != 1 {
e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
return nil, nil
}
var ok bool
msg, ok = tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
if !ok {
e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
return nil, nil
}
} else {
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
if err != nil {
return nil, nil
}
i := int(idx)
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(block, blockRes)
if i >= len(ethMsgs) {
e.logger.Debug("block txs index out of bound", "index", i)
return nil, nil
}
msg = ethMsgs[i]
}
return rpctypes.NewTransactionFromMsg(
msg,
common.BytesToHash(block.Block.Hash()),
uint64(block.Block.Height),
uint64(idx),
e.chainIDEpoch,
)
}
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx)
resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
block, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
if err != nil {
e.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
return nil, nil
}
if resBlock.Block == nil {
if block.Block == nil {
e.logger.Debug("block not found", "hash", hash.Hex())
return nil, nil
}
i := int(idx)
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock)
if i >= len(ethMsgs) {
e.logger.Debug("block txs index out of bound", "index", i)
return nil, nil
}
msg := ethMsgs[i]
baseFee, err := e.backend.BaseFee(resBlock.Block.Height)
if err != nil {
return nil, err
}
return rpctypes.NewTransactionFromMsg(
msg,
hash,
uint64(resBlock.Block.Height),
uint64(idx),
baseFee,
)
return e.getTransactionByBlockAndIndex(block, idx)
}
// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
resBlock, err := e.clientCtx.Client.Block(e.ctx, blockNum.TmHeight())
block, err := e.clientCtx.Client.Block(e.ctx, blockNum.TmHeight())
if err != nil {
e.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
return nil, nil
}
if resBlock.Block == nil {
if block.Block == nil {
e.logger.Debug("block not found", "height", blockNum.Int64())
return nil, nil
}
i := int(idx)
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock)
if i >= len(ethMsgs) {
e.logger.Debug("block txs index out of bound", "index", i)
return nil, nil
}
msg := ethMsgs[i]
return rpctypes.NewTransactionFromMsg(
msg,
common.BytesToHash(resBlock.Block.Hash()),
uint64(resBlock.Block.Height),
uint64(idx),
e.chainIDEpoch,
)
return e.getTransactionByBlockAndIndex(block, idx)
}
// GetTransactionReceipt returns the transaction receipt identified by hash.
@ -801,7 +823,7 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
// get eth index based on block's txs
var txIndex uint64
msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock)
msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
for i := range msgs {
if msgs[i].Hash == hexTx {
txIndex = uint64(i)

View File

@ -4,8 +4,10 @@ import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"math/big"
"strconv"
abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
@ -244,3 +246,26 @@ func BaseFeeFromEvents(events []abci.Event) *big.Int {
}
return nil
}
// TxIndexFromEvents parses the tx index from cosmos events
func TxIndexFromEvents(events []abci.Event) (uint64, error) {
for _, event := range events {
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
for _, attr := range event.Attributes {
if bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxIndex)) {
result, err := strconv.ParseInt(string(attr.Value), 10, 64)
if err != nil {
return 0, err
}
if result < 0 {
return 0, errors.New("negative tx index")
}
return uint64(result), nil
}
}
}
return 0, errors.New("not found")
}

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmtypes "github.com/tendermint/tendermint/types"
@ -26,6 +27,7 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
sender := msg.From
tx := msg.AsTransaction()
txIndex := k.GetTxIndexTransient()
response, err := k.ApplyTransaction(tx)
if err != nil {
@ -36,6 +38,8 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
sdk.NewAttribute(sdk.AttributeKeyAmount, tx.Value().String()),
// add event for ethereum transaction hash format
sdk.NewAttribute(types.AttributeKeyEthereumTxHash, response.Hash),
// add event for index of valid ethereum tx
sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatInt(int64(txIndex), 10)),
}
if len(ctx.TxBytes()) > 0 {

View File

@ -15,6 +15,7 @@ The `x/evm` module emits the Cosmos SDK events after a state execution. The EVM
| ethereum_tx | `"contract"` | `{hex_address}` |
| ethereum_tx | `"txHash"` | `{tendermint_hex_hash}` |
| ethereum_tx | `"ethereumTxHash"` | `{hex_hash}` |
| ethereum_tx | `"txIndex"` | `{tx_index}` |
| tx_log | `"txLog"` | `{tx_log}` |
| message | `"sender"` | `{eth_address}` |
| message | `"action"` | `"ethereum"` |

View File

@ -10,6 +10,7 @@ const (
AttributeKeyRecipient = "recipient"
AttributeKeyTxHash = "txHash"
AttributeKeyEthereumTxHash = "ethereumTxHash"
AttributeKeyTxIndex = "txIndex"
AttributeKeyTxType = "txType"
AttributeKeyTxLog = "txLog"
// tx failed in eth vm execution