nautechsystems/nautilus_trader · critical · anyhow::Error
Verified finalized header ledger mismatch for height {number
Error message
Verified finalized header ledger mismatch for height {number} What it means
This is a data-integrity assertion: after inserting and reading back the finalized header ledger row, the code ensure()s the stored tuple (hash, parent_hash, timestamp, base_fee_per_gas, manifest_digest) equals what was intended to write. A mismatch means the ledger row at this height differs from the header being verified — i.e. the append-only invariant of the verified header ledger was violated. It indicates either a conflicting header for the same height or corrupted/stale ledger data.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:7028
.bind(&base_fee)
.bind(finality.manifest_digest)
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to extend finalized header ledger: {e}"))?;
let stored = sqlx::query_as::<_, (String, String, i64, Option<String>, String)>(
"
SELECT hash, parent_hash, timestamp, base_fee_per_gas, manifest_digest
FROM execution_verified_finalized_header
WHERE chain_id = $1 AND wallet_address = $2 AND number = $3
",
)
.bind(chain_id)
.bind(finality.wallet_address)
.bind(number)
.fetch_one(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to validate finalized header ledger: {e}"))?;
anyhow::ensure!(
stored
== (
header.hash.clone(),
header.parent_hash.clone(),
timestamp,
base_fee,
finality.manifest_digest.to_string(),
),
"Finalized header ledger conflicts at height {}",
header.number
);
}
let (current_status, intent_nonce, fill_emitted, terminal_emitted) =
sqlx::query_as::<_, (String, Option<i64>, bool, bool)>(
"
SELECT status, nonce, fill_emitted, terminal_emitted
FROM execution_intentView on GitHub (pinned to 18893faf8b)
Solutions
- Compare the stored tuple in the error context with the incoming header values to identify which field diverged
- If a legitimate reorg occurred, clear/rebuild the ledger rows for the affected heights from the authoritative source before reprocessing
- Confirm chain_id and wallet_address scoping are correct — the mismatch may be a foreign row
- Ensure manifest_digest and base_fee computation is deterministic and unchanged since the original write
- Treat as a integrity alarm: do not overwrite the ledger row silently; investigate which writer produced the divergent header
Example fix
// before
anyhow::ensure!(
stored == (header.hash.clone(), header.parent_hash.clone(), timestamp, base_fee, finality.manifest_digest),
"Verified finalized header ledger mismatch for height {number}"
);
// after
let expected = (header.hash.clone(), header.parent_hash.clone(), timestamp, base_fee, finality.manifest_digest);
anyhow::ensure!(
stored == expected,
"Verified finalized header ledger mismatch for height {number}: stored={stored:?} expected={expected:?}"
); Defensive patterns
Strategy: validation
Validate before calling
// Before writing, check for a conflicting ledger row
let existing = sqlx::query_as::<_, (String, String)>(
"SELECT hash, parent_hash FROM execution_verified_finalized_header \
WHERE chain_id = $1 AND wallet_address = $2 AND number = $3"
)
.bind(chain_id).bind(finality.wallet_address).bind(number)
.fetch_optional(&mut *conn).await?;
if let Some((hash, parent)) = &existing {
if *hash != header.hash || *parent != header.parent_hash {
return Err(anyhow!("reorg/conflict detected at height {number}"));
}
} Type guard
fn ledger_matches(stored: &(String, String, i64, Option<String>, String),
expected: &(String, String, i64, Option<String>, String)) -> bool {
stored == expected
} Try / catch
match apply_verified_finality(...).await {
Err(e) if e.to_string().contains("ledger mismatch") => {
// halt processing for this chain and alert — possible reorg or corrupt ledger
alert_ops("verified header ledger integrity violation");
Err(e)
}
other => other,
} Prevention
- Never overwrite ledger rows; treat mismatches as reorg/corruption alarms
- Track chain reorgs and rebuild the ledger segment for re-parented heights
- Keep manifest_digest computation versioned and deterministic
- Ensure single writer per (chain_id, wallet) to avoid divergent headers
When it happens
Trigger: Re-verifying a height whose ledger row already exists with a different hash/parent_hash (chain reorg or fork); a prior partial/corrupt write; recomputing timestamp/base_fee/manifest_digest differently than when the row was first stored; a wrong wallet_address or chain_id scoping pulling in a foreign row.
Common situations: Replaying finality events after a node switched to a different chain branch (reorg); backfilling historical headers over rows recorded from another source; mixing testnet/mainnet data under the same chain_id; a code change altering manifest_digest computation between the original write and re-verification.
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
- Execution verification manifest identity changed
- Finalized header manifest identity changed
- Migrated terminal transaction hash was not found
- Execution payload marker exists without state
- Execution payload storage is marked ready without its write
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/7f7749fab12e24fb.
Report an issue: GitHub.