Skip to content

Commit 02ec3f7

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 6d16896 commit 02ec3f7

4 files changed

Lines changed: 86 additions & 34 deletions

File tree

lightning/src/events/bump_transaction/mod.rs

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::ln::chan_utils::{
2929
HTLC_TIMEOUT_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT, HTLC_TIMEOUT_INPUT_P2A_ANCHOR_WITNESS_WEIGHT,
3030
P2WSH_TXOUT_WEIGHT, SEGWIT_MARKER_FLAG_WEIGHT, TRUC_CHILD_MAX_WEIGHT, TRUC_MAX_WEIGHT,
3131
};
32+
use crate::ln::funding::ConfirmedUtxo;
3233
use crate::ln::types::ChannelId;
3334
use crate::prelude::*;
3435
use crate::sign::ecdsa::EcdsaChannelSigner;
@@ -345,14 +346,24 @@ impl Utxo {
345346
pub struct CoinSelection {
346347
/// The set of UTXOs (with at least 1 confirmation) to spend and use within a transaction
347348
/// requiring additional fees.
348-
pub confirmed_utxos: Vec<Utxo>,
349+
pub confirmed_utxos: Vec<ConfirmedUtxo>,
349350
/// An additional output tracking whether any change remained after coin selection. This output
350351
/// should always have a value above dust for its given `script_pubkey`. It should not be
351352
/// spent until the transaction it belongs to confirms to ensure mempool descendant limits are
352353
/// not met. This implies no other party should be able to spend it except us.
353354
pub change_output: Option<TxOut>,
354355
}
355356

357+
impl CoinSelection {
358+
fn satisfaction_weight(&self) -> u64 {
359+
self.confirmed_utxos.iter().map(|ConfirmedUtxo { utxo, .. }| utxo.satisfaction_weight).sum()
360+
}
361+
362+
fn amount(&self) -> Amount {
363+
self.confirmed_utxos.iter().map(|ConfirmedUtxo { utxo, .. }| utxo.output.value).sum()
364+
}
365+
}
366+
356367
/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
357368
/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
358369
/// which most wallets should be able to satisfy. Otherwise, consider implementing [`WalletSource`],
@@ -419,9 +430,14 @@ pub trait CoinSelectionSource {
419430
pub trait WalletSource {
420431
/// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
421432
fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec<Utxo>, ()>;
433+
434+
/// Returns the previous transaction containing the UTXO.
435+
fn get_prevtx<'a>(&'a self, utxo: &Utxo) -> AsyncResult<'a, Transaction, ()>;
436+
422437
/// Returns a script to use for change above dust resulting from a successful coin selection
423438
/// attempt.
424439
fn get_change_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf, ()>;
440+
425441
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
426442
/// the transaction known to the wallet (i.e., any provided via
427443
/// [`WalletSource::list_confirmed_utxos`]).
@@ -607,10 +623,13 @@ where
607623
Some(TxOut { script_pubkey: change_script, value: change_output_amount })
608624
};
609625

610-
Ok(CoinSelection {
611-
confirmed_utxos: selected_utxos.into_iter().map(|(utxo, _)| utxo).collect(),
612-
change_output,
613-
})
626+
let mut confirmed_utxos = Vec::with_capacity(selected_utxos.len());
627+
for (utxo, _) in selected_utxos {
628+
let prevtx = self.source.get_prevtx(&utxo).await?;
629+
confirmed_utxos.push(ConfirmedUtxo { utxo, prevtx });
630+
}
631+
632+
Ok(CoinSelection { confirmed_utxos, change_output })
614633
}
615634
}
616635

@@ -719,7 +738,7 @@ where
719738

720739
/// Updates a transaction with the result of a successful coin selection attempt.
721740
fn process_coin_selection(&self, tx: &mut Transaction, coin_selection: &CoinSelection) {
722-
for utxo in coin_selection.confirmed_utxos.iter() {
741+
for ConfirmedUtxo { utxo, .. } in coin_selection.confirmed_utxos.iter() {
723742
tx.input.push(TxIn {
724743
previous_output: utxo.outpoint,
725744
script_sig: ScriptBuf::new(),
@@ -841,12 +860,10 @@ where
841860
output: vec![],
842861
};
843862

844-
let input_satisfaction_weight: u64 =
845-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum();
863+
let input_satisfaction_weight = coin_selection.satisfaction_weight();
846864
let total_satisfaction_weight =
847865
anchor_input_witness_weight + EMPTY_SCRIPT_SIG_WEIGHT + input_satisfaction_weight;
848-
let total_input_amount = must_spend_amount
849-
+ coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum();
866+
let total_input_amount = must_spend_amount + coin_selection.amount();
850867

851868
self.process_coin_selection(&mut anchor_tx, &coin_selection);
852869
let anchor_txid = anchor_tx.compute_txid();
@@ -856,7 +873,7 @@ where
856873
// add witness_utxo to anchor input
857874
anchor_psbt.inputs[0].witness_utxo = Some(anchor_descriptor.previous_utxo());
858875
// add witness_utxo to remaining inputs
859-
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
876+
for (idx, ConfirmedUtxo { utxo, .. }) in coin_selection.confirmed_utxos.into_iter().enumerate() {
860877
// add 1 to skip the anchor input
861878
let index = idx + 1;
862879
debug_assert_eq!(
@@ -1096,13 +1113,11 @@ where
10961113
utxo_id = claim_id.step_with_bytes(&broadcasted_htlcs.to_be_bytes());
10971114

10981115
#[cfg(debug_assertions)]
1099-
let input_satisfaction_weight: u64 =
1100-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum();
1116+
let input_satisfaction_weight = coin_selection.satisfaction_weight();
11011117
#[cfg(debug_assertions)]
11021118
let total_satisfaction_weight = must_spend_satisfaction_weight + input_satisfaction_weight;
11031119
#[cfg(debug_assertions)]
1104-
let input_value: u64 =
1105-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value.to_sat()).sum();
1120+
let input_value = coin_selection.amount().to_sat();
11061121
#[cfg(debug_assertions)]
11071122
let total_input_amount = must_spend_amount + input_value;
11081123

@@ -1120,7 +1135,7 @@ where
11201135
}
11211136

11221137
// add witness_utxo to remaining inputs
1123-
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
1138+
for (idx, ConfirmedUtxo { utxo, .. }) in coin_selection.confirmed_utxos.into_iter().enumerate() {
11241139
// offset to skip the htlc inputs
11251140
let index = idx + selected_htlcs.len();
11261141
debug_assert_eq!(htlc_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint);
@@ -1269,9 +1284,8 @@ mod tests {
12691284
use crate::util::ser::Readable;
12701285
use crate::util::test_utils::{TestBroadcaster, TestLogger};
12711286

1272-
use bitcoin::hashes::Hash;
12731287
use bitcoin::hex::FromHex;
1274-
use bitcoin::{Network, ScriptBuf, Transaction, Txid};
1288+
use bitcoin::{Network, ScriptBuf, Transaction};
12751289

12761290
struct TestCoinSelectionSource {
12771291
// (commitment + anchor value, commitment + input weight, target feerate, result)
@@ -1291,8 +1305,12 @@ mod tests {
12911305
Ok(res)
12921306
}
12931307
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
1294-
// FIXME: Why does handle_channel_close override the witness?
1295-
Ok(psbt.unsigned_tx)
1308+
let mut tx = psbt.unsigned_tx;
1309+
// Channel output, add a realistic size witness to make the assertions happy
1310+
//
1311+
// FIXME: This doesn't seem to be needed since handle_channel_close overrides it
1312+
tx.input.first_mut().unwrap().witness = Witness::from_slice(&[vec![42; 162]]);
1313+
Ok(tx)
12961314
}
12971315
}
12981316

@@ -1328,6 +1346,16 @@ mod tests {
13281346
.weight()
13291347
.to_wu();
13301348

1349+
let prevtx = Transaction {
1350+
version: Version::TWO,
1351+
lock_time: LockTime::ZERO,
1352+
input: vec![],
1353+
output: vec![TxOut {
1354+
value: Amount::from_sat(200),
1355+
script_pubkey: ScriptBuf::new()
1356+
}],
1357+
};
1358+
13311359
let broadcaster = TestBroadcaster::new(Network::Testnet);
13321360
let source = TestCoinSelectionSource {
13331361
expected_selects: Mutex::new(vec![
@@ -1342,14 +1370,14 @@ mod tests {
13421370
commitment_and_anchor_fee,
13431371
868,
13441372
CoinSelection {
1345-
confirmed_utxos: vec![Utxo {
1346-
outpoint: OutPoint { txid: Txid::from_byte_array([44; 32]), vout: 0 },
1347-
output: TxOut {
1348-
value: Amount::from_sat(200),
1349-
script_pubkey: ScriptBuf::new(),
1373+
confirmed_utxos: vec![ConfirmedUtxo {
1374+
utxo: Utxo {
1375+
outpoint: OutPoint { txid: prevtx.compute_txid(), vout: 0 },
1376+
output: prevtx.output[0].clone(),
1377+
satisfaction_weight: 5, // Just the script_sig and witness lengths
1378+
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
13501379
},
1351-
satisfaction_weight: 5, // Just the script_sig and witness lengths
1352-
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
1380+
prevtx,
13531381
}],
13541382
change_output: None,
13551383
},

lightning/src/events/bump_transaction/sync.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,14 @@ use super::{
3636
pub trait WalletSourceSync {
3737
/// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
3838
fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()>;
39+
40+
/// Returns the previous transaction containing the UTXO.
41+
fn get_prevtx(&self, utxo: &Utxo) -> Result<Transaction, ()>;
42+
3943
/// Returns a script to use for change above dust resulting from a successful coin selection
4044
/// attempt.
4145
fn get_change_script(&self) -> Result<ScriptBuf, ()>;
46+
4247
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
4348
/// the transaction known to the wallet (i.e., any provided via
4449
/// [`WalletSource::list_confirmed_utxos`]).
@@ -76,6 +81,11 @@ where
7681
Box::pin(async move { utxos })
7782
}
7883

84+
fn get_prevtx<'a>(&'a self, utxo: &Utxo) -> AsyncResult<'a, Transaction, ()> {
85+
let prevtx = self.0.get_prevtx(utxo);
86+
Box::pin(async move { prevtx })
87+
}
88+
7989
fn get_change_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf, ()> {
8090
let script = self.0.get_change_script();
8191
Box::pin(async move { script })

lightning/src/ln/funding.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,22 +99,26 @@ impl SpliceContribution {
9999
}
100100
}
101101

102-
/// An input to contribute to a channel's funding transaction either when using the v2 channel
103-
/// establishment protocol or when splicing.
102+
/// An unspent transaction output with at least one confirmation.
104103
#[derive(Debug, Clone)]
105-
pub struct FundingTxInput {
106-
/// The unspent [`TxOut`] that the input spends.
104+
pub struct ConfirmedUtxo {
105+
/// The unspent [`TxOut`] found in [`prevtx`].
107106
///
108107
/// [`TxOut`]: bitcoin::TxOut
109-
pub(super) utxo: Utxo,
108+
/// [`prevtx`]: Self::prevtx
109+
pub(crate) utxo: Utxo,
110110

111111
/// The transaction containing the unspent [`TxOut`] referenced by [`utxo`].
112112
///
113113
/// [`TxOut`]: bitcoin::TxOut
114114
/// [`utxo`]: Self::utxo
115-
pub(super) prevtx: Transaction,
115+
pub(crate) prevtx: Transaction,
116116
}
117117

118+
/// An input to contribute to a channel's funding transaction either when using the v2 channel
119+
/// establishment protocol or when splicing.
120+
pub type FundingTxInput = ConfirmedUtxo;
121+
118122
impl_writeable_tlv_based!(FundingTxInput, {
119123
(1, utxo, required),
120124
(3, _sequence, (legacy, Sequence, |input: &FundingTxInput| Some(input.utxo.sequence))),

lightning/src/util/test_utils.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ use crate::util::persist::{KVStore, KVStoreSync, MonitorName};
6262
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
6363
use crate::util::test_channel_signer::{EnforcementState, TestChannelSigner};
6464

65+
use bitcoin::absolute::LockTime;
6566
use bitcoin::amount::Amount;
6667
use bitcoin::block::Block;
6768
use bitcoin::constants::genesis_block;
@@ -71,7 +72,7 @@ use bitcoin::hashes::{hex::FromHex, Hash};
7172
use bitcoin::network::Network;
7273
use bitcoin::script::{Builder, Script, ScriptBuf};
7374
use bitcoin::sighash::{EcdsaSighashType, SighashCache};
74-
use bitcoin::transaction::{Transaction, TxOut};
75+
use bitcoin::transaction::{Transaction, TxOut, Version};
7576
use bitcoin::{opcodes, Witness};
7677

7778
use bitcoin::secp256k1::ecdh::SharedSecret;
@@ -2279,6 +2280,15 @@ impl WalletSourceSync for TestWalletSource {
22792280
Ok(self.utxos.lock().unwrap().clone())
22802281
}
22812282

2283+
fn get_prevtx(&self, utxo: &Utxo) -> Result<Transaction, ()> {
2284+
Ok(Transaction {
2285+
version: Version::TWO,
2286+
lock_time: LockTime::ZERO,
2287+
input: vec![],
2288+
output: vec![utxo.output.clone()],
2289+
})
2290+
}
2291+
22822292
fn get_change_script(&self) -> Result<ScriptBuf, ()> {
22832293
let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp));
22842294
Ok(ScriptBuf::new_p2wpkh(&public_key.wpubkey_hash().unwrap()))

0 commit comments

Comments
 (0)