datahaven-xyz/datahaven · error
HeaderNotFinalized
HeaderNotFinalized
Error message
HeaderNotFinalized
What it means
verify_execution_proof in the ethereum-client pallet checks that the supplied execution-layer header's slot does not exceed the slot of the latest finalized beacon state stored at LatestFinalizedBlockRoot. A header with a slot beyond that point cannot yet be proven as an ancestor of a finalized checkpoint, so the proof is rejected with HeaderNotFinalized. The pallet only accepts proofs for headers that are provably finalized.
Solutions
- Wait until the beacon client finalizes the target slot (update_finalized_beacon_state / checkpoint update) and resubmit the proof.
- Verify the pallet is bootstrapped to a current checkpoint and that relayers follow finality, not just head.
- Check the execution header slot against FinalizedBeaconState[LatestFinalizedBlockRoot].slot before submission.
Example fix
// before: submit as soon as header is seen
await api.tx.ethereumClient.submitExecutionProof(proof).signAndSend(relayer);
// after
const latestRoot = await api.query.ethereumClient.latestFinalizedBlockRoot();
const state = await api.query.ethereumClient.finalizedBeaconState(latestRoot);
if (proof.header.slot > state.slot.toNumber()) throw new Error('header not yet finalized');
await api.tx.ethereumClient.submitExecutionProof(proof).signAndSend(relayer); Defensive patterns
Strategy: retry
Validate before calling
const latestRoot = await api.query.ethereumClient.latestFinalizedBlockRoot();
const state = await api.query.ethereumClient.finalizedBeaconState(latestRoot);
if (!state.isSome) throw new Error('client not bootstrapped');
if (proof.header.slot > state.value.slot.toNumber()) throw new Error('wait for finality: header slot beyond latest finalized state'); Try / catch
try {
await api.tx.ethereumClient.submitExecutionProof(proof).signAndSend(relayer);
} catch (e) {
if (String(e).includes('HeaderNotFinalized')) scheduleRetryAfterFinality(proof);
else throw e;
} Prevention
- Relayers should track beacon finality checkpoints, not chain head.
- Gate submissions on state.slot >= header.slot.
- Bootstrap the light client to a recent checkpoint before operating.
- Add backoff-based resubmission for proofs that are merely early.
When it happens
Trigger: Submitting an execution proof (e.g. via submit_execution_proof / message-relay style extrinsics) whose execution header slot is greater than latest_finalized_state.slot — i.e., relaying a header that FinalizedBeaconState hasn't caught up to yet.
Common situations: Relayer racing ahead of the beacon finality updates (submitting proofs before the bootstrap/latest-finalized root is updated); the pallet not yet bootstrapped to a recent checkpoint; clock/slot arithmetic off between relayer and chain.
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
- InvalidExecutionHeaderProof
- InvalidAncestryMerkleProof
- SyncCommitteeUpdateRequired
- InvalidSyncCommitteeUpdate
- SyncCommitteeParticipantsNotSupermajority
AI-assisted analysis of datahaven-xyz/datahaven@edcb13dbbc (2026-09-13).
Data as JSON: /api/errors/e9b73343e2596787.
Report an issue: GitHub.
Appendix: source
Thrown at operator/pallets/ethereum-client/src/impls.rs:81
log::trace!(
target: "ethereum-client",
"💫 Failed to decode transaction receipt: {}",
err
);
Err(InvalidProof)
}
}
}
/// Validates an execution header with ancestry_proof against a finalized checkpoint on
/// chain.The beacon header containing the execution header is sent, plus the execution header,
/// along with a proof that the execution header is rooted in the beacon header body.
pub(crate) fn verify_execution_proof(execution_proof: &ExecutionProof) -> DispatchResult {
let latest_finalized_state =
FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
.ok_or(Error::<T>::NotBootstrapped)?;
// Checks that the header is an ancestor of a finalized header, using slot number.
ensure!(
execution_proof.header.slot <= latest_finalized_state.slot,
Error::<T>::HeaderNotFinalized
);
let beacon_block_root: H256 = execution_proof
.header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
match &execution_proof.ancestry_proof {
Some(proof) => {
Self::verify_ancestry_proof(
beacon_block_root,
execution_proof.header.slot,
&proof.header_branch,
proof.finalized_block_root,
)?;
}View on GitHub (pinned to edcb13dbbc)