rabia-banking-example 0.4.1

Banking ledger state machine implementation example using the Rabia SMR protocol
Documentation
# Banking SMR Example

This example demonstrates how to build a sophisticated financial ledger using State Machine Replication (SMR) with the Rabia consensus protocol. It showcases complex business logic, validation, and transaction management in a distributed system.

## What This Example Shows

The Banking SMR demonstrates advanced SMR concepts for real-world applications:

1. **Complex Business Logic**: Account management with validation and business rules
2. **Multi-Entity State**: Managing accounts, transactions, and relationships
3. **Atomic Operations**: Transfer operations that must succeed or fail completely
4. **Validation and Error Handling**: Comprehensive input validation and business rule enforcement
5. **Audit Trail**: Complete transaction history for compliance and debugging
6. **Consistent State**: Financial invariants maintained across all replicas

## State Machine Implementation

The banking system implements these operations:

### Account Management
- `CreateAccount { account_id, initial_balance }` - Create new account with validation
- `GetAccount { account_id }` - Get complete account information
- `ListAccounts` - Get all accounts (admin operation)

### Financial Operations  
- `Deposit { account_id, amount }` - Add funds to an account
- `Withdraw { account_id, amount }` - Remove funds with balance checking
- `Transfer { from_account, to_account, amount }` - Atomic transfer between accounts
- `GetBalance { account_id }` - Get current account balance

### Reporting and Audit
- `GetTransactionHistory { account_id, limit }` - Get transaction history with filtering

## Key SMR Features Demonstrated

### Complex State Management
```rust
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BankingState {
    /// All accounts indexed by account ID
    pub accounts: HashMap<String, Account>,
    /// Complete transaction history for audit trail
    pub transactions: Vec<Transaction>,
    /// Operation counter for metrics
    pub operation_count: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Account {
    pub account_id: String,
    pub balance: i64,  // Using cents to avoid floating point issues
    pub created_at: u64,
    pub last_transaction_at: u64,
    pub transaction_count: u64,
}
```

### Business Logic Validation
```rust
async fn apply_command(&mut self, command: Self::Command) -> Self::Response {
    self.state.operation_count += 1;
    
    match command {
        BankingCommand::Transfer { from_account, to_account, amount } => {
            // Validate transfer amount
            if let Err(e) = Self::validate_amount(amount) {
                return BankingResponse::error(e);
            }
            
            // Prevent self-transfers
            if from_account == to_account {
                return BankingResponse::error("Cannot transfer to same account".to_string());
            }
            
            // Verify both accounts exist
            let from_balance = match self.state.accounts.get(&from_account) {
                Some(account) => account.balance,
                None => return BankingResponse::error("Source account not found".to_string()),
            };
            
            if !self.state.accounts.contains_key(&to_account) {
                return BankingResponse::error("Destination account not found".to_string());
            }
            
            // Check sufficient funds
            if from_balance < amount {
                return BankingResponse::error("Insufficient funds".to_string());
            }
            
            // Atomically update both accounts
            self.state.accounts.get_mut(&from_account).unwrap()
                .update_balance(from_balance - amount);
            self.state.accounts.get_mut(&to_account).unwrap()
                .update_balance(self.state.accounts[&to_account].balance + amount);
            
            // Record transaction for audit trail
            let transaction = Transaction {
                transaction_id: Self::generate_transaction_id(),
                from_account: Some(from_account),
                to_account: Some(to_account),
                amount,
                timestamp: current_timestamp(),
                transaction_type: TransactionType::Transfer,
            };
            self.state.transactions.push(transaction);
            
            BankingResponse::success_with_transaction(None, transaction_id)
        }
        // ... other operations
    }
}
```

### Audit Trail and Compliance
```rust
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Transaction {
    pub transaction_id: String,       // Unique identifier  
    pub from_account: Option<String>, // None for deposits
    pub to_account: Option<String>,   // None for withdrawals
    pub amount: i64,                  // Amount in cents
    pub timestamp: u64,               // UTC timestamp
    pub transaction_type: TransactionType,
}

// Every financial operation creates an immutable audit record
BankingCommand::GetTransactionHistory { account_id, limit } => {
    let mut transactions: Vec<Transaction> = if let Some(account_id) = account_id {
        // Filter transactions for specific account
        self.state.transactions.iter()
            .filter(|tx| tx.involves_account(&account_id))
            .cloned().collect()
    } else {
        // Return all transactions
        self.state.transactions.clone()
    };
    
    // Sort by timestamp (newest first) for consistent ordering
    transactions.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
    
    // Apply limit for pagination
    if let Some(limit) = limit {
        transactions.truncate(limit);
    }
    
    BankingResponse::success(Some(BankingData::Transactions(transactions)))
}
```

### Financial Invariants
The banking SMR maintains critical financial invariants:

1. **Conservation of Money**: Total balance across all accounts never changes unless money is deposited or withdrawn
2. **Non-negative Balances**: Accounts cannot have negative balances (no overdrafts)
3. **Atomic Transfers**: Transfers either complete fully or fail completely
4. **Audit Trail**: Every balance change is recorded in the transaction history

## Running the Example

```bash
# Run the banking SMR example
cargo run --bin banking_smr_example

# Run with multiple replicas to see consensus
cargo run --bin banking_smr_cluster  

# Run comprehensive tests
cargo test -p banking_smr

# Run stress tests with concurrent operations
cargo test -p banking_smr -- --test-threads=1 test_concurrent_operations
```

## Use Cases

This pattern is ideal for:

- **Financial Systems**: Banks, payment processors, digital wallets
- **Trading Platforms**: Order books, settlement systems, clearing houses  
- **Accounting Systems**: General ledgers, bookkeeping, financial reporting
- **Resource Management**: Credits, quotas, resource allocation
- **Gaming Economies**: Virtual currencies, item trading, player balances
- **Supply Chain**: Inventory tracking, asset transfers, ownership records

## Advanced Features

### Concurrent Operations with Consistency
```rust
// All replicas process these operations in the same order
let batch_operations = vec![
    BankingCommand::Transfer { 
        from_account: "alice".to_string(), 
        to_account: "bob".to_string(), 
        amount: 100 
    },
    BankingCommand::Transfer { 
        from_account: "bob".to_string(), 
        to_account: "charlie".to_string(), 
        amount: 50 
    },
    BankingCommand::Deposit { 
        account_id: "alice".to_string(), 
        amount: 200 
    },
];

// All operations applied atomically in order across all replicas
let results = banking_smr.apply_commands(batch_operations).await;
```

### Financial Reporting and Analytics
```rust
// The banking state provides rich analytics
impl BankingSMR {
    pub fn total_value(&self) -> i64 {
        self.state.accounts.values().map(|a| a.balance).sum()
    }
    
    pub fn account_count(&self) -> usize {
        self.state.accounts.len()
    }
    
    pub fn transaction_volume(&self) -> i64 {
        self.state.transactions.iter().map(|t| t.amount).sum()
    }
    
    pub fn most_active_accounts(&self) -> Vec<(String, u64)> {
        let mut accounts: Vec<_> = self.state.accounts.iter()
            .map(|(id, account)| (id.clone(), account.transaction_count))
            .collect();
        accounts.sort_by(|a, b| b.1.cmp(&a.1));
        accounts
    }
}
```

### Compliance and Monitoring
```rust
// Transaction monitoring for compliance
impl Transaction {
    pub fn is_large_transaction(&self) -> bool {
        self.amount > 1_000_000 // $10,000+ requires reporting
    }
    
    pub fn involves_account(&self, account_id: &str) -> bool {
        self.from_account.as_ref() == Some(account_id) || 
        self.to_account.as_ref() == Some(account_id)
    }
    
    pub fn transaction_age_days(&self) -> u64 {
        let now = current_timestamp();
        (now - self.timestamp) / (24 * 60 * 60 * 1000)
    }
}
```

## Performance Considerations

### Memory Management
- **Account Storage**: Efficient HashMap storage for fast account lookups
- **Transaction History**: Consider archiving old transactions for large systems
- **State Snapshots**: Compressed serialization for efficient persistence

### Throughput Optimization  
- **Batch Processing**: Group multiple operations for higher throughput
- **Read Replicas**: Balance queries can be served from any replica
- **Operation Ordering**: Independent operations can be processed concurrently

### Consistency Guarantees
- **Strong Consistency**: All replicas see the same account balances
- **Linearizable Operations**: Operations appear to execute atomically
- **Durable Transactions**: All committed transactions survive node failures

## Security Considerations

### Input Validation
```rust
fn validate_amount(amount: i64) -> Result<(), String> {
    if amount <= 0 {
        return Err("Amount must be positive".to_string());
    }
    if amount > 1_000_000_000 { // $10M limit
        return Err("Amount exceeds maximum limit".to_string());
    }
    Ok(())
}

fn validate_account_id(account_id: &str) -> Result<(), String> {
    if account_id.is_empty() {
        return Err("Account ID cannot be empty".to_string());
    }
    if account_id.len() > 50 {
        return Err("Account ID too long".to_string());
    }
    if !account_id.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
        return Err("Invalid characters in account ID".to_string());
    }
    Ok(())
}
```

### Audit Requirements
- **Immutable History**: Transaction records are never modified or deleted
- **Complete Trail**: Every balance change is recorded with timestamps
- **Deterministic IDs**: Transaction IDs are generated deterministically for consistency

## Implementation Notes

### Why This Works Well for SMR

1. **Clear State Model**: Accounts and transactions have well-defined relationships
2. **Deterministic Operations**: All operations produce predictable results
3. **Strong Invariants**: Financial rules are enforced consistently
4. **Atomic Operations**: Complex operations (transfers) succeed or fail completely
5. **Rich Audit Trail**: Complete history enables debugging and compliance

### SMR Considerations

1. **State Size**: Large transaction histories require careful management
2. **Snapshot Strategy**: Balance between recovery time and storage overhead
3. **Validation Logic**: All business rules must be deterministic across replicas
4. **Error Handling**: Failed operations must not corrupt the state machine

## Testing Strategy

### Unit Tests
- Individual operation correctness
- Edge case handling (insufficient funds, invalid accounts)
- State serialization/deserialization

### Integration Tests  
- Multi-operation scenarios
- Concurrent transfer testing
- State consistency verification

### Stress Tests
- High-volume transaction processing
- Memory usage under load
- Performance benchmarking

## Next Steps

After understanding the banking example, explore:
- [SMR Developer Guide]../../docs/SMR_GUIDE.md - Comprehensive SMR development guide
- [Performance Benchmarks]../../benchmarks/ - Banking SMR performance optimization
- [Custom State Machine]../custom_state_machine.rs - Template for your own SMR applications

This banking example demonstrates how SMR can provide the strong consistency and fault tolerance required for financial applications while maintaining high performance and scalability.