nautechsystems/nautilus_trader · error · anyhow::Error

Failed to start signed transaction persistence: {e}

Error message

Failed to start signed transaction persistence: {e}

What it means

In `add_execution_transaction_payload`, after validating the payload representation and chain ID, a database transaction is started via `self.pool.begin()`. This error wraps a failure of that `pool.begin().await` call, meaning no persistence work for the signed transaction was attempted — the transaction never started.

Source

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

    }

    async fn add_execution_transaction_payload(
        &self,
        intent_id: i64,
        chain_id: u32,
        transaction_hash: &str,
        raw_transaction: Option<&[u8]>,
        sealed_transaction: Option<&[u8]>,
    ) -> anyhow::Result<ExecutionTransactionHashRow> {
        anyhow::ensure!(
            raw_transaction.is_some() != sealed_transaction.is_some(),
            "A signed transaction requires exactly one payload representation"
        );
        let chain_id_db = i32::try_from(chain_id)
            .with_context(|| format!("Chain ID {chain_id} exceeds PostgreSQL INTEGER"))?;
        let mut transaction =
            self.pool.begin().await.map_err(|e| {
                anyhow::anyhow!("Failed to start signed transaction persistence: {e}")
            })?;

        if let Some(envelope) = sealed_transaction {
            let state_row = sqlx::query(
                "SELECT deployment_id, protocol_version, operation, active_key_id \
                 FROM execution_payload_state WHERE component = 'signed_transactions' FOR SHARE",
            )
            .fetch_optional(&mut *transaction)
            .await
            .context("failed to lock execution payload state for protected persistence")?
            .ok_or_else(|| anyhow::anyhow!("Execution payload protection is not active"))?;
            let state = execution_payload_state_from_row(&state_row)?;
            anyhow::ensure!(
                state.protocol_version == EXECUTION_PAYLOAD_PROTOCOL_VERSION
                    && state.operation == "ready",
                "Execution payload storage is not ready for protected persistence"
            );
            anyhow::ensure!(

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify database reachability and credentials (DATABASE_URL) and that Postgres is accepting connections
  2. Increase sqlx pool capacity/tune `acquire_timeout` if the pool is exhausted under broadcast load
  3. Retry the call — starting a transaction is safe to retry since nothing was persisted
  4. Check pool metrics/logs for connection acquisition timeouts and set max_lifetime below any intermediary TCP idle timeout

Example fix

// before
let mut transaction = self.pool.begin().await.map_err(|e| {
    anyhow::anyhow!("Failed to start signed transaction persistence: {e}")
})?;
// after
let mut transaction = self.pool.begin().await.with_context(|| {
    format!("Failed to start signed transaction persistence for intent {intent_id}: pool status: {}", self.pool.size())
})?;
Defensive patterns

Strategy: retry

Validate before calling

// Verify pool connectivity before persisting
sqlx::query("SELECT 1").execute(&db.pool).await
    .context("database pool unavailable for signed transaction persistence")?;

Try / catch

let mut transaction = loop {
    match db.pool.begin().await {
        Ok(tx) => break tx,
        Err(e) if attempts < 3 => { attempts += 1; tokio::time::sleep(backoff).await; }
        Err(e) => return Err(anyhow::anyhow!("Failed to start signed transaction persistence: {e}")),
    }
};

Prevention

When it happens

Trigger: `self.pool.begin()` fails: the connection pool is exhausted (all connections checked out), the pool cannot acquire a healthy connection within `acquire_timeout`, the database is unreachable, credentials were rejected at connect time, or the database is starting up/shutting down.

Common situations: Postgres down or restarting when a signed transaction is persisted before broadcast; pool max_connections too small for concurrent execution workers; network partition or firewall dropping pooled idle connections; wrong DATABASE_URL after a config change.

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/64103cd1a6f4cdb6. Report an issue: GitHub.