nautechsystems/nautilus_trader · error

Failed to assign verified execution nonce: {e}

Error message

Failed to assign verified execution nonce: {e}

What it means

The final `UPDATE execution_intent SET nonce = $2 ... WHERE id = $1 AND status = 'prepared' AND active AND (nonce IS NULL OR nonce = $2)` matched zero rows (or the execute itself failed). If `.execute` errors, this message wraps the SQLx error; the code also throws it via the following `rows_affected() == 1` ensure when the WHERE predicate filtered the row out. The library throws it because the nonce write must land on a prepared, active intent that owns no conflicting nonce.

Source

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

            .await
            .map_err(|e| anyhow::anyhow!("Failed to persist pre-sign verification: {e}"))?;
        }

        let result = sqlx::query(
            "
            UPDATE execution_intent
            SET nonce = $2, updated_at = NOW()
            WHERE id = $1
              AND status = 'prepared'
              AND active
              AND (nonce IS NULL OR nonce = $2)
            ",
        )
        .bind(assignment.intent_id)
        .bind(nonce)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to assign verified execution nonce: {e}"))?;
        anyhow::ensure!(
            result.rows_affected() == 1,
            "Execution intent {} is not prepared for canonical nonce {}",
            assignment.intent_id,
            assignment.nonce
        );
        transaction
            .commit()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to commit verified nonce assignment: {e}"))?;
        Ok(())
    }

    /// Appends one verified decision batch before an action on an existing active intent.
    pub(crate) async fn record_execution_verification_batch(
        &self,
        batch: &ExecutionVerificationBatch<'_>,
    ) -> anyhow::Result<()> {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Treat it as a lost race: re-read the intent's current status/nonce, and if the nonce was already assigned to the same value, treat the operation as idempotent success; otherwise restart the assignment flow.
  2. If the SQLx error is a permissions error (42501), grant UPDATE on execution_intent to the application role.
  3. Keep all mutation on the intent inside this transaction (hold the FOR UPDATE lock) so status cannot change mid-assignment; avoid external writers bypassing the API.
  4. Inspect the inner `{e}` when present for the concrete driver-level cause (connection, permission, trigger).

Example fix

// before: treating every failure as fatal
match db.assign_execution_intent_nonce_verified(&assignment).await {
    Err(e) => return Err(e),
    Ok(()) => {}
}
// after: idempotent handling of concurrent assignment
match db.assign_execution_intent_nonce_verified(&assignment).await {
    Ok(()) => {}
    Err(e) if intent_already_has_nonce(&db, assignment.intent_id, nonce).await => { /* already assigned */ }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: try-catch

Validate before calling

let (status, active, nonce): (String, bool, Option<i64>) = sqlx::query_as(
    "SELECT status, active, nonce FROM execution_intent WHERE id = $1")
    .bind(&assignment.intent_id).fetch_optional(pool).await?
    .ok_or_else(|| anyhow!("intent missing"))?;
anyhow::ensure!(status == "prepared" && active && (nonce.is_none() || nonce == Some(assignment.nonce)), "intent not assignable");

Try / catch

match db.assign_execution_intent_nonce_verified(&assignment).await {
    Err(e) if e.to_string().contains("Failed to assign verified execution nonce")
        || e.to_string().contains("is not prepared for canonical nonce") => {
        // lost race or lost update: re-read intent and either treat as idempotent success or restart the flow
    }
    other => other?,
}

Prevention

When it happens

Trigger: The intent's status changed from "prepared" between the earlier SELECT and this UPDATE (another transaction assigned/executed/cancelled it); the intent was deactivated; the intent already holds a different nonce; or the UPDATE fails outright (connection loss, permission denied on UPDATE, trigger abort).

Common situations: A concurrent nonce-assignment or cancellation racing the FOR UPDATE window (e.g. from a different connection outside the lock, or trigger-side effects); admin manually flipping status/active in the DB; running with a role lacking UPDATE privilege on execution_intent; long transactions causing the DBA to kill the session.

Related errors


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