mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 11:31:08 +03:00
add "smallest first" strategy for coin selection (#256)
This commit is contained in:
parent
8f33c7e0fe
commit
04eb400422
3 changed files with 87 additions and 6 deletions
|
@ -209,6 +209,13 @@ fn main() {
|
||||||
.long("min_conf")
|
.long("min_conf")
|
||||||
.default_value("1")
|
.default_value("1")
|
||||||
.takes_value(true))
|
.takes_value(true))
|
||||||
|
.arg(Arg::with_name("selection_strategy")
|
||||||
|
.help("Coin/Output selection strategy.")
|
||||||
|
.short("s")
|
||||||
|
.long("selection")
|
||||||
|
.possible_values(&["default", "smallest"])
|
||||||
|
.default_value("default")
|
||||||
|
.takes_value(true))
|
||||||
.arg(Arg::with_name("dest")
|
.arg(Arg::with_name("dest")
|
||||||
.help("Send the transaction to the provided server")
|
.help("Send the transaction to the provided server")
|
||||||
.short("d")
|
.short("d")
|
||||||
|
@ -397,9 +404,12 @@ fn wallet_command(wallet_args: &ArgMatches) {
|
||||||
.expect("Could not parse amount as a number with optional decimal point.");
|
.expect("Could not parse amount as a number with optional decimal point.");
|
||||||
let minimum_confirmations: u64 = send_args
|
let minimum_confirmations: u64 = send_args
|
||||||
.value_of("minimum_confirmations")
|
.value_of("minimum_confirmations")
|
||||||
.unwrap_or("1")
|
.unwrap()
|
||||||
.parse()
|
.parse()
|
||||||
.expect("Could not parse minimum_confirmations as a whole number.");
|
.expect("Could not parse minimum_confirmations as a whole number.");
|
||||||
|
let selection_strategy = send_args
|
||||||
|
.value_of("selection_strategy")
|
||||||
|
.expect("Selection strategy required");
|
||||||
let mut dest = "stdout";
|
let mut dest = "stdout";
|
||||||
if let Some(d) = send_args.value_of("dest") {
|
if let Some(d) = send_args.value_of("dest") {
|
||||||
dest = d;
|
dest = d;
|
||||||
|
@ -410,6 +420,7 @@ fn wallet_command(wallet_args: &ArgMatches) {
|
||||||
amount,
|
amount,
|
||||||
minimum_confirmations,
|
minimum_confirmations,
|
||||||
dest.to_string(),
|
dest.to_string(),
|
||||||
|
(selection_strategy == "default"),
|
||||||
);
|
);
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {}, //success messaged logged internally
|
Ok(_) => {}, //success messaged logged internally
|
||||||
|
@ -425,7 +436,7 @@ fn wallet_command(wallet_args: &ArgMatches) {
|
||||||
.expect("Could not parse amount as number with optional decimal point.");
|
.expect("Could not parse amount as number with optional decimal point.");
|
||||||
let minimum_confirmations: u64 = send_args
|
let minimum_confirmations: u64 = send_args
|
||||||
.value_of("minimum_confirmations")
|
.value_of("minimum_confirmations")
|
||||||
.unwrap_or("1")
|
.unwrap()
|
||||||
.parse()
|
.parse()
|
||||||
.expect("Could not parse minimum_confirmations as a whole number.");
|
.expect("Could not parse minimum_confirmations as a whole number.");
|
||||||
wallet::issue_burn_tx(&wallet_config, &keychain, amount, minimum_confirmations)
|
wallet::issue_burn_tx(&wallet_config, &keychain, amount, minimum_confirmations)
|
||||||
|
|
|
@ -36,6 +36,7 @@ pub fn issue_send_tx(
|
||||||
amount: u64,
|
amount: u64,
|
||||||
minimum_confirmations: u64,
|
minimum_confirmations: u64,
|
||||||
dest: String,
|
dest: String,
|
||||||
|
selection_strategy: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
checker::refresh_outputs(config, keychain)?;
|
checker::refresh_outputs(config, keychain)?;
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ pub fn issue_send_tx(
|
||||||
current_height,
|
current_height,
|
||||||
minimum_confirmations,
|
minimum_confirmations,
|
||||||
lock_height,
|
lock_height,
|
||||||
|
selection_strategy,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let partial_tx = build_partial_tx(amount, blind_sum, tx);
|
let partial_tx = build_partial_tx(amount, blind_sum, tx);
|
||||||
|
@ -79,12 +81,19 @@ fn build_send_tx(
|
||||||
current_height: u64,
|
current_height: u64,
|
||||||
minimum_confirmations: u64,
|
minimum_confirmations: u64,
|
||||||
lock_height: u64,
|
lock_height: u64,
|
||||||
|
default_strategy: bool,
|
||||||
) -> Result<(Transaction, BlindingFactor), Error> {
|
) -> Result<(Transaction, BlindingFactor), Error> {
|
||||||
let key_id = keychain.clone().root_key_id();
|
let key_id = keychain.clone().root_key_id();
|
||||||
|
|
||||||
// select some spendable coins from the wallet
|
// select some spendable coins from the wallet
|
||||||
let coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
let coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||||
wallet_data.select(key_id.clone(), current_height, minimum_confirmations)
|
wallet_data.select(
|
||||||
|
key_id.clone(),
|
||||||
|
amount,
|
||||||
|
current_height,
|
||||||
|
minimum_confirmations,
|
||||||
|
default_strategy,
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// build transaction skeleton with inputs and change
|
// build transaction skeleton with inputs and change
|
||||||
|
@ -124,9 +133,11 @@ pub fn issue_burn_tx(
|
||||||
|
|
||||||
// select some spendable coins from the wallet
|
// select some spendable coins from the wallet
|
||||||
let coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
let coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||||
wallet_data.select(key_id.clone(), current_height, minimum_confirmations)
|
wallet_data.select(key_id.clone(), amount, current_height, minimum_confirmations, false)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
debug!(LOGGER, "selected some coins - {}", coins.len());
|
||||||
|
|
||||||
let mut parts = inputs_and_change(&coins, config, keychain, key_id, amount)?;
|
let mut parts = inputs_and_change(&coins, config, keychain, key_id, amount)?;
|
||||||
|
|
||||||
// add burn output and fees
|
// add burn output and fees
|
||||||
|
|
|
@ -499,8 +499,67 @@ impl WalletData {
|
||||||
self.outputs.get(&key_id.to_hex())
|
self.outputs.get(&key_id.to_hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select spendable coins from the wallet
|
/// Select spendable coins from the wallet.
|
||||||
|
/// Default strategy is "aggressive".
|
||||||
|
/// Non-default strategy is "smallest_first".
|
||||||
|
/// When we introduce additional strategies we should pass something other than a bool in.
|
||||||
pub fn select(
|
pub fn select(
|
||||||
|
&self,
|
||||||
|
root_key_id: keychain::Identifier,
|
||||||
|
amount: u64,
|
||||||
|
current_height: u64,
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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> {
|
||||||
|
let mut eligible = self.outputs
|
||||||
|
.values()
|
||||||
|
.filter(|out| {
|
||||||
|
out.root_key_id == root_key_id
|
||||||
|
&& out.eligible_to_spend(current_height, minimum_confirmations)
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<OutputData>>();
|
||||||
|
|
||||||
|
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<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selects all eligible outputs to spend to reduce UTXO set as much as possible (the default).
|
||||||
|
fn select_aggressive(
|
||||||
&self,
|
&self,
|
||||||
root_key_id: keychain::Identifier,
|
root_key_id: keychain::Identifier,
|
||||||
current_height: u64,
|
current_height: u64,
|
||||||
|
@ -512,7 +571,7 @@ impl WalletData {
|
||||||
out.root_key_id == root_key_id
|
out.root_key_id == root_key_id
|
||||||
&& out.eligible_to_spend(current_height, minimum_confirmations)
|
&& out.eligible_to_spend(current_height, minimum_confirmations)
|
||||||
})
|
})
|
||||||
.map(|out| out.clone())
|
.cloned()
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue