Skip to content

Commit

Permalink
fix mimblewimble#2510: wallet coin selection respects max_block_weight
Browse files Browse the repository at this point in the history
Deprecate "soft" max_outputs limit and introduce "hard" max_outputs
limit based on max_block_weight.
  • Loading branch information
i1skn committed Feb 8, 2019
1 parent 49ca8eb commit 039e965
Show file tree
Hide file tree
Showing 14 changed files with 37 additions and 55 deletions.
4 changes: 0 additions & 4 deletions src/bin/cmd/wallet_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,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 @@ -390,7 +387,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
2 changes: 0 additions & 2 deletions wallet/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ pub struct SendArgs {
pub dest: String,
pub change_outputs: usize,
pub fluff: bool,
pub max_outputs: usize,
}

pub fn send(
Expand All @@ -216,7 +215,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 @@ -324,7 +324,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
2 changes: 0 additions & 2 deletions wallet/src/libwallet/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,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 +650,6 @@ where
&mut *w,
&mut slate,
minimum_confirmations,
max_outputs,
num_change_outputs,
selection_strategy_is_use_all,
&parent_key_id,
Expand Down
49 changes: 19 additions & 30 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 @@ -253,7 +262,7 @@ where
amount,
current_height,
minimum_confirmations,
max_outputs,
calculate_max_inputs_in_block(change_outputs),
selection_strategy_is_use_all,
parent_key_id,
);
Expand Down Expand Up @@ -315,7 +324,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 @@ -439,40 +448,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 @@ -481,7 +470,7 @@ where
eligible.reverse();
(
max_available,
eligible.iter().take(max_outputs).cloned().collect(),
eligible.iter().take(max_available).cloned().collect(),
)
}

Expand Down
2 changes: 0 additions & 2 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 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 @@ -73,7 +72,6 @@ where
wallet,
slate,
minimum_confirmations,
max_outputs,
num_change_outputs,
selection_strategy_is_use_all,
parent_key_id.clone(),
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
4 changes: 0 additions & 4 deletions wallet/tests/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> {
let (slate_i, lock_fn) = sender_api.initiate_tx(
None, amount, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
Expand All @@ -248,7 +247,6 @@ fn setup_restore(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 All @@ -270,7 +268,6 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> {
None,
amount * 3, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
Expand Down Expand Up @@ -298,7 +295,6 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> {
None,
amount * 3, // 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/self_send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ fn self_send_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
21 changes: 18 additions & 3 deletions wallet/tests/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> {
let (slate_i, lock_fn) = sender_api.initiate_tx(
None, amount, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
Expand Down Expand Up @@ -234,7 +233,6 @@ fn basic_transaction_api(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 Expand Up @@ -285,6 +283,24 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> {
Ok(())
})?;

// Initiate transaction with weight more that
// core::global::max_block_weight() should fail
let invalid_amount_of_outputs =
core::global::max_block_weight() / core::consensus::BLOCK_OUTPUT_WEIGHT + 1;
let res = wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
// note this will increment the block count as part of the transaction "posting"
sender_api.initiate_tx(
None,
amount, // amount
2, // minimum confirmations
invalid_amount_of_outputs, // num change outputs
true, // select all outputs
None,
)?;
Ok(())
});
assert!(res.is_err());

// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
Expand Down Expand Up @@ -331,7 +347,6 @@ fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> {
let (slate_i, lock_fn) = sender_api.initiate_tx(
None, amount, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
Expand Down

0 comments on commit 039e965

Please sign in to comment.