Skip to content

Commit 76bb833

Browse files
committed
Remove wallet argument from FundingTemplate::splice_out
It does not require coin selection, so the wallet argument is not necessary.
1 parent 67bf852 commit 76bb833

4 files changed

Lines changed: 118 additions & 105 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,7 +1504,6 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
15041504
counterparty_node_id: &PublicKey,
15051505
channel_id: &ChannelId,
15061506
wallet: &TestWalletSource,
1507-
logger: Arc<dyn Logger + MaybeSend + MaybeSync>,
15081507
funding_feerate_sat_per_kw: FeeRate| {
15091508
// We conditionally splice out `MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS` only when the node
15101509
// has double the balance required to send a payment upon a `0xff` byte. We do this to
@@ -1524,12 +1523,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
15241523
value: Amount::from_sat(MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS),
15251524
script_pubkey: wallet.get_change_script().unwrap(),
15261525
}];
1527-
funding_template.splice_out_sync(
1528-
outputs,
1529-
feerate,
1530-
FeeRate::MAX,
1531-
&WalletSync::new(wallet, logger.clone()),
1532-
)
1526+
funding_template.splice_out(outputs, feerate, FeeRate::MAX)
15331527
});
15341528
};
15351529

@@ -2450,30 +2444,26 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
24502444
0xa4 => {
24512445
let cp_node_id = nodes[1].get_our_node_id();
24522446
let wallet = &wallets[0];
2453-
let logger = Arc::clone(&loggers[0]);
24542447
let feerate_sat_per_kw = fee_estimators[0].feerate_sat_per_kw();
2455-
splice_out(&nodes[0], &cp_node_id, &chan_a_id, wallet, logger, feerate_sat_per_kw);
2448+
splice_out(&nodes[0], &cp_node_id, &chan_a_id, wallet, feerate_sat_per_kw);
24562449
},
24572450
0xa5 => {
24582451
let cp_node_id = nodes[0].get_our_node_id();
24592452
let wallet = &wallets[1];
2460-
let logger = Arc::clone(&loggers[1]);
24612453
let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw();
2462-
splice_out(&nodes[1], &cp_node_id, &chan_a_id, wallet, logger, feerate_sat_per_kw);
2454+
splice_out(&nodes[1], &cp_node_id, &chan_a_id, wallet, feerate_sat_per_kw);
24632455
},
24642456
0xa6 => {
24652457
let cp_node_id = nodes[2].get_our_node_id();
24662458
let wallet = &wallets[1];
2467-
let logger = Arc::clone(&loggers[1]);
24682459
let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw();
2469-
splice_out(&nodes[1], &cp_node_id, &chan_b_id, wallet, logger, feerate_sat_per_kw);
2460+
splice_out(&nodes[1], &cp_node_id, &chan_b_id, wallet, feerate_sat_per_kw);
24702461
},
24712462
0xa7 => {
24722463
let cp_node_id = nodes[1].get_our_node_id();
24732464
let wallet = &wallets[2];
2474-
let logger = Arc::clone(&loggers[2]);
24752465
let feerate_sat_per_kw = fee_estimators[2].feerate_sat_per_kw();
2476-
splice_out(&nodes[2], &cp_node_id, &chan_b_id, wallet, logger, feerate_sat_per_kw);
2466+
splice_out(&nodes[2], &cp_node_id, &chan_b_id, wallet, feerate_sat_per_kw);
24772467
},
24782468

24792469
// Sync node by 1 block to cover confirmation of a transaction.

fuzz/src/full_stack.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,13 +1083,9 @@ pub fn do_test(mut data: &[u8], logger: &Arc<dyn Logger + MaybeSend + MaybeSync>
10831083
value: Amount::from_sat(splice_out_sats),
10841084
script_pubkey: wallet.get_change_script().unwrap(),
10851085
}];
1086-
let wallet_sync = WalletSync::new(&wallet, Arc::clone(&logger));
1087-
if let Ok(contribution) = funding_template.splice_out_sync(
1088-
outputs,
1089-
feerate,
1090-
FeeRate::MAX,
1091-
&wallet_sync,
1092-
) {
1086+
if let Ok(contribution) =
1087+
funding_template.splice_out(outputs, feerate, FeeRate::MAX)
1088+
{
10931089
let _ = channelmanager.funding_contributed(
10941090
&chan_id,
10951091
&counterparty,

lightning/src/ln/funding.rs

Lines changed: 107 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,12 @@ impl PriorContribution {
206206
/// For a fresh splice (no pending splice to replace), build a new contribution using one of
207207
/// the splice methods:
208208
/// - [`FundingTemplate::splice_in_sync`] to add funds to the channel
209-
/// - [`FundingTemplate::splice_out_sync`] to remove funds from the channel
209+
/// - [`FundingTemplate::splice_out`] to remove funds from the channel
210210
/// - [`FundingTemplate::splice_in_and_out_sync`] to do both
211211
///
212-
/// These perform coin selection and require `min_feerate` and `max_feerate` parameters.
212+
/// These require `min_feerate` and `max_feerate` parameters. The splice-in variants perform
213+
/// coin selection when wallet inputs are needed, while splice-out spends only from the channel
214+
/// balance.
213215
///
214216
/// # Replace By Fee (RBF)
215217
///
@@ -285,31 +287,13 @@ macro_rules! build_funding_contribution {
285287
let max_feerate: FeeRate = $max_feerate;
286288
let force_coin_selection: bool = $force_coin_selection;
287289

288-
if feerate > max_feerate {
289-
return Err(FundingContributionError::FeeRateExceedsMaximum { feerate, max_feerate });
290-
}
291-
292-
if let Some(min_rbf_feerate) = min_rbf_feerate {
293-
if feerate < min_rbf_feerate {
294-
return Err(FundingContributionError::FeeRateBelowRbfMinimum { feerate, min_rbf_feerate });
295-
}
296-
}
297-
298-
// Validate user-provided amounts are within MAX_MONEY before coin selection to
299-
// ensure FundingContribution::net_value() arithmetic cannot overflow. With all
300-
// amounts bounded by MAX_MONEY (~2.1e15 sat), the worst-case net_value()
301-
// computation is -2 * MAX_MONEY (~-4.2e15), well within i64::MIN (~-9.2e18).
302-
if value_added > Amount::MAX_MONEY {
303-
return Err(FundingContributionError::InvalidSpliceValue);
304-
}
305-
306-
let mut value_removed = Amount::ZERO;
307-
for txout in outputs.iter() {
308-
value_removed = match value_removed.checked_add(txout.value) {
309-
Some(sum) if sum <= Amount::MAX_MONEY => sum,
310-
_ => return Err(FundingContributionError::InvalidSpliceValue),
311-
};
312-
}
290+
let value_removed = validate_funding_contribution_params(
291+
value_added,
292+
&outputs,
293+
min_rbf_feerate,
294+
feerate,
295+
max_feerate,
296+
)?;
313297

314298
let is_splice = shared_input.is_some();
315299

@@ -348,25 +332,52 @@ macro_rules! build_funding_contribution {
348332

349333
let CoinSelection { confirmed_utxos: inputs, change_output } = coin_selection;
350334

351-
// The caller creating a FundingContribution is always the initiator for fee estimation
352-
// purposes — this is conservative, overestimating rather than underestimating fees if
353-
// the node ends up as the acceptor.
354-
let estimated_fee = estimate_transaction_fee(&inputs, &outputs, change_output.as_ref(), true, is_splice, feerate);
355-
debug_assert!(estimated_fee <= Amount::MAX_MONEY);
356-
357-
let contribution = FundingContribution {
335+
Ok(FundingContribution::new(
358336
value_added,
359-
estimated_fee,
360-
inputs,
361337
outputs,
338+
inputs,
362339
change_output,
363340
feerate,
364341
max_feerate,
365342
is_splice,
343+
))
344+
}};
345+
}
346+
347+
fn validate_funding_contribution_params(
348+
value_added: Amount, outputs: &[TxOut], min_rbf_feerate: Option<FeeRate>, feerate: FeeRate,
349+
max_feerate: FeeRate,
350+
) -> Result<Amount, FundingContributionError> {
351+
if feerate > max_feerate {
352+
return Err(FundingContributionError::FeeRateExceedsMaximum { feerate, max_feerate });
353+
}
354+
355+
if let Some(min_rbf_feerate) = min_rbf_feerate {
356+
if feerate < min_rbf_feerate {
357+
return Err(FundingContributionError::FeeRateBelowRbfMinimum {
358+
feerate,
359+
min_rbf_feerate,
360+
});
361+
}
362+
}
363+
364+
// Validate user-provided amounts are within MAX_MONEY before coin selection to
365+
// ensure FundingContribution::net_value() arithmetic cannot overflow. With all
366+
// amounts bounded by MAX_MONEY (~2.1e15 sat), the worst-case net_value()
367+
// computation is -2 * MAX_MONEY (~-4.2e15), well within i64::MIN (~-9.2e18).
368+
if value_added > Amount::MAX_MONEY {
369+
return Err(FundingContributionError::InvalidSpliceValue);
370+
}
371+
372+
let mut value_removed = Amount::ZERO;
373+
for txout in outputs.iter() {
374+
value_removed = match value_removed.checked_add(txout.value) {
375+
Some(sum) if sum <= Amount::MAX_MONEY => sum,
376+
_ => return Err(FundingContributionError::InvalidSpliceValue),
366377
};
378+
}
367379

368-
Ok(contribution)
369-
}};
380+
Ok(value_removed)
370381
}
371382

372383
impl FundingTemplate {
@@ -410,44 +421,37 @@ impl FundingTemplate {
410421
)
411422
}
412423

413-
/// Creates a [`FundingContribution`] for removing funds from a channel using `wallet` to
414-
/// perform coin selection.
424+
/// Creates a [`FundingContribution`] for removing funds from a channel.
425+
///
426+
/// Fees are paid from the channel balance, so this does not perform coin selection or spend
427+
/// wallet inputs.
415428
///
416429
/// `outputs` are the complete set of withdrawal outputs for this contribution. When
417430
/// replacing a prior contribution via RBF, use [`FundingTemplate::prior_contribution`] to
418431
/// inspect the prior parameters. To keep existing withdrawals and add new ones, include the
419432
/// prior's outputs: combine [`FundingContribution::outputs`] with the new outputs.
420-
pub async fn splice_out<W: CoinSelectionSource + MaybeSend>(
421-
self, outputs: Vec<TxOut>, min_feerate: FeeRate, max_feerate: FeeRate, wallet: W,
422-
) -> Result<FundingContribution, FundingContributionError> {
423-
if outputs.is_empty() {
424-
return Err(FundingContributionError::InvalidSpliceValue);
425-
}
426-
let FundingTemplate { shared_input, min_rbf_feerate, .. } = self;
427-
build_funding_contribution!(Amount::ZERO, outputs, shared_input, min_rbf_feerate, min_feerate, max_feerate, false, wallet, await)
428-
}
429-
430-
/// Creates a [`FundingContribution`] for removing funds from a channel using `wallet` to
431-
/// perform coin selection.
432-
///
433-
/// See [`FundingTemplate::splice_out`] for details.
434-
pub fn splice_out_sync<W: CoinSelectionSourceSync>(
435-
self, outputs: Vec<TxOut>, min_feerate: FeeRate, max_feerate: FeeRate, wallet: W,
433+
pub fn splice_out(
434+
self, outputs: Vec<TxOut>, min_feerate: FeeRate, max_feerate: FeeRate,
436435
) -> Result<FundingContribution, FundingContributionError> {
437436
if outputs.is_empty() {
438437
return Err(FundingContributionError::InvalidSpliceValue);
439438
}
440-
let FundingTemplate { shared_input, min_rbf_feerate, .. } = self;
441-
build_funding_contribution!(
439+
validate_funding_contribution_params(
440+
Amount::ZERO,
441+
&outputs,
442+
self.min_rbf_feerate,
443+
min_feerate,
444+
max_feerate,
445+
)?;
446+
Ok(FundingContribution::new(
442447
Amount::ZERO,
443448
outputs,
444-
shared_input,
445-
min_rbf_feerate,
449+
vec![],
450+
None,
446451
min_feerate,
447452
max_feerate,
448-
false,
449-
wallet,
450-
)
453+
self.shared_input.is_some(),
454+
))
451455
}
452456

453457
/// Creates a [`FundingContribution`] for both adding and removing funds from a channel using
@@ -708,6 +712,35 @@ impl_writeable_tlv_based!(FundingContribution, {
708712
});
709713

710714
impl FundingContribution {
715+
fn new(
716+
value_added: Amount, outputs: Vec<TxOut>, inputs: Vec<FundingTxInput>,
717+
change_output: Option<TxOut>, feerate: FeeRate, max_feerate: FeeRate, is_splice: bool,
718+
) -> Self {
719+
// The caller creating a FundingContribution is always the initiator for fee estimation
720+
// purposes — this is conservative, overestimating rather than underestimating fees if the
721+
// node ends up as the acceptor.
722+
let estimated_fee = estimate_transaction_fee(
723+
&inputs,
724+
&outputs,
725+
change_output.as_ref(),
726+
true,
727+
is_splice,
728+
feerate,
729+
);
730+
debug_assert!(estimated_fee <= Amount::MAX_MONEY);
731+
732+
Self {
733+
value_added,
734+
estimated_fee,
735+
inputs,
736+
outputs,
737+
change_output,
738+
feerate,
739+
max_feerate,
740+
is_splice,
741+
}
742+
}
743+
711744
pub(super) fn feerate(&self) -> FeeRate {
712745
self.feerate
713746
}
@@ -1440,17 +1473,17 @@ mod tests {
14401473
));
14411474
}
14421475

1443-
// splice_out_sync with single output value > MAX_MONEY
1476+
// splice_out with single output value > MAX_MONEY
14441477
{
14451478
let template = FundingTemplate::new(None, None, None);
14461479
let outputs = vec![funding_output_sats(over_max.to_sat())];
14471480
assert!(matches!(
1448-
template.splice_out_sync(outputs, feerate, feerate, UnreachableWallet),
1481+
template.splice_out(outputs, feerate, feerate),
14491482
Err(FundingContributionError::InvalidSpliceValue),
14501483
));
14511484
}
14521485

1453-
// splice_out_sync with multiple outputs summing > MAX_MONEY
1486+
// splice_out with multiple outputs summing > MAX_MONEY
14541487
{
14551488
let template = FundingTemplate::new(None, None, None);
14561489
let half_over = Amount::MAX_MONEY / 2 + Amount::from_sat(1);
@@ -1459,7 +1492,7 @@ mod tests {
14591492
funding_output_sats(half_over.to_sat()),
14601493
];
14611494
assert!(matches!(
1462-
template.splice_out_sync(outputs, feerate, feerate, UnreachableWallet),
1495+
template.splice_out(outputs, feerate, feerate),
14631496
Err(FundingContributionError::InvalidSpliceValue),
14641497
));
14651498
}
@@ -2454,23 +2487,22 @@ mod tests {
24542487
}
24552488

24562489
#[test]
2457-
fn test_splice_out_sync_skips_coin_selection_during_rbf() {
2458-
// When splice_out_sync is called on a template with min_rbf_feerate set (user
2459-
// choosing a fresh splice-out instead of rbf_sync), coin selection should NOT run.
2460-
// Fees come from the channel balance.
2490+
fn test_splice_out_skips_coin_selection_during_rbf() {
2491+
// When splice_out is called on a template with min_rbf_feerate set (user
2492+
// choosing a fresh splice-out instead of rbf_sync), fees still come from the
2493+
// channel balance rather than wallet inputs.
24612494
let min_rbf_feerate = FeeRate::from_sat_per_kwu(5000);
24622495
let feerate = FeeRate::from_sat_per_kwu(5000);
24632496
let withdrawal = funding_output_sats(20_000);
24642497

24652498
let template =
24662499
FundingTemplate::new(Some(shared_input(100_000)), Some(min_rbf_feerate), None);
24672500

2468-
// UnreachableWallet panics if coin selection runs — verifying it is skipped.
2469-
let contribution = template
2470-
.splice_out_sync(vec![withdrawal.clone()], feerate, FeeRate::MAX, UnreachableWallet)
2471-
.unwrap();
2501+
let contribution =
2502+
template.splice_out(vec![withdrawal.clone()], feerate, FeeRate::MAX).unwrap();
24722503
assert_eq!(contribution.value_added, Amount::ZERO);
24732504
assert!(contribution.inputs.is_empty());
2505+
assert!(contribution.change_output.is_none());
24742506
assert_eq!(contribution.outputs, vec![withdrawal]);
24752507
}
24762508
}

lightning/src/ln/splicing_tests.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,7 @@ pub fn initiate_splice_out<'a, 'b, 'c, 'd>(
271271
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
272272
let funding_template = initiator.node.splice_channel(&channel_id, &node_id_acceptor).unwrap();
273273
let feerate = funding_template.min_rbf_feerate().unwrap_or(floor_feerate);
274-
let wallet = WalletSync::new(Arc::clone(&initiator.wallet_source), initiator.logger);
275-
let funding_contribution =
276-
funding_template.splice_out_sync(outputs, feerate, FeeRate::MAX, &wallet).unwrap();
274+
let funding_contribution = funding_template.splice_out(outputs, feerate, FeeRate::MAX).unwrap();
277275
match initiator.node.funding_contributed(
278276
&channel_id,
279277
&node_id_acceptor,
@@ -1367,9 +1365,8 @@ fn fails_initiating_concurrent_splices(reconnect: bool) {
13671365
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
13681366

13691367
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_1_id).unwrap();
1370-
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
13711368
let funding_contribution =
1372-
funding_template.splice_out_sync(outputs.clone(), feerate, FeeRate::MAX, &wallet).unwrap();
1369+
funding_template.splice_out(outputs.clone(), feerate, FeeRate::MAX).unwrap();
13731370
nodes[0]
13741371
.node
13751372
.funding_contributed(&channel_id, &node_1_id, funding_contribution.clone(), None)
@@ -6336,9 +6333,7 @@ fn test_splice_revalidation_at_quiescence() {
63366333

63376334
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
63386335
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
6339-
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
6340-
let contribution =
6341-
funding_template.splice_out_sync(outputs, feerate, FeeRate::MAX, &wallet).unwrap();
6336+
let contribution = funding_template.splice_out(outputs, feerate, FeeRate::MAX).unwrap();
63426337

63436338
nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution.clone(), None).unwrap();
63446339
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty(), "stfu should be delayed");

0 commit comments

Comments
 (0)