Skip to content

Commit 96b9e6a

Browse files
committed
Use CoinSelection::change_output when splicing
Now that CoinSelection is used to fund a splice funding transaction, use that for determining of a change output should be used. Previously, the initiator could either provide a change script upfront or let LDK generate one using SignerProvider::get_destination_script. Since older versions may have serialized a SpliceInstruction without a change script while waiting on quiescence, LDK must still generate a change output in this case.
1 parent 2fd3351 commit 96b9e6a

7 files changed

Lines changed: 186 additions & 211 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2083,7 +2083,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
20832083
Ok(funding_template) => {
20842084
let wallet = WalletSync::new(&wallets[0], Arc::clone(&loggers[0]));
20852085
if let Ok(contribution) =
2086-
funding_template.splice_in_sync(None, Amount::from_sat(10_000), &wallet)
2086+
funding_template.splice_in_sync(Amount::from_sat(10_000), &wallet)
20872087
{
20882088
let _ = nodes[0].funding_contributed(
20892089
&chan_a_id,
@@ -2109,7 +2109,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
21092109
Ok(funding_template) => {
21102110
let wallet = WalletSync::new(&wallets[1], Arc::clone(&loggers[1]));
21112111
if let Ok(contribution) =
2112-
funding_template.splice_in_sync(None, Amount::from_sat(10_000), &wallet)
2112+
funding_template.splice_in_sync(Amount::from_sat(10_000), &wallet)
21132113
{
21142114
let _ = nodes[1].funding_contributed(
21152115
&chan_a_id,
@@ -2135,7 +2135,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
21352135
Ok(funding_template) => {
21362136
let wallet = WalletSync::new(&wallets[1], Arc::clone(&loggers[1]));
21372137
if let Ok(contribution) =
2138-
funding_template.splice_in_sync(None, Amount::from_sat(10_000), &wallet)
2138+
funding_template.splice_in_sync(Amount::from_sat(10_000), &wallet)
21392139
{
21402140
let _ = nodes[1].funding_contributed(
21412141
&chan_b_id,
@@ -2161,7 +2161,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
21612161
Ok(funding_template) => {
21622162
let wallet = WalletSync::new(&wallets[2], Arc::clone(&loggers[2]));
21632163
if let Ok(contribution) =
2164-
funding_template.splice_in_sync(None, Amount::from_sat(10_000), &wallet)
2164+
funding_template.splice_in_sync(Amount::from_sat(10_000), &wallet)
21652165
{
21662166
let _ = nodes[2].funding_contributed(
21672167
&chan_b_id,

fuzz/src/full_stack.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,11 +1038,9 @@ pub fn do_test(mut data: &[u8], logger: &Arc<dyn Logger + MaybeSend + MaybeSync>
10381038
FeeRate::from_sat_per_kwu(253),
10391039
) {
10401040
let wallet_sync = WalletSync::new(&wallet, Arc::clone(&logger));
1041-
if let Ok(contribution) = funding_template.splice_in_sync(
1042-
None,
1043-
Amount::from_sat(splice_in_sats.min(900_000)),
1044-
&wallet_sync,
1045-
) {
1041+
if let Ok(contribution) = funding_template
1042+
.splice_in_sync(Amount::from_sat(splice_in_sats.min(900_000)), &wallet_sync)
1043+
{
10461044
let _ = channelmanager.funding_contributed(
10471045
&chan_id,
10481046
&counterparty,

lightning-tests/src/upgrade_downgrade_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ fn do_test_0_1_htlc_forward_after_splice(fail_htlc: bool) {
458458
}];
459459
let channel_id = ChannelId(chan_id_bytes_a);
460460
let funding_contribution = initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs);
461-
let splice_tx = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
461+
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
462462
for node in nodes.iter() {
463463
mine_transaction(node, &splice_tx);
464464
connect_blocks(node, ANTI_REORG_DELAY - 1);

lightning/src/ln/channel.rs

Lines changed: 82 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2908,6 +2908,7 @@ impl_writeable_tlv_based!(PendingFunding, {
29082908
enum FundingNegotiation {
29092909
AwaitingAck {
29102910
context: FundingNegotiationContext,
2911+
change_strategy: ChangeStrategy,
29112912
new_holder_funding_key: PublicKey,
29122913
},
29132914
ConstructingTransaction {
@@ -6680,18 +6681,25 @@ pub(super) struct FundingNegotiationContext {
66806681
/// The funding outputs we will be contributing to the channel.
66816682
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
66826683
pub our_funding_outputs: Vec<TxOut>,
6684+
}
6685+
6686+
/// How the funding transaction's change is determined.
6687+
#[derive(Debug)]
6688+
pub(super) enum ChangeStrategy {
6689+
/// The change output, if any, is included in the FundingContribution's outputs.
6690+
FromCoinSelection,
6691+
66836692
/// The change output script. This will be used if needed or -- if not set -- generated using
66846693
/// `SignerProvider::get_destination_script`.
6685-
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
6686-
pub change_script: Option<ScriptBuf>,
6694+
LegacyUserProvided(Option<ScriptBuf>),
66876695
}
66886696

66896697
impl FundingNegotiationContext {
66906698
/// Prepare and start interactive transaction negotiation.
66916699
/// If error occurs, it is caused by our side, not the counterparty.
66926700
fn into_interactive_tx_constructor<SP: SignerProvider, ES: EntropySource>(
66936701
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6694-
entropy_source: &ES, holder_node_id: PublicKey,
6702+
entropy_source: &ES, holder_node_id: PublicKey, change_strategy: ChangeStrategy,
66956703
) -> Result<InteractiveTxConstructor, NegotiationError> {
66966704
debug_assert_eq!(
66976705
self.shared_funding_input.is_some(),
@@ -6712,46 +6720,15 @@ impl FundingNegotiationContext {
67126720
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
67136721
};
67146722

6715-
// Optionally add change output
6716-
let change_value_opt = if !self.our_funding_inputs.is_empty() {
6717-
match calculate_change_output_value(
6718-
&self,
6719-
self.shared_funding_input.is_some(),
6720-
&shared_funding_output.script_pubkey,
6721-
context.holder_dust_limit_satoshis,
6722-
) {
6723-
Ok(change_value_opt) => change_value_opt,
6724-
Err(reason) => {
6725-
return Err(self.into_negotiation_error(reason));
6726-
},
6727-
}
6728-
} else {
6729-
None
6730-
};
6731-
6732-
if let Some(change_value) = change_value_opt {
6733-
let change_script = if let Some(script) = self.change_script {
6734-
script
6735-
} else {
6736-
match signer_provider.get_destination_script(context.channel_keys_id) {
6737-
Ok(script) => script,
6738-
Err(_) => {
6739-
let reason = AbortReason::InternalError("Error getting change script");
6740-
return Err(self.into_negotiation_error(reason));
6741-
},
6742-
}
6743-
};
6744-
let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6745-
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6746-
let change_output_fee =
6747-
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6748-
let change_value_decreased_with_fee =
6749-
change_value.to_sat().saturating_sub(change_output_fee);
6750-
// Check dust limit again
6751-
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6752-
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6753-
self.our_funding_outputs.push(change_output);
6754-
}
6723+
match self.calculate_change_output(
6724+
context,
6725+
signer_provider,
6726+
&shared_funding_output,
6727+
change_strategy,
6728+
) {
6729+
Ok(Some(change_output)) => self.our_funding_outputs.push(change_output),
6730+
Ok(None) => {},
6731+
Err(reason) => return Err(self.into_negotiation_error(reason)),
67556732
}
67566733

67576734
let constructor_args = InteractiveTxConstructorArgs {
@@ -6773,6 +6750,52 @@ impl FundingNegotiationContext {
67736750
InteractiveTxConstructor::new(constructor_args)
67746751
}
67756752

6753+
fn calculate_change_output<SP: SignerProvider>(
6754+
&self, context: &ChannelContext<SP>, signer_provider: &SP, shared_funding_output: &TxOut,
6755+
change_strategy: ChangeStrategy,
6756+
) -> Result<Option<TxOut>, AbortReason> {
6757+
if self.our_funding_inputs.is_empty() {
6758+
return Ok(None);
6759+
}
6760+
6761+
let change_script = match change_strategy {
6762+
ChangeStrategy::FromCoinSelection => return Ok(None),
6763+
ChangeStrategy::LegacyUserProvided(change_script) => change_script,
6764+
};
6765+
6766+
let change_value = calculate_change_output_value(
6767+
&self,
6768+
self.shared_funding_input.is_some(),
6769+
&shared_funding_output.script_pubkey,
6770+
context.holder_dust_limit_satoshis,
6771+
)?;
6772+
6773+
if let Some(change_value) = change_value {
6774+
let change_script = match change_script {
6775+
Some(script) => script,
6776+
None => match signer_provider.get_destination_script(context.channel_keys_id) {
6777+
Ok(script) => script,
6778+
Err(_) => {
6779+
return Err(AbortReason::InternalError("Error getting change script"))
6780+
},
6781+
},
6782+
};
6783+
let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6784+
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6785+
let change_output_fee =
6786+
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6787+
let change_value_decreased_with_fee =
6788+
change_value.to_sat().saturating_sub(change_output_fee);
6789+
// Check dust limit again
6790+
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6791+
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6792+
return Ok(Some(change_output));
6793+
}
6794+
}
6795+
6796+
Ok(None)
6797+
}
6798+
67766799
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
67776800
let (contributed_inputs, contributed_outputs) = self.into_contributed_inputs_and_outputs();
67786801
NegotiationError { reason, contributed_inputs, contributed_outputs }
@@ -12235,14 +12258,13 @@ where
1223512258
shared_funding_input: Some(prev_funding_input),
1223612259
our_funding_inputs,
1223712260
our_funding_outputs,
12238-
change_script,
1223912261
};
1224012262

12241-
self.send_splice_init_internal(context)
12263+
self.send_splice_init_internal(context, ChangeStrategy::LegacyUserProvided(change_script))
1224212264
}
1224312265

1224412266
fn send_splice_init_internal(
12245-
&mut self, context: FundingNegotiationContext,
12267+
&mut self, context: FundingNegotiationContext, change_strategy: ChangeStrategy,
1224612268
) -> msgs::SpliceInit {
1224712269
debug_assert!(self.pending_splice.is_none());
1224812270
// Rotate the funding pubkey using the prev_funding_txid as a tweak
@@ -12263,8 +12285,11 @@ where
1226312285
let funding_contribution_satoshis = context.our_funding_contribution.to_sat();
1226412286
let locktime = context.funding_tx_locktime.to_consensus_u32();
1226512287

12266-
let funding_negotiation =
12267-
FundingNegotiation::AwaitingAck { context, new_holder_funding_key: funding_pubkey };
12288+
let funding_negotiation = FundingNegotiation::AwaitingAck {
12289+
context,
12290+
change_strategy,
12291+
new_holder_funding_key: funding_pubkey,
12292+
};
1226812293
self.pending_splice = Some(PendingFunding {
1226912294
funding_negotiation: Some(funding_negotiation),
1227012295
negotiated_candidates: vec![],
@@ -12490,7 +12515,6 @@ where
1249012515
shared_funding_input: Some(prev_funding_input),
1249112516
our_funding_inputs: Vec::new(),
1249212517
our_funding_outputs: Vec::new(),
12493-
change_script: None,
1249412518
};
1249512519

1249612520
let mut interactive_tx_constructor = funding_negotiation_context
@@ -12500,6 +12524,8 @@ where
1250012524
signer_provider,
1250112525
entropy_source,
1250212526
holder_node_id.clone(),
12527+
// ChangeStrategy doesn't matter when no inputs are contributed
12528+
ChangeStrategy::FromCoinSelection,
1250312529
)
1250412530
.map_err(|err| {
1250512531
ChannelError::WarnAndDisconnect(format!(
@@ -12550,11 +12576,11 @@ where
1255012576
let pending_splice =
1255112577
self.pending_splice.as_mut().expect("We should have returned an error earlier!");
1255212578
// TODO: Good candidate for a let else statement once MSRV >= 1.65
12553-
let funding_negotiation_context =
12554-
if let Some(FundingNegotiation::AwaitingAck { context, .. }) =
12579+
let (funding_negotiation_context, change_strategy) =
12580+
if let Some(FundingNegotiation::AwaitingAck { context, change_strategy, .. }) =
1255512581
pending_splice.funding_negotiation.take()
1255612582
{
12557-
context
12583+
(context, change_strategy)
1255812584
} else {
1255912585
panic!("We should have returned an error earlier!");
1256012586
};
@@ -12566,6 +12592,7 @@ where
1256612592
signer_provider,
1256712593
entropy_source,
1256812594
holder_node_id.clone(),
12595+
change_strategy,
1256912596
)
1257012597
.map_err(|err| {
1257112598
ChannelError::WarnAndDisconnect(format!(
@@ -12596,7 +12623,7 @@ where
1259612623
let (funding_negotiation_context, new_holder_funding_key) = match &pending_splice
1259712624
.funding_negotiation
1259812625
{
12599-
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key }) => {
12626+
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key, .. }) => {
1260012627
(context, new_holder_funding_key)
1260112628
},
1260212629
Some(FundingNegotiation::ConstructingTransaction { .. })
@@ -13523,7 +13550,7 @@ where
1352313550
},
1352413551
};
1352513552
let funding_feerate_per_kw = contribution.feerate().to_sat_per_kwu() as u32;
13526-
let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts();
13553+
let (our_funding_inputs, our_funding_outputs) = contribution.into_tx_parts();
1352713554

1352813555
let context = FundingNegotiationContext {
1352913556
is_initiator,
@@ -13533,10 +13560,9 @@ where
1353313560
shared_funding_input: Some(prev_funding_input),
1353413561
our_funding_inputs,
1353513562
our_funding_outputs,
13536-
change_script,
1353713563
};
1353813564

13539-
let splice_init = self.send_splice_init_internal(context);
13565+
let splice_init = self.send_splice_init_internal(context, ChangeStrategy::FromCoinSelection);
1354013566
return Ok(Some(StfuResponse::SpliceInit(splice_init)));
1354113567
},
1354213568
#[cfg(any(test, fuzzing))]
@@ -14320,7 +14346,6 @@ impl<SP: SignerProvider> PendingV2Channel<SP> {
1432014346
shared_funding_input: None,
1432114347
our_funding_inputs: funding_inputs,
1432214348
our_funding_outputs: Vec::new(),
14323-
change_script: None,
1432414349
};
1432514350
let chan = Self {
1432614351
funding,
@@ -14467,7 +14492,6 @@ impl<SP: SignerProvider> PendingV2Channel<SP> {
1446714492
shared_funding_input: None,
1446814493
our_funding_inputs: our_funding_inputs.clone(),
1446914494
our_funding_outputs: Vec::new(),
14470-
change_script: None,
1447114495
};
1447214496
let shared_funding_output = TxOut {
1447314497
value: Amount::from_sat(funding.get_value_satoshis()),

0 commit comments

Comments
 (0)