use std::collections::HashMap;
use std::sync::Mutex;
use async_trait::async_trait;
use crate::amount::Amount;
use crate::settlement::traits::SettlementLayer;
use crate::settlement::types::{FailureReason, SettlementStatus};
use crate::transaction::{Transaction, TxId, Verified};
use crate::wallet::WalletId;
#[derive(Debug)]
pub struct InMemorySettlement {
store: Mutex<HashMap<TxId, SettlementStatus>>,
nonces: Mutex<HashMap<WalletId, u64>>,
balances: Mutex<HashMap<WalletId, Amount>>,
}
impl Default for InMemorySettlement {
fn default() -> Self {
Self::new()
}
}
impl InMemorySettlement {
pub fn new() -> Self {
Self {
store: Mutex::new(HashMap::new()),
nonces: Mutex::new(HashMap::new()),
balances: Mutex::new(HashMap::new()),
}
}
pub fn deposit(&self, wallet: WalletId, amount: Amount) {
let mut balances = self.balances.lock().unwrap();
let entry = balances.entry(wallet).or_insert(Amount::new(0).unwrap());
*entry = Amount::new(entry.value() + amount.value()).unwrap();
}
pub fn balance(&self, wallet: &WalletId) -> Option<Amount> {
let balances = self.balances.lock().unwrap();
balances.get(wallet).cloned()
}
pub fn transaction(&self, tx_id: &TxId) -> Option<SettlementStatus> {
let store = self.store.lock().unwrap();
store.get(tx_id).cloned()
}
}
#[async_trait]
impl SettlementLayer for InMemorySettlement {
type Error = std::io::Error;
async fn submit(&self, tx: Transaction<Verified>) -> Result<TxId, Self::Error> {
let mut store = self.store.lock().unwrap();
let mut nonces = self.nonces.lock().unwrap();
let mut balances = self.balances.lock().unwrap();
if store.get(&tx.id).is_some() {
return Ok(tx.id);
}
let sender = tx.from.clone();
let incoming_nonce = tx.nonce;
if let Some(last_nonce) = nonces.get(&sender)
&& incoming_nonce <= *last_nonce
{
return Err(std::io::Error::other("nonce replay detected"));
}
let sender_balance = balances
.get(&tx.from)
.cloned()
.unwrap_or_else(|| Amount::new(0).unwrap());
if sender_balance.value() < tx.amount.value() {
return Err(std::io::Error::other("insufficient funds"));
}
balances.insert(
tx.from.clone(),
Amount::new(sender_balance.value() - tx.amount.value()).unwrap(),
);
let receiver_balance = balances
.get(&tx.to)
.cloned()
.unwrap_or_else(|| Amount::new(0).unwrap());
balances.insert(
tx.to.clone(),
Amount::new(receiver_balance.value() + tx.amount.value()).unwrap(),
);
store.insert(tx.id.clone(), SettlementStatus::Pending);
nonces.insert(sender, incoming_nonce);
Ok(tx.id)
}
async fn status(&self, tx_id: &TxId) -> Result<SettlementStatus, Self::Error> {
let mut store = self.store.lock().unwrap();
match store.get_mut(tx_id) {
Some(current) => {
*current = match *current {
SettlementStatus::Pending => SettlementStatus::Confirmed,
SettlementStatus::Confirmed => SettlementStatus::Finalized,
SettlementStatus::Finalized => SettlementStatus::Finalized,
SettlementStatus::Failed(ref reason) => {
SettlementStatus::Failed(reason.clone())
}
};
Ok(current.clone())
}
None => Ok(SettlementStatus::Failed(FailureReason::Unknown)),
}
}
async fn await_finality(&self, tx_id: &TxId) -> Result<SettlementStatus, Self::Error> {
loop {
let status = self.status(tx_id).await?;
if matches!(
status,
SettlementStatus::Finalized | SettlementStatus::Failed(_)
) {
return Ok(status);
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
}
async fn estimate_fee(&self, _tx: &Transaction<Verified>) -> Result<Amount, Self::Error> {
Ok(Amount::new(1).unwrap())
}
}