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

  1. Wait until the beacon client finalizes the target slot (update_finalized_beacon_state / checkpoint update) and resubmit the proof.
  2. Verify the pallet is bootstrapped to a current checkpoint and that relayers follow finality, not just head.
  3. 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

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


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)