Move change calculus to coin_select

The former way to compute and create change was inside `create_tx`, just after
performing coin selection.
It blocked the opportunity to have an "ensemble" algorithm to decide between
multiple coin selection algorithms based on a metric, like Waste.

Now, change isn't created inside `coin_select` but the change amount and the
possibility to create change is decided inside the `coin_select` method. In
this way, change is associated with the coin selection algorithm that generated
it, and a method to decide between them can be implemented.
This commit is contained in:
Cesar Alvarez Vallero
2022-06-13 10:49:31 -03:00
parent 3644a452c1
commit 32ae95f463
3 changed files with 228 additions and 46 deletions

View File

@@ -72,6 +72,7 @@ use crate::psbt::PsbtUtils;
use crate::signer::SignerError;
use crate::testutils;
use crate::types::*;
use crate::wallet::coin_selection::Excess::{Change, NoChange};
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
const COINBASE_MATURITY: u32 = 100;
@@ -777,6 +778,15 @@ where
current_height,
)?;
// get drain script
let drain_script = match params.drain_to {
Some(ref drain_recipient) => drain_recipient.clone(),
None => self
.get_internal_address(AddressIndex::New)?
.address
.script_pubkey(),
};
let coin_selection = coin_selection.coin_select(
self.database.borrow().deref(),
required_utxos,
@@ -784,8 +794,10 @@ where
fee_rate,
outgoing,
fee_amount,
&drain_script,
)?;
let mut fee_amount = coin_selection.fee_amount;
let excess = &coin_selection.excess;
tx.input = coin_selection
.selected
@@ -798,26 +810,6 @@ where
})
.collect();
// prepare the drain output
let mut drain_output = {
let script_pubkey = match params.drain_to {
Some(ref drain_recipient) => drain_recipient.clone(),
None => self
.get_internal_address(AddressIndex::New)?
.address
.script_pubkey(),
};
TxOut {
script_pubkey,
value: 0,
}
};
fee_amount += fee_rate.fee_vb(serialize(&drain_output).len());
let drain_val = (coin_selection.selected_amount() - outgoing).saturating_sub(fee_amount);
if tx.output.is_empty() {
// Uh oh, our transaction has no outputs.
// We allow this when:
@@ -827,10 +819,15 @@ where
// Otherwise, we don't know who we should send the funds to, and how much
// we should send!
if params.drain_to.is_some() && (params.drain_wallet || !params.utxos.is_empty()) {
if drain_val.is_dust(&drain_output.script_pubkey) {
if let NoChange {
dust_threshold,
remaining_amount,
change_fee,
} = excess
{
return Err(Error::InsufficientFunds {
needed: drain_output.script_pubkey.dust_value().as_sat(),
available: drain_val,
needed: *dust_threshold,
available: remaining_amount.saturating_sub(*change_fee),
});
}
} else {
@@ -838,15 +835,25 @@ where
}
}
if drain_val.is_dust(&drain_output.script_pubkey) {
fee_amount += drain_val;
} else {
drain_output.value = drain_val;
if self.is_mine(&drain_output.script_pubkey)? {
received += drain_val;
match excess {
NoChange {
remaining_amount, ..
} => fee_amount += remaining_amount,
Change { amount, fee } => {
if self.is_mine(&drain_script)? {
received += amount;
}
fee_amount += fee;
// create drain output
let drain_output = TxOut {
value: *amount,
script_pubkey: drain_script,
};
tx.output.push(drain_output);
}
tx.output.push(drain_output);
}
};
// sort input/outputs according to the chosen algorithm
params.ordering.sort_tx(&mut tx);