nautechsystems/nautilus_trader · error · anyhow::Error

Failed to start verified finality transition: {e}

Error message

Failed to start verified finality transition: {e}

What it means

Thrown when `self.pool.begin()` fails, i.e. the database pool could not open a new SQL transaction to record a verified finality transition. The original sqlx error is wrapped with this context message so callers can tell which stage of finality persistence failed. No ledger rows are modified because the transaction never started.

Source

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

                    .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 =
            i64::try_from(finality.nonce).context("Execution nonce exceeds PostgreSQL BIGINT")?;
        let next_nonce = nonce
            .checked_add(1)
            .ok_or_else(|| anyhow::anyhow!("Canonical nonce overflow"))?;
        let block_number = i64::try_from(finality.block_number)
            .context("Finality block exceeds PostgreSQL BIGINT")?;
        let gas_used = i64::try_from(finality.gas_used)
            .context("Finality gas used exceeds PostgreSQL BIGINT")?;
        let mut transaction =
            self.pool.begin().await.map_err(|e| {
                anyhow::anyhow!("Failed to start verified finality transition: {e}")
            })?;
        let (manifest_version, manifest_digest, stored_nonce, revision) =
            sqlx::query_as::<_, (String, String, i64, i64)>(
                "
                SELECT manifest_version, manifest_digest, next_canonical_nonce, revision
                FROM execution_verification_nonce
                WHERE chain_id = $1 AND wallet_address = $2
                FOR UPDATE
                ",
            )
            .bind(chain_id)
            .bind(finality.wallet_address)
            .fetch_optional(&mut *transaction)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to lock finality nonce ledger: {e}"))?
            .ok_or_else(|| anyhow::anyhow!("Canonical nonce ledger is not initialized"))?;
        anyhow::ensure!(
            manifest_version == finality.manifest_version

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check database connectivity and pool health (DB reachable, credentials valid, listener up)
  2. Increase pool max_connections or acquire_timeout in sqlx pool config if the pool is exhausted under load
  3. Retry the finality recording with backoff — it is transactional and safe to re-attempt once connectivity is restored
  4. Inspect the wrapped sqlx error (`{e}` in the message) for the root cause (auth, TLS, timeout, connection refused)

Example fix

// before
// pool configured with defaults, starves under concurrent finality writes
PgPoolOptions::new().max_connections(5).connect(url).await
// after
PgPoolOptions::new()
    .max_connections(20)
    .acquire_timeout(Duration::from_secs(10))
    .connect(url)
    .await
Defensive patterns

Strategy: retry

Validate before calling

// Rust: probe pool health before high-stakes finality writes
let healthy = sqlx::query("SELECT 1").execute(&db.pool).await.is_ok();
ensure!(healthy, "database pool unavailable; defer finality recording");

Try / catch

// Retry transactional start with bounded backoff
for attempt in 0..3 {
    match db.record_execution_finality_verified(&finality).await {
        Err(e) if e.to_string().contains("Failed to start verified finality transition")
            && attempt < 2 => tokio::time::sleep(backoff(attempt)).await,
        other => { other?; break }
    }
}

Prevention

When it happens

Trigger: Calling record_execution_finality_verified (directly or via the execution pipeline) while the PostgreSQL pool is unavailable: pool exhausted (all connections checked out), database down/restarting, network partition to the DB, TLS/auth failure at connection time, or pool acquisition timeout.

Common situations: Under heavy load the pool's max_connections is exhausted by concurrent finality recordings; a failover or maintenance window dropped DB connectivity; connection lifetime settings closed idle connections while traffic resumed; misconfigured DATABASE_URL credentials after a rotation.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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