diff --git a/chains/heads/client.go b/chains/heads/client.go index a751a54..9f307fc 100644 --- a/chains/heads/client.go +++ b/chains/heads/client.go @@ -15,6 +15,8 @@ type Client[H chains.Head[BLOCK_HASH], S chains.Subscription, ID chains.ID, BLOC // SubscribeToHeads is the method in which the client receives new Head. // It can be implemented differently for each chain i.e websocket, polling, etc SubscribeToHeads(ctx context.Context) (<-chan H, S, error) + // LatestSafeBlock - returns the latest block that was marked as safe + LatestSafeBlock(ctx context.Context) (safe H, err error) // LatestFinalizedBlock - returns the latest block that was marked as finalized LatestFinalizedBlock(ctx context.Context) (head H, err error) } diff --git a/chains/heads/tracker.go b/chains/heads/tracker.go index 0beb87a..25d2f8b 100644 --- a/chains/heads/tracker.go +++ b/chains/heads/tracker.go @@ -39,6 +39,8 @@ type Tracker[H chains.Head[BLOCK_HASH], BLOCK_HASH chains.Hashable] interface { // Backfill given a head will fill in any missing heads up to latestFinalized Backfill(ctx context.Context, headWithChain H, prevHeadWithChain H) (err error) LatestChain() H + // LatestSafeBlock returns the latest block that is considered safe to use. + LatestSafeBlock(ctx context.Context) (safe H, err error) // LatestAndFinalizedBlock - returns latest and latest finalized blocks. // NOTE: Returns latest finalized block as is, ignoring the FinalityTagBypass feature flag. LatestAndFinalizedBlock(ctx context.Context) (latest, finalized H, err error) @@ -47,6 +49,7 @@ type Tracker[H chains.Head[BLOCK_HASH], BLOCK_HASH chains.Hashable] interface { type ChainConfig interface { BlockEmissionIdleWarningThreshold() time.Duration FinalityDepth() uint32 + SafeDepth() uint32 FinalityTagEnabled() bool FinalizedBlockOffset() uint32 } @@ -396,6 +399,42 @@ func (t *tracker[HTH, S, ID, BHASH]) backfillLoop(ctx context.Context) { } } +func (t *tracker[HTH, S, ID, BHASH]) LatestSafeBlock(ctx context.Context) (safe HTH, err error) { + if t.config.FinalityTagEnabled() { + latestSafe, err2 := t.client.LatestSafeBlock(ctx) + if err2 != nil { + return latestSafe, fmt.Errorf("failed to get latest finalized block: %w", err2) + } + + if !latestSafe.IsValid() { + return latestSafe, fmt.Errorf("failed to get valid latest finalized block") + } + return latestSafe, nil + } + latest, err := t.client.HeadByNumber(ctx, nil) + if err != nil { + err = fmt.Errorf("failed to get latest block: %w", err) + return + } + + if !latest.IsValid() { + err = fmt.Errorf("expected latest block to be valid") + return + } + if t.instantFinality() { + return latest, nil + } + safeDepth := int64(t.config.SafeDepth()) + if safeDepth <= 0 { + safeDepth = int64(t.config.FinalityDepth()) + } + safeBlockNumber := latest.BlockNumber() - safeDepth + if safeBlockNumber <= 0 { + safeBlockNumber = 0 + } + return t.getHeadAtHeight(ctx, latest.BlockHash(), safeBlockNumber) +} + // LatestAndFinalizedBlock - returns latest and latest finalized blocks. // NOTE: Returns latest finalized block as is, ignoring the FinalityTagBypass feature flag. // TODO: BCI-3321 use cached values instead of making RPC requests