diff --git a/src/bin/grin.rs b/src/bin/grin.rs index 12db85d8a..03ff4243b 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -218,8 +218,8 @@ fn main() { .help("Coin/Output selection strategy.") .short("s") .long("selection") - .possible_values(&["default", "smallest"]) - .default_value("default") + .possible_values(&["all", "smallest"]) + .default_value("all") .takes_value(true)) .arg(Arg::with_name("dest") .help("Send the transaction to the provided server") diff --git a/wallet/src/types.rs b/wallet/src/types.rs index 145d5cc32..d87b49f20 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -500,8 +500,8 @@ impl WalletData { } /// Select spendable coins from the wallet. - /// Default strategy is "aggressive". - /// Non-default strategy is "smallest_first". + /// Default strategy is to spend the maximum number of outputs (up to max_outputs). + /// Alternative strategy is to spend smallest outputs first but only as many as necessary. /// When we introduce additional strategies we should pass something other than a bool in. pub fn select( &self, @@ -511,31 +511,9 @@ impl WalletData { minimum_confirmations: u64, default_strategy: bool, ) -> Vec { - if default_strategy { - self.select_aggressive( - root_key_id, - current_height, - minimum_confirmations, - ) - } else { - self.select_smallest_first( - root_key_id, - amount, - current_height, - minimum_confirmations, - ) - } - } + let max_outputs = 500; - // Selects the smallest number of outputs after ordering them by value. - // Reduces "dust" but leaves larger outputs unspent if possible. - fn select_smallest_first( - &self, - root_key_id: keychain::Identifier, - amount: u64, - current_height: u64, - minimum_confirmations: u64, - ) -> Vec { + // first find all eligible outputs based on number of confirmations let mut eligible = self.outputs .values() .filter(|out| { @@ -545,34 +523,57 @@ impl WalletData { .cloned() .collect::>(); + // sort eligible outputs by increasing value eligible.sort_by_key(|out| out.value); - let mut total_amount = 0; - eligible.iter() - .take_while(|out| { - let res = total_amount < amount; - total_amount += out.value; - res - }) - .cloned() - .collect::>() + // use a sliding window to identify potential sets of possible outputs to spend + if eligible.len() > max_outputs { + for window in eligible.windows(max_outputs) { + let eligible = window.iter().cloned().collect::>(); + if let Some(outputs) = self.select_from(amount, default_strategy, eligible) { + return outputs; + } + } + } else { + if let Some(outputs) = self.select_from(amount, default_strategy, eligible.clone()) { + return outputs; + } + } + + // we failed to find a suitable set of outputs to spend, + // so return the largest amount we can so we can provide guidance on what is possible + eligible.reverse(); + eligible.iter().take(max_outputs).cloned().collect() } - // Selects all eligible outputs to spend to reduce UTXO set as much as possible (the default). - fn select_aggressive( + // Select the full list of outputs if we are using the default strategy. + // Otherwise select just enough outputs to cover the desired amount. + fn select_from( &self, - root_key_id: keychain::Identifier, - current_height: u64, - minimum_confirmations: u64, - ) -> Vec { - self.outputs - .values() - .filter(|out| { - out.root_key_id == root_key_id - && out.eligible_to_spend(current_height, minimum_confirmations) - }) - .cloned() - .collect() + amount: u64, + select_all: bool, + outputs: Vec, + ) -> Option> { + let total = outputs.iter().fold(0, |acc, x| acc + x.value); + if total >= amount { + if select_all { + return Some(outputs.iter().cloned().collect()); + } else { + let mut selected_amount = 0; + return Some( + outputs.iter() + .take_while(|out| { + let res = selected_amount < amount; + selected_amount += out.value; + res + }) + .cloned() + .collect() + ); + } + } else { + None + } } /// Next child index when we want to create a new output.