Skip to content

Commit a1fe138

Browse files
jkczyzclaude
andcommitted
Contribute to splice as acceptor
When both nodes want to splice simultaneously, the quiescence tie-breaker designates one as the initiator. Previously, the losing node responded with zero contribution, requiring a second full splice session after the first splice locked. This is wasteful, especially for often-offline nodes that may connect and immediately want to splice. Instead, the losing node contributes to the winner's splice as the acceptor, merging both contributions into a single splice transaction. Since the FundingContribution was originally built with initiator fees (which include common fields and shared input/output weight), the fee is adjusted to the acceptor rate before contributing, with the surplus returned to the change output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 965850b commit a1fe138

File tree

3 files changed

+515
-89
lines changed

3 files changed

+515
-89
lines changed

lightning/src/ln/channel.rs

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12198,6 +12198,26 @@ where
1219812198
self.propose_quiescence(logger, QuiescentAction::Splice { contribution, locktime })
1219912199
}
1220012200

12201+
/// Returns a reference to the funding contribution queued by a pending [`QuiescentAction`],
12202+
/// if any.
12203+
fn queued_funding_contribution(&self) -> Option<&FundingContribution> {
12204+
match &self.quiescent_action {
12205+
Some(QuiescentAction::Splice { contribution, .. }) => Some(contribution),
12206+
_ => None,
12207+
}
12208+
}
12209+
12210+
/// Consumes and returns the funding contribution from the pending [`QuiescentAction`], if any.
12211+
fn take_queued_funding_contribution(&mut self) -> Option<FundingContribution> {
12212+
match &self.quiescent_action {
12213+
Some(QuiescentAction::Splice { .. }) => match self.quiescent_action.take() {
12214+
Some(QuiescentAction::Splice { contribution, .. }) => Some(contribution),
12215+
_ => unreachable!(),
12216+
},
12217+
_ => None,
12218+
}
12219+
}
12220+
1220112221
fn send_splice_init_internal(
1220212222
&mut self, context: FundingNegotiationContext,
1220312223
) -> msgs::SpliceInit {
@@ -12297,10 +12317,6 @@ where
1229712317
));
1229812318
}
1229912319

12300-
// TODO(splicing): Once splice acceptor can contribute, check that inputs are sufficient,
12301-
// similarly to the check in `funding_contributed`.
12302-
debug_assert_eq!(our_funding_contribution, SignedAmount::ZERO);
12303-
1230412320
let their_funding_contribution = SignedAmount::from_sat(msg.funding_contribution_satoshis);
1230512321
if their_funding_contribution == SignedAmount::ZERO {
1230612322
return Err(ChannelError::WarnAndDisconnect(format!(
@@ -12424,11 +12440,37 @@ where
1242412440
}
1242512441

1242612442
pub(crate) fn splice_init<ES: EntropySource, L: Logger>(
12427-
&mut self, msg: &msgs::SpliceInit, our_funding_contribution_satoshis: i64,
12428-
entropy_source: &ES, holder_node_id: &PublicKey, logger: &L,
12443+
&mut self, msg: &msgs::SpliceInit, entropy_source: &ES, holder_node_id: &PublicKey,
12444+
logger: &L,
1242912445
) -> Result<msgs::SpliceAck, ChannelError> {
12430-
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
12431-
let splice_funding = self.validate_splice_init(msg, our_funding_contribution)?;
12446+
let feerate = FeeRate::from_sat_per_kwu(msg.funding_feerate_per_kw as u64);
12447+
let our_funding_contribution = self.queued_funding_contribution().and_then(|c| {
12448+
c.net_value_for_acceptor_at_feerate(feerate)
12449+
.map_err(|e| {
12450+
log_info!(
12451+
logger,
12452+
"Cannot accommodate initiator's feerate for channel {}: {}; \
12453+
proceeding without contribution",
12454+
self.context.channel_id(),
12455+
e,
12456+
);
12457+
})
12458+
.ok()
12459+
});
12460+
12461+
let splice_funding =
12462+
self.validate_splice_init(msg, our_funding_contribution.unwrap_or(SignedAmount::ZERO))?;
12463+
12464+
let (our_funding_inputs, our_funding_outputs) = if our_funding_contribution.is_some() {
12465+
self.take_queued_funding_contribution()
12466+
.expect("queued_funding_contribution was Some")
12467+
.for_acceptor_at_feerate(feerate)
12468+
.expect("feerate compatibility already checked")
12469+
.into_tx_parts()
12470+
} else {
12471+
Default::default()
12472+
};
12473+
let our_funding_contribution = our_funding_contribution.unwrap_or(SignedAmount::ZERO);
1243212474

1243312475
log_info!(
1243412476
logger,
@@ -12445,8 +12487,8 @@ where
1244512487
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1244612488
funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw,
1244712489
shared_funding_input: Some(prev_funding_input),
12448-
our_funding_inputs: Vec::new(),
12449-
our_funding_outputs: Vec::new(),
12490+
our_funding_inputs,
12491+
our_funding_outputs,
1245012492
};
1245112493

1245212494
let mut interactive_tx_constructor = funding_negotiation_context
@@ -12458,11 +12500,6 @@ where
1245812500
);
1245912501
debug_assert!(interactive_tx_constructor.take_initiator_first_message().is_none());
1246012502

12461-
// TODO(splicing): if quiescent_action is set, integrate what the user wants to do into the
12462-
// counterparty-initiated splice. For always-on nodes this probably isn't a useful
12463-
// optimization, but for often-offline nodes it may be, as we may connect and immediately
12464-
// go into splicing from both sides.
12465-
1246612503
let new_funding_pubkey = splice_funding.get_holder_pubkeys().funding_pubkey;
1246712504
self.pending_splice = Some(PendingFunding {
1246812505
funding_negotiation: Some(FundingNegotiation::ConstructingTransaction {

lightning/src/ln/channelmanager.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12813,9 +12813,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1281312813
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
1281412814
let peer_state = &mut *peer_state_lock;
1281512815

12816-
// TODO(splicing): Currently not possible to contribute on the splicing-acceptor side
12817-
let our_funding_contribution = 0i64;
12818-
1281912816
// Look for the channel
1282012817
match peer_state.channel_by_id.entry(msg.channel_id) {
1282112818
hash_map::Entry::Vacant(_) => {
@@ -12835,7 +12832,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1283512832
if let Some(ref mut funded_channel) = chan_entry.get_mut().as_funded_mut() {
1283612833
let init_res = funded_channel.splice_init(
1283712834
msg,
12838-
our_funding_contribution,
1283912835
&self.entropy_source,
1284012836
&self.get_our_node_id(),
1284112837
&self.logger,

0 commit comments

Comments
 (0)