diff --git a/api/src/owner.rs b/api/src/owner.rs index e504bb0f3..cb521be99 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -298,7 +298,7 @@ where ) -> Result { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; - owner::create_account_path(&mut **w, keychain_mask, label) + owner::create_account_path(&mut **w, keychain_mask, label.to_string()) } /// Sets the wallet's currently active account. This sets the diff --git a/impls/src/backends/lmdb.rs b/impls/src/backends/lmdb.rs index 92293e422..dcb9aa3c3 100644 --- a/impls/src/backends/lmdb.rs +++ b/impls/src/backends/lmdb.rs @@ -274,13 +274,11 @@ where /// Set parent path by account name fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error> { - let label = label.to_owned(); - let res = self.acct_path_iter().find(|l| l.label == label); - if let Some(a) = res { + if let Some(a) = self.get_acct_path(label)? { self.set_parent_key_id(a.path); Ok(()) } else { - Err(ErrorKind::UnknownAccountLabel(label).into()) + Err(ErrorKind::UnknownAccountLabel(label.to_string()).into()) } } @@ -302,8 +300,25 @@ where .map_err(|e| e.into()) } - fn iter<'a>(&'a self) -> Box + 'a> { - Box::new(self.db.iter(&[OUTPUT_PREFIX]).unwrap().map(|o| o.1)) + fn iter<'a>( + &'a self, + parent_key_id: Option<&Identifier>, + tx_id: Option, + ) -> Box + 'a> { + let parent_key_id = parent_key_id.cloned(); + Box::new( + self.db + .iter(&[OUTPUT_PREFIX]) + .unwrap() + .map(|o: (_, OutputData)| o.1) + .filter(move |o| { + parent_key_id + .as_ref() + .map(|id| o.root_key_id == *id) + .unwrap_or(true) + }) + .filter(move |o| tx_id.map(|id| o.tx_log_entry == Some(id)).unwrap_or(true)), + ) } fn get_tx_log_entry(&self, u: &Uuid) -> Result, Error> { @@ -311,8 +326,40 @@ where self.db.get_ser(&key).map_err(|e| e.into()) } - fn tx_log_iter<'a>(&'a self) -> Box + 'a> { - Box::new(self.db.iter(&[TX_LOG_ENTRY_PREFIX]).unwrap().map(|o| o.1)) + fn tx_log_iter<'a>( + &'a self, + parent_key_id: Option<&Identifier>, + tx_id: Option, + tx_slate_id: Option, + outstanding_only: bool, + ) -> Box + 'a> { + use crate::libwallet::TxLogEntryType::*; + let parent_key_id = parent_key_id.cloned(); + Box::new( + self.db + .iter(&[TX_LOG_ENTRY_PREFIX]) + .unwrap() + .map(|o: (_, TxLogEntry)| o.1) + .filter(move |tx| { + parent_key_id + .as_ref() + .map(|id| tx.parent_key_id == *id) + .unwrap_or(true) + }) + .filter(move |tx| tx_id.map(|id| tx.id == id).unwrap_or(true)) + .filter(move |tx| { + tx_slate_id + .map(|id| tx.tx_slate_id == Some(id)) + .unwrap_or(true) + }) + .filter(move |tx| { + if outstanding_only { + !tx.confirmed && (tx.tx_type == TxReceived || tx.tx_type == TxSent) + } else { + true + } + }), + ) } fn get_private_context( @@ -350,7 +397,7 @@ where ) } - fn get_acct_path(&self, label: String) -> Result, Error> { + fn get_acct_path(&self, label: &str) -> Result, Error> { let acct_key = to_key(ACCOUNT_PATH_MAPPING_PREFIX, &mut label.as_bytes().to_vec()); self.db.get_ser(&acct_key).map_err(|e| e.into()) } diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index c26f795f2..0773e05db 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -74,7 +74,7 @@ where check_ttl(w, &ret_slate)?; let parent_key_id = match dest_acct_name { Some(d) => { - let pm = w.get_acct_path(d.to_owned())?; + let pm = w.get_acct_path(d)?; match pm { Some(p) => p.path, None => w.parent_key_id(), diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 96f21ec20..8193c89a4 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -56,7 +56,7 @@ where pub fn create_account_path<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, - label: &str, + label: String, ) -> Result where T: WalletBackend<'a, C, K>, @@ -311,7 +311,7 @@ where { let parent_key_id = match args.src_acct_name { Some(d) => { - let pm = w.get_acct_path(d)?; + let pm = w.get_acct_path(&d)?; match pm { Some(p) => p.path, None => w.parent_key_id(), @@ -411,7 +411,7 @@ where { let parent_key_id = match args.dest_acct_name { Some(d) => { - let pm = w.get_acct_path(d)?; + let pm = w.get_acct_path(&d)?; match pm { Some(p) => p.path, None => w.parent_key_id(), @@ -473,7 +473,7 @@ where check_ttl(w, &ret_slate)?; let parent_key_id = match args.src_acct_name { Some(d) => { - let pm = w.get_acct_path(d)?; + let pm = w.get_acct_path(&d)?; match pm { Some(p) => p.path, None => w.parent_key_id(), diff --git a/libwallet/src/internal/keys.rs b/libwallet/src/internal/keys.rs index 8026e8d34..ca590260c 100644 --- a/libwallet/src/internal/keys.rs +++ b/libwallet/src/internal/keys.rs @@ -63,28 +63,22 @@ where pub fn new_acct_path<'a, T: ?Sized, C, K>( wallet: &mut T, keychain_mask: Option<&SecretKey>, - label: &str, + label: String, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { - let label = label.to_owned(); - if wallet.acct_path_iter().any(|l| l.label == label) { + if wallet.get_acct_path(&label)?.is_some() { return Err(ErrorKind::AccountLabelAlreadyExists(label).into()); } // We're always using paths at m/k/0 for parent keys for output derivations // so find the highest of those, then increment (to conform with external/internal // derivation chains in BIP32 spec) - - let highest_entry = wallet.acct_path_iter().max_by(|a, b| { - ::from(a.path.to_path().path[0]).cmp(&::from(b.path.to_path().path[0])) - }); - let return_id = { - if let Some(e) = highest_entry { + if let Some(e) = wallet.highest_acct_path() { let mut p = e.path.to_path(); p.path[0] = ChildNumber::from(::from(p.path[0]) + 1); p.to_identifier() diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index 8aa477e3f..39e70b03f 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -539,11 +539,7 @@ where { // first find all eligible outputs based on number of confirmations let mut eligible = wallet - .iter() - .filter(|out| { - out.root_key_id == *parent_key_id - && out.eligible_to_spend(current_height, minimum_confirmations) - }) + .eligible_outputs(parent_key_id, current_height, minimum_confirmations) .collect::>(); let max_available = eligible.len(); diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index fdfda9df1..4cef42c5c 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -49,26 +49,12 @@ where K: Keychain + 'a, { // just read the wallet here, no need for a write lock - let mut outputs = wallet - .iter() - .filter(|out| show_spent || out.status != OutputStatus::Spent) - .collect::>(); - // only include outputs with a given tx_id if provided - if let Some(id) = tx_id { - outputs = outputs - .into_iter() - .filter(|out| out.tx_log_entry == Some(id)) - .collect::>(); - } - - if let Some(k) = parent_key_id { - outputs = outputs - .iter() - .filter(|o| o.root_key_id == *k) - .cloned() - .collect() - } + let mut outputs: Vec<_> = if show_spent { + wallet.iter(parent_key_id, tx_id).collect() + } else { + wallet.unspent_outputs(parent_key_id, tx_id).collect() + }; outputs.sort_by_key(|out| out.n_child); let keychain = wallet.keychain(keychain_mask)?; @@ -102,31 +88,8 @@ where C: NodeClient + 'a, K: Keychain + 'a, { - let mut txs: Vec = wallet - .tx_log_iter() - .filter(|tx_entry| { - let f_pk = match parent_key_id { - Some(k) => tx_entry.parent_key_id == *k, - None => true, - }; - let f_tx_id = match tx_id { - Some(i) => tx_entry.id == i, - None => true, - }; - let f_txs = match tx_slate_id { - Some(t) => tx_entry.tx_slate_id == Some(t), - None => true, - }; - let f_outstanding = match outstanding_only { - true => { - !tx_entry.confirmed - && (tx_entry.tx_type == TxLogEntryType::TxReceived - || tx_entry.tx_type == TxLogEntryType::TxSent) - } - false => true, - }; - f_pk && f_tx_id && f_txs && f_outstanding - }) + let mut txs: Vec<_> = wallet + .tx_log_iter(parent_key_id, tx_id, tx_slate_id, outstanding_only) .collect(); txs.sort_by_key(|tx| tx.creation_ts); Ok(txs) @@ -166,23 +129,12 @@ where let mut wallet_outputs: HashMap)> = HashMap::new(); let keychain = wallet.keychain(keychain_mask)?; - let unspents: Vec = wallet - .iter() - .filter(|x| x.root_key_id == *parent_key_id && x.status != OutputStatus::Spent) - .collect(); - - let tx_entries = retrieve_txs(wallet, None, None, Some(&parent_key_id), true)?; // Only select outputs that are actually involved in an outstanding transaction - let unspents: Vec = match update_all { - false => unspents - .into_iter() - .filter(|x| match x.tx_log_entry.as_ref() { - Some(t) => tx_entries.iter().any(|te| te.id == *t), - None => true, - }) - .collect(), - true => unspents, + let unspents: Vec<_> = if update_all { + wallet.unspent_outputs(Some(parent_key_id), None).collect() + } else { + wallet.outstanding_outputs(Some(parent_key_id)).collect() }; for out in unspents { @@ -375,12 +327,8 @@ where return Ok(()); } let mut ids_to_del = vec![]; - for out in wallet.iter() { - if out.status == OutputStatus::Unconfirmed - && out.height > 0 - && out.height < height - 50 - && out.is_coinbase - { + for out in wallet.unconfirmed_outputs(None) { + if out.height > 0 && out.height < height - 50 && out.is_coinbase { ids_to_del.push(out.key_id.clone()) } } @@ -405,9 +353,7 @@ where K: Keychain + 'a, { let current_height = wallet.last_confirmed_height()?; - let outputs = wallet - .iter() - .filter(|out| out.root_key_id == *parent_key_id); + let outputs = wallet.iter(Some(parent_key_id), None); let mut unspent_total = 0; let mut immature_total = 0; diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 551668485..5a5a68d62 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -180,7 +180,11 @@ where fn parent_key_id(&mut self) -> Identifier; /// Iterate over all output data stored by the backend - fn iter<'a>(&'a self) -> Box + 'a>; + fn iter<'a>( + &'a self, + parent_key_id: Option<&Identifier>, + tx_id: Option, + ) -> Box + 'a>; /// Get output data by id fn get(&self, id: &Identifier, mmr_index: &Option) -> Result; @@ -196,14 +200,20 @@ where participant_id: usize, ) -> Result; - /// Iterate over all output data stored by the backend - fn tx_log_iter<'a>(&'a self) -> Box + 'a>; + /// Iterate over all transaction logs stored by the backend + fn tx_log_iter<'a>( + &'a self, + parent_key_id: Option<&Identifier>, + tx_id: Option, + tx_slate_id: Option, + outstanding_only: bool, + ) -> Box + 'a>; /// Iterate over all stored account paths fn acct_path_iter<'a>(&'a self) -> Box + 'a>; /// Gets an account path for a given label - fn get_acct_path(&self, label: String) -> Result, Error>; + fn get_acct_path(&self, label: &str) -> Result, Error>; /// Stores a transaction fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error>; @@ -234,6 +244,71 @@ where /// Flag whether the wallet needs a full UTXO scan on next update attempt fn init_status(&mut self) -> Result; + + /// Iterate over all outputs that are not marked as spent + fn unspent_outputs<'a>( + &'a self, + parent_key_id: Option<&Identifier>, + tx_id: Option, + ) -> Box + 'a> { + Box::new( + self.iter(parent_key_id, tx_id) + .filter(|x| x.status != OutputStatus::Spent), + ) + } + + /// Iterate over all outputs that are marked as unconfirmed + fn unconfirmed_outputs<'a>( + &'a self, + parent_key_id: Option<&Identifier>, + ) -> Box + 'a> { + Box::new( + self.iter(parent_key_id, None) + .filter(|x| x.status == OutputStatus::Unconfirmed), + ) + } + + /// Iterate over all outputs that are involved in an outstanding transaction + fn outstanding_outputs<'a>( + &'a self, + parent_key_id: Option<&Identifier>, + ) -> Box + 'a> { + let tx_entries: Vec<_> = self + .tx_log_iter(parent_key_id, None, None, true) + .map(|t| t.id) + .collect(); + + Box::new( + self.iter(parent_key_id, None) + .filter(|x| x.status != OutputStatus::Spent) + .filter(move |x| { + x.tx_log_entry + .as_ref() + .map(|t| tx_entries.contains(t)) + .unwrap_or(true) + }), + ) + } + + /// Iterate over all outputs that are considered eligible for spending + fn eligible_outputs<'a>( + &'a self, + parent_key_id: &Identifier, + current_height: u64, + minimum_confirmations: u64, + ) -> Box + 'a> { + Box::new( + self.iter(Some(parent_key_id), None) + .filter(move |x| x.eligible_to_spend(current_height, minimum_confirmations)), + ) + } + + /// Find account path with highest k in m/k/0 + fn highest_acct_path(&self) -> Option { + self.acct_path_iter().max_by(|a, b| { + ::from(a.path.to_path().path[0]).cmp(&::from(b.path.to_path().path[0])) + }) + } } /// Batch trait to update the output data backend atomically. Trying to use a