Skip to content

Commit 42d0251

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 61caf7f commit 42d0251

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
@@ -1863,7 +1863,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
18631863
},
18641864

18651865
0xa0 => {
1866-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1866+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18671867
let feerate_sat_per_kw =
18681868
fee_estimators[0].ret_val.load(atomic::Ordering::Acquire);
18691869
let feerate = FeeRate::from_sat_per_kwu(feerate_sat_per_kw as u64);
@@ -1881,7 +1881,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
18811881
}
18821882
},
18831883
0xa1 => {
1884-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1884+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
18851885
let feerate_sat_per_kw =
18861886
fee_estimators[1].ret_val.load(atomic::Ordering::Acquire);
18871887
let feerate = FeeRate::from_sat_per_kwu(feerate_sat_per_kw as u64);
@@ -1899,7 +1899,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
18991899
}
19001900
},
19011901
0xa2 => {
1902-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1902+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
19031903
let feerate_sat_per_kw =
19041904
fee_estimators[1].ret_val.load(atomic::Ordering::Acquire);
19051905
let feerate = FeeRate::from_sat_per_kwu(feerate_sat_per_kw as u64);
@@ -1917,7 +1917,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out:
19171917
}
19181918
},
19191919
0xa3 => {
1920-
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000), None);
1920+
let contribution = SpliceContribution::splice_in(Amount::from_sat(10_000));
19211921
let feerate_sat_per_kw =
19221922
fee_estimators[2].ret_val.load(atomic::Ordering::Acquire);
19231923
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
@@ -2893,6 +2893,7 @@ impl_writeable_tlv_based!(PendingFunding, {
28932893
enum FundingNegotiation {
28942894
AwaitingAck {
28952895
context: FundingNegotiationContext,
2896+
change_strategy: ChangeStrategy,
28962897
new_holder_funding_key: PublicKey,
28972898
},
28982899
ConstructingTransaction {
@@ -6741,18 +6742,25 @@ pub(super) struct FundingNegotiationContext {
67416742
/// The funding outputs we will be contributing to the channel.
67426743
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
67436744
pub our_funding_outputs: Vec<TxOut>,
6745+
}
6746+
6747+
/// How the funding transaction's change is determined.
6748+
#[derive(Debug)]
6749+
pub(super) enum ChangeStrategy {
6750+
/// The change output, if any, is included in the FundingContribution's outputs.
6751+
FromCoinSelection,
6752+
67446753
/// The change output script. This will be used if needed or -- if not set -- generated using
67456754
/// `SignerProvider::get_destination_script`.
6746-
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
6747-
pub change_script: Option<ScriptBuf>,
6755+
LegacyUserProvided(Option<ScriptBuf>),
67486756
}
67496757

67506758
impl FundingNegotiationContext {
67516759
/// Prepare and start interactive transaction negotiation.
67526760
/// If error occurs, it is caused by our side, not the counterparty.
67536761
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
67546762
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6755-
entropy_source: &ES, holder_node_id: PublicKey,
6763+
entropy_source: &ES, holder_node_id: PublicKey, change_strategy: ChangeStrategy,
67566764
) -> Result<InteractiveTxConstructor, NegotiationError>
67576765
where
67586766
SP::Target: SignerProvider,
@@ -6777,46 +6785,15 @@ impl FundingNegotiationContext {
67776785
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
67786786
};
67796787

6780-
// Optionally add change output
6781-
let change_value_opt = if !self.our_funding_inputs.is_empty() {
6782-
match calculate_change_output_value(
6783-
&self,
6784-
self.shared_funding_input.is_some(),
6785-
&shared_funding_output.script_pubkey,
6786-
context.holder_dust_limit_satoshis,
6787-
) {
6788-
Ok(change_value_opt) => change_value_opt,
6789-
Err(reason) => {
6790-
return Err(self.into_negotiation_error(reason));
6791-
},
6792-
}
6793-
} else {
6794-
None
6795-
};
6796-
6797-
if let Some(change_value) = change_value_opt {
6798-
let change_script = if let Some(script) = self.change_script {
6799-
script
6800-
} else {
6801-
match signer_provider.get_destination_script(context.channel_keys_id) {
6802-
Ok(script) => script,
6803-
Err(_) => {
6804-
let reason = AbortReason::InternalError("Error getting change script");
6805-
return Err(self.into_negotiation_error(reason));
6806-
},
6807-
}
6808-
};
6809-
let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6810-
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6811-
let change_output_fee =
6812-
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6813-
let change_value_decreased_with_fee =
6814-
change_value.to_sat().saturating_sub(change_output_fee);
6815-
// Check dust limit again
6816-
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6817-
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6818-
self.our_funding_outputs.push(change_output);
6819-
}
6788+
match self.calculate_change_output(
6789+
context,
6790+
signer_provider,
6791+
&shared_funding_output,
6792+
change_strategy,
6793+
) {
6794+
Ok(Some(change_output)) => self.our_funding_outputs.push(change_output),
6795+
Ok(None) => {},
6796+
Err(reason) => return Err(self.into_negotiation_error(reason)),
68206797
}
68216798

68226799
let constructor_args = InteractiveTxConstructorArgs {
@@ -6838,6 +6815,55 @@ impl FundingNegotiationContext {
68386815
InteractiveTxConstructor::new(constructor_args)
68396816
}
68406817

6818+
fn calculate_change_output<SP: Deref>(
6819+
&self, context: &ChannelContext<SP>, signer_provider: &SP, shared_funding_output: &TxOut,
6820+
change_strategy: ChangeStrategy,
6821+
) -> Result<Option<TxOut>, AbortReason>
6822+
where
6823+
SP::Target: SignerProvider,
6824+
{
6825+
if self.our_funding_inputs.is_empty() {
6826+
return Ok(None);
6827+
}
6828+
6829+
let change_script = match change_strategy {
6830+
ChangeStrategy::FromCoinSelection => return Ok(None),
6831+
ChangeStrategy::LegacyUserProvided(change_script) => change_script,
6832+
};
6833+
6834+
let change_value = calculate_change_output_value(
6835+
&self,
6836+
self.shared_funding_input.is_some(),
6837+
&shared_funding_output.script_pubkey,
6838+
context.holder_dust_limit_satoshis,
6839+
)?;
6840+
6841+
if let Some(change_value) = change_value {
6842+
let change_script = match change_script {
6843+
Some(script) => script,
6844+
None => match signer_provider.get_destination_script(context.channel_keys_id) {
6845+
Ok(script) => script,
6846+
Err(_) => {
6847+
return Err(AbortReason::InternalError("Error getting change script"))
6848+
},
6849+
},
6850+
};
6851+
let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6852+
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6853+
let change_output_fee =
6854+
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6855+
let change_value_decreased_with_fee =
6856+
change_value.to_sat().saturating_sub(change_output_fee);
6857+
// Check dust limit again
6858+
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6859+
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6860+
return Ok(Some(change_output));
6861+
}
6862+
}
6863+
6864+
Ok(None)
6865+
}
6866+
68416867
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
68426868
let (contributed_inputs, contributed_outputs) = self.into_contributed_inputs_and_outputs();
68436869
NegotiationError { reason, contributed_inputs, contributed_outputs }
@@ -12262,14 +12288,13 @@ where
1226212288
shared_funding_input: Some(prev_funding_input),
1226312289
our_funding_inputs,
1226412290
our_funding_outputs,
12265-
change_script,
1226612291
};
1226712292

12268-
self.send_splice_init_internal(context)
12293+
self.send_splice_init_internal(context, ChangeStrategy::LegacyUserProvided(change_script))
1226912294
}
1227012295

1227112296
fn send_splice_init_internal(
12272-
&mut self, context: FundingNegotiationContext,
12297+
&mut self, context: FundingNegotiationContext, change_strategy: ChangeStrategy,
1227312298
) -> msgs::SpliceInit {
1227412299
debug_assert!(self.pending_splice.is_none());
1227512300
// Rotate the funding pubkey using the prev_funding_txid as a tweak
@@ -12290,8 +12315,11 @@ where
1229012315
let funding_contribution_satoshis = context.our_funding_contribution.to_sat();
1229112316
let locktime = context.funding_tx_locktime.to_consensus_u32();
1229212317

12293-
let funding_negotiation =
12294-
FundingNegotiation::AwaitingAck { context, new_holder_funding_key: funding_pubkey };
12318+
let funding_negotiation = FundingNegotiation::AwaitingAck {
12319+
context,
12320+
change_strategy,
12321+
new_holder_funding_key: funding_pubkey,
12322+
};
1229512323
self.pending_splice = Some(PendingFunding {
1229612324
funding_negotiation: Some(funding_negotiation),
1229712325
negotiated_candidates: vec![],
@@ -12521,7 +12549,6 @@ where
1252112549
shared_funding_input: Some(prev_funding_input),
1252212550
our_funding_inputs: Vec::new(),
1252312551
our_funding_outputs: Vec::new(),
12524-
change_script: None,
1252512552
};
1252612553

1252712554
let mut interactive_tx_constructor = funding_negotiation_context
@@ -12531,6 +12558,8 @@ where
1253112558
signer_provider,
1253212559
entropy_source,
1253312560
holder_node_id.clone(),
12561+
// ChangeStrategy doesn't matter when no inputs are contributed
12562+
ChangeStrategy::FromCoinSelection,
1253412563
)
1253512564
.map_err(|err| {
1253612565
ChannelError::WarnAndDisconnect(format!(
@@ -12585,11 +12614,11 @@ where
1258512614
let pending_splice =
1258612615
self.pending_splice.as_mut().expect("We should have returned an error earlier!");
1258712616
// TODO: Good candidate for a let else statement once MSRV >= 1.65
12588-
let funding_negotiation_context =
12589-
if let Some(FundingNegotiation::AwaitingAck { context, .. }) =
12617+
let (funding_negotiation_context, change_strategy) =
12618+
if let Some(FundingNegotiation::AwaitingAck { context, change_strategy, .. }) =
1259012619
pending_splice.funding_negotiation.take()
1259112620
{
12592-
context
12621+
(context, change_strategy)
1259312622
} else {
1259412623
panic!("We should have returned an error earlier!");
1259512624
};
@@ -12601,6 +12630,7 @@ where
1260112630
signer_provider,
1260212631
entropy_source,
1260312632
holder_node_id.clone(),
12633+
change_strategy,
1260412634
)
1260512635
.map_err(|err| {
1260612636
ChannelError::WarnAndDisconnect(format!(
@@ -12631,7 +12661,7 @@ where
1263112661
let (funding_negotiation_context, new_holder_funding_key) = match &pending_splice
1263212662
.funding_negotiation
1263312663
{
12634-
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key }) => {
12664+
Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key, .. }) => {
1263512665
(context, new_holder_funding_key)
1263612666
},
1263712667
Some(FundingNegotiation::ConstructingTransaction { .. })
@@ -13594,7 +13624,7 @@ where
1359413624
},
1359513625
};
1359613626
let funding_feerate_per_kw = contribution.feerate().to_sat_per_kwu() as u32;
13597-
let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts();
13627+
let (our_funding_inputs, our_funding_outputs) = contribution.into_tx_parts();
1359813628

1359913629
let context = FundingNegotiationContext {
1360013630
is_initiator,
@@ -13604,10 +13634,9 @@ where
1360413634
shared_funding_input: Some(prev_funding_input),
1360513635
our_funding_inputs,
1360613636
our_funding_outputs,
13607-
change_script,
1360813637
};
1360913638

13610-
let splice_init = self.send_splice_init_internal(context);
13639+
let splice_init = self.send_splice_init_internal(context, ChangeStrategy::FromCoinSelection);
1361113640
return Ok(Some(StfuResponse::SpliceInit(splice_init)));
1361213641
},
1361313642
#[cfg(any(test, fuzzing))]
@@ -14444,7 +14473,6 @@ where
1444414473
shared_funding_input: None,
1444514474
our_funding_inputs: funding_inputs,
1444614475
our_funding_outputs: Vec::new(),
14447-
change_script: None,
1444814476
};
1444914477
let chan = Self {
1445014478
funding,
@@ -14598,7 +14626,6 @@ where
1459814626
shared_funding_input: None,
1459914627
our_funding_inputs: our_funding_inputs.clone(),
1460014628
our_funding_outputs: Vec::new(),
14601-
change_script: None,
1460214629
};
1460314630
let shared_funding_output = TxOut {
1460414631
value: Amount::from_sat(funding.get_value_satoshis()),

0 commit comments

Comments
 (0)