Skip to content

Race Condition in UTXO Locking #41

@stringhandler

Description

@stringhandler

Description

The idempotency check and UTXO selection are not atomic operations. Two concurrent requests can:

  1. Both pass the idempotency check
  2. Both select the same UTXOs as "unspent"
  3. Both attempt to lock those UTXOs
  4. Result in double-spending or transaction failures

Vulnerable Code

// src/transactions/fund_locker.rs:144-156
pub async fn lock(
    &self,
    account_id: i64,
    amount: MicroMinotari,
    // ... other params
) -> Result<LockFundsResult, anyhow::Error> {
    // Step 1: Check idempotency (NOT in transaction)
    let mut conn = self.db_pool.acquire().await?;
    if let Some(idempotency_key_str) = &idempotency_key
        && let Some(response) = db::find_pending_transaction_locked_funds_by_idempotency_key(
            &mut conn, idempotency_key_str, account_id).await? {
        return Ok(response);
    }

    // ⚠️ GAP HERE - Another request can run!

    // Step 2: Select UTXOs (sees same unspent outputs)
    let input_selector = InputSelector::new(account_id, confirmation_window);
    let utxo_selection = input_selector
        .fetch_unspent_outputs(&mut conn, amount, num_outputs, fee_per_gram, estimated_output_size)
        .await?;

    // Step 3: Begin transaction (too late!)
    let mut transaction = self.db_pool.begin().await?;

    // Step 4: Create pending transaction
    let pending_tx_id = db::create_pending_transaction(
        &mut transaction,
        &idempotency_key,
        // ...
    ).await?;

    // Step 5: Lock outputs
    for utxo in &utxo_selection.utxos {
        db::lock_output(&mut transaction, utxo.id, &pending_tx_id, expires_at).await?;
    }

    transaction.commit().await?;
    Ok(result)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions