Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: wallet coin selection respects max_block_weight #2546

Merged
merged 3 commits into from
Feb 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions servers/tests/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,6 @@ impl LocalServerContainer {
let client_n = HTTPNodeClient::new(&config.check_node_api_http_addr, None);
let client_w = HTTPWalletCommAdapter::new();

let max_outputs = 500;
let change_outputs = 1;

let mut wallet = LMDBBackend::new(config.clone(), "", client_n)
Expand All @@ -389,7 +388,6 @@ impl LocalServerContainer {
None,
amount,
minimum_confirmations,
max_outputs,
change_outputs,
selection_strategy == "all",
None,
Expand Down
1 change: 0 additions & 1 deletion servers/tests/simulnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,6 @@ fn replicate_tx_fluff_failure() {
let (mut slate, lock_fn) = api.initiate_tx(
None, amount, // amount
2, // minimum confirmations
500, // max outputs
1000, // num change outputs
true, // select all outputs
None,
Expand Down
4 changes: 0 additions & 4 deletions src/bin/cmd/wallet_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,6 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
// fluff
let fluff = args.is_present("fluff");

// max_outputs
let max_outputs = 500;

Ok(command::SendArgs {
amount: amount,
message: message,
Expand All @@ -402,7 +399,6 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
dest: dest.to_owned(),
change_outputs: change_outputs,
fluff: fluff,
max_outputs: max_outputs,
})
}

Expand Down
3 changes: 0 additions & 3 deletions wallet/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ pub struct SendArgs {
pub dest: String,
pub change_outputs: usize,
pub fluff: bool,
pub max_outputs: usize,
}

pub fn send(
Expand All @@ -223,7 +222,6 @@ pub fn send(
None,
args.amount,
args.minimum_confirmations,
args.max_outputs,
args.change_outputs,
strategy == "all",
)
Expand All @@ -237,7 +235,6 @@ pub fn send(
None,
args.amount,
args.minimum_confirmations,
args.max_outputs,
args.change_outputs,
args.selection_strategy == "all",
args.message.clone(),
Expand Down
1 change: 0 additions & 1 deletion wallet/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,6 @@ where
None,
args.amount,
args.minimum_confirmations,
args.max_outputs,
args.num_change_outputs,
args.selection_strategy_is_use_all,
args.message,
Expand Down
19 changes: 2 additions & 17 deletions wallet/src/libwallet/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,15 +535,10 @@ where
/// * `amount` - The amount to send, in nanogrins. (`1 G = 1_000_000_000nG`)
/// * `minimum_confirmations` - The minimum number of confirmations an output
/// should have in order to be included in the transaction.
/// * `max_outputs` - By default, the wallet selects as many inputs as possible in a
/// transaction, to reduce the Output set and the fees. The wallet will attempt to spend
/// include up to `max_outputs` in a transaction, however if this is not enough to cover
/// the whole amount, the wallet will include more outputs. This parameter should be considered
/// a soft limit.
/// * `num_change_outputs` - The target number of change outputs to create in the transaction.
/// The actual number created will be `num_change_outputs` + whatever remainder is needed.
/// * `selection_strategy_is_use_all` - If `true`, attempt to use up as many outputs as
/// possible to create the transaction, up the 'soft limit' of `max_outputs`. This helps
/// possible to create the transaction. This helps
/// to reduce the size of the UTXO set and the amount of data stored in the wallet, and
/// minimizes fees. This will generally result in many inputs and a large change output(s),
/// usually much larger than the amount being sent. If `false`, the transaction will include
Expand Down Expand Up @@ -600,7 +595,6 @@ where
/// None,
/// amount, // amount
/// 10, // minimum confirmations
/// 500, // max outputs
/// 1, // num change outputs
/// true, // select all outputs
/// Some("Have some Grins. Love, Yeastplume".to_owned()),
Expand All @@ -619,7 +613,6 @@ where
src_acct_name: Option<&str>,
amount: u64,
minimum_confirmations: u64,
max_outputs: usize,
num_change_outputs: usize,
selection_strategy_is_use_all: bool,
message: Option<String>,
Expand Down Expand Up @@ -651,7 +644,6 @@ where
&mut *w,
&mut slate,
minimum_confirmations,
max_outputs,
num_change_outputs,
selection_strategy_is_use_all,
&parent_key_id,
Expand Down Expand Up @@ -681,15 +673,10 @@ where
/// * `amount` - The amount to send, in nanogrins. (`1 G = 1_000_000_000nG`)
/// * `minimum_confirmations` - The minimum number of confirmations an output
/// should have in order to be included in the transaction.
/// * `max_outputs` - By default, the wallet selects as many inputs as possible in a
/// transaction, to reduce the Output set and the fees. The wallet will attempt to spend
/// include up to `max_outputs` in a transaction, however if this is not enough to cover
/// the whole amount, the wallet will include more outputs. This parameter should be considered
/// a soft limit.
/// * `num_change_outputs` - The target number of change outputs to create in the transaction.
/// The actual number created will be `num_change_outputs` + whatever remainder is needed.
/// * `selection_strategy_is_use_all` - If `true`, attempt to use up as many outputs as
/// possible to create the transaction, up the 'soft limit' of `max_outputs`. This helps
/// possible to estimate the transaction. This helps
/// to reduce the size of the UTXO set and the amount of data stored in the wallet, and
/// minimizes fees. This will generally result in many inputs and a large change output(s),
/// usually much larger than the amount being sent. If `false`, the transaction will include
Expand All @@ -706,7 +693,6 @@ where
src_acct_name: Option<&str>,
amount: u64,
minimum_confirmations: u64,
max_outputs: usize,
num_change_outputs: usize,
selection_strategy_is_use_all: bool,
) -> Result<
Expand All @@ -732,7 +718,6 @@ where
&mut *w,
amount,
minimum_confirmations,
max_outputs,
num_change_outputs,
selection_strategy_is_use_all,
&parent_key_id,
Expand Down
51 changes: 19 additions & 32 deletions wallet/src/libwallet/internal/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

use crate::core::core::{amount_to_hr_string, Transaction};
use crate::core::libtx::{build, tx_fee};
use crate::core::{consensus, global};
use crate::keychain::{Identifier, Keychain};
use crate::libwallet::error::{Error, ErrorKind};
use crate::libwallet::internal::keys;
use crate::libwallet::slate::Slate;
use crate::libwallet::types::*;
use std::cmp::min;
use std::collections::HashMap;
use std::marker::PhantomData;

Expand All @@ -33,7 +35,6 @@ pub fn build_send_tx<T: ?Sized, C, K>(
wallet: &mut T,
slate: &mut Slate,
minimum_confirmations: u64,
max_outputs: usize,
change_outputs: usize,
selection_strategy_is_use_all: bool,
parent_key_id: Identifier,
Expand All @@ -49,7 +50,6 @@ where
slate.height,
minimum_confirmations,
slate.lock_height,
max_outputs,
change_outputs,
selection_strategy_is_use_all,
&parent_key_id,
Expand Down Expand Up @@ -220,6 +220,16 @@ where
Ok((key_id, context, Box::new(wallet_add_fn)))
}

/// Calculate maximal amount of inputs in transaction given amount of outputs
fn calculate_max_inputs_in_block(num_outputs: usize) -> usize {
let coinbase_weight = consensus::BLOCK_OUTPUT_WEIGHT + consensus::BLOCK_KERNEL_WEIGHT;
global::max_block_weight().saturating_sub(
coinbase_weight
+ consensus::BLOCK_OUTPUT_WEIGHT.saturating_mul(num_outputs)
+ consensus::BLOCK_KERNEL_WEIGHT,
) / consensus::BLOCK_INPUT_WEIGHT
}

/// Builds a transaction to send to someone from the HD seed associated with the
/// wallet and the amount to send. Handles reading through the wallet data file,
/// selecting outputs to spend and building the change.
Expand All @@ -229,7 +239,6 @@ pub fn select_send_tx<T: ?Sized, C, K>(
current_height: u64,
minimum_confirmations: u64,
lock_height: u64,
max_outputs: usize,
change_outputs: usize,
selection_strategy_is_use_all: bool,
parent_key_id: &Identifier,
Expand All @@ -252,7 +261,6 @@ where
amount,
current_height,
minimum_confirmations,
max_outputs,
change_outputs,
selection_strategy_is_use_all,
&parent_key_id,
Expand All @@ -275,7 +283,6 @@ pub fn select_coins_and_fee<T: ?Sized, C, K>(
amount: u64,
current_height: u64,
minimum_confirmations: u64,
max_outputs: usize,
change_outputs: usize,
selection_strategy_is_use_all: bool,
parent_key_id: &Identifier,
Expand All @@ -299,7 +306,7 @@ where
amount,
current_height,
minimum_confirmations,
max_outputs,
calculate_max_inputs_in_block(change_outputs),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies if this is a misunderstanding, I'm not too familiar with this code. But shouldn't you calculate the max number of inputs for change_outputs + 1 to account for the receiver's output?

Copy link
Contributor Author

@i1skn i1skn Feb 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, but first, we need to understand do we need a change. First, we calculate assuming that there will be no change (this gives us also the lowest fee). We check do we have fee+amount on our balance or not and only then we figure out do we need a change output or we can transfer without it.
If we need one - we set:

let num_outputs = change_outputs + 1;

and then select coins using num_outputs
coins = select_coins(
wallet,
amount_with_fee,
current_height,
minimum_confirmations,
calculate_max_inputs_in_block(num_outputs),
selection_strategy_is_use_all,
parent_key_id,
)

At least that's how I get it, probably @yeastplume can verify does this make sense or not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, correct, that's what happens.

selection_strategy_is_use_all,
parent_key_id,
);
Expand Down Expand Up @@ -361,7 +368,7 @@ where
amount_with_fee,
current_height,
minimum_confirmations,
max_outputs,
calculate_max_inputs_in_block(num_outputs),
selection_strategy_is_use_all,
parent_key_id,
)
Expand Down Expand Up @@ -476,40 +483,20 @@ where
})
.collect::<Vec<OutputData>>();

let max_available = eligible.len();
// max_available can not be bigger than max_outputs
let max_available = min(eligible.len(), max_outputs);

// sort eligible outputs by increasing value
eligible.sort_by_key(|out| out.value);

// use a sliding window to identify potential sets of possible outputs to spend
// Case of amount > total amount of max_outputs(500):
// The limit exists because by default, we always select as many inputs as
// possible in a transaction, to reduce both the Output set and the fees.
// But that only makes sense up to a point, hence the limit to avoid being too
// greedy. But if max_outputs(500) is actually not enough to cover the whole
// amount, the wallet should allow going over it to satisfy what the user
// wants to send. So the wallet considers max_outputs more of a soft limit.
if eligible.len() > max_outputs {
for window in eligible.windows(max_outputs) {
if max_available > 0 {
for window in eligible.windows(max_available) {
let windowed_eligibles = window.iter().cloned().collect::<Vec<_>>();
if let Some(outputs) = select_from(amount, select_all, windowed_eligibles) {
return (max_available, outputs);
}
}
// Not exist in any window of which total amount >= amount.
// Then take coins from the smallest one up to the total amount of selected
// coins = the amount.
if let Some(outputs) = select_from(amount, false, eligible.clone()) {
debug!(
"Extending maximum number of outputs. {} outputs selected.",
outputs.len()
);
return (max_available, outputs);
}
} else {
if let Some(outputs) = select_from(amount, select_all, eligible.clone()) {
return (max_available, outputs);
}
}

// we failed to find a suitable set of outputs to spend,
Expand All @@ -518,7 +505,7 @@ where
eligible.reverse();
(
max_available,
eligible.iter().take(max_outputs).cloned().collect(),
eligible.iter().take(max_available).cloned().collect(),
)
}

Expand Down
4 changes: 0 additions & 4 deletions wallet/src/libwallet/internal/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ pub fn estimate_send_tx<T: ?Sized, C, K>(
wallet: &mut T,
amount: u64,
minimum_confirmations: u64,
max_outputs: usize,
num_change_outputs: usize,
selection_strategy_is_use_all: bool,
parent_key_id: &Identifier,
Expand Down Expand Up @@ -80,7 +79,6 @@ where
amount,
current_height,
minimum_confirmations,
max_outputs,
num_change_outputs,
selection_strategy_is_use_all,
parent_key_id,
Expand All @@ -93,7 +91,6 @@ pub fn add_inputs_to_slate<T: ?Sized, C, K>(
wallet: &mut T,
slate: &mut Slate,
minimum_confirmations: u64,
max_outputs: usize,
num_change_outputs: usize,
selection_strategy_is_use_all: bool,
parent_key_id: &Identifier,
Expand All @@ -119,7 +116,6 @@ where
wallet,
slate,
minimum_confirmations,
max_outputs,
num_change_outputs,
selection_strategy_is_use_all,
parent_key_id.clone(),
Expand Down
2 changes: 0 additions & 2 deletions wallet/src/libwallet/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,6 @@ pub struct SendTXArgs {
pub method: String,
/// destination url
pub dest: String,
/// Max number of outputs
pub max_outputs: usize,
/// Number of change outputs to generate
pub num_change_outputs: usize,
/// whether to use all outputs (combine)
Expand Down
1 change: 0 additions & 1 deletion wallet/src/test_framework/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ where
None, // account
amount, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
Expand Down
1 change: 0 additions & 1 deletion wallet/tests/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
let (mut slate, lock_fn) = api.initiate_tx(
None, reward, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
Expand Down
1 change: 0 additions & 1 deletion wallet/tests/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
None,
reward * 2, // amount
cm, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None, // optional message
Expand Down
1 change: 0 additions & 1 deletion wallet/tests/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
Some("mining"),
reward * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
Some(message.to_owned()), // optional message
Expand Down
2 changes: 0 additions & 2 deletions wallet/tests/repost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
Some("mining"),
reward * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
Expand Down Expand Up @@ -200,7 +199,6 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
None,
amount * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
Expand Down
Loading