Merge #154: gui: cleanup the "amount left to select" calculation
493dbde666b73097848a5a4c7d10ee1ad7cfeab2 gui: cleanup the "amount left to select" calculation (Antoine Poinsot)
Pull request description:
It don't think it was blatantly wrong, but neither was it entirely correct. And it was seriously lacking comments explaining what was going on for such a tricky calculation.
In order to make sure it's correct, and make this more code more maintainable (and readable to future contributors), clean it up by using a more obviously-correct approach: a transaction template that is filled with inputs depending on the coins that were selected.
I DID NOT TEST IT!
ACKs for top commit:
edouardparis:
ACK 493dbde666b73097848a5a4c7d10ee1ad7cfeab2
Tree-SHA512: b8094326f42ed86a4fcd4e16961a7cb77b07d423215665248dd20db66e2c2eb5500fd7cbdb58d0febae5beb3a40f7b531ec24d1c3e7d7a16a45686ac40a89e9b
This commit is contained in:
commit
6572b2fa4a
@ -5,7 +5,9 @@ use std::sync::Arc;
|
||||
use iced::{Command, Element};
|
||||
use liana::{
|
||||
config::Config as DaemonConfig,
|
||||
miniscript::bitcoin::{util::psbt::Psbt, Address, Amount, Denomination, OutPoint, Script},
|
||||
miniscript::bitcoin::{
|
||||
self, util::psbt::Psbt, Address, Amount, Denomination, OutPoint, Script,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -223,44 +225,58 @@ impl ChooseCoins {
|
||||
}
|
||||
|
||||
fn amount_left_to_select(&mut self, cfg: &DaemonConfig) {
|
||||
let mut tx_size = 0_u64;
|
||||
let mut outgoing_amount = 0_u64;
|
||||
for (address, amount) in &self.recipients {
|
||||
outgoing_amount += amount.to_sat();
|
||||
tx_size += 8 + address.script_pubkey().len() as u64;
|
||||
}
|
||||
|
||||
// change output
|
||||
tx_size += 8 + 34;
|
||||
// overhead
|
||||
tx_size += 11;
|
||||
|
||||
let input_size = cfg
|
||||
.main_descriptor
|
||||
.receive_descriptor()
|
||||
.spender_input_size();
|
||||
|
||||
let mut selected_amount = 0_u64;
|
||||
for (coin, selected) in &self.coins {
|
||||
if *selected {
|
||||
selected_amount += coin.amount.to_sat();
|
||||
tx_size += input_size as u64;
|
||||
// We need the feerate in order to compute the required amount of BTC to
|
||||
// select. Return early if we don't to not do unnecessary computation.
|
||||
let feerate = match self.feerate.value.parse::<u64>() {
|
||||
Ok(f) => f,
|
||||
Err(_) => {
|
||||
self.amount_left_to_select = None;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If feerate is set we can calcul the required amount.
|
||||
if let Ok(feerate) = self.feerate.value.parse::<u64>() {
|
||||
let required_amount = tx_size * feerate + outgoing_amount;
|
||||
// The coins to be included in this transaction.
|
||||
let selected_coins: Vec<_> = self
|
||||
.coins
|
||||
.iter()
|
||||
.filter_map(|(c, selected)| if *selected { Some(c) } else { None })
|
||||
.collect();
|
||||
|
||||
if selected_amount > required_amount {
|
||||
self.amount_left_to_select = Some(Amount::from_sat(0));
|
||||
} else {
|
||||
self.amount_left_to_select =
|
||||
Some(Amount::from_sat(required_amount - selected_amount));
|
||||
}
|
||||
} else {
|
||||
self.amount_left_to_select = None;
|
||||
}
|
||||
// A dummy representation of the transaction that will be computed, for
|
||||
// the purpose of computing its size in order to anticipate the fees needed.
|
||||
// NOTE: we make the conservative estimation a change output will always be
|
||||
// needed.
|
||||
let tx_template = bitcoin::Transaction {
|
||||
version: 2,
|
||||
lock_time: bitcoin::PackedLockTime(0),
|
||||
input: selected_coins
|
||||
.iter()
|
||||
.map(|_| bitcoin::TxIn::default())
|
||||
.collect(),
|
||||
output: self
|
||||
.recipients
|
||||
.iter()
|
||||
.map(|(addr, amount)| bitcoin::TxOut {
|
||||
script_pubkey: addr.script_pubkey(),
|
||||
value: amount.to_sat(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
// nValue size + scriptPubKey CompactSize + OP_0 + PUSH32 + <wit program>
|
||||
const CHANGE_TXO_SIZE: usize = 8 + 1 + 1 + 1 + 32;
|
||||
let satisfaction_vsize = cfg.main_descriptor.change_descriptor().max_sat_weight() / 4;
|
||||
let transaction_size =
|
||||
tx_template.vsize() + satisfaction_vsize * tx_template.input.len() + CHANGE_TXO_SIZE;
|
||||
|
||||
// Now the calculation of the amount left to be selected by the user is a simple
|
||||
// substraction between the value needed by the transaction to be created and the
|
||||
// value that was selected already.
|
||||
let selected_amount = selected_coins.iter().map(|c| c.amount.to_sat()).sum();
|
||||
let output_sum: u64 = tx_template.output.iter().map(|o| o.value).sum();
|
||||
let needed_amount: u64 = transaction_size as u64 * feerate + output_sum;
|
||||
self.amount_left_to_select = Some(Amount::from_sat(
|
||||
needed_amount.saturating_sub(selected_amount),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user