Skip to content

fix(core): queue all future blocks of a batch instead of failing - #2550

Open
gzliudan wants to merge 1 commit into
XinFinOrg:dev-upgradefrom
gzliudan:fix-future-block-batch
Open

fix(core): queue all future blocks of a batch instead of failing#2550
gzliudan wants to merge 1 commit into
XinFinOrg:dev-upgradefrom
gzliudan:fix-future-block-batch

Conversation

@gzliudan

@gzliudan gzliudan commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

insertChain treats a mid-batch ErrFutureBlock as a fatal verification error: both future-block loops only accepted ErrUnknownAncestor, so a batch whose first or middle block was in the future stopped at its second block and returned consensus.ErrFutureBlock. The downloader wraps any non-nil InsertChain error into errInvalidChain (eth/downloader/downloader.go) and Synchronise answers that by dropping the delivering peer, aborting the round and retrying with another peer. Near the chain tip this repeats and continuously evicts innocent peers.

Root cause

The XDPoS engines check the header timestamp before the parent lookup and with zero tolerance (header.Time > now), unlike upstream geth's 15-second allowedFutureBlockTimeSeconds. Consequently the children of a future block fail the future check too — they surface as ErrFutureBlock, never as ErrUnknownAncestor, so the inner loops (which only accept ErrUnknownAncestor) stop at the second block and the rest of the batch is silently skipped.

Before #2534 the same scenario silently dropped the tail and failed one batch later with a bogus bad-block report.

Fix

Accept consensus.ErrFutureBlock in both future-block loops of insertChain (first-block path and tail path), so the whole tail enters the futureBlocks queue and the import reports success. No error is swallowed: the loops drain naturally to (nil, nil) and genuine verification errors still propagate. The queued blocks are imported by procFutureBlocks (100 ms ticker) once their timestamps are reached, matching the upstream geth design intent.

Blocks beyond the now+30s future window still return an error (abort + peer drop), which is the documented behaviour for a local clock more than 30 seconds behind — an environment problem, not an invalid chain.

A genuine verification error on a block after the queued future tail (e.g. a malformed validator field, which the XDPoS engines check before the timestamp) also surfaces: it is recorded as a bad block and returned, so only a fully drained tail reports success. Future, pruned-ancestor and known blocks remain exempt from the bad-block report — they are legitimate chain states. The same applies to a genuine error after the queued prefix when the batch starts with a future block: it is recorded and returned as well. When the known-block import PR lands, its final-return filter reports the same block again; WriteBadBlock deduplicates by (number, hash), so the only effect is one extra BAD BLOCK log line.

Tests

New core/blockchain_futureblocks_test.go (self-contained, own failRangeEngine with an atomic.Uint64 fail range because the chain's future-block loop calls VerifyHeaders concurrently):

  • TestInsertChainQueuesMidBatchFutureBlocks — a batch rejected as future mid-way returns n == len(blocks), err nil, head stays at blocks[1], the whole tail is in futureBlocks, no bad-block records.
  • TestInsertChainQueuesFutureBatchFromFirstBlock — same for a batch whose first block is already in the future (pre-existing scenario).
  • TestInsertChainProcFutureBlocksResumesImport — once headers verify again, the queued tail imports and the head reaches the batch tip (head polled with a deadline because InsertChain uses TryLock and the background loop may be mid-import).

All three are mutation-verified: reverting either loop condition alone makes the corresponding test fail. go test ./core/ and go test ./eth/downloader/ pass, also under -race; gofmt/go vet clean.

Compatibility

No consensus-rule changes: block/receipt structure, state transition and wire protocol are untouched. Only the error handling of the import path changes. No migration or node-operator action required. Suggested label: consensus.

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 156bb5b4-e1f7-4b45-b54d-e8e7c8f0c3cb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A verification error following queued future blocks is still discarded by the final nil-error return.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Queues entire future-block batches to prevent downloader peer eviction near the chain tip.

Changes:

  • Accepts ErrFutureBlock while queuing descendants.
  • Adds tests for first/mid-batch queuing and resumed import.
File summaries
File Description
core/blockchain.go Expands future-block queue handling.
core/blockchain_futureblocks_test.go Tests queuing and later processing.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread core/blockchain.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Two error paths can omit bad-block reporting or silently report an incomplete import as successful.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

core/blockchain.go:1959

  • These exemptions currently suppress both the bad-block report and the return. If iteration stops on ErrPrunedAncestor or ErrKnownBlock, block is still unconsumed but execution falls through to the final nil return, recreating a silent partial import. Exempt legitimate states only from reportBlock; every non-nil iterator error must still be returned.
		if err != nil && !errors.Is(err, consensus.ErrFutureBlock) &&
			!errors.Is(err, consensus.ErrPrunedAncestor) && !errors.Is(err, ErrKnownBlock) {
			bc.reportBlock(block, nil, err)
			return it.index, events, coalescedLogs, err
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread core/blockchain.go Outdated
@gzliudan
gzliudan force-pushed the fix-future-block-batch branch 8 times, most recently from ed3a128 to 635a9ab Compare September 4, 2026 05:11
XDPoS engines check the header timestamp before the parent lookup with
zero tolerance, so children of a future block fail verification with
ErrFutureBlock rather than ErrUnknownAncestor. The insertChain
future-block loops only accepted ErrUnknownAncestor, so a batch whose
first or middle block was in the future stopped at its second block and
returned the error, which the downloader wraps into errInvalidChain and
answers by dropping the peer on a valid delivery. Before XinFinOrg#2534 the same
scenario was worse: the tail was silently dropped and the import failed
one batch later with a bogus bad-block report against a valid block.

Accept ErrFutureBlock in both loops so the whole tail enters the
futureBlocks queue; procFutureBlocks imports the queued blocks once
their timestamps are reached. Both loops share one queueFutureTail
helper. Queuing stays bounded by the maxTimeFutureBlocks window: a
block beyond it is logged at Warn and its error is returned to the
downloader, as before this change, because a block more than 30s ahead
only comes from a skewed local clock or a misbehaving peer, and a node
in either state must not report a successful import that moved nothing.

A genuine verification error after the processed prefix or the queued
tail now surfaces and is reported from the unified exit, so the
ChainHeadEvent for any canonical progress is still broadcast before the
failure. Only deterministic consensus violations (the exported XDPoS v1
and v2 sentinels) are recorded as bad blocks; verification errors that
depend on local database state, such as a QC whose epoch-switch header
is not yet available, are logged at Warn and returned without
polluting the bad-block table with valid blocks. The exemption set for
future, pruned, unknown-ancestor and known blocks is defined once in
isBenignImportError and every comparison uses errors.Is, so engines
wrapping the sentinels keep the same behaviour.

The regression tests live in a self-contained file with their own
engine type, so this commit merges cleanly whether it lands before,
after, or without the known-block import commit.
@gzliudan
gzliudan force-pushed the fix-future-block-batch branch from 635a9ab to 991b51e Compare September 4, 2026 06:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants