nautechsystems/nautilus_trader · error · anyhow::Error
Verified finality headers must form a continuous chain throu
Error message
Verified finality headers must form a continuous chain through the inclusion height
What it means
This error fires when the `finalized_headers` slice attached to a verified finality transition is empty, is not strictly sequential (each header's number must be the previous plus one and each must be the previous header's parent-hash child), or its last header does not reach the transaction's inclusion block height. The database layer requires an unbroken, hash-linked header chain proving canonicality up to the inclusion block before persisting finality. It is a fail-fast precondition checked before any SQL executes.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6906
///
/// Returns an error if the receipt transition, nonce ledger, manifest identity, or evidence
/// is inconsistent, or if persistence fails.
pub(crate) async fn record_execution_finality_verified(
&self,
finality: &ExecutionFinalityTransition<'_>,
) -> anyhow::Result<()> {
anyhow::ensure!(
matches!(
finality.status,
TransactionStatus::Finalized | TransactionStatus::Reverted
),
"Verified finality requires a finalized or reverted status"
);
anyhow::ensure!(
!finality.decisions.is_empty(),
"Verified finality requires decision evidence"
);
anyhow::ensure!(
!finality.finalized_headers.is_empty()
&& finality.finalized_headers.windows(2).all(|headers| {
headers[1].number == headers[0].number.saturating_add(1)
&& headers[1].parent_hash == headers[0].hash
})
&& finality
.finalized_headers
.last()
.is_some_and(|header| header.number >= finality.block_number),
"Verified finality headers must form a continuous chain through the inclusion height"
);
let chain_id = i32::try_from(finality.chain_id)
.context("Verification chain ID exceeds PostgreSQL INTEGER")?;
let nonce =
i64::try_from(finality.nonce).context("Execution nonce exceeds PostgreSQL BIGINT")?;
let next_nonce = nonce
.checked_add(1)
.ok_or_else(|| anyhow::anyhow!("Canonical nonce overflow"))?;View on GitHub (pinned to 18893faf8b)
Solutions
- Re-fetch the finalized header range from the node and verify each header's number is contiguous and each parent_hash links to the previous header's hash before calling the API
- Ensure the header range extends at least to finality.block_number (the inclusion height)
- Re-order headers oldest-to-newest if they were collected newest-first
- If a reorg invalidated the chain, rebuild the finality transition from the current canonical chain instead of reusing stale headers
Example fix
// before let headers = client.finalized_headers(from..to).await?; // may contain gaps after reorg db.record_execution_finality_verified(&finality_with(headers)).await?; // after let headers = client.finalized_headers(from..=finality.block_number).await?; assert!(windows(2).all(|h| h[1].number == h[0].number + 1 && h[1].parent_hash == h[0].hash)); db.record_execution_finality_verified(&finality_with(headers)).await?;
Defensive patterns
Strategy: validation
Validate before calling
// Rust: validate the header chain before persisting finality
fn headers_form_chain(headers: &[ExecutionVerifiedHeader], inclusion: u64) -> bool {
!headers.is_empty()
&& headers.windows(2).all(|h| {
h[1].number == h[0].number.saturating_add(1) && h[1].parent_hash == h[0].hash
})
&& headers.last().is_some_and(|h| h.number >= inclusion)
}
ensure!(headers_form_chain(finality.finalized_headers, finality.block_number)); Type guard
fn is_contiguous_header_chain(headers: &[ExecutionVerifiedHeader]) -> bool {
headers.windows(2).all(|h| h[1].parent_hash == h[0].hash && h[1].number == h[0].number + 1)
} Try / catch
match db.record_execution_finality_verified(&finality).await {
Err(e) if e.to_string().contains("continuous chain") => {
// re-fetch headers from the node and rebuild the transition before retrying
}
other => other?,
} Prevention
- Fetch the header range as `inclusion_height - n ..= inclusion_height` from a single consistent RPC snapshot
- Validate parent-hash linkage immediately after fetching, before building the transition
- Detect reorgs (header number/hash mismatch on refetch) and rebuild finality from the canonical chain
- Keep headers sorted ascending by number when aggregating from multiple sources
When it happens
Trigger: Calling record_execution_finality_verified with an empty finalized_headers slice; headers fetched with a gap (e.g. reorg removed a block); headers in the wrong order; header.number < finality.block_number for the last header; parent_hash not matching the previous header's hash.
Common situations: RPC provider returned a partial or reorged header range; caching layer deduplicated a header that was actually required for continuity; a reorg happened between finality decision and header fetch; test fixture headers hand-built without linking parent hashes; off-by-one so the last header stops one block short of the inclusion height.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Verified finalized transaction count advanced without an act
- Verified finalized headers are not one continuous parent-lin
- Verified finalized transaction count is outside the owned re
- Verified finalized header extension does not start at the du
- Finalized header ledger conflicts at height {}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/fe28b07ee29d5d32.
Report an issue: GitHub.