nautechsystems/nautilus_trader · error · anyhow::Error

Verified finality requires decision evidence

Error message

Verified finality requires decision evidence

What it means

This error is thrown by record_execution_finality_verified in BlockchainCacheDatabase when a caller attempts to persist a verified finality transition whose `decisions` slice is empty. The database layer refuses to record finality without at least one verification-decision record, because decision evidence is the audit trail proving how the transaction reached Finalized/Reverted status. It is a fail-fast precondition check before any SQL runs, so nothing is written to the database when it fires.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:6902

    /// Records verified final consumption and advances the canonical nonce in one transaction.
    ///
    /// # Errors
    ///
    /// 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 =

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Populate `finality.decisions` with the verification decision records produced by the finality verifier before calling the persistence API
  2. Check upstream decision collection: ensure the verifier's output is not being filtered to empty (e.g. an over-strict predicate or a failed fetch of vote/decision data)
  3. If a transaction genuinely has no decision evidence, route it through the non-verified finality recording path instead of record_execution_finality_verified

Example fix

// before
let finality = ExecutionFinalityTransition { status, decisions: &[], .. };
db.record_execution_finality_verified(&finality).await?;
// after
let finality = ExecutionFinalityTransition { status, decisions: &decisions[..], .. };
assert!(!decisions.is_empty(), "finality must carry decision evidence");
db.record_execution_finality_verified(&finality).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: check before calling the persistence API
if finality.decisions.is_empty() {
    return Err(anyhow!("refusing to persist finality without decision evidence"));
}
db.record_execution_finality_verified(&finality).await?;

Type guard

fn has_decision_evidence(finality: &ExecutionFinalityTransition<'_>) -> bool {
    !finality.decisions.is_empty()
}

Try / catch

match db.record_execution_finality_verified(&finality).await {
    Err(e) if e.to_string().contains("decision evidence") => {
        // rebuild decisions from the verifier, then retry once
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling record_execution_finality_verified (or the higher-level execution pipeline that routes into it) with an ExecutionFinalityTransition whose `decisions: &[...]` field is an empty slice, while status is Finalized or Reverted.

Common situations: Upstream verification logic short-circuits and skips recording decisions when no committee/vote records were fetched; a refactoring changed decision collection to a filtered iterator that yields nothing; a hand-constructed ExecutionFinalityTransition in tests or tooling forgot to populate decisions.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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