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:
parent
e752d80e9f
commit
514785bd89
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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"` |
|
||||
|
@ -10,6 +10,7 @@ const (
|
||||
AttributeKeyRecipient = "recipient"
|
||||
AttributeKeyTxHash = "txHash"
|
||||
AttributeKeyEthereumTxHash = "ethereumTxHash"
|
||||
AttributeKeyTxIndex = "txIndex"
|
||||
AttributeKeyTxType = "txType"
|
||||
AttributeKeyTxLog = "txLog"
|
||||
// tx failed in eth vm execution
|
||||
|
Loading…
Reference in New Issue
Block a user