datahaven-xyz/datahaven · error
InvalidExecutionHeaderProof
InvalidExecutionHeaderProof
Error message
InvalidExecutionHeaderProof
What it means
verify_execution_proof verifies that the execution header's hash_tree_root is a valid Merkle branch of the header's body_root, using the fixed generalized index for the execution header (execution_header_gindex) via SSZ verify_merkle_branch. If the branch is malformed, has wrong length, corresponds to a different leaf, or was computed against an incompatible spec, verification fails with InvalidExecutionHeaderProof.
Solutions
- Regenerate the execution_branch from the beacon block body using the exact generalized index returned by execution_header_gindex() and standard SSZ hash_tree_root.
- Confirm the branch length equals generalized_index_length(execution_header_gindex) (expected 3 for the standard execution header gindex).
- Verify the proof is built against the same header whose body_root is in execution_proof.header (not a different fork).
- Update pallet gindex config if an Ethereum spec change altered the execution header's position in the block body tree.
Example fix
// before: hand-built branch
const executionBranch = block.body.executionPayloadBranch; // wrong index
// after: use the gindex-derived branch matching the pallet's config
const gIndex = EXECUTION_HEADER_GENERALIZED_INDEX; // must match execution_header_gindex()
const executionBranch = getMerkleBranch(block.body, gIndex);
await api.tx.ethereumClient.submitExecutionProof({ header, executionHeader, executionBranch }).signAndSend(relayer); Defensive patterns
Strategy: validation
Validate before calling
if (proof.executionBranch.length !== EXPECTED_EXECUTION_HEADER_GINDEX_LENGTH) throw new Error(`branch length must be ${EXPECTED_EXECUTION_HEADER_GINDEX_LENGTH}`);
const recomputed = ssz.BeaconBlockBody.hashTreeRoot(proof.body);
// ensure branch is generated for this exact body_root Type guard
const hasValidBranchShape = (p) => Array.isArray(p.executionBranch) && p.executionBranch.length === 3 && p.executionBranch.every(isHex32);
Try / catch
try {
await api.tx.ethereumClient.submitExecutionProof(proof).signAndSend(relayer);
} catch (e) {
if (String(e).includes('InvalidExecutionHeaderProof')) regenerateProofAndAlert(proof);
else throw e;
} Prevention
- Generate branches with the same SSZ library/spec version the pallet uses.
- Assert branch length matches generalized_index_length(execution_header_gindex).
- Verify proofs locally with verify_merkle_branch before submission.
- Re-verify branch construction after Ethereum hard forks.
When it happens
Trigger: Submitting an execution proof whose execution_branch array is wrong-length, mis-ordered, computed for a different header/body_root, or generated with an incompatible SSZ/hash-tree-root implementation or spec version than execution_header_gindex expects.
Common situations: Relayer bugs building the branch (wrong gindex depth/leaf), hard-fork changes in Ethereum SSZ layouts vs the pallet's configured gindex, or truncating/copying branch arrays during serialization.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- InvalidAncestryMerkleProof
- HeaderNotFinalized
- InvalidSyncCommitteeMerkleProof
- InvalidBlockRootsRootMerkleProof
- InvalidHeaderMerkleProof
AI-assisted analysis of datahaven-xyz/datahaven@edcb13dbbc (2026-09-13).
Data as JSON: /api/errors/5b9d81229562d523.
Report an issue: GitHub.
Appendix: source
Thrown at operator/pallets/ethereum-client/src/impls.rs:121
// finalized header root at the expected slot.
let state = <FinalizedBeaconState<T>>::get(beacon_block_root)
.ok_or(Error::<T>::ExpectedFinalizedHeaderNotStored)?;
if execution_proof.header.slot != state.slot {
return Err(Error::<T>::ExpectedFinalizedHeaderNotStored.into());
}
}
}
// Gets the hash tree root of the execution header, in preparation for the execution
// header proof (used to check that the execution header is rooted in the beacon
// header body.
let execution_header_root: H256 = execution_proof
.execution_header
.hash_tree_root()
.map_err(|_| Error::<T>::BlockBodyHashTreeRootFailed)?;
let execution_header_gindex = Self::execution_header_gindex();
ensure!(
verify_merkle_branch(
execution_header_root,
&execution_proof.execution_branch,
subtree_index(execution_header_gindex),
generalized_index_length(execution_header_gindex),
execution_proof.header.body_root
),
Error::<T>::InvalidExecutionHeaderProof
);
Ok(())
}
/// Verify that `block_root` is an ancestor of `finalized_block_root` Used to prove that
/// an execution header is an ancestor of a finalized header (i.e. the blocks are
/// on the same chain).
fn verify_ancestry_proof(
block_root: H256,
block_slot: u64,View on GitHub (pinned to edcb13dbbc)