use crate::{ storage::{Batch, Storage}, transaction::Type, AccountId, Amount, AssetManager, Error, Payment, Status, Transaction, TransactionId, }; use std::{cmp::Ordering, collections::HashMap}; #[derive(Clone, Debug)] pub struct Ledger<'a, B, S> where B: Batch<'a>, S: Storage<'a, B> + Sync + Send, { storage: S, asset_manager: AssetManager, _phantom: std::marker::PhantomData<&'a B>, } impl<'a, B, S> Ledger<'a, B, S> where B: Batch<'a>, S: Storage<'a, B> + Sync + Send, { pub fn new(storage: S, asset_manager: AssetManager) -> Self { Self { storage, asset_manager, _phantom: std::marker::PhantomData, } } pub fn parse_amount(&self, asset: &str, amount: &str) -> Result { Ok(self.asset_manager.human_amount_by_name(asset, amount)?) } /// Selects the unspent payments to be used as inputs of the new transaction. /// /// This function also returns a list of transactions that will be used as /// exchanged transactions, to make sure the main transaction doesn't hold /// extra funds, by splitting any large unspent payments into two, one that /// matches exactly the needed amount, and another one that will be used as /// change. These exchange transaction are internal transactions and they /// are created as settled. async fn create_inputs_to_pay_from_accounts( &self, payments: Vec<(AccountId, Amount)>, ) -> Result<(Vec, Vec), Error> { let mut to_spend = HashMap::new(); for (account_id, amount) in payments.into_iter() { let id = (account_id, *amount.asset()); if let Some(value) = to_spend.get_mut(&id) { *value += amount.cents(); } else { to_spend.insert(id, amount.cents()); } } let mut change_transactions = vec![]; let mut payments: Vec = vec![]; for ((account, asset), mut to_spend_cents) in to_spend.into_iter() { let iterator = self .storage .get_unspent_payments(&account, asset.id, to_spend_cents) .await?; for payment in iterator.into_iter() { let cents = payment.amount.cents(); to_spend_cents -= cents; payments.push(payment); match to_spend_cents.cmp(&0) { Ordering::Equal => { // No change amount, we are done with this input break; } Ordering::Less => { // There is a change amount, we need to split the last // input into two payment_ids into the same accounts in // a transaction that will settle immediately, otherwise // the change amount will be unspentable until this // transaction settles. By doing so the current // operation will have no change and it can safely take // its time to settle without making any change amount // unspentable. let to_spend_cents = to_spend_cents.abs(); let input = payments .pop() .ok_or(Error::InsufficientBalance(account.clone(), asset.id))?; let split_input = Transaction::new( "Exchange transaction".to_owned(), // Set the change transaction as settled. This is an // internal transaction to split an existing payment // into two. Since this is an internal transaction it // can be settled immediately. // // Because this internal transaction is being settled // immediately, the other payment can be used right away, // otherwise it would be locked until the main // transaction settles. Status::Settled, Type::Internal, vec![input], vec![ (account.clone(), asset.new_amount(cents - to_spend_cents)), (account.clone(), asset.new_amount(to_spend_cents)), ], ) .await?; // Spend the new payment payments.push(split_input.creates()[0].clone()); // Return the split payment transaction to be executed // later as a pre-requisite for the new transaction change_transactions.push(split_input); // Go to the next payment input or exit the loop break; } _ => { // We need more funds, continue to the selecting the // available payment if any } } } if to_spend_cents > 0 { // We don't have enough payment to cover the to_spend_cents // Return an insufficient balance error return Err(Error::InsufficientBalance(account, asset.id)); } } Ok((change_transactions, payments)) } /// Creates a new transaction and returns it. /// /// The input is pretty simple, take this amounts from these given accounts /// and send them to these accounts (and amounts). The job of this function /// is to figure it out how to do it. This function will make sure that each /// account has enough balance, selecting the unspent payments from each /// account that will be spent. It will also return a list of transactions /// that will be used to return the change to the accounts, these accounts /// can be settled immediately so no other funds required to perform the /// transaction are locked. /// /// This functions performs read only operations on top of the storage layer /// and it will guarantee execution (meaning that it will not lock any /// funds, so these transactions may fail if any selected payment is spent /// between the time the transaction is created and executed). /// /// A NewTransaction struct is returned, the change_transactions should be /// executed and set as settled before the transaction is executed, /// otherwise it will fail. A failure in any execution will render the /// entire operation as failed but no funds will be locked. pub async fn new_transaction( &'a self, reference: String, status: Status, from: Vec<(AccountId, Amount)>, to: Vec<(AccountId, Amount)>, ) -> Result { let (change_transactions, payments) = self.create_inputs_to_pay_from_accounts(from).await?; for mut change_tx in change_transactions.into_iter() { change_tx.persist(&self.storage).await?; } let mut transaction = Transaction::new(reference, status, Type::Transaction, payments, to).await?; transaction.persist(&self.storage).await?; Ok(transaction) } /// Return the balances from a given account /// /// The balance is a vector of Amounts, one for each asset. The balance will /// return only spendable amounts, meaning that any amount that is locked in /// a transaction will not be returned. /// /// TODO: Return locked funds as well. pub async fn get_balance(&self, account: &AccountId) -> Result, Error> { Ok(self.storage.get_balance(account).await?) } pub async fn deposit( &'a self, account: &AccountId, amount: Amount, status: Status, reference: String, ) -> Result { let mut transaction = Transaction::new_external_deposit(reference, status, vec![(account.clone(), amount)])?; println!("{}", serde_json::to_string_pretty(&transaction).unwrap()); transaction.persist(&self.storage).await?; Ok(transaction) } pub async fn withdrawal( &'a self, account: &AccountId, amount: Amount, status: Status, reference: String, ) -> Result { let (change_transactions, payments) = self .create_inputs_to_pay_from_accounts(vec![(account.clone(), amount)]) .await?; for mut change_tx in change_transactions.into_iter() { change_tx.persist(&self.storage).await?; } let mut transaction = Transaction::new_external_withdrawal(reference, status, payments)?; transaction.persist(&self.storage).await?; Ok(transaction) } /// Returns the transaction object by a given transaction id pub async fn get_transaction( &'a self, transaction_id: &TransactionId, ) -> Result { Ok(self .storage .get_transaction(transaction_id) .await? .try_into()?) } /// Returns all transactions from a given account. It can be optionally be /// sorted by transaction type. The transactions are sorted from newest to /// oldest. pub async fn get_transactions( &'a self, account_id: &AccountId, typ: Option, ) -> Result, Error> { let r = self .storage .get_transactions(account_id, typ) .await? .into_iter() .map(|x| x.try_into()) .collect::, _>>()?; Ok(r) } /// Attemps to change the status of a given transaction id. On success the /// new transaction object is returned, otherwise an error is returned. pub async fn change_status( &'a self, transaction_id: &TransactionId, new_status: Status, ) -> Result { let mut tx: Transaction = self .storage .get_transaction(transaction_id) .await? .try_into()?; tx.change_status(new_status)?; tx.persist(&self.storage).await?; Ok(tx) } }