# Implement Saga Pattern for Swap Operations with Recovery Mechanism

## Overview

This PR refactors the swap operation implementation to use the saga pattern - a distributed transaction pattern that provides reliable transaction management through explicit state tracking and compensation-based error handling. The implementation includes a robust recovery mechanism that automatically handles swap operations interrupted by crashes, power loss, or network failures.

## What Changed

**Saga Pattern Implementation:**
- Introduced a strict linear state machine for swaps: `Initial` → `SetupComplete` → `Signed` → `Completed`
- New modular `swap_saga` module with state validation, compensation logic, and saga orchestration
- Automatic rollback of database changes on failure, ensuring atomic swap operations
- Replaced previous swap implementation (`swap.rs`, `blinded_message_writer.rs`) with saga-based approach

**Recovery Mechanism:**
- Added `operation_id` and `operation_kind` columns to database schema for tracking which operation proofs belong to
- New `recover_from_bad_swaps()` method that runs on mint startup to handle incomplete swaps
- For proofs left in `PENDING` state from swap operations:
  - If blind signatures exist: marks proofs as `SPENT` (swap completed but not finalized)
  - If no blind signatures exist: removes proofs from database (swap failed partway through)
- Database migrations included for both PostgreSQL and SQLite
This commit is contained in:
tsk
2025-10-22 08:30:33 -05:00
committed by GitHub
parent db2764c566
commit 33c206a310
28 changed files with 4550 additions and 361 deletions

View File

@@ -2,7 +2,7 @@
use std::collections::HashMap;
use cdk_common::database::{self, MintDatabase, MintKeysDatabase};
use cdk_common::mint::{self, MintKeySetInfo, MintQuote};
use cdk_common::mint::{self, MintKeySetInfo, MintQuote, Operation};
use cdk_common::nuts::{CurrencyUnit, Id, Proofs};
use cdk_common::MintInfo;
@@ -56,8 +56,10 @@ pub async fn new_with_state(
tx.add_melt_quote(quote).await?;
}
tx.add_proofs(pending_proofs, None).await?;
tx.add_proofs(spent_proofs, None).await?;
tx.add_proofs(pending_proofs, None, &Operation::new_swap())
.await?;
tx.add_proofs(spent_proofs, None, &Operation::new_swap())
.await?;
let mint_info_bytes = serde_json::to_vec(&mint_info)?;
tx.kv_write(
CDK_MINT_PRIMARY_NAMESPACE,