fix(consensus): Inconsistent Safe And Finalized Heads#2163
fix(consensus): Inconsistent Safe And Finalized Heads#2163
Conversation
🟡 Heimdall Review Status
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
43fbd1d to
e5de3f7
Compare
| } | ||
|
|
||
| // Consolidate and delegated forkchoice share equal priority. | ||
| (Self::Consolidate(_) | Self::DelegatedForkchoice(_), _) => Ordering::Equal, |
There was a problem hiding this comment.
PartialEq and Ord now disagree for Consolidate vs DelegatedForkchoice: eq() returns false (only same-variant pairs match at line 172-173), but cmp() returns Ordering::Equal here. Rust's Ord contract requires a == b iff cmp(a, b) == Equal.
This extends a pre-existing inconsistency (Seal vs GetPayload has the same issue), and since the only consumer is a BinaryHeap (which only calls cmp), there's no practical bug today. But if EngineTask were ever placed in a BTreeSet, used with .dedup() after sorting, or compared via == in code that assumes Ord-consistency, it would silently misbehave.
Non-blocking, but worth a tracking issue. The simplest fix: fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal }.
|
LGTM! Will approve once tested on our nodes |
483b612 to
ae567e6
Compare
Review SummaryOverall: Looks good. The fix correctly addresses the root cause — safe and finalized signals were sent independently, allowing finalized to race ahead of a safe head that the EL hadn't actually accepted yet. What the PR does well
Notes on prior inline comments
No new findings. |
Summary
This change fixes a follow-node-only inconsistency between delegated safe and finalized updates. Before this patch, the L2 delegate actor sent safe and finalized signals as separate fire-and-forget requests, so a restart could leave the engine on a conservative safe head while the delegate path still enqueued a newer finalized target. If the safe forkchoice update returned Syncing, the engine's actual safe head did not advance, but the separate finalize request could still arrive and trip the invariant that finalized blocks must already be safe.
The fix adds a dedicated delegated forkchoice path for follow mode that applies delegated safe and finalized labels together inside the engine. The new task consolidates the delegated safe head first and then derives the finalized target from the engine's real post-consolidation safe head, which prevents follow nodes from finalizing past what the local EL has actually accepted while leaving validator and sequencer behavior unchanged. The PR also adds engine and service regressions covering the restart path and the case where delegated safe data is unavailable.