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

  1. 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.
  2. If manifest_digest differs, re-run bootstrap with the manifest recorded in the ledger, or rebuild the ledger under the new manifest.
  3. If hash/parent_hash differ, treat it as a fork: rebuild the ledger from the trusted checkpoint or last common ancestor instead of appending.
  4. If only timestamp/base_fee differ, check for serialization/type drift between write and read paths after upgrades and normalize formats.
  5. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/d201d037eb823be0. Report an issue: GitHub.