nautechsystems/nautilus_trader · error · anyhow::Error
Verified finalized headers are not one continuous parent-lin
Error message
Verified finalized headers are not one continuous parent-linked chain
What it means
After confirming the finalized-header ledger is non-empty, `ensure_execution_verification_schema` validates that consecutive headers form one continuous parent-linked chain: each header's `number` must be exactly the previous number plus one, and each header's `parent_hash` must equal the previous header's `hash`. This `anyhow::ensure!` fails when the verified evidence has gaps, duplicates, or a fork, meaning the bootstrap data cannot be trusted as a canonical ledger. Throwing here prevents installing a verification schema on top of inconsistent evidence.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:3869
///
/// A database with retained execution intents but no verification ledger requires an archive-
/// verified migration which classifies every retained intent. An empty database initializes
/// its canonical nonce from a verified finalized-height transaction count and its header
/// ledger from the trusted checkpoint.
///
/// # Errors
///
/// Returns an error if the schema, migration snapshot, historical reconstruction, or retained
/// evidence is inconsistent, or persistence fails.
pub(crate) async fn ensure_execution_verification_schema(
&self,
bootstrap: &ExecutionVerificationBootstrap<'_>,
) -> anyhow::Result<()> {
let first_header = bootstrap
.finalized_headers
.first()
.ok_or_else(|| anyhow::anyhow!("Verified finalized header ledger is empty"))?;
anyhow::ensure!(
bootstrap.finalized_headers.windows(2).all(|headers| {
headers[1].number == headers[0].number.saturating_add(1)
&& headers[1].parent_hash == headers[0].hash
}),
"Verified finalized headers are not one continuous parent-linked chain"
);
anyhow::ensure!(
bootstrap.provider_ids.len() == 3
&& bootstrap.operator_ids.len() == 3
&& bootstrap.failure_domain_ids.len() >= 3
&& !bootstrap.decisions.is_empty(),
"Connect verification evidence is incomplete"
);
let chain_id = i32::try_from(bootstrap.chain_id)
.context("Verification chain ID exceeds PostgreSQL INTEGER")?;
let checkpoint_number = i64::try_from(bootstrap.checkpoint_number)
.context("Verification checkpoint exceeds PostgreSQL BIGINT")?;
let checkpoint_timestamp = i64::try_from(bootstrap.checkpoint_timestamp)View on GitHub (pinned to 18893faf8b)
Solutions
- Sort the finalized headers by block number and re-fetch any missing numbers so the sequence is strictly contiguous before building the bootstrap.
- Verify parent-hash linkage per pair yourself (same check as the ensure!) and re-fetch from a different archive provider when a link breaks (fork/gap).
- Deduplicate headers when aggregating from multiple providers so repeated numbers do not break the `number == prev + 1` check.
- Confirm all headers were fetched from a single consistent chain view (same provider/era) rather than mixed across a reorg.
Example fix
// before
let headers = fetch_headers_from_providers(&providers).await?; // possibly unsorted/duplicated
let bootstrap = ExecutionVerificationBootstrap { finalized_headers: &headers, ..b };
database.ensure_execution_verification_schema(&bootstrap).await?;
// after
let mut headers = fetch_headers_from_providers(&providers).await?;
headers.sort_by_key(|h| h.number);
headers.dedup_by_key(|h| h.number);
for w in headers.windows(2) {
anyhow::ensure!(
w[1].number == w[0].number + 1 && w[1].parent_hash == w[0].hash,
"gap or fork between blocks {} and {}",
w[0].number,
w[1].number
);
}
let bootstrap = ExecutionVerificationBootstrap { finalized_headers: &headers, ..b }; Defensive patterns
Strategy: validation
Validate before calling
fn validate_header_chain(headers: &[VerifiedFinalizedHeader]) -> Result<(), String> {
for w in headers.windows(2) {
if w[1].number != w[0].number.saturating_add(1) {
return Err(format!("gap between block {} and {}", w[0].number, w[1].number));
}
if w[1].parent_hash != w[0].hash {
return Err(format!("parent-hash fork at block {}", w[1].number));
}
}
Ok(())
} Type guard
fn is_contiguous_parent_linked_chain(headers: &[VerifiedFinalizedHeader]) -> bool {
headers.windows(2).all(|w| {
w[1].number == w[0].number.saturating_add(1) && w[1].parent_hash == w[0].hash
})
} Try / catch
if let Err(e) = database.ensure_execution_verification_schema(&bootstrap).await {
if e.to_string().contains("not one continuous parent-linked chain") {
// refetch headers from a fresh archive provider, sort, dedupe, revalidate, retry once
} else {
return Err(e);
}
} Prevention
- Sort and dedupe headers by block number before building the bootstrap.
- Fetch all headers from a single consistent provider/view to avoid mixing fork states across a reorg.
- Run the same windows(2) linkage check client-side and log the offending pair before calling the API.
- Re-fetch missing blocks instead of passing gappy ranges to migration.
When it happens
Trigger: Passing an `ExecutionVerificationBootstrap` whose `finalized_headers` slice contains headers where `headers[1].number != headers[0].number + 1` or `headers[1].parent_hash != headers[0].hash` for any adjacent pair — e.g. headers fetched from different forks, missing middle blocks, or duplicated/unordered entries.
Common situations: Fetching finalized headers from multiple providers during a chain reorg/fork and concatenating results without deduplication; a checkpoint range spanning a gap because an RPC paginated or dropped blocks; headers not sorted by number before constructing the bootstrap; switching endpoints mid-bootstrap between clients with divergent views.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Verified finalized header extension does not start at the du
- Finalized header ledger conflicts at height {}
- Verified finality headers must form a continuous chain throu
- Replacement hash {transaction_hash} conflicts with another i
- Verified finalized transaction count advanced without an act
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/90e539573d62bcb3.
Report an issue: GitHub.