From a3c931bdb4813790d07b457644be86e044c64342 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 15:07:33 +0700 Subject: [PATCH 01/11] enable EVMTxIndexer along with Json-RPC, now EVMTxIndexer is a core part of Json-RPC --- server/config/config.go | 4 --- server/config/toml.go | 3 -- server/flags/flags.go | 1 - server/start.go | 62 +++++++++++++++++++---------------------- 4 files changed, 29 insertions(+), 41 deletions(-) diff --git a/server/config/config.go b/server/config/config.go index e5effce215..963cb08202 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -126,8 +126,6 @@ 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 bool `mapstructure:"enable-indexer"` // MetricsAddress defines the metrics server to listen on MetricsAddress string `mapstructure:"metrics-address"` // FixRevertGasRefundHeight defines the upgrade height for fix of revert gas refund logic when transaction reverted @@ -232,7 +230,6 @@ func DefaultJSONRPCConfig() *JSONRPCConfig { HTTPIdleTimeout: DefaultHTTPIdleTimeout, AllowUnprotectedTxs: DefaultAllowUnprotectedTxs, MaxOpenConnections: DefaultMaxOpenConnections, - EnableIndexer: false, MetricsAddress: DefaultJSONRPCMetricsAddress, FixRevertGasRefundHeight: DefaultFixRevertGasRefundHeight, } @@ -342,7 +339,6 @@ func GetConfig(v *viper.Viper) (Config, error) { 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"), MetricsAddress: v.GetString("json-rpc.metrics-address"), FixRevertGasRefundHeight: v.GetInt64("json-rpc.fix-revert-gas-refund-height"), }, diff --git a/server/config/toml.go b/server/config/toml.go index 8e912136f5..7d3df73dd8 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -70,9 +70,6 @@ allow-unprotected-txs = {{ .JSONRPC.AllowUnprotectedTxs }} # for the server listener. max-open-connections = {{ .JSONRPC.MaxOpenConnections }} -# EnableIndexer enables the custom transaction indexer for the EVM (ethereum transactions). -enable-indexer = {{ .JSONRPC.EnableIndexer }} - # MetricsAddress defines the EVM Metrics server address to bind to. Pass --metrics in CLI to enable # Prometheus metrics path: /debug/metrics/prometheus metrics-address = "{{ .JSONRPC.MetricsAddress }}" diff --git a/server/flags/flags.go b/server/flags/flags.go index 6b905a8d27..47c847dae1 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -51,7 +51,6 @@ const ( JSONRPCHTTPIdleTimeout = "json-rpc.http-idle-timeout" JSONRPCAllowUnprotectedTxs = "json-rpc.allow-unprotected-txs" JSONRPCMaxOpenConnections = "json-rpc.max-open-connections" - JSONRPCEnableIndexer = "json-rpc.enable-indexer" // JSONRPCEnableMetrics enables EVM RPC metrics server. // Set to `metrics` which is hardcoded flag from go-ethereum. // https://github.com/ethereum/go-ethereum/blob/master/metrics/metrics.go#L35-L55 diff --git a/server/start.go b/server/start.go index 479556e5a1..e2855de7b7 100644 --- a/server/start.go +++ b/server/start.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/EscanBE/evermint/v12/constants" + "github.com/EscanBE/evermint/v12/indexer" rpcclient "github.com/cometbft/cometbft/rpc/client" "io" "net" @@ -48,11 +49,9 @@ import ( pruningtypes "github.com/cosmos/cosmos-sdk/store/pruning/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/EscanBE/evermint/v12/indexer" ethdebug "github.com/EscanBE/evermint/v12/rpc/namespaces/ethereum/debug" "github.com/EscanBE/evermint/v12/server/config" srvflags "github.com/EscanBE/evermint/v12/server/flags" - evertypes "github.com/EscanBE/evermint/v12/types" ) // DBOpener is a function to open `application.db`, potentially with customized options. @@ -195,7 +194,6 @@ which accepts a path for the resulting pprof file. 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().Int(srvflags.JSONRPCMaxOpenConnections, config.DefaultMaxOpenConnections, "Sets the maximum number of simultaneous connections for the server listener") //nolint:lll - cmd.Flags().Bool(srvflags.JSONRPCEnableIndexer, false, "Enable the custom tx indexer for json-rpc") cmd.Flags().Bool(srvflags.JSONRPCEnableMetrics, false, "Define if EVM rpc metrics server should be enabled") 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)") //nolint:lll @@ -354,7 +352,6 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt if gRPCOnly { logger.Info("starting node in query only mode; Tendermint is disabled") config.GRPC.Enable = true - config.JSONRPC.EnableIndexer = false } else { logger.Info("starting node with ABCI Tendermint in-process") @@ -388,7 +385,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt // 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 || config.JSONRPC.EnableIndexer) && tmNode != nil { + if (config.API.Enable || config.GRPC.Enable || config.JSONRPC.Enable) && tmNode != nil { clientCtx = clientCtx.WithClient(local.New(tmNode)) app.RegisterTxService(clientCtx) @@ -407,33 +404,6 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt ethmetricsexp.Setup(config.JSONRPC.MetricsAddress) } - var idxer evertypes.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("indexer", "evm") - idxer = indexer.NewKVIndexer(idxDB, idxLogger, clientCtx) - indexerService := NewEVMIndexerService(idxer, clientCtx.Client.(rpcclient.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 - } - } - if config.API.Enable || config.JSONRPC.Enable { genDoc, err := genDocProvider() if err != nil { @@ -540,6 +510,32 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt ) if config.JSONRPC.Enable { + // Start EVMTxIndexer service + 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("indexer", "evm") + evmTxIndexer := indexer.NewKVIndexer(idxDB, idxLogger, clientCtx) + indexerService := NewEVMIndexerService(evmTxIndexer, clientCtx.Client.(rpcclient.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 + } + + // Start Json-RPC server genDoc, err := genDocProvider() if err != nil { return err @@ -549,7 +545,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt tmEndpoint := "/websocket" tmRPCAddr := cfg.RPC.ListenAddress - httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &config, idxer) + httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &config, evmTxIndexer) if err != nil { return err } From 8c8a81e13fd2673c0b11c347b9b98e7868ef2716 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 15:28:55 +0700 Subject: [PATCH 02/11] use EVMTxIndexer is the only way to search tx --- rpc/backend/backend.go | 4 ++++ rpc/backend/indexer_test.go | 7 ++++++- rpc/backend/tx_info.go | 39 ++++--------------------------------- rpc/backend/tx_info_test.go | 17 ++++++---------- 4 files changed, 20 insertions(+), 47 deletions(-) diff --git a/rpc/backend/backend.go b/rpc/backend/backend.go index b17f06aa39..0f208acd48 100644 --- a/rpc/backend/backend.go +++ b/rpc/backend/backend.go @@ -157,6 +157,10 @@ func NewBackend( panic(err) } + if indexer == nil { + panic("indexer is required") + } + return &Backend{ ctx: context.Background(), clientCtx: clientCtx, diff --git a/rpc/backend/indexer_test.go b/rpc/backend/indexer_test.go index 48865638a7..3e6a4c8106 100644 --- a/rpc/backend/indexer_test.go +++ b/rpc/backend/indexer_test.go @@ -31,7 +31,7 @@ func RegisterIndexerGetByBlockAndIndex(queryClient *mocks.EVMTxIndexer, height i func RegisterIndexerGetByBlockAndIndexError(queryClient *mocks.EVMTxIndexer, height int64, index int32) { queryClient.On("GetByBlockAndIndex", height, index). - Return(nil, sdkerrors.ErrInvalidRequest) + Return(nil, sdkerrors.ErrNotFound) } func RegisterIndexerGetByTxHash(queryClient *mocks.EVMTxIndexer, hash common.Hash, height int64) { @@ -46,3 +46,8 @@ func RegisterIndexerGetByTxHash(queryClient *mocks.EVMTxIndexer, hash common.Has CumulativeGasUsed: mockGasUsed, }, nil) } + +func RegisterIndexerGetByTxHashErr(queryClient *mocks.EVMTxIndexer, hash common.Hash) { + queryClient.On("GetByTxHash", hash). + Return(nil, sdkerrors.ErrNotFound) +} diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index 819cb43a5f..2b967c6aac 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -6,8 +6,6 @@ import ( "math" "math/big" - errorsmod "cosmossdk.io/errors" - rpctypes "github.com/EscanBE/evermint/v12/rpc/types" "github.com/EscanBE/evermint/v12/types" evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" @@ -273,44 +271,15 @@ func (b *Backend) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNum return b.GetTransactionByBlockAndIndex(block, idx) } -// 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/cometbft/cometbft/issues/6539 +// GetTxByEthHash get the ETH-transaction by hash from the indexer func (b *Backend) GetTxByEthHash(hash common.Hash) (*types.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()) - txResult, err := b.queryTendermintTxIndexer(query, func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx { - return txs.GetTxByHash(hash) - }) - if err != nil { - return nil, errorsmod.Wrapf(err, "GetTxByEthHash %s", hash.Hex()) - } - return txResult, nil + return b.indexer.GetByTxHash(hash) } -// GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs +// GetTxByTxIndex get the ETH-transaction by block height and index from the indexer func (b *Backend) GetTxByTxIndex(height int64, index uint) (*types.TxResult, error) { int32Index := int32(index) // #nosec G701 -- checked for int overflow already - if b.indexer != nil { - return b.indexer.GetByBlockAndIndex(height, int32Index) - } - - // 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)) // #nosec G701 -- checked for int overflow already - }) - if err != nil { - return nil, errorsmod.Wrapf(err, "GetTxByTxIndex %d %d", height, index) - } - return txResult, nil + return b.indexer.GetByBlockAndIndex(height, int32Index) } // queryTendermintTxIndexer query tx in tendermint tx indexer diff --git a/rpc/backend/tx_info_test.go b/rpc/backend/tx_info_test.go index a29ffee866..30e9b66f48 100644 --- a/rpc/backend/tx_info_test.go +++ b/rpc/backend/tx_info_test.go @@ -188,7 +188,7 @@ func (suite *BackendTestSuite) TestGetTransactionsByHashPending() { } func (suite *BackendTestSuite) TestGetTxByEthHash() { - msgEthereumTx, bz := suite.buildEthereumTx() + msgEthereumTx, _ := suite.buildEthereumTx() rpcTransaction, _ := rpctypes.NewRPCTransaction(msgEthereumTx.AsTransaction(), common.Hash{}, 0, 0, big.NewInt(1), suite.backend.chainID) testCases := []struct { @@ -201,10 +201,8 @@ func (suite *BackendTestSuite) TestGetTxByEthHash() { { "fail - Indexer disabled can't find transaction", func() { - suite.backend.indexer = nil - client := suite.backend.clientCtx.Client.(*mocks.Client) - query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, common.HexToHash(msgEthereumTx.Hash).Hex()) - RegisterTxSearch(client, query, bz) + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetByTxHashErr(indexer, msgEthereumTx.AsTransaction().Hash()) }, msgEthereumTx, rpcTransaction, @@ -473,8 +471,6 @@ func (suite *BackendTestSuite) TestGetTransactionByBlockNumberAndIndex() { } func (suite *BackendTestSuite) TestGetTransactionByTxIndex() { - _, bz := suite.buildEthereumTx() - testCases := []struct { name string registerMock func() @@ -486,11 +482,10 @@ func (suite *BackendTestSuite) TestGetTransactionByTxIndex() { { "fail - Ethereum tx with query not found", func() { - client := suite.backend.clientCtx.Client.(*mocks.Client) - suite.backend.indexer = nil - RegisterTxSearch(client, "tx.height=0 AND ethereum_tx.txIndex=0", bz) + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetByBlockAndIndexError(indexer, 1, 0) }, - 0, + 1, 0, &evertypes.TxResult{}, false, From 2e52e74806d324fc39dc14ed8adeaaa5cfd17fa4 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 15:56:28 +0700 Subject: [PATCH 03/11] force last indexed block to local node earliest height, solve disadvantage no.2 in ticket #95 --- server/indexer_service.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/server/indexer_service.go b/server/indexer_service.go index b306a389bf..50dc7050ff 100644 --- a/server/indexer_service.go +++ b/server/indexer_service.go @@ -53,7 +53,8 @@ func (eis *EVMIndexerService) OnStart() error { ctx, ServiceName, types.QueryForEvent(types.EventNewBlockHeader).String(), - 0) + 0, + ) if err != nil { return err } @@ -73,15 +74,19 @@ func (eis *EVMIndexerService) OnStart() error { } }() - lastBlock, err := eis.txIdxr.LastIndexedBlock() + lastIndexedBlock, err := eis.txIdxr.LastIndexedBlock() if err != nil { return err } - if lastBlock == -1 { - lastBlock = latestBlock + if lastIndexedBlock == -1 { + lastIndexedBlock = latestBlock + } else if lastIndexedBlock < status.SyncInfo.EarliestBlockHeight { + lastIndexedBlock = status.SyncInfo.EarliestBlockHeight + // kinda unsafe, but we don't have a better way to do this } + for { - if latestBlock <= lastBlock { + if latestBlock <= lastIndexedBlock { // nothing to index. wait for signal of new block select { case <-newBlockSignal: @@ -89,7 +94,7 @@ func (eis *EVMIndexerService) OnStart() error { } continue } - for i := lastBlock + 1; i <= latestBlock; i++ { + for i := lastIndexedBlock + 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) @@ -103,7 +108,7 @@ func (eis *EVMIndexerService) OnStart() error { 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 + lastIndexedBlock = blockResult.Height } } } From 9d550e3366e81d7740d9ffe947811edcf6ce3378 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 16:33:09 +0700 Subject: [PATCH 04/11] prepare ready state for indexer, prevent query indexed data when not ready, solve `disadvantage no.1` --- indexer/kv_indexer.go | 47 ++++++++++++++++++++++++++-- indexer/kv_indexer_test.go | 3 ++ integration_test_util/chain_suite.go | 2 ++ rpc/backend/mocks/indexer.go | 5 +++ rpc/backend/tracing_test.go | 2 ++ rpc/backend/tx_info_test.go | 3 ++ server/indexer_cmd.go | 6 ++++ server/indexer_service.go | 12 ++++++- types/indexer.go | 3 ++ 9 files changed, 79 insertions(+), 4 deletions(-) diff --git a/indexer/kv_indexer.go b/indexer/kv_indexer.go index 0fff390a68..40fb87d409 100644 --- a/indexer/kv_indexer.go +++ b/indexer/kv_indexer.go @@ -3,6 +3,7 @@ package indexer import ( "fmt" "github.com/EscanBE/evermint/v12/constants" + "sync" errorsmod "cosmossdk.io/errors" rpctypes "github.com/EscanBE/evermint/v12/rpc/types" @@ -30,16 +31,28 @@ const ( var _ evertypes.EVMTxIndexer = &KVIndexer{} +var ErrIndexerNotReady = fmt.Errorf("indexer not ready") + // KVIndexer implements an ETH-Tx indexer on a KV db. type KVIndexer struct { db dbm.DB logger log.Logger clientCtx client.Context + + mu *sync.RWMutex + ready bool } // NewKVIndexer creates the KVIndexer func NewKVIndexer(db dbm.DB, logger log.Logger, clientCtx client.Context) *KVIndexer { - return &KVIndexer{db, logger, clientCtx} + return &KVIndexer{ + db: db, + logger: logger, + clientCtx: clientCtx, + + mu: &sync.RWMutex{}, + ready: false, + } } // IndexBlock indexes all ETH Txs of the block. @@ -124,6 +137,18 @@ func (kv *KVIndexer) IndexBlock(block *tmtypes.Block, txResults []*abci.Response return nil } +func (kv *KVIndexer) Ready() { + kv.mu.Lock() + defer kv.mu.Unlock() + kv.ready = true +} + +func (kv *KVIndexer) isReady() bool { + kv.mu.RLock() + defer kv.mu.RUnlock() + return kv.ready +} + // LastIndexedBlock returns the last block number which was indexed and flushed into database. // Returns -1 if db is empty. func (kv *KVIndexer) LastIndexedBlock() (int64, error) { @@ -137,6 +162,22 @@ func (kv *KVIndexer) FirstIndexedBlock() (int64, error) { // GetByTxHash finds eth tx by eth tx hash func (kv *KVIndexer) GetByTxHash(hash common.Hash) (*evertypes.TxResult, error) { + if !kv.isReady() { + return nil, ErrIndexerNotReady + } + return kv.getByTxHash(hash) +} + +// GetByBlockAndIndex finds eth tx by block number and eth tx index +func (kv *KVIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*evertypes.TxResult, error) { + if !kv.isReady() { + return nil, ErrIndexerNotReady + } + return kv.getByBlockAndIndex(blockNumber, txIndex) +} + +// getByTxHash finds eth tx by eth tx hash +func (kv *KVIndexer) getByTxHash(hash common.Hash) (*evertypes.TxResult, error) { bz, err := kv.db.Get(TxHashKey(hash)) if err != nil { return nil, errorsmod.Wrapf(err, "GetByTxHash %s", hash.Hex()) @@ -152,7 +193,7 @@ func (kv *KVIndexer) GetByTxHash(hash common.Hash) (*evertypes.TxResult, error) } // GetByBlockAndIndex finds eth tx by block number and eth tx index -func (kv *KVIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*evertypes.TxResult, error) { +func (kv *KVIndexer) getByBlockAndIndex(blockNumber int64, txIndex int32) (*evertypes.TxResult, error) { bz, err := kv.db.Get(TxIndexKey(blockNumber, txIndex)) if err != nil { return nil, errorsmod.Wrapf(err, "GetByBlockAndIndex %d %d", blockNumber, txIndex) @@ -160,7 +201,7 @@ func (kv *KVIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*ever if len(bz) == 0 { return nil, fmt.Errorf("tx not found, block: %d, eth-index: %d", blockNumber, txIndex) } - return kv.GetByTxHash(common.BytesToHash(bz)) + return kv.getByTxHash(common.BytesToHash(bz)) } // TxHashKey returns the key for db entry: `tx hash -> tx result struct` diff --git a/indexer/kv_indexer_test.go b/indexer/kv_indexer_test.go index 66c3b6278a..df086ec176 100644 --- a/indexer/kv_indexer_test.go +++ b/indexer/kv_indexer_test.go @@ -160,6 +160,9 @@ func TestKVIndexer(t *testing.T) { err = idxer.IndexBlock(tc.block, tc.blockResult) require.NoError(t, err) + + idxer.Ready() + if !tc.expSuccess { first, err := idxer.FirstIndexedBlock() require.NoError(t, err) diff --git a/integration_test_util/chain_suite.go b/integration_test_util/chain_suite.go index 8322df7ef4..620a6cdb42 100644 --- a/integration_test_util/chain_suite.go +++ b/integration_test_util/chain_suite.go @@ -623,6 +623,8 @@ func (suite *ChainIntegrationTestSuite) triggerEvmIndexer(latestHeight int64, bl err = suite.EvmTxIndexer.IndexBlock(tmBlk, tmAbciResponse.DeliverTxs) suite.Require().NoErrorf(err, "failed to index block %d", ch) } + + suite.EvmTxIndexer.Ready() } // GetBlockStoreAndStateStore returns blockStore and stateStore if Tendermint is Enabled. diff --git a/rpc/backend/mocks/indexer.go b/rpc/backend/mocks/indexer.go index 4f9873e2fe..bd28d96741 100644 --- a/rpc/backend/mocks/indexer.go +++ b/rpc/backend/mocks/indexer.go @@ -124,6 +124,11 @@ func (_m *EVMTxIndexer) LastIndexedBlock() (int64, error) { return r0, r1 } +// Ready provides a mock function with given fields: +func (_m *EVMTxIndexer) Ready() { + _m.Called() +} + type mockConstructorTestingTNewEVMTxIndexer interface { mock.TestingT Cleanup(func()) diff --git a/rpc/backend/tracing_test.go b/rpc/backend/tracing_test.go index 9c5bddea89..fe2e0ac04a 100644 --- a/rpc/backend/tracing_test.go +++ b/rpc/backend/tracing_test.go @@ -190,6 +190,8 @@ func (suite *BackendTestSuite) TestTraceTransaction() { err := suite.backend.indexer.IndexBlock(tc.block, tc.responseBlock) suite.Require().NoError(err) + suite.backend.indexer.Ready() + txResult, err := suite.backend.TraceTransaction(txHash, nil) if tc.expPass { diff --git a/rpc/backend/tx_info_test.go b/rpc/backend/tx_info_test.go index 30e9b66f48..5acf48fe71 100644 --- a/rpc/backend/tx_info_test.go +++ b/rpc/backend/tx_info_test.go @@ -114,6 +114,7 @@ func (suite *BackendTestSuite) TestGetTransactionByHash() { suite.backend.indexer = indexer.NewKVIndexer(db, tmlog.NewNopLogger(), suite.backend.clientCtx) err := suite.backend.indexer.IndexBlock(block, responseDeliver) suite.Require().NoError(err) + suite.backend.indexer.Ready() rpcTx, err := suite.backend.GetTransactionByHash(common.HexToHash(tc.tx.Hash)) @@ -358,6 +359,7 @@ func (suite *BackendTestSuite) TestGetTransactionByBlockAndIndex() { block := &types.Block{Header: types.Header{Height: 1, ChainID: "test"}, Data: types.Data{Txs: []types.Tx{txBz}}} err := suite.backend.indexer.IndexBlock(block, defaultResponseDeliverTx) suite.Require().NoError(err) + suite.backend.indexer.Ready() _, err = RegisterBlockResults(client, 1) suite.Require().NoError(err) RegisterBaseFee(queryClient, sdk.NewInt(1)) @@ -609,6 +611,7 @@ func (suite *BackendTestSuite) TestGetTransactionReceipt() { suite.backend.indexer = indexer.NewKVIndexer(db, tmlog.NewNopLogger(), suite.backend.clientCtx) err := suite.backend.indexer.IndexBlock(tc.block, tc.blockResult) suite.Require().NoError(err) + suite.backend.indexer.Ready() txReceipt, err := suite.backend.GetTransactionReceipt(common.HexToHash(tc.tx.Hash)) if tc.expPass { diff --git a/server/indexer_cmd.go b/server/indexer_cmd.go index b08200a2fa..9bebd50a66 100644 --- a/server/indexer_cmd.go +++ b/server/indexer_cmd.go @@ -93,6 +93,9 @@ func NewIndexTxCmd() *cobra.Command { return err } } + idxer.Ready() + + break case "forward": latest, err := idxer.LastIndexedBlock() if err != nil { @@ -107,6 +110,9 @@ func NewIndexTxCmd() *cobra.Command { return err } } + idxer.Ready() + + break default: return fmt.Errorf("unknown direction %s", args[0]) } diff --git a/server/indexer_service.go b/server/indexer_service.go index 50dc7050ff..c3eb4aa8e9 100644 --- a/server/indexer_service.go +++ b/server/indexer_service.go @@ -85,9 +85,19 @@ func (eis *EVMIndexerService) OnStart() error { // kinda unsafe, but we don't have a better way to do this } + var isIndexerMarkedReady bool + for { - if latestBlock <= lastIndexedBlock { + if lastIndexedBlock >= latestBlock { // nothing to index. wait for signal of new block + + // mark indexer ready if not yet + if !isIndexerMarkedReady { + eis.txIdxr.Ready() + isIndexerMarkedReady = true + } + + // wait select { case <-newBlockSignal: case <-time.After(NewBlockWaitTimeout): diff --git a/types/indexer.go b/types/indexer.go index ccb77ce126..06e4b0e5c9 100644 --- a/types/indexer.go +++ b/types/indexer.go @@ -18,6 +18,9 @@ type EVMTxIndexer interface { // Notes: no guarantee data is flushed into database after this function returns, it might be flushed at later point. IndexBlock(*tmtypes.Block, []*abci.ResponseDeliverTx) error + // Ready is an external trigger that indicates the indexer is ready to serve requests. + Ready() + // GetByTxHash returns nil if tx not found. GetByTxHash(common.Hash) (*TxResult, error) From c2720669f94af76ff66ab01c6ce9c8fe5976eb2a Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 17:33:45 +0700 Subject: [PATCH 05/11] improve `eth_blockNumber` by using last request indexed block number from indexer instead of using gRPC call then check header --- indexer/kv_indexer.go | 34 +++++++++++++++++++++++++++++----- rpc/backend/blocks.go | 32 ++++++++------------------------ rpc/backend/indexer_test.go | 5 +++++ rpc/backend/mocks/indexer.go | 28 ++++++++++++++++++++++++++++ types/indexer.go | 3 +++ 5 files changed, 73 insertions(+), 29 deletions(-) diff --git a/indexer/kv_indexer.go b/indexer/kv_indexer.go index 40fb87d409..bdf32cc140 100644 --- a/indexer/kv_indexer.go +++ b/indexer/kv_indexer.go @@ -39,16 +39,18 @@ type KVIndexer struct { logger log.Logger clientCtx client.Context - mu *sync.RWMutex - ready bool + mu *sync.RWMutex + ready bool + lastRequestIndexedBlock int64 // indexer does not index empty block so LastIndexedBlock() might be different from last request indexed block. } // NewKVIndexer creates the KVIndexer func NewKVIndexer(db dbm.DB, logger log.Logger, clientCtx client.Context) *KVIndexer { return &KVIndexer{ - db: db, - logger: logger, - clientCtx: clientCtx, + db: db, + logger: logger, + clientCtx: clientCtx, + lastRequestIndexedBlock: -1, mu: &sync.RWMutex{}, ready: false, @@ -134,6 +136,13 @@ func (kv *KVIndexer) IndexBlock(block *tmtypes.Block, txResults []*abci.Response if err := batch.Write(); err != nil { return errorsmod.Wrapf(err, "IndexBlock %d, write batch", block.Height) } + + kv.mu.Lock() + defer kv.mu.Unlock() + if kv.lastRequestIndexedBlock < height { + kv.lastRequestIndexedBlock = height + } + return nil } @@ -176,6 +185,21 @@ func (kv *KVIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*ever return kv.getByBlockAndIndex(blockNumber, txIndex) } +// GetLastRequestIndexedBlock returns the block height of the latest success called to IndexBlock() +func (kv *KVIndexer) GetLastRequestIndexedBlock() (int64, error) { + kv.mu.RLock() + defer kv.mu.RUnlock() + if !kv.ready { + return -1, ErrIndexerNotReady + } + + if kv.lastRequestIndexedBlock == -1 { + return LoadLastBlock(kv.db) + } + + return kv.lastRequestIndexedBlock, nil +} + // getByTxHash finds eth tx by eth tx hash func (kv *KVIndexer) getByTxHash(hash common.Hash) (*evertypes.TxResult, error) { bz, err := kv.db.Get(TxHashKey(hash)) diff --git a/rpc/backend/blocks.go b/rpc/backend/blocks.go index ae8fe45977..34bf6176c7 100644 --- a/rpc/backend/blocks.go +++ b/rpc/backend/blocks.go @@ -2,45 +2,29 @@ package backend import ( "fmt" - tmrpcclient "github.com/cometbft/cometbft/rpc/client" - "math" - "math/big" - "strconv" - rpctypes "github.com/EscanBE/evermint/v12/rpc/types" evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" - grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" "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/trie" "github.com/pkg/errors" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" + "math" + "math/big" ) -// BlockNumber returns the current block number in abci app state. Because abci -// app state could lag behind from tendermint latest block, it's more stable for -// the client to use the latest block number in abci app state than tendermint -// rpc. +// BlockNumber returns the current block number, based on indexed block state of the EVMTxIndexer. func (b *Backend) BlockNumber() (hexutil.Uint64, error) { - // do any grpc query, ignore the response and use the returned block height - var header metadata.MD - _, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{}, grpc.Header(&header)) + height, err := b.indexer.GetLastRequestIndexedBlock() if err != nil { - return hexutil.Uint64(0), err + return 0, err } - blockHeightHeader := header.Get(grpctypes.GRPCBlockHeightHeader) - if headerLen := len(blockHeightHeader); headerLen != 1 { - return 0, fmt.Errorf("unexpected '%s' gRPC header length; got %d, expected: %d", grpctypes.GRPCBlockHeightHeader, headerLen, 1) - } - - height, err := strconv.ParseUint(blockHeightHeader[0], 10, 64) - if err != nil { - return 0, fmt.Errorf("failed to parse block height: %w", err) + if height < 1 { + return 0, fmt.Errorf("no block indexed yet") } if height > math.MaxInt64 { diff --git a/rpc/backend/indexer_test.go b/rpc/backend/indexer_test.go index 3e6a4c8106..faec87b1a6 100644 --- a/rpc/backend/indexer_test.go +++ b/rpc/backend/indexer_test.go @@ -51,3 +51,8 @@ func RegisterIndexerGetByTxHashErr(queryClient *mocks.EVMTxIndexer, hash common. queryClient.On("GetByTxHash", hash). Return(nil, sdkerrors.ErrNotFound) } + +func RegisterIndexerGetLastRequestIndexedBlock(queryClient *mocks.EVMTxIndexer, height int64) { + queryClient.On("GetLastRequestIndexedBlock"). + Return(height, nil) +} diff --git a/rpc/backend/mocks/indexer.go b/rpc/backend/mocks/indexer.go index bd28d96741..b274b776f0 100644 --- a/rpc/backend/mocks/indexer.go +++ b/rpc/backend/mocks/indexer.go @@ -78,6 +78,34 @@ func (_m *EVMTxIndexer) GetByTxHash(_a0 common.Hash) (*types.TxResult, error) { return r0, r1 } +// GetLastIndexedBlock provides a mock function with given fields: +func (_m *EVMTxIndexer) GetLastRequestIndexedBlock() (int64, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetLastRequestIndexedBlock") + } + + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func() (int64, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // IndexBlock provides a mock function with given fields: _a0, _a1 func (_m *EVMTxIndexer) IndexBlock(_a0 *cometbfttypes.Block, _a1 []*abcitypes.ResponseDeliverTx) error { ret := _m.Called(_a0, _a1) diff --git a/types/indexer.go b/types/indexer.go index 06e4b0e5c9..385c252845 100644 --- a/types/indexer.go +++ b/types/indexer.go @@ -26,4 +26,7 @@ type EVMTxIndexer interface { // GetByBlockAndIndex returns nil if tx not found. GetByBlockAndIndex(int64, int32) (*TxResult, error) + + // GetLastRequestIndexedBlock returns the block height of the latest success called to IndexBlock() + GetLastRequestIndexedBlock() (int64, error) } From 9eb4af3509310614ccc11476eb07b5b4f6d42171 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 21:56:02 +0700 Subject: [PATCH 06/11] handle edge cases when indexer not ready yet --- rpc/backend/call_tx.go | 7 ++++++- rpc/backend/chain_info.go | 4 ++++ rpc/backend/tx_info.go | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index b8ba75ce05..27ad71370d 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -386,7 +386,12 @@ func (b *Backend) GasPrice() (*hexutil.Big, error) { result *big.Int err error ) - if head := b.CurrentHeader(); head.BaseFee != nil { + + head := b.CurrentHeader() + if head == nil { + return nil, errors.New("failed to get current block header") + } + if head.BaseFee != nil { result, err = b.SuggestGasTipCap(head.BaseFee) if err != nil { return nil, err diff --git a/rpc/backend/chain_info.go b/rpc/backend/chain_info.go index ead974bcf8..e73fb91381 100644 --- a/rpc/backend/chain_info.go +++ b/rpc/backend/chain_info.go @@ -3,6 +3,7 @@ package backend import ( "errors" "fmt" + "github.com/EscanBE/evermint/v12/indexer" tmrpcclient "github.com/cometbft/cometbft/rpc/client" "math/big" "strconv" @@ -28,6 +29,9 @@ func (b *Backend) ChainID() (*hexutil.Big, error) { // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config bn, err := b.BlockNumber() if err != nil { + if err == indexer.ErrIndexerNotReady { + return nil, err + } b.logger.Debug("failed to fetch latest block number", "error", err.Error()) return (*hexutil.Big)(eip155ChainID), nil } diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index 2b967c6aac..cb87ed8df4 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -2,6 +2,7 @@ package backend import ( "fmt" + "github.com/EscanBE/evermint/v12/indexer" tmrpcclient "github.com/cometbft/cometbft/rpc/client" "math" "math/big" @@ -144,6 +145,10 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (*rpctypes.RPCReceipt, res, err := b.GetTxByEthHash(hash) if err != nil { + if err == indexer.ErrIndexerNotReady { + b.logger.Debug("indexer not ready", "hash", hexTx) + return nil, err + } b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error()) return nil, nil } From bcd5c3261b6c28b9dae1823849d2d4e660dcdb31 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 21:56:07 +0700 Subject: [PATCH 07/11] fix testcases after improvement of `eth_blockNumber` --- rpc/backend/account_info_test.go | 50 +++-- rpc/backend/blocks_test.go | 350 +++++++++++++++++++++++++------ rpc/backend/call_tx_test.go | 110 ++++++++-- rpc/backend/chain_info_test.go | 71 +++++-- rpc/backend/indexer_test.go | 16 ++ rpc/backend/node_info_test.go | 7 +- rpc/backend/sign_tx_test.go | 109 +++++++--- rpc/backend/tx_info_test.go | 45 +++- 8 files changed, 600 insertions(+), 158 deletions(-) diff --git a/rpc/backend/account_info_test.go b/rpc/backend/account_info_test.go index e8d63fceaf..b0b2b28719 100644 --- a/rpc/backend/account_info_test.go +++ b/rpc/backend/account_info_test.go @@ -4,17 +4,15 @@ import ( "fmt" "math/big" + "github.com/EscanBE/evermint/v12/rpc/backend/mocks" + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + utiltx "github.com/EscanBE/evermint/v12/testutil/tx" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" tmrpcclient "github.com/cometbft/cometbft/rpc/client" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "google.golang.org/grpc/metadata" - - "github.com/EscanBE/evermint/v12/rpc/backend/mocks" - rpctypes "github.com/EscanBE/evermint/v12/rpc/types" - utiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" ) func (suite *BackendTestSuite) TestGetCode() { @@ -367,9 +365,8 @@ func (suite *BackendTestSuite) TestGetTransactionCount() { false, rpctypes.NewBlockNumber(big.NewInt(1)), func(addr common.Address, bn rpctypes.BlockNumber) { - var header metadata.MD - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParams(queryClient, &header, 1) + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, true, hexutil.Uint64(0), @@ -379,15 +376,36 @@ func (suite *BackendTestSuite) TestGetTransactionCount() { false, rpctypes.NewBlockNumber(big.NewInt(10000)), func(addr common.Address, bn rpctypes.BlockNumber) { - var header metadata.MD - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParams(queryClient, &header, 1) + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, false, hexutil.Uint64(0), }, + { + name: "fail - indexer not ready", + accExists: false, + blockNum: rpctypes.NewBlockNumber(big.NewInt(10000)), + registerMock: func(addr common.Address, bn rpctypes.BlockNumber) { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + expPass: false, + expTxCount: hexutil.Uint64(0), + }, + { + name: "fail - indexer returns error", + accExists: false, + blockNum: rpctypes.NewBlockNumber(big.NewInt(10000)), + registerMock: func(addr common.Address, bn rpctypes.BlockNumber) { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) + }, + expPass: false, + expTxCount: hexutil.Uint64(0), + }, // TODO: Error mocking the GetAccount call - problem with Any type - // { + //{ // "pass - returns the number of transactions at the given address up to the given block number", // true, // rpctypes.NewBlockNumber(big.NewInt(1)), @@ -395,6 +413,7 @@ func (suite *BackendTestSuite) TestGetTransactionCount() { // client := suite.backend.clientCtx.Client.(*mocks.Client) // account, err := suite.backend.clientCtx.AccountRetriever.GetAccount(suite.backend.clientCtx, suite.acc) // suite.Require().NoError(err) + // // request := &authtypes.QueryAccountRequest{Address: sdk.AccAddress(suite.acc.Bytes()).String()} // requestMarshal, _ := request.Marshal() // RegisterABCIQueryAccount( @@ -403,10 +422,13 @@ func (suite *BackendTestSuite) TestGetTransactionCount() { // tmrpcclient.ABCIQueryOptions{Height: int64(1), Prove: false}, // account, // ) + // + // indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + // RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) // }, // true, // hexutil.Uint64(0), - // }, + //}, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { diff --git a/rpc/backend/blocks_test.go b/rpc/backend/blocks_test.go index 4cfbbe83d2..dfec45dc11 100644 --- a/rpc/backend/blocks_test.go +++ b/rpc/backend/blocks_test.go @@ -7,6 +7,10 @@ import ( "cosmossdk.io/math" + "github.com/EscanBE/evermint/v12/rpc/backend/mocks" + ethrpc "github.com/EscanBE/evermint/v12/rpc/types" + utiltx "github.com/EscanBE/evermint/v12/testutil/tx" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" "github.com/cometbft/cometbft/abci/types" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" tmtypes "github.com/cometbft/cometbft/types" @@ -15,12 +19,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/trie" - "google.golang.org/grpc/metadata" - - "github.com/EscanBE/evermint/v12/rpc/backend/mocks" - ethrpc "github.com/EscanBE/evermint/v12/rpc/types" - utiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" ) func (suite *BackendTestSuite) TestBlockNumber() { @@ -31,41 +29,42 @@ func (suite *BackendTestSuite) TestBlockNumber() { expPass bool }{ { - "fail - invalid block header height", - func() { - var header metadata.MD - height := int64(1) - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParamsInvalidHeight(queryClient, &header, height) + name: "pass - indexer indexed up to block 1", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, - 0x0, - false, + expBlockNumber: 0x1, + expPass: true, }, { - "fail - invalid block header", - func() { - var header metadata.MD - height := int64(1) - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParamsInvalidHeader(queryClient, &header, height) + name: "pass - indexer indexed up to block 3", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 3) }, - 0x0, - false, + expBlockNumber: 0x3, + expPass: true, }, { - "pass - app state header height 1", - func() { - var header metadata.MD - height := int64(1) - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParams(queryClient, &header, height) + name: "fail - indexer not ready", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) }, - 0x1, - true, + expPass: false, + }, + { + name: "fail - indexer returns error", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) + }, + expPass: false, }, } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.Run(tc.name, func() { suite.SetupTest() // reset test and queries tc.registerMock() @@ -169,9 +168,10 @@ func (suite *BackendTestSuite) TestGetBlockByNumber() { RegisterBaseFee(queryClient, baseFee) RegisterValidatorAccount(queryClient, validator) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, false, true, @@ -195,16 +195,92 @@ func (suite *BackendTestSuite) TestGetBlockByNumber() { RegisterBaseFee(queryClient, baseFee) RegisterValidatorAccount(queryClient, validator) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) RegisterIndexerGetByTxHash(indexer, msgEthereumTx.AsTransaction().Hash(), height) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, false, true, }, + { + name: "fail - indexer not ready", + blockNumber: ethrpc.BlockNumber(1), + fullTx: false, + baseFee: sdk.NewInt(1).BigInt(), + validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), + tx: msgEthereumTx, + txBz: bz, + registerMock: func(blockNum ethrpc.BlockNumber, baseFee math.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlock(client, height, txBz) + blockRes, _ = RegisterBlockResults(client, blockNum.Int64()) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterValidatorAccount(queryClient, validator) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + expNoop: false, + expPass: false, + }, + { + name: "fail - indexer returns error when fetching tx", + blockNumber: ethrpc.BlockNumber(1), + fullTx: false, + baseFee: sdk.NewInt(1).BigInt(), + validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), + tx: msgEthereumTx, + txBz: bz, + registerMock: func(blockNum ethrpc.BlockNumber, baseFee math.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlock(client, height, txBz) + blockRes, _ = RegisterBlockResults(client, blockNum.Int64()) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + RegisterParamsWithoutHeader(queryClient, height) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetByTxHashErr(indexer, msgEthereumTx.AsTransaction().Hash()) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) + }, + expNoop: false, + expPass: false, + }, + { + name: "pass - indexer returns error when get latest block number", + blockNumber: ethrpc.BlockNumber(1), + fullTx: false, + baseFee: sdk.NewInt(1).BigInt(), + validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), + tx: msgEthereumTx, + txBz: bz, + registerMock: func(blockNum ethrpc.BlockNumber, baseFee math.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlock(client, height, txBz) + blockRes, _ = RegisterBlockResults(client, blockNum.Int64()) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetByTxHash(indexer, msgEthereumTx.AsTransaction().Hash(), height) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) + }, + expNoop: false, + expPass: true, + }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { @@ -326,9 +402,10 @@ func (suite *BackendTestSuite) TestGetBlockByHash() { RegisterBaseFee(queryClient, baseFee) RegisterValidatorAccount(queryClient, validator) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, false, true, @@ -353,16 +430,59 @@ func (suite *BackendTestSuite) TestGetBlockByHash() { RegisterBaseFee(queryClient, baseFee) RegisterValidatorAccount(queryClient, validator) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) RegisterIndexerGetByTxHash(indexer, msgEthereumTx.AsTransaction().Hash(), height) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, false, true, }, + { + name: "fail - indexer not ready", + hash: common.BytesToHash(block.Hash()), + fullTx: true, + baseFee: sdk.NewInt(1).BigInt(), + validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), + registerMock: func(hash common.Hash, baseFee math.Int, validator sdk.AccAddress, txBz []byte) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, hash, txBz) + + blockRes, _ = RegisterBlockResults(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterValidatorAccount(queryClient, validator) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + expPass: false, + }, + { + name: "success - indexer returns error", + hash: common.BytesToHash(block.Hash()), + fullTx: true, + baseFee: sdk.NewInt(1).BigInt(), + validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), + registerMock: func(hash common.Hash, baseFee math.Int, validator sdk.AccAddress, txBz []byte) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, hash, txBz) + + blockRes, _ = RegisterBlockResults(client, height) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) + }, + expPass: true, + }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { @@ -587,25 +707,43 @@ func (suite *BackendTestSuite) TestTendermintBlockByNumber() { true, }, { - "fail - blockNum < 0 with app state height error", + "fail - blockNum < 0 with indexer returns error", ethrpc.BlockNumber(-1), func(_ ethrpc.BlockNumber) { - var header metadata.MD - appHeight := int64(1) - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParamsError(queryClient, &header, appHeight) + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) }, false, false, }, { - "pass - blockNum < 0 with app state height >= 1", + name: "fail - blockNum < 0 with indexer not ready", + blockNumber: ethrpc.BlockNumber(-1), + registerMock: func(_ ethrpc.BlockNumber) { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + found: false, + expPass: false, + }, + { + name: "fail - blockNum < 0 with indexer returns error", + blockNumber: ethrpc.BlockNumber(-1), + registerMock: func(_ ethrpc.BlockNumber) { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) + }, + found: false, + expPass: false, + }, + { + "pass - blockNum < 0 with indexed height >= 1", ethrpc.BlockNumber(-1), func(blockNum ethrpc.BlockNumber) { - var header metadata.MD appHeight := int64(1) - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParams(queryClient, &header, appHeight) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, appHeight) tmHeight := appHeight client := suite.backend.clientCtx.Client.(*mocks.Client) @@ -947,9 +1085,10 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { client := suite.backend.clientCtx.Client.(*mocks.Client) RegisterConsensusParams(client, height) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, false, true, @@ -975,12 +1114,11 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { client := suite.backend.clientCtx.Client.(*mocks.Client) RegisterConsensusParams(client, height) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) RegisterIndexerGetByTxHash(indexer, msgEthereumTx.AsTransaction().Hash(), height) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, true, true, @@ -1006,12 +1144,11 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { client := suite.backend.clientCtx.Client.(*mocks.Client) RegisterConsensusParams(client, height) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) RegisterIndexerGetByTxHash(indexer, msgEthereumTx.AsTransaction().Hash(), height) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, true, true, @@ -1037,12 +1174,11 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { client := suite.backend.clientCtx.Client.(*mocks.Client) RegisterConsensusParamsError(client, height) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) RegisterIndexerGetByTxHash(indexer, msgEthereumTx.AsTransaction().Hash(), height) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, true, true, @@ -1074,9 +1210,10 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { client := suite.backend.clientCtx.Client.(*mocks.Client) RegisterConsensusParams(client, height) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, false, true, @@ -1102,12 +1239,11 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { client := suite.backend.clientCtx.Client.(*mocks.Client) RegisterConsensusParams(client, height) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) RegisterIndexerGetByTxHash(indexer, msgEthereumTx.AsTransaction().Hash(), height) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, true, true, @@ -1133,16 +1269,96 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { client := suite.backend.clientCtx.Client.(*mocks.Client) RegisterConsensusParams(client, height) - var header metadata.MD - RegisterParams(queryClient, &header, height) RegisterParamsWithoutHeader(queryClient, height) indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) RegisterIndexerGetByTxHash(indexer, msgEthereumTx.AsTransaction().Hash(), height) + RegisterIndexerGetLastRequestIndexedBlock(indexer, height) }, true, true, }, + { + name: "fail - indexer not ready", + baseFee: sdk.NewInt(1).BigInt(), + validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), + height: int64(1), + resBlock: &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + blockRes: &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + fullTx: false, + registerMock: func(baseFee math.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterValidatorAccount(queryClient, validator) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + expTxs: false, + expPass: false, + }, + { + name: "pass - indexer returns error when getting latest block number", + baseFee: sdk.NewInt(1).BigInt(), + validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), + height: int64(1), + resBlock: &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + blockRes: &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + fullTx: true, + registerMock: func(baseFee math.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetByTxHash(indexer, msgEthereumTx.AsTransaction().Hash(), height) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) + }, + expTxs: true, + expPass: true, + }, + { + name: "fail - indexer returns error when getting tx by hash", + baseFee: sdk.NewInt(1).BigInt(), + validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), + height: int64(1), + resBlock: &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + blockRes: &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + fullTx: false, + registerMock: func(baseFee math.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + + RegisterParamsWithoutHeader(queryClient, height) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetByTxHashErr(indexer, msgEthereumTx.AsTransaction().Hash()) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) + }, + expTxs: false, + expPass: false, + }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { @@ -1151,6 +1367,13 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { block, err := suite.backend.RPCBlockFromTendermintBlock(tc.resBlock, tc.blockRes, tc.fullTx) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + return + } + var expBlock map[string]interface{} header := tc.resBlock.Block.Header gasLimit := int64(^uint32(0)) // for `MaxGas = -1` (DefaultConsensusParams) @@ -1180,10 +1403,7 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { ) if tc.expPass { - suite.Require().NoError(err) suite.Equal(expBlock, block) - } else { - suite.Require().Error(err) } }) } diff --git a/rpc/backend/call_tx_test.go b/rpc/backend/call_tx_test.go index 92c167bbf9..a1aff190ec 100644 --- a/rpc/backend/call_tx_test.go +++ b/rpc/backend/call_tx_test.go @@ -14,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" - "google.golang.org/grpc/metadata" ) func (suite *BackendTestSuite) TestResend() { @@ -61,15 +60,16 @@ func (suite *BackendTestSuite) TestResend() { { "pass - Can't set Tx defaults BaseFee disabled", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParams(queryClient, &header, 1) _, err := RegisterBlock(client, 1, nil) suite.Require().NoError(err) _, err = RegisterBlockResults(client, 1) suite.Require().NoError(err) RegisterBaseFeeDisabled(queryClient) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -83,17 +83,18 @@ func (suite *BackendTestSuite) TestResend() { { "pass - Can't set Tx defaults", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) feeMarketClient := suite.backend.queryClient.FeeMarket.(*mocks.FeeMarketQueryClient) - RegisterParams(queryClient, &header, 1) RegisterFeeMarketParams(feeMarketClient, 1) _, err := RegisterBlock(client, 1, nil) suite.Require().NoError(err) _, err = RegisterBlockResults(client, 1) suite.Require().NoError(err) RegisterBaseFee(queryClient, baseFee) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -106,15 +107,16 @@ func (suite *BackendTestSuite) TestResend() { { "pass - MaxFeePerGas is nil", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParams(queryClient, &header, 1) _, err := RegisterBlock(client, 1, nil) suite.Require().NoError(err) _, err = RegisterBlockResults(client, 1) suite.Require().NoError(err) RegisterBaseFeeDisabled(queryClient) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -144,11 +146,11 @@ func (suite *BackendTestSuite) TestResend() { { "fail - Block error", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParams(queryClient, &header, 1) RegisterBlockError(client, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -161,15 +163,16 @@ func (suite *BackendTestSuite) TestResend() { { "pass - MaxFeePerGas is nil", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParams(queryClient, &header, 1) _, err := RegisterBlock(client, 1, nil) suite.Require().NoError(err) _, err = RegisterBlockResults(client, 1) suite.Require().NoError(err) RegisterBaseFee(queryClient, baseFee) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -186,15 +189,16 @@ func (suite *BackendTestSuite) TestResend() { { "pass - Chain Id is nil", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParams(queryClient, &header, 1) _, err := RegisterBlock(client, 1, nil) suite.Require().NoError(err) _, err = RegisterBlockResults(client, 1) suite.Require().NoError(err) RegisterBaseFee(queryClient, baseFee) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -209,7 +213,6 @@ func (suite *BackendTestSuite) TestResend() { { "fail - Pending transactions error", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) _, err := RegisterBlock(client, 1, nil) @@ -218,9 +221,11 @@ func (suite *BackendTestSuite) TestResend() { suite.Require().NoError(err) RegisterBaseFee(queryClient, baseFee) RegisterEstimateGas(queryClient, callArgs) - RegisterParams(queryClient, &header, 1) RegisterParamsWithoutHeader(queryClient, 1) RegisterUnconfirmedTxsError(client, nil) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -239,7 +244,6 @@ func (suite *BackendTestSuite) TestResend() { { "fail - Not Ethereum txs", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) _, err := RegisterBlock(client, 1, nil) @@ -248,9 +252,11 @@ func (suite *BackendTestSuite) TestResend() { suite.Require().NoError(err) RegisterBaseFee(queryClient, baseFee) RegisterEstimateGas(queryClient, callArgs) - RegisterParams(queryClient, &header, 1) RegisterParamsWithoutHeader(queryClient, 1) RegisterUnconfirmedTxsEmpty(client, nil) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -266,6 +272,46 @@ func (suite *BackendTestSuite) TestResend() { common.Hash{}, false, }, + { + name: "fail - indexer not ready", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + args: evmtypes.TransactionArgs{ + Nonce: &txNonce, + To: &toAddr, + MaxFeePerGas: gasPrice, + MaxPriorityFeePerGas: gasPrice, + Value: gasPrice, + Gas: nil, + ChainID: callArgs.ChainID, + }, + gasPrice: gasPrice, + gasLimit: nil, + expHash: common.Hash{}, + expPass: false, + }, + { + name: "fail - indexer returns error", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + args: evmtypes.TransactionArgs{ + Nonce: &txNonce, + To: &toAddr, + MaxFeePerGas: gasPrice, + MaxPriorityFeePerGas: gasPrice, + Value: gasPrice, + Gas: nil, + ChainID: callArgs.ChainID, + }, + gasPrice: gasPrice, + gasLimit: nil, + expHash: common.Hash{}, + expPass: false, + }, } for _, tc := range testCases { @@ -472,17 +518,18 @@ func (suite *BackendTestSuite) TestGasPrice() { { "pass - get the default gas price", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) feeMarketClient := suite.backend.queryClient.FeeMarket.(*mocks.FeeMarketQueryClient) RegisterFeeMarketParams(feeMarketClient, 1) - RegisterParams(queryClient, &header, 1) _, err := RegisterBlock(client, 1, nil) suite.Require().NoError(err) _, err = RegisterBlockResults(client, 1) suite.Require().NoError(err) RegisterBaseFee(queryClient, sdk.NewInt(1)) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, defaultGasPrice, true, @@ -490,21 +537,40 @@ func (suite *BackendTestSuite) TestGasPrice() { { "fail - can't get gasFee, FeeMarketParams error", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) feeMarketClient := suite.backend.queryClient.FeeMarket.(*mocks.FeeMarketQueryClient) RegisterFeeMarketParamsError(feeMarketClient, 1) - RegisterParams(queryClient, &header, 1) _, err := RegisterBlock(client, 1, nil) suite.Require().NoError(err) _, err = RegisterBlockResults(client, 1) suite.Require().NoError(err) RegisterBaseFee(queryClient, sdk.NewInt(1)) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, defaultGasPrice, false, }, + { + name: "fail - indexer not ready", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + expGas: defaultGasPrice, + expPass: false, + }, + { + name: "fail - indexer returns error", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) + }, + expGas: defaultGasPrice, + expPass: false, + }, } for _, tc := range testCases { diff --git a/rpc/backend/chain_info_test.go b/rpc/backend/chain_info_test.go index 0664c8da30..c631bf4e2b 100644 --- a/rpc/backend/chain_info_test.go +++ b/rpc/backend/chain_info_test.go @@ -8,8 +8,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ethrpc "github.com/ethereum/go-ethereum/rpc" - "google.golang.org/grpc/metadata" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cometbft/cometbft/abci/types" @@ -163,14 +161,31 @@ func (suite *BackendTestSuite) TestChainId() { expPass bool }{ { - "pass - block is at or past the EIP-155 replay-protection fork block, return chainID from config ", - func() { - var header metadata.MD - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterParamsInvalidHeight(queryClient, &header, int64(1)) + name: "pass - block is at or past the EIP-155 replay-protection fork block, return chainID from config", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) }, - expChainID, - true, + expChainID: expChainID, + expPass: true, + }, + { + name: "fail - indexer not ready", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + expChainID: nil, + expPass: false, + }, + { + name: "pass - indexer returns error", + registerMock: func() { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) + }, + expChainID: expChainID, + expPass: true, }, } @@ -332,12 +347,12 @@ func (suite *BackendTestSuite) TestFeeHistory() { expPass bool }{ { - "fail - can't get params ", + "fail - can't get latest block height", func(validator sdk.AccAddress) { - var header metadata.MD - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) suite.backend.cfg.JSONRPC.FeeHistoryCap = 0 - RegisterParamsError(queryClient, &header, ethrpc.BlockNumber(1).Int64()) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) }, 1, -1, @@ -345,13 +360,27 @@ func (suite *BackendTestSuite) TestFeeHistory() { nil, false, }, + { + name: "fail - indexer not ready", + registerMock: func(validator sdk.AccAddress) { + suite.backend.cfg.JSONRPC.FeeHistoryCap = 0 + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + }, + userBlockCount: 1, + latestBlock: -1, + expFeeHistory: nil, + validator: nil, + expPass: false, + }, { "fail - user block count higher than max block count ", func(validator sdk.AccAddress) { - var header metadata.MD - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) suite.backend.cfg.JSONRPC.FeeHistoryCap = 0 - RegisterParams(queryClient, &header, ethrpc.BlockNumber(1).Int64()) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, 1, -1, @@ -401,9 +430,10 @@ func (suite *BackendTestSuite) TestFeeHistory() { RegisterValidatorAccount(queryClient, validator) RegisterConsensusParams(client, 1) - var header metadata.MD - RegisterParams(queryClient, &header, 1) RegisterParamsWithoutHeader(queryClient, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, 1, 1, @@ -414,7 +444,6 @@ func (suite *BackendTestSuite) TestFeeHistory() { { "pass - Valid FeeHistoryResults object", func(validator sdk.AccAddress) { - var header metadata.MD baseFee := sdk.NewInt(1) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) client := suite.backend.clientCtx.Client.(*mocks.Client) @@ -426,8 +455,10 @@ func (suite *BackendTestSuite) TestFeeHistory() { RegisterBaseFee(queryClient, baseFee) RegisterValidatorAccount(queryClient, validator) RegisterConsensusParams(client, 1) - RegisterParams(queryClient, &header, 1) RegisterParamsWithoutHeader(queryClient, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, 1, 1, diff --git a/rpc/backend/indexer_test.go b/rpc/backend/indexer_test.go index faec87b1a6..490d0b9a18 100644 --- a/rpc/backend/indexer_test.go +++ b/rpc/backend/indexer_test.go @@ -1,6 +1,7 @@ package backend import ( + "github.com/EscanBE/evermint/v12/indexer" "github.com/EscanBE/evermint/v12/rpc/backend/mocks" "github.com/EscanBE/evermint/v12/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -52,7 +53,22 @@ func RegisterIndexerGetByTxHashErr(queryClient *mocks.EVMTxIndexer, hash common. Return(nil, sdkerrors.ErrNotFound) } +func RegisterIndexerGetByTxHashErrNotReady(queryClient *mocks.EVMTxIndexer, hash common.Hash) { + queryClient.On("GetByTxHash", hash). + Return(nil, indexer.ErrIndexerNotReady) +} + func RegisterIndexerGetLastRequestIndexedBlock(queryClient *mocks.EVMTxIndexer, height int64) { queryClient.On("GetLastRequestIndexedBlock"). Return(height, nil) } + +func RegisterIndexerGetLastRequestIndexedBlockErr(queryClient *mocks.EVMTxIndexer) { + queryClient.On("GetLastRequestIndexedBlock"). + Return(int64(0), sdkerrors.ErrNotFound) +} + +func RegisterIndexerGetLastRequestIndexedBlockErrNotReady(queryClient *mocks.EVMTxIndexer) { + queryClient.On("GetLastRequestIndexedBlock"). + Return(int64(0), indexer.ErrIndexerNotReady) +} diff --git a/rpc/backend/node_info_test.go b/rpc/backend/node_info_test.go index 2b2f7beaba..5f9099ca0a 100644 --- a/rpc/backend/node_info_test.go +++ b/rpc/backend/node_info_test.go @@ -14,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/spf13/viper" - "google.golang.org/grpc/metadata" ) func (suite *BackendTestSuite) TestRPCMinGasPrice() { @@ -249,12 +248,14 @@ func (suite *BackendTestSuite) TestSetEtherbase() { { "fail - error querying for account", func() { - var header metadata.MD client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterStatus(client) RegisterValidatorAccount(queryClient, suite.acc) - RegisterParams(queryClient, &header, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) + c := sdk.NewDecCoin(constants.BaseDenom, sdk.NewIntFromBigInt(big.NewInt(1))) suite.backend.cfg.SetMinGasPrices(sdk.DecCoins{c}) delAddr, _ := suite.backend.GetCoinbase() diff --git a/rpc/backend/sign_tx_test.go b/rpc/backend/sign_tx_test.go index e4a97764f4..0d59549a37 100644 --- a/rpc/backend/sign_tx_test.go +++ b/rpc/backend/sign_tx_test.go @@ -3,8 +3,10 @@ package backend import ( "fmt" - "cosmossdk.io/math" - + "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" + "github.com/EscanBE/evermint/v12/rpc/backend/mocks" + utiltx "github.com/EscanBE/evermint/v12/testutil/tx" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" "github.com/cosmos/cosmos-sdk/crypto" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" @@ -12,12 +14,6 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" goethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/signer/core/apitypes" - "google.golang.org/grpc/metadata" - - "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" - "github.com/EscanBE/evermint/v12/rpc/backend/mocks" - utiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" ) func (suite *BackendTestSuite) TestSendTransaction() { @@ -56,14 +52,14 @@ func (suite *BackendTestSuite) TestSendTransaction() { { "fail - Block error can't set Tx defaults", func() { - var header metadata.MD - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) client := suite.backend.clientCtx.Client.(*mocks.Client) armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") err := suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") suite.Require().NoError(err) - RegisterParams(queryClient, &header, 1) RegisterBlockError(client, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, callArgsDefault, hash, @@ -72,19 +68,20 @@ func (suite *BackendTestSuite) TestSendTransaction() { { "fail - Cannot validate transaction gas set to 0", func() { - var header metadata.MD queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) client := suite.backend.clientCtx.Client.(*mocks.Client) armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") err := suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") suite.Require().NoError(err) - RegisterParams(queryClient, &header, 1) _, err = RegisterBlock(client, 1, nil) suite.Require().NoError(err) _, err = RegisterBlockResults(client, 1) suite.Require().NoError(err) RegisterBaseFee(queryClient, baseFee) RegisterParamsWithoutHeader(queryClient, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) }, evmtypes.TransactionArgs{ From: &from, @@ -99,7 +96,21 @@ func (suite *BackendTestSuite) TestSendTransaction() { { "fail - Cannot broadcast transaction", func() { - client, txBytes := broadcastTx(suite, priv, baseFee, callArgsDefault) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + _ = suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + _, err := RegisterBlock(client, 1, nil) + suite.Require().NoError(err) + _, err = RegisterBlockResults(client, 1) + suite.Require().NoError(err) + RegisterBaseFee(queryClient, baseFee) + RegisterParamsWithoutHeader(queryClient, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) + + txBytes := broadcastTx(suite, callArgsDefault) RegisterBroadcastTxError(client, txBytes) }, callArgsDefault, @@ -109,13 +120,61 @@ func (suite *BackendTestSuite) TestSendTransaction() { { "pass - Return the transaction hash", func() { - client, txBytes := broadcastTx(suite, priv, baseFee, callArgsDefault) + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + client := suite.backend.clientCtx.Client.(*mocks.Client) + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + _ = suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + _, err := RegisterBlock(client, 1, nil) + suite.Require().NoError(err) + _, err = RegisterBlockResults(client, 1) + suite.Require().NoError(err) + RegisterBaseFee(queryClient, baseFee) + RegisterParamsWithoutHeader(queryClient, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlock(indexer, 1) + + txBytes := broadcastTx(suite, callArgsDefault) RegisterBroadcastTx(client, txBytes) }, callArgsDefault, hash, true, }, + { + name: "fail - when indexer not ready", + registerMock: func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + _ = suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + RegisterParamsWithoutHeader(queryClient, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + + _ = broadcastTx(suite, callArgsDefault) + }, + args: callArgsDefault, + expHash: common.Hash{}, + expPass: false, + }, + { + name: "fail - when indexer returns error", + registerMock: func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) + armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") + _ = suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") + RegisterParamsWithoutHeader(queryClient, 1) + + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) + + _ = broadcastTx(suite, callArgsDefault) + }, + args: callArgsDefault, + expHash: common.Hash{}, + expPass: false, + }, } for _, tc := range testCases { @@ -241,25 +300,13 @@ func (suite *BackendTestSuite) TestSignTypedData() { } } -func broadcastTx(suite *BackendTestSuite, priv *ethsecp256k1.PrivKey, baseFee math.Int, callArgsDefault evmtypes.TransactionArgs) (client *mocks.Client, txBytes []byte) { - var header metadata.MD - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - client = suite.backend.clientCtx.Client.(*mocks.Client) - armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") - _ = suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") - RegisterParams(queryClient, &header, 1) - _, err := RegisterBlock(client, 1, nil) - suite.Require().NoError(err) - _, err = RegisterBlockResults(client, 1) - suite.Require().NoError(err) - RegisterBaseFee(queryClient, baseFee) - RegisterParamsWithoutHeader(queryClient, 1) +func broadcastTx(suite *BackendTestSuite, callArgsDefault evmtypes.TransactionArgs) []byte { ethSigner := ethtypes.LatestSigner(suite.backend.ChainConfig()) msg := callArgsDefault.ToTransaction() - err = msg.Sign(ethSigner, suite.backend.clientCtx.Keyring) + err := msg.Sign(ethSigner, suite.backend.clientCtx.Keyring) suite.Require().NoError(err) tx, _ := msg.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), evmtypes.DefaultEVMDenom) txEncoder := suite.backend.clientCtx.TxConfig.TxEncoder() - txBytes, _ = txEncoder(tx) - return client, txBytes + txBytes, _ := txEncoder(tx) + return txBytes } diff --git a/rpc/backend/tx_info_test.go b/rpc/backend/tx_info_test.go index 5acf48fe71..7f2eac1f56 100644 --- a/rpc/backend/tx_info_test.go +++ b/rpc/backend/tx_info_test.go @@ -17,7 +17,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "google.golang.org/grpc/metadata" ) func (suite *BackendTestSuite) TestGetTransactionByHash() { @@ -570,10 +569,8 @@ func (suite *BackendTestSuite) TestGetTransactionReceipt() { { "fail - Receipts do not match", func() { - var header metadata.MD queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) client := suite.backend.clientCtx.Client.(*mocks.Client) - RegisterParams(queryClient, &header, 1) RegisterParamsWithoutHeader(queryClient, 1) _, err := RegisterBlock(client, 1, txBz) suite.Require().NoError(err) @@ -622,6 +619,48 @@ func (suite *BackendTestSuite) TestGetTransactionReceipt() { } }) } + + testCasesIndexerErr := []struct { + name string + registerMock func(txHash common.Hash) + tx *evmtypes.MsgEthereumTx + expErr bool + }{ + { + name: "fail - indexer not ready", + registerMock: func(txHash common.Hash) { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetByTxHashErrNotReady(indexer, txHash) + }, + tx: msgEthereumTx, + expErr: true, + }, + { + name: "fail - indexer returns error", + registerMock: func(txHash common.Hash) { + indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) + RegisterIndexerGetByTxHashErr(indexer, txHash) + }, + tx: msgEthereumTx, + expErr: false, + }, + } + for _, tc := range testCasesIndexerErr { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + signedTxHash := common.HexToHash(tc.tx.Hash) + tc.registerMock(signedTxHash) + + receipt, err := suite.backend.GetTransactionReceipt(signedTxHash) + if tc.expErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + suite.Nil(receipt) + }) + } } func (suite *BackendTestSuite) TestGetGasUsed() { From ac6fa795ee7d18495c14ffb7ef3458bbeb45e949 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 23:22:26 +0700 Subject: [PATCH 08/11] Json-RPC from now on, wait for indexer finished indexing blocks, before start serving requests --- indexer/kv_indexer.go | 10 +--------- rpc/backend/mocks/indexer.go | 18 ++++++++++++++++++ server/start.go | 10 ++++++++++ types/indexer.go | 3 +++ 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/indexer/kv_indexer.go b/indexer/kv_indexer.go index bdf32cc140..d303be138e 100644 --- a/indexer/kv_indexer.go +++ b/indexer/kv_indexer.go @@ -31,8 +31,6 @@ const ( var _ evertypes.EVMTxIndexer = &KVIndexer{} -var ErrIndexerNotReady = fmt.Errorf("indexer not ready") - // KVIndexer implements an ETH-Tx indexer on a KV db. type KVIndexer struct { db dbm.DB @@ -152,7 +150,7 @@ func (kv *KVIndexer) Ready() { kv.ready = true } -func (kv *KVIndexer) isReady() bool { +func (kv *KVIndexer) IsReady() bool { kv.mu.RLock() defer kv.mu.RUnlock() return kv.ready @@ -171,17 +169,11 @@ func (kv *KVIndexer) FirstIndexedBlock() (int64, error) { // GetByTxHash finds eth tx by eth tx hash func (kv *KVIndexer) GetByTxHash(hash common.Hash) (*evertypes.TxResult, error) { - if !kv.isReady() { - return nil, ErrIndexerNotReady - } return kv.getByTxHash(hash) } // GetByBlockAndIndex finds eth tx by block number and eth tx index func (kv *KVIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*evertypes.TxResult, error) { - if !kv.isReady() { - return nil, ErrIndexerNotReady - } return kv.getByBlockAndIndex(blockNumber, txIndex) } diff --git a/rpc/backend/mocks/indexer.go b/rpc/backend/mocks/indexer.go index b274b776f0..645c3fca23 100644 --- a/rpc/backend/mocks/indexer.go +++ b/rpc/backend/mocks/indexer.go @@ -124,6 +124,24 @@ func (_m *EVMTxIndexer) IndexBlock(_a0 *cometbfttypes.Block, _a1 []*abcitypes.Re return r0 } +// IsReady provides a mock function with given fields: +func (_m *EVMTxIndexer) IsReady() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsReady") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // LastIndexedBlock provides a mock function with given fields: func (_m *EVMTxIndexer) LastIndexedBlock() (int64, error) { ret := _m.Called() diff --git a/server/start.go b/server/start.go index e2855de7b7..3e11d4f4d8 100644 --- a/server/start.go +++ b/server/start.go @@ -535,6 +535,16 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt case <-time.After(types.ServerStartTime): // assume server started successfully } + // waiting for indexer indexes blocks + for { + time.Sleep(1 * time.Second) + if evmTxIndexer.IsReady() { + break + } + + logger.Info("indexer still in progress, keep waiting") + } + // Start Json-RPC server genDoc, err := genDocProvider() if err != nil { diff --git a/types/indexer.go b/types/indexer.go index 385c252845..db65bb01ce 100644 --- a/types/indexer.go +++ b/types/indexer.go @@ -21,6 +21,9 @@ type EVMTxIndexer interface { // Ready is an external trigger that indicates the indexer is ready to serve requests. Ready() + // IsReady returns true if the indexer is indexed completely and ready to serve requests. + IsReady() bool + // GetByTxHash returns nil if tx not found. GetByTxHash(common.Hash) (*TxResult, error) From 382427fa0fbaa928b93b493317b8be9a53e12b16 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 23:28:12 +0700 Subject: [PATCH 09/11] remove testcases where indexer not ready --- indexer/kv_indexer.go | 3 -- rpc/backend/account_info_test.go | 11 ----- rpc/backend/blocks_test.go | 85 -------------------------------- rpc/backend/call_tx_test.go | 31 +----------- rpc/backend/chain_info.go | 8 +-- rpc/backend/chain_info_test.go | 23 --------- rpc/backend/indexer_test.go | 11 ----- rpc/backend/sign_tx_test.go | 17 ------- rpc/backend/tx_info.go | 5 -- rpc/backend/tx_info_test.go | 9 ---- 10 files changed, 3 insertions(+), 200 deletions(-) diff --git a/indexer/kv_indexer.go b/indexer/kv_indexer.go index d303be138e..a7747e84c2 100644 --- a/indexer/kv_indexer.go +++ b/indexer/kv_indexer.go @@ -181,9 +181,6 @@ func (kv *KVIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*ever func (kv *KVIndexer) GetLastRequestIndexedBlock() (int64, error) { kv.mu.RLock() defer kv.mu.RUnlock() - if !kv.ready { - return -1, ErrIndexerNotReady - } if kv.lastRequestIndexedBlock == -1 { return LoadLastBlock(kv.db) diff --git a/rpc/backend/account_info_test.go b/rpc/backend/account_info_test.go index b0b2b28719..260a21d4c5 100644 --- a/rpc/backend/account_info_test.go +++ b/rpc/backend/account_info_test.go @@ -382,17 +382,6 @@ func (suite *BackendTestSuite) TestGetTransactionCount() { false, hexutil.Uint64(0), }, - { - name: "fail - indexer not ready", - accExists: false, - blockNum: rpctypes.NewBlockNumber(big.NewInt(10000)), - registerMock: func(addr common.Address, bn rpctypes.BlockNumber) { - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - expPass: false, - expTxCount: hexutil.Uint64(0), - }, { name: "fail - indexer returns error", accExists: false, diff --git a/rpc/backend/blocks_test.go b/rpc/backend/blocks_test.go index dfec45dc11..050d7cb352 100644 --- a/rpc/backend/blocks_test.go +++ b/rpc/backend/blocks_test.go @@ -46,14 +46,6 @@ func (suite *BackendTestSuite) TestBlockNumber() { expBlockNumber: 0x3, expPass: true, }, - { - name: "fail - indexer not ready", - registerMock: func() { - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - expPass: false, - }, { name: "fail - indexer returns error", registerMock: func() { @@ -204,29 +196,6 @@ func (suite *BackendTestSuite) TestGetBlockByNumber() { false, true, }, - { - name: "fail - indexer not ready", - blockNumber: ethrpc.BlockNumber(1), - fullTx: false, - baseFee: sdk.NewInt(1).BigInt(), - validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), - tx: msgEthereumTx, - txBz: bz, - registerMock: func(blockNum ethrpc.BlockNumber, baseFee math.Int, validator sdk.AccAddress, txBz []byte) { - height := blockNum.Int64() - client := suite.backend.clientCtx.Client.(*mocks.Client) - resBlock, _ = RegisterBlock(client, height, txBz) - blockRes, _ = RegisterBlockResults(client, blockNum.Int64()) - - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterValidatorAccount(queryClient, validator) - - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - expNoop: false, - expPass: false, - }, { name: "fail - indexer returns error when fetching tx", blockNumber: ethrpc.BlockNumber(1), @@ -439,27 +408,6 @@ func (suite *BackendTestSuite) TestGetBlockByHash() { false, true, }, - { - name: "fail - indexer not ready", - hash: common.BytesToHash(block.Hash()), - fullTx: true, - baseFee: sdk.NewInt(1).BigInt(), - validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), - registerMock: func(hash common.Hash, baseFee math.Int, validator sdk.AccAddress, txBz []byte) { - height := int64(1) - client := suite.backend.clientCtx.Client.(*mocks.Client) - resBlock, _ = RegisterBlockByHash(client, hash, txBz) - - blockRes, _ = RegisterBlockResults(client, height) - - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterValidatorAccount(queryClient, validator) - - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - expPass: false, - }, { name: "success - indexer returns error", hash: common.BytesToHash(block.Hash()), @@ -716,16 +664,6 @@ func (suite *BackendTestSuite) TestTendermintBlockByNumber() { false, false, }, - { - name: "fail - blockNum < 0 with indexer not ready", - blockNumber: ethrpc.BlockNumber(-1), - registerMock: func(_ ethrpc.BlockNumber) { - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - found: false, - expPass: false, - }, { name: "fail - blockNum < 0 with indexer returns error", blockNumber: ethrpc.BlockNumber(-1), @@ -1278,29 +1216,6 @@ func (suite *BackendTestSuite) TestGetEthBlockFromTendermint() { true, true, }, - { - name: "fail - indexer not ready", - baseFee: sdk.NewInt(1).BigInt(), - validator: sdk.AccAddress(utiltx.GenerateAddress().Bytes()), - height: int64(1), - resBlock: &tmrpctypes.ResultBlock{ - Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), - }, - blockRes: &tmrpctypes.ResultBlockResults{ - Height: 1, - TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, - }, - fullTx: false, - registerMock: func(baseFee math.Int, validator sdk.AccAddress, height int64) { - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - RegisterValidatorAccount(queryClient, validator) - - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - expTxs: false, - expPass: false, - }, { name: "pass - indexer returns error when getting latest block number", baseFee: sdk.NewInt(1).BigInt(), diff --git a/rpc/backend/call_tx_test.go b/rpc/backend/call_tx_test.go index a1aff190ec..6a7ed9a503 100644 --- a/rpc/backend/call_tx_test.go +++ b/rpc/backend/call_tx_test.go @@ -272,31 +272,11 @@ func (suite *BackendTestSuite) TestResend() { common.Hash{}, false, }, - { - name: "fail - indexer not ready", - registerMock: func() { - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - args: evmtypes.TransactionArgs{ - Nonce: &txNonce, - To: &toAddr, - MaxFeePerGas: gasPrice, - MaxPriorityFeePerGas: gasPrice, - Value: gasPrice, - Gas: nil, - ChainID: callArgs.ChainID, - }, - gasPrice: gasPrice, - gasLimit: nil, - expHash: common.Hash{}, - expPass: false, - }, { name: "fail - indexer returns error", registerMock: func() { indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) + RegisterIndexerGetLastRequestIndexedBlockErr(indexer) }, args: evmtypes.TransactionArgs{ Nonce: &txNonce, @@ -553,15 +533,6 @@ func (suite *BackendTestSuite) TestGasPrice() { defaultGasPrice, false, }, - { - name: "fail - indexer not ready", - registerMock: func() { - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - expGas: defaultGasPrice, - expPass: false, - }, { name: "fail - indexer returns error", registerMock: func() { diff --git a/rpc/backend/chain_info.go b/rpc/backend/chain_info.go index e73fb91381..cf2db24b02 100644 --- a/rpc/backend/chain_info.go +++ b/rpc/backend/chain_info.go @@ -3,7 +3,6 @@ package backend import ( "errors" "fmt" - "github.com/EscanBE/evermint/v12/indexer" tmrpcclient "github.com/cometbft/cometbft/rpc/client" "math/big" "strconv" @@ -29,9 +28,6 @@ func (b *Backend) ChainID() (*hexutil.Big, error) { // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config bn, err := b.BlockNumber() if err != nil { - if err == indexer.ErrIndexerNotReady { - return nil, err - } b.logger.Debug("failed to fetch latest block number", "error", err.Error()) return (*hexutil.Big)(eip155ChainID), nil } @@ -151,8 +147,8 @@ func (b *Backend) GetCoinbase() (sdk.AccAddress, error) { // FeeHistory returns data relevant for fee estimation based on the specified range of blocks. func (b *Backend) FeeHistory( userBlockCount rpc.DecimalOrHex, // number blocks to fetch, maximum is 100 - lastBlock rpc.BlockNumber, // the block to start search , to oldest - rewardPercentiles []float64, // percentiles to fetch reward + lastBlock rpc.BlockNumber, // the block to start search , to oldest + rewardPercentiles []float64, // percentiles to fetch reward ) (*rpctypes.FeeHistoryResult, error) { blockEnd := int64(lastBlock) //#nosec G701 -- checked for int overflow already diff --git a/rpc/backend/chain_info_test.go b/rpc/backend/chain_info_test.go index c631bf4e2b..f5d43ce65a 100644 --- a/rpc/backend/chain_info_test.go +++ b/rpc/backend/chain_info_test.go @@ -169,15 +169,6 @@ func (suite *BackendTestSuite) TestChainId() { expChainID: expChainID, expPass: true, }, - { - name: "fail - indexer not ready", - registerMock: func() { - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - expChainID: nil, - expPass: false, - }, { name: "pass - indexer returns error", registerMock: func() { @@ -360,20 +351,6 @@ func (suite *BackendTestSuite) TestFeeHistory() { nil, false, }, - { - name: "fail - indexer not ready", - registerMock: func(validator sdk.AccAddress) { - suite.backend.cfg.JSONRPC.FeeHistoryCap = 0 - - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - }, - userBlockCount: 1, - latestBlock: -1, - expFeeHistory: nil, - validator: nil, - expPass: false, - }, { "fail - user block count higher than max block count ", func(validator sdk.AccAddress) { diff --git a/rpc/backend/indexer_test.go b/rpc/backend/indexer_test.go index 490d0b9a18..64040ba199 100644 --- a/rpc/backend/indexer_test.go +++ b/rpc/backend/indexer_test.go @@ -1,7 +1,6 @@ package backend import ( - "github.com/EscanBE/evermint/v12/indexer" "github.com/EscanBE/evermint/v12/rpc/backend/mocks" "github.com/EscanBE/evermint/v12/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -53,11 +52,6 @@ func RegisterIndexerGetByTxHashErr(queryClient *mocks.EVMTxIndexer, hash common. Return(nil, sdkerrors.ErrNotFound) } -func RegisterIndexerGetByTxHashErrNotReady(queryClient *mocks.EVMTxIndexer, hash common.Hash) { - queryClient.On("GetByTxHash", hash). - Return(nil, indexer.ErrIndexerNotReady) -} - func RegisterIndexerGetLastRequestIndexedBlock(queryClient *mocks.EVMTxIndexer, height int64) { queryClient.On("GetLastRequestIndexedBlock"). Return(height, nil) @@ -67,8 +61,3 @@ func RegisterIndexerGetLastRequestIndexedBlockErr(queryClient *mocks.EVMTxIndexe queryClient.On("GetLastRequestIndexedBlock"). Return(int64(0), sdkerrors.ErrNotFound) } - -func RegisterIndexerGetLastRequestIndexedBlockErrNotReady(queryClient *mocks.EVMTxIndexer) { - queryClient.On("GetLastRequestIndexedBlock"). - Return(int64(0), indexer.ErrIndexerNotReady) -} diff --git a/rpc/backend/sign_tx_test.go b/rpc/backend/sign_tx_test.go index 0d59549a37..f48fb7ab55 100644 --- a/rpc/backend/sign_tx_test.go +++ b/rpc/backend/sign_tx_test.go @@ -141,23 +141,6 @@ func (suite *BackendTestSuite) TestSendTransaction() { hash, true, }, - { - name: "fail - when indexer not ready", - registerMock: func() { - queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) - armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") - _ = suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") - RegisterParamsWithoutHeader(queryClient, 1) - - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetLastRequestIndexedBlockErrNotReady(indexer) - - _ = broadcastTx(suite, callArgsDefault) - }, - args: callArgsDefault, - expHash: common.Hash{}, - expPass: false, - }, { name: "fail - when indexer returns error", registerMock: func() { diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index cb87ed8df4..2b967c6aac 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -2,7 +2,6 @@ package backend import ( "fmt" - "github.com/EscanBE/evermint/v12/indexer" tmrpcclient "github.com/cometbft/cometbft/rpc/client" "math" "math/big" @@ -145,10 +144,6 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (*rpctypes.RPCReceipt, res, err := b.GetTxByEthHash(hash) if err != nil { - if err == indexer.ErrIndexerNotReady { - b.logger.Debug("indexer not ready", "hash", hexTx) - return nil, err - } b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error()) return nil, nil } diff --git a/rpc/backend/tx_info_test.go b/rpc/backend/tx_info_test.go index 7f2eac1f56..d8f1046fba 100644 --- a/rpc/backend/tx_info_test.go +++ b/rpc/backend/tx_info_test.go @@ -626,15 +626,6 @@ func (suite *BackendTestSuite) TestGetTransactionReceipt() { tx *evmtypes.MsgEthereumTx expErr bool }{ - { - name: "fail - indexer not ready", - registerMock: func(txHash common.Hash) { - indexer := suite.backend.indexer.(*mocks.EVMTxIndexer) - RegisterIndexerGetByTxHashErrNotReady(indexer, txHash) - }, - tx: msgEthereumTx, - expErr: true, - }, { name: "fail - indexer returns error", registerMock: func(txHash common.Hash) { From 8cd56838cba1e3912051efc0915280011514b37e Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 23:46:30 +0700 Subject: [PATCH 10/11] apply threshold for indexing blocks during startup --- server/indexer_service.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/server/indexer_service.go b/server/indexer_service.go index c3eb4aa8e9..7503e23161 100644 --- a/server/indexer_service.go +++ b/server/indexer_service.go @@ -86,6 +86,21 @@ func (eis *EVMIndexerService) OnStart() error { } var isIndexerMarkedReady bool + startupIndexBlockFailureTracker := make(map[int64]int) + const startupIndexBlockFailureThreshold = 10 + markFailedToIndexBlock := func(h int64) (shouldSkip bool) { + if cnt, found := startupIndexBlockFailureTracker[h]; found { + cnt++ + startupIndexBlockFailureTracker[h] = cnt + if cnt >= startupIndexBlockFailureThreshold { + shouldSkip = true + } + } else { + startupIndexBlockFailureTracker[h] = 1 + } + + return + } for { if lastIndexedBlock >= latestBlock { @@ -95,6 +110,10 @@ func (eis *EVMIndexerService) OnStart() error { if !isIndexerMarkedReady { eis.txIdxr.Ready() isIndexerMarkedReady = true + + for h, _ := range startupIndexBlockFailureTracker { + eis.Logger.Error("skipped indexing block after multiple retries", "height", h) + } } // wait @@ -107,18 +126,28 @@ func (eis *EVMIndexerService) OnStart() error { for i := lastIndexedBlock + 1; i <= latestBlock; i++ { block, err := eis.client.Block(ctx, &i) if err != nil { + if !isIndexerMarkedReady && markFailedToIndexBlock(i) { + lastIndexedBlock = i + } eis.Logger.Error("failed to fetch block", "height", i, "err", err) break } blockResult, err := eis.client.BlockResults(ctx, &i) if err != nil { + if !isIndexerMarkedReady && markFailedToIndexBlock(i) { + lastIndexedBlock = i + } 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) + } else if !isIndexerMarkedReady { + delete(startupIndexBlockFailureTracker, i) + + eis.Logger.Info("indexed block", "height", i) } - lastIndexedBlock = blockResult.Height + lastIndexedBlock = i } } } From c8d3f33ed5d653f2e6a38c5e120bc1fdb859ead4 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 15 Jan 2024 23:56:43 +0700 Subject: [PATCH 11/11] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cdfcd2e39..bff1bb9c92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ - (ante) [#64](https://github.com/EscanBE/evermint/pull/64) Add EvmTx sender override protection in ante and correct changelog - (lint) [#83](https://github.com/EscanBE/evermint/pull/83) Update lint rules and fix lint issues - (cleanup) [#92](https://github.com/EscanBE/evermint/pull/92) Cleanup un-used `x/evm` and `x/erc20` types +- (indexer) [#96](https://github.com/EscanBE/evermint/pull/96) Make EVMTxIndexer mandatory service, starts before Json-RPC ### Bug Fixes