Skip to content

Commit 3253a99

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 b9dfd8c commit 3253a99

File tree

5 files changed

+159
-173
lines changed

5 files changed

+159
-173
lines changed

fuzz/src/chanmon_consistency.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,7 +1859,7 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
18591859
},
18601860

18611861
0xa0 => {
1862-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1862+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18631863
if let Err(e) = nodes[0].splice_channel(
18641864
&chan_a_id,
18651865
&nodes[1].get_our_node_id(),
@@ -1873,7 +1873,7 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
18731873
}
18741874
},
18751875
0xa1 => {
1876-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1876+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18771877
if let Err(e) = nodes[1].splice_channel(
18781878
&chan_a_id,
18791879
&nodes[0].get_our_node_id(),
@@ -1887,7 +1887,7 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
18871887
}
18881888
},
18891889
0xa2 => {
1890-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1890+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18911891
if let Err(e) = nodes[1].splice_channel(
18921892
&chan_b_id,
18931893
&nodes[2].get_our_node_id(),
@@ -1901,7 +1901,7 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
19011901
}
19021902
},
19031903
0xa3 => {
1904-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1904+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
19051905
if let Err(e) = nodes[2].splice_channel(
19061906
&chan_b_id,
19071907
&nodes[1].get_our_node_id(),

lightning/src/ln/channel.rs

Lines changed: 85 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2717,6 +2717,7 @@ impl_writeable_tlv_based!(PendingFunding, {
27172717
enum FundingNegotiation {
27182718
AwaitingAck {
27192719
context: FundingNegotiationContext,
2720+
change_strategy: ChangeStrategy,
27202721
new_holder_funding_key: PublicKey,
27212722
},
27222723
ConstructingTransaction {
@@ -6541,18 +6542,25 @@ pub(super) struct FundingNegotiationContext {
65416542
/// The funding outputs we will be contributing to the channel.
65426543
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
65436544
pub our_funding_outputs: Vec<TxOut>,
6545+
}
6546+
6547+
/// How the funding transaction's change is determined.
6548+
#[derive(Debug)]
6549+
pub(super) enum ChangeStrategy {
6550+
/// The change output, if any, is included in the FundingContribution's outputs.
6551+
FromCoinSelection,
6552+
65446553
/// The change output script. This will be used if needed or -- if not set -- generated using
65456554
/// `SignerProvider::get_destination_script`.
6546-
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
6547-
pub change_script: Option<ScriptBuf>,
6555+
LegacyUserProvided(Option<ScriptBuf>),
65486556
}
65496557

65506558
impl FundingNegotiationContext {
65516559
/// Prepare and start interactive transaction negotiation.
65526560
/// If error occurs, it is caused by our side, not the counterparty.
65536561
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
65546562
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6555-
entropy_source: &ES, holder_node_id: PublicKey,
6563+
entropy_source: &ES, holder_node_id: PublicKey, change_strategy: ChangeStrategy,
65566564
) -> Result<InteractiveTxConstructor, NegotiationError>
65576565
where
65586566
SP::Target: SignerProvider,
@@ -6577,46 +6585,15 @@ impl FundingNegotiationContext {
65776585
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
65786586
};
65796587

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

66226599
let constructor_args = InteractiveTxConstructorArgs {
@@ -6638,6 +6615,55 @@ impl FundingNegotiationContext {
66386615
InteractiveTxConstructor::new(constructor_args)
66396616
}
66406617

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

1205012076
let context = FundingNegotiationContext {
1205112077
is_initiator,
@@ -12055,10 +12081,9 @@ where
1205512081
shared_funding_input: Some(prev_funding_input),
1205612082
our_funding_inputs,
1205712083
our_funding_outputs,
12058-
change_script,
1205912084
};
1206012085

12061-
Ok(self.send_splice_init_internal(context))
12086+
Ok(self.send_splice_init_internal(context, ChangeStrategy::FromCoinSelection))
1206212087
}
1206312088

1206412089
fn send_splice_init(&mut self, instructions: SpliceInstructions) -> msgs::SpliceInit {
@@ -12080,14 +12105,13 @@ where
1208012105
shared_funding_input: Some(prev_funding_input),
1208112106
our_funding_inputs,
1208212107
our_funding_outputs,
12083-
change_script,
1208412108
};
1208512109

12086-
self.send_splice_init_internal(context)
12110+
self.send_splice_init_internal(context, ChangeStrategy::LegacyUserProvided(change_script))
1208712111
}
1208812112

1208912113
fn send_splice_init_internal(
12090-
&mut self, context: FundingNegotiationContext,
12114+
&mut self, context: FundingNegotiationContext, change_strategy: ChangeStrategy,
1209112115
) -> msgs::SpliceInit {
1209212116
debug_assert!(self.pending_splice.is_none());
1209312117
// Rotate the funding pubkey using the prev_funding_txid as a tweak
@@ -12108,8 +12132,11 @@ where
1210812132
let funding_contribution_satoshis = context.our_funding_contribution.to_sat();
1210912133
let locktime = context.funding_tx_locktime.to_consensus_u32();
1211012134

12111-
let funding_negotiation =
12112-
FundingNegotiation::AwaitingAck { context, new_holder_funding_key: funding_pubkey };
12135+
let funding_negotiation = FundingNegotiation::AwaitingAck {
12136+
context,
12137+
change_strategy,
12138+
new_holder_funding_key: funding_pubkey,
12139+
};
1211312140
self.pending_splice = Some(PendingFunding {
1211412141
funding_negotiation: Some(funding_negotiation),
1211512142
negotiated_candidates: vec![],
@@ -12339,7 +12366,6 @@ where
1233912366
shared_funding_input: Some(prev_funding_input),
1234012367
our_funding_inputs: Vec::new(),
1234112368
our_funding_outputs: Vec::new(),
12342-
change_script: None,
1234312369
};
1234412370

1234512371
let mut interactive_tx_constructor = funding_negotiation_context
@@ -12349,6 +12375,8 @@ where
1234912375
signer_provider,
1235012376
entropy_source,
1235112377
holder_node_id.clone(),
12378+
// ChangeStrategy doesn't matter when no inputs are contributed
12379+
ChangeStrategy::FromCoinSelection,
1235212380
)
1235312381
.map_err(|err| {
1235412382
ChannelError::WarnAndDisconnect(format!(
@@ -12403,11 +12431,11 @@ where
1240312431
let pending_splice =
1240412432
self.pending_splice.as_mut().expect("We should have returned an error earlier!");
1240512433
// TODO: Good candidate for a let else statement once MSRV >= 1.65
12406-
let funding_negotiation_context =
12407-
if let Some(FundingNegotiation::AwaitingAck { context, .. }) =
12434+
let (funding_negotiation_context, change_strategy) =
12435+
if let Some(FundingNegotiation::AwaitingAck { context, change_strategy, .. }) =
1240812436
pending_splice.funding_negotiation.take()
1240912437
{
12410-
context
12438+
(context, change_strategy)
1241112439
} else {
1241212440
panic!("We should have returned an error earlier!");
1241312441
};
@@ -12419,6 +12447,7 @@ where
1241912447
signer_provider,
1242012448
entropy_source,
1242112449
holder_node_id.clone(),
12450+
change_strategy,
1242212451
)
1242312452
.map_err(|err| {
1242412453
ChannelError::WarnAndDisconnect(format!(
@@ -12449,7 +12478,7 @@ where
1244912478
let (funding_negotiation_context, new_holder_funding_key) = match &pending_splice
1245012479
.funding_negotiation
1245112480
{
12452-
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key }) => {
12481+
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key, .. }) => {
1245312482
(context, new_holder_funding_key)
1245412483
},
1245512484
Some(FundingNegotiation::ConstructingTransaction { .. })
@@ -14241,7 +14270,6 @@ where
1424114270
shared_funding_input: None,
1424214271
our_funding_inputs: funding_inputs,
1424314272
our_funding_outputs: Vec::new(),
14244-
change_script: None,
1424514273
};
1424614274
let chan = Self {
1424714275
funding,
@@ -14395,7 +14423,6 @@ where
1439514423
shared_funding_input: None,
1439614424
our_funding_inputs: our_funding_inputs.clone(),
1439714425
our_funding_outputs: Vec::new(),
14398-
change_script: None,
1439914426
};
1440014427
let shared_funding_output = TxOut {
1440114428
value: Amount::from_sat(funding.get_value_satoshis()),

0 commit comments

Comments
 (0)