{"record":{"id":"90e539573d62bcb3","repo":"nautechsystems/nautilus_trader","slug":"verified-finalized-headers-are-not-one-continuous","errorCode":null,"errorMessage":"Verified finalized headers are not one continuous parent-linked chain","messagePattern":"Verified finalized headers are not one continuous parent-linked chain","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":3869,"sourceCode":"    ///\n    /// A database with retained execution intents but no verification ledger requires an archive-\n    /// verified migration which classifies every retained intent. An empty database initializes\n    /// its canonical nonce from a verified finalized-height transaction count and its header\n    /// ledger from the trusted checkpoint.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the schema, migration snapshot, historical reconstruction, or retained\n    /// evidence is inconsistent, or persistence fails.\n    pub(crate) async fn ensure_execution_verification_schema(\n        &self,\n        bootstrap: &ExecutionVerificationBootstrap<'_>,\n    ) -> anyhow::Result<()> {\n        let first_header = bootstrap\n            .finalized_headers\n            .first()\n            .ok_or_else(|| anyhow::anyhow!(\"Verified finalized header ledger is empty\"))?;\n        anyhow::ensure!(\n            bootstrap.finalized_headers.windows(2).all(|headers| {\n                headers[1].number == headers[0].number.saturating_add(1)\n                    && headers[1].parent_hash == headers[0].hash\n            }),\n            \"Verified finalized headers are not one continuous parent-linked chain\"\n        );\n        anyhow::ensure!(\n            bootstrap.provider_ids.len() == 3\n                && bootstrap.operator_ids.len() == 3\n                && bootstrap.failure_domain_ids.len() >= 3\n                && !bootstrap.decisions.is_empty(),\n            \"Connect verification evidence is incomplete\"\n        );\n        let chain_id = i32::try_from(bootstrap.chain_id)\n            .context(\"Verification chain ID exceeds PostgreSQL INTEGER\")?;\n        let checkpoint_number = i64::try_from(bootstrap.checkpoint_number)\n            .context(\"Verification checkpoint exceeds PostgreSQL BIGINT\")?;\n        let checkpoint_timestamp = i64::try_from(bootstrap.checkpoint_timestamp)","sourceCodeStart":3851,"sourceCodeEnd":3887,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L3851-L3887","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet headers = fetch_headers_from_providers(&providers).await?; // possibly unsorted/duplicated\nlet bootstrap = ExecutionVerificationBootstrap { finalized_headers: &headers, ..b };\ndatabase.ensure_execution_verification_schema(&bootstrap).await?;\n\n// after\nlet mut headers = fetch_headers_from_providers(&providers).await?;\nheaders.sort_by_key(|h| h.number);\nheaders.dedup_by_key(|h| h.number);\nfor w in headers.windows(2) {\n    anyhow::ensure!(\n        w[1].number == w[0].number + 1 && w[1].parent_hash == w[0].hash,\n        \"gap or fork between blocks {} and {}\",\n        w[0].number,\n        w[1].number\n    );\n}\nlet bootstrap = ExecutionVerificationBootstrap { finalized_headers: &headers, ..b };","handlingStrategy":"validation","validationCode":"fn validate_header_chain(headers: &[VerifiedFinalizedHeader]) -> Result<(), String> {\n    for w in headers.windows(2) {\n        if w[1].number != w[0].number.saturating_add(1) {\n            return Err(format!(\"gap between block {} and {}\", w[0].number, w[1].number));\n        }\n        if w[1].parent_hash != w[0].hash {\n            return Err(format!(\"parent-hash fork at block {}\", w[1].number));\n        }\n    }\n    Ok(())\n}","typeGuard":"fn is_contiguous_parent_linked_chain(headers: &[VerifiedFinalizedHeader]) -> bool {\n    headers.windows(2).all(|w| {\n        w[1].number == w[0].number.saturating_add(1) && w[1].parent_hash == w[0].hash\n    })\n}","tryCatchPattern":"if let Err(e) = database.ensure_execution_verification_schema(&bootstrap).await {\n    if e.to_string().contains(\"not one continuous parent-linked chain\") {\n        // refetch headers from a fresh archive provider, sort, dedupe, revalidate, retry once\n    } else {\n        return Err(e);\n    }\n}","preventionTips":["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."],"tags":["blockchain","validation","consistency","data-integrity"],"backgroundTag":"schema-validation-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}