nautechsystems/nautilus_trader · error · anyhow::Error
Finalized header ledger conflicts at height {}
Error message
Finalized header ledger conflicts at height {} What it means
This error is raised by the post-insert consistency check: after inserting a verified finalized header, the code re-reads the stored row and `anyhow::ensure!`s it equals the header just written (hash, parent_hash, timestamp, base_fee, manifest_digest). A mismatch means the durable row at that height differs from the verified header — the ledger is said to conflict at that height. Because inserts use `ON CONFLICT DO NOTHING`, a pre-existing divergent row at the same (chain_id, wallet_address, number) silently survives the insert and is caught here. The failing height is included in the message.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:4498
.bind(&base_fee)
.bind(bootstrap.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(bootstrap.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,
bootstrap.manifest_digest.to_string(),
),
"Finalized header ledger conflicts at height {}",
header.number
);
}
let finalized_height = bootstrap
.finalized_headers
.last()
.expect("verified finalized headers are nonempty")
.number;View on GitHub (pinned to 18893faf8b)
Solutions
- Query the stored row at the reported height (`SELECT * FROM execution_verified_finalized_header WHERE chain_id=$1 AND wallet_address=$2 AND number=$3`) and diff each field against the verified header to see which value conflicts.
- If manifest_digest differs, re-run bootstrap with the manifest recorded in the ledger, or rebuild the ledger under the new manifest.
- If hash/parent_hash differ, treat it as a fork: rebuild the ledger from the trusted checkpoint or last common ancestor instead of appending.
- If only timestamp/base_fee differ, check for serialization/type drift between write and read paths after upgrades and normalize formats.
- Ensure a single writer owns each (chain_id, wallet_address) ledger to avoid concurrent divergent writes.
Example fix
// before: silent conflict when the row pre-exists with different data INSERT INTO execution_verified_finalized_header (...) VALUES (...) ON CONFLICT (chain_id, wallet_address, number) DO NOTHING; // after: make divergence explicit instead of relying on read-back failure INSERT INTO execution_verified_finalized_header (...) VALUES (...) ON CONFLICT (chain_id, wallet_address, number) DO UPDATE SET hash = EXCLUDED.hash WHERE execution_verified_finalized_header.hash = EXCLUDED.hash; -- no-op if equal; rowcount 0 signals conflict
Defensive patterns
Strategy: validation
Validate before calling
let stored: Option<(String, String, i64, Option<String>, String)> = sqlx::query_as(
"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(wallet).bind(number as i64).fetch_optional(&pool).await?;
if let Some(row) = stored {
if row.0 != header.hash || row.4 != manifest_digest {
return Err(anyhow!("pre-insert conflict at height {number}: stored={row:?}"));
}
} Type guard
fn row_matches_header(row: &StoredRow, h: &VerifiedHeader, manifest: &str) -> bool {
row.hash == h.hash
&& row.parent_hash == h.parent_hash
&& row.timestamp == h.timestamp as i64
&& row.base_fee_per_gas == h.base_fee_per_gas.as_ref().map(|v| v.to_string())
&& row.manifest_digest == manifest
} Try / catch
match extend_finalized_ledger(&mut tx, &bootstrap).await {
Err(e) if e.to_string().contains("ledger conflicts at height") => {
let height = extract_conflict_height(&e);
// diff stored row vs verified header, then rebuild from common ancestor
Err(e.context(format!("ledger divergence at {height}; rebuild required")))
}
other => other,
} Prevention
- Pre-check for existing divergent rows before inserting (ON CONFLICT DO NOTHING hides them).
- Track manifest digest per ledger and refuse bootstraps from a different manifest.
- Detect reorgs upstream before extending the ledger.
- Keep serialization of timestamp/base_fee identical on write and read paths.
- Restrict the ledger to a single writer per (chain_id, wallet_address).
When it happens
Trigger: A row already exists at `header.number` for this (chain_id, wallet_address) with a different hash/parent_hash/timestamp/base_fee_per_gas/manifest_digest (fork or reorg, or data written by a different manifest); or the write path is subtly corrupting values (e.g. base_fee serialized differently than the SELECT parses it), so the read-back never equals the in-memory header.
Common situations: Chain reorg after the conflicting height was durably recorded; two deployments with different manifests sharing one ledger; re-running an old bootstrap against a ledger advanced by a newer version (manifest_digest mismatch); type/serialization drift after an upgrade making timestamp or base_fee comparisons fail.
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
- Verified finalized header extension does not start at the du
- Verified finalized transaction count advanced without an act
- Verified finalized headers are not one continuous parent-lin
- Verified finalized transaction count is outside the owned re
- Verified finalized headers do not start at the trusted check
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/d201d037eb823be0.
Report an issue: GitHub.