-
Notifications
You must be signed in to change notification settings - Fork 49
Unconditional next-block header fetch at chain tip in RPCLogStreamer.GetNextPage causes backfill stall and noisy retries #1879
Description
Severity: Low | Likelihood: Low | Impact: Medium | Type: Vulnerability
Details
GetNextPage performs a reorg check by fetching header(fromBlock+1) before verifying fromBlock < highestBlock; at chain tip this header does not exist, causing repeated errors and a stall until a new block appears.
In RPCLogStreamer.GetNextPage, the function calls HeaderByNumber(fromBlockNumber+1) for reorg detection whenever fromBlockNumber > 0, before ensuring that fromBlockNumber is below the current head. At the chain tip (fromBlockNumber == highestBlock), the next block header is not yet available, so HeaderByNumber(fromBlock+1) fails. The caller (watchContract) treats this as a generic error, sleeps 100ms, and retries, never reaching ErrEndOfBackfill or switching to subscription mode. This causes repeated error logs and a temporary stall at the backfill boundary.
Exploitation
Scenario 1
Malicious or degraded RPC provider returns errors for HeaderByNumber(fromBlock+1) around the backfill cursor, preventing GetNextPage from reaching end-of-backfill and keeping the indexer stuck retrying indefinitely without switching to subscription.
Preconditions / Assumptions:
- (a) Node uses an untrusted or malfunctioning RPC provider for rpcClient
- (b) Indexer is near or at the chain tip (fromBlockNumber >= highestBlock)
- (c) Provider persistently returns errors to HeaderByNumber(fromBlock+1)
Scenario 2
On node restart near chain tip with a quiet chain, GetNextPage probes header(fromBlock+1) which does not exist yet, returning an error; the backfill loop logs, sleeps, and retries until a new block is mined, stalling indexing and producing noisy logs.
Preconditions / Assumptions:
- (a) Node previously indexed up to chain head for the watched contracts
- (b) Operator restarts the node
- (c) Chain is idle or slow so the next block is not yet available when GetNextPage runs
- (d) RPC provider returns NotFound for non-existent headers
Scenario 3
While the node is stalled at the backfill boundary, an attacker emits many matching events to the watched contract, filling the subscription channel; this causes backpressure and potential subscription reconnection churn until backfill eventually completes.
Preconditions / Assumptions:
- (a) Node is already stalled at the end-of-backfill boundary
- (b) Watched contracts are publicly callable
- (c) Attacker is willing to pay gas to emit many matching logs during the stall
Files impacted
pkg/indexer/rpc_streamer/rpc_log_streamer.go
Lines 355-359:
// Do not check for reorgs at block height 0. Genesis does not have a parent.
if fromBlockNumber > 0 {
nextBlockHeader, err := r.rpcClient.HeaderByNumber(
ctx,Metadata
Metadata
Assignees
Labels
Type
Projects
Status