Skip to content

fix(follower): handle syncing semantics in the executor - #7353

Merged
hamdiallam merged 5 commits into
hamdi/follow.executor.updatesfrom
hamdi/follower.syncing
Aug 30, 2026
Merged

fix(follower): handle syncing semantics in the executor#7353
hamdiallam merged 5 commits into
hamdi/follow.executor.updatesfrom
hamdi/follower.syncing

Conversation

@hamdiallam

@hamdiallam hamdiallam commented Aug 29, 2026

Copy link
Copy Markdown
Contributor
  • If Reth report SYNCING, hold back from acknowledging any blocks. These blocks remain in the queue and will be flushed once the sync is complete
  • When syncing, additional FCUs are blocked when there are pending finalized blocks to flush. This prevents starving progressing finalized state. However the actor loop is not blocked and the pending target can continually update while the node is syncing
  • Backfill blocks from CL to EL on startup as Reth may not have persisted state

@hamdiallam
hamdiallam force-pushed the hamdi/follower.syncing branch from b65ca11 to 7c49ea4 Compare August 29, 2026 23:45
@hamdiallam
hamdiallam changed the base branch from main to hamdi/follow.executor.updates August 29, 2026 23:46
@hamdiallam

Copy link
Copy Markdown
Contributor Author

cyclops audit fast

@tempoxyz-bot tempoxyz-bot left a comment

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.

👁️ Cyclops Review

This change adds follower-executor syncing retries and startup backfill, but two verified liveness failures remain.

Reviewer Callouts
  • Execution-provider test stub: It returns VALID for fabricated heads never submitted through new_payload, masking Reth's unknown-head SYNCING behavior.
  • last_fcu semantics: It represents both a speculative pipeline target and an accepted forkchoice head, although those states diverge after SYNCING.
  • Single pending acknowledgement: With max_pending_acks = 1, any indefinitely withheld acknowledgement halts consensus-to-execution delivery.

match submit_forkchoice_update(&context, &execution_engine, &forkchoice).await {
Ok(()) => ExecutionTaskResult::Completed(forkchoice),
Ok(ForkchoiceOutcome::Valid) => ExecutionTaskResult::Completed(forkchoice),
Ok(ForkchoiceOutcome::Syncing) => ExecutionTaskResult::Completed(forkchoice),

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.

🚨 [SECURITY] Speculative certified head permanently deadlocks finalized-block delivery

A head-only FCU that returns SYNCING is recorded as completed, so its block-unknown certified head becomes last_fcu. The next finalized block FCU is built from that speculative head; because the certificate has a higher round, advancing finality does not repoint the head to the delivered block. Reth returns SYNCING, the block is requeued at the front without acknowledgement, and max_pending_acks = 1 prevents marshal from delivering the blocks needed to make the head reachable. Normal certificate-before-block ordering can therefore make the follower retry forever.

Recommended Fix:
Track speculative pipeline targets separately from FCUs confirmed VALID. Build block FCUs from a confirmed head (or the just-accepted block), acknowledge a block whose new_payload succeeded even if only its speculative-head FCU is syncing, and retry the speculative head asynchronously. Update the stub to return SYNCING for unknown heads and add this regression case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Non issue. The head will be reachable over el p2p. Marshal will deliver Update::Block as they come in

.marshal
.get_block(Height::new(height))
.await
.ok_or_else(|| eyre!("marshal missing backfill block at height `{height}`"))?;

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.

⚠️ [ISSUE] Missing startup backfill block permanently prevents follower startup

Any missing marshal block between the EL finalized height and the stored floor becomes an error, and run immediately returns. The follow engine is torn down and, while storage remains unchanged, every restart fails at the same height. The unbounded backfill retry loop also prevents the actor from servicing normal block and finalization messages while Reth remains syncing.

Recommended Fix:
Make startup backfill recoverable: do not terminate the actor when marshal temporarily lacks a height, and schedule bounded/yielding retries while continuing to service the mailbox (or reconcile the floor and resume normal marshal delivery). Add tests for a missing intermediate block and persistent SYNCING during backfill.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Non issue. Anything marshal has processed should be durable in order. There cannot be gaps or this would be corrupted state

pepyakin
pepyakin previously approved these changes Aug 30, 2026
);
actor.start();

context.sleep(Duration::from_secs(2)).await;

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.

why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The first part of this test has new_payload syncing for the first time so we need to sleep to account for the retry

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it makes sense to split this into a unique test though

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@hamdiallam
hamdiallam merged commit 04bdc3f into hamdi/follow.executor.updates Aug 30, 2026
40 of 46 checks passed
@hamdiallam
hamdiallam deleted the hamdi/follower.syncing branch August 30, 2026 16:36
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.

3 participants