datahaven-xyz/datahaven · error · Error
InvalidFinalizedHeaderGap
InvalidFinalizedHeaderGap
Error message
InvalidFinalizedHeaderGap
What it means
The gap between the current finalized header slot and the new finalized header slot exceeds `SLOTS_PER_HISTORICAL_ROOT` (8192 slots, one sync committee period). The light client caches block roots to serve ancestry proofs for execution headers; a gap larger than one historical-roots window would break those proofs, so `verify_update` rejects it.
Solutions
- Import updates incrementally so the finalized header advances at most one period at a time.
- Re-bootstrap from a fresh trusted checkpoint if the gap cannot be covered by intermediate updates.
- Keep the relayer monitored so finalized-header gaps never exceed one period.
- Combine with period checks: the SkippedSyncCommitteePeriod guard usually fires first; fix the same root cause.
Example fix
// before
submit(latest_update); // finalized_header.slot far ahead
// after
while client.latest_finalized_slot() + 8192 < latest_update.finalized_header.slot {
submit(next_intermediate_update())?;
}
submit(latest_update); Defensive patterns
Strategy: validation
Validate before calling
if (latestFinalizedSlot + 8192 < update.finalizedHeader.slot) throw new Error('finalized header gap too large'); Try / catch
try { await submit(update); } catch (e) { if (e.includes('InvalidFinalizedHeaderGap')) { submitIntermediateUpdates(); } else throw e; } Prevention
- Advance the client at most one period per import
- Watch finalized-slot lag metrics
- Re-bootstrap after long outages
When it happens
Trigger: Submitting an update where `latest_finalized_state.slot + 8192 < update.finalized_header.slot` — the client's finalized header is more than one full sync committee period behind the update being imported.
Common situations: Relayer downtime longer than one period followed by submission of only the latest update; client halted (see the Halted kill-switch) while the chain kept finalizing; operator skips intermediate updates.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- InvalidSyncCommitteeMerkleProof
- InvalidBlockRootsRootMerkleProof
- InvalidUpdateSlot
- SkippedSyncCommitteePeriod
- IrrelevantUpdate
AI-assisted analysis of datahaven-xyz/datahaven@edcb13dbbc (2026-09-13).
Data as JSON: /api/errors/4e8b12e656809e98.
Report an issue: GitHub.
Appendix: source
Thrown at operator/pallets/ethereum-client/src/lib.rs:363
)
}
// Verify update is relevant.
let update_attested_period = compute_period(update.attested_header.slot);
let update_finalized_period = compute_period(update.finalized_header.slot);
let update_has_next_sync_committee = !<NextSyncCommittee<T>>::exists()
&& (update.next_sync_committee_update.is_some()
&& update_attested_period == store_period);
ensure!(
update.attested_header.slot > latest_finalized_state.slot
|| update_has_next_sync_committee,
Error::<T>::IrrelevantUpdate
);
// Verify the finalized header gap between the current finalized header and new imported
// header is not larger than the sync committee period, otherwise we cannot do
// ancestry proofs for execution headers in the gap.
ensure!(
latest_finalized_state
.slot
.saturating_add(config::SLOTS_PER_HISTORICAL_ROOT as u64)
>= update.finalized_header.slot,
Error::<T>::InvalidFinalizedHeaderGap
);
let fork_versions = T::ForkVersions::get();
let finalized_root_gindex = Self::finalized_root_gindex_at_slot(
update.attested_header.slot,
fork_versions.clone(),
);
// Verify that the `finality_branch`, if present, confirms `finalized_header` to match
// the finalized checkpoint root saved in the state of `attested_header`.
let finalized_block_root: H256 = update
.finalized_header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;View on GitHub (pinned to edcb13dbbc)