nautechsystems/nautilus_trader · error

Canonical head {head_block} is behind execution creation blo

Error message

Canonical head {head_block} is behind execution creation block {from_block}

What it means

`replacement_scan_range` computes the block range to scan for a replacement transaction and requires the current canonical head to be at or beyond the original creation block. A head behind `from_block` means the chain view regressed relative to the execution's own history (fork/reorg or connected to a stale/different node), so no valid scan range exists.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:3574

                effective_gas_price: &effective_gas_price,
                manifest_version: &self.manifest_version,
                manifest_digest: &self.manifest_digest,
                provider_ids: &self.provider_ids,
                operator_ids: &self.operator_ids,
                failure_domain_ids: &self.failure_domain_ids,
                decisions: &decisions,
                finalized_headers: &included.finality.finalized_headers,
            })
            .await
    }

    fn release_slot(&self) {
        *self.in_flight.lock() = None;
    }
}

fn replacement_scan_range(from_block: u64, head_block: u64) -> anyhow::Result<RangeInclusive<u64>> {
    anyhow::ensure!(
        head_block >= from_block,
        "Canonical head {head_block} is behind execution creation block {from_block}"
    );
    let max_end = from_block.saturating_add(MAX_REPLACEMENT_SCAN_BLOCKS - 1);
    Ok(from_block..=head_block.min(max_end))
}

fn current_unix_secs() -> anyhow::Result<u64> {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|_| anyhow::anyhow!("Trusted host clock precedes the Unix epoch"))
        .map(|duration| duration.as_secs())
}

fn validate_payload_operation_batch_size(batch_size: usize) -> anyhow::Result<i64> {
    anyhow::ensure!(
        (1..=MAX_PAYLOAD_OPERATION_BATCH_SIZE).contains(&batch_size),
        "Payload operation batch size must be between 1 and {MAX_PAYLOAD_OPERATION_BATCH_SIZE}"

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the connected node's sync status and chain ID; wait for it to reach at least `from_block`.
  2. Re-point the client at the previously used healthy RPC endpoint.
  3. Recompute `from_block` from current canonical chain data.

Example fix

// before: failover to lagging node
let head = lagging_provider.block_number().await?; // 800
replacement_scan_range(1000, head)?; // ensure! fails
// after: guard before scanning
ensure!(head >= from_block, "waiting for node sync");
Defensive patterns

Strategy: validation

Validate before calling

let head = provider.block_number().await?;
anyhow::ensure!(head >= from_block, "node head {} behind creation block {}", head, from_block);

Try / catch

match replacement_scan_range(from_block, head) {
    Err(e) if e.to_string().contains("behind execution creation block") => wait_for_sync_then_retry().await,
    other => other,
}

Prevention

When it happens

Trigger: Requesting a replacement (speed-up/cancel) scan when `head_block < from_block` — e.g. after switching to a lagging RPC node, or a reorg that rolled the head below the block where the original transaction was created.

Common situations: Failover to an unsynced RPC endpoint; connecting to a different network with lower block height; misconfigured `from_block` computed from a wrong clock or chain data.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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