nautechsystems/nautilus_trader · error
Retained terminal receipt is above the verified finalized bo
Error message
Retained terminal receipt is above the verified finalized boundary
What it means
When migrating a retained terminal (finalized/reverted) intent, the client fetches its receipt and requires the receipt's block number to be at or below the currently verified `finalized` block. This error is thrown when the stored transaction receipt points to a block that the node no longer considers finalized — i.e. the receipt cannot be proven final under the current finalized header, so the terminal status cannot be safely migrated. It guards against trusting receipts from unfinalized or reorged-out blocks.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:5505
nonce < next_canonical_nonce,
"Retained active nonce {nonce} is above canonical nonce {next_canonical_nonce}"
);
let receipt_verification = required_verification(
self.verification
.verify_receipt(&B256::from_str(¤t.transaction_hash).with_context(
|| {
format!(
"Retained transaction hash {} is invalid",
current.transaction_hash
)
},
)?)
.await,
"migration receipt",
)?;
let receipt = receipt_verification.value.clone();
anyhow::ensure!(
receipt.block_number <= finalized.number,
"Retained terminal receipt is above the verified finalized boundary"
);
let inclusion_verification = required_verification(
self.verification.verify_block(receipt.block_number).await,
"migration inclusion header",
)?;
anyhow::ensure!(
inclusion_verification.value.hash == receipt.block_hash
&& finalized_headers.iter().any(|header| {
header.number == receipt.block_number
&& header.hash == receipt.block_hash.to_string()
}),
"Retained terminal receipt is not on the verified finalized ancestry"
);
let tx_hash = B256::from_str(¤t.transaction_hash)
.context("Retained transaction hash is invalid")?;
let included = IncludedTransaction {View on GitHub (pinned to 18893faf8b)
Solutions
- Wait for the node to sync so its finalized block number is >= the receipt's block number, then re-run migration.
- Verify you are connected to the same chain/network the receipts were produced on; fix the RPC/chain config and retry.
- Re-verify the intent's transaction on-chain (fetch a fresh receipt) and update the stored terminal record if the original receipt was from a reorged block.
- If the receipt's block genuinely lost finality, revert the intent to a recoverable state so it can be re-prepared and re-signed.
Example fix
// before (migrating against a lagging node)
let finalized = provider.latest_finalized().await?; // block 100
// receipt.block_number = 105 -> error
// after (gate migration on finality catch-up)
if finalized.number < receipt.block_number {
wait_for_finality(receipt.block_number).await?;
} Defensive patterns
Strategy: retry
Validate before calling
async fn finality_caught_up(provider: &Provider, receipt_block: u64) -> Result<bool, Error> {
let finalized = provider.get_finalized_block().await?;
Ok(finalized.number >= receipt_block)
} Type guard
fn receipt_is_final(receipt_block: u64, finalized_number: u64) -> bool {
receipt_block <= finalized_number
} Try / catch
// retry until the node's finalized head passes the receipt block
loop {
match run_migration().await {
Err(e) if e.to_string().contains("above the verified finalized boundary") => {
tokio::time::sleep(Duration::from_secs(12)).await; // ~1 block
continue;
}
other => break other,
}
} Prevention
- Only run migration after the node reports a healthy, current finalized head.
- Use the same chain/RPC endpoint that produced the retained receipts.
- On weak-finality chains, wait extra confirmations before persisting receipts as terminal.
- Alert on finalized-head regressions, which indicate reorgs that can invalidate retained receipts.
When it happens
Trigger: Raised by `anyhow::ensure!(receipt.block_number <= finalized.number, ...)` when: the node's finalized head moved backwards or the migration runs against a different/forked chain than the one that produced the receipt; the node is behind and its `finalized` block is older than the receipt's block; or the persisted receipt references a block from an uncle/reorged fork.
Common situations: Switching RPC providers or networks (testnet vs fork) while retaining state; a chain reorg invalidating previously 'final' receipts (weak-finality chains); running migration immediately after node startup before finalized head catches up; replaying state from a snapshot ahead of the current node's finalized block.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- Finalized execution transaction {tx_hash} no longer has a re
- Finalized block {} changed from {} to {} before intent valid
- Finalized block {} changed from {} to {} before fill emissio
- Finalized header verification disagreed
- Finalized header verification is locally invalid
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/54402029f37efd67.
Report an issue: GitHub.