diff --git a/go.mod b/go.mod index c57877c6dd..0683a49ff1 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/armon/go-metrics v0.4.1 github.com/btcsuite/btcd v0.23.4 github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/cenkalti/backoff/v4 v4.1.3 github.com/cometbft/cometbft v0.37.4 github.com/cometbft/cometbft-db v0.9.1 github.com/cosmos/cosmos-proto v1.0.0-beta.4 @@ -69,7 +70,6 @@ require ( github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect diff --git a/server/indexer_service.go b/server/indexer_service.go index 2edbc02aa5..f59817aed6 100644 --- a/server/indexer_service.go +++ b/server/indexer_service.go @@ -17,8 +17,10 @@ package server import ( "context" + "errors" "time" + "github.com/cenkalti/backoff/v4" "github.com/cometbft/cometbft/libs/service" rpcclient "github.com/cometbft/cometbft/rpc/client" "github.com/cometbft/cometbft/types" @@ -54,6 +56,14 @@ func NewEVMIndexerService( // and indexing them by events. func (eis *EVMIndexerService) OnStart() error { ctx := context.Background() + + // when kava in state-sync mode, it returns zero as latest_block_height, which leads to undesired behaviour, more + // details here: https://kava-labs.atlassian.net/wiki/spaces/ENG/pages/1623687169/EVM-Indexer+State+Sync+Issue + // to prevent this we wait until state-sync will finish + if err := waitUntilClientReady(ctx, eis.client, backoff.NewConstantBackOff(time.Second)); err != nil { + return err + } + status, err := eis.client.Status(ctx) if err != nil { return err @@ -122,3 +132,24 @@ func (eis *EVMIndexerService) OnStart() error { } } } + +// waitUntilClientReady waits until StatusClient is ready to serve requests +func waitUntilClientReady(ctx context.Context, client rpcclient.StatusClient, b backoff.BackOff) error { + err := backoff.Retry(func() error { + status, err := client.Status(ctx) + if err != nil { + return err + } + + if status.SyncInfo.LatestBlockHeight == 0 { + return errors.New("node isn't ready, possibly in state sync process") + } + + return nil + }, b) + if err != nil { + return err + } + + return nil +} diff --git a/server/indexer_service_test.go b/server/indexer_service_test.go new file mode 100644 index 0000000000..a147f1f869 --- /dev/null +++ b/server/indexer_service_test.go @@ -0,0 +1,75 @@ +package server + +import ( + "context" + "testing" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/stretchr/testify/require" + + coretypes "github.com/cometbft/cometbft/rpc/core/types" +) + +var ( + failedResponse = &coretypes.ResultStatus{ + SyncInfo: coretypes.SyncInfo{ + LatestBlockHeight: 0, + }, + } + + successfulResponse = &coretypes.ResultStatus{ + SyncInfo: coretypes.SyncInfo{ + LatestBlockHeight: 1, + }, + } +) + +type statusClientMock struct { + // retries left before success response + retriesLeft uint +} + +func newStatusClientMock(retriesLeft uint) *statusClientMock { + return &statusClientMock{ + retriesLeft: retriesLeft, + } +} + +func (m *statusClientMock) Status(context.Context) (*coretypes.ResultStatus, error) { + if m.retriesLeft == 0 { + return successfulResponse, nil + } + + m.retriesLeft-- + return failedResponse, nil +} + +func TestWaitUntilClientReady(t *testing.T) { + for _, tc := range []struct { + desc string + retriesLeft uint + }{ + { + desc: "test case #1", + retriesLeft: 0, + }, + { + desc: "test case #2", + retriesLeft: 1, + }, + { + desc: "test case #3", + retriesLeft: 10, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + ctxb := context.Background() + mock := newStatusClientMock(tc.retriesLeft) + + err := waitUntilClientReady(ctxb, mock, backoff.NewConstantBackOff(time.Nanosecond)) + require.NoError(t, err) + require.Equal(t, uint(0), mock.retriesLeft) + }) + } +}