Skip to content

Commit 8f82573

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 b5a1835 commit 8f82573

File tree

5 files changed

+118
-54
lines changed

5 files changed

+118
-54
lines changed

lightning/src/events/bump_transaction/mod.rs

Lines changed: 64 additions & 31 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;
@@ -340,20 +341,33 @@ impl Utxo {
340341
}
341342
}
342343

344+
/// An unspent transaction output with at least one confirmation.
345+
pub type ConfirmedUtxo = FundingTxInput;
346+
343347
/// The result of a successful coin selection attempt for a transaction requiring additional UTXOs
344348
/// to cover its fees.
345349
#[derive(Clone, Debug)]
346350
pub struct CoinSelection {
347351
/// The set of UTXOs (with at least 1 confirmation) to spend and use within a transaction
348352
/// requiring additional fees.
349-
pub confirmed_utxos: Vec<Utxo>,
353+
pub confirmed_utxos: Vec<ConfirmedUtxo>,
350354
/// An additional output tracking whether any change remained after coin selection. This output
351355
/// should always have a value above dust for its given `script_pubkey`. It should not be
352356
/// spent until the transaction it belongs to confirms to ensure mempool descendant limits are
353357
/// not met. This implies no other party should be able to spend it except us.
354358
pub change_output: Option<TxOut>,
355359
}
356360

361+
impl CoinSelection {
362+
fn satisfaction_weight(&self) -> u64 {
363+
self.confirmed_utxos.iter().map(|ConfirmedUtxo { utxo, .. }| utxo.satisfaction_weight).sum()
364+
}
365+
366+
fn amount(&self) -> Amount {
367+
self.confirmed_utxos.iter().map(|ConfirmedUtxo { utxo, .. }| utxo.output.value).sum()
368+
}
369+
}
370+
357371
/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
358372
/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
359373
/// which most wallets should be able to satisfy. Otherwise, consider implementing [`WalletSource`],
@@ -424,11 +438,18 @@ pub trait WalletSource {
424438
fn list_confirmed_utxos<'a>(
425439
&'a self,
426440
) -> impl Future<Output = Result<Vec<Utxo>, ()>> + MaybeSend + 'a;
441+
442+
/// Returns the previous transaction containing the UTXO.
443+
fn get_prevtx<'a>(
444+
&'a self, utxo: &Utxo,
445+
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a;
446+
427447
/// Returns a script to use for change above dust resulting from a successful coin selection
428448
/// attempt.
429449
fn get_change_script<'a>(
430450
&'a self,
431451
) -> impl Future<Output = Result<ScriptBuf, ()>> + MaybeSend + 'a;
452+
432453
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
433454
/// the transaction known to the wallet (i.e., any provided via
434455
/// [`WalletSource::list_confirmed_utxos`]).
@@ -616,10 +637,13 @@ where
616637
Some(TxOut { script_pubkey: change_script, value: change_output_amount })
617638
};
618639

619-
Ok(CoinSelection {
620-
confirmed_utxos: selected_utxos.into_iter().map(|(utxo, _)| utxo).collect(),
621-
change_output,
622-
})
640+
let mut confirmed_utxos = Vec::with_capacity(selected_utxos.len());
641+
for (utxo, _) in selected_utxos {
642+
let prevtx = self.source.get_prevtx(&utxo).await?;
643+
confirmed_utxos.push(ConfirmedUtxo { utxo, prevtx });
644+
}
645+
646+
Ok(CoinSelection { confirmed_utxos, change_output })
623647
}
624648
}
625649

@@ -730,7 +754,7 @@ where
730754

731755
/// Updates a transaction with the result of a successful coin selection attempt.
732756
fn process_coin_selection(&self, tx: &mut Transaction, coin_selection: &CoinSelection) {
733-
for utxo in coin_selection.confirmed_utxos.iter() {
757+
for ConfirmedUtxo { utxo, .. } in coin_selection.confirmed_utxos.iter() {
734758
tx.input.push(TxIn {
735759
previous_output: utxo.outpoint,
736760
script_sig: ScriptBuf::new(),
@@ -852,12 +876,10 @@ where
852876
output: vec![],
853877
};
854878

855-
let input_satisfaction_weight: u64 =
856-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum();
879+
let input_satisfaction_weight = coin_selection.satisfaction_weight();
857880
let total_satisfaction_weight =
858881
anchor_input_witness_weight + EMPTY_SCRIPT_SIG_WEIGHT + input_satisfaction_weight;
859-
let total_input_amount = must_spend_amount
860-
+ coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum();
882+
let total_input_amount = must_spend_amount + coin_selection.amount();
861883

862884
self.process_coin_selection(&mut anchor_tx, &coin_selection);
863885
let anchor_txid = anchor_tx.compute_txid();
@@ -872,10 +894,10 @@ where
872894
let index = idx + 1;
873895
debug_assert_eq!(
874896
anchor_psbt.unsigned_tx.input[index].previous_output,
875-
utxo.outpoint
897+
utxo.outpoint()
876898
);
877-
if utxo.output.script_pubkey.is_witness_program() {
878-
anchor_psbt.inputs[index].witness_utxo = Some(utxo.output);
899+
if utxo.output().script_pubkey.is_witness_program() {
900+
anchor_psbt.inputs[index].witness_utxo = Some(utxo.into_output());
879901
}
880902
}
881903

@@ -1107,13 +1129,11 @@ where
11071129
utxo_id = claim_id.step_with_bytes(&broadcasted_htlcs.to_be_bytes());
11081130

11091131
#[cfg(debug_assertions)]
1110-
let input_satisfaction_weight: u64 =
1111-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum();
1132+
let input_satisfaction_weight = coin_selection.satisfaction_weight();
11121133
#[cfg(debug_assertions)]
11131134
let total_satisfaction_weight = must_spend_satisfaction_weight + input_satisfaction_weight;
11141135
#[cfg(debug_assertions)]
1115-
let input_value: u64 =
1116-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value.to_sat()).sum();
1136+
let input_value = coin_selection.amount().to_sat();
11171137
#[cfg(debug_assertions)]
11181138
let total_input_amount = must_spend_amount + input_value;
11191139

@@ -1134,9 +1154,12 @@ where
11341154
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
11351155
// offset to skip the htlc inputs
11361156
let index = idx + selected_htlcs.len();
1137-
debug_assert_eq!(htlc_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint);
1138-
if utxo.output.script_pubkey.is_witness_program() {
1139-
htlc_psbt.inputs[index].witness_utxo = Some(utxo.output);
1157+
debug_assert_eq!(
1158+
htlc_psbt.unsigned_tx.input[index].previous_output,
1159+
utxo.outpoint()
1160+
);
1161+
if utxo.output().script_pubkey.is_witness_program() {
1162+
htlc_psbt.inputs[index].witness_utxo = Some(utxo.into_output());
11401163
}
11411164
}
11421165

@@ -1280,9 +1303,8 @@ mod tests {
12801303
use crate::util::ser::Readable;
12811304
use crate::util::test_utils::{TestBroadcaster, TestLogger};
12821305

1283-
use bitcoin::hashes::Hash;
12841306
use bitcoin::hex::FromHex;
1285-
use bitcoin::{Network, ScriptBuf, Transaction, Txid};
1307+
use bitcoin::{Network, ScriptBuf, Transaction};
12861308

12871309
struct TestCoinSelectionSource {
12881310
// (commitment + anchor value, commitment + input weight, target feerate, result)
@@ -1302,8 +1324,12 @@ mod tests {
13021324
Ok(res)
13031325
}
13041326
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
1305-
// FIXME: Why does handle_channel_close override the witness?
1306-
Ok(psbt.unsigned_tx)
1327+
let mut tx = psbt.unsigned_tx;
1328+
// Channel output, add a realistic size witness to make the assertions happy
1329+
//
1330+
// FIXME: This doesn't seem to be needed since handle_channel_close overrides it
1331+
tx.input.first_mut().unwrap().witness = Witness::from_slice(&[vec![42; 162]]);
1332+
Ok(tx)
13071333
}
13081334
}
13091335

@@ -1339,6 +1365,13 @@ mod tests {
13391365
.weight()
13401366
.to_wu();
13411367

1368+
let prevtx = Transaction {
1369+
version: Version::TWO,
1370+
lock_time: LockTime::ZERO,
1371+
input: vec![],
1372+
output: vec![TxOut { value: Amount::from_sat(200), script_pubkey: ScriptBuf::new() }],
1373+
};
1374+
13421375
let broadcaster = TestBroadcaster::new(Network::Testnet);
13431376
let source = TestCoinSelectionSource {
13441377
expected_selects: Mutex::new(vec![
@@ -1353,14 +1386,14 @@ mod tests {
13531386
commitment_and_anchor_fee,
13541387
868,
13551388
CoinSelection {
1356-
confirmed_utxos: vec![Utxo {
1357-
outpoint: OutPoint { txid: Txid::from_byte_array([44; 32]), vout: 0 },
1358-
output: TxOut {
1359-
value: Amount::from_sat(200),
1360-
script_pubkey: ScriptBuf::new(),
1389+
confirmed_utxos: vec![ConfirmedUtxo {
1390+
utxo: Utxo {
1391+
outpoint: OutPoint { txid: prevtx.compute_txid(), vout: 0 },
1392+
output: prevtx.output[0].clone(),
1393+
satisfaction_weight: 5, // Just the script_sig and witness lengths
1394+
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
13611395
},
1362-
satisfaction_weight: 5, // Just the script_sig and witness lengths
1363-
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
1396+
prevtx,
13641397
}],
13651398
change_output: None,
13661399
},

lightning/src/events/bump_transaction/sync.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
42+
fn get_prevtx(&self, utxo: &Utxo) -> 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,14 @@ where
7984
async move { utxos }
8085
}
8186

87+
/// Returns the previous transaction containing the UTXO.
88+
fn get_prevtx<'a>(
89+
&'a self, utxo: &Utxo,
90+
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
91+
let prevtx = self.0.get_prevtx(utxo);
92+
Box::pin(async move { prevtx })
93+
}
94+
8295
fn get_change_script<'a>(
8396
&'a self,
8497
) -> 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
@@ -395,8 +395,7 @@ fn do_connect_block_without_consistency_checks<'a, 'b, 'c, 'd>(
395395
let wallet_script = node.wallet_source.get_change_script().unwrap();
396396
for (idx, output) in tx.output.iter().enumerate() {
397397
if output.script_pubkey == wallet_script {
398-
let outpoint = bitcoin::OutPoint { txid: tx.compute_txid(), vout: idx as u32 };
399-
node.wallet_source.add_utxo(outpoint, output.value);
398+
node.wallet_source.add_utxo(tx.clone(), idx as u32);
400399
}
401400
}
402401
}

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, {
@@ -225,6 +226,11 @@ impl FundingTxInput {
225226
self.utxo.outpoint
226227
}
227228

229+
/// The unspent output.
230+
pub fn output(&self) -> &TxOut {
231+
&self.utxo.output
232+
}
233+
228234
/// The sequence number to use in the [`TxIn`].
229235
///
230236
/// [`TxIn`]: bitcoin::TxIn
@@ -239,8 +245,13 @@ impl FundingTxInput {
239245
self.utxo.sequence = sequence;
240246
}
241247

242-
/// Converts the [`FundingTxInput`] into a [`Utxo`] for coin selection.
248+
/// Converts the [`FundingTxInput`] into a [`Utxo`].
243249
pub fn into_utxo(self) -> Utxo {
244250
self.utxo
245251
}
252+
253+
/// Converts the [`FundingTxInput`] into a [`TxOut`].
254+
pub fn into_output(self) -> TxOut {
255+
self.utxo.output
256+
}
246257
}

lightning/src/util/test_utils.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::chain::channelmonitor::{
2222
use crate::chain::transaction::OutPoint;
2323
use crate::chain::WatchedOutput;
2424
use crate::events::bump_transaction::sync::WalletSourceSync;
25-
use crate::events::bump_transaction::Utxo;
25+
use crate::events::bump_transaction::{ConfirmedUtxo, Utxo};
2626
#[cfg(any(test, feature = "_externalize_tests"))]
2727
use crate::ln::chan_utils::CommitmentTransaction;
2828
use crate::ln::channel_state::ChannelDetails;
@@ -2211,7 +2211,7 @@ impl Drop for TestScorer {
22112211

22122212
pub struct TestWalletSource {
22132213
secret_key: SecretKey,
2214-
utxos: Mutex<Vec<Utxo>>,
2214+
utxos: Mutex<Vec<ConfirmedUtxo>>,
22152215
secp: Secp256k1<bitcoin::secp256k1::All>,
22162216
}
22172217

@@ -2220,21 +2220,13 @@ impl TestWalletSource {
22202220
Self { secret_key, utxos: Mutex::new(Vec::new()), secp: Secp256k1::new() }
22212221
}
22222222

2223-
pub fn add_utxo(&self, outpoint: bitcoin::OutPoint, value: Amount) -> TxOut {
2224-
let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp));
2225-
let utxo = Utxo::new_v0_p2wpkh(outpoint, value, &public_key.wpubkey_hash().unwrap());
2226-
self.utxos.lock().unwrap().push(utxo.clone());
2227-
utxo.output
2228-
}
2229-
2230-
pub fn add_custom_utxo(&self, utxo: Utxo) -> TxOut {
2231-
let output = utxo.output.clone();
2223+
pub fn add_utxo(&self, prevtx: Transaction, vout: u32) {
2224+
let utxo = ConfirmedUtxo::new_p2wpkh(prevtx, vout).unwrap();
22322225
self.utxos.lock().unwrap().push(utxo);
2233-
output
22342226
}
22352227

22362228
pub fn remove_utxo(&self, outpoint: bitcoin::OutPoint) {
2237-
self.utxos.lock().unwrap().retain(|utxo| utxo.outpoint != outpoint);
2229+
self.utxos.lock().unwrap().retain(|utxo| utxo.outpoint() != outpoint);
22382230
}
22392231

22402232
pub fn clear_utxos(&self) {
@@ -2247,12 +2239,12 @@ impl TestWalletSource {
22472239
let utxos = self.utxos.lock().unwrap();
22482240
for i in 0..tx.input.len() {
22492241
if let Some(utxo) =
2250-
utxos.iter().find(|utxo| utxo.outpoint == tx.input[i].previous_output)
2242+
utxos.iter().find(|utxo| utxo.outpoint() == tx.input[i].previous_output)
22512243
{
22522244
let sighash = SighashCache::new(&tx).p2wpkh_signature_hash(
22532245
i,
2254-
&utxo.output.script_pubkey,
2255-
utxo.output.value,
2246+
&utxo.output().script_pubkey,
2247+
utxo.output().value,
22562248
EcdsaSighashType::All,
22572249
)?;
22582250
#[cfg(not(feature = "grind_signatures"))]
@@ -2277,7 +2269,23 @@ impl TestWalletSource {
22772269

22782270
impl WalletSourceSync for TestWalletSource {
22792271
fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()> {
2280-
Ok(self.utxos.lock().unwrap().clone())
2272+
Ok(self
2273+
.utxos
2274+
.lock()
2275+
.unwrap()
2276+
.iter()
2277+
.map(|ConfirmedUtxo { utxo, .. }| utxo.clone())
2278+
.collect())
2279+
}
2280+
2281+
fn get_prevtx(&self, utxo: &Utxo) -> Result<Transaction, ()> {
2282+
self.utxos
2283+
.lock()
2284+
.unwrap()
2285+
.iter()
2286+
.find(|confirmed_utxo| confirmed_utxo.utxo == *utxo)
2287+
.map(|ConfirmedUtxo { prevtx, .. }| prevtx.clone())
2288+
.ok_or(())
22812289
}
22822290

22832291
fn get_change_script(&self) -> Result<ScriptBuf, ()> {

0 commit comments

Comments
 (0)