nautechsystems/nautilus_trader · critical · anyhow::Error

Execution transaction {transaction_hash} conflicts with its

Error message

Execution transaction {transaction_hash} conflicts with its persisted record

What it means

The execution_transaction insert uses ON CONFLICT (chain_id, transaction_hash) DO UPDATE ... WHERE every existing column (wallet_address, nonce, purpose, status, client_order_id) equals the incoming values, making exact re-insertion idempotent. rows_affected() == 0 (enforced at crates/adapters/blockchain/src/cache/database.rs:3027) means a row for that (chain_id, transaction_hash) already exists with different metadata - hash reuse or record mismatch - which the signer refuses rather than overwrite.

Source

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

            WHERE execution_transaction.wallet_address = EXCLUDED.wallet_address
              AND execution_transaction.nonce = EXCLUDED.nonce
              AND execution_transaction.purpose = EXCLUDED.purpose
              AND execution_transaction.status = EXCLUDED.status
              AND execution_transaction.client_order_id IS NOT DISTINCT FROM EXCLUDED.client_order_id
        ",
        )
        .bind(chain_id as i32)
        .bind(wallet_address)
        .bind(nonce as i64)
        .bind(transaction_hash)
        .bind(purpose)
        .bind(status)
        .bind(client_order_id)
        .execute(&self.pool)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to insert into execution_transaction table: {e}"))?;

        anyhow::ensure!(
            result.rows_affected() == 1,
            "Execution transaction {transaction_hash} conflicts with its persisted record"
        );
        Ok(())
    }

    /// Installs execution schema version 2 without changing existing transaction rows.
    ///
    /// The migration locks the legacy transaction table, refuses unresolved version 1 rows,
    /// installs the versioned intent and hash-history tables, and fences older writers before
    /// releasing the lock. This prevents a mixed-version process from bypassing the new signer
    /// ownership constraints.
    ///
    /// # Errors
    ///
    /// Returns an error if the database operation fails.
    pub async fn ensure_execution_transaction_schema(&self) -> anyhow::Result<()> {
        let mut transaction = self

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Query the persisted row: SELECT * FROM execution_transaction WHERE chain_id = $1 AND transaction_hash = $2; and diff each column against the incoming values to find the mismatched field.
  2. If the status legitimately advanced, apply the status change through the update path rather than re-inserting new metadata.
  3. Ensure one writer path per wallet and never reuse a transaction hash across orders or purposes.
  4. If the row is provably wrong, repair it deliberately with an audited migration/fixup instead of forcing the insert.
Defensive patterns

Strategy: validation

Validate before calling

-- Before recording, check the persisted row for this hash
SELECT wallet_address, nonce, purpose, status, client_order_id
FROM execution_transaction
WHERE chain_id = $1 AND transaction_hash = $2;
-- If a row exists, every column must match the values you are about to record

Try / catch

if let Err(e) = db.add_execution_transaction(...).await {
    if e.to_string().contains("conflicts with its persisted record") {
        // data integrity violation: halt, investigate the divergent row, never force-overwrite
        log::error!("tx hash reuse or metadata mismatch detected: {e}");
        return Err(e);
    }
    return Err(e);
}

Prevention

When it happens

Trigger: Re-recording an existing transaction hash with any changed field: a different client_order_id or purpose for the same hash, a status transition submitted as a re-insert instead of an update, stale queued records replayed after a wallet/nonce change, or a genuine hash collision across wallets.

Common situations: An upstream bug assigning one tx hash to two operations; restoring from backup and replaying recorded transactions with updated statuses; multiple signer processes sharing the database with mismatched wallet configurations; manual database edits.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/a81106ec9a7a8ef5. Report an issue: GitHub.