nautechsystems/nautilus_trader · error · anyhow::Error

Failed to commit signed transaction: {e}

Error message

Failed to commit signed transaction: {e}

What it means

The final COMMIT of the persistence transaction failed. All prior work (payload state checks, intent lock, signed row insert, status update, transition record) is rolled back, so the operation can be retried safely, but nothing was persisted. The library wraps the commit error with context.

Source

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

            "
            INSERT INTO execution_transaction_transition (
                intent_id, transaction_hash_id, transition_key, from_status, to_status
            ) VALUES ($1, $2, $3, $4, 'signed')
            ON CONFLICT (intent_id, transition_key) DO NOTHING
            ",
        )
        .bind(intent_id)
        .bind(row.id)
        .bind(format!("signed:{transaction_hash}"))
        .bind(current_status)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to record signed transaction transition: {e}"))?;

        transaction
            .commit()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to commit signed transaction: {e}"))?;
        Ok(row)
    }

    /// Records an idempotent transaction observation and advances its intent state.
    ///
    /// # Errors
    ///
    /// Returns an error if the transition is invalid, the hash is unknown, or persistence fails.
    #[expect(
        clippy::too_many_arguments,
        reason = "the parameters are the canonical receipt observation persisted atomically"
    )]
    pub async fn record_execution_status(
        &self,
        intent_id: i64,
        transaction_hash: &str,
        status: TransactionStatus,
        block_number: Option<u64>,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Retry the entire add_execution_transaction call — transactionality guarantees no partial state, and retries are idempotent (ON CONFLICT guards).
  2. Harden connectivity: keepalives, reasonable pool idle timeouts, and statement_timeout sized to the transaction's work.
  3. Check for failover events in the database logs around the failure time.
  4. Split heavy work out of the DB transaction where possible to shorten the commit window.
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: confirm a trivial transaction can commit before batch signing
let mut tx = pool.begin().await?;
tx.commit().await.context("database cannot commit; aborting signing batch")?;

Try / catch

let result = retry(3, backoff(250).jitter(), || async {
    db.add_execution_transaction(intent_id, chain_id, hash, sealed).await
}).await;
if let Err(e) = result { error!(%e, "commit failed after retries; verify no partial state — operation is transactional"); }

Prevention

When it happens

Trigger: transaction.commit() errored: connection dropped between the last statement and COMMIT (network blip, pool idle timeout, DB failover), server-side commit failure, or the transaction already aborted server-side due to a timeout.

Common situations: PostgreSQL restart/failover mid-transaction; load balancer terminating long-idle connections; very large sealed payloads extending transaction duration past network idle limits; infrastructure churn during deployments.

Related errors


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