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
- Populate `finality.decisions` with the verification decision records produced by the finality verifier before calling the persistence API
- 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)
- 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
- Always construct ExecutionFinalityTransition from the verifier's full decision output, never a filtered subset by default
- Add a debug_assert!(!decisions.is_empty()) at the construction site
- Route finality transitions without evidence to a dedicated quarantine/audit path instead of the verified recorder
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
- Cannot persist bar with composite bar type {}: the bar table
- AX maintenance_margin_pct {maintenance_margin_pct} exceeds i
- AX final-settlement fill must also be classified as a block
- command client order ID {} does not match cached order {}
- Cannot persist account with no events: {}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/e6a831075bdfa51e.
Report an issue: GitHub.