/ abzu-token / src / sled_ledger.rs
sled_ledger.rs
  1  use crate::balance::Balance;
  2  use crate::error::TokenError;
  3  use crate::ledger::{Address, Ledger};
  4  use sled::{Db, Transactional};
  5  
  6  /// Persistent ledger implementation using Sled
  7  pub struct SledLedger {
  8      #[allow(dead_code)]
  9      db: Db,
 10      balances: sled::Tree,
 11      locked: sled::Tree,
 12  }
 13  
 14  impl SledLedger {
 15      /// Open or create a SledLedger at the specified path
 16      pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self, TokenError> {
 17          let db = sled::open(path).map_err(|e| TokenError::Storage(e.to_string()))?;
 18          let balances = db.open_tree("balances").map_err(|e| TokenError::Storage(e.to_string()))?;
 19          let locked = db.open_tree("locked").map_err(|e| TokenError::Storage(e.to_string()))?;
 20          
 21          Ok(Self {
 22              db,
 23              balances,
 24              locked,
 25          })
 26      }
 27      
 28      /// Helper to get balance from a tree
 29      fn get_balance_from_tree(&self, tree: &sled::Tree, address: &Address) -> Result<Balance, TokenError> {
 30          match tree.get(address.as_bytes()).map_err(|e| TokenError::Storage(e.to_string()))? {
 31              Some(bytes) => serde_json::from_slice(&bytes)
 32                  .map_err(|e| TokenError::Storage(format!("Serialization error: {}", e))),
 33              None => Ok(Balance::ZERO),
 34          }
 35      }
 36      
 37      /// Helper to set balance in a tree
 38      fn set_balance_in_tree(&self, tree: &sled::Tree, address: &Address, balance: Balance) -> Result<(), TokenError> {
 39          if balance.is_zero() {
 40              tree.remove(address.as_bytes()).map_err(|e| TokenError::Storage(e.to_string()))?;
 41          } else {
 42              let bytes = serde_json::to_vec(&balance)
 43                  .map_err(|e| TokenError::Storage(format!("Serialization error: {}", e)))?;
 44              tree.insert(address.as_bytes(), bytes).map_err(|e| TokenError::Storage(e.to_string()))?;
 45          }
 46          Ok(())
 47      }
 48  }
 49  
 50  impl Ledger for SledLedger {
 51      fn balance(&self, address: &Address) -> Balance {
 52          self.get_balance_from_tree(&self.balances, address).unwrap_or(Balance::ZERO)
 53      }
 54  
 55      fn transfer(
 56          &self,
 57          from: &Address,
 58          to: &Address,
 59          amount: Balance,
 60      ) -> Result<(), TokenError> {
 61          if amount.is_zero() {
 62              return Ok(());
 63          }
 64  
 65          let from_bytes = from.as_bytes();
 66          let to_bytes = to.as_bytes();
 67  
 68          // Transactional update to ensure atomicity
 69          let tx_result = (&self.balances, &self.locked).transaction(|(tx_balances, tx_locked)| {
 70              // Read From Balance
 71              let from_bal_bytes = tx_balances.get(from_bytes)?.unwrap_or_default();
 72              let from_balance: Balance = if from_bal_bytes.is_empty() {
 73                  Balance::ZERO
 74              } else {
 75                  serde_json::from_slice(&from_bal_bytes).map_err(|_| sled::transaction::ConflictableTransactionError::Abort(TokenError::Storage("Serde error".into())))?
 76              };
 77  
 78              // Read Locked Balance
 79              let locked_val_bytes = tx_locked.get(from_bytes)?.unwrap_or_default();
 80              let locked_val: Balance = if locked_val_bytes.is_empty() {
 81                  Balance::ZERO
 82              } else {
 83                   serde_json::from_slice(&locked_val_bytes).map_err(|_| sled::transaction::ConflictableTransactionError::Abort(TokenError::Storage("Serde error".into())))?
 84              };
 85              
 86              // Check available
 87              let available = from_balance.saturating_sub(locked_val);
 88              if available.drops() < amount.drops() {
 89                  return Err(sled::transaction::ConflictableTransactionError::Abort(TokenError::InsufficientBalance {
 90                      have: available.drops(),
 91                      need: amount.drops(),
 92                  }));
 93              }
 94              
 95              // Debit From
 96              let new_from = from_balance.checked_sub(amount).map_err(|e| sled::transaction::ConflictableTransactionError::Abort(e))?;
 97              if new_from.is_zero() {
 98                  tx_balances.remove(from_bytes)?;
 99              } else {
100                  let bytes = serde_json::to_vec(&new_from).unwrap();
101                  tx_balances.insert(from_bytes, bytes)?;
102              }
103              
104              // Credit To
105              let to_bal_bytes = tx_balances.get(to_bytes)?.unwrap_or_default();
106              let to_balance: Balance = if to_bal_bytes.is_empty() {
107                  Balance::ZERO
108              } else {
109                  serde_json::from_slice(&to_bal_bytes).map_err(|_| sled::transaction::ConflictableTransactionError::Abort(TokenError::Storage("Serde error".into())))?
110              };
111              
112              let new_to = to_balance.checked_add(amount).map_err(|e| sled::transaction::ConflictableTransactionError::Abort(e))?;
113              let to_bytes_ser = serde_json::to_vec(&new_to).unwrap();
114              tx_balances.insert(to_bytes, to_bytes_ser)?;
115              
116              Ok(())
117          });
118          
119          tx_result.map_err(|e| match e {
120              sled::transaction::TransactionError::Abort(err) => err,
121              sled::transaction::TransactionError::Storage(err) => TokenError::Storage(err.to_string()),
122          })
123      }
124  
125      fn credit(&self, address: &Address, amount: Balance) -> Result<(), TokenError> {
126          let current = self.get_balance_from_tree(&self.balances, address)?;
127          let new_balance = current.checked_add(amount)?;
128          self.set_balance_in_tree(&self.balances, address, new_balance)?;
129          Ok(())
130      }
131  
132      fn debit(&self, address: &Address, amount: Balance) -> Result<(), TokenError> {
133          let current = self.get_balance_from_tree(&self.balances, address)?;
134          let new_balance = current.checked_sub(amount)?;
135          self.set_balance_in_tree(&self.balances, address, new_balance)?;
136          Ok(())
137      }
138  
139      fn lock(&self, address: &Address, amount: Balance) -> Result<(), TokenError> {
140          let available = self.available_balance(address);
141          if available.drops() < amount.drops() {
142              return Err(TokenError::InsufficientBalance {
143                  have: available.drops(),
144                  need: amount.drops(),
145              });
146          }
147          
148          let current_locked = self.get_balance_from_tree(&self.locked, address)?;
149          let new_locked = current_locked.checked_add(amount)?;
150          self.set_balance_in_tree(&self.locked, address, new_locked)?;
151          Ok(())
152      }
153  
154      fn unlock(&self, address: &Address, amount: Balance) -> Result<(), TokenError> {
155          let current_locked = self.get_balance_from_tree(&self.locked, address)?;
156          let new_locked = current_locked.checked_sub(amount)?;
157          self.set_balance_in_tree(&self.locked, address, new_locked)?;
158          Ok(())
159      }
160  
161      fn locked_balance(&self, address: &Address) -> Balance {
162          self.get_balance_from_tree(&self.locked, address).unwrap_or(Balance::ZERO)
163      }
164  }