nautechsystems/nautilus_trader · error
Replacement block conflicts with its canonical header
Error message
Replacement block conflicts with its canonical header
What it means
When starting a replacement scan from scratch, the client fetches the canonical header at the start block and, independently, the replacement (full) block at that height, then requires the block's derived header to match the canonical header. A mismatch means the two RPC views of the same block height disagree — typically a reorg or an inconsistent node.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:3087
"canonical replacement window",
)?;
decisions.push(verification_decision(&window, Some(start), Some(end)));
blocks = window.value;
} else {
let start_header = required_verification(
self.verification.verify_block(start).await,
"canonical replacement start header",
)?;
decisions.push(verification_decision(
&start_header,
Some(start),
Some(start),
));
let start_block = required_verification(
self.verification.verify_replacement_block(start).await,
"canonical replacement start block",
)?;
anyhow::ensure!(
VerifiedBlockHeader::from(start_block.value.clone()) == start_header.value,
"Replacement block conflicts with its canonical header"
);
decisions.push(verification_decision(
&start_block,
Some(start),
Some(start),
));
blocks.push(start_block.value);
if end > start {
let window = required_verification(
self.verification
.verify_replacement_window(start_header.value, end)
.await,
"canonical replacement window",
)?;
decisions.push(verification_decision(&window, Some(start + 1), Some(end)));View on GitHub (pinned to 18893faf8b)
Solutions
- Retry the scan after the reorg settles; the two views should converge
- Pin all requests to a single trusted RPC node instead of a load balancer
- Confirm both endpoints resolve to the same chain_id
- Clear or advance the replacement cursor and re-run reconciliation
Defensive patterns
Strategy: retry
Validate before calling
let header = client.block_header(start).await?;
let block = client.replacement_block(start).await?;
if VerifiedBlockHeader::from(block) != header {
return Err("provider views disagree; wait for reorg to settle");
} Try / catch
match result {
Err(e) if e.to_string().contains("conflicts with its canonical header") => {
tokio::time::sleep(reorg_settle_delay).await;
retry_scan()
}
other => other,
} Prevention
- Pin to a single RPC endpoint instead of a load balancer
- Wait for a few confirmations before scanning near the head
- Compare chain_id across providers
- Cross-check block hashes against a second source during suspected reorgs
When it happens
Trigger: verify_replacement_block(start) returned a block whose header hash/fields differ from verify_block(start) for the same height, usually because the RPC served the block from different branches of a reorg, or a misconfigured node served a different chain.
Common situations: Live chain reorg at the start height; load-balanced RPC routing requests to different nodes mid-reorg; provider serving cached data from before the reorg; accidentally switching between mainnet and a fork/testnet endpoint.
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
- Replacement scan tip conflicts with the verified canonical h
- Replacement scan cursor conflicts with the finalized header
- Canonical nonce advanced without an authenticated signer tra
- 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/afabb79d18aad75f.
Report an issue: GitHub.