From 8b9abfcaecbcfe7b9c248ee586bf34ac3b634685 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Thu, 12 May 2022 14:27:19 +0800 Subject: [PATCH 01/10] Store eth tx index separately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: #1075 Solution: - run a optional indexer service - adapt the json-rpc to the more efficient query changelog changelog fix lint fix backward compatibility fix lint timeout better strconv fix linter fix package name add cli command to index old tx fix for loop indexer cmd don't have access to local rpc workaround exceed block gas limit situation add unit tests for indexer refactor polish the indexer module Update server/config/toml.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> improve comments share code between GetTxByEthHash and GetTxByIndex fix unit test Update server/indexer.go Co-authored-by: Freddy Caceres --- CHANGELOG.md | 4 + docs/api/proto-docs.md | 40 ++ proto/ethermint/types/v1/indexer.proto | 28 ++ rpc/apis.go | 28 +- rpc/backend/backend.go | 8 +- rpc/backend/backend_suite_test.go | 2 +- rpc/backend/tracing.go | 21 +- rpc/backend/tx_info.go | 239 ++++++------ rpc/namespaces/ethereum/eth/api.go | 15 +- rpc/types/events.go | 124 ++++--- rpc/types/events_test.go | 95 +---- server/config/config.go | 4 + server/config/toml.go | 3 + server/flags/flags.go | 1 + server/indexer.go | 229 ++++++++++++ server/indexer_cmd.go | 106 ++++++ server/indexer_service.go | 109 ++++++ server/indexer_test.go | 189 ++++++++++ server/json_rpc.go | 5 +- server/start.go | 37 +- server/util.go | 3 + testutil/network/util.go | 2 +- types/indexer.go | 19 + types/indexer.pb.go | 484 +++++++++++++++++++++++++ 24 files changed, 1475 insertions(+), 320 deletions(-) create mode 100644 proto/ethermint/types/v1/indexer.proto create mode 100644 server/indexer.go create mode 100644 server/indexer_cmd.go create mode 100644 server/indexer_service.go create mode 100644 server/indexer_test.go create mode 100644 types/indexer.go create mode 100644 types/indexer.pb.go diff --git a/CHANGELOG.md b/CHANGELOG.md index e62e5b4e8f..37bf53331d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (rpc) [\#1143](https://github.com/evmos/ethermint/pull/1143) Restrict unprotected txs on the node JSON-RPC configuration. * (all) [\#1137](https://github.com/evmos/ethermint/pull/1137) Rename go module to `evmos/ethermint` +### API Breaking + +- (json-rpc) [tharsis#1121](https://github.com/tharsis/ethermint/pull/1121) Store eth tx index separately + ### Improvements * (deps) [\#1147](https://github.com/evmos/ethermint/pull/1147) Bump Go version to `1.18`. diff --git a/docs/api/proto-docs.md b/docs/api/proto-docs.md index 6401c08690..f0be7b3536 100644 --- a/docs/api/proto-docs.md +++ b/docs/api/proto-docs.md @@ -79,6 +79,9 @@ - [ethermint/types/v1/account.proto](#ethermint/types/v1/account.proto) - [EthAccount](#ethermint.types.v1.EthAccount) +- [ethermint/types/v1/indexer.proto](#ethermint/types/v1/indexer.proto) + - [TxResult](#ethermint.types.v1.TxResult) + - [ethermint/types/v1/web3.proto](#ethermint/types/v1/web3.proto) - [ExtensionOptionsWeb3Tx](#ethermint.types.v1.ExtensionOptionsWeb3Tx) @@ -1134,6 +1137,43 @@ authtypes.BaseAccount type. It is compatible with the auth AccountKeeper. + + + + + + + + + + + +

Top

+ +## ethermint/types/v1/indexer.proto + + + + + +### TxResult +TxResult is the value stored in eth tx indexer + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `height` | [int64](#int64) | | the block height | +| `tx_index` | [uint32](#uint32) | | cosmos tx index | +| `msg_index` | [uint32](#uint32) | | the msg index in a batch tx | +| `eth_tx_index` | [int32](#int32) | | eth tx index | +| `failed` | [bool](#bool) | | if the eth tx is failed | +| `gas_used` | [uint64](#uint64) | | gas used by tx, if exceeds block gas limit, it's set to gas limit which is what's actually deducted by ante handler. | +| `cumulative_gas_used` | [uint64](#uint64) | | the cumulative gas used within current batch tx | + + + + + diff --git a/proto/ethermint/types/v1/indexer.proto b/proto/ethermint/types/v1/indexer.proto new file mode 100644 index 0000000000..c58799d3d1 --- /dev/null +++ b/proto/ethermint/types/v1/indexer.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; +package ethermint.types.v1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/evmos/ethermint/types"; + +// TxResult is the value stored in eth tx indexer +message TxResult { + option (gogoproto.goproto_getters) = false; + + // the block height + int64 height = 1; + // cosmos tx index + uint32 tx_index = 2; + // the msg index in a batch tx + uint32 msg_index = 3; + + // eth tx index + int32 eth_tx_index = 4; + // if the eth tx is failed + bool failed = 5; + // gas used by tx, if exceeds block gas limit, + // it's set to gas limit which is what's actually deducted by ante handler. + uint64 gas_used = 6; + // the cumulative gas used within current batch tx + uint64 cumulative_gas_used = 7; +} diff --git a/rpc/apis.go b/rpc/apis.go index eb9f2d5346..e6ca8f1186 100644 --- a/rpc/apis.go +++ b/rpc/apis.go @@ -19,6 +19,7 @@ import ( "github.com/evmos/ethermint/rpc/namespaces/ethereum/personal" "github.com/evmos/ethermint/rpc/namespaces/ethereum/txpool" "github.com/evmos/ethermint/rpc/namespaces/ethereum/web3" + ethermint "github.com/evmos/ethermint/types" rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client" ) @@ -48,6 +49,7 @@ type APICreator = func( clientCtx client.Context, tendermintWebsocketClient *rpcclient.WSClient, allowUnprotectedTxs bool, + indexer ethermint.EVMTxIndexer, ) []rpc.API // apiCreators defines the JSON-RPC API namespaces. @@ -55,8 +57,8 @@ var apiCreators map[string]APICreator func init() { apiCreators = map[string]APICreator{ - EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API { - evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs) + EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) []rpc.API { + evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer) return []rpc.API{ { Namespace: EthNamespace, @@ -72,7 +74,7 @@ func init() { }, } }, - Web3Namespace: func(*server.Context, client.Context, *rpcclient.WSClient, bool) []rpc.API { + Web3Namespace: func(*server.Context, client.Context, *rpcclient.WSClient, bool, ethermint.EVMTxIndexer) []rpc.API { return []rpc.API{ { Namespace: Web3Namespace, @@ -82,7 +84,7 @@ func init() { }, } }, - NetNamespace: func(_ *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, _ bool) []rpc.API { + NetNamespace: func(_ *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, _ bool, _ ethermint.EVMTxIndexer) []rpc.API { return []rpc.API{ { Namespace: NetNamespace, @@ -92,8 +94,8 @@ func init() { }, } }, - PersonalNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API { - evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs) + PersonalNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) []rpc.API { + evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer) return []rpc.API{ { Namespace: PersonalNamespace, @@ -103,7 +105,7 @@ func init() { }, } }, - TxPoolNamespace: func(ctx *server.Context, _ client.Context, _ *rpcclient.WSClient, _ bool) []rpc.API { + TxPoolNamespace: func(ctx *server.Context, _ client.Context, _ *rpcclient.WSClient, _ bool, _ ethermint.EVMTxIndexer) []rpc.API { return []rpc.API{ { Namespace: TxPoolNamespace, @@ -113,8 +115,8 @@ func init() { }, } }, - DebugNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API { - evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs) + DebugNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) []rpc.API { + evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer) return []rpc.API{ { Namespace: DebugNamespace, @@ -124,8 +126,8 @@ func init() { }, } }, - MinerNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API { - evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs) + MinerNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) []rpc.API { + evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer) return []rpc.API{ { Namespace: MinerNamespace, @@ -139,12 +141,12 @@ func init() { } // GetRPCAPIs returns the list of all APIs -func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool, selectedAPIs []string) []rpc.API { +func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer, selectedAPIs []string) []rpc.API { var apis []rpc.API for _, ns := range selectedAPIs { if creator, ok := apiCreators[ns]; ok { - apis = append(apis, creator(ctx, clientCtx, tmWSClient, allowUnprotectedTxs)...) + apis = append(apis, creator(ctx, clientCtx, tmWSClient, allowUnprotectedTxs, indexer)...) } else { ctx.Logger.Error("invalid namespace value", "namespace", ns) } diff --git a/rpc/backend/backend.go b/rpc/backend/backend.go index d3c68246f3..75594a130b 100644 --- a/rpc/backend/backend.go +++ b/rpc/backend/backend.go @@ -103,8 +103,8 @@ type EVMBackend interface { // Tx Info GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransaction, error) - GetTxByEthHash(txHash common.Hash) (*tmrpctypes.ResultTx, error) - GetTxByTxIndex(height int64, txIndex uint) (*tmrpctypes.ResultTx, error) + GetTxByEthHash(txHash common.Hash) (*ethermint.TxResult, error) + GetTxByTxIndex(height int64, txIndex uint) (*ethermint.TxResult, error) GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) @@ -140,10 +140,11 @@ type Backend struct { chainID *big.Int cfg config.Config allowUnprotectedTxs bool + indexer ethermint.EVMTxIndexer } // NewBackend creates a new Backend instance for cosmos and ethereum namespaces -func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context, allowUnprotectedTxs bool) *Backend { +func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) *Backend { chainID, err := ethermint.ParseChainID(clientCtx.ChainID) if err != nil { panic(err) @@ -176,5 +177,6 @@ func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context chainID: chainID, cfg: appConf, allowUnprotectedTxs: allowUnprotectedTxs, + indexer: indexer, } } diff --git a/rpc/backend/backend_suite_test.go b/rpc/backend/backend_suite_test.go index fa7efb0d8b..2d1bd2ddbf 100644 --- a/rpc/backend/backend_suite_test.go +++ b/rpc/backend/backend_suite_test.go @@ -56,7 +56,7 @@ func (suite *BackendTestSuite) SetupTest() { allowUnprotectedTxs := false - suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs) + suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, nil) suite.backend.queryClient.QueryClient = mocks.NewQueryClient(suite.T()) suite.backend.clientCtx.Client = mocks.NewClient(suite.T()) suite.backend.ctx = rpctypes.ContextWithHeight(1) diff --git a/rpc/backend/tracing.go b/rpc/backend/tracing.go index 6c54d5124d..37fa2003f2 100644 --- a/rpc/backend/tracing.go +++ b/rpc/backend/tracing.go @@ -32,23 +32,14 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi return nil, err } - parsedTxs, err := rpctypes.ParseTxResult(&transaction.TxResult) - if err != nil { - return nil, fmt.Errorf("failed to parse tx events: %s", hash.Hex()) - } - parsedTx := parsedTxs.GetTxByHash(hash) - if parsedTx == nil { - return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hash.Hex()) - } - // check tx index is not out of bound - if uint32(len(blk.Block.Txs)) < transaction.Index { - b.logger.Debug("tx index out of bounds", "index", transaction.Index, "hash", hash.String(), "height", blk.Block.Height) + if uint32(len(blk.Block.Txs)) < transaction.TxIndex { + b.logger.Debug("tx index out of bounds", "index", transaction.TxIndex, "hash", hash.String(), "height", blk.Block.Height) return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height) } var predecessors []*evmtypes.MsgEthereumTx - for _, txBz := range blk.Block.Txs[:transaction.Index] { + for _, txBz := range blk.Block.Txs[:transaction.TxIndex] { tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz) if err != nil { b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error()) @@ -64,14 +55,14 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi } } - tx, err := b.clientCtx.TxConfig.TxDecoder()(transaction.Tx) + tx, err := b.clientCtx.TxConfig.TxDecoder()(blk.Block.Txs[transaction.TxIndex]) if err != nil { b.logger.Debug("tx not found", "hash", hash) return nil, err } // add predecessor messages in current cosmos tx - for i := 0; i < parsedTx.MsgIndex; i++ { + for i := 0; i < int(transaction.MsgIndex); i++ { ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx) if !ok { continue @@ -79,7 +70,7 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi predecessors = append(predecessors, ethMsg) } - ethMessage, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx) + ethMessage, ok := tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx) if !ok { b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx)) return nil, fmt.Errorf("invalid transaction type %T", tx) diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index 4de5966144..bc54fc3b43 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -3,11 +3,14 @@ package backend import ( "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" rpctypes "github.com/evmos/ethermint/rpc/types" + ethermint "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/pkg/errors" tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -19,87 +22,42 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac hexTx := txHash.Hex() if err != nil { - // try to find tx in mempool - txs, err := b.PendingTransactions() - if err != nil { - b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error()) - return nil, nil - } - - for _, tx := range txs { - msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash) - if err != nil { - // not ethereum tx - continue - } - - if msg.Hash == hexTx { - rpctx, err := rpctypes.NewTransactionFromMsg( - msg, - common.Hash{}, - uint64(0), - uint64(0), - nil, - ) - if err != nil { - return nil, err - } - return rpctx, nil - } - } - - b.logger.Debug("tx not found", "hash", hexTx) - return nil, nil + return b.getTransactionByHashPending(txHash) } - if !TxSuccessOrExceedsBlockGasLimit(&res.TxResult) { - return nil, errors.New("invalid ethereum tx") - } - - parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult) + block, err := b.GetTendermintBlockByNumber(rpctypes.BlockNumber(res.Height)) if err != nil { - return nil, fmt.Errorf("failed to parse tx events: %s", hexTx) - } - - parsedTx := parsedTxs.GetTxByHash(txHash) - if parsedTx == nil { - return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx) + return nil, err } - tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx) + tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex]) if err != nil { return nil, err } - // the `msgIndex` is inferred from tx events, should be within the bound. - msg, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx) + // the `res.MsgIndex` is inferred from tx index, should be within the bound. + msg, ok := tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) if !ok { return nil, errors.New("invalid ethereum tx") } - block, err := b.clientCtx.Client.Block(b.ctx, &res.Height) - if err != nil { - b.logger.Debug("block not found", "height", res.Height, "error", err.Error()) - return nil, err - } - blockRes, err := b.GetTendermintBlockResultByNumber(&block.Block.Height) if err != nil { b.logger.Debug("block result not found", "height", block.Block.Height, "error", err.Error()) return nil, nil } - if parsedTx.EthTxIndex == -1 { + if res.EthTxIndex == -1 { // Fallback to find tx index by iterating all valid eth transactions msgs := b.GetEthereumMsgsFromTendermintBlock(block, blockRes) for i := range msgs { if msgs[i].Hash == hexTx { - parsedTx.EthTxIndex = int64(i) + res.EthTxIndex = int32(i) break } } } - if parsedTx.EthTxIndex == -1 { + if res.EthTxIndex == -1 { return nil, errors.New("can't find index of ethereum tx") } @@ -113,11 +71,47 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac msg, common.BytesToHash(block.BlockID.Hash.Bytes()), uint64(res.Height), - uint64(parsedTx.EthTxIndex), + uint64(res.EthTxIndex), baseFee, ) } +// getTransactionByHashPending find pending tx from mempool +func (b *Backend) getTransactionByHashPending(txHash common.Hash) (*rpctypes.RPCTransaction, error) { + hexTx := txHash.Hex() + // try to find tx in mempool + txs, err := b.PendingTransactions() + if err != nil { + b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error()) + return nil, nil + } + + for _, tx := range txs { + msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash) + if err != nil { + // not ethereum tx + continue + } + + if msg.Hash == hexTx { + rpctx, err := rpctypes.NewTransactionFromMsg( + msg, + common.Hash{}, + uint64(0), + uint64(0), + nil, + ) + if err != nil { + return nil, err + } + return rpctx, nil + } + } + + b.logger.Debug("tx not found", "hash", hexTx) + return nil, nil +} + // GetTransactionReceipt returns the transaction receipt identified by hash. func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { hexTx := hash.Hex() @@ -129,44 +123,17 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ return nil, nil } - // don't ignore the txs which exceed block gas limit. - if !TxSuccessOrExceedsBlockGasLimit(&res.TxResult) { - return nil, nil - } - - parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult) - if err != nil { - return nil, fmt.Errorf("failed to parse tx events: %s, %v", hexTx, err) - } - - parsedTx := parsedTxs.GetTxByHash(hash) - if parsedTx == nil { - return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx) - } - - resBlock, err := b.clientCtx.Client.Block(b.ctx, &res.Height) + resBlock, err := b.GetTendermintBlockByNumber(rpctypes.BlockNumber(res.Height)) if err != nil { b.logger.Debug("block not found", "height", res.Height, "error", err.Error()) return nil, nil } - - tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx) + tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex]) if err != nil { b.logger.Debug("decoding failed", "error", err.Error()) return nil, fmt.Errorf("failed to decode tx: %w", err) } - - if res.TxResult.Code != 0 { - // tx failed, we should return gas limit as gas used, because that's how the fee get deducted. - for i := 0; i <= parsedTx.MsgIndex; i++ { - gasLimit := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx).GetGas() - parsedTxs.Txs[i].GasUsed = gasLimit - } - } - - // the `msgIndex` is inferred from tx events, should be within the bound, - // and the tx is found by eth tx hash, so the msg type must be correct. - ethMsg := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx) + ethMsg := tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) txData, err := evmtypes.UnpackTxData(ethMsg.Data) if err != nil { @@ -180,42 +147,45 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ b.logger.Debug("failed to retrieve block results", "height", res.Height, "error", err.Error()) return nil, nil } - for i := 0; i < int(res.Index) && i < len(blockRes.TxsResults); i++ { - cumulativeGasUsed += uint64(blockRes.TxsResults[i].GasUsed) + for _, txResult := range blockRes.TxsResults[0:res.TxIndex] { + cumulativeGasUsed += uint64(txResult.GasUsed) } - cumulativeGasUsed += parsedTxs.AccumulativeGasUsed(parsedTx.MsgIndex) + cumulativeGasUsed += res.CumulativeGasUsed - // Get the transaction result from the log var status hexutil.Uint - if res.TxResult.Code != 0 || parsedTx.Failed { + if res.Failed { status = hexutil.Uint(ethtypes.ReceiptStatusFailed) } else { status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful) } - from, err := ethMsg.GetSender(b.chainID) + chainID, err := b.ChainID() + if err != nil { + return nil, err + } + + from, err := ethMsg.GetSender(chainID.ToInt()) if err != nil { return nil, err } // parse tx logs from events - logs, err := parsedTx.ParseTxLogs() + logs, err := TxLogsFromEvents(blockRes.TxsResults[res.TxIndex].Events, int(res.MsgIndex)) if err != nil { - b.logger.Debug("failed to parse logs", "hash", hexTx, "error", err.Error()) + b.logger.Debug("logs not found", "hash", hexTx, "error", err.Error()) } - if parsedTx.EthTxIndex == -1 { + if res.EthTxIndex == -1 { // Fallback to find tx index by iterating all valid eth transactions msgs := b.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes) for i := range msgs { if msgs[i].Hash == hexTx { - parsedTx.EthTxIndex = int64(i) + res.EthTxIndex = int32(i) break } } } - - if parsedTx.EthTxIndex == -1 { + if res.EthTxIndex == -1 { return nil, errors.New("can't find index of ethereum tx") } @@ -230,13 +200,14 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ // They are stored in the chain database. "transactionHash": hash, "contractAddress": nil, - "gasUsed": hexutil.Uint64(parsedTx.GasUsed), + "gasUsed": hexutil.Uint64(res.GasUsed), + "type": hexutil.Uint(txData.TxType()), // Inclusion information: These fields provide information about the inclusion of the // transaction corresponding to this receipt. "blockHash": common.BytesToHash(resBlock.Block.Header.Hash()).Hex(), "blockNumber": hexutil.Uint64(res.Height), - "transactionIndex": hexutil.Uint64(parsedTx.EthTxIndex), + "transactionIndex": hexutil.Uint64(res.EthTxIndex), // sender and receiver (contract or EOA) addreses "from": from, @@ -304,32 +275,66 @@ func (b *Backend) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNum // 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 (b *Backend) GetTxByEthHash(hash common.Hash) (*tmrpctypes.ResultTx, error) { +func (b *Backend) GetTxByEthHash(hash common.Hash) (*ethermint.TxResult, error) { + if b.indexer != nil { + return b.indexer.GetByTxHash(hash) + } + + // fallback to tendermint tx indexer query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex()) - resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "") + txResult, err := b.queryTendermintTxIndexer(query, func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx { + return txs.GetTxByHash(hash) + }) if err != nil { - return nil, err + return nil, sdkerrors.Wrapf(err, "GetTxByEthHash %s", hash.Hex()) } - if len(resTxs.Txs) == 0 { - return nil, errors.Errorf("ethereum tx not found for hash %s", hash.Hex()) - } - return resTxs.Txs[0], nil + return txResult, nil } // GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs -func (b *Backend) GetTxByTxIndex(height int64, index uint) (*tmrpctypes.ResultTx, error) { +func (b *Backend) GetTxByTxIndex(height int64, index uint) (*ethermint.TxResult, error) { + if b.indexer != nil { + return b.indexer.GetByBlockAndIndex(height, int32(index)) + } + + // fallback to tendermint tx indexer query := fmt.Sprintf("tx.height=%d AND %s.%s=%d", height, evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyTxIndex, index, ) + txResult, err := b.queryTendermintTxIndexer(query, func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx { + return txs.GetTxByTxIndex(int(index)) + }) + if err != nil { + return nil, sdkerrors.Wrapf(err, "GetTxByTxIndex %d %d", height, index) + } + return txResult, nil +} + +// queryTendermintTxIndexer query tx in tendermint tx indexer +func (b *Backend) queryTendermintTxIndexer(query string, txGetter func(*rpctypes.ParsedTxs) *rpctypes.ParsedTx) (*ethermint.TxResult, error) { resTxs, err := b.clientCtx.Client.TxSearch(b.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 nil, errors.New("ethereum tx not found") + } + txResult := resTxs.Txs[0] + if !TxSuccessOrExceedsBlockGasLimit(&txResult.TxResult) { + return nil, errors.New("invalid ethereum tx") } - return resTxs.Txs[0], nil + + var tx sdk.Tx + if txResult.TxResult.Code != 0 { + // it's only needed when the tx exceeds block gas limit + tx, err = b.clientCtx.TxConfig.TxDecoder()(txResult.Tx) + if err != nil { + return nil, fmt.Errorf("invalid ethereum tx") + } + } + + return rpctypes.ParseTxIndexerResult(txResult, tx, txGetter) } // getTransactionByBlockAndIndex is the common code shared by `GetTransactionByBlockNumberAndIndex` and `GetTransactionByBlockHashAndIndex`. @@ -340,28 +345,18 @@ func (b *Backend) GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, i } var msg *evmtypes.MsgEthereumTx - // try /tx_search first + // find in tx indexer res, err := b.GetTxByTxIndex(block.Block.Height, uint(idx)) if err == nil { - tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx) + tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex]) if err != nil { b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) return nil, nil } - parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult) - if err != nil { - return nil, fmt.Errorf("failed to parse tx events: %d, %v", idx, err) - } - - parsedTx := parsedTxs.GetTxByTxIndex(int(idx)) - if parsedTx == nil { - return nil, fmt.Errorf("ethereum tx not found in msgs: %d", idx) - } - var ok bool // msgIndex is inferred from tx events, should be within bound. - msg, ok = tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx) + msg, ok = tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) if !ok { b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) return nil, nil diff --git a/rpc/namespaces/ethereum/eth/api.go b/rpc/namespaces/ethereum/eth/api.go index 571251f8f0..13ff3400f2 100644 --- a/rpc/namespaces/ethereum/eth/api.go +++ b/rpc/namespaces/ethereum/eth/api.go @@ -2,7 +2,6 @@ package eth import ( "context" - "fmt" "math/big" "github.com/ethereum/go-ethereum/signer/core/apitypes" @@ -453,23 +452,19 @@ func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, err return nil, nil } - if res.TxResult.Code != 0 { + if res.Failed { // failed, return empty logs return nil, nil } - parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult) + resBlockResult, err := e.backend.GetTendermintBlockResultByNumber(&res.Height) if err != nil { - return nil, fmt.Errorf("failed to parse tx events: %s, %v", hexTx, err) - } - - parsedTx := parsedTxs.GetTxByHash(txHash) - if parsedTx == nil { - return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx) + e.logger.Debug("block result not found", "number", res.Height, "error", err.Error()) + return nil, nil } // parse tx logs from events - return parsedTx.ParseTxLogs() + return backend.TxLogsFromEvents(resBlockResult.TxsResults[res.TxIndex].Events, int(res.MsgIndex)) } // SignTypedData signs EIP-712 conformant typed data diff --git a/rpc/types/events.go b/rpc/types/events.go index 6b74d3784a..e48fc39aa8 100644 --- a/rpc/types/events.go +++ b/rpc/types/events.go @@ -1,13 +1,15 @@ package types import ( - "encoding/json" + "fmt" "strconv" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" + ethermint "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" abci "github.com/tendermint/tendermint/abci/types" + tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" ) // EventFormat is the format version of the events. @@ -52,11 +54,9 @@ type ParsedTx struct { Hash common.Hash // -1 means uninitialized - EthTxIndex int64 + EthTxIndex int32 GasUsed uint64 Failed bool - // unparsed tx log json strings - RawLogs [][]byte } // NewParsedTx initialize a ParsedTx @@ -64,20 +64,6 @@ func NewParsedTx(msgIndex int) ParsedTx { return ParsedTx{MsgIndex: msgIndex, EthTxIndex: -1} } -// ParseTxLogs decode the raw logs into ethereum format. -func (p ParsedTx) ParseTxLogs() ([]*ethtypes.Log, error) { - logs := make([]*evmtypes.Log, 0, len(p.RawLogs)) - for _, raw := range p.RawLogs { - var log evmtypes.Log - if err := json.Unmarshal(raw, &log); err != nil { - return nil, err - } - - logs = append(logs, &log) - } - return evmtypes.LogsToEthereum(logs), nil -} - // ParsedTxs is the tx infos parsed from eth tx events. type ParsedTxs struct { // one item per message @@ -88,7 +74,7 @@ type ParsedTxs struct { // ParseTxResult parse eth tx infos from cosmos-sdk events. // It supports two event formats, the formats are described in the comments of the format constants. -func ParseTxResult(result *abci.ResponseDeliverTx) (*ParsedTxs, error) { +func ParseTxResult(result *abci.ResponseDeliverTx, tx sdk.Tx) (*ParsedTxs, error) { format := eventFormatUnknown // the index of current ethereum_tx event in format 1 or the second part of format 2 eventIndex := -1 @@ -97,40 +83,38 @@ func ParseTxResult(result *abci.ResponseDeliverTx) (*ParsedTxs, error) { TxHashes: make(map[common.Hash]int), } for _, event := range result.Events { - switch event.Type { - case evmtypes.EventTypeEthereumTx: - if format == eventFormatUnknown { - // discover the format version by inspect the first ethereum_tx event. - if len(event.Attributes) > 2 { - format = eventFormat1 - } else { - format = eventFormat2 - } + if event.Type != evmtypes.EventTypeEthereumTx { + continue + } + + if format == eventFormatUnknown { + // discover the format version by inspect the first ethereum_tx event. + if len(event.Attributes) > 2 { + format = eventFormat1 + } else { + format = eventFormat2 } + } - if len(event.Attributes) == 2 { - // the first part of format 2 + if len(event.Attributes) == 2 { + // the first part of format 2 + if err := p.newTx(event.Attributes); err != nil { + return nil, err + } + } else { + // format 1 or second part of format 2 + eventIndex++ + if format == eventFormat1 { + // append tx if err := p.newTx(event.Attributes); err != nil { return nil, err } } else { - // format 1 or second part of format 2 - eventIndex++ - if format == eventFormat1 { - // append tx - if err := p.newTx(event.Attributes); err != nil { - return nil, err - } - } else { - // the second part of format 2, update tx fields - if err := p.updateTx(eventIndex, event.Attributes); err != nil { - return nil, err - } + // the second part of format 2, update tx fields + if err := p.updateTx(eventIndex, event.Attributes); err != nil { + return nil, err } } - case evmtypes.EventTypeTxLog: - // reuse the eventIndex set by previous ethereum_tx event - p.Txs[eventIndex].RawLogs = parseRawLogs(event.Attributes) } } @@ -139,9 +123,42 @@ func ParseTxResult(result *abci.ResponseDeliverTx) (*ParsedTxs, error) { p.Txs[0].GasUsed = uint64(result.GasUsed) } + // this could only happen if tx exceeds block gas limit + if result.Code != 0 && tx != nil { + for i := 0; i < len(p.Txs); i++ { + p.Txs[i].Failed = true + + // replace gasUsed with gasLimit because that's what's actually deducted. + gasLimit := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx).GetGas() + p.Txs[i].GasUsed = gasLimit + } + } return p, nil } +// ParseTxIndexerResult parse tm tx result to a format compatible with the custom tx indexer. +func ParseTxIndexerResult(txResult *tmrpctypes.ResultTx, tx sdk.Tx, getter func(*ParsedTxs) *ParsedTx) (*ethermint.TxResult, error) { + txs, err := ParseTxResult(&txResult.TxResult, tx) + if err != nil { + return nil, fmt.Errorf("failed to parse tx events: block %d, index %d, %v", txResult.Height, txResult.Index, err) + } + + parsedTx := getter(txs) + if parsedTx == nil { + return nil, fmt.Errorf("ethereum tx not found in msgs: block %d, index %d", txResult.Height, txResult.Index) + } + + return ðermint.TxResult{ + Height: txResult.Height, + TxIndex: txResult.Index, + MsgIndex: uint32(parsedTx.MsgIndex), + EthTxIndex: parsedTx.EthTxIndex, + Failed: parsedTx.Failed, + GasUsed: parsedTx.GasUsed, + CumulativeGasUsed: txs.AccumulativeGasUsed(parsedTx.MsgIndex), + }, nil +} + // newTx parse a new tx from events, called during parsing. func (p *ParsedTxs) newTx(attrs []abci.EventAttribute) error { msgIndex := len(p.Txs) @@ -215,17 +232,17 @@ func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error { case evmtypes.AttributeKeyEthereumTxHash: tx.Hash = common.HexToHash(string(value)) case evmtypes.AttributeKeyTxIndex: - txIndex, err := strconv.ParseInt(string(value), 10, 64) + txIndex, err := strconv.ParseUint(string(value), 10, 31) if err != nil { return err } - tx.EthTxIndex = txIndex + tx.EthTxIndex = int32(txIndex) case evmtypes.AttributeKeyTxGasUsed: - gasUsed, err := strconv.ParseInt(string(value), 10, 64) + gasUsed, err := strconv.ParseUint(string(value), 10, 64) if err != nil { return err } - tx.GasUsed = uint64(gasUsed) + tx.GasUsed = gasUsed case evmtypes.AttributeKeyEthereumTxFailed: tx.Failed = len(value) > 0 } @@ -240,10 +257,3 @@ func fillTxAttributes(tx *ParsedTx, attrs []abci.EventAttribute) error { } return nil } - -func parseRawLogs(attrs []abci.EventAttribute) (logs [][]byte) { - for _, attr := range attrs { - logs = append(logs, attr.Value) - } - return logs -} diff --git a/rpc/types/events_test.go b/rpc/types/events_test.go index 68e097d38c..833084e054 100644 --- a/rpc/types/events_test.go +++ b/rpc/types/events_test.go @@ -11,11 +11,6 @@ import ( ) func TestParseTxResult(t *testing.T) { - rawLogs := [][]byte{ - []byte("{\"address\":\"0xdcC261c03cD2f33eBea404318Cdc1D9f8b78e1AD\",\"topics\":[\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\",\"0x000000000000000000000000569608516a81c0b1247310a3e0cd001046da0663\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANB/GezJGOI=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":5}"), - []byte("{\"address\":\"0x569608516A81C0B1247310A3E0CD001046dA0663\",\"topics\":[\"0xe2403640ba68fed3a2f88b7557551d1993f84b99bb10ff833f0cf8db0c5e0486\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANB/GezJGOI=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":6}"), - []byte("{\"address\":\"0x569608516A81C0B1247310A3E0CD001046dA0663\",\"topics\":[\"0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\",\"0x0000000000000000000000000000000000000000000000000000000000000001\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":7}"), - } address := "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2" txHash := common.BigToHash(big.NewInt(1)) txHash2 := common.BigToHash(big.NewInt(2)) @@ -46,11 +41,6 @@ func TestParseTxResult(t *testing.T) { {Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")}, {Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")}, }}, - {Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{ - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]}, - }}, {Type: "message", Attributes: []abci.EventAttribute{ {Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")}, {Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")}, @@ -76,7 +66,6 @@ func TestParseTxResult(t *testing.T) { EthTxIndex: 10, GasUsed: 21000, Failed: false, - RawLogs: rawLogs, }, { MsgIndex: 1, @@ -84,7 +73,6 @@ func TestParseTxResult(t *testing.T) { EthTxIndex: 11, GasUsed: 21000, Failed: true, - RawLogs: nil, }, }, }, @@ -113,11 +101,6 @@ func TestParseTxResult(t *testing.T) { {Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")}, {Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")}, }}, - {Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{ - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]}, - }}, {Type: "message", Attributes: []abci.EventAttribute{ {Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")}, {Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")}, @@ -133,7 +116,6 @@ func TestParseTxResult(t *testing.T) { EthTxIndex: 0, GasUsed: 21000, Failed: false, - RawLogs: rawLogs, }, }, }, @@ -150,11 +132,6 @@ func TestParseTxResult(t *testing.T) { {Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")}, {Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")}, }}, - {Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{ - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]}, - }}, {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ {Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())}, {Key: []byte("txIndex"), Value: []byte("0x01")}, @@ -182,11 +159,6 @@ func TestParseTxResult(t *testing.T) { {Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")}, {Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")}, }}, - {Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{ - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]}, - }}, {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ {Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())}, {Key: []byte("txIndex"), Value: []byte("10")}, @@ -243,7 +215,7 @@ func TestParseTxResult(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - parsed, err := ParseTxResult(&tc.response) + parsed, err := ParseTxResult(&tc.response, nil) if tc.expTxs == nil { require.Error(t, err) } else { @@ -252,8 +224,6 @@ func TestParseTxResult(t *testing.T) { require.Equal(t, expTx, parsed.GetTxByMsgIndex(msgIndex)) require.Equal(t, expTx, parsed.GetTxByHash(expTx.Hash)) require.Equal(t, expTx, parsed.GetTxByTxIndex(int(expTx.EthTxIndex))) - _, err := expTx.ParseTxLogs() - require.NoError(t, err) } // non-exists tx hash require.Nil(t, parsed.GetTxByHash(common.Hash{})) @@ -264,66 +234,3 @@ func TestParseTxResult(t *testing.T) { }) } } - -func TestParseTxLogs(t *testing.T) { - rawLogs := [][]byte{ - []byte("{\"address\":\"0xdcC261c03cD2f33eBea404318Cdc1D9f8b78e1AD\",\"topics\":[\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\",\"0x000000000000000000000000569608516a81c0b1247310a3e0cd001046da0663\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANB/GezJGOI=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":5}"), - []byte("{\"address\":\"0x569608516A81C0B1247310A3E0CD001046dA0663\",\"topics\":[\"0xe2403640ba68fed3a2f88b7557551d1993f84b99bb10ff833f0cf8db0c5e0486\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANB/GezJGOI=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":6}"), - []byte("{\"address\":\"0x569608516A81C0B1247310A3E0CD001046dA0663\",\"topics\":[\"0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\",\"0x0000000000000000000000000000000000000000000000000000000000000001\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":7}"), - } - address := "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2" - txHash := common.BigToHash(big.NewInt(1)) - txHash2 := common.BigToHash(big.NewInt(2)) - response := abci.ResponseDeliverTx{ - GasUsed: 21000, - Events: []abci.Event{ - {Type: "coin_received", Attributes: []abci.EventAttribute{ - {Key: []byte("receiver"), Value: []byte("ethm12luku6uxehhak02py4rcz65zu0swh7wjun6msa")}, - {Key: []byte("amount"), Value: []byte("1252860basetcro")}, - }}, - {Type: "coin_spent", Attributes: []abci.EventAttribute{ - {Key: []byte("spender"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")}, - {Key: []byte("amount"), Value: []byte("1252860basetcro")}, - }}, - {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())}, - {Key: []byte("txIndex"), Value: []byte("10")}, - {Key: []byte("amount"), Value: []byte("1000")}, - {Key: []byte("txGasUsed"), Value: []byte("21000")}, - {Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")}, - {Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")}, - }}, - {Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{ - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]}, - {Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]}, - }}, - {Type: "message", Attributes: []abci.EventAttribute{ - {Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")}, - {Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")}, - {Key: []byte("module"), Value: []byte("evm")}, - {Key: []byte("sender"), Value: []byte(address)}, - }}, - {Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())}, - {Key: []byte("txIndex"), Value: []byte("11")}, - {Key: []byte("amount"), Value: []byte("1000")}, - {Key: []byte("txGasUsed"), Value: []byte("21000")}, - {Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")}, - {Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")}, - {Key: []byte("ethereumTxFailed"), Value: []byte("contract reverted")}, - }}, - {Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{}}, - }, - } - parsed, err := ParseTxResult(&response) - require.NoError(t, err) - tx1 := parsed.GetTxByMsgIndex(0) - txLogs1, err := tx1.ParseTxLogs() - require.NoError(t, err) - require.NotEmpty(t, txLogs1) - - tx2 := parsed.GetTxByMsgIndex(1) - txLogs2, err := tx2.ParseTxLogs() - require.Empty(t, txLogs2) -} diff --git a/server/config/config.go b/server/config/config.go index a4ba48a99f..31333c1d28 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -103,6 +103,8 @@ type JSONRPCConfig struct { // AllowUnprotectedTxs restricts unprotected (non EIP155 signed) transactions to be submitted via // the node's RPC when global parameter is disabled. AllowUnprotectedTxs bool `mapstructure:"allow-unprotected-txs"` + // EnableIndexer defines if enable the custom indexer service. + EnableIndexer bool `mapstructure:"enable-indexer"` } // TLSConfig defines the certificate and matching private key for the server. @@ -202,6 +204,7 @@ func DefaultJSONRPCConfig() *JSONRPCConfig { HTTPTimeout: DefaultHTTPTimeout, HTTPIdleTimeout: DefaultHTTPIdleTimeout, AllowUnprotectedTxs: DefaultAllowUnprotectedTxs, + EnableIndexer: false, } } @@ -305,6 +308,7 @@ func GetConfig(v *viper.Viper) Config { BlockRangeCap: v.GetInt32("json-rpc.block-range-cap"), HTTPTimeout: v.GetDuration("json-rpc.http-timeout"), HTTPIdleTimeout: v.GetDuration("json-rpc.http-idle-timeout"), + EnableIndexer: v.GetBool("json-rpc.enable-indexer"), }, TLS: TLSConfig{ CertificatePath: v.GetString("tls.certificate-path"), diff --git a/server/config/toml.go b/server/config/toml.go index 93d3285add..f74a3d7ac4 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -66,6 +66,9 @@ http-idle-timeout = "{{ .JSONRPC.HTTPIdleTimeout }}" # the node's RPC when the global parameter is disabled. allow-unprotected-txs = {{ .JSONRPC.AllowUnprotectedTxs }} +# EnableIndexer enables the custom transaction indexer for the EVM (ethereum transactions). +enable-indexer = {{ .JSONRPC.EnableIndexer }} + ############################################################################### ### TLS Configuration ### ############################################################################### diff --git a/server/flags/flags.go b/server/flags/flags.go index 67fb0cc791..90a723702e 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -47,6 +47,7 @@ const ( JSONRPCHTTPTimeout = "json-rpc.http-timeout" JSONRPCHTTPIdleTimeout = "json-rpc.http-idle-timeout" JSONRPCAllowUnprotectedTxs = "json-rpc.allow-unprotected-txs" + JSONRPCEnableIndexer = "json-rpc.enable-indexer" ) // EVM flags diff --git a/server/indexer.go b/server/indexer.go new file mode 100644 index 0000000000..ba18a481ab --- /dev/null +++ b/server/indexer.go @@ -0,0 +1,229 @@ +package server + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/ethereum/go-ethereum/common" + "github.com/evmos/ethermint/rpc/backend" + rpctypes "github.com/evmos/ethermint/rpc/types" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" + + ethermint "github.com/evmos/ethermint/types" + evmtypes "github.com/evmos/ethermint/x/evm/types" +) + +const ( + KeyPrefixTxHash = 1 + KeyPrefixTxIndex = 2 + + // TxIndexKeyLength is the length of tx-index key + TxIndexKeyLength = 1 + 8 + 8 +) + +var _ ethermint.EVMTxIndexer = &KVIndexer{} + +// TxHashKey returns the key for db entry: `tx hash -> tx result struct` +func TxHashKey(hash common.Hash) []byte { + return append([]byte{KeyPrefixTxHash}, hash.Bytes()...) +} + +// TxIndexKey returns the key for db entry: `(block number, tx index) -> tx hash` +func TxIndexKey(blockNumber int64, txIndex int32) []byte { + bz1 := sdk.Uint64ToBigEndian(uint64(blockNumber)) + bz2 := sdk.Uint64ToBigEndian(uint64(txIndex)) + return append(append([]byte{KeyPrefixTxIndex}, bz1...), bz2...) +} + +func parseBlockNumberFromKey(key []byte) (int64, error) { + if len(key) != TxIndexKeyLength { + return 0, fmt.Errorf("wrong tx index key length, expect: %d, got: %d", TxIndexKeyLength, len(key)) + } + + return int64(sdk.BigEndianToUint64(key[1:9])), nil +} + +// LoadLastBlock returns the latest indexed block number, returns -1 if db is empty +func LoadLastBlock(db dbm.DB) (int64, error) { + it, err := db.ReverseIterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1}) + if err != nil { + return 0, sdkerrors.Wrap(err, "LoadLastBlock") + } + defer it.Close() + if !it.Valid() { + return -1, nil + } + return parseBlockNumberFromKey(it.Key()) +} + +// LoadFirstBlock loads the first indexed block, returns -1 if db is empty +func LoadFirstBlock(db dbm.DB) (int64, error) { + it, err := db.Iterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1}) + if err != nil { + return 0, sdkerrors.Wrap(err, "LoadFirstBlock") + } + defer it.Close() + if !it.Valid() { + return -1, nil + } + return parseBlockNumberFromKey(it.Key()) +} + +// KVIndexer implements a eth tx indexer on a KV db. +type KVIndexer struct { + db dbm.DB + logger log.Logger + clientCtx client.Context +} + +// NewKVIndexer creates the KVIndexer +func NewKVIndexer(db dbm.DB, logger log.Logger, clientCtx client.Context) *KVIndexer { + return &KVIndexer{db, logger, clientCtx} +} + +// LastIndexedBlock returns the latest indexed block number, returns -1 if db is empty +func (kv *KVIndexer) LastIndexedBlock() (int64, error) { + return LoadLastBlock(kv.db) +} + +// FirstIndexedBlock returns the first indexed block number, returns -1 if db is empty +func (kv *KVIndexer) FirstIndexedBlock() (int64, error) { + return LoadFirstBlock(kv.db) +} + +// IndexBlock index all the eth txs in a block through the following steps: +// - Iterates over all of the Txs in Block +// - Parses eth Tx infos from cosmos-sdk events for every TxResult +// - Iterates over all the messages of the Tx +// - Builds and stores a indexer.TxResult based on parsed events for every message +func (kv *KVIndexer) IndexBlock(blk *tmtypes.Block, txResults []*abci.ResponseDeliverTx) error { + height := blk.Header.Height + + batch := kv.db.NewBatch() + defer batch.Close() + + // record index of valid eth tx during the iteration + var ethTxIndex int32 + for txIndex, tx := range blk.Txs { + result := txResults[txIndex] + if !backend.TxSuccessOrExceedsBlockGasLimit(result) { + continue + } + tx, err := kv.clientCtx.TxConfig.TxDecoder()(tx) + if err != nil { + kv.logger.Error("Fail to decode tx", "err", err, "block", height, "txIndex", txIndex) + continue + } + if !isEthTx(tx) { + continue + } + + txs, err := rpctypes.ParseTxResult(result, tx) + if err != nil { + kv.logger.Error("Fail to parse event", "err", err, "block", height, "txIndex", txIndex) + continue + } + + var cumulativeGasUsed uint64 + for msgIndex, msg := range tx.GetMsgs() { + ethMsg := msg.(*evmtypes.MsgEthereumTx) + txHash := common.HexToHash(ethMsg.Hash) + + txResult := ethermint.TxResult{ + Height: height, + TxIndex: uint32(txIndex), + MsgIndex: uint32(msgIndex), + EthTxIndex: ethTxIndex, + } + if result.Code != abci.CodeTypeOK { + // exceeds block gas limit scenario, set gas used to gas limit because that's what's charged by ante handler. + // some old versions don't emit any events, so workaround here directly. + txResult.GasUsed = ethMsg.GetGas() + txResult.Failed = true + } else { + parsedTx := txs.GetTxByMsgIndex(msgIndex) + if parsedTx == nil { + kv.logger.Error("msg index not found in events", "msgIndex", msgIndex) + continue + } + if parsedTx.EthTxIndex >= 0 && parsedTx.EthTxIndex != ethTxIndex { + kv.logger.Error("eth tx index don't match", "expect", ethTxIndex, "found", parsedTx.EthTxIndex) + } + txResult.GasUsed = parsedTx.GasUsed + txResult.Failed = parsedTx.Failed + } + + cumulativeGasUsed += txResult.GasUsed + txResult.CumulativeGasUsed = cumulativeGasUsed + ethTxIndex++ + + if err := saveTxResult(kv.clientCtx.Codec, batch, txHash, &txResult); err != nil { + return sdkerrors.Wrapf(err, "IndexBlock %d", height) + } + } + } + if err := batch.Write(); err != nil { + return sdkerrors.Wrapf(err, "IndexBlock %d, write batch", blk.Height) + } + return nil +} + +// isEthTx check if the tx is an eth tx +func isEthTx(tx sdk.Tx) bool { + extTx, ok := tx.(authante.HasExtensionOptionsTx) + if !ok { + return false + } + opts := extTx.GetExtensionOptions() + if len(opts) != 1 || opts[0].GetTypeUrl() != "/ethermint.evm.v1.ExtensionOptionsEthereumTx" { + return false + } + return true +} + +// saveTxResult index the txResult into the kv db batch +func saveTxResult(codec codec.Codec, batch dbm.Batch, txHash common.Hash, txResult *ethermint.TxResult) error { + bz := codec.MustMarshal(txResult) + if err := batch.Set(TxHashKey(txHash), bz); err != nil { + return sdkerrors.Wrap(err, "set tx-hash key") + } + if err := batch.Set(TxIndexKey(txResult.Height, txResult.EthTxIndex), txHash.Bytes()); err != nil { + return sdkerrors.Wrap(err, "set tx-index key") + } + return nil +} + +// GetByTxHash finds eth tx by eth tx hash +func (kv *KVIndexer) GetByTxHash(hash common.Hash) (*ethermint.TxResult, error) { + bz, err := kv.db.Get(TxHashKey(hash)) + if err != nil { + return nil, sdkerrors.Wrapf(err, "GetByTxHash %s", hash.Hex()) + } + if len(bz) == 0 { + return nil, fmt.Errorf("tx not found, hash: %s", hash.Hex()) + } + var txKey ethermint.TxResult + if err := kv.clientCtx.Codec.Unmarshal(bz, &txKey); err != nil { + return nil, sdkerrors.Wrapf(err, "GetByTxHash %s", hash.Hex()) + } + return &txKey, nil +} + +// GetByBlockAndIndex finds eth tx by block number and eth tx index +func (kv *KVIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*ethermint.TxResult, error) { + bz, err := kv.db.Get(TxIndexKey(blockNumber, txIndex)) + if err != nil { + return nil, sdkerrors.Wrapf(err, "GetByBlockAndIndex %d %d", blockNumber, txIndex) + } + if len(bz) == 0 { + return nil, fmt.Errorf("tx not found, block: %d, eth-index: %d", blockNumber, txIndex) + } + return kv.GetByTxHash(common.BytesToHash(bz)) +} diff --git a/server/indexer_cmd.go b/server/indexer_cmd.go new file mode 100644 index 0000000000..8a90d8226d --- /dev/null +++ b/server/indexer_cmd.go @@ -0,0 +1,106 @@ +package server + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + tmnode "github.com/tendermint/tendermint/node" + sm "github.com/tendermint/tendermint/state" + tmstore "github.com/tendermint/tendermint/store" +) + +func NewIndexTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "index-eth-tx [forward|backward]", + Short: "Index historical eth txs", + Long: `Index historical eth txs, it only support two traverse direction to avoid creating gaps in the indexer db if using arbitrary block ranges: + - backward: index the blocks from the first indexed block to the earliest block in the chain. + - forward: index the blocks from the latest indexed block to latest block in the chain. + `, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + serverCtx := server.GetServerContextFromCmd(cmd) + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + cfg := serverCtx.Config + home := cfg.RootDir + logger := serverCtx.Logger + idxDB, err := OpenIndexerDB(home, server.GetAppDBBackend(serverCtx.Viper)) + if err != nil { + logger.Error("failed to open evm indexer DB", "error", err.Error()) + return err + } + indexer := NewKVIndexer(idxDB, logger.With("module", "evmindex"), clientCtx) + + // open local tendermint db, because the local rpc won't be available. + tmdb, err := tmnode.DefaultDBProvider(&tmnode.DBContext{ID: "blockstore", Config: cfg}) + if err != nil { + return err + } + blockStore := tmstore.NewBlockStore(tmdb) + + stateDB, err := tmnode.DefaultDBProvider(&tmnode.DBContext{ID: "state", Config: cfg}) + if err != nil { + return err + } + stateStore := sm.NewStore(stateDB) + + indexBlock := func(height int64) error { + blk := blockStore.LoadBlock(height) + if blk == nil { + return fmt.Errorf("block not found %d", height) + } + resBlk, err := stateStore.LoadABCIResponses(height) + if err != nil { + return err + } + if err := indexer.IndexBlock(blk, resBlk.DeliverTxs); err != nil { + return err + } + fmt.Println(height) + return nil + } + + switch args[0] { + case "backward": + first, err := indexer.FirstIndexedBlock() + if err != nil { + return err + } + if first == -1 { + return fmt.Errorf("indexer db is empty") + } + for i := first - 1; i > 0; i-- { + if err := indexBlock(i); err != nil { + return err + } + } + case "forward": + latest, err := indexer.LastIndexedBlock() + if err != nil { + return err + } + if latest == -1 { + // start from genesis if empty + latest = 0 + } + for i := latest + 1; i <= blockStore.Height(); i++ { + if err := indexBlock(i); err != nil { + return err + } + } + default: + return fmt.Errorf("unknown direction %s", args[0]) + } + + return nil + }, + } + return cmd +} diff --git a/server/indexer_service.go b/server/indexer_service.go new file mode 100644 index 0000000000..6f5bc929e0 --- /dev/null +++ b/server/indexer_service.go @@ -0,0 +1,109 @@ +package server + +import ( + "context" + "time" + + "github.com/tendermint/tendermint/libs/service" + rpcclient "github.com/tendermint/tendermint/rpc/client" + "github.com/tendermint/tendermint/types" + + ethermint "github.com/evmos/ethermint/types" +) + +const ( + Subscriber = "EVMIndexerService" + + NewBlockWaitTimeout = 60 * time.Second +) + +// EVMIndexerService indexes transactions for json-rpc service. +type EVMIndexerService struct { + service.BaseService + + txIdxr ethermint.EVMTxIndexer + client rpcclient.Client +} + +// NewEVMIndexerService returns a new service instance. +func NewEVMIndexerService( + txIdxr ethermint.EVMTxIndexer, + client rpcclient.Client, +) *EVMIndexerService { + is := &EVMIndexerService{txIdxr: txIdxr, client: client} + is.BaseService = *service.NewBaseService(nil, "EVMIndexerService", is) + return is +} + +// OnStart implements service.Service by subscribing for new blocks +// and indexing them by events. +func (eis *EVMIndexerService) OnStart() error { + ctx := context.Background() + status, err := eis.client.Status(ctx) + if err != nil { + return err + } + latestBlock := status.SyncInfo.LatestBlockHeight + newBlockSignal := make(chan struct{}, 1) + + // Use SubscribeUnbuffered here to ensure both subscriptions does not get + // canceled due to not pulling messages fast enough. Cause this might + // sometimes happen when there are no other subscribers. + blockHeadersChan, err := eis.client.Subscribe( + ctx, + Subscriber, + types.QueryForEvent(types.EventNewBlockHeader).String(), + 0) + if err != nil { + return err + } + + go func() { + for { + msg := <-blockHeadersChan + eventDataHeader := msg.Data.(types.EventDataNewBlockHeader) + if eventDataHeader.Header.Height > latestBlock { + latestBlock = eventDataHeader.Header.Height + // notify + select { + case newBlockSignal <- struct{}{}: + default: + } + } + } + }() + + lastBlock, err := eis.txIdxr.LastIndexedBlock() + if err != nil { + return err + } + if lastBlock == -1 { + lastBlock = latestBlock + } + for { + if latestBlock <= lastBlock { + // nothing to index. wait for signal of new block + select { + case <-newBlockSignal: + case <-time.After(NewBlockWaitTimeout): + } + continue + } + for i := lastBlock + 1; i <= latestBlock; i++ { + block, err := eis.client.Block(ctx, &i) + if err != nil { + eis.Logger.Error("failed to fetch block", "height", i, "err", err) + break + } + blockResult, err := eis.client.BlockResults(ctx, &i) + if err != nil { + eis.Logger.Error("failed to fetch block result", "height", i, "err", err) + break + } + if err := eis.txIdxr.IndexBlock(block.Block, blockResult.TxsResults); err != nil { + eis.Logger.Error("failed to index block", "height", i, "err", err) + } + lastBlock = blockResult.Height + } + } +} diff --git a/server/indexer_test.go b/server/indexer_test.go new file mode 100644 index 0000000000..9afd6afc64 --- /dev/null +++ b/server/indexer_test.go @@ -0,0 +1,189 @@ +package server_test + +import ( + "math/big" + "testing" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/simapp/params" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/evmos/ethermint/app" + "github.com/evmos/ethermint/crypto/ethsecp256k1" + evmenc "github.com/evmos/ethermint/encoding" + "github.com/evmos/ethermint/server" + "github.com/evmos/ethermint/tests" + "github.com/evmos/ethermint/x/evm/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + tmlog "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" +) + +func TestKVIndexer(t *testing.T) { + priv, err := ethsecp256k1.GenerateKey() + require.NoError(t, err) + from := common.BytesToAddress(priv.PubKey().Address().Bytes()) + signer := tests.NewSigner(priv) + ethSigner := ethtypes.LatestSignerForChainID(nil) + + to := common.BigToAddress(big.NewInt(1)) + tx := types.NewTx( + nil, 0, &to, big.NewInt(1000), 21000, nil, nil, nil, nil, nil, + ) + tx.From = from.Hex() + require.NoError(t, tx.Sign(ethSigner, signer)) + txHash := tx.AsTransaction().Hash() + + encodingConfig := MakeEncodingConfig() + clientCtx := client.Context{}.WithTxConfig(encodingConfig.TxConfig).WithCodec(encodingConfig.Codec) + + // build cosmos-sdk wrapper tx + tmTx, err := tx.BuildTx(clientCtx.TxConfig.NewTxBuilder(), "aphoton") + require.NoError(t, err) + txBz, err := clientCtx.TxConfig.TxEncoder()(tmTx) + require.NoError(t, err) + + // build an invalid wrapper tx + builder := clientCtx.TxConfig.NewTxBuilder() + require.NoError(t, builder.SetMsgs(tx)) + tmTx2 := builder.GetTx() + txBz2, err := clientCtx.TxConfig.TxEncoder()(tmTx2) + require.NoError(t, err) + + testCases := []struct { + name string + block *tmtypes.Block + blockResult []*abci.ResponseDeliverTx + expSuccess bool + }{ + { + "success, format 1", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + []*abci.ResponseDeliverTx{ + &abci.ResponseDeliverTx{ + Code: 0, + Events: []abci.Event{ + {Type: types.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())}, + {Key: []byte("txIndex"), Value: []byte("0")}, + {Key: []byte("amount"), Value: []byte("1000")}, + {Key: []byte("txGasUsed"), Value: []byte("21000")}, + {Key: []byte("txHash"), Value: []byte("")}, + {Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")}, + }}, + }, + }, + }, + true, + }, + { + "success, format 2", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + []*abci.ResponseDeliverTx{ + &abci.ResponseDeliverTx{ + Code: 0, + Events: []abci.Event{ + {Type: types.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())}, + {Key: []byte("txIndex"), Value: []byte("0")}, + }}, + {Type: types.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ + {Key: []byte("amount"), Value: []byte("1000")}, + {Key: []byte("txGasUsed"), Value: []byte("21000")}, + {Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")}, + {Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")}, + }}, + }, + }, + }, + true, + }, + { + "success, exceed block gas limit", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + []*abci.ResponseDeliverTx{ + &abci.ResponseDeliverTx{ + Code: 11, + Log: "out of gas in location: block gas meter; gasWanted: 21000", + Events: []abci.Event{}, + }, + }, + true, + }, + { + "fail, failed eth tx", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + []*abci.ResponseDeliverTx{ + &abci.ResponseDeliverTx{ + Code: 15, + Log: "nonce mismatch", + Events: []abci.Event{}, + }, + }, + false, + }, + { + "fail, invalid events", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + []*abci.ResponseDeliverTx{ + &abci.ResponseDeliverTx{ + Code: 0, + Events: []abci.Event{}, + }, + }, + false, + }, + { + "fail, not eth tx", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz2}}}, + []*abci.ResponseDeliverTx{ + &abci.ResponseDeliverTx{ + Code: 0, + Events: []abci.Event{}, + }, + }, + false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := dbm.NewMemDB() + indexer := server.NewKVIndexer(db, tmlog.NewNopLogger(), clientCtx) + + err = indexer.IndexBlock(tc.block, tc.blockResult) + require.NoError(t, err) + if !tc.expSuccess { + first, err := indexer.FirstIndexedBlock() + require.NoError(t, err) + require.Equal(t, int64(-1), first) + + last, err := indexer.LastIndexedBlock() + require.NoError(t, err) + require.Equal(t, int64(-1), last) + } else { + first, err := indexer.FirstIndexedBlock() + require.NoError(t, err) + require.Equal(t, tc.block.Header.Height, first) + + last, err := indexer.LastIndexedBlock() + require.NoError(t, err) + require.Equal(t, tc.block.Header.Height, last) + + res1, err := indexer.GetByTxHash(txHash) + require.NoError(t, err) + require.NotNil(t, res1) + res2, err := indexer.GetByBlockAndIndex(1, 0) + require.NoError(t, err) + require.Equal(t, res1, res2) + } + }) + } +} + +// MakeEncodingConfig creates the EncodingConfig +func MakeEncodingConfig() params.EncodingConfig { + return evmenc.MakeConfig(app.ModuleBasics) +} diff --git a/server/json_rpc.go b/server/json_rpc.go index c27399b88c..ea475dca8f 100644 --- a/server/json_rpc.go +++ b/server/json_rpc.go @@ -15,10 +15,11 @@ import ( "github.com/evmos/ethermint/rpc" "github.com/evmos/ethermint/server/config" + ethermint "github.com/evmos/ethermint/types" ) // StartJSONRPC starts the JSON-RPC server -func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEndpoint string, config config.Config) (*http.Server, chan struct{}, error) { +func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEndpoint string, config config.Config, indexer ethermint.EVMTxIndexer) (*http.Server, chan struct{}, error) { tmWsClient := ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger) logger := ctx.Logger.With("module", "geth") @@ -39,7 +40,7 @@ func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEn allowUnprotectedTxs := config.JSONRPC.AllowUnprotectedTxs rpcAPIArr := config.JSONRPC.API - apis := rpc.GetRPCAPIs(ctx, clientCtx, tmWsClient, allowUnprotectedTxs, rpcAPIArr) + apis := rpc.GetRPCAPIs(ctx, clientCtx, tmWsClient, allowUnprotectedTxs, indexer, rpcAPIArr) for _, api := range apis { if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil { diff --git a/server/start.go b/server/start.go index c4e979f309..763576f4f9 100644 --- a/server/start.go +++ b/server/start.go @@ -43,6 +43,7 @@ import ( ethdebug "github.com/evmos/ethermint/rpc/namespaces/ethereum/debug" "github.com/evmos/ethermint/server/config" srvflags "github.com/evmos/ethermint/server/flags" + ethermint "github.com/evmos/ethermint/types" ) // StartCmd runs the service passed in, either stand-alone or in-process with @@ -164,6 +165,7 @@ which accepts a path for the resulting pprof file. cmd.Flags().Bool(srvflags.JSONRPCAllowUnprotectedTxs, config.DefaultAllowUnprotectedTxs, "Allow for unprotected (non EIP155 signed) transactions to be submitted via the node's RPC when the global parameter is disabled") cmd.Flags().Int32(srvflags.JSONRPCLogsCap, config.DefaultLogsCap, "Sets the max number of results can be returned from single `eth_getLogs` query") cmd.Flags().Int32(srvflags.JSONRPCBlockRangeCap, config.DefaultBlockRangeCap, "Sets the max block range allowed for `eth_getLogs` query") + cmd.Flags().Bool(srvflags.JSONRPCEnableIndexer, false, "Enable the custom tx indexer for json-rpc") cmd.Flags().String(srvflags.EVMTracer, config.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)") cmd.Flags().Uint64(srvflags.EVMMaxTxGasWanted, config.DefaultMaxTxGasWanted, "the gas wanted for each eth tx returned in ante handler in check tx mode") @@ -322,13 +324,39 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty // Add the tx service to the gRPC router. We only need to register this // service if API or gRPC or JSONRPC is enabled, and avoid doing so in the general // case, because it spawns a new local tendermint RPC client. - if config.API.Enable || config.GRPC.Enable || config.JSONRPC.Enable { + if config.API.Enable || config.GRPC.Enable || config.JSONRPC.Enable || config.JSONRPC.EnableIndexer { clientCtx = clientCtx.WithClient(local.New(tmNode)) app.RegisterTxService(clientCtx) app.RegisterTendermintService(clientCtx) } + var indexer ethermint.EVMTxIndexer + if config.JSONRPC.EnableIndexer { + idxDB, err := OpenIndexerDB(home, server.GetAppDBBackend(ctx.Viper)) + if err != nil { + logger.Error("failed to open evm indexer DB", "error", err.Error()) + return err + } + idxLogger := ctx.Logger.With("module", "evmindex") + indexer = NewKVIndexer(idxDB, idxLogger, clientCtx) + indexerService := NewEVMIndexerService(indexer, clientCtx.Client) + indexerService.SetLogger(idxLogger) + + errCh := make(chan error) + go func() { + if err := indexerService.Start(); err != nil { + errCh <- err + } + }() + + select { + case err := <-errCh: + return err + case <-time.After(types.ServerStartTime): // assume server started successfully + } + } + var apiSrv *api.Server if config.API.Enable { genDoc, err := genDocProvider() @@ -426,7 +454,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty tmEndpoint := "/websocket" tmRPCAddr := cfg.RPC.ListenAddress - httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, config) + httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, config, indexer) if err != nil { return err } @@ -481,6 +509,11 @@ func openDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) { return dbm.NewDB("application", backendType, dataDir) } +func OpenIndexerDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) { + dataDir := filepath.Join(rootDir, "data") + return dbm.NewDB("evmindexer", backendType, dataDir) +} + func openTraceWriter(traceWriterFile string) (w io.Writer, err error) { if traceWriterFile == "" { return diff --git a/server/util.go b/server/util.go index 885f657d9e..449b4c9013 100644 --- a/server/util.go +++ b/server/util.go @@ -42,6 +42,9 @@ func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator type sdkserver.ExportCmd(appExport, defaultNodeHome), version.NewVersionCommand(), sdkserver.NewRollbackCmd(defaultNodeHome), + + // custom tx indexer command + NewIndexTxCmd(), ) } diff --git a/testutil/network/util.go b/testutil/network/util.go index 05dcbb5f31..4440aa5636 100644 --- a/testutil/network/util.go +++ b/testutil/network/util.go @@ -131,7 +131,7 @@ func startInProcess(cfg Config, val *Validator) error { tmEndpoint := "/websocket" tmRPCAddr := val.RPCAddress - val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, *val.AppConfig) + val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, *val.AppConfig, nil) if err != nil { return err } diff --git a/types/indexer.go b/types/indexer.go new file mode 100644 index 0000000000..c6103dcd7b --- /dev/null +++ b/types/indexer.go @@ -0,0 +1,19 @@ +package types + +import ( + "github.com/ethereum/go-ethereum/common" + abci "github.com/tendermint/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +// EVMTxIndexer defines the interface of custom eth tx indexer. +type EVMTxIndexer interface { + // LastIndexedBlock returns -1 if indexer db is empty + LastIndexedBlock() (int64, error) + IndexBlock(*tmtypes.Block, []*abci.ResponseDeliverTx) error + + // GetByTxHash returns nil if tx not found. + GetByTxHash(common.Hash) (*TxResult, error) + // GetByBlockAndIndex returns nil if tx not found. + GetByBlockAndIndex(int64, int32) (*TxResult, error) +} diff --git a/types/indexer.pb.go b/types/indexer.pb.go new file mode 100644 index 0000000000..aa61b1ee09 --- /dev/null +++ b/types/indexer.pb.go @@ -0,0 +1,484 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ethermint/types/v1/indexer.proto + +package types + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// TxResult is the value stored in eth tx indexer +type TxResult struct { + // the block height + Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // cosmos tx index + TxIndex uint32 `protobuf:"varint,2,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"` + // the msg index in a batch tx + MsgIndex uint32 `protobuf:"varint,3,opt,name=msg_index,json=msgIndex,proto3" json:"msg_index,omitempty"` + // eth tx index + EthTxIndex int32 `protobuf:"varint,4,opt,name=eth_tx_index,json=ethTxIndex,proto3" json:"eth_tx_index,omitempty"` + // if the eth tx is failed + Failed bool `protobuf:"varint,5,opt,name=failed,proto3" json:"failed,omitempty"` + // gas used by tx, if exceeds block gas limit, + // it's set to gas limit which is what's actually deducted by ante handler. + GasUsed uint64 `protobuf:"varint,6,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` + // the cumulative gas used within current batch tx + CumulativeGasUsed uint64 `protobuf:"varint,7,opt,name=cumulative_gas_used,json=cumulativeGasUsed,proto3" json:"cumulative_gas_used,omitempty"` +} + +func (m *TxResult) Reset() { *m = TxResult{} } +func (m *TxResult) String() string { return proto.CompactTextString(m) } +func (*TxResult) ProtoMessage() {} +func (*TxResult) Descriptor() ([]byte, []int) { + return fileDescriptor_1197e10a8be8ed28, []int{0} +} +func (m *TxResult) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TxResult) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TxResult.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TxResult) XXX_Merge(src proto.Message) { + xxx_messageInfo_TxResult.Merge(m, src) +} +func (m *TxResult) XXX_Size() int { + return m.Size() +} +func (m *TxResult) XXX_DiscardUnknown() { + xxx_messageInfo_TxResult.DiscardUnknown(m) +} + +var xxx_messageInfo_TxResult proto.InternalMessageInfo + +func init() { + proto.RegisterType((*TxResult)(nil), "ethermint.types.v1.TxResult") +} + +func init() { proto.RegisterFile("ethermint/types/v1/indexer.proto", fileDescriptor_1197e10a8be8ed28) } + +var fileDescriptor_1197e10a8be8ed28 = []byte{ + // 295 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0x31, 0x4b, 0xc3, 0x40, + 0x18, 0x86, 0x73, 0xb6, 0x4d, 0xe3, 0xa1, 0x83, 0x51, 0x4a, 0x54, 0x88, 0x87, 0x53, 0xa6, 0x84, + 0xe2, 0xd6, 0xd1, 0x45, 0x5c, 0x8f, 0xba, 0xb8, 0x84, 0xb4, 0xf9, 0xbc, 0x3b, 0xe8, 0xf5, 0x4a, + 0xef, 0x4b, 0x88, 0xff, 0xc0, 0xd1, 0x9f, 0xe0, 0xcf, 0x71, 0xec, 0xe8, 0x28, 0x2d, 0xfe, 0x0f, + 0xe9, 0x35, 0x44, 0x70, 0xfb, 0x5e, 0x9e, 0xe7, 0xe3, 0x85, 0x97, 0x32, 0x40, 0x09, 0x6b, 0xad, + 0x96, 0x98, 0xe1, 0xeb, 0x0a, 0x6c, 0x56, 0x8f, 0x33, 0xb5, 0x2c, 0xa1, 0x81, 0x75, 0xba, 0x5a, + 0x1b, 0x34, 0x61, 0xd8, 0x19, 0xa9, 0x33, 0xd2, 0x7a, 0x7c, 0x75, 0x21, 0x8c, 0x30, 0x0e, 0x67, + 0xfb, 0xeb, 0x60, 0xde, 0xfe, 0x10, 0x1a, 0x4c, 0x1b, 0x0e, 0xb6, 0x5a, 0x60, 0x38, 0xa2, 0xbe, + 0x04, 0x25, 0x24, 0x46, 0x84, 0x91, 0xa4, 0xc7, 0xdb, 0x14, 0x5e, 0xd2, 0x00, 0x9b, 0xdc, 0x55, + 0x44, 0x47, 0x8c, 0x24, 0xa7, 0x7c, 0x88, 0xcd, 0xe3, 0x3e, 0x86, 0xd7, 0xf4, 0x58, 0x5b, 0xd1, + 0xb2, 0x9e, 0x63, 0x81, 0xb6, 0xe2, 0x00, 0x19, 0x3d, 0x01, 0x94, 0x79, 0xf7, 0xdb, 0x67, 0x24, + 0x19, 0x70, 0x0a, 0x28, 0xa7, 0xed, 0xfb, 0x88, 0xfa, 0x2f, 0x85, 0x5a, 0x40, 0x19, 0x0d, 0x18, + 0x49, 0x02, 0xde, 0xa6, 0x7d, 0xa3, 0x28, 0x6c, 0x5e, 0x59, 0x28, 0x23, 0x9f, 0x91, 0xa4, 0xcf, + 0x87, 0xa2, 0xb0, 0x4f, 0x16, 0xca, 0x30, 0xa5, 0xe7, 0xf3, 0x4a, 0x57, 0x8b, 0x02, 0x55, 0x0d, + 0x79, 0x67, 0x0d, 0x9d, 0x75, 0xf6, 0x87, 0x1e, 0x0e, 0xfe, 0xa4, 0xff, 0xf6, 0x71, 0xe3, 0xdd, + 0x4f, 0x3e, 0xb7, 0x31, 0xd9, 0x6c, 0x63, 0xf2, 0xbd, 0x8d, 0xc9, 0xfb, 0x2e, 0xf6, 0x36, 0xbb, + 0xd8, 0xfb, 0xda, 0xc5, 0xde, 0x33, 0x13, 0x0a, 0x65, 0x35, 0x4b, 0xe7, 0x46, 0x67, 0x50, 0x6b, + 0x63, 0xb3, 0x7f, 0xf3, 0xce, 0x7c, 0x37, 0xd5, 0xdd, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc1, + 0x34, 0xa8, 0x0b, 0x78, 0x01, 0x00, 0x00, +} + +func (m *TxResult) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TxResult) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TxResult) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.CumulativeGasUsed != 0 { + i = encodeVarintIndexer(dAtA, i, uint64(m.CumulativeGasUsed)) + i-- + dAtA[i] = 0x38 + } + if m.GasUsed != 0 { + i = encodeVarintIndexer(dAtA, i, uint64(m.GasUsed)) + i-- + dAtA[i] = 0x30 + } + if m.Failed { + i-- + if m.Failed { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } + if m.EthTxIndex != 0 { + i = encodeVarintIndexer(dAtA, i, uint64(m.EthTxIndex)) + i-- + dAtA[i] = 0x20 + } + if m.MsgIndex != 0 { + i = encodeVarintIndexer(dAtA, i, uint64(m.MsgIndex)) + i-- + dAtA[i] = 0x18 + } + if m.TxIndex != 0 { + i = encodeVarintIndexer(dAtA, i, uint64(m.TxIndex)) + i-- + dAtA[i] = 0x10 + } + if m.Height != 0 { + i = encodeVarintIndexer(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintIndexer(dAtA []byte, offset int, v uint64) int { + offset -= sovIndexer(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *TxResult) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovIndexer(uint64(m.Height)) + } + if m.TxIndex != 0 { + n += 1 + sovIndexer(uint64(m.TxIndex)) + } + if m.MsgIndex != 0 { + n += 1 + sovIndexer(uint64(m.MsgIndex)) + } + if m.EthTxIndex != 0 { + n += 1 + sovIndexer(uint64(m.EthTxIndex)) + } + if m.Failed { + n += 2 + } + if m.GasUsed != 0 { + n += 1 + sovIndexer(uint64(m.GasUsed)) + } + if m.CumulativeGasUsed != 0 { + n += 1 + sovIndexer(uint64(m.CumulativeGasUsed)) + } + return n +} + +func sovIndexer(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozIndexer(x uint64) (n int) { + return sovIndexer(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *TxResult) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIndexer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxResult: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxResult: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIndexer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TxIndex", wireType) + } + m.TxIndex = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIndexer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TxIndex |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MsgIndex", wireType) + } + m.MsgIndex = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIndexer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MsgIndex |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EthTxIndex", wireType) + } + m.EthTxIndex = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIndexer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EthTxIndex |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Failed", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIndexer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Failed = bool(v != 0) + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field GasUsed", wireType) + } + m.GasUsed = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIndexer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.GasUsed |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CumulativeGasUsed", wireType) + } + m.CumulativeGasUsed = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIndexer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CumulativeGasUsed |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipIndexer(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthIndexer + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipIndexer(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowIndexer + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowIndexer + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowIndexer + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthIndexer + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupIndexer + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthIndexer + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthIndexer = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowIndexer = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupIndexer = fmt.Errorf("proto: unexpected end of group") +) From d4c4a80bfaa0c799fece8560e86371dd39cba247 Mon Sep 17 00:00:00 2001 From: yihuang Date: Wed, 10 Aug 2022 18:17:18 +0800 Subject: [PATCH 02/10] Apply suggestions from code review --- rpc/backend/tx_info.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index bc54fc3b43..e84180e2fd 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -172,7 +172,7 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ // parse tx logs from events logs, err := TxLogsFromEvents(blockRes.TxsResults[res.TxIndex].Events, int(res.MsgIndex)) if err != nil { - b.logger.Debug("logs not found", "hash", hexTx, "error", err.Error()) + b.logger.Debug("failed to parse logs", "hash", hexTx, "error", err.Error()) } if res.EthTxIndex == -1 { @@ -201,7 +201,6 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ "transactionHash": hash, "contractAddress": nil, "gasUsed": hexutil.Uint64(res.GasUsed), - "type": hexutil.Uint(txData.TxType()), // Inclusion information: These fields provide information about the inclusion of the // transaction corresponding to this receipt. From 5b90194dab2aec75e2ffeaf9a4d1adcba342be63 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Wed, 10 Aug 2022 19:32:52 +0800 Subject: [PATCH 03/10] test enable-indexer in integration test --- tests/integration_tests/conftest.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py index 53d709c682..43c7ed248e 100644 --- a/tests/integration_tests/conftest.py +++ b/tests/integration_tests/conftest.py @@ -1,12 +1,21 @@ +from pathlib import Path + import pytest -from .network import setup_ethermint, setup_geth +from .network import setup_custom_ethermint, setup_ethermint, setup_geth -@pytest.fixture(scope="session") -def ethermint(tmp_path_factory): - path = tmp_path_factory.mktemp("ethermint") - yield from setup_ethermint(path, 26650) +@pytest.fixture(scope="session", params=[False, True]) +def ethermint(request, tmp_path_factory): + enable_indexer = request.param + if enable_indexer: + path = tmp_path_factory.mktemp("indexer") + yield from setup_custom_ethermint( + path, 26660, Path(__file__).parent / "configs/enable-indexer.jsonnet" + ) + else: + path = tmp_path_factory.mktemp("ethermint") + yield from setup_ethermint(path, 26650) @pytest.fixture(scope="session") From 2a1c17e9fad327d4f954556ea37f2b00a68268c3 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Wed, 10 Aug 2022 19:37:43 +0800 Subject: [PATCH 04/10] fix go lint --- server/indexer.go | 8 ++--- .../configs/enable-indexer.jsonnet | 20 ++++++++++++ tests/integration_tests/conftest.py | 31 +++++++++++-------- 3 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 tests/integration_tests/configs/enable-indexer.jsonnet diff --git a/server/indexer.go b/server/indexer.go index ba18a481ab..9bcc3b6466 100644 --- a/server/indexer.go +++ b/server/indexer.go @@ -99,10 +99,10 @@ func (kv *KVIndexer) FirstIndexedBlock() (int64, error) { } // IndexBlock index all the eth txs in a block through the following steps: -// - Iterates over all of the Txs in Block -// - Parses eth Tx infos from cosmos-sdk events for every TxResult -// - Iterates over all the messages of the Tx -// - Builds and stores a indexer.TxResult based on parsed events for every message +// - Iterates over all of the Txs in Block +// - Parses eth Tx infos from cosmos-sdk events for every TxResult +// - Iterates over all the messages of the Tx +// - Builds and stores a indexer.TxResult based on parsed events for every message func (kv *KVIndexer) IndexBlock(blk *tmtypes.Block, txResults []*abci.ResponseDeliverTx) error { height := blk.Header.Height diff --git a/tests/integration_tests/configs/enable-indexer.jsonnet b/tests/integration_tests/configs/enable-indexer.jsonnet new file mode 100644 index 0000000000..c21c6a98c0 --- /dev/null +++ b/tests/integration_tests/configs/enable-indexer.jsonnet @@ -0,0 +1,20 @@ +local config = import 'default.jsonnet'; + +config { + 'ethermint_9000-1'+: { + config+: { + tx_index+: { + indexer: 'null', + }, + }, + 'app-config'+: { + pruning: 'everything', + 'state-sync'+: { + 'snapshot-interval': 0, + }, + 'json-rpc'+: { + 'enable-indexer': true, + }, + }, + }, +} diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py index 43c7ed248e..5417176b9b 100644 --- a/tests/integration_tests/conftest.py +++ b/tests/integration_tests/conftest.py @@ -5,17 +5,18 @@ from .network import setup_custom_ethermint, setup_ethermint, setup_geth -@pytest.fixture(scope="session", params=[False, True]) -def ethermint(request, tmp_path_factory): - enable_indexer = request.param - if enable_indexer: - path = tmp_path_factory.mktemp("indexer") - yield from setup_custom_ethermint( - path, 26660, Path(__file__).parent / "configs/enable-indexer.jsonnet" - ) - else: - path = tmp_path_factory.mktemp("ethermint") - yield from setup_ethermint(path, 26650) +@pytest.fixture(scope="session") +def ethermint(tmp_path_factory): + path = tmp_path_factory.mktemp("ethermint") + yield from setup_ethermint(path, 26650) + + +@pytest.fixture(scope="session") +def ethermint_indexer(tmp_path_factory): + path = tmp_path_factory.mktemp("indexer") + yield from setup_custom_ethermint( + path, 26660, Path(__file__).parent / "configs/enable-indexer.jsonnet" + ) @pytest.fixture(scope="session") @@ -24,8 +25,10 @@ def geth(tmp_path_factory): yield from setup_geth(path, 8545) -@pytest.fixture(scope="session", params=["ethermint", "geth", "ethermint-ws"]) -def cluster(request, ethermint, geth): +@pytest.fixture( + scope="session", params=["ethermint", "geth", "ethermint-ws", "enable-indexer"] +) +def cluster(request, ethermint, ethermint_indexer, geth): """ run on both ethermint and geth """ @@ -38,5 +41,7 @@ def cluster(request, ethermint, geth): ethermint_ws = ethermint.copy() ethermint_ws.use_websocket() yield ethermint_ws + elif provider == "enable-indexer": + yield ethermint_indexer else: raise NotImplementedError From 8ade689bed52a6542c03b3c4a8f0562138a7fe4e Mon Sep 17 00:00:00 2001 From: HuangYi Date: Wed, 10 Aug 2022 21:15:53 +0800 Subject: [PATCH 05/10] address review suggestions --- server/indexer.go | 122 +++++++++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 60 deletions(-) diff --git a/server/indexer.go b/server/indexer.go index 9bcc3b6466..0cf9449c50 100644 --- a/server/indexer.go +++ b/server/indexer.go @@ -30,52 +30,6 @@ const ( var _ ethermint.EVMTxIndexer = &KVIndexer{} -// TxHashKey returns the key for db entry: `tx hash -> tx result struct` -func TxHashKey(hash common.Hash) []byte { - return append([]byte{KeyPrefixTxHash}, hash.Bytes()...) -} - -// TxIndexKey returns the key for db entry: `(block number, tx index) -> tx hash` -func TxIndexKey(blockNumber int64, txIndex int32) []byte { - bz1 := sdk.Uint64ToBigEndian(uint64(blockNumber)) - bz2 := sdk.Uint64ToBigEndian(uint64(txIndex)) - return append(append([]byte{KeyPrefixTxIndex}, bz1...), bz2...) -} - -func parseBlockNumberFromKey(key []byte) (int64, error) { - if len(key) != TxIndexKeyLength { - return 0, fmt.Errorf("wrong tx index key length, expect: %d, got: %d", TxIndexKeyLength, len(key)) - } - - return int64(sdk.BigEndianToUint64(key[1:9])), nil -} - -// LoadLastBlock returns the latest indexed block number, returns -1 if db is empty -func LoadLastBlock(db dbm.DB) (int64, error) { - it, err := db.ReverseIterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1}) - if err != nil { - return 0, sdkerrors.Wrap(err, "LoadLastBlock") - } - defer it.Close() - if !it.Valid() { - return -1, nil - } - return parseBlockNumberFromKey(it.Key()) -} - -// LoadFirstBlock loads the first indexed block, returns -1 if db is empty -func LoadFirstBlock(db dbm.DB) (int64, error) { - it, err := db.Iterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1}) - if err != nil { - return 0, sdkerrors.Wrap(err, "LoadFirstBlock") - } - defer it.Close() - if !it.Valid() { - return -1, nil - } - return parseBlockNumberFromKey(it.Key()) -} - // KVIndexer implements a eth tx indexer on a KV db. type KVIndexer struct { db dbm.DB @@ -88,39 +42,31 @@ func NewKVIndexer(db dbm.DB, logger log.Logger, clientCtx client.Context) *KVInd return &KVIndexer{db, logger, clientCtx} } -// LastIndexedBlock returns the latest indexed block number, returns -1 if db is empty -func (kv *KVIndexer) LastIndexedBlock() (int64, error) { - return LoadLastBlock(kv.db) -} - -// FirstIndexedBlock returns the first indexed block number, returns -1 if db is empty -func (kv *KVIndexer) FirstIndexedBlock() (int64, error) { - return LoadFirstBlock(kv.db) -} - // IndexBlock index all the eth txs in a block through the following steps: // - Iterates over all of the Txs in Block // - Parses eth Tx infos from cosmos-sdk events for every TxResult // - Iterates over all the messages of the Tx // - Builds and stores a indexer.TxResult based on parsed events for every message -func (kv *KVIndexer) IndexBlock(blk *tmtypes.Block, txResults []*abci.ResponseDeliverTx) error { - height := blk.Header.Height +func (kv *KVIndexer) IndexBlock(block *tmtypes.Block, txResults []*abci.ResponseDeliverTx) error { + height := block.Header.Height batch := kv.db.NewBatch() defer batch.Close() // record index of valid eth tx during the iteration var ethTxIndex int32 - for txIndex, tx := range blk.Txs { + for txIndex, tx := range block.Txs { result := txResults[txIndex] if !backend.TxSuccessOrExceedsBlockGasLimit(result) { continue } + tx, err := kv.clientCtx.TxConfig.TxDecoder()(tx) if err != nil { kv.logger.Error("Fail to decode tx", "err", err, "block", height, "txIndex", txIndex) continue } + if !isEthTx(tx) { continue } @@ -170,11 +116,21 @@ func (kv *KVIndexer) IndexBlock(blk *tmtypes.Block, txResults []*abci.ResponseDe } } if err := batch.Write(); err != nil { - return sdkerrors.Wrapf(err, "IndexBlock %d, write batch", blk.Height) + return sdkerrors.Wrapf(err, "IndexBlock %d, write batch", block.Height) } return nil } +// LastIndexedBlock returns the latest indexed block number, returns -1 if db is empty +func (kv *KVIndexer) LastIndexedBlock() (int64, error) { + return LoadLastBlock(kv.db) +} + +// FirstIndexedBlock returns the first indexed block number, returns -1 if db is empty +func (kv *KVIndexer) FirstIndexedBlock() (int64, error) { + return LoadFirstBlock(kv.db) +} + // isEthTx check if the tx is an eth tx func isEthTx(tx sdk.Tx) bool { extTx, ok := tx.(authante.HasExtensionOptionsTx) @@ -227,3 +183,49 @@ func (kv *KVIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*ethe } return kv.GetByTxHash(common.BytesToHash(bz)) } + +// TxHashKey returns the key for db entry: `tx hash -> tx result struct` +func TxHashKey(hash common.Hash) []byte { + return append([]byte{KeyPrefixTxHash}, hash.Bytes()...) +} + +// TxIndexKey returns the key for db entry: `(block number, tx index) -> tx hash` +func TxIndexKey(blockNumber int64, txIndex int32) []byte { + bz1 := sdk.Uint64ToBigEndian(uint64(blockNumber)) + bz2 := sdk.Uint64ToBigEndian(uint64(txIndex)) + return append(append([]byte{KeyPrefixTxIndex}, bz1...), bz2...) +} + +func parseBlockNumberFromKey(key []byte) (int64, error) { + if len(key) != TxIndexKeyLength { + return 0, fmt.Errorf("wrong tx index key length, expect: %d, got: %d", TxIndexKeyLength, len(key)) + } + + return int64(sdk.BigEndianToUint64(key[1:9])), nil +} + +// LoadLastBlock returns the latest indexed block number, returns -1 if db is empty +func LoadLastBlock(db dbm.DB) (int64, error) { + it, err := db.ReverseIterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1}) + if err != nil { + return 0, sdkerrors.Wrap(err, "LoadLastBlock") + } + defer it.Close() + if !it.Valid() { + return -1, nil + } + return parseBlockNumberFromKey(it.Key()) +} + +// LoadFirstBlock loads the first indexed block, returns -1 if db is empty +func LoadFirstBlock(db dbm.DB) (int64, error) { + it, err := db.Iterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1}) + if err != nil { + return 0, sdkerrors.Wrap(err, "LoadFirstBlock") + } + defer it.Close() + if !it.Valid() { + return -1, nil + } + return parseBlockNumberFromKey(it.Key()) +} From b0b4847937dcbaa37b82af22a2c5a79be5a6ee6f Mon Sep 17 00:00:00 2001 From: Freddy Caceres Date: Wed, 10 Aug 2022 09:17:44 -0400 Subject: [PATCH 06/10] fix linter --- server/config/config.go | 4 ++-- server/flags/flags.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/config/config.go b/server/config/config.go index 65485abc01..b6abb53b4e 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -108,7 +108,7 @@ type JSONRPCConfig struct { // MaxOpenConnections sets the maximum number of simultaneous connections // for the server listener. MaxOpenConnections int `mapstructure:"max-open-connections"` - // EnableIndexer defines if enable the custom indexer service. + // EnableIndexer defines if enable the custom indexer service. EnableIndexer bool `mapstructure:"enable-indexer"` } @@ -315,7 +315,7 @@ func GetConfig(v *viper.Viper) Config { HTTPTimeout: v.GetDuration("json-rpc.http-timeout"), HTTPIdleTimeout: v.GetDuration("json-rpc.http-idle-timeout"), MaxOpenConnections: v.GetInt("json-rpc.max-open-connections"), - EnableIndexer: v.GetBool("json-rpc.enable-indexer"), + EnableIndexer: v.GetBool("json-rpc.enable-indexer"), }, TLS: TLSConfig{ CertificatePath: v.GetString("tls.certificate-path"), diff --git a/server/flags/flags.go b/server/flags/flags.go index 0db5b14359..77e02d6589 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -48,7 +48,7 @@ const ( JSONRPCHTTPIdleTimeout = "json-rpc.http-idle-timeout" JSONRPCAllowUnprotectedTxs = "json-rpc.allow-unprotected-txs" JSONRPCMaxOpenConnections = "json-rpc.max-open-connections" - JSONRPCEnableIndexer = "json-rpc.enable-indexer" + JSONRPCEnableIndexer = "json-rpc.enable-indexer" ) // EVM flags From 724bc7690a133b75e80756653538d250b9438818 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Thu, 11 Aug 2022 19:00:26 +0800 Subject: [PATCH 07/10] address review suggestions - test indexer in backend unit test - add comments --- default.nix | 2 +- docs/api/proto-docs.md | 2 +- server/indexer.go => indexer/kv_indexer.go | 71 +++++++++++----------- proto/ethermint/types/v1/indexer.proto | 3 +- rpc/backend/backend.go | 8 ++- rpc/backend/backend_suite_test.go | 7 ++- rpc/backend/blocks_info.go | 2 +- rpc/backend/evm_backend_test.go | 4 +- rpc/backend/tx_info.go | 5 +- rpc/backend/utils.go | 15 ----- rpc/types/utils.go | 16 +++++ server/indexer_cmd.go | 14 +++-- server/start.go | 8 +-- types/indexer.pb.go | 3 +- 14 files changed, 91 insertions(+), 69 deletions(-) rename server/indexer.go => indexer/kv_indexer.go (98%) diff --git a/default.nix b/default.nix index 4bc36173d6..c319aeaf69 100644 --- a/default.nix +++ b/default.nix @@ -17,7 +17,7 @@ in buildGoApplication rec { inherit pname version tags ldflags; src = lib.sourceByRegex ./. [ - "^(x|app|cmd|client|server|crypto|rpc|types|encoding|ethereum|testutil|version|go.mod|go.sum|gomod2nix.toml)($|/.*)" + "^(x|app|cmd|client|server|crypto|rpc|types|encoding|ethereum|indexer|testutil|version|go.mod|go.sum|gomod2nix.toml)($|/.*)" "^tests(/.*[.]go)?$" ]; modules = ./gomod2nix.toml; diff --git a/docs/api/proto-docs.md b/docs/api/proto-docs.md index 803e10cdcc..a9d9cbf3a3 100644 --- a/docs/api/proto-docs.md +++ b/docs/api/proto-docs.md @@ -1199,7 +1199,7 @@ TxResult is the value stored in eth tx indexer | `height` | [int64](#int64) | | the block height | | `tx_index` | [uint32](#uint32) | | cosmos tx index | | `msg_index` | [uint32](#uint32) | | the msg index in a batch tx | -| `eth_tx_index` | [int32](#int32) | | eth tx index | +| `eth_tx_index` | [int32](#int32) | | eth tx index, the index in the list of valid eth tx in the block, aka. the transaction list returned by eth_getBlock api. | | `failed` | [bool](#bool) | | if the eth tx is failed | | `gas_used` | [uint64](#uint64) | | gas used by tx, if exceeds block gas limit, it's set to gas limit which is what's actually deducted by ante handler. | | `cumulative_gas_used` | [uint64](#uint64) | | the cumulative gas used within current batch tx | diff --git a/server/indexer.go b/indexer/kv_indexer.go similarity index 98% rename from server/indexer.go rename to indexer/kv_indexer.go index 0cf9449c50..bc8afce4fa 100644 --- a/server/indexer.go +++ b/indexer/kv_indexer.go @@ -1,4 +1,4 @@ -package server +package indexer import ( "fmt" @@ -9,7 +9,6 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authante "github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/ethereum/go-ethereum/common" - "github.com/evmos/ethermint/rpc/backend" rpctypes "github.com/evmos/ethermint/rpc/types" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" @@ -57,7 +56,7 @@ func (kv *KVIndexer) IndexBlock(block *tmtypes.Block, txResults []*abci.Response var ethTxIndex int32 for txIndex, tx := range block.Txs { result := txResults[txIndex] - if !backend.TxSuccessOrExceedsBlockGasLimit(result) { + if !rpctypes.TxSuccessOrExceedsBlockGasLimit(result) { continue } @@ -131,31 +130,6 @@ func (kv *KVIndexer) FirstIndexedBlock() (int64, error) { return LoadFirstBlock(kv.db) } -// isEthTx check if the tx is an eth tx -func isEthTx(tx sdk.Tx) bool { - extTx, ok := tx.(authante.HasExtensionOptionsTx) - if !ok { - return false - } - opts := extTx.GetExtensionOptions() - if len(opts) != 1 || opts[0].GetTypeUrl() != "/ethermint.evm.v1.ExtensionOptionsEthereumTx" { - return false - } - return true -} - -// saveTxResult index the txResult into the kv db batch -func saveTxResult(codec codec.Codec, batch dbm.Batch, txHash common.Hash, txResult *ethermint.TxResult) error { - bz := codec.MustMarshal(txResult) - if err := batch.Set(TxHashKey(txHash), bz); err != nil { - return sdkerrors.Wrap(err, "set tx-hash key") - } - if err := batch.Set(TxIndexKey(txResult.Height, txResult.EthTxIndex), txHash.Bytes()); err != nil { - return sdkerrors.Wrap(err, "set tx-index key") - } - return nil -} - // GetByTxHash finds eth tx by eth tx hash func (kv *KVIndexer) GetByTxHash(hash common.Hash) (*ethermint.TxResult, error) { bz, err := kv.db.Get(TxHashKey(hash)) @@ -196,14 +170,6 @@ func TxIndexKey(blockNumber int64, txIndex int32) []byte { return append(append([]byte{KeyPrefixTxIndex}, bz1...), bz2...) } -func parseBlockNumberFromKey(key []byte) (int64, error) { - if len(key) != TxIndexKeyLength { - return 0, fmt.Errorf("wrong tx index key length, expect: %d, got: %d", TxIndexKeyLength, len(key)) - } - - return int64(sdk.BigEndianToUint64(key[1:9])), nil -} - // LoadLastBlock returns the latest indexed block number, returns -1 if db is empty func LoadLastBlock(db dbm.DB) (int64, error) { it, err := db.ReverseIterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1}) @@ -229,3 +195,36 @@ func LoadFirstBlock(db dbm.DB) (int64, error) { } return parseBlockNumberFromKey(it.Key()) } + +// isEthTx check if the tx is an eth tx +func isEthTx(tx sdk.Tx) bool { + extTx, ok := tx.(authante.HasExtensionOptionsTx) + if !ok { + return false + } + opts := extTx.GetExtensionOptions() + if len(opts) != 1 || opts[0].GetTypeUrl() != "/ethermint.evm.v1.ExtensionOptionsEthereumTx" { + return false + } + return true +} + +// saveTxResult index the txResult into the kv db batch +func saveTxResult(codec codec.Codec, batch dbm.Batch, txHash common.Hash, txResult *ethermint.TxResult) error { + bz := codec.MustMarshal(txResult) + if err := batch.Set(TxHashKey(txHash), bz); err != nil { + return sdkerrors.Wrap(err, "set tx-hash key") + } + if err := batch.Set(TxIndexKey(txResult.Height, txResult.EthTxIndex), txHash.Bytes()); err != nil { + return sdkerrors.Wrap(err, "set tx-index key") + } + return nil +} + +func parseBlockNumberFromKey(key []byte) (int64, error) { + if len(key) != TxIndexKeyLength { + return 0, fmt.Errorf("wrong tx index key length, expect: %d, got: %d", TxIndexKeyLength, len(key)) + } + + return int64(sdk.BigEndianToUint64(key[1:9])), nil +} diff --git a/proto/ethermint/types/v1/indexer.proto b/proto/ethermint/types/v1/indexer.proto index c58799d3d1..e1d0be03c9 100644 --- a/proto/ethermint/types/v1/indexer.proto +++ b/proto/ethermint/types/v1/indexer.proto @@ -16,7 +16,8 @@ message TxResult { // the msg index in a batch tx uint32 msg_index = 3; - // eth tx index + // eth tx index, the index in the list of valid eth tx in the block, + // aka. the transaction list returned by eth_getBlock api. int32 eth_tx_index = 4; // if the eth tx is failed bool failed = 5; diff --git a/rpc/backend/backend.go b/rpc/backend/backend.go index 75594a130b..340524cd0f 100644 --- a/rpc/backend/backend.go +++ b/rpc/backend/backend.go @@ -144,7 +144,13 @@ type Backend struct { } // NewBackend creates a new Backend instance for cosmos and ethereum namespaces -func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) *Backend { +func NewBackend( + ctx *server.Context, + logger log.Logger, + clientCtx client.Context, + allowUnprotectedTxs bool, + indexer ethermint.EVMTxIndexer, +) *Backend { chainID, err := ethermint.ParseChainID(clientCtx.ChainID) if err != nil { panic(err) diff --git a/rpc/backend/backend_suite_test.go b/rpc/backend/backend_suite_test.go index 2d1bd2ddbf..016f525ad5 100644 --- a/rpc/backend/backend_suite_test.go +++ b/rpc/backend/backend_suite_test.go @@ -8,6 +8,8 @@ import ( "path/filepath" "testing" + dbm "github.com/tendermint/tm-db" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/server" @@ -20,6 +22,7 @@ import ( "github.com/evmos/ethermint/app" "github.com/evmos/ethermint/crypto/hd" "github.com/evmos/ethermint/encoding" + "github.com/evmos/ethermint/indexer" "github.com/evmos/ethermint/rpc/backend/mocks" rpctypes "github.com/evmos/ethermint/rpc/types" evmtypes "github.com/evmos/ethermint/x/evm/types" @@ -56,7 +59,9 @@ func (suite *BackendTestSuite) SetupTest() { allowUnprotectedTxs := false - suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, nil) + idxer := indexer.NewKVIndexer(dbm.NewMemDB(), ctx.Logger, clientCtx) + + suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, idxer) suite.backend.queryClient.QueryClient = mocks.NewQueryClient(suite.T()) suite.backend.clientCtx.Client = mocks.NewClient(suite.T()) suite.backend.ctx = rpctypes.ContextWithHeight(1) diff --git a/rpc/backend/blocks_info.go b/rpc/backend/blocks_info.go index ca97153b9b..653725a96d 100644 --- a/rpc/backend/blocks_info.go +++ b/rpc/backend/blocks_info.go @@ -297,7 +297,7 @@ func (b *Backend) GetEthereumMsgsFromTendermintBlock( // Check if tx exists on EVM by cross checking with blockResults: // - Include unsuccessful tx that exceeds block gas limit // - Exclude unsuccessful tx with any other error but ExceedBlockGasLimit - if !TxSuccessOrExceedsBlockGasLimit(txResults[i]) { + if !rpctypes.TxSuccessOrExceedsBlockGasLimit(txResults[i]) { b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash())) continue } diff --git a/rpc/backend/evm_backend_test.go b/rpc/backend/evm_backend_test.go index f17eed47e4..4e78917b09 100644 --- a/rpc/backend/evm_backend_test.go +++ b/rpc/backend/evm_backend_test.go @@ -949,7 +949,7 @@ func (suite *BackendTestSuite) TestGetEthereumMsgsFromTendermintBlock() { TxsResults: []*types.ResponseDeliverTx{ { Code: 1, - Log: ExceedBlockGasLimitError, + Log: ethrpc.ExceedBlockGasLimitError, }, }, }, @@ -964,7 +964,7 @@ func (suite *BackendTestSuite) TestGetEthereumMsgsFromTendermintBlock() { TxsResults: []*types.ResponseDeliverTx{ { Code: 0, - Log: ExceedBlockGasLimitError, + Log: ethrpc.ExceedBlockGasLimitError, }, }, }, diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index e84180e2fd..a75e6015c3 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -57,6 +57,7 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac } } } + // if we still unable to find the eth tx index, return error, shouldn't happen. if res.EthTxIndex == -1 { return nil, errors.New("can't find index of ethereum tx") } @@ -94,6 +95,7 @@ func (b *Backend) getTransactionByHashPending(txHash common.Hash) (*rpctypes.RPC } if msg.Hash == hexTx { + // use zero block values since it's not included in a block yet rpctx, err := rpctypes.NewTransactionFromMsg( msg, common.Hash{}, @@ -185,6 +187,7 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ } } } + // return error if still unable to find the eth tx index if res.EthTxIndex == -1 { return nil, errors.New("can't find index of ethereum tx") } @@ -320,7 +323,7 @@ func (b *Backend) queryTendermintTxIndexer(query string, txGetter func(*rpctypes return nil, errors.New("ethereum tx not found") } txResult := resTxs.Txs[0] - if !TxSuccessOrExceedsBlockGasLimit(&txResult.TxResult) { + if !rpctypes.TxSuccessOrExceedsBlockGasLimit(&txResult.TxResult) { return nil, errors.New("invalid ethereum tx") } diff --git a/rpc/backend/utils.go b/rpc/backend/utils.go index 737df49afd..d95a6854f8 100644 --- a/rpc/backend/utils.go +++ b/rpc/backend/utils.go @@ -23,10 +23,6 @@ import ( evmtypes "github.com/evmos/ethermint/x/evm/types" ) -// ExceedBlockGasLimitError defines the error message when tx execution exceeds the block gas limit. -// The tx fee is deducted in ante handler, so it shouldn't be ignored in JSON-RPC API. -const ExceedBlockGasLimitError = "out of gas in location: block gas meter; gasWanted:" - type txGasAndReward struct { gasUsed uint64 reward *big.Int @@ -247,17 +243,6 @@ func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) { return evmtypes.LogsToEthereum(logs), nil } -// TxExceedBlockGasLimit returns true if the tx exceeds block gas limit. -func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool { - return strings.Contains(res.Log, ExceedBlockGasLimitError) -} - -// TxSuccessOrExceedsBlockGasLimit returnsrue if the transaction was successful -// or if it failed with an ExceedBlockGasLimit error -func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool { - return res.Code == 0 || TxExceedBlockGasLimit(res) -} - // ShouldIgnoreGasUsed returns true if the gasUsed in result should be ignored // workaround for issue: https://github.com/cosmos/cosmos-sdk/issues/10832 func ShouldIgnoreGasUsed(res *abci.ResponseDeliverTx) bool { diff --git a/rpc/types/utils.go b/rpc/types/utils.go index 7e0aebd938..5843ed24ef 100644 --- a/rpc/types/utils.go +++ b/rpc/types/utils.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "math/big" + "strings" abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" @@ -22,6 +23,10 @@ import ( "github.com/ethereum/go-ethereum/params" ) +// ExceedBlockGasLimitError defines the error message when tx execution exceeds the block gas limit. +// The tx fee is deducted in ante handler, so it shouldn't be ignored in JSON-RPC API. +const ExceedBlockGasLimitError = "out of gas in location: block gas meter; gasWanted:" + // RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes. func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) { tx, err := clientCtx.TxConfig.TxDecoder()(txBz) @@ -243,3 +248,14 @@ func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error { } return nil } + +// TxExceedBlockGasLimit returns true if the tx exceeds block gas limit. +func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool { + return strings.Contains(res.Log, ExceedBlockGasLimitError) +} + +// TxSuccessOrExceedsBlockGasLimit returnsrue if the transaction was successful +// or if it failed with an ExceedBlockGasLimit error +func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool { + return res.Code == 0 || TxExceedBlockGasLimit(res) +} diff --git a/server/indexer_cmd.go b/server/indexer_cmd.go index 8a90d8226d..8430fd7adc 100644 --- a/server/indexer_cmd.go +++ b/server/indexer_cmd.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server" + "github.com/evmos/ethermint/indexer" tmnode "github.com/tendermint/tendermint/node" sm "github.com/tendermint/tendermint/state" tmstore "github.com/tendermint/tendermint/store" @@ -28,6 +29,11 @@ func NewIndexTxCmd() *cobra.Command { return err } + direction := args[0] + if direction != "backward" && direction != "forward" { + return fmt.Errorf("unknown index direction, expect: backward|forward, got: %s", direction) + } + cfg := serverCtx.Config home := cfg.RootDir logger := serverCtx.Logger @@ -36,7 +42,7 @@ func NewIndexTxCmd() *cobra.Command { logger.Error("failed to open evm indexer DB", "error", err.Error()) return err } - indexer := NewKVIndexer(idxDB, logger.With("module", "evmindex"), clientCtx) + idxer := indexer.NewKVIndexer(idxDB, logger.With("module", "evmindex"), clientCtx) // open local tendermint db, because the local rpc won't be available. tmdb, err := tmnode.DefaultDBProvider(&tmnode.DBContext{ID: "blockstore", Config: cfg}) @@ -60,7 +66,7 @@ func NewIndexTxCmd() *cobra.Command { if err != nil { return err } - if err := indexer.IndexBlock(blk, resBlk.DeliverTxs); err != nil { + if err := idxer.IndexBlock(blk, resBlk.DeliverTxs); err != nil { return err } fmt.Println(height) @@ -69,7 +75,7 @@ func NewIndexTxCmd() *cobra.Command { switch args[0] { case "backward": - first, err := indexer.FirstIndexedBlock() + first, err := idxer.FirstIndexedBlock() if err != nil { return err } @@ -82,7 +88,7 @@ func NewIndexTxCmd() *cobra.Command { } } case "forward": - latest, err := indexer.LastIndexedBlock() + latest, err := idxer.LastIndexedBlock() if err != nil { return err } diff --git a/server/start.go b/server/start.go index 5639c058a8..f84dbe6873 100644 --- a/server/start.go +++ b/server/start.go @@ -40,10 +40,10 @@ import ( servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" "github.com/cosmos/cosmos-sdk/server/types" + "github.com/evmos/ethermint/indexer" ethdebug "github.com/evmos/ethermint/rpc/namespaces/ethereum/debug" "github.com/evmos/ethermint/server/config" srvflags "github.com/evmos/ethermint/server/flags" - ethermint "github.com/evmos/ethermint/types" ) // StartCmd runs the service passed in, either stand-alone or in-process with @@ -332,7 +332,6 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty app.RegisterTendermintService(clientCtx) } - var indexer ethermint.EVMTxIndexer if config.JSONRPC.EnableIndexer { idxDB, err := OpenIndexerDB(home, server.GetAppDBBackend(ctx.Viper)) if err != nil { @@ -340,8 +339,8 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty return err } idxLogger := ctx.Logger.With("module", "evmindex") - indexer = NewKVIndexer(idxDB, idxLogger, clientCtx) - indexerService := NewEVMIndexerService(indexer, clientCtx.Client) + idxer := indexer.NewKVIndexer(idxDB, idxLogger, clientCtx) + indexerService := NewEVMIndexerService(idxer, clientCtx.Client) indexerService.SetLogger(idxLogger) errCh := make(chan error) @@ -510,6 +509,7 @@ func openDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) { return dbm.NewDB("application", backendType, dataDir) } +// OpenIndexerDB opens the custom eth indexer db, using the same db backend as the main app func OpenIndexerDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) { dataDir := filepath.Join(rootDir, "data") return dbm.NewDB("evmindexer", backendType, dataDir) diff --git a/types/indexer.pb.go b/types/indexer.pb.go index aa61b1ee09..2e15c18545 100644 --- a/types/indexer.pb.go +++ b/types/indexer.pb.go @@ -31,7 +31,8 @@ type TxResult struct { TxIndex uint32 `protobuf:"varint,2,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"` // the msg index in a batch tx MsgIndex uint32 `protobuf:"varint,3,opt,name=msg_index,json=msgIndex,proto3" json:"msg_index,omitempty"` - // eth tx index + // eth tx index, the index in the list of valid eth tx in the block, + // aka. the transaction list returned by eth_getBlock api. EthTxIndex int32 `protobuf:"varint,4,opt,name=eth_tx_index,json=ethTxIndex,proto3" json:"eth_tx_index,omitempty"` // if the eth tx is failed Failed bool `protobuf:"varint,5,opt,name=failed,proto3" json:"failed,omitempty"` From 436188da4229169287c6d063fc813115404b64e0 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Thu, 11 Aug 2022 19:16:56 +0800 Subject: [PATCH 08/10] fix build --- server/start.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/start.go b/server/start.go index f84dbe6873..55a67199cd 100644 --- a/server/start.go +++ b/server/start.go @@ -44,6 +44,7 @@ import ( ethdebug "github.com/evmos/ethermint/rpc/namespaces/ethereum/debug" "github.com/evmos/ethermint/server/config" srvflags "github.com/evmos/ethermint/server/flags" + ethermint "github.com/evmos/ethermint/types" ) // StartCmd runs the service passed in, either stand-alone or in-process with @@ -332,6 +333,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty app.RegisterTendermintService(clientCtx) } + var idxer ethermint.EVMTxIndexer if config.JSONRPC.EnableIndexer { idxDB, err := OpenIndexerDB(home, server.GetAppDBBackend(ctx.Viper)) if err != nil { @@ -339,7 +341,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty return err } idxLogger := ctx.Logger.With("module", "evmindex") - idxer := indexer.NewKVIndexer(idxDB, idxLogger, clientCtx) + idxer = indexer.NewKVIndexer(idxDB, idxLogger, clientCtx) indexerService := NewEVMIndexerService(idxer, clientCtx.Client) indexerService.SetLogger(idxLogger) @@ -454,7 +456,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty tmEndpoint := "/websocket" tmRPCAddr := cfg.RPC.ListenAddress - httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &config, indexer) + httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &config, idxer) if err != nil { return err } From c1e299e97432c1d2168159a7da95433a03764ce3 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Thu, 11 Aug 2022 19:19:21 +0800 Subject: [PATCH 09/10] fix test --- .../kv_indexer_test.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename server/indexer_test.go => indexer/kv_indexer_test.go (92%) diff --git a/server/indexer_test.go b/indexer/kv_indexer_test.go similarity index 92% rename from server/indexer_test.go rename to indexer/kv_indexer_test.go index 9afd6afc64..e183e3db92 100644 --- a/server/indexer_test.go +++ b/indexer/kv_indexer_test.go @@ -1,4 +1,4 @@ -package server_test +package indexer_test import ( "math/big" @@ -11,7 +11,7 @@ import ( "github.com/evmos/ethermint/app" "github.com/evmos/ethermint/crypto/ethsecp256k1" evmenc "github.com/evmos/ethermint/encoding" - "github.com/evmos/ethermint/server" + "github.com/evmos/ethermint/indexer" "github.com/evmos/ethermint/tests" "github.com/evmos/ethermint/x/evm/types" "github.com/stretchr/testify/require" @@ -151,31 +151,31 @@ func TestKVIndexer(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { db := dbm.NewMemDB() - indexer := server.NewKVIndexer(db, tmlog.NewNopLogger(), clientCtx) + idxer := indexer.NewKVIndexer(db, tmlog.NewNopLogger(), clientCtx) - err = indexer.IndexBlock(tc.block, tc.blockResult) + err = idxer.IndexBlock(tc.block, tc.blockResult) require.NoError(t, err) if !tc.expSuccess { - first, err := indexer.FirstIndexedBlock() + first, err := idxer.FirstIndexedBlock() require.NoError(t, err) require.Equal(t, int64(-1), first) - last, err := indexer.LastIndexedBlock() + last, err := idxer.LastIndexedBlock() require.NoError(t, err) require.Equal(t, int64(-1), last) } else { - first, err := indexer.FirstIndexedBlock() + first, err := idxer.FirstIndexedBlock() require.NoError(t, err) require.Equal(t, tc.block.Header.Height, first) - last, err := indexer.LastIndexedBlock() + last, err := idxer.LastIndexedBlock() require.NoError(t, err) require.Equal(t, tc.block.Header.Height, last) - res1, err := indexer.GetByTxHash(txHash) + res1, err := idxer.GetByTxHash(txHash) require.NoError(t, err) require.NotNil(t, res1) - res2, err := indexer.GetByBlockAndIndex(1, 0) + res2, err := idxer.GetByBlockAndIndex(1, 0) require.NoError(t, err) require.Equal(t, res1, res2) } From a6fc34a258865d6fdf23cd24f61f412ef236d0e6 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Thu, 11 Aug 2022 23:39:18 +0800 Subject: [PATCH 10/10] service name --- server/indexer_service.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/indexer_service.go b/server/indexer_service.go index 6f5bc929e0..d8b233d7a1 100644 --- a/server/indexer_service.go +++ b/server/indexer_service.go @@ -12,7 +12,7 @@ import ( ) const ( - Subscriber = "EVMIndexerService" + ServiceName = "EVMIndexerService" NewBlockWaitTimeout = 60 * time.Second ) @@ -31,7 +31,7 @@ func NewEVMIndexerService( client rpcclient.Client, ) *EVMIndexerService { is := &EVMIndexerService{txIdxr: txIdxr, client: client} - is.BaseService = *service.NewBaseService(nil, "EVMIndexerService", is) + is.BaseService = *service.NewBaseService(nil, ServiceName, is) return is } @@ -51,7 +51,7 @@ func (eis *EVMIndexerService) OnStart() error { // sometimes happen when there are no other subscribers. blockHeadersChan, err := eis.client.Subscribe( ctx, - Subscriber, + ServiceName, types.QueryForEvent(types.EventNewBlockHeader).String(), 0) if err != nil {