nautechsystems/nautilus_trader · error · anyhow::Error
Failed to persist finality verification: {e}
Error message
Failed to persist finality verification: {e} What it means
Wraps a sqlx failure while INSERTing/UPSERTing a per-decision finality verification row (with failure_domain_ids, normalized_value_digest, revision, transition_key) into its verification table. The whole finality transaction rolls back, so nothing (headers, receipt, intent status) is committed. The root database error is embedded via `{e}`.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:7108
)
",
)
.bind(finality.intent_id)
.bind(nonce)
.bind(decision.read_class)
.bind(height_start)
.bind(height_end)
.bind(finality.manifest_version)
.bind(finality.manifest_digest)
.bind(finality.provider_ids)
.bind(finality.operator_ids)
.bind(finality.failure_domain_ids)
.bind(&decision.normalized_value_digest)
.bind(revision)
.bind(transition_key)
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to persist finality verification: {e}"))?;
}
let hash_result = sqlx::query(
"
UPDATE execution_transaction_hash
SET status = $3, block_number = $4, block_hash = $5,
receipt_success = $6, gas_used = $7, effective_gas_price = $8,
updated_at = NOW()
WHERE intent_id = $1 AND transaction_hash = $2
",
)
.bind(finality.intent_id)
.bind(finality.transaction_hash)
.bind(finality.status.as_str())
.bind(block_number)
.bind(finality.block_hash)
.bind(finality.receipt_success)
.bind(gas_used)View on GitHub (pinned to 18893faf8b)
Solutions
- Read the embedded `{e}` for the exact Postgres error (constraint, type, connection)
- If it is a duplicate transition_key, the verification was already recorded — make the insert idempotent (ON CONFLICT DO NOTHING) or skip
- Run pending migrations to align the verification table schema
- Check bind parameter types/sizes (digest, revision, failure_domain_ids array) against column definitions
Example fix
// before
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to persist finality verification: {e}"))?;
// after
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to persist finality verification (intent={}, decision={index}): {e}", finality.intent_id))?; Defensive patterns
Strategy: validation
Validate before calling
// Check whether this transition_key was already persisted
let seen = sqlx::query_scalar::<_, i64>(
"SELECT COUNT(*) FROM execution_finality_verification WHERE transition_key = $1"
)
.bind(&transition_key)
.fetch_one(&mut *conn).await?;
if seen > 0 { /* already recorded — skip */ } Try / catch
match persist_finality(...).await {
Err(e) if e.to_string().contains("duplicate key") => Ok(()), // idempotent replay
Err(e) if is_transient_db_error(&e) => retry_with_backoff(3, || persist_finality(...)),
Err(e) => Err(e),
Ok(v) => Ok(v),
} Prevention
- Make verification inserts idempotent with ON CONFLICT DO NOTHING on transition_key
- Keep decision digests and revision types aligned with column definitions across migrations
- Batch decisions within reasonable size limits
- Test finality replay paths in staging to catch constraint conflicts
When it happens
Trigger: The per-decision INSERT fails: unique conflict on transition_key for a replayed finality event, schema drift (missing columns), oversized failure_domain_ids array, connection loss, or bind type mismatch on the digest/revision fields.
Common situations: Re-applying the same transition_key after a partial retry; migrations out of sync between environments; Postgres array/bytea type changes after an upgrade; large decision batches exceeding statement or packet limits.
Related errors
- Failed to assign nonce {nonce} to execution intent: {e}
- Failed to commit recoverable transition: {e}
- Failed to start signed transaction persistence: {e}
- Failed to record verified finality receipt: {e}
- Failed to record verified finality intent: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/f9512a8cebd36dcc.
Report an issue: GitHub.