Skip to content

Commit 1c8e7e9

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 c3616cd commit 1c8e7e9

6 files changed

Lines changed: 181 additions & 182 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,7 +1824,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
18241824
},
18251825

18261826
0xa0 => {
1827-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1827+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18281828
let feerate_sat_per_kw =
18291829
fee_estimators[0].ret_val.load(atomic::Ordering::Acquire);
18301830
let feerate = FeeRate::from_sat_per_kwu(feerate_sat_per_kw as u64);
@@ -1842,7 +1842,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
18421842
}
18431843
},
18441844
0xa1 => {
1845-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1845+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18461846
let feerate_sat_per_kw =
18471847
fee_estimators[1].ret_val.load(atomic::Ordering::Acquire);
18481848
let feerate = FeeRate::from_sat_per_kwu(feerate_sat_per_kw as u64);
@@ -1860,7 +1860,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
18601860
}
18611861
},
18621862
0xa2 => {
1863-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1863+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18641864
let feerate_sat_per_kw =
18651865
fee_estimators[1].ret_val.load(atomic::Ordering::Acquire);
18661866
let feerate = FeeRate::from_sat_per_kwu(feerate_sat_per_kw as u64);
@@ -1878,7 +1878,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
18781878
}
18791879
},
18801880
0xa3 => {
1881-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1881+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18821882
let feerate_sat_per_kw =
18831883
fee_estimators[2].ret_val.load(atomic::Ordering::Acquire);
18841884
let feerate = FeeRate::from_sat_per_kwu(feerate_sat_per_kw as u64);

lightning-tests/src/upgrade_downgrade_tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,8 @@ fn do_test_0_1_htlc_forward_after_splice(fail_htlc: bool) {
455455
value: Amount::from_sat(1_000),
456456
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
457457
}]);
458-
let splice_tx = splice_channel(&nodes[0], &nodes[1], ChannelId(chan_id_bytes_a), contribution);
458+
let (splice_tx, _) =
459+
splice_channel(&nodes[0], &nodes[1], ChannelId(chan_id_bytes_a), contribution);
459460
for node in nodes.iter() {
460461
mine_transaction(node, &splice_tx);
461462
connect_blocks(node, ANTI_REORG_DELAY - 1);

lightning/src/ln/channel.rs

Lines changed: 85 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,7 @@ impl_writeable_tlv_based!(PendingFunding, {
27152715
enum FundingNegotiation {
27162716
AwaitingAck {
27172717
context: FundingNegotiationContext,
2718+
change_strategy: ChangeStrategy,
27182719
new_holder_funding_key: PublicKey,
27192720
},
27202721
ConstructingTransaction {
@@ -6547,18 +6548,25 @@ pub(super) struct FundingNegotiationContext {
65476548
/// The funding outputs we will be contributing to the channel.
65486549
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
65496550
pub our_funding_outputs: Vec<TxOut>,
6551+
}
6552+
6553+
/// How the funding transaction's change is determined.
6554+
#[derive(Debug)]
6555+
pub(super) enum ChangeStrategy {
6556+
/// The change output, if any, is included in the FundingContribution's outputs.
6557+
FromCoinSelection,
6558+
65506559
/// The change output script. This will be used if needed or -- if not set -- generated using
65516560
/// `SignerProvider::get_destination_script`.
6552-
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
6553-
pub change_script: Option<ScriptBuf>,
6561+
LegacyUserProvided(Option<ScriptBuf>),
65546562
}
65556563

65566564
impl FundingNegotiationContext {
65576565
/// Prepare and start interactive transaction negotiation.
65586566
/// If error occurs, it is caused by our side, not the counterparty.
65596567
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
65606568
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6561-
entropy_source: &ES, holder_node_id: PublicKey,
6569+
entropy_source: &ES, holder_node_id: PublicKey, change_strategy: ChangeStrategy,
65626570
) -> Result<InteractiveTxConstructor, NegotiationError>
65636571
where
65646572
SP::Target: SignerProvider,
@@ -6583,46 +6591,15 @@ impl FundingNegotiationContext {
65836591
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
65846592
};
65856593

6586-
// Optionally add change output
6587-
let change_value_opt = if !self.our_funding_inputs.is_empty() {
6588-
match calculate_change_output_value(
6589-
&self,
6590-
self.shared_funding_input.is_some(),
6591-
&shared_funding_output.script_pubkey,
6592-
context.holder_dust_limit_satoshis,
6593-
) {
6594-
Ok(change_value_opt) => change_value_opt,
6595-
Err(reason) => {
6596-
return Err(self.into_negotiation_error(reason));
6597-
},
6598-
}
6599-
} else {
6600-
None
6601-
};
6602-
6603-
if let Some(change_value) = change_value_opt {
6604-
let change_script = if let Some(script) = self.change_script {
6605-
script
6606-
} else {
6607-
match signer_provider.get_destination_script(context.channel_keys_id) {
6608-
Ok(script) => script,
6609-
Err(_) => {
6610-
let reason = AbortReason::InternalError("Error getting change script");
6611-
return Err(self.into_negotiation_error(reason));
6612-
},
6613-
}
6614-
};
6615-
let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6616-
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6617-
let change_output_fee =
6618-
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6619-
let change_value_decreased_with_fee =
6620-
change_value.to_sat().saturating_sub(change_output_fee);
6621-
// Check dust limit again
6622-
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6623-
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6624-
self.our_funding_outputs.push(change_output);
6625-
}
6594+
match self.calculate_change_output(
6595+
context,
6596+
signer_provider,
6597+
&shared_funding_output,
6598+
change_strategy,
6599+
) {
6600+
Ok(Some(change_output)) => self.our_funding_outputs.push(change_output),
6601+
Ok(None) => {},
6602+
Err(reason) => return Err(self.into_negotiation_error(reason)),
66266603
}
66276604

66286605
let constructor_args = InteractiveTxConstructorArgs {
@@ -6644,6 +6621,55 @@ impl FundingNegotiationContext {
66446621
InteractiveTxConstructor::new(constructor_args)
66456622
}
66466623

6624+
fn calculate_change_output<SP: Deref>(
6625+
&self, context: &ChannelContext<SP>, signer_provider: &SP, shared_funding_output: &TxOut,
6626+
change_strategy: ChangeStrategy,
6627+
) -> Result<Option<TxOut>, AbortReason>
6628+
where
6629+
SP::Target: SignerProvider,
6630+
{
6631+
if self.our_funding_inputs.is_empty() {
6632+
return Ok(None);
6633+
}
6634+
6635+
let change_script = match change_strategy {
6636+
ChangeStrategy::FromCoinSelection => return Ok(None),
6637+
ChangeStrategy::LegacyUserProvided(change_script) => change_script,
6638+
};
6639+
6640+
let change_value = calculate_change_output_value(
6641+
&self,
6642+
self.shared_funding_input.is_some(),
6643+
&shared_funding_output.script_pubkey,
6644+
context.holder_dust_limit_satoshis,
6645+
)?;
6646+
6647+
if let Some(change_value) = change_value {
6648+
let change_script = match change_script {
6649+
Some(script) => script,
6650+
None => match signer_provider.get_destination_script(context.channel_keys_id) {
6651+
Ok(script) => script,
6652+
Err(_) => {
6653+
return Err(AbortReason::InternalError("Error getting change script"))
6654+
},
6655+
},
6656+
};
6657+
let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6658+
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6659+
let change_output_fee =
6660+
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6661+
let change_value_decreased_with_fee =
6662+
change_value.to_sat().saturating_sub(change_output_fee);
6663+
// Check dust limit again
6664+
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6665+
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6666+
return Ok(Some(change_output));
6667+
}
6668+
}
6669+
6670+
Ok(None)
6671+
}
6672+
66476673
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
66486674
let (contributed_inputs, contributed_outputs) = self.into_contributed_inputs_and_outputs();
66496675
NegotiationError { reason, contributed_inputs, contributed_outputs }
@@ -12128,14 +12154,13 @@ where
1212812154
shared_funding_input: Some(prev_funding_input),
1212912155
our_funding_inputs,
1213012156
our_funding_outputs,
12131-
change_script,
1213212157
};
1213312158

12134-
self.send_splice_init_internal(context)
12159+
self.send_splice_init_internal(context, ChangeStrategy::LegacyUserProvided(change_script))
1213512160
}
1213612161

1213712162
fn send_splice_init_internal(
12138-
&mut self, context: FundingNegotiationContext,
12163+
&mut self, context: FundingNegotiationContext, change_strategy: ChangeStrategy,
1213912164
) -> msgs::SpliceInit {
1214012165
debug_assert!(self.pending_splice.is_none());
1214112166
// Rotate the funding pubkey using the prev_funding_txid as a tweak
@@ -12156,8 +12181,11 @@ where
1215612181
let funding_contribution_satoshis = context.our_funding_contribution.to_sat();
1215712182
let locktime = context.funding_tx_locktime.to_consensus_u32();
1215812183

12159-
let funding_negotiation =
12160-
FundingNegotiation::AwaitingAck { context, new_holder_funding_key: funding_pubkey };
12184+
let funding_negotiation = FundingNegotiation::AwaitingAck {
12185+
context,
12186+
change_strategy,
12187+
new_holder_funding_key: funding_pubkey,
12188+
};
1216112189
self.pending_splice = Some(PendingFunding {
1216212190
funding_negotiation: Some(funding_negotiation),
1216312191
negotiated_candidates: vec![],
@@ -12387,7 +12415,6 @@ where
1238712415
shared_funding_input: Some(prev_funding_input),
1238812416
our_funding_inputs: Vec::new(),
1238912417
our_funding_outputs: Vec::new(),
12390-
change_script: None,
1239112418
};
1239212419

1239312420
let mut interactive_tx_constructor = funding_negotiation_context
@@ -12397,6 +12424,8 @@ where
1239712424
signer_provider,
1239812425
entropy_source,
1239912426
holder_node_id.clone(),
12427+
// ChangeStrategy doesn't matter when no inputs are contributed
12428+
ChangeStrategy::FromCoinSelection,
1240012429
)
1240112430
.map_err(|err| {
1240212431
ChannelError::WarnAndDisconnect(format!(
@@ -12451,11 +12480,11 @@ where
1245112480
let pending_splice =
1245212481
self.pending_splice.as_mut().expect("We should have returned an error earlier!");
1245312482
// TODO: Good candidate for a let else statement once MSRV >= 1.65
12454-
let funding_negotiation_context =
12455-
if let Some(FundingNegotiation::AwaitingAck { context, .. }) =
12483+
let (funding_negotiation_context, change_strategy) =
12484+
if let Some(FundingNegotiation::AwaitingAck { context, change_strategy, .. }) =
1245612485
pending_splice.funding_negotiation.take()
1245712486
{
12458-
context
12487+
(context, change_strategy)
1245912488
} else {
1246012489
panic!("We should have returned an error earlier!");
1246112490
};
@@ -12467,6 +12496,7 @@ where
1246712496
signer_provider,
1246812497
entropy_source,
1246912498
holder_node_id.clone(),
12499+
change_strategy,
1247012500
)
1247112501
.map_err(|err| {
1247212502
ChannelError::WarnAndDisconnect(format!(
@@ -12497,7 +12527,7 @@ where
1249712527
let (funding_negotiation_context, new_holder_funding_key) = match &pending_splice
1249812528
.funding_negotiation
1249912529
{
12500-
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key }) => {
12530+
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key, .. }) => {
1250112531
(context, new_holder_funding_key)
1250212532
},
1250312533
Some(FundingNegotiation::ConstructingTransaction { .. })
@@ -13460,7 +13490,7 @@ where
1346013490
},
1346113491
};
1346213492
let funding_feerate_per_kw = contribution.feerate().to_sat_per_kwu() as u32;
13463-
let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts();
13493+
let (our_funding_inputs, our_funding_outputs) = contribution.into_tx_parts();
1346413494

1346513495
let context = FundingNegotiationContext {
1346613496
is_initiator,
@@ -13470,10 +13500,9 @@ where
1347013500
shared_funding_input: Some(prev_funding_input),
1347113501
our_funding_inputs,
1347213502
our_funding_outputs,
13473-
change_script,
1347413503
};
1347513504

13476-
let splice_init = self.send_splice_init_internal(context);
13505+
let splice_init = self.send_splice_init_internal(context, ChangeStrategy::FromCoinSelection);
1347713506
return Ok(Some(StfuResponse::SpliceInit(splice_init)));
1347813507
},
1347913508
#[cfg(any(test, fuzzing))]
@@ -14310,7 +14339,6 @@ where
1431014339
shared_funding_input: None,
1431114340
our_funding_inputs: funding_inputs,
1431214341
our_funding_outputs: Vec::new(),
14313-
change_script: None,
1431414342
};
1431514343
let chan = Self {
1431614344
funding,
@@ -14464,7 +14492,6 @@ where
1446414492
shared_funding_input: None,
1446514493
our_funding_inputs: our_funding_inputs.clone(),
1446614494
our_funding_outputs: Vec::new(),
14467-
change_script: None,
1446814495
};
1446914496
let shared_funding_output = TxOut {
1447014497
value: Amount::from_sat(funding.get_value_satoshis()),

0 commit comments

Comments
 (0)