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

Commit

Permalink
Optimize tx index lookup in web3 rpc
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yihuang committed Dec 15, 2021
1 parent 9828964 commit 8a07a3f
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 78 deletions.
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

0 comments on commit 8a07a3f

Please sign in to comment.