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_intent

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Compare the stored tuple in the error context with the incoming header values to identify which field diverged
  2. If a legitimate reorg occurred, clear/rebuild the ledger rows for the affected heights from the authoritative source before reprocessing
  3. Confirm chain_id and wallet_address scoping are correct — the mismatch may be a foreign row
  4. Ensure manifest_digest and base_fee computation is deterministic and unchanged since the original write
  5. 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

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


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