Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Optimize tx index lookup in web3 rpc #810

Merged
merged 3 commits into from
Dec 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
109 changes: 79 additions & 30 deletions rpc/ethereum/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -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 {
e.logger.Debug("block not found", "height", res.Height, "error", err.Error())
return nil, nil
return nil, err
}

if len(tx.GetMsgs()) != 1 {
return nil, errors.New("invalid ethereum tx")
}

var txIndex uint64
msgs := e.GetEthereumMsgsFromTendermintBlock(resBlock)
msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
if !ok {
return nil, errors.New("invalid ethereum tx")
}

for i := range msgs {
if msgs[i].Hash == hexTx {
txIndex = uint64(i)
break
}
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, err
}

msg := msgs[txIndex]
// 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
}
}
}
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,
Expand All @@ -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()))
Expand Down Expand Up @@ -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
}
if len(tx.GetMsgs()) != 1 {
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)
ethMsg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
if !ok {
continue
}

result = append(result, ethMsg)
}

return result
Expand Down
118 changes: 70 additions & 48 deletions rpc/ethereum/namespaces/eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -309,37 +310,47 @@ 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
}

// 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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
Loading