Explorar o código

refactor: select proofs on spending condition

thesimplekid hai 9 meses
pai
achega
1f2a49b4bb

+ 1 - 1
bindings/cdk-js/src/wallet.rs

@@ -277,7 +277,7 @@ impl JsWallet {
         self.inner
             .send(
                 &mint_url,
-                &unit.into(),
+                unit.into(),
                 memo,
                 Amount::from(amount),
                 &target,

+ 12 - 5
crates/cdk-redb/src/wallet.rs

@@ -5,7 +5,9 @@ use std::sync::Arc;
 use async_trait::async_trait;
 use cdk::cdk_database;
 use cdk::cdk_database::WalletDatabase;
-use cdk::nuts::{Id, KeySetInfo, Keys, MintInfo, Proofs, PublicKey, SpendingConditions, State};
+use cdk::nuts::{
+    CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, Proofs, PublicKey, SpendingConditions, State,
+};
 use cdk::types::{MeltQuote, MintQuote, ProofInfo};
 use cdk::url::UncheckedUrl;
 use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
@@ -399,6 +401,7 @@ impl WalletDatabase for RedbWalletDatabase {
     async fn get_proofs(
         &self,
         mint_url: Option<UncheckedUrl>,
+        unit: Option<CurrencyUnit>,
         state: Option<Vec<State>>,
         spending_conditions: Option<Vec<SpendingConditions>>,
     ) -> Result<Option<Vec<ProofInfo>>, Self::Err> {
@@ -415,10 +418,14 @@ impl WalletDatabase for RedbWalletDatabase {
                 let mut proof = None;
 
                 if let Ok(proof_info) = serde_json::from_str::<ProofInfo>(v.value()) {
-                    match proof_info.matches_conditions(&mint_url, &state, &spending_conditions) {
-                        Ok(true) => proof = Some(proof_info),
-                        Ok(false) => (),
-                        Err(_) => (),
+                    match proof_info.matches_conditions(
+                        &mint_url,
+                        &unit,
+                        &state,
+                        &spending_conditions,
+                    ) {
+                        true => proof = Some(proof_info),
+                        false => (),
                     }
                 }
 

+ 7 - 4
crates/cdk-rexie/src/wallet.rs

@@ -4,7 +4,9 @@ use std::result::Result;
 
 use async_trait::async_trait;
 use cdk::cdk_database::WalletDatabase;
-use cdk::nuts::{Id, KeySetInfo, Keys, MintInfo, Proofs, PublicKey, SpendingConditions, State};
+use cdk::nuts::{
+    CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, Proofs, PublicKey, SpendingConditions, State,
+};
 use cdk::types::{MeltQuote, MintQuote, ProofInfo};
 use cdk::url::UncheckedUrl;
 use rexie::*;
@@ -448,6 +450,7 @@ impl WalletDatabase for RexieWalletDatabase {
     async fn get_proofs(
         &self,
         mint_url: Option<UncheckedUrl>,
+        unit: Option<CurrencyUnit>,
         state: Option<Vec<State>>,
         spending_conditions: Option<Vec<SpendingConditions>>,
     ) -> Result<Option<Vec<ProofInfo>>, Self::Err> {
@@ -472,12 +475,12 @@ impl WalletDatabase for RexieWalletDatabase {
                 if let Ok(proof_info) = serde_wasm_bindgen::from_value::<ProofInfo>(v) {
                     proof = match proof_info.matches_conditions(
                         &mint_url,
+                        &unit,
                         &state,
                         &spending_conditions,
                     ) {
-                        Ok(true) => Some(proof_info),
-                        Ok(false) => None,
-                        Err(_) => None,
+                        true => Some(proof_info),
+                        false => None,
                     };
                 }
 

+ 3 - 2
crates/cdk/src/cdk_database/mod.rs

@@ -12,9 +12,9 @@ use crate::mint::MintKeySetInfo;
 #[cfg(feature = "wallet")]
 use crate::nuts::State;
 #[cfg(feature = "mint")]
-use crate::nuts::{BlindSignature, CurrencyUnit, Proof};
+use crate::nuts::{BlindSignature, Proof};
 #[cfg(any(feature = "wallet", feature = "mint"))]
-use crate::nuts::{Id, MintInfo, PublicKey};
+use crate::nuts::{CurrencyUnit, Id, MintInfo, PublicKey};
 #[cfg(feature = "wallet")]
 use crate::nuts::{KeySetInfo, Keys, Proofs, SpendingConditions};
 #[cfg(feature = "mint")]
@@ -81,6 +81,7 @@ pub trait WalletDatabase {
     async fn get_proofs(
         &self,
         mint_url: Option<UncheckedUrl>,
+        unit: Option<CurrencyUnit>,
         state: Option<Vec<State>>,
         spending_conditions: Option<Vec<SpendingConditions>>,
     ) -> Result<Option<Vec<ProofInfo>>, Self::Err>;

+ 8 - 5
crates/cdk/src/cdk_database/wallet_memory.rs

@@ -8,7 +8,9 @@ use tokio::sync::RwLock;
 
 use super::WalletDatabase;
 use crate::cdk_database::Error;
-use crate::nuts::{Id, KeySetInfo, Keys, MintInfo, Proofs, PublicKey, SpendingConditions, State};
+use crate::nuts::{
+    CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, Proofs, PublicKey, SpendingConditions, State,
+};
 use crate::types::{MeltQuote, MintQuote, ProofInfo};
 use crate::url::UncheckedUrl;
 
@@ -169,6 +171,7 @@ impl WalletDatabase for WalletMemoryDatabase {
     async fn get_proofs(
         &self,
         mint_url: Option<UncheckedUrl>,
+        unit: Option<CurrencyUnit>,
         state: Option<Vec<State>>,
         spending_conditions: Option<Vec<SpendingConditions>>,
     ) -> Result<Option<Vec<ProofInfo>>, Error> {
@@ -178,10 +181,10 @@ impl WalletDatabase for WalletMemoryDatabase {
             .clone()
             .into_values()
             .filter_map(|proof_info| {
-                match proof_info.matches_conditions(&mint_url, &state, &spending_conditions) {
-                    Ok(true) => Some(proof_info),
-                    Ok(false) => None,
-                    Err(_) => None,
+                match proof_info.matches_conditions(&mint_url, &unit, &state, &spending_conditions)
+                {
+                    true => Some(proof_info),
+                    false => None,
                 }
             })
             .collect();

+ 13 - 6
crates/cdk/src/types.rs

@@ -129,32 +129,39 @@ impl ProofInfo {
     pub fn matches_conditions(
         &self,
         mint_url: &Option<UncheckedUrl>,
+        unit: &Option<CurrencyUnit>,
         state: &Option<Vec<State>>,
         spending_conditions: &Option<Vec<SpendingConditions>>,
-    ) -> Result<bool, Error> {
+    ) -> bool {
         if let Some(mint_url) = mint_url {
             if mint_url.ne(&self.mint_url) {
-                return Ok(false);
+                return false;
+            }
+        }
+
+        if let Some(unit) = unit {
+            if unit.ne(&self.unit) {
+                return false;
             }
         }
 
         if let Some(state) = state {
             if !state.contains(&self.state) {
-                return Ok(false);
+                return false;
             }
         }
 
         if let Some(spending_conditions) = spending_conditions {
             match &self.spending_condition {
-                None => return Ok(false),
+                None => return false,
                 Some(s) => {
                     if !spending_conditions.contains(s) {
-                        return Ok(false);
+                        return false;
                     }
                 }
             }
         }
 
-        Ok(true)
+        true
     }
 }

+ 133 - 46
crates/cdk/src/wallet.rs

@@ -143,7 +143,7 @@ impl Wallet {
 
         if let Some(proofs) = self
             .localstore
-            .get_proofs(None, Some(vec![State::Unspent]), None)
+            .get_proofs(None, None, Some(vec![State::Unspent]), None)
             .await?
         {
             for proof in proofs {
@@ -164,7 +164,7 @@ impl Wallet {
 
         if let Some(proofs) = self
             .localstore
-            .get_proofs(None, Some(vec![State::Pending]), None)
+            .get_proofs(None, None, Some(vec![State::Pending]), None)
             .await?
         {
             let amount = proofs.iter().map(|p| p.proof.amount).sum();
@@ -186,7 +186,7 @@ impl Wallet {
         for (mint, _) in mints {
             if let Some(proofs) = self
                 .localstore
-                .get_proofs(Some(mint.clone()), None, None)
+                .get_proofs(Some(mint.clone()), None, None, None)
                 .await?
             {
                 let mut balances = HashMap::new();
@@ -211,7 +211,7 @@ impl Wallet {
     pub async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> {
         Ok(self
             .localstore
-            .get_proofs(Some(mint_url), Some(vec![State::Unspent]), None)
+            .get_proofs(Some(mint_url), None, Some(vec![State::Unspent]), None)
             .await?
             .map(|p| p.into_iter().map(|p| p.proof).collect()))
     }
@@ -373,8 +373,9 @@ impl Wallet {
                 .localstore
                 .get_proofs(
                     Some(mint.clone()),
-                    Some(vec![State::Unspent, State::Pending]),
                     None,
+                    Some(vec![State::Unspent, State::Pending]),
+                    Some(vec![]),
                 )
                 .await?
             {
@@ -858,33 +859,62 @@ impl Wallet {
     pub async fn send(
         &mut self,
         mint_url: &UncheckedUrl,
-        unit: &CurrencyUnit,
+        unit: CurrencyUnit,
         memo: Option<String>,
         amount: Amount,
         amount_split_target: &SplitTarget,
         conditions: Option<SpendingConditions>,
     ) -> Result<String, Error> {
-        let input_proofs = self.select_proofs(mint_url.clone(), unit, amount).await?;
+        let (condition_input_proofs, input_proofs) = self
+            .select_proofs(mint_url.clone(), unit.clone(), amount, conditions.clone())
+            .await?;
 
-        let send_proofs = match (
-            input_proofs
-                .iter()
-                .map(|p| p.amount)
-                .sum::<Amount>()
-                .eq(&amount),
-            &conditions,
-        ) {
-            (true, None) => Some(input_proofs),
-            _ => {
-                self.swap(
-                    mint_url,
-                    unit,
-                    Some(amount),
-                    amount_split_target,
-                    input_proofs,
-                    conditions,
+        let send_proofs = match conditions {
+            Some(_) => {
+                let needed_amount = condition_input_proofs
+                    .iter()
+                    .map(|p| p.amount)
+                    .sum::<Amount>();
+
+                let top_up_proofs = self
+                    .swap(
+                        mint_url,
+                        &unit,
+                        Some(needed_amount),
+                        amount_split_target,
+                        input_proofs,
+                        conditions,
+                    )
+                    .await?;
+
+                Some(
+                    [
+                        condition_input_proofs,
+                        top_up_proofs.ok_or(Error::InsufficientFunds)?,
+                    ]
+                    .concat(),
                 )
-                .await?
+            }
+            None => {
+                match input_proofs
+                    .iter()
+                    .map(|p| p.amount)
+                    .sum::<Amount>()
+                    .eq(&amount)
+                {
+                    true => Some(input_proofs),
+                    false => {
+                        self.swap(
+                            mint_url,
+                            &unit,
+                            Some(amount),
+                            amount_split_target,
+                            input_proofs,
+                            conditions,
+                        )
+                        .await?
+                    }
+                }
             }
         };
 
@@ -962,32 +992,83 @@ impl Wallet {
     pub async fn select_proofs(
         &self,
         mint_url: UncheckedUrl,
-        unit: &CurrencyUnit,
+        unit: CurrencyUnit,
         amount: Amount,
-    ) -> Result<Proofs, Error> {
-        let mint_proofs: Proofs = self
-            .localstore
-            .get_proofs(Some(mint_url.clone()), Some(vec![State::Unspent]), None)
-            .await?
-            .ok_or(Error::InsufficientFunds)?
-            .into_iter()
-            .map(|p| p.proof)
-            .collect();
+        conditions: Option<SpendingConditions>,
+    ) -> Result<(Proofs, Proofs), Error> {
+        let mut condition_mint_proofs = Vec::new();
+
+        if conditions.is_some() {
+            condition_mint_proofs = self
+                .localstore
+                .get_proofs(
+                    Some(mint_url.clone()),
+                    Some(unit.clone()),
+                    Some(vec![State::Unspent]),
+                    None,
+                )
+                .await?
+                .unwrap_or_default()
+                .into_iter()
+                .map(|p| p.proof)
+                .collect();
+        }
 
         let mint_keysets = self
             .localstore
-            .get_mint_keysets(mint_url)
+            .get_mint_keysets(mint_url.clone())
             .await?
             .ok_or(Error::UnknownKey)?;
 
         let (active, inactive): (HashSet<KeySetInfo>, HashSet<KeySetInfo>) = mint_keysets
             .into_iter()
-            .filter(|p| p.unit.eq(unit))
+            .filter(|p| p.unit.eq(&unit.clone()))
             .partition(|x| x.active);
 
         let active: HashSet<Id> = active.iter().map(|k| k.id).collect();
         let inactive: HashSet<Id> = inactive.iter().map(|k| k.id).collect();
 
+        let (mut condition_active_proofs, mut condition_inactive_proofs): (Proofs, Proofs) =
+            condition_mint_proofs
+                .into_iter()
+                .partition(|p| active.contains(&p.keyset_id));
+
+        condition_active_proofs.reverse();
+        condition_inactive_proofs.reverse();
+
+        let condition_proofs = [condition_inactive_proofs, condition_active_proofs].concat();
+
+        let mut condition_selected_proofs: Proofs = Vec::new();
+
+        for proof in condition_proofs {
+            if condition_selected_proofs
+                .iter()
+                .map(|p| p.amount)
+                .sum::<Amount>()
+                < amount
+            {
+                condition_selected_proofs.push(proof);
+            } else {
+                return Ok((condition_selected_proofs, vec![]));
+            }
+        }
+
+        let condition_proof_total = condition_selected_proofs.iter().map(|p| p.amount).sum();
+
+        let mint_proofs: Proofs = self
+            .localstore
+            .get_proofs(
+                Some(mint_url.clone()),
+                Some(unit.clone()),
+                Some(vec![State::Unspent]),
+                None,
+            )
+            .await?
+            .ok_or(Error::InsufficientFunds)?
+            .into_iter()
+            .map(|p| p.proof)
+            .collect();
+
         let mut active_proofs: Proofs = Vec::new();
         let mut inactive_proofs: Proofs = Vec::new();
 
@@ -1002,15 +1083,15 @@ impl Wallet {
         active_proofs.reverse();
         inactive_proofs.reverse();
 
-        inactive_proofs.append(&mut active_proofs);
-
-        let proofs = inactive_proofs;
-
         let mut selected_proofs: Proofs = Vec::new();
 
-        for proof in proofs {
-            if selected_proofs.iter().map(|p| p.amount).sum::<Amount>() < amount {
+        for proof in [inactive_proofs, active_proofs].concat() {
+            if selected_proofs.iter().map(|p| p.amount).sum::<Amount>() + condition_proof_total
+                < amount
+            {
                 selected_proofs.push(proof);
+            } else {
+                break;
             }
         }
 
@@ -1018,7 +1099,7 @@ impl Wallet {
             return Err(Error::InsufficientFunds);
         }
 
-        Ok(selected_proofs)
+        Ok((condition_selected_proofs, selected_proofs))
     }
 
     /// Melt
@@ -1042,8 +1123,14 @@ impl Wallet {
         };
 
         let proofs = self
-            .select_proofs(mint_url.clone(), &quote_info.unit, quote_info.amount)
-            .await?;
+            .select_proofs(
+                mint_url.clone(),
+                quote_info.unit.clone(),
+                quote_info.amount,
+                None,
+            )
+            .await?
+            .1;
 
         let proofs_amount = proofs.iter().map(|p| p.amount).sum();