Skip to content

Commit 4891c0b

Browse files
committed
f - use select_confirmed_utxos
1 parent 83c0a81 commit 4891c0b

File tree

3 files changed

+108
-31
lines changed

3 files changed

+108
-31
lines changed

src/lib.rs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,7 +1290,6 @@ impl Node {
12901290

12911291
let fee_rate = self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding);
12921292

1293-
// Phase 1: Initiate splice negotiation
12941293
let funding_template = self
12951294
.channel_manager
12961295
.splice_channel(&channel_details.channel_id, &counterparty_node_id, fee_rate)
@@ -1299,22 +1298,17 @@ impl Node {
12991298
Error::ChannelSplicingFailed
13001299
})?;
13011300

1302-
// Phase 2: Coin selection via LdkWallet (wraps our WalletSource)
1303-
let ldk_wallet = LdkWallet::new(Arc::clone(&self.wallet), Arc::clone(&self.logger));
13041301
let contribution = self
13051302
.runtime
13061303
.block_on(
1307-
funding_template.splice_in(Amount::from_sat(splice_amount_sats), &ldk_wallet),
1304+
funding_template
1305+
.splice_in(Amount::from_sat(splice_amount_sats), Arc::clone(&self.wallet)),
13081306
)
13091307
.map_err(|()| {
13101308
log_error!(self.logger, "Failed to splice channel: coin selection failed");
13111309
Error::ChannelSplicingFailed
13121310
})?;
13131311

1314-
// Persist wallet state after coin selection
1315-
self.wallet.persist()?;
1316-
1317-
// Phase 3: Submit contribution
13181312
self.channel_manager
13191313
.funding_contributed(
13201314
&channel_details.channel_id,
@@ -1365,7 +1359,6 @@ impl Node {
13651359

13661360
let fee_rate = self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding);
13671361

1368-
// Phase 1: Initiate splice negotiation
13691362
let funding_template = self
13701363
.channel_manager
13711364
.splice_channel(&channel_details.channel_id, &counterparty_node_id, fee_rate)
@@ -1374,24 +1367,18 @@ impl Node {
13741367
Error::ChannelSplicingFailed
13751368
})?;
13761369

1377-
// Phase 2: Build contribution with splice-out outputs
13781370
let outputs = vec![bitcoin::TxOut {
13791371
value: Amount::from_sat(splice_amount_sats),
13801372
script_pubkey: address.script_pubkey(),
13811373
}];
1382-
let ldk_wallet = LdkWallet::new(Arc::clone(&self.wallet), Arc::clone(&self.logger));
13831374
let contribution = self
13841375
.runtime
1385-
.block_on(funding_template.splice_out(outputs, &ldk_wallet))
1376+
.block_on(funding_template.splice_out(outputs, Arc::clone(&self.wallet)))
13861377
.map_err(|()| {
13871378
log_error!(self.logger, "Failed to splice channel: coin selection failed");
13881379
Error::ChannelSplicingFailed
13891380
})?;
13901381

1391-
// Persist wallet state after coin selection
1392-
self.wallet.persist()?;
1393-
1394-
// Phase 3: Submit contribution
13951382
self.channel_manager
13961383
.funding_contributed(
13971384
&channel_details.channel_id,

src/wallet/mod.rs

Lines changed: 104 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::str::FromStr;
1111
use std::sync::{Arc, Mutex};
1212

1313
use bdk_chain::spk_client::{FullScanRequest, SyncRequest};
14+
use bdk_wallet::descriptor::ExtendedDescriptor;
1415
use bdk_wallet::event::WalletEvent;
1516
#[allow(deprecated)]
1617
use bdk_wallet::SignOptions;
@@ -20,20 +21,23 @@ use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
2021
use bitcoin::blockdata::locktime::absolute::LockTime;
2122
use bitcoin::hashes::Hash;
2223
use bitcoin::key::XOnlyPublicKey;
23-
use bitcoin::psbt::Psbt;
24+
use bitcoin::psbt::{self, Psbt};
2425
use bitcoin::secp256k1::ecdh::SharedSecret;
2526
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
2627
use bitcoin::secp256k1::{All, PublicKey, Scalar, Secp256k1, SecretKey};
2728
use bitcoin::transaction::Sequence;
2829
use bitcoin::{
29-
Address, Amount, FeeRate, OutPoint, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash,
30+
Address, Amount, FeeRate, OutPoint, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, Weight,
3031
WitnessProgram, WitnessVersion,
3132
};
3233
use lightning::chain::chaininterface::BroadcasterInterface;
3334
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
34-
use lightning::chain::{BestBlock, Listen};
35-
use lightning::events::bump_transaction::{Utxo, WalletSource};
35+
use lightning::chain::{BestBlock, ClaimId, Listen};
36+
use lightning::events::bump_transaction::{
37+
CoinSelection, CoinSelectionSource, Input, Utxo, WalletSource,
38+
};
3639
use lightning::ln::channelmanager::PaymentId;
40+
use lightning::ln::funding::FundingTxInput;
3741
use lightning::ln::inbound_payment::ExpandedKey;
3842
use lightning::ln::msgs::UnsignedGossipMessage;
3943
use lightning::ln::script::ShutdownScript;
@@ -444,16 +448,6 @@ impl Wallet {
444448
Ok(())
445449
}
446450

447-
pub(crate) fn persist(&self) -> Result<(), Error> {
448-
let mut locked_wallet = self.inner.lock().unwrap();
449-
let mut locked_persister = self.persister.lock().unwrap();
450-
locked_wallet.persist(&mut locked_persister).map_err(|e| {
451-
log_error!(self.logger, "Failed to persist wallet: {}", e);
452-
Error::PersistenceFailed
453-
})?;
454-
Ok(())
455-
}
456-
457451
pub(crate) fn get_balances(
458452
&self, total_anchor_channels_reserve_sats: u64,
459453
) -> Result<(u64, u64), Error> {
@@ -717,6 +711,82 @@ impl Wallet {
717711
Ok(txid)
718712
}
719713

714+
pub(crate) fn select_confirmed_utxos(
715+
&self, must_spend: Vec<Input>, must_pay_to: &[TxOut], fee_rate: FeeRate,
716+
) -> Result<CoinSelection, ()> {
717+
let mut locked_wallet = self.inner.lock().unwrap();
718+
let mut locked_persister = self.persister.lock().unwrap();
719+
720+
debug_assert!(matches!(
721+
locked_wallet.public_descriptor(KeychainKind::External),
722+
ExtendedDescriptor::Wpkh(_)
723+
));
724+
debug_assert!(matches!(
725+
locked_wallet.public_descriptor(KeychainKind::Internal),
726+
ExtendedDescriptor::Wpkh(_)
727+
));
728+
729+
let mut tx_builder = locked_wallet.build_tx();
730+
tx_builder.only_witness_utxo();
731+
732+
for input in &must_spend {
733+
let psbt_input = psbt::Input {
734+
witness_utxo: Some(input.previous_utxo.clone()),
735+
..Default::default()
736+
};
737+
let weight = Weight::from_wu(input.satisfaction_weight);
738+
tx_builder.add_foreign_utxo(input.outpoint, psbt_input, weight).map_err(|_| ())?;
739+
}
740+
741+
for output in must_pay_to {
742+
tx_builder.add_recipient(output.script_pubkey.clone(), output.value);
743+
}
744+
745+
tx_builder.fee_rate(fee_rate);
746+
tx_builder.exclude_unconfirmed();
747+
748+
let unsigned_tx = tx_builder
749+
.finish()
750+
.map_err(|e| {
751+
log_error!(self.logger, "Failed to select confirmed UTXOs: {}", e);
752+
})?
753+
.unsigned_tx;
754+
755+
let confirmed_utxos = unsigned_tx
756+
.input
757+
.iter()
758+
.filter(|txin| must_spend.iter().all(|input| input.outpoint != txin.previous_output))
759+
.filter_map(|txin| {
760+
locked_wallet
761+
.tx_details(txin.previous_output.txid)
762+
.map(|tx_details| tx_details.tx.deref().clone())
763+
.map(|prevtx| FundingTxInput::new_p2wpkh(prevtx, txin.previous_output.vout))
764+
})
765+
.collect::<Result<Vec<_>, ()>>()?;
766+
767+
if unsigned_tx.output.len() > must_pay_to.len() + 1 {
768+
log_error!(
769+
self.logger,
770+
"Unexpected number of change outputs during coin selection: {}",
771+
unsigned_tx.output.len() - must_pay_to.len(),
772+
);
773+
return Err(());
774+
}
775+
776+
let change_output = unsigned_tx
777+
.output
778+
.into_iter()
779+
.filter(|txout| must_pay_to.iter().all(|output| output != txout))
780+
.next();
781+
782+
locked_wallet.persist(&mut locked_persister).map_err(|e| {
783+
log_error!(self.logger, "Failed to persist wallet: {}", e);
784+
()
785+
})?;
786+
787+
Ok(CoinSelection { confirmed_utxos, change_output })
788+
}
789+
720790
fn list_confirmed_utxos_inner(&self) -> Result<Vec<Utxo>, ()> {
721791
let locked_wallet = self.inner.lock().unwrap();
722792
let mut utxos = Vec::new();
@@ -1079,6 +1149,26 @@ impl WalletSource for Wallet {
10791149
}
10801150
}
10811151

1152+
// Anchor bumping uses LdkWallet for coin selection, which wraps a WalletSource to implement
1153+
// CoinSelectionSource. Splicing uses this implementation of coin selection instead.
1154+
impl CoinSelectionSource for Wallet {
1155+
fn select_confirmed_utxos<'a>(
1156+
&'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
1157+
target_feerate_sat_per_1000_weight: u32, _max_tx_weight: u64,
1158+
) -> impl Future<Output = Result<CoinSelection, ()>> + Send + 'a {
1159+
debug_assert!(claim_id.is_none());
1160+
let fee_rate = FeeRate::from_sat_per_kwu(target_feerate_sat_per_1000_weight as u64);
1161+
async move { self.select_confirmed_utxos(must_spend, must_pay_to, fee_rate) }
1162+
}
1163+
1164+
fn sign_psbt<'a>(
1165+
&'a self, psbt: Psbt,
1166+
) -> impl Future<Output = Result<Transaction, ()>> + Send + 'a {
1167+
debug_assert!(false);
1168+
async move { self.sign_psbt_inner(psbt) }
1169+
}
1170+
}
1171+
10821172
/// Similar to [`KeysManager`], but overrides the destination and shutdown scripts so they are
10831173
/// directly spendable by the BDK wallet.
10841174
pub(crate) struct WalletKeysManager {

tests/integration_tests_rust.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ async fn splice_channel() {
983983
expect_channel_ready_event!(node_a, node_b.node_id());
984984
expect_channel_ready_event!(node_b, node_a.node_id());
985985

986-
let expected_splice_in_fee_sat = 253;
986+
let expected_splice_in_fee_sat = 255;
987987

988988
let payments = node_b.list_payments();
989989
let payment =

0 commit comments

Comments
 (0)