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

  1. 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.
  2. Confirm the branch length equals generalized_index_length(execution_header_gindex) (expected 3 for the standard execution header gindex).
  3. Verify the proof is built against the same header whose body_root is in execution_proof.header (not a different fork).
  4. 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

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


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)