Skip to content

Commit 39a1e62

Browse files
committed
Make ClaimId optional in coin selection
CoinSelectionSource is used for anchor bumping where a ClaimId is passed in to avoid double spending other claims. To re-use this trait for funding a splice, the ClaimId must be optional. And, if None, then any locked UTXOs may be considered ineligible by an implementation.
1 parent edd0c83 commit 39a1e62

File tree

2 files changed

+24
-11
lines changed

2 files changed

+24
-11
lines changed

lightning/src/events/bump_transaction/mod.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -425,9 +425,12 @@ pub trait CoinSelectionSource {
425425
/// which UTXOs to double spend is left to the implementation, but it must strive to keep the
426426
/// set of other claims being double spent to a minimum.
427427
///
428+
/// If `claim_id` is not set, then the selection should be treated as if it were for a unique
429+
/// claim and must NOT be double-spent rather than being kept to a minimum.
430+
///
428431
/// [`ChannelMonitor::rebroadcast_pending_claims`]: crate::chain::channelmonitor::ChannelMonitor::rebroadcast_pending_claims
429432
fn select_confirmed_utxos<'a>(
430-
&'a self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
433+
&'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
431434
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
432435
) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a;
433436
/// Signs and provides the full witness for all inputs within the transaction known to the
@@ -492,7 +495,7 @@ where
492495
// TODO: Do we care about cleaning this up once the UTXOs have a confirmed spend? We can do so
493496
// by checking whether any UTXOs that exist in the map are no longer returned in
494497
// `list_confirmed_utxos`.
495-
locked_utxos: Mutex<HashMap<OutPoint, ClaimId>>,
498+
locked_utxos: Mutex<HashMap<OutPoint, Option<ClaimId>>>,
496499
}
497500

498501
impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> Wallet<W, L>
@@ -514,11 +517,13 @@ where
514517
/// least 1 satoshi at the current feerate, otherwise, we'll only attempt to spend those which
515518
/// contribute at least twice their fee.
516519
async fn select_confirmed_utxos_internal(
517-
&self, utxos: &[Utxo], claim_id: ClaimId, force_conflicting_utxo_spend: bool,
520+
&self, utxos: &[Utxo], claim_id: Option<ClaimId>, force_conflicting_utxo_spend: bool,
518521
tolerate_high_network_feerates: bool, target_feerate_sat_per_1000_weight: u32,
519522
preexisting_tx_weight: u64, input_amount_sat: Amount, target_amount_sat: Amount,
520523
max_tx_weight: u64,
521524
) -> Result<CoinSelection, ()> {
525+
debug_assert!(!(claim_id.is_none() && force_conflicting_utxo_spend));
526+
522527
// P2WSH and P2TR outputs are both the heaviest-weight standard outputs at 34 bytes
523528
let max_coin_selection_weight = max_tx_weight
524529
.checked_sub(preexisting_tx_weight + P2WSH_TXOUT_WEIGHT)
@@ -538,7 +543,12 @@ where
538543
.iter()
539544
.filter_map(|utxo| {
540545
if let Some(utxo_claim_id) = locked_utxos.get(&utxo.outpoint) {
541-
if *utxo_claim_id != claim_id && !force_conflicting_utxo_spend {
546+
// TODO(splicing): For splicing (i.e., claim_id.is_none()), ideally we'd
547+
// allow force_conflicting_utxo_spend for an RBF attempt. However, we'd need
548+
// something similar to a ClaimId to identify a splice.
549+
if (utxo_claim_id.is_none() || *utxo_claim_id != claim_id)
550+
&& !force_conflicting_utxo_spend
551+
{
542552
log_trace!(
543553
self.logger,
544554
"Skipping UTXO {} to prevent conflicting spend",
@@ -678,7 +688,7 @@ where
678688
W::Target: WalletSource + MaybeSend + MaybeSync,
679689
{
680690
fn select_confirmed_utxos<'a>(
681-
&'a self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
691+
&'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
682692
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
683693
) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a {
684694
async move {
@@ -703,6 +713,9 @@ where
703713

704714
let configs = [(false, false), (false, true), (true, false), (true, true)];
705715
for (force_conflicting_utxo_spend, tolerate_high_network_feerates) in configs {
716+
if claim_id.is_none() && force_conflicting_utxo_spend {
717+
continue;
718+
}
706719
log_debug!(
707720
self.logger,
708721
"Attempting coin selection targeting {} sat/kW (force_conflicting_utxo_spend = {}, tolerate_high_network_feerates = {})",
@@ -874,7 +887,7 @@ where
874887
let coin_selection: CoinSelection = self
875888
.utxo_source
876889
.select_confirmed_utxos(
877-
claim_id,
890+
Some(claim_id),
878891
must_spend,
879892
&[],
880893
package_target_feerate_sat_per_1000_weight,
@@ -1137,7 +1150,7 @@ where
11371150
let coin_selection: CoinSelection = match self
11381151
.utxo_source
11391152
.select_confirmed_utxos(
1140-
utxo_id,
1153+
Some(utxo_id),
11411154
must_spend,
11421155
&htlc_tx.output,
11431156
target_feerate_sat_per_1000_weight,
@@ -1358,7 +1371,7 @@ mod tests {
13581371
}
13591372
impl CoinSelectionSourceSync for TestCoinSelectionSource {
13601373
fn select_confirmed_utxos(
1361-
&self, _claim_id: ClaimId, must_spend: Vec<Input>, _must_pay_to: &[TxOut],
1374+
&self, _claim_id: Option<ClaimId>, must_spend: Vec<Input>, _must_pay_to: &[TxOut],
13621375
target_feerate_sat_per_1000_weight: u32, _max_tx_weight: u64,
13631376
) -> Result<CoinSelection, ()> {
13641377
let mut expected_selects = self.expected_selects.lock().unwrap();

lightning/src/events/bump_transaction/sync.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ where
135135
W::Target: WalletSourceSync + MaybeSend + MaybeSync,
136136
{
137137
fn select_confirmed_utxos(
138-
&self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &[TxOut],
138+
&self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
139139
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
140140
) -> Result<CoinSelection, ()> {
141141
let fut = self.wallet.select_confirmed_utxos(
@@ -214,7 +214,7 @@ pub trait CoinSelectionSourceSync {
214214
///
215215
/// [`ChannelMonitor::rebroadcast_pending_claims`]: crate::chain::channelmonitor::ChannelMonitor::rebroadcast_pending_claims
216216
fn select_confirmed_utxos(
217-
&self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &[TxOut],
217+
&self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
218218
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
219219
) -> Result<CoinSelection, ()>;
220220

@@ -247,7 +247,7 @@ where
247247
T::Target: CoinSelectionSourceSync,
248248
{
249249
fn select_confirmed_utxos<'a>(
250-
&'a self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
250+
&'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
251251
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
252252
) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a {
253253
let coins = self.0.select_confirmed_utxos(

0 commit comments

Comments
 (0)