Rework wallet coin selection to select a max of 500 outputs (#265)

This commit is contained in:
AntiochP 2017-11-14 18:54:28 -05:00 committed by Ignotus Peverell
parent c2a95637b3
commit 8269bdd873
2 changed files with 52 additions and 51 deletions

View file

@ -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")

View file

@ -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<OutputData> {
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<OutputData> {
// 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::<Vec<OutputData>>();
// 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::<Vec<_>>()
// 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::<Vec<_>>();
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<OutputData> {
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<OutputData>,
) -> Option<Vec<OutputData>> {
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.