Skip to content

Commit fc3fa7c

Browse files
committed
Use FundingTxInput instead of Utxo in CoinSelection
In order to reuse CoinSelectionSource for splicing, the previous transaction of each UTXO is needed. Update CoinSelection to use FundingTxInput (renamed to ConfirmedUtxo) so that it is available. This requires adding a method to WalletSource to look up a previous transaction for a UTXO. Otherwise, Wallet's implementation of CoinSelectionSource would need WalletSource to include the previous transactions when listing confirmed UTXOs to select from. But this would be inefficient since only some UTXOs are selected.
1 parent c80afe9 commit fc3fa7c

6 files changed

Lines changed: 129 additions & 57 deletions

File tree

fuzz/src/full_stack.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -668,9 +668,7 @@ pub fn do_test(mut data: &[u8], logger: &Arc<dyn Logger>) {
668668
script_pubkey: wallet.get_change_script().unwrap(),
669669
}],
670670
};
671-
let coinbase_txid = coinbase_tx.compute_txid();
672-
wallet
673-
.add_utxo(bitcoin::OutPoint { txid: coinbase_txid, vout: 0 }, Amount::from_sat(1_000_000));
671+
wallet.add_utxo(coinbase_tx.clone(), 0);
674672

675673
loop {
676674
match get_slice!(1)[0] {

lightning/src/events/bump_transaction/mod.rs

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::ln::chan_utils::{
3030
HTLC_TIMEOUT_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT, HTLC_TIMEOUT_INPUT_P2A_ANCHOR_WITNESS_WEIGHT,
3131
P2WSH_TXOUT_WEIGHT, SEGWIT_MARKER_FLAG_WEIGHT, TRUC_CHILD_MAX_WEIGHT, TRUC_MAX_WEIGHT,
3232
};
33+
use crate::ln::funding::FundingTxInput;
3334
use crate::ln::types::ChannelId;
3435
use crate::prelude::*;
3536
use crate::sign::ecdsa::EcdsaChannelSigner;
@@ -354,20 +355,33 @@ impl Utxo {
354355
}
355356
}
356357

358+
/// An unspent transaction output with at least one confirmation.
359+
pub type ConfirmedUtxo = FundingTxInput;
360+
357361
/// The result of a successful coin selection attempt for a transaction requiring additional UTXOs
358362
/// to cover its fees.
359363
#[derive(Clone, Debug)]
360364
pub struct CoinSelection {
361365
/// The set of UTXOs (with at least 1 confirmation) to spend and use within a transaction
362366
/// requiring additional fees.
363-
pub confirmed_utxos: Vec<Utxo>,
367+
pub confirmed_utxos: Vec<ConfirmedUtxo>,
364368
/// An additional output tracking whether any change remained after coin selection. This output
365369
/// should always have a value above dust for its given `script_pubkey`. It should not be
366370
/// spent until the transaction it belongs to confirms to ensure mempool descendant limits are
367371
/// not met. This implies no other party should be able to spend it except us.
368372
pub change_output: Option<TxOut>,
369373
}
370374

375+
impl CoinSelection {
376+
fn satisfaction_weight(&self) -> u64 {
377+
self.confirmed_utxos.iter().map(|ConfirmedUtxo { utxo, .. }| utxo.satisfaction_weight).sum()
378+
}
379+
380+
fn input_amount(&self) -> Amount {
381+
self.confirmed_utxos.iter().map(|ConfirmedUtxo { utxo, .. }| utxo.output.value).sum()
382+
}
383+
}
384+
371385
/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
372386
/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
373387
/// which most wallets should be able to satisfy. Otherwise, consider implementing [`WalletSource`],
@@ -438,11 +452,18 @@ pub trait WalletSource {
438452
fn list_confirmed_utxos<'a>(
439453
&'a self,
440454
) -> impl Future<Output = Result<Vec<Utxo>, ()>> + MaybeSend + 'a;
455+
456+
/// Returns the previous transaction containing the UTXO referenced by the outpoint.
457+
fn get_prevtx<'a>(
458+
&'a self, outpoint: OutPoint,
459+
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a;
460+
441461
/// Returns a script to use for change above dust resulting from a successful coin selection
442462
/// attempt.
443463
fn get_change_script<'a>(
444464
&'a self,
445465
) -> impl Future<Output = Result<ScriptBuf, ()>> + MaybeSend + 'a;
466+
446467
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
447468
/// the transaction known to the wallet (i.e., any provided via
448469
/// [`WalletSource::list_confirmed_utxos`]).
@@ -628,10 +649,26 @@ where
628649
Some(TxOut { script_pubkey: change_script, value: change_output_amount })
629650
};
630651

631-
Ok(CoinSelection {
632-
confirmed_utxos: selected_utxos.into_iter().map(|(utxo, _)| utxo).collect(),
633-
change_output,
634-
})
652+
let mut confirmed_utxos = Vec::with_capacity(selected_utxos.len());
653+
for (utxo, _) in selected_utxos {
654+
let prevtx = self.source.get_prevtx(utxo.outpoint).await?;
655+
let prevtx_id = prevtx.compute_txid();
656+
if prevtx_id != utxo.outpoint.txid
657+
|| prevtx.output.get(utxo.outpoint.vout as usize).is_none()
658+
{
659+
log_error!(
660+
self.logger,
661+
"Tx {} from wallet source doesn't contain output referenced by outpoint: {}",
662+
prevtx_id,
663+
utxo.outpoint,
664+
);
665+
return Err(());
666+
}
667+
668+
confirmed_utxos.push(ConfirmedUtxo { utxo, prevtx });
669+
}
670+
671+
Ok(CoinSelection { confirmed_utxos, change_output })
635672
}
636673
}
637674

@@ -740,7 +777,7 @@ where
740777

741778
/// Updates a transaction with the result of a successful coin selection attempt.
742779
fn process_coin_selection(&self, tx: &mut Transaction, coin_selection: &CoinSelection) {
743-
for utxo in coin_selection.confirmed_utxos.iter() {
780+
for ConfirmedUtxo { utxo, .. } in coin_selection.confirmed_utxos.iter() {
744781
tx.input.push(TxIn {
745782
previous_output: utxo.outpoint,
746783
script_sig: ScriptBuf::new(),
@@ -865,12 +902,10 @@ where
865902
output: vec![],
866903
};
867904

868-
let input_satisfaction_weight: u64 =
869-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum();
905+
let input_satisfaction_weight = coin_selection.satisfaction_weight();
870906
let total_satisfaction_weight =
871907
anchor_input_witness_weight + EMPTY_SCRIPT_SIG_WEIGHT + input_satisfaction_weight;
872-
let total_input_amount = must_spend_amount
873-
+ coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum();
908+
let total_input_amount = must_spend_amount + coin_selection.input_amount();
874909

875910
self.process_coin_selection(&mut anchor_tx, &coin_selection);
876911
let anchor_txid = anchor_tx.compute_txid();
@@ -885,10 +920,10 @@ where
885920
let index = idx + 1;
886921
debug_assert_eq!(
887922
anchor_psbt.unsigned_tx.input[index].previous_output,
888-
utxo.outpoint
923+
utxo.outpoint()
889924
);
890-
if utxo.output.script_pubkey.is_witness_program() {
891-
anchor_psbt.inputs[index].witness_utxo = Some(utxo.output);
925+
if utxo.output().script_pubkey.is_witness_program() {
926+
anchor_psbt.inputs[index].witness_utxo = Some(utxo.into_output());
892927
}
893928
}
894929

@@ -1127,13 +1162,11 @@ where
11271162
utxo_id = claim_id.step_with_bytes(&broadcasted_htlcs.to_be_bytes());
11281163

11291164
#[cfg(debug_assertions)]
1130-
let input_satisfaction_weight: u64 =
1131-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum();
1165+
let input_satisfaction_weight = coin_selection.satisfaction_weight();
11321166
#[cfg(debug_assertions)]
11331167
let total_satisfaction_weight = must_spend_satisfaction_weight + input_satisfaction_weight;
11341168
#[cfg(debug_assertions)]
1135-
let input_value: u64 =
1136-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value.to_sat()).sum();
1169+
let input_value = coin_selection.input_amount().to_sat();
11371170
#[cfg(debug_assertions)]
11381171
let total_input_amount = must_spend_amount + input_value;
11391172

@@ -1154,9 +1187,12 @@ where
11541187
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
11551188
// offset to skip the htlc inputs
11561189
let index = idx + selected_htlcs.len();
1157-
debug_assert_eq!(htlc_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint);
1158-
if utxo.output.script_pubkey.is_witness_program() {
1159-
htlc_psbt.inputs[index].witness_utxo = Some(utxo.output);
1190+
debug_assert_eq!(
1191+
htlc_psbt.unsigned_tx.input[index].previous_output,
1192+
utxo.outpoint()
1193+
);
1194+
if utxo.output().script_pubkey.is_witness_program() {
1195+
htlc_psbt.inputs[index].witness_utxo = Some(utxo.into_output());
11601196
}
11611197
}
11621198

@@ -1311,10 +1347,9 @@ mod tests {
13111347
use crate::util::ser::Readable;
13121348
use crate::util::test_utils::{TestBroadcaster, TestLogger};
13131349

1314-
use bitcoin::hashes::Hash;
13151350
use bitcoin::hex::FromHex;
13161351
use bitcoin::{
1317-
Network, ScriptBuf, Transaction, Txid, WitnessProgram, WitnessVersion, XOnlyPublicKey,
1352+
Network, ScriptBuf, Transaction, WitnessProgram, WitnessVersion, XOnlyPublicKey,
13181353
};
13191354

13201355
struct TestCoinSelectionSource {
@@ -1335,9 +1370,17 @@ mod tests {
13351370
Ok(res)
13361371
}
13371372
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
1373+
let prevtx_ids: Vec<_> = self
1374+
.expected_selects
1375+
.lock()
1376+
.unwrap()
1377+
.iter()
1378+
.flat_map(|selection| selection.3.confirmed_utxos.iter())
1379+
.map(|utxo| utxo.prevtx.compute_txid())
1380+
.collect();
13381381
let mut tx = psbt.unsigned_tx;
13391382
for input in tx.input.iter_mut() {
1340-
if input.previous_output.txid != Txid::from_byte_array([44; 32]) {
1383+
if prevtx_ids.contains(&input.previous_output.txid) {
13411384
// Channel output, add a realistic size witness to make the assertions happy
13421385
input.witness = Witness::from_slice(&[vec![42; 162]]);
13431386
}
@@ -1378,6 +1421,13 @@ mod tests {
13781421
.weight()
13791422
.to_wu();
13801423

1424+
let prevtx = Transaction {
1425+
version: Version::TWO,
1426+
lock_time: LockTime::ZERO,
1427+
input: vec![],
1428+
output: vec![TxOut { value: Amount::from_sat(200), script_pubkey: ScriptBuf::new() }],
1429+
};
1430+
13811431
let broadcaster = TestBroadcaster::new(Network::Testnet);
13821432
let source = TestCoinSelectionSource {
13831433
expected_selects: Mutex::new(vec![
@@ -1392,14 +1442,14 @@ mod tests {
13921442
commitment_and_anchor_fee,
13931443
868,
13941444
CoinSelection {
1395-
confirmed_utxos: vec![Utxo {
1396-
outpoint: OutPoint { txid: Txid::from_byte_array([44; 32]), vout: 0 },
1397-
output: TxOut {
1398-
value: Amount::from_sat(200),
1399-
script_pubkey: ScriptBuf::new(),
1445+
confirmed_utxos: vec![ConfirmedUtxo {
1446+
utxo: Utxo {
1447+
outpoint: OutPoint { txid: prevtx.compute_txid(), vout: 0 },
1448+
output: prevtx.output[0].clone(),
1449+
satisfaction_weight: 5, // Just the script_sig and witness lengths
1450+
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
14001451
},
1401-
satisfaction_weight: 5, // Just the script_sig and witness lengths
1402-
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
1452+
prevtx,
14031453
}],
14041454
change_output: None,
14051455
},

lightning/src/events/bump_transaction/sync.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::sign::SignerProvider;
2121
use crate::util::async_poll::{dummy_waker, MaybeSend, MaybeSync};
2222
use crate::util::logger::Logger;
2323

24-
use bitcoin::{Psbt, ScriptBuf, Transaction, TxOut};
24+
use bitcoin::{OutPoint, Psbt, ScriptBuf, Transaction, TxOut};
2525

2626
use super::BumpTransactionEvent;
2727
use super::{
@@ -37,9 +37,14 @@ use super::{
3737
pub trait WalletSourceSync {
3838
/// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
3939
fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()>;
40+
41+
/// Returns the previous transaction containing the UTXO referenced by the outpoint.
42+
fn get_prevtx(&self, outpoint: OutPoint) -> Result<Transaction, ()>;
43+
4044
/// Returns a script to use for change above dust resulting from a successful coin selection
4145
/// attempt.
4246
fn get_change_script(&self) -> Result<ScriptBuf, ()>;
47+
4348
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
4449
/// the transaction known to the wallet (i.e., any provided via
4550
/// [`WalletSource::list_confirmed_utxos`]).
@@ -79,6 +84,13 @@ where
7984
async move { utxos }
8085
}
8186

87+
fn get_prevtx<'a>(
88+
&'a self, outpoint: OutPoint,
89+
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
90+
let prevtx = self.0.get_prevtx(outpoint);
91+
Box::pin(async move { prevtx })
92+
}
93+
8294
fn get_change_script<'a>(
8395
&'a self,
8496
) -> impl Future<Output = Result<ScriptBuf, ()>> + MaybeSend + 'a {

lightning/src/ln/functional_test_utils.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,7 @@ fn do_connect_block_without_consistency_checks<'a, 'b, 'c, 'd>(
397397
let wallet_script = node.wallet_source.get_change_script().unwrap();
398398
for (idx, output) in tx.output.iter().enumerate() {
399399
if output.script_pubkey == wallet_script {
400-
let outpoint = bitcoin::OutPoint { txid: tx.compute_txid(), vout: idx as u32 };
401-
node.wallet_source.add_utxo(outpoint, output.value);
400+
node.wallet_source.add_utxo(tx.clone(), idx as u32);
402401
}
403402
}
404403
}

lightning/src/ln/funding.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,17 @@ impl SpliceContribution {
103103
/// establishment protocol or when splicing.
104104
#[derive(Debug, Clone)]
105105
pub struct FundingTxInput {
106-
/// The unspent [`TxOut`] that the input spends.
106+
/// The unspent [`TxOut`] found in [`prevtx`].
107107
///
108108
/// [`TxOut`]: bitcoin::TxOut
109-
pub(super) utxo: Utxo,
109+
/// [`prevtx`]: Self::prevtx
110+
pub(crate) utxo: Utxo,
110111

111112
/// The transaction containing the unspent [`TxOut`] referenced by [`utxo`].
112113
///
113114
/// [`TxOut`]: bitcoin::TxOut
114115
/// [`utxo`]: Self::utxo
115-
pub(super) prevtx: Transaction,
116+
pub(crate) prevtx: Transaction,
116117
}
117118

118119
impl_writeable_tlv_based!(FundingTxInput, {
@@ -237,6 +238,11 @@ impl FundingTxInput {
237238
self.utxo.outpoint
238239
}
239240

241+
/// The unspent output.
242+
pub fn output(&self) -> &TxOut {
243+
&self.utxo.output
244+
}
245+
240246
/// The sequence number to use in the [`TxIn`].
241247
///
242248
/// [`TxIn`]: bitcoin::TxIn
@@ -251,8 +257,13 @@ impl FundingTxInput {
251257
self.utxo.sequence = sequence;
252258
}
253259

254-
/// Converts the [`FundingTxInput`] into a [`Utxo`] for coin selection.
260+
/// Converts the [`FundingTxInput`] into a [`Utxo`].
255261
pub fn into_utxo(self) -> Utxo {
256262
self.utxo
257263
}
264+
265+
/// Converts the [`FundingTxInput`] into a [`TxOut`].
266+
pub fn into_output(self) -> TxOut {
267+
self.utxo.output
268+
}
258269
}

0 commit comments

Comments
 (0)