Stop persisting QuiescentAction and remove legacy code
What changed, and why it matters
This commit removes old, no-longer-used code paths for splicing (a way to resize a Lightning channel). It stops saving a 'quiescent action' to disk because the only remaining variant cannot be serialized, and it drops legacy change-output calculation logic. On loading old data, the stored action is read and then discarded, and the 'awaiting quiescence' flag is cleared so the node does not get stuck waiting for something it can no longer act upon. The change is mostly cleanup and hardening against stale persisted state.
Review the backwards-compatibility path to confirm that discarding TLV 65 and clearing awaiting_quiescence cannot leave a channel in an inconsistent or exploitable state after an upgrade from 0.2. Ensure tests cover upgrade scenarios where a LegacySplice or persisted Splice quiescent_action existed. No immediate patch is indicated beyond normal review and regression testing.
Security signals we found
Removal of persisted state that could not be serialized (FundingContribution inside QuiescentAction::Splice)
Clearing awaiting_quiescence flag on deserialization to prevent a channel from being stuck in an unactionable quiescent state
Backwards-compatible deserialization that discards obsolete TLV 65 data
Dead-code elimination in funding transaction construction paths
Reduction of API surface around signer_provider in splice flow
Evidence from the diff
The patch deletes QuiescentAction::LegacySplice, SpliceInstructions, ChangeStrategy, calculate_change_output, calculate_change_output_value, and the legacy send_splice_init method. QuiescentAction is no longer written during serialization (TLV 65 is removed from the write path), but on deserialization TLV 65 is still parsed for backwards compatibility and then thrown away, and the awaiting_quiescence channel flag is reset. splice_init/splice_ack and into_interactive_tx_constructor lose their signer_provider parameter because change-output derivation is gone. FundingContribution’s serialization impl is also removed (it is now produced transiently and not persisted).
Changed components
lightning/src/ln/channel.rslightning/src/ln/channelmanager.rslightning/src/ln/funding.rslightning/src/ln/interactivetxs.rslightning/src/ln/splicing_tests.rsInspect captured patch +36 / −497
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 7fc1b34..9dff609 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -28,7 +28,7 @@ use bitcoin::{secp256k1, sighash, FeeRate, Sequence, TxIn};
use crate::blinded_path::message::BlindedMessagePath;
use crate::chain::chaininterface::{
- fee_for_weight, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, TransactionType,
+ ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, TransactionType,
};
use crate::chain::channelmonitor::{
ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, CommitmentHTLCData,
@@ -57,9 +57,9 @@ use crate::ln::channelmanager::{
};
use crate::ln::funding::{FundingContribution, FundingTemplate, FundingTxInput};
use crate::ln::interactivetxs::{
- calculate_change_output_value, get_output_weight, AbortReason, HandleTxCompleteValue,
- InteractiveTxConstructor, InteractiveTxConstructorArgs, InteractiveTxMessageSend,
- InteractiveTxSigningSession, NegotiationError, SharedOwnedInput, SharedOwnedOutput,
+ AbortReason, HandleTxCompleteValue, InteractiveTxConstructor, InteractiveTxConstructorArgs,
+ InteractiveTxMessageSend, InteractiveTxSigningSession, NegotiationError, SharedOwnedInput,
+ SharedOwnedOutput,
};
use crate::ln::msgs;
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError, OnionErrorPacket};
@@ -2910,7 +2910,6 @@ impl_writeable_tlv_based!(PendingFunding, {
enum FundingNegotiation {
AwaitingAck {
context: FundingNegotiationContext,
- change_strategy: ChangeStrategy,
new_holder_funding_key: PublicKey,
},
ConstructingTransaction {
@@ -2996,38 +2995,8 @@ impl PendingFunding {
}
}
-#[derive(Debug)]
-pub(crate) struct SpliceInstructions {
- adjusted_funding_contribution: SignedAmount,
- our_funding_inputs: Vec<FundingTxInput>,
- our_funding_outputs: Vec<TxOut>,
- change_script: Option<ScriptBuf>,
- funding_feerate_per_kw: u32,
- locktime: u32,
-}
-
-impl SpliceInstructions {
- fn into_contributed_inputs_and_outputs(self) -> (Vec<bitcoin::OutPoint>, Vec<TxOut>) {
- (
- self.our_funding_inputs.into_iter().map(|input| input.utxo.outpoint).collect(),
- self.our_funding_outputs,
- )
- }
-}
-
-impl_writeable_tlv_based!(SpliceInstructions, {
- (1, adjusted_funding_contribution, required),
- (3, our_funding_inputs, required_vec),
- (5, our_funding_outputs, required_vec),
- (7, change_script, option),
- (9, funding_feerate_per_kw, required),
- (11, locktime, required),
-});
-
#[derive(Debug)]
pub(crate) enum QuiescentAction {
- // Deprecated in favor of the Splice variant and no longer produced as of LDK 0.3.
- LegacySplice(SpliceInstructions),
Splice {
contribution: FundingContribution,
locktime: LockTime,
@@ -3045,10 +3014,6 @@ pub(super) enum QuiescentError {
impl From<QuiescentAction> for QuiescentError {
fn from(action: QuiescentAction) -> Self {
match action {
- QuiescentAction::LegacySplice(_) => {
- debug_assert!(false);
- QuiescentError::DoNothing
- },
QuiescentAction::Splice { contribution, .. } => {
let (contributed_inputs, contributed_outputs) =
contribution.into_contributed_inputs_and_outputs();
@@ -3070,24 +3035,6 @@ pub(crate) enum StfuResponse {
SpliceInit(msgs::SpliceInit),
}
-#[cfg(any(test, fuzzing, feature = "_test_utils"))]
-impl_writeable_tlv_based_enum_upgradable!(QuiescentAction,
- (0, DoNothing) => {},
- (2, Splice) => {
- (0, contribution, required),
- (1, locktime, required),
- },
- {1, LegacySplice} => (),
-);
-#[cfg(not(any(test, fuzzing, feature = "_test_utils")))]
-impl_writeable_tlv_based_enum_upgradable!(QuiescentAction,
- (2, Splice) => {
- (0, contribution, required),
- (1, locktime, required),
- },
- {1, LegacySplice} => (),
-);
-
/// Wrapper around a [`Transaction`] useful for caching the result of [`Transaction::compute_txid`].
struct ConfirmedTransaction<'a> {
tx: &'a Transaction,
@@ -6393,23 +6340,12 @@ pub(super) struct FundingNegotiationContext {
pub our_funding_outputs: Vec<TxOut>,
}
-/// How the funding transaction's change is determined.
-#[derive(Debug)]
-pub(super) enum ChangeStrategy {
- /// The change output, if any, is included in the FundingContribution's outputs.
- FromCoinSelection,
-
- /// The change output script. This will be used if needed or -- if not set -- generated using
- /// `SignerProvider::get_destination_script`.
- LegacyUserProvided(Option<ScriptBuf>),
-}
-
impl FundingNegotiationContext {
/// Prepare and start interactive transaction negotiation.
/// If error occurs, it is caused by our side, not the counterparty.
fn into_interactive_tx_constructor<SP: SignerProvider, ES: EntropySource>(
- mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
- entropy_source: &ES, holder_node_id: PublicKey, change_strategy: ChangeStrategy,
+ self, context: &ChannelContext<SP>, funding: &FundingScope, entropy_source: &ES,
+ holder_node_id: PublicKey,
) -> Result<InteractiveTxConstructor, NegotiationError> {
debug_assert_eq!(
self.shared_funding_input.is_some(),
@@ -6422,25 +6358,11 @@ impl FundingNegotiationContext {
debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_)));
}
- // Note: For the error case when the inputs are insufficient, it will be handled after
- // the `calculate_change_output_value` call below
-
let shared_funding_output = TxOut {
value: Amount::from_sat(funding.get_value_satoshis()),
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
};
- match self.calculate_change_output(
- context,
- signer_provider,
- &shared_funding_output,
- change_strategy,
- ) {
- Ok(Some(change_output)) => self.our_funding_outputs.push(change_output),
- Ok(None) => {},
- Err(reason) => return Err(self.into_negotiation_error(reason)),
- }
-
let constructor_args = InteractiveTxConstructorArgs {
entropy_source,
holder_node_id,
@@ -6460,57 +6382,6 @@ impl FundingNegotiationContext {
InteractiveTxConstructor::new(constructor_args)
}
- fn calculate_change_output<SP: SignerProvider>(
- &self, context: &ChannelContext<SP>, signer_provider: &SP, shared_funding_output: &TxOut,
- change_strategy: ChangeStrategy,
- ) -> Result<Option<TxOut>, AbortReason> {
- if self.our_funding_inputs.is_empty() {
- return Ok(None);
- }
-
- let change_script = match change_strategy {
- ChangeStrategy::FromCoinSelection => return Ok(None),
- ChangeStrategy::LegacyUserProvided(change_script) => change_script,
- };
-
- let change_value = calculate_change_output_value(
- &self,
- self.shared_funding_input.is_some(),
- &shared_funding_output.script_pubkey,
- context.holder_dust_limit_satoshis,
- )?;
-
- if let Some(change_value) = change_value {
- let change_script = match change_script {
- Some(script) => script,
- None => match signer_provider.get_destination_script(context.channel_keys_id) {
- Ok(script) => script,
- Err(_) => {
- return Err(AbortReason::InternalError("Error getting change script"))
- },
- },
- };
- let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
- let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
- let change_output_fee =
- fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
- let change_value_decreased_with_fee =
- change_value.to_sat().saturating_sub(change_output_fee);
- // Check dust limit again
- if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
- change_output.value = Amount::from_sat(change_value_decreased_with_fee);
- return Ok(Some(change_output));
- }
- }
-
- Ok(None)
- }
-
- fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
- let (contributed_inputs, contributed_outputs) = self.into_contributed_inputs_and_outputs();
- NegotiationError { reason, contributed_inputs, contributed_outputs }
- }
-
fn into_contributed_inputs_and_outputs(self) -> (Vec<bitcoin::OutPoint>, Vec<TxOut>) {
let contributed_inputs =
self.our_funding_inputs.into_iter().map(|input| input.utxo.outpoint).collect();
@@ -6757,15 +6628,6 @@ where
fn abandon_quiescent_action(&mut self) -> Option<SpliceFundingFailed> {
match self.quiescent_action.take() {
- Some(QuiescentAction::LegacySplice(instructions)) => {
- let (inputs, outputs) = instructions.into_contributed_inputs_and_outputs();
- Some(SpliceFundingFailed {
- funding_txo: None,
- channel_type: None,
- contributed_inputs: inputs,
- contributed_outputs: outputs,
- })
- },
Some(QuiescentAction::Splice { contribution, .. }) => {
let (inputs, outputs) = contribution.into_contributed_inputs_and_outputs();
Some(SpliceFundingFailed {
@@ -11974,33 +11836,7 @@ where
self.propose_quiescence(logger, QuiescentAction::Splice { contribution, locktime })
}
- fn send_splice_init(&mut self, instructions: SpliceInstructions) -> msgs::SpliceInit {
- let SpliceInstructions {
- adjusted_funding_contribution,
- our_funding_inputs,
- our_funding_outputs,
- change_script,
- funding_feerate_per_kw,
- locktime,
- } = instructions;
-
- let prev_funding_input = self.funding.to_splice_funding_input();
- let context = FundingNegotiationContext {
- is_initiator: true,
- our_funding_contribution: adjusted_funding_contribution,
- funding_tx_locktime: LockTime::from_consensus(locktime),
- funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,
- shared_funding_input: Some(prev_funding_input),
- our_funding_inputs,
- our_funding_outputs,
- };
-
- self.send_splice_init_internal(context, ChangeStrategy::LegacyUserProvided(change_script))
- }
-
- fn send_splice_init_internal(
- &mut self, context: FundingNegotiationContext, change_strategy: ChangeStrategy,
- ) -> msgs::SpliceInit {
+ fn send_splice_init(&mut self, context: FundingNegotiationContext) -> msgs::SpliceInit {
debug_assert!(self.pending_splice.is_none());
// Rotate the funding pubkey using the prev_funding_txid as a tweak
let prev_funding_txid = self.funding.get_funding_txid();
@@ -12020,11 +11856,8 @@ where
let funding_contribution_satoshis = context.our_funding_contribution.to_sat();
let locktime = context.funding_tx_locktime.to_consensus_u32();
- let funding_negotiation = FundingNegotiation::AwaitingAck {
- context,
- change_strategy,
- new_holder_funding_key: funding_pubkey,
- };
+ let funding_negotiation =
+ FundingNegotiation::AwaitingAck { context, new_holder_funding_key: funding_pubkey };
self.pending_splice = Some(PendingFunding {
funding_negotiation: Some(funding_negotiation),
negotiated_candidates: vec![],
@@ -12228,7 +12061,7 @@ where
pub(crate) fn splice_init<ES: EntropySource, L: Logger>(
&mut self, msg: &msgs::SpliceInit, our_funding_contribution_satoshis: i64,
- signer_provider: &SP, entropy_source: &ES, holder_node_id: &PublicKey, logger: &L,
+ entropy_source: &ES, holder_node_id: &PublicKey, logger: &L,
) -> Result<msgs::SpliceAck, ChannelError> {
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
let splice_funding = self.validate_splice_init(msg, our_funding_contribution)?;
@@ -12256,11 +12089,8 @@ where
.into_interactive_tx_constructor(
&self.context,
&splice_funding,
- signer_provider,
entropy_source,
holder_node_id.clone(),
- // ChangeStrategy doesn't matter when no inputs are contributed
- ChangeStrategy::FromCoinSelection,
)
.map_err(|err| {
ChannelError::WarnAndDisconnect(format!(
@@ -12295,8 +12125,8 @@ where
}
pub(crate) fn splice_ack<ES: EntropySource, L: Logger>(
- &mut self, msg: &msgs::SpliceAck, signer_provider: &SP, entropy_source: &ES,
- holder_node_id: &PublicKey, logger: &L,
+ &mut self, msg: &msgs::SpliceAck, entropy_source: &ES, holder_node_id: &PublicKey,
+ logger: &L,
) -> Result<Option<InteractiveTxMessageSend>, ChannelError> {
let splice_funding = self.validate_splice_ack(msg)?;
@@ -12311,11 +12141,11 @@ where
let pending_splice =
self.pending_splice.as_mut().expect("We should have returned an error earlier!");
// TODO: Good candidate for a let else statement once MSRV >= 1.65
- let (funding_negotiation_context, change_strategy) =
- if let Some(FundingNegotiation::AwaitingAck { context, change_strategy, .. }) =
+ let funding_negotiation_context =
+ if let Some(FundingNegotiation::AwaitingAck { context, .. }) =
pending_splice.funding_negotiation.take()
{
- (context, change_strategy)
+ context
} else {
panic!("We should have returned an error earlier!");
};
@@ -12324,10 +12154,8 @@ where
.into_interactive_tx_constructor(
&self.context,
&splice_funding,
- signer_provider,
entropy_source,
holder_node_id.clone(),
- change_strategy,
)
.map_err(|err| {
ChannelError::WarnAndDisconnect(format!(
@@ -13212,22 +13040,6 @@ where
"Internal Error: Didn't have anything to do after reaching quiescence".to_owned()
));
},
- Some(QuiescentAction::LegacySplice(instructions)) => {
- if self.pending_splice.is_some() {
- debug_assert!(false);
- self.quiescent_action = Some(QuiescentAction::LegacySplice(instructions));
-
- return Err(ChannelError::WarnAndDisconnect(
- format!(
- "Channel {} cannot be spliced as it already has a splice pending",
- self.context.channel_id(),
- ),
- ));
- }
-
- let splice_init = self.send_splice_init(instructions);
- return Ok(Some(StfuResponse::SpliceInit(splice_init)));
- },
Some(QuiescentAction::Splice { contribution, locktime }) => {
// TODO(splicing): If the splice has been negotiated but has not been locked, we
// can RBF here to add the contribution.
@@ -13259,7 +13071,7 @@ where
our_funding_outputs,
};
- let splice_init = self.send_splice_init_internal(context, ChangeStrategy::FromCoinSelection);
+ let splice_init = self.send_splice_init(context);
return Ok(Some(StfuResponse::SpliceInit(splice_init)));
},
#[cfg(any(test, fuzzing, feature = "_test_utils"))]
@@ -13301,8 +13113,7 @@ where
// We can't initiate another splice while ours is pending, so don't bother becoming
// quiescent yet.
// TODO(splicing): Allow the splice as an RBF once supported.
- let has_splice_action = matches!(action, QuiescentAction::Splice { .. })
- || matches!(action, QuiescentAction::LegacySplice(_));
+ let has_splice_action = matches!(action, QuiescentAction::Splice { .. });
if has_splice_action && self.pending_splice.is_some() {
log_given_level!(
logger,
@@ -14894,7 +14705,7 @@ impl<SP: SignerProvider> Writeable for FundedChannel<SP> {
(61, fulfill_attribution_data, optional_vec), // Added in 0.2
(63, holder_commitment_point_current, option), // Added in 0.2
(64, pending_splice, option), // Added in 0.2
- (65, self.quiescent_action, option), // Added in 0.2
+ // 65 was previously used for quiescent_action
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
(71, holder_commitment_point_previous_revoked, option), // Added in 0.3
@@ -15284,7 +15095,6 @@ impl<'a, 'b, 'c, ES: EntropySource, SP: SignerProvider>
let mut minimum_depth_override: Option<u32> = None;
let mut pending_splice: Option<PendingFunding> = None;
- let mut quiescent_action = None;
let mut pending_outbound_held_htlc_flags_opt: Option<Vec<Option<()>>> = None;
let mut holding_cell_held_htlc_flags_opt: Option<Vec<Option<()>>> = None;
@@ -15338,7 +15148,7 @@ impl<'a, 'b, 'c, ES: EntropySource, SP: SignerProvider>
(61, fulfill_attribution_data, optional_vec), // Added in 0.2
(63, holder_commitment_point_current_opt, option), // Added in 0.2
(64, pending_splice, option), // Added in 0.2
- (65, quiescent_action, upgradable_option), // Added in 0.2
+ // 65 quiescent_action: Added in 0.2; removed in 0.3
(67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2
(71, holder_commitment_point_previous_revoked_opt, option), // Added in 0.3
@@ -15803,7 +15613,7 @@ impl<'a, 'b, 'c, ES: EntropySource, SP: SignerProvider>
},
holder_commitment_point,
pending_splice,
- quiescent_action,
+ quiescent_action: None,
})
}
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 6bf04cd..8e06129 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -12845,7 +12845,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
let init_res = funded_channel.splice_init(
msg,
our_funding_contribution,
- &self.signer_provider,
&self.entropy_source,
&self.get_our_node_id(),
&self.logger,
@@ -12889,7 +12888,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
if let Some(ref mut funded_channel) = chan_entry.get_mut().as_funded_mut() {
let splice_ack_res = funded_channel.splice_ack(
msg,
- &self.signer_provider,
&self.entropy_source,
&self.get_our_node_id(),
&self.logger,
diff --git a/lightning/src/ln/funding.rs b/lightning/src/ln/funding.rs
index dc29b23..1d1762a 100644
--- a/lightning/src/ln/funding.rs
+++ b/lightning/src/ln/funding.rs
@@ -286,16 +286,6 @@ pub struct FundingContribution {
is_splice: bool,
}
-impl_writeable_tlv_based!(FundingContribution, {
- (1, value_added, required),
- (3, estimated_fee, required),
- (5, inputs, optional_vec),
- (7, outputs, optional_vec),
- (9, change_output, option),
- (11, feerate, required),
- (13, is_splice, required),
-});
-
impl FundingContribution {
pub(super) fn feerate(&self) -> FeeRate {
self.feerate
diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs
index 7e7a9fb..17dab19 100644
--- a/lightning/src/ln/interactivetxs.rs
+++ b/lightning/src/ln/interactivetxs.rs
@@ -12,7 +12,7 @@ use crate::io_extras::sink;
use crate::prelude::*;
use bitcoin::absolute::LockTime as AbsoluteLockTime;
-use bitcoin::amount::{Amount, SignedAmount};
+use bitcoin::amount::Amount;
use bitcoin::consensus::Encodable;
use bitcoin::constants::WITNESS_SCALE_FACTOR;
use bitcoin::ecdsa::Signature as BitcoinSignature;
@@ -31,7 +31,7 @@ use crate::ln::chan_utils::{
BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT, FUNDING_TRANSACTION_WITNESS_WEIGHT,
SEGWIT_MARKER_FLAG_WEIGHT,
};
-use crate::ln::channel::{FundingNegotiationContext, TOTAL_BITCOIN_SUPPLY_SATOSHIS};
+use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
use crate::ln::funding::FundingTxInput;
use crate::ln::msgs;
use crate::ln::msgs::{MessageSendEvent, SerialId, TxSignatures};
@@ -2323,102 +2323,16 @@ impl InteractiveTxConstructor {
}
}
-/// Determine whether a change output should be added, and if yes, of what size, considering our
-/// given inputs and outputs, and intended contribution. Takes into account the fees and the dust
-/// limit.
-///
-/// Three outcomes are possible:
-/// - Inputs are sufficient for intended contribution, fees, and a larger-than-dust change:
-/// `Ok(Some(change_amount))`
-/// - Inputs are sufficient for intended contribution and fees, and a change output isn't needed:
-/// `Ok(None)`
-/// - Inputs are not sufficient to cover contribution and fees:
-/// `Err(AbortReason::InsufficientFees)`
-///
-/// Parameters:
-/// - `context` - Context of the funding negotiation, including non-shared inputs and feerate.
-/// - `is_splice` - Whether we splicing an existing channel or dual-funding a new one.
-/// - `shared_output_funding_script` - The script of the shared output.
-/// - `funding_outputs` - Our funding outputs.
-/// - `change_output_dust_limit` - The dust limit (in sats) to consider.
-pub(super) fn calculate_change_output_value(
- context: &FundingNegotiationContext, is_splice: bool, shared_output_funding_script: &ScriptBuf,
- change_output_dust_limit: u64,
-) -> Result<Option<Amount>, AbortReason> {
- let mut total_input_value = Amount::ZERO;
- let mut our_funding_inputs_weight = 0u64;
- for FundingTxInput { utxo, .. } in context.our_funding_inputs.iter() {
- total_input_value = total_input_value.checked_add(utxo.output.value).unwrap_or(Amount::MAX);
-
- let weight = BASE_INPUT_WEIGHT + utxo.satisfaction_weight;
- our_funding_inputs_weight = our_funding_inputs_weight.saturating_add(weight);
- }
-
- let funding_outputs = &context.our_funding_outputs;
- let total_output_value = funding_outputs
- .iter()
- .fold(Amount::ZERO, |total, out| total.checked_add(out.value).unwrap_or(Amount::MAX));
-
- let our_funding_outputs_weight = funding_outputs.iter().fold(0u64, |weight, out| {
- weight.saturating_add(get_output_weight(&out.script_pubkey).to_wu())
- });
- let mut weight = our_funding_outputs_weight.saturating_add(our_funding_inputs_weight);
-
- // If we are the initiator, we must pay for the weight of the funding output and
- // all common fields in the funding transaction.
- if context.is_initiator {
- weight = weight.saturating_add(get_output_weight(shared_output_funding_script).to_wu());
- weight = weight.saturating_add(TX_COMMON_FIELDS_WEIGHT);
- if is_splice {
- // TODO(taproot): Needs to consider different weights based on channel type
- weight = weight.saturating_add(BASE_INPUT_WEIGHT);
- weight = weight.saturating_add(EMPTY_SCRIPT_SIG_WEIGHT);
- weight = weight.saturating_add(FUNDING_TRANSACTION_WITNESS_WEIGHT);
- #[cfg(feature = "grind_signatures")]
- {
- // Guarantees a low R signature
- weight -= 1;
- }
- }
- }
-
- let contributed_fees =
- Amount::from_sat(fee_for_weight(context.funding_feerate_sat_per_1000_weight, weight));
-
- let contributed_input_value =
- context.our_funding_contribution + total_output_value.to_signed().unwrap();
- assert!(contributed_input_value > SignedAmount::ZERO);
- let contributed_input_value = contributed_input_value.unsigned_abs();
-
- let total_input_value_less_fees =
- total_input_value.checked_sub(contributed_fees).unwrap_or(Amount::ZERO);
- if total_input_value_less_fees < contributed_input_value {
- // Not enough to cover contribution plus fees
- return Err(AbortReason::InsufficientFees);
- }
-
- let remaining_value = total_input_value_less_fees
- .checked_sub(contributed_input_value)
- .expect("remaining_value should not be negative");
- if remaining_value.to_sat() < change_output_dust_limit {
- // Enough to cover contribution plus fees, but leftover is below dust limit; no change
- Ok(None)
- } else {
- // Enough to have over-dust change
- Ok(Some(remaining_value))
- }
-}
-
#[cfg(test)]
mod tests {
use crate::chain::chaininterface::{fee_for_weight, FEERATE_FLOOR_SATS_PER_KW};
- use crate::ln::channel::{FundingNegotiationContext, TOTAL_BITCOIN_SUPPLY_SATOSHIS};
+ use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
use crate::ln::funding::FundingTxInput;
use crate::ln::interactivetxs::{
- calculate_change_output_value, generate_holder_serial_id, AbortReason,
- HandleTxCompleteValue, InteractiveTxConstructor, InteractiveTxConstructorArgs,
- InteractiveTxMessageSend, SharedOwnedInput, SharedOwnedOutput, MAX_INPUTS_OUTPUTS_COUNT,
- MAX_RECEIVED_TX_ADD_INPUT_COUNT, MAX_RECEIVED_TX_ADD_OUTPUT_COUNT,
+ generate_holder_serial_id, AbortReason, HandleTxCompleteValue, InteractiveTxConstructor,
+ InteractiveTxConstructorArgs, InteractiveTxMessageSend, SharedOwnedInput,
+ SharedOwnedOutput, MAX_INPUTS_OUTPUTS_COUNT, MAX_RECEIVED_TX_ADD_INPUT_COUNT,
+ MAX_RECEIVED_TX_ADD_OUTPUT_COUNT,
};
use crate::ln::types::ChannelId;
use crate::sign::EntropySource;
@@ -2433,8 +2347,7 @@ mod tests {
use bitcoin::transaction::Version;
use bitcoin::{opcodes, WScriptHash, Weight, XOnlyPublicKey};
use bitcoin::{
- OutPoint, PubkeyHash, ScriptBuf, Sequence, SignedAmount, Transaction, TxIn, TxOut,
- WPubkeyHash,
+ OutPoint, PubkeyHash, ScriptBuf, Sequence, Transaction, TxIn, TxOut, WPubkeyHash,
};
use super::{
@@ -3398,118 +3311,6 @@ mod tests {
assert_eq!(generate_holder_serial_id(&&entropy_source, false) % 2, 1)
}
- #[test]
- fn test_calculate_change_output_value_open() {
- let input_prevouts = [
- TxOut {
- value: Amount::from_sat(70_000),
- script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()),
- },
- TxOut {
- value: Amount::from_sat(60_000),
- script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()),
- },
- ];
- let inputs = input_prevouts
- .iter()
- .map(|txout| {
- let prevtx = Transaction {
- input: Vec::new(),
- output: vec![(*txout).clone()],
- lock_time: AbsoluteLockTime::ZERO,
- version: Version::TWO,
- };
-
- FundingTxInput::new_p2wpkh(prevtx, 0).unwrap()
- })
- .collect();
- let txout = TxOut { value: Amount::from_sat(10_000), script_pubkey: ScriptBuf::new() };
- let outputs = vec![txout];
- let funding_feerate_sat_per_1000_weight = 3000;
-
- let total_inputs: Amount = input_prevouts.iter().map(|o| o.value).sum();
- let total_outputs: Amount = outputs.iter().map(|o| o.value).sum();
- let fees = if cfg!(feature = "grind_signatures") {
- Amount::from_sat(1734)
- } else {
- Amount::from_sat(1740)
- };
- let common_fees = Amount::from_sat(234);
-
- // There is leftover for change
- let context = FundingNegotiationContext {
- is_initiator: true,
- our_funding_contribution: SignedAmount::from_sat(110_000),
- funding_tx_locktime: AbsoluteLockTime::ZERO,
- funding_feerate_sat_per_1000_weight,
- shared_funding_input: None,
- our_funding_inputs: inputs,
- our_funding_outputs: outputs,
- };
- let gross_change =
- total_inputs - total_outputs - context.our_funding_contribution.to_unsigned().unwrap();
- assert_eq!(
- calculate_change_output_value(&context, false, &ScriptBuf::new(), 300),
- Ok(Some(gross_change - fees - common_fees)),
- );
-
- // There is leftover for change, without common fees
- let context = FundingNegotiationContext { is_initiator: false, ..context };
- assert_eq!(
- calculate_change_output_value(&context, false, &ScriptBuf::new(), 300),
- Ok(Some(gross_change - fees)),
- );
-
- // Insufficient inputs, no leftover
- let context = FundingNegotiationContext {
- is_initiator: false,
- our_funding_contribution: SignedAmount::from_sat(130_000),
- ..context
- };
- assert_eq!(
- calculate_change_output_value(&context, false, &ScriptBuf::new(), 300),
- Err(AbortReason::InsufficientFees),
- );
-
- // Very small leftover
- let context = FundingNegotiationContext {
- is_initiator: false,
- our_funding_contribution: SignedAmount::from_sat(118_000),
- ..context
- };
- assert_eq!(
- calculate_change_output_value(&context, false, &ScriptBuf::new(), 300),
- Ok(None),
- );
-
- // Small leftover, but not dust
- let context = FundingNegotiationContext {
- is_initiator: false,
- our_funding_contribution: SignedAmount::from_sat(117_992),
- ..context
- };
- let gross_change =
- total_inputs - total_outputs - context.our_funding_contribution.to_unsigned().unwrap();
- assert_eq!(
- calculate_change_output_value(&context, false, &ScriptBuf::new(), 100),
- Ok(Some(gross_change - fees)),
- );
-
- // Larger fee, smaller change
- let context = FundingNegotiationContext {
- is_initiator: true,
- our_funding_contribution: SignedAmount::from_sat(110_000),
- funding_feerate_sat_per_1000_weight: funding_feerate_sat_per_1000_weight * 3,
- ..context
- };
- let gross_change =
- total_inputs - total_outputs - context.our_funding_contribution.to_unsigned().unwrap();
- assert_eq!(
- calculate_change_output_value(&context, false, &ScriptBuf::new(), 300),
- Ok(Some(gross_change - fees * 3 - common_fees * 3)),
- );
- }
-
fn do_verify_tx_signatures(
transaction: Transaction, prev_outputs: Vec<TxOut>,
) -> Result<(), String> {
diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs
index f7c4700..66ff2e8 100644
--- a/lightning/src/ln/splicing_tests.rs
+++ b/lightning/src/ln/splicing_tests.rs
@@ -1684,28 +1684,23 @@ fn do_test_splice_reestablish(reload: bool, async_monitor_update: bool) {
#[test]
fn test_propose_splice_while_disconnected() {
- do_test_propose_splice_while_disconnected(false, false);
- do_test_propose_splice_while_disconnected(false, true);
- do_test_propose_splice_while_disconnected(true, false);
- do_test_propose_splice_while_disconnected(true, true);
+ do_test_propose_splice_while_disconnected(false);
+ do_test_propose_splice_while_disconnected(true);
}
#[cfg(test)]
-fn do_test_propose_splice_while_disconnected(reload: bool, use_0conf: bool) {
+fn do_test_propose_splice_while_disconnected(use_0conf: bool) {
// Test that both nodes are able to propose a splice while the counterparty is disconnected, and
// whoever doesn't go first due to the quiescence tie-breaker, will retry their splice after the
// first one becomes locked.
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let (persister_0a, persister_0b, persister_1a, persister_1b);
- let (chain_monitor_0a, chain_monitor_0b, chain_monitor_1a, chain_monitor_1b);
let mut config = test_default_channel_config();
if use_0conf {
config.channel_handshake_limits.trust_own_funding_0conf = true;
}
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
- let (node_0a, node_0b, node_1a, node_1b);
- let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
@@ -1743,15 +1738,8 @@ fn do_test_propose_splice_while_disconnected(reload: bool, use_0conf: bool) {
value: Amount::from_sat(splice_out_sat),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
- let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
- let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1, feerate).unwrap();
- let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let node_0_funding_contribution =
- funding_template.splice_out_sync(node_0_outputs, &wallet).unwrap();
- nodes[0]
- .node
- .funding_contributed(&channel_id, &node_id_1, node_0_funding_contribution.clone(), None)
- .unwrap();
+ initiate_splice_out(&nodes[0], &nodes[1], channel_id, node_0_outputs).unwrap();
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
@@ -1759,38 +1747,11 @@ fn do_test_propose_splice_while_disconnected(reload: bool, use_0conf: bool) {
value: Amount::from_sat(splice_out_sat),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}];
- let funding_template = nodes[1].node.splice_channel(&channel_id, &node_id_0, feerate).unwrap();
- let wallet = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let node_1_funding_contribution =
- funding_template.splice_out_sync(node_1_outputs, &wallet).unwrap();
- nodes[1]
- .node
- .funding_contributed(&channel_id, &node_id_0, node_1_funding_contribution.clone(), None)
- .unwrap();
+ initiate_splice_out(&nodes[1], &nodes[0], channel_id, node_1_outputs).unwrap();
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
- if reload {
- let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
- reload_node!(
- nodes[0],
- nodes[0].node.encode(),
- &[&encoded_monitor_0],
- persister_0a,
- chain_monitor_0a,
- node_0a
- );
- let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
- reload_node!(
- nodes[1],
- nodes[1].node.encode(),
- &[&encoded_monitor_1],
- persister_1a,
- chain_monitor_1a,
- node_1a
- );
- }
-
// Reconnect the nodes. Both nodes should attempt quiescence as the initiator, but only one will
// be it via the tie-breaker.
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
@@ -1911,29 +1872,8 @@ fn do_test_propose_splice_while_disconnected(reload: bool, use_0conf: bool) {
// Reconnect the nodes. This should trigger the node which lost the tie-breaker to resend `stfu`
// for their splice attempt.
- if reload {
- let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
- reload_node!(
- nodes[0],
- nodes[0].node.encode(),
- &[&encoded_monitor_0],
- persister_0b,
- chain_monitor_0b,
- node_0b
- );
- let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
- reload_node!(
- nodes[1],
- nodes[1].node.encode(),
- &[&encoded_monitor_1],
- persister_1b,
- chain_monitor_1b,
- node_1b
- );
- } else {
- nodes[0].node.peer_disconnected(node_id_1);
- nodes[1].node.peer_disconnected(node_id_0);
- }
+ nodes[0].node.peer_disconnected(node_id_1);
+ nodes[1].node.peer_disconnected(node_id_0);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
if !use_0conf {
reconnect_args.send_announcement_sigs = (true, true);
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.