Ver código fonte

Reading the available amounts from the mint

Cesar Rodas 1 mês atrás
pai
commit
df824056e6

+ 58 - 29
crates/cashu/src/amount.rs

@@ -59,32 +59,43 @@ impl Amount {
     /// Amount one
     pub const ONE: Amount = Amount(1);
 
-    /// Split into parts that are powers of two
-    pub fn split(&self) -> Vec<Self> {
-        let sats = self.0;
-        (0_u64..64)
-            .rev()
-            .filter_map(|bit| {
-                let part = 1 << bit;
-                ((sats & part) == part).then_some(Self::from(part))
-            })
-            .collect()
+    /// Split into parts that are in the `supported_amounts`
+    pub fn split(&self, supported_amounts: &[u64]) -> Result<Vec<Self>, Error> {
+        let (amounts, left_over) =
+            supported_amounts
+                .iter()
+                .fold((Vec::new(), self.0), |(mut acc, mut total), &amount| {
+                    while total >= amount {
+                        acc.push(Self::from(amount));
+                        total = total.checked_sub(amount).unwrap_or_default();
+                    }
+                    (acc, total)
+                });
+
+        if left_over != 0 {
+            return Err(Error::SplitValuesGreater);
+        }
+
+        Ok(amounts)
     }
 
     /// Split into parts that are powers of two by target
-    pub fn split_targeted(&self, target: &SplitTarget) -> Result<Vec<Self>, Error> {
+    pub fn split_targeted(
+        &self,
+        target: &SplitTarget,
+        supported_amounts: &[u64],
+    ) -> Result<Vec<Self>, Error> {
         let mut parts = match target {
-            SplitTarget::None => self.split(),
+            SplitTarget::None => self.split(supported_amounts)?,
             SplitTarget::Value(amount) => {
                 if self.le(amount) {
-                    return Ok(self.split());
+                    return self.split(supported_amounts);
                 }
 
                 let mut parts_total = Amount::ZERO;
                 let mut parts = Vec::new();
 
-                // The powers of two that are need to create target value
-                let parts_of_value = amount.split();
+                let parts_of_value = amount.split(supported_amounts)?;
 
                 while parts_total.lt(self) {
                     for part in parts_of_value.iter().copied() {
@@ -92,7 +103,7 @@ impl Amount {
                             parts.push(part);
                         } else {
                             let amount_left = *self - parts_total;
-                            parts.extend(amount_left.split());
+                            parts.extend(amount_left.split(supported_amounts)?);
                         }
 
                         parts_total = Amount::try_sum(parts.clone().iter().copied())?;
@@ -115,7 +126,7 @@ impl Amount {
                     }
                     Ordering::Greater => {
                         let extra = *self - values_total;
-                        let mut extra_amount = extra.split();
+                        let mut extra_amount = extra.split(supported_amounts)?;
                         let mut values = values.clone();
 
                         values.append(&mut extra_amount);
@@ -130,13 +141,17 @@ impl Amount {
     }
 
     /// Splits amount into powers of two while accounting for the swap fee
-    pub fn split_with_fee(&self, fee_ppk: u64) -> Result<Vec<Self>, Error> {
-        let without_fee_amounts = self.split();
+    pub fn split_with_fee(
+        &self,
+        fee_ppk: u64,
+        supported_amounts: &[u64],
+    ) -> Result<Vec<Self>, Error> {
+        let without_fee_amounts = self.split(supported_amounts)?;
         let fee_ppk = fee_ppk * without_fee_amounts.len() as u64;
         let fee = Amount::from(fee_ppk.div_ceil(1000));
         let new_amount = self.checked_add(fee).ok_or(Error::AmountOverflow)?;
 
-        let split = new_amount.split();
+        let split = new_amount.split(supported_amounts)?;
         let split_fee_ppk = split.len() as u64 * fee_ppk;
         let split_fee = Amount::from(split_fee_ppk.div_ceil(1000));
 
@@ -147,7 +162,7 @@ impl Amount {
         }
         self.checked_add(Amount::ONE)
             .ok_or(Error::AmountOverflow)?
-            .split_with_fee(fee_ppk)
+            .split_with_fee(fee_ppk, supported_amounts)
     }
 
     /// Checked addition for Amount. Returns None if overflow occurs.
@@ -361,19 +376,30 @@ mod tests {
 
     #[test]
     fn test_split_amount() {
-        assert_eq!(Amount::from(1).split(), vec![Amount::from(1)]);
-        assert_eq!(Amount::from(2).split(), vec![Amount::from(2)]);
         assert_eq!(
-            Amount::from(3).split(),
+            Amount::from(1).split(&[3, 2, 1]).unwrap(),
+            vec![Amount::from(1)]
+        );
+        assert_eq!(
+            Amount::from(2).split(&[3, 2, 1]).unwrap(),
+            vec![Amount::from(2)]
+        );
+        assert_eq!(
+            Amount::from(3).split(&[2, 1]).unwrap(),
             vec![Amount::from(2), Amount::from(1)]
         );
         let amounts: Vec<Amount> = [8, 2, 1].iter().map(|a| Amount::from(*a)).collect();
-        assert_eq!(Amount::from(11).split(), amounts);
+        assert_eq!(Amount::from(11).split(&[20, 8, 2, 1]).unwrap(), amounts);
         let amounts: Vec<Amount> = [128, 64, 32, 16, 8, 4, 2, 1]
             .iter()
             .map(|a| Amount::from(*a))
             .collect();
-        assert_eq!(Amount::from(255).split(), amounts);
+        assert_eq!(
+            Amount::from(255)
+                .split(&[500, 128, 64, 32, 16, 8, 4, 2, 1])
+                .unwrap(),
+            amounts
+        );
     }
 
     #[test]
@@ -381,14 +407,14 @@ mod tests {
         let amount = Amount(65);
 
         let split = amount
-            .split_targeted(&SplitTarget::Value(Amount(32)))
+            .split_targeted(&SplitTarget::Value(Amount(32)), &[64, 32, 4, 2, 1])
             .unwrap();
         assert_eq!(vec![Amount(1), Amount(32), Amount(32)], split);
 
         let amount = Amount(150);
 
         let split = amount
-            .split_targeted(&SplitTarget::Value(Amount::from(50)))
+            .split_targeted(&SplitTarget::Value(Amount::from(50)), &[50])
             .unwrap();
         assert_eq!(
             vec![
@@ -408,7 +434,10 @@ mod tests {
         let amount = Amount::from(63);
 
         let split = amount
-            .split_targeted(&SplitTarget::Value(Amount::from(32)))
+            .split_targeted(
+                &SplitTarget::Value(Amount::from(32)),
+                &[128, 64, 32, 16, 8, 4, 2, 1],
+            )
             .unwrap();
         assert_eq!(
             vec![

+ 4 - 2
crates/cashu/src/nuts/nut00/mod.rs

@@ -746,8 +746,9 @@ impl PreMintSecrets {
         keyset_id: Id,
         amount: Amount,
         amount_split_target: &SplitTarget,
+        supported_amounts: &[u64],
     ) -> Result<Self, Error> {
-        let amount_split = amount.split_targeted(amount_split_target)?;
+        let amount_split = amount.split_targeted(amount_split_target, supported_amounts)?;
 
         let mut output = Vec::with_capacity(amount_split.len());
 
@@ -830,8 +831,9 @@ impl PreMintSecrets {
         amount: Amount,
         amount_split_target: &SplitTarget,
         conditions: &SpendingConditions,
+        supported_amounts: &[u64],
     ) -> Result<Self, Error> {
-        let amount_split = amount.split_targeted(amount_split_target)?;
+        let amount_split = amount.split_targeted(amount_split_target, supported_amounts)?;
 
         let mut output = Vec::with_capacity(amount_split.len());
 

+ 2 - 1
crates/cashu/src/nuts/nut13.rs

@@ -127,12 +127,13 @@ impl PreMintSecrets {
         seed: &[u8; 64],
         amount: Amount,
         amount_split_target: &SplitTarget,
+        supported_amounts: &[u64],
     ) -> Result<Self, Error> {
         let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
 
         let mut counter = counter;
 
-        for amount in amount.split_targeted(amount_split_target)? {
+        for amount in amount.split_targeted(amount_split_target, supported_amounts)? {
             let secret = Secret::from_seed(seed, keyset_id, counter)?;
             let blinding_factor = SecretKey::from_seed(seed, keyset_id, counter)?;
 

+ 1 - 0
crates/cdk/src/wallet/builder.rs

@@ -166,6 +166,7 @@ impl WalletBuilder {
             seed,
             client: client.clone(),
             subscription: SubscriptionManager::new(client, self.use_http_subscription),
+            supported_amounts: Default::default(),
         })
     }
 }

+ 28 - 0
crates/cdk/src/wallet/mod.rs

@@ -4,6 +4,7 @@ use std::collections::HashMap;
 use std::str::FromStr;
 use std::sync::Arc;
 
+use arc_swap::ArcSwap;
 use cdk_common::database::{self, WalletDatabase};
 use cdk_common::subscription::Params;
 use getrandom::getrandom;
@@ -87,6 +88,7 @@ pub struct Wallet {
     seed: [u8; 64],
     client: Arc<dyn MintConnector + Send + Sync>,
     subscription: SubscriptionManager,
+    supported_amounts: Arc<ArcSwap<HashMap<Id, Vec<u64>>>>,
 }
 
 const ALPHANUMERIC: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -323,6 +325,32 @@ impl Wallet {
         }
     }
 
+    /// Get supported amounts
+    ///
+    /// The amounts are cached in the Wallet trait in memory
+    pub async fn get_supported_amounts(&self, keyset_id: Id) -> Result<Vec<u64>, Error> {
+        let supported_amounts = self.supported_amounts.load();
+        if let Some(amounts) = supported_amounts.get(&keyset_id) {
+            return Ok(amounts.to_owned());
+        }
+
+        let mut supported_amounts_to_update = (*(*supported_amounts)).clone();
+        drop(supported_amounts);
+
+        let keys = self.load_keyset_keys(keyset_id.clone()).await?;
+        let amounts = keys
+            .iter()
+            .map(|(amount, _)| *amount.as_ref())
+            .collect::<Vec<_>>();
+
+        supported_amounts_to_update.insert(keyset_id, amounts.clone());
+
+        self.supported_amounts
+            .store(Arc::new(supported_amounts_to_update));
+
+        Ok(amounts)
+    }
+
     /// Get amounts needed to refill proof state
     #[instrument(skip(self))]
     pub async fn amounts_needed_for_state_target(&self) -> Result<Vec<Amount>, Error> {

+ 4 - 1
crates/cdk/src/wallet/proofs.rs

@@ -241,6 +241,7 @@ impl Wallet {
         active_keyset_ids: &Vec<Id>,
         keyset_fees: &HashMap<Id, u64>,
         include_fees: bool,
+        supported_amounts: &[u64],
     ) -> Result<Proofs, Error> {
         tracing::debug!(
             "amount={}, proofs={:?}",
@@ -257,7 +258,7 @@ impl Wallet {
         proofs.sort_by(|a, b| a.cmp(b).reverse());
 
         // Split the amount into optimal amounts
-        let optimal_amounts = amount.split();
+        let optimal_amounts = amount.split(supported_amounts)?;
 
         // Track selected proofs and remaining amounts (include all inactive proofs first)
         let mut selected_proofs: HashSet<Proof> = proofs
@@ -430,6 +431,7 @@ impl Wallet {
         mut selected_proofs: Proofs,
         active_keyset_ids: &Vec<Id>,
         keyset_fees: &HashMap<Id, u64>,
+        supported_amounts: &[u64],
     ) -> Result<Proofs, Error> {
         tracing::debug!("Including fees");
         let fee =
@@ -464,6 +466,7 @@ impl Wallet {
             active_keyset_ids,
             &HashMap::new(), // Fees are already calculated
             false,
+            supported_amounts,
         )?);
         tracing::debug!(
             "Selected proofs: {:?}",