Skip to content

fix(consensus): reconcile existing safe block before build fallback#3885

Draft
niran wants to merge 1 commit into
mainfrom
codex/unsafe-head-sync-regression-tests
Draft

fix(consensus): reconcile existing safe block before build fallback#3885
niran wants to merge 1 commit into
mainfrom
codex/unsafe-head-sync-regression-tests

Conversation

@niran

@niran niran commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • check for an already-imported derived safe block before falling back to FCU-with-attributes build
  • when the block exists and matches the derived attributes, apply it through the follow-safe path so unsafe/local-safe/safe advance together
  • treat follow-safe FCUs that return without advancing state as temporary errors so the consolidate task is retried instead of silently popped
  • add regression coverage for the case where an earlier unsafe FCU returned SYNCING, EL later backfilled the block, and safe derivation resumes while consensus unsafe is still pinned

Testing

  • cargo +nightly fmt
  • git diff --check
  • cargo test -p base-consensus-engine

Change Management

type=routine
risk=low
impact=sev5

@cb-heimdall

Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

Comment on lines +207 to +209
Err(_) => {
warn!(target: "engine", block_num, "Failed to fetch derived L2 block before build fallback");
return Err(ConsolidateTaskError::FailedToFetchUnsafeL2Block);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: The error from l2_block_by_label is discarded here. While this follows the pre-existing pattern at line 262, it makes diagnosing RPC failures harder. Consider logging the error:

Suggested change
Err(_) => {
warn!(target: "engine", block_num, "Failed to fetch derived L2 block before build fallback");
return Err(ConsolidateTaskError::FailedToFetchUnsafeL2Block);
Err(e) => {
warn!(target: "engine", block_num, error = ?e, "Failed to fetch derived L2 block before build fallback");
return Err(ConsolidateTaskError::FailedToFetchUnsafeL2Block);
}

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

✅ base-std fork tests: all 616 passed

base/base is fully in sync with the base-std spec.

Dependency Ref Commit
base-std main 4658f1b7
base-anvil 0092692587d8d064dd2c6923ce26a682c58f3694 00926925

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Summary

Overall this is a well-structured fix for a real edge case where an earlier unsafe FCU returns SYNCING, reth backfills the block, but consensus never advances its unsafe head — causing safe derivation to fall through to an unnecessary build_and_seal.

What the PR does right

  • The new reconcile_existing_derived_block method correctly checks whether the EL already has the derived block before falling back to FCU-with-attributes build, avoiding stale builds.
  • The ForkchoiceUpdateDidNotAdvance post-condition check in reconcile_to_safe_head correctly catches the case where SynchronizeTask succeeds but the state didn't actually advance (e.g., EL returned SYNCING and safe_only_sync_update filtered the update). Marking this as Temporary severity ensures the task is retried rather than silently dropped.
  • The execute() refactor from a flat if/else to a match correctly preserves the original semantics while inserting the new reconciliation step only in the Attributes path where safe_head >= unsafe_head.
  • Tests cover both non-span and last-in-span variants and verify that no FCU-with-attributes (stale build) is issued during reconciliation.

Minor finding

  • Discarded error context (task.rs:207): The Err(_) in reconcile_existing_derived_block discards the actual RPC error, making production debugging harder. This follows the pre-existing pattern but would benefit from logging the error value. (Left inline comment with suggestion.)

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