nautechsystems/nautilus_trader · critical
Replacement scan cursor conflicts with the finalized header
Error message
Replacement scan cursor conflicts with the finalized header ledger
What it means
After finding the durable header for the cursor's block number, the code compares the stored hash with `cursor.hash` under `anyhow::ensure!`. A mismatch means the cursor claims a hash that contradicts the verified finalized header ledger — i.e. the local view of that block diverges from what was durably verified (or the cursor is corrupt/forged).
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6315
"
SELECT hash
FROM execution_verified_finalized_header
WHERE chain_id = $1 AND wallet_address = $2 AND number = $3
",
)
.bind(chain_id)
.bind(scan.wallet_address)
.bind(number)
.fetch_optional(&mut *transaction)
.await
.context("failed to validate replacement cursor against finalized headers")?
.ok_or_else(|| {
anyhow::anyhow!(
"Replacement scan cursor {} is not durably finalized",
cursor.number
)
})?;
anyhow::ensure!(
durable_hash == cursor.hash,
"Replacement scan cursor conflicts with the finalized header ledger"
);
let existing = sqlx::query_as::<_, (i64, String)>(
"
SELECT finalized_cursor_number, finalized_cursor_hash
FROM execution_replacement_scan
WHERE intent_id = $1
FOR UPDATE
",
)
.bind(scan.intent_id)
.fetch_optional(&mut *transaction)
.await
.context("failed to lock replacement scan progress")?;
if let Some((stored_number, stored_hash)) = existing {
anyhow::ensure!(View on GitHub (pinned to 18893faf8b)
Solutions
- Re-verify the header at that height and rebuild the cursor from the hash stored in `execution_verified_finalized_header`.
- If a genuine reorg occurred, run the reorg-handling path to update the finalized header ledger before re-submitting the scan.
- Confirm chain_id is correct; a mismatched network produces entirely different hashes at the same height.
Example fix
// before: trusting a cached cursor hash
let cursor = cached_cursor.clone();
// after: adopt the durably verified hash
let verified = db.verified_finalized_header(chain_id, wallet, cursor.number).await?;
let cursor = ExecutionVerifiedHeader { hash: verified.hash, ..cursor }; Defensive patterns
Strategy: validation
Validate before calling
let stored = sqlx::query_scalar::<_, String>("SELECT hash FROM execution_verified_finalized_header WHERE chain_id=$1 AND wallet_address=$2 AND number=$3").bind(chain_id).bind(wallet).bind(cursor.number as i64).fetch_optional(&pool).await?;
if stored.as_deref() != Some(cursor.hash.as_str()) { return Err(anyhow!("cursor hash diverges from finalized header ledger")); } Type guard
fn cursor_hash_matches(cursor: &ExecutionVerifiedHeader, durable_hash: &str) -> bool { cursor.hash == durable_hash } Prevention
- Rebuild cursors from the durable header ledger rather than cached RPC responses.
- Run reorg handling promptly so divergent hashes are reconciled before scans.
- Double-check chain_id when constructing headers; cross-network hashes never match.
When it happens
Trigger: Calling `record_execution_replacement_scan` with a `finalized_cursor` whose `hash` differs from the hash stored in `execution_verified_finalized_header` for the same (chain_id, wallet_address, number).
Common situations: A chain reorg replaced the block at that height after it was durably verified; the cursor hash was computed from a different network (wrong chain_id); stale bytes were cached and hashed instead of the verified header.
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
- Canonical nonce advanced without an authenticated signer tra
- Replacement block conflicts with its canonical header
- Replacement scan tip conflicts with the verified canonical h
- Profiler receipt position does not match its ingestion water
- Profiler log position does not match its ingestion watermark
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/aa85fabbd1bb796a.
Report an issue: GitHub.