diff --git a/internal/clients/bbnclient/bbnclient.go b/internal/clients/bbnclient/bbnclient.go index d391c8a..d0e8aab 100644 --- a/internal/clients/bbnclient/bbnclient.go +++ b/internal/clients/bbnclient/bbnclient.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "strings" + "time" "github.com/babylonlabs-io/babylon-staking-indexer/internal/types" "github.com/babylonlabs-io/babylon/client/config" @@ -14,6 +15,13 @@ import ( "github.com/rs/zerolog/log" ) +const ( + // Backoff parameters for retries for getting BBN block result + initialBackoff = 500 * time.Millisecond // Start with 500ms + backoffFactor = 2 // Exponential backoff factor + maxRetries = 10 // 8 minutes in worst case +) + type BbnClient struct { queryClient *query.QueryClient } @@ -38,18 +46,6 @@ func (c *BbnClient) GetLatestBlockNumber(ctx context.Context) (int64, *types.Err return status.SyncInfo.LatestBlockHeight, nil } -func (c *BbnClient) GetBlockResults(ctx context.Context, blockHeight int64) (*ctypes.ResultBlockResults, *types.Error) { - resp, err := c.queryClient.RPCClient.BlockResults(ctx, &blockHeight) - if err != nil { - return nil, types.NewErrorWithMsg( - http.StatusInternalServerError, - types.InternalServiceError, - fmt.Sprintf("failed to get block results for block %d: %s", blockHeight, err.Error()), - ) - } - return resp, nil -} - func (c *BbnClient) GetCheckpointParams(ctx context.Context) (*CheckpointParams, *types.Error) { params, err := c.queryClient.BTCCheckpointParams() if err != nil { @@ -107,6 +103,44 @@ func (c *BbnClient) GetAllStakingParams(ctx context.Context) (map[uint32]*Stakin return allParams, nil } +// GetBlockResultsWithRetry retries the `getBlockResults` method with exponential backoff +// when the block is not yet available. +func (c *BbnClient) GetBlockResultsWithRetry( + ctx context.Context, blockHeight *int64, +) (*ctypes.ResultBlockResults, *types.Error) { + backoff := initialBackoff + var resp *ctypes.ResultBlockResults + var err *types.Error + + for i := 0; i < maxRetries; i++ { + resp, err = c.getBlockResults(ctx, blockHeight) + if err == nil { + return resp, nil + } + + if strings.Contains( + err.Err.Error(), + "must be less than or equal to the current blockchain height", + ) { + log.Debug(). + Str("block_height", fmt.Sprintf("%d", *blockHeight)). + Str("backoff", backoff.String()). + Msg("Block not yet available, retrying...") + time.Sleep(backoff) + backoff *= backoffFactor + continue + } + return nil, err + } + + // If we exhaust retries, return a not found error + return nil, types.NewErrorWithMsg( + http.StatusNotFound, + types.NotFound, + fmt.Sprintf("Block height %d not found after retries", *blockHeight), + ) +} + func (c *BbnClient) Subscribe(subscriber, query string, outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) { return c.queryClient.RPCClient.Subscribe(context.Background(), subscriber, query, outCapacity...) } diff --git a/internal/clients/bbnclient/interface.go b/internal/clients/bbnclient/interface.go index 022a762..f53c176 100644 --- a/internal/clients/bbnclient/interface.go +++ b/internal/clients/bbnclient/interface.go @@ -11,8 +11,8 @@ type BbnInterface interface { GetCheckpointParams(ctx context.Context) (*CheckpointParams, *types.Error) GetAllStakingParams(ctx context.Context) (map[uint32]*StakingParams, *types.Error) GetLatestBlockNumber(ctx context.Context) (int64, *types.Error) - GetBlockResults( - ctx context.Context, blockHeight int64, + GetBlockResultsWithRetry( + ctx context.Context, blockHeight *int64, ) (*ctypes.ResultBlockResults, *types.Error) Subscribe(subscriber, query string, outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) UnsubscribeAll(subscriber string) error diff --git a/internal/services/bootstrap.go b/internal/services/bootstrap.go index df5b7fc..bcea1d2 100644 --- a/internal/services/bootstrap.go +++ b/internal/services/bootstrap.go @@ -105,7 +105,7 @@ func (s *Service) getEventsFromBlock( ctx context.Context, blockHeight int64, ) ([]BbnEvent, *types.Error) { events := make([]BbnEvent, 0) - blockResult, err := s.bbn.GetBlockResults(ctx, blockHeight) + blockResult, err := s.bbn.GetBlockResultsWithRetry(ctx, &blockHeight) if err != nil { return nil, err }