Skip to content

Commit fcccbac

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 5e4096c commit fcccbac

File tree

6 files changed

+158
-174
lines changed

6 files changed

+158
-174
lines changed

fuzz/src/chanmon_consistency.rs

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

18301830
0xa0 => {
1831-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1831+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18321832
if let Err(e) = nodes[0].splice_channel(
18331833
&chan_a_id,
18341834
&nodes[1].get_our_node_id(),
@@ -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
if let Err(e) = nodes[1].splice_channel(
18471847
&chan_a_id,
18481848
&nodes[0].get_our_node_id(),
@@ -1856,7 +1856,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
18561856
}
18571857
},
18581858
0xa2 => {
1859-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1859+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18601860
if let Err(e) = nodes[1].splice_channel(
18611861
&chan_b_id,
18621862
&nodes[2].get_our_node_id(),
@@ -1870,7 +1870,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
18701870
}
18711871
},
18721872
0xa3 => {
1873-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1873+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18741874
if let Err(e) = nodes[2].splice_channel(
18751875
&chan_b_id,
18761876
&nodes[1].get_our_node_id(),

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 {
@@ -6539,18 +6540,25 @@ pub(super) struct FundingNegotiationContext {
65396540
/// The funding outputs we will be contributing to the channel.
65406541
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
65416542
pub our_funding_outputs: Vec<TxOut>,
6543+
}
6544+
6545+
/// How the funding transaction's change is determined.
6546+
#[derive(Debug)]
6547+
pub(super) enum ChangeStrategy {
6548+
/// The change output, if any, is included in the FundingContribution's outputs.
6549+
FromCoinSelection,
6550+
65426551
/// The change output script. This will be used if needed or -- if not set -- generated using
65436552
/// `SignerProvider::get_destination_script`.
6544-
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
6545-
pub change_script: Option<ScriptBuf>,
6553+
LegacyUserProvided(Option<ScriptBuf>),
65466554
}
65476555

65486556
impl FundingNegotiationContext {
65496557
/// Prepare and start interactive transaction negotiation.
65506558
/// If error occurs, it is caused by our side, not the counterparty.
65516559
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
65526560
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6553-
entropy_source: &ES, holder_node_id: PublicKey,
6561+
entropy_source: &ES, holder_node_id: PublicKey, change_strategy: ChangeStrategy,
65546562
) -> Result<InteractiveTxConstructor, NegotiationError>
65556563
where
65566564
SP::Target: SignerProvider,
@@ -6575,46 +6583,15 @@ impl FundingNegotiationContext {
65756583
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
65766584
};
65776585

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

66206597
let constructor_args = InteractiveTxConstructorArgs {
@@ -6636,6 +6613,55 @@ impl FundingNegotiationContext {
66366613
InteractiveTxConstructor::new(constructor_args)
66376614
}
66386615

6616+
fn calculate_change_output<SP: Deref>(
6617+
&self, context: &ChannelContext<SP>, signer_provider: &SP, shared_funding_output: &TxOut,
6618+
change_strategy: ChangeStrategy,
6619+
) -> Result<Option<TxOut>, AbortReason>
6620+
where
6621+
SP::Target: SignerProvider,
6622+
{
6623+
if self.our_funding_inputs.is_empty() {
6624+
return Ok(None);
6625+
}
6626+
6627+
let change_script = match change_strategy {
6628+
ChangeStrategy::FromCoinSelection => return Ok(None),
6629+
ChangeStrategy::LegacyUserProvided(change_script) => change_script,
6630+
};
6631+
6632+
let change_value = calculate_change_output_value(
6633+
&self,
6634+
self.shared_funding_input.is_some(),
6635+
&shared_funding_output.script_pubkey,
6636+
context.holder_dust_limit_satoshis,
6637+
)?;
6638+
6639+
if let Some(change_value) = change_value {
6640+
let change_script = match change_script {
6641+
Some(script) => script,
6642+
None => match signer_provider.get_destination_script(context.channel_keys_id) {
6643+
Ok(script) => script,
6644+
Err(_) => {
6645+
return Err(AbortReason::InternalError("Error getting change script"))
6646+
},
6647+
},
6648+
};
6649+
let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6650+
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6651+
let change_output_fee =
6652+
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6653+
let change_value_decreased_with_fee =
6654+
change_value.to_sat().saturating_sub(change_output_fee);
6655+
// Check dust limit again
6656+
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6657+
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6658+
return Ok(Some(change_output));
6659+
}
6660+
}
6661+
6662+
Ok(None)
6663+
}
6664+
66396665
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
66406666
let (contributed_inputs, contributed_outputs) = self.into_contributed_inputs_and_outputs();
66416667
NegotiationError { reason, contributed_inputs, contributed_outputs }
@@ -12043,7 +12069,7 @@ where
1204312069
let prev_funding_input = self.funding.to_splice_funding_input();
1204412070
let is_initiator = contribution.is_initiator();
1204512071
let funding_feerate_per_kw = contribution.feerate().to_sat_per_kwu() as u32;
12046-
let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts();
12072+
let (our_funding_inputs, our_funding_outputs) = contribution.into_tx_parts();
1204712073

1204812074
let context = FundingNegotiationContext {
1204912075
is_initiator,
@@ -12053,10 +12079,9 @@ where
1205312079
shared_funding_input: Some(prev_funding_input),
1205412080
our_funding_inputs,
1205512081
our_funding_outputs,
12056-
change_script,
1205712082
};
1205812083

12059-
Ok(self.send_splice_init_internal(context))
12084+
Ok(self.send_splice_init_internal(context, ChangeStrategy::FromCoinSelection))
1206012085
}
1206112086

1206212087
fn send_splice_init(&mut self, instructions: SpliceInstructions) -> msgs::SpliceInit {
@@ -12078,14 +12103,13 @@ where
1207812103
shared_funding_input: Some(prev_funding_input),
1207912104
our_funding_inputs,
1208012105
our_funding_outputs,
12081-
change_script,
1208212106
};
1208312107

12084-
self.send_splice_init_internal(context)
12108+
self.send_splice_init_internal(context, ChangeStrategy::LegacyUserProvided(change_script))
1208512109
}
1208612110

1208712111
fn send_splice_init_internal(
12088-
&mut self, context: FundingNegotiationContext,
12112+
&mut self, context: FundingNegotiationContext, change_strategy: ChangeStrategy,
1208912113
) -> msgs::SpliceInit {
1209012114
debug_assert!(self.pending_splice.is_none());
1209112115
// Rotate the funding pubkey using the prev_funding_txid as a tweak
@@ -12106,8 +12130,11 @@ where
1210612130
let funding_contribution_satoshis = context.our_funding_contribution.to_sat();
1210712131
let locktime = context.funding_tx_locktime.to_consensus_u32();
1210812132

12109-
let funding_negotiation =
12110-
FundingNegotiation::AwaitingAck { context, new_holder_funding_key: funding_pubkey };
12133+
let funding_negotiation = FundingNegotiation::AwaitingAck {
12134+
context,
12135+
change_strategy,
12136+
new_holder_funding_key: funding_pubkey,
12137+
};
1211112138
self.pending_splice = Some(PendingFunding {
1211212139
funding_negotiation: Some(funding_negotiation),
1211312140
negotiated_candidates: vec![],
@@ -12337,7 +12364,6 @@ where
1233712364
shared_funding_input: Some(prev_funding_input),
1233812365
our_funding_inputs: Vec::new(),
1233912366
our_funding_outputs: Vec::new(),
12340-
change_script: None,
1234112367
};
1234212368

1234312369
let mut interactive_tx_constructor = funding_negotiation_context
@@ -12347,6 +12373,8 @@ where
1234712373
signer_provider,
1234812374
entropy_source,
1234912375
holder_node_id.clone(),
12376+
// ChangeStrategy doesn't matter when no inputs are contributed
12377+
ChangeStrategy::FromCoinSelection,
1235012378
)
1235112379
.map_err(|err| {
1235212380
ChannelError::WarnAndDisconnect(format!(
@@ -12401,11 +12429,11 @@ where
1240112429
let pending_splice =
1240212430
self.pending_splice.as_mut().expect("We should have returned an error earlier!");
1240312431
// TODO: Good candidate for a let else statement once MSRV >= 1.65
12404-
let funding_negotiation_context =
12405-
if let Some(FundingNegotiation::AwaitingAck { context, .. }) =
12432+
let (funding_negotiation_context, change_strategy) =
12433+
if let Some(FundingNegotiation::AwaitingAck { context, change_strategy, .. }) =
1240612434
pending_splice.funding_negotiation.take()
1240712435
{
12408-
context
12436+
(context, change_strategy)
1240912437
} else {
1241012438
panic!("We should have returned an error earlier!");
1241112439
};
@@ -12417,6 +12445,7 @@ where
1241712445
signer_provider,
1241812446
entropy_source,
1241912447
holder_node_id.clone(),
12448+
change_strategy,
1242012449
)
1242112450
.map_err(|err| {
1242212451
ChannelError::WarnAndDisconnect(format!(
@@ -12447,7 +12476,7 @@ where
1244712476
let (funding_negotiation_context, new_holder_funding_key) = match &pending_splice
1244812477
.funding_negotiation
1244912478
{
12450-
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key }) => {
12479+
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key, .. }) => {
1245112480
(context, new_holder_funding_key)
1245212481
},
1245312482
Some(FundingNegotiation::ConstructingTransaction { .. })
@@ -14239,7 +14268,6 @@ where
1423914268
shared_funding_input: None,
1424014269
our_funding_inputs: funding_inputs,
1424114270
our_funding_outputs: Vec::new(),
14242-
change_script: None,
1424314271
};
1424414272
let chan = Self {
1424514273
funding,
@@ -14393,7 +14421,6 @@ where
1439314421
shared_funding_input: None,
1439414422
our_funding_inputs: our_funding_inputs.clone(),
1439514423
our_funding_outputs: Vec::new(),
14396-
change_script: None,
1439714424
};
1439814425
let shared_funding_output = TxOut {
1439914426
value: Amount::from_sat(funding.get_value_satoshis()),

0 commit comments

Comments
 (0)