| Crates.io | rabia-banking-example |
| lib.rs | rabia-banking-example |
| version | 0.4.1 |
| created_at | 2025-08-22 14:18:24.832124+00 |
| updated_at | 2025-08-22 14:18:24.832124+00 |
| description | Banking ledger state machine implementation example using the Rabia SMR protocol |
| homepage | https://github.com/rabia-rs/rabia |
| repository | https://github.com/rabia-rs/rabia |
| max_upload_size | |
| id | 1806404 |
| size | 57,439 |
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.
The Banking SMR demonstrates advanced SMR concepts for real-world applications:
The banking system implements these operations:
CreateAccount { account_id, initial_balance } - Create new account with validationGetAccount { account_id } - Get complete account informationListAccounts - Get all accounts (admin operation)Deposit { account_id, amount } - Add funds to an accountWithdraw { account_id, amount } - Remove funds with balance checkingTransfer { from_account, to_account, amount } - Atomic transfer between accountsGetBalance { account_id } - Get current account balanceGetTransactionHistory { account_id, limit } - Get transaction history with filtering#[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,
}
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
}
}
#[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)))
}
The banking SMR maintains critical financial invariants:
# 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
This pattern is ideal for:
// 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;
// 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
}
}
// 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)
}
}
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(())
}
After understanding the banking example, explore:
This banking example demonstrates how SMR can provide the strong consistency and fault tolerance required for financial applications while maintaining high performance and scalability.