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

  1. Sort the finalized headers by block number and re-fetch any missing numbers so the sequence is strictly contiguous before building the bootstrap.
  2. 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).
  3. Deduplicate headers when aggregating from multiple providers so repeated numbers do not break the `number == prev + 1` check.
  4. 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

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


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