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

  1. Retry the scan after the reorg settles; the two views should converge
  2. Pin all requests to a single trusted RPC node instead of a load balancer
  3. Confirm both endpoints resolve to the same chain_id
  4. 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

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


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