nautechsystems/nautilus_trader · error · anyhow::Error
Finalized block {} changed from {} to {} before intent valid
Error message
Finalized block {} changed from {} to {} before intent validation What it means
finalized_transaction_matches re-fetches the block at the receipt's block_number and compares its hash to the receipt's block_hash; a mismatch means a block previously reported as finalized now has a different hash. The validation aborts because the included-transaction evidence (receipt) no longer matches the canonical chain the node is serving.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:2129
executor.release_slot();
Ok(())
}
Ok(InclusionOutcome::Pending(message)) => anyhow::bail!(message),
Err(e) => Err(e),
}
}
async fn finalized_transaction_matches(
included: &IncludedTransaction,
intent: &ExecutionIntentRow,
nonce: u64,
executor: &TransactionExecutor,
) -> anyhow::Result<bool> {
let block = executor
.http_rpc_client
.block_by_number(included.block_number, true)
.await?;
anyhow::ensure!(
block.hash == included.receipt.block_hash,
"Finalized block {} changed from {} to {} before intent validation",
included.block_number,
included.receipt.block_hash,
block.hash
);
let Some(transaction) = block
.transactions
.iter()
.find(|transaction| transaction.hash == included.tx_hash)
else {
anyhow::bail!(
"Finalized block {} does not contain transaction {}",
included.block_number,
included.tx_hash
);
};
let (expected_to, expected_input, expected_value) = persisted_call_fields(intent)?;View on GitHub (pinned to 2114cf6f76)
Solutions
- Retry reconciliation against a trusted, canonical RPC provider; a genuinely finalized block will not change again on the canonical chain
- Cross-check the block hash across multiple independent providers (two different companies) to identify the inconsistent endpoint
- If providers agree the block really did change, follow the network's incident guidance and reconcile the intent against the new canonical state
- Pin the client to a single high-quality endpoint rather than a rotating pool during reconciliation
Defensive patterns
Strategy: retry
Validate before calling
from web3 import Web3 # Cross-check the finalized block across providers before reconciling h1 = Web3(Web3.HTTPProvider(rpc_a)).eth.get_block(block_number)['hash'] h2 = Web3(Web3.HTTPProvider(rpc_b)).eth.get_block(block_number)['hash'] assert h1 == h2 == receipt_block_hash, 'providers disagree on finalized block; pick a canonical endpoint'
Try / catch
for attempt in range(5):
try:
client.connect()
break
except Exception as e:
if 'changed from' not in str(e) or 'before intent validation' not in str(e):
raise
time.sleep(20 * (attempt + 1)) # inconsistent/forked node view; retry or switch endpoint
else:
raise RuntimeError('finalized-block view never stabilized') Prevention
- Reconcile against a single trusted, canonical RPC endpoint rather than rotating multi-provider pools
- After any network reorg incident, let finality settle before reconnecting a wallet with active intents
When it happens
Trigger: Intent validation during reconciliation while the RPC node's view of a finalized block changes: deep reorgs crossing the finalized boundary (rare but possible on some networks), or inconsistent load-balanced RPC backends disagreeing about the canonical chain.
Common situations: Post-incident reconciliation after a network-level reorg event; RPC load balancers routing the second request to a node on a different fork; misconfigured/attacking endpoints serving non-canonical blocks.
Related errors
- Persisted terminal transaction {tx_hash} is not stable at th
- Finalized execution transaction {tx_hash} no longer has a re
- Finalized block {} changed from {} to {} before fill emissio
- Canonical head changed during signer-nonce replacement scan
- Finalized block {} does not contain transaction {}
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/f0a5063ad982c936.
Report an issue: GitHub.