nautechsystems/nautilus_trader · error

Verified inclusion header {} does not match receipt hash {}

Error message

Verified inclusion header {} does not match receipt hash {}

What it means

The library cross-checks the verified inclusion header against the finalized receipt: header number must equal the included block number and header hash must equal the receipt's block hash. A mismatch means the header used to prove finality describes a different block than the one containing the receipt — a reorg, stale header, or inconsistent data source.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:4802

        OrderSide::Buy => {
            anyhow::ensure!(
                quote_amount.is_positive() && quote_amount.unsigned_abs() == plan.amount_in,
                "Finalized Swap input {quote_amount} does not match the persisted amount {}",
                plan.amount_in
            );
            anyhow::ensure!(
                base_amount.is_negative(),
                "Finalized Swap base amount {base_amount} is not a BUY output"
            );
            raw_amount_to_quantity(
                base_amount.unsigned_abs(),
                plan.pool.get_base_token().decimals,
            )?
        }
    };

    let block = &included.finality.inclusion_header;
    anyhow::ensure!(
        block.number == included.block_number
            && block.hash == included.receipt.block_hash.to_string(),
        "Verified inclusion header {} does not match receipt hash {}",
        included.block_number,
        included.receipt.block_hash
    );
    let timestamp_ns = block
        .timestamp
        .checked_mul(NANOSECONDS_IN_SECOND)
        .ok_or_else(|| anyhow::anyhow!("Finalized block timestamp overflows nanoseconds"))?;
    let mut swap = event.to_pool_swap(
        plan.pool.chain.clone(),
        plan.instrument_id,
        plan.pool.pool_identifier,
        UnixNanos::from(timestamp_ns),
    );
    swap.calculate_trade_info(&plan.pool.token0, &plan.pool.token1, None)?;
    let trade = swap

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-fetch both the inclusion header and receipt from the same RPC endpoint after finality and re-run verification
  2. Detect and wait out reorgs: confirm the block is canonical at finality depth before comparing hashes
  3. Disable/align caching so the header is not reused across verification attempts
  4. Normalize hash encoding (checksum/case) before comparison if hashes come as strings from different sources
Defensive patterns

Strategy: validation

Validate before calling

if block.number != included.block_number || block.hash != included.receipt.block_hash.to_string() {
    return Err(anyhow!("inclusion header stale or reorged"));
}

Try / catch

match verify_inclusion_header(&header, &receipt) {
    Err(e) if is_hash_mismatch(&e) => { refresh_header_and_receipt(tx_hash).await?; verify_inclusion_header(...)? }
    other => other,
}

Prevention

When it happens

Trigger: included.finality.inclusion_header was fetched before/after the receipt such that a reorg changed the canonical block at that height; the header came from a different provider than the receipt; receipt.block_hash decoded from a differently-cased or differently-encoded hash string.

Common situations: Chain reorganization between header fetch and receipt confirmation; mixing providers with different canonical views (e.g. a load-balanced RPC where one node lags); header cached from an earlier verification run and reused after the chain advanced.

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 nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/b69d02f7206d1107. Report an issue: GitHub.