Skip to content

Commit 1272772

Browse files
wpaulinojkczyz
andcommitted
Refactor FundingNegotiationContext and PendingSplice
FundingNegotiationContext and PendingSplice both hold the user's contribution to a splice, which doesn't need to be duplicated. Instead, only store this in FundingNegotiationContext, which then can be used to create an InteractiveTxConstructor when transitioning to FundingNegotiation:::ConstructingTransaction. This commit updates that code to properly compute change outputs using the FundingNegotiationContext by not considering the shared input since it is accounted for in the shared output. Co-authored-by: Wilmer Paulino <[email protected]> Co-authored-by: Jeffrey Czyz <[email protected]>
1 parent f1db67e commit 1272772

File tree

3 files changed

+191
-219
lines changed

3 files changed

+191
-219
lines changed

lightning/src/ln/channel.rs

Lines changed: 108 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@ use crate::ln::channelmanager::{
5757
};
5858
use crate::ln::interactivetxs::{
5959
calculate_change_output_value, get_output_weight, AbortReason, HandleTxCompleteResult,
60-
InteractiveTxConstructor, InteractiveTxConstructorArgs, InteractiveTxMessageSend,
61-
InteractiveTxMessageSendResult, InteractiveTxSigningSession, SharedOwnedOutput,
62-
TX_COMMON_FIELDS_WEIGHT,
60+
InteractiveTxConstructor, InteractiveTxConstructorArgs, InteractiveTxMessageSendResult,
61+
InteractiveTxSigningSession, SharedOwnedInput, SharedOwnedOutput, TX_COMMON_FIELDS_WEIGHT,
6362
};
6463
use crate::ln::msgs;
6564
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError, OnionErrorPacket};
@@ -2174,7 +2173,6 @@ impl FundingScope {
21742173
/// Info about a pending splice, used in the pre-splice channel
21752174
#[cfg(splicing)]
21762175
struct PendingSplice {
2177-
pub our_funding_contribution: i64,
21782176
funding_negotiation: Option<FundingNegotiation>,
21792177

21802178
/// The funding txid used in the `splice_locked` sent to the counterparty.
@@ -2778,86 +2776,6 @@ impl<SP: Deref> PendingV2Channel<SP>
27782776
where
27792777
SP::Target: SignerProvider,
27802778
{
2781-
/// Prepare and start interactive transaction negotiation.
2782-
/// `change_destination_opt` - Optional destination for optional change; if None,
2783-
/// default destination address is used.
2784-
/// If error occurs, it is caused by our side, not the counterparty.
2785-
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled
2786-
#[rustfmt::skip]
2787-
fn begin_interactive_funding_tx_construction<ES: Deref>(
2788-
&mut self, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey,
2789-
change_destination_opt: Option<ScriptBuf>,
2790-
) -> Result<Option<InteractiveTxMessageSend>, AbortReason>
2791-
where ES::Target: EntropySource
2792-
{
2793-
debug_assert!(matches!(self.context.channel_state, ChannelState::NegotiatingFunding(_)));
2794-
debug_assert!(self.interactive_tx_constructor.is_none());
2795-
2796-
let mut funding_inputs = Vec::new();
2797-
mem::swap(&mut self.funding_negotiation_context.our_funding_inputs, &mut funding_inputs);
2798-
2799-
// TODO(splicing): Add prev funding tx as input, must be provided as a parameter
2800-
2801-
// Add output for funding tx
2802-
// Note: For the error case when the inputs are insufficient, it will be handled after
2803-
// the `calculate_change_output_value` call below
2804-
let mut funding_outputs = Vec::new();
2805-
2806-
let shared_funding_output = TxOut {
2807-
value: Amount::from_sat(self.funding.get_value_satoshis()),
2808-
script_pubkey: self.funding.get_funding_redeemscript().to_p2wsh(),
2809-
};
2810-
2811-
// Optionally add change output
2812-
let change_script = if let Some(script) = change_destination_opt {
2813-
script
2814-
} else {
2815-
signer_provider.get_destination_script(self.context.channel_keys_id)
2816-
.map_err(|_err| AbortReason::InternalError("Error getting destination script"))?
2817-
};
2818-
let change_value_opt = calculate_change_output_value(
2819-
self.funding.is_outbound(), self.funding_negotiation_context.our_funding_satoshis,
2820-
&funding_inputs, None,
2821-
&shared_funding_output.script_pubkey, &funding_outputs,
2822-
self.funding_negotiation_context.funding_feerate_sat_per_1000_weight,
2823-
change_script.minimal_non_dust().to_sat(),
2824-
)?;
2825-
if let Some(change_value) = change_value_opt {
2826-
let mut change_output = TxOut {
2827-
value: Amount::from_sat(change_value),
2828-
script_pubkey: change_script,
2829-
};
2830-
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
2831-
let change_output_fee = fee_for_weight(self.funding_negotiation_context.funding_feerate_sat_per_1000_weight, change_output_weight);
2832-
let change_value_decreased_with_fee = change_value.saturating_sub(change_output_fee);
2833-
// Check dust limit again
2834-
if change_value_decreased_with_fee > self.context.holder_dust_limit_satoshis {
2835-
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
2836-
funding_outputs.push(change_output);
2837-
}
2838-
}
2839-
2840-
let constructor_args = InteractiveTxConstructorArgs {
2841-
entropy_source,
2842-
holder_node_id,
2843-
counterparty_node_id: self.context.counterparty_node_id,
2844-
channel_id: self.context.channel_id(),
2845-
feerate_sat_per_kw: self.funding_negotiation_context.funding_feerate_sat_per_1000_weight,
2846-
is_initiator: self.funding.is_outbound(),
2847-
funding_tx_locktime: self.funding_negotiation_context.funding_tx_locktime,
2848-
inputs_to_contribute: funding_inputs,
2849-
shared_funding_input: None,
2850-
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, self.funding_negotiation_context.our_funding_satoshis),
2851-
outputs_to_contribute: funding_outputs,
2852-
};
2853-
let mut tx_constructor = InteractiveTxConstructor::new(constructor_args)?;
2854-
let msg = tx_constructor.take_initiator_first_message();
2855-
2856-
self.interactive_tx_constructor = Some(tx_constructor);
2857-
2858-
Ok(msg)
2859-
}
2860-
28612779
pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult {
28622780
InteractiveTxMessageSendResult(match &mut self.interactive_tx_constructor {
28632781
Some(ref mut tx_constructor) => tx_constructor
@@ -2937,7 +2855,6 @@ where
29372855
where
29382856
L::Target: Logger
29392857
{
2940-
let our_funding_satoshis = self.funding_negotiation_context.our_funding_satoshis;
29412858
let transaction_number = self.unfunded_context.transaction_number();
29422859

29432860
let mut output_index = None;
@@ -2972,7 +2889,7 @@ where
29722889
};
29732890

29742891
let funding_ready_for_sig_event = if signing_session.local_inputs_count() == 0 {
2975-
debug_assert_eq!(our_funding_satoshis, 0);
2892+
debug_assert_eq!(self.funding_negotiation_context.our_funding_contribution_satoshis, 0);
29762893
if signing_session.provide_holder_witnesses(self.context.channel_id, Vec::new()).is_err() {
29772894
debug_assert!(
29782895
false,
@@ -5877,28 +5794,107 @@ fn check_v2_funding_inputs_sufficient(
58775794

58785795
/// Context for negotiating channels (dual-funded V2 open, splicing)
58795796
pub(super) struct FundingNegotiationContext {
5797+
/// Whether we initiated the funding negotiation.
5798+
pub is_initiator: bool,
58805799
/// The amount in satoshis we will be contributing to the channel.
5881-
pub our_funding_satoshis: u64,
5800+
pub our_funding_contribution_satoshis: i64,
58825801
/// The amount in satoshis our counterparty will be contributing to the channel.
58835802
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
5884-
pub their_funding_satoshis: Option<u64>,
5803+
pub their_funding_contribution_satoshis: Option<i64>,
58855804
/// The funding transaction locktime suggested by the initiator. If set by us, it is always set
58865805
/// to the current block height to align incentives against fee-sniping.
58875806
pub funding_tx_locktime: LockTime,
58885807
/// The feerate set by the initiator to be used for the funding transaction.
58895808
#[allow(dead_code)] // TODO(dual_funding): Remove once V2 channels is enabled.
58905809
pub funding_feerate_sat_per_1000_weight: u32,
58915810
/// The funding inputs we will be contributing to the channel.
5892-
///
5893-
/// Note that the `our_funding_satoshis` field is equal to the total value of `our_funding_inputs`
5894-
/// minus any fees paid for our contributed weight. This means that change will never be generated
5895-
/// and the maximum value possible will go towards funding the channel.
5896-
///
5897-
/// Note that this field may be emptied once the interactive negotiation has been started.
58985811
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
58995812
pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>,
59005813
}
59015814

5815+
impl FundingNegotiationContext {
5816+
/// Prepare and start interactive transaction negotiation.
5817+
/// `change_destination_opt` - Optional destination for optional change; if None,
5818+
/// default destination address is used.
5819+
/// If error occurs, it is caused by our side, not the counterparty.
5820+
#[cfg(splicing)]
5821+
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
5822+
self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
5823+
entropy_source: &ES, holder_node_id: PublicKey, change_destination_opt: Option<ScriptBuf>,
5824+
shared_funding_input: Option<SharedOwnedInput>,
5825+
) -> Result<InteractiveTxConstructor, AbortReason>
5826+
where
5827+
SP::Target: SignerProvider,
5828+
ES::Target: EntropySource,
5829+
{
5830+
if shared_funding_input.is_some() {
5831+
debug_assert!(matches!(context.channel_state, ChannelState::ChannelReady(_)));
5832+
} else {
5833+
debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_)));
5834+
}
5835+
5836+
// Add output for funding tx
5837+
// Note: For the error case when the inputs are insufficient, it will be handled after
5838+
// the `calculate_change_output_value` call below
5839+
let mut funding_outputs = Vec::new();
5840+
5841+
let shared_funding_output = TxOut {
5842+
value: Amount::from_sat(funding.get_value_satoshis()),
5843+
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
5844+
};
5845+
5846+
// Optionally add change output
5847+
if self.our_funding_contribution_satoshis > 0 {
5848+
let change_value_opt = calculate_change_output_value(
5849+
&self,
5850+
funding.channel_transaction_parameters.splice_parent_funding_txid.is_some(),
5851+
&shared_funding_output.script_pubkey,
5852+
&funding_outputs,
5853+
context.holder_dust_limit_satoshis,
5854+
)?;
5855+
if let Some(change_value) = change_value_opt {
5856+
let change_script = if let Some(script) = change_destination_opt {
5857+
script
5858+
} else {
5859+
signer_provider.get_destination_script(context.channel_keys_id).map_err(
5860+
|_err| AbortReason::InternalError("Error getting destination script"),
5861+
)?
5862+
};
5863+
let mut change_output =
5864+
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
5865+
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
5866+
let change_output_fee =
5867+
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
5868+
let change_value_decreased_with_fee =
5869+
change_value.saturating_sub(change_output_fee);
5870+
// Check dust limit again
5871+
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
5872+
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
5873+
funding_outputs.push(change_output);
5874+
}
5875+
}
5876+
}
5877+
5878+
let constructor_args = InteractiveTxConstructorArgs {
5879+
entropy_source,
5880+
holder_node_id,
5881+
counterparty_node_id: context.counterparty_node_id,
5882+
channel_id: context.channel_id(),
5883+
feerate_sat_per_kw: self.funding_feerate_sat_per_1000_weight,
5884+
is_initiator: self.is_initiator,
5885+
funding_tx_locktime: self.funding_tx_locktime,
5886+
inputs_to_contribute: self.our_funding_inputs,
5887+
shared_funding_input,
5888+
shared_funding_output: SharedOwnedOutput::new(
5889+
shared_funding_output,
5890+
funding.value_to_self_msat / 1000,
5891+
),
5892+
outputs_to_contribute: funding_outputs,
5893+
};
5894+
InteractiveTxConstructor::new(constructor_args)
5895+
}
5896+
}
5897+
59025898
// Holder designates channel data owned for the benefit of the user client.
59035899
// Counterparty designates channel data owned by the another channel participant entity.
59045900
pub(super) struct FundedChannel<SP: Deref>
@@ -10424,11 +10420,13 @@ where
1042410420
) -> Result<msgs::SpliceInit, APIError> {
1042510421
// Check if a splice has been initiated already.
1042610422
// Note: only a single outstanding splice is supported (per spec)
10427-
if let Some(splice_info) = &self.pending_splice {
10428-
return Err(APIError::APIMisuseError { err: format!(
10429-
"Channel {} cannot be spliced, as it has already a splice pending (contribution {})",
10430-
self.context.channel_id(), splice_info.our_funding_contribution
10431-
)});
10423+
if self.pending_splice.is_some() {
10424+
return Err(APIError::APIMisuseError {
10425+
err: format!(
10426+
"Channel {} cannot be spliced, as it has already a splice pending",
10427+
self.context.channel_id(),
10428+
),
10429+
});
1043210430
}
1043310431

1043410432
if !self.context.is_live() {
@@ -10461,7 +10459,6 @@ where
1046110459
)})?;
1046210460

1046310461
self.pending_splice = Some(PendingSplice {
10464-
our_funding_contribution: our_funding_contribution_satoshis,
1046510462
funding_negotiation: None,
1046610463
sent_funding_txid: None,
1046710464
received_funding_txid: None,
@@ -10498,9 +10495,10 @@ where
1049810495
let our_funding_contribution_satoshis = 0i64;
1049910496

1050010497
// Check if a splice has been initiated already.
10501-
if let Some(splice_info) = &self.pending_splice {
10498+
if self.pending_splice.is_some() {
1050210499
return Err(ChannelError::Warn(format!(
10503-
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution,
10500+
"Channel {} already has a splice pending",
10501+
self.context.channel_id(),
1050410502
)));
1050510503
}
1050610504

@@ -12111,9 +12109,10 @@ where
1211112109
holder_commitment_point: HolderCommitmentPoint::new(&context.holder_signer, &context.secp_ctx),
1211212110
};
1211312111
let funding_negotiation_context = FundingNegotiationContext {
12114-
our_funding_satoshis: funding_satoshis,
12112+
is_initiator: true,
12113+
our_funding_contribution_satoshis: funding_satoshis as i64,
1211512114
// TODO(dual_funding) TODO(splicing) Include counterparty contribution, once that's enabled
12116-
their_funding_satoshis: None,
12115+
their_funding_contribution_satoshis: None,
1211712116
funding_tx_locktime,
1211812117
funding_feerate_sat_per_1000_weight,
1211912118
our_funding_inputs: funding_inputs,
@@ -12265,8 +12264,9 @@ where
1226512264
context.channel_id = channel_id;
1226612265

1226712266
let funding_negotiation_context = FundingNegotiationContext {
12268-
our_funding_satoshis: our_funding_satoshis,
12269-
their_funding_satoshis: Some(msg.common_fields.funding_satoshis),
12267+
is_initiator: false,
12268+
our_funding_contribution_satoshis: our_funding_satoshis as i64,
12269+
their_funding_contribution_satoshis: Some(msg.common_fields.funding_satoshis as i64),
1227012270
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1227112271
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
1227212272
our_funding_inputs: our_funding_inputs.clone(),
@@ -12368,7 +12368,8 @@ where
1236812368
}),
1236912369
channel_type: Some(self.funding.get_channel_type().clone()),
1237012370
},
12371-
funding_satoshis: self.funding_negotiation_context.our_funding_satoshis,
12371+
funding_satoshis: self.funding_negotiation_context.our_funding_contribution_satoshis
12372+
as u64,
1237212373
second_per_commitment_point,
1237312374
require_confirmed_inputs: None,
1237412375
}

lightning/src/ln/channelmanager.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9190,7 +9190,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
91909190

91919191
// Inbound V2 channels with contributed inputs are not considered unfunded.
91929192
if let Some(unfunded_chan) = chan.as_unfunded_v2() {
9193-
if unfunded_chan.funding_negotiation_context.our_funding_satoshis != 0 {
9193+
if unfunded_chan.funding_negotiation_context.our_funding_contribution_satoshis > 0 {
91949194
continue;
91959195
}
91969196
}

0 commit comments

Comments
 (0)