Skip to content

Commit 51e342b

Browse files
committed
Emit DiscardFunding events for double spent splice transactions
Once we see a splice transaction become locked, we want to emit a `DiscardFunding` event for every alternative funding transaction candidate that may have been negotiated due to RBFs, as they may contain inputs the user has considered "locked" that do not exist in the confirmed transaction. If the channel closes before the splice is locked, we rely on the `ChannelMonitor` to produce these events for us instead.
1 parent 6f78d57 commit 51e342b

File tree

4 files changed

+112
-46
lines changed

4 files changed

+112
-46
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3906,6 +3906,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
39063906
}
39073907

39083908
fn promote_funding(&mut self, new_funding_txid: Txid) -> Result<(), ()> {
3909+
let prev_funding_txid = self.funding.funding_txid();
3910+
39093911
let new_funding = self
39103912
.pending_funding
39113913
.iter_mut()
@@ -3921,9 +3923,20 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
39213923
self.funding.prev_holder_commitment_tx.clone(),
39223924
);
39233925

3926+
let no_further_updates_allowed = self.no_further_updates_allowed();
3927+
39243928
// The swap above places the previous `FundingScope` into `pending_funding`.
39253929
for funding in self.pending_funding.drain(..) {
3926-
self.outputs_to_watch.remove(&funding.funding_txid());
3930+
let funding_txid = funding.funding_txid();
3931+
self.outputs_to_watch.remove(&funding_txid);
3932+
if no_further_updates_allowed && funding_txid != prev_funding_txid {
3933+
self.pending_events.push(Event::DiscardFunding {
3934+
channel_id: self.channel_id,
3935+
funding_info: crate::events::FundingInfo::OutPoint {
3936+
outpoint: funding.funding_outpoint(),
3937+
},
3938+
});
3939+
}
39273940
}
39283941
if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.take() {
39293942
// In exceedingly rare cases, it's possible there was a reorg that caused a potential funding to

lightning/src/events/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,10 @@ pub enum Event {
15021502
/// Used to indicate to the user that they can abandon the funding transaction and recycle the
15031503
/// inputs for another purpose.
15041504
///
1505+
/// When splicing, users can expect to receive an event for each negotiated splice transaction
1506+
/// that did not become locked. The negotiated splice transaction that became locked can be
1507+
/// obtained via [`Event::ChannelReady::funding_txo`].
1508+
///
15051509
/// This event is not guaranteed to be generated for channels that are closed due to a restart.
15061510
///
15071511
/// # Failure Behavior and Persistence

lightning/src/ln/channel.rs

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ use crate::chain::transaction::{OutPoint, TransactionData};
3939
use crate::chain::BestBlock;
4040
use crate::events::bump_transaction::BASE_INPUT_WEIGHT;
4141
use crate::events::ClosureReason;
42+
#[cfg(splicing)]
43+
use crate::events::FundingInfo;
4244
use crate::ln::chan_utils;
4345
#[cfg(splicing)]
4446
use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT;
@@ -6086,16 +6088,35 @@ where
60866088

60876089
#[cfg(splicing)]
60886090
macro_rules! promote_splice_funding {
6089-
($self: expr, $funding: expr) => {
6091+
($self: expr, $funding: expr) => {{
6092+
let prev_funding_txid = $self.funding.get_funding_txid();
60906093
if let Some(scid) = $self.funding.short_channel_id {
60916094
$self.context.historical_scids.push(scid);
60926095
}
60936096
core::mem::swap(&mut $self.funding, $funding);
60946097
$self.interactive_tx_signing_session = None;
60956098
$self.pending_splice = None;
6096-
$self.pending_funding.clear();
60976099
$self.context.announcement_sigs_state = AnnouncementSigsState::NotSent;
6098-
};
6100+
6101+
// The swap above places the previous `FundingScope` into `pending_funding`.
6102+
let discarded_funding = $self
6103+
.pending_funding
6104+
.drain(..)
6105+
.filter(|funding| funding.get_funding_txid() != prev_funding_txid)
6106+
.map(|mut funding| {
6107+
funding
6108+
.funding_transaction
6109+
.take()
6110+
.map(|tx| FundingInfo::Tx { transaction: tx })
6111+
.unwrap_or_else(|| FundingInfo::OutPoint {
6112+
outpoint: funding
6113+
.get_funding_txo()
6114+
.expect("Negotiated splices must have a known funding outpoint"),
6115+
})
6116+
})
6117+
.collect::<Vec<_>>();
6118+
discarded_funding
6119+
}};
60996120
}
61006121

61016122
#[cfg(any(test, fuzzing))]
@@ -6179,6 +6200,7 @@ pub struct SpliceFundingPromotion {
61796200
pub funding_txo: OutPoint,
61806201
pub monitor_update: Option<ChannelMonitorUpdate>,
61816202
pub announcement_sigs: Option<msgs::AnnouncementSignatures>,
6203+
pub discarded_funding: Vec<FundingInfo>,
61826204
}
61836205

61846206
impl<SP: Deref> FundedChannel<SP>
@@ -8634,23 +8656,25 @@ where
86348656
log_trace!(logger, "Regenerating latest commitment update in channel {} with{} {} update_adds, {} update_fulfills, {} update_fails, and {} update_fail_malformeds",
86358657
&self.context.channel_id(), if update_fee.is_some() { " update_fee," } else { "" },
86368658
update_add_htlcs.len(), update_fulfill_htlcs.len(), update_fail_htlcs.len(), update_fail_malformed_htlcs.len());
8637-
let commitment_signed =
8638-
if let Ok(update) = self.send_commitment_no_state_update(logger) {
8639-
if self.context.signer_pending_commitment_update {
8640-
log_trace!(
8641-
logger,
8642-
"Commitment update generated: clearing signer_pending_commitment_update"
8643-
);
8644-
self.context.signer_pending_commitment_update = false;
8645-
}
8646-
update
8647-
} else {
8648-
if !self.context.signer_pending_commitment_update {
8649-
log_trace!(logger, "Commitment update awaiting signer: setting signer_pending_commitment_update");
8650-
self.context.signer_pending_commitment_update = true;
8651-
}
8652-
return Err(());
8653-
};
8659+
let commitment_signed = if let Ok(update) = self.send_commitment_no_state_update(logger) {
8660+
if self.context.signer_pending_commitment_update {
8661+
log_trace!(
8662+
logger,
8663+
"Commitment update generated: clearing signer_pending_commitment_update"
8664+
);
8665+
self.context.signer_pending_commitment_update = false;
8666+
}
8667+
update
8668+
} else {
8669+
if !self.context.signer_pending_commitment_update {
8670+
log_trace!(
8671+
logger,
8672+
"Commitment update awaiting signer: setting signer_pending_commitment_update"
8673+
);
8674+
self.context.signer_pending_commitment_update = true;
8675+
}
8676+
return Err(());
8677+
};
86548678
Ok(msgs::CommitmentUpdate {
86558679
update_add_htlcs,
86568680
update_fulfill_htlcs,
@@ -9954,16 +9978,16 @@ where
99549978
&self.context.channel_id,
99559979
);
99569980

9957-
{
9981+
let discarded_funding = {
99589982
// Scope `funding` since it is swapped within `promote_splice_funding` and we don't want
99599983
// to unintentionally use it.
99609984
let funding = self
99619985
.pending_funding
99629986
.iter_mut()
99639987
.find(|funding| funding.get_funding_txid() == Some(splice_txid))
99649988
.unwrap();
9965-
promote_splice_funding!(self, funding);
9966-
}
9989+
promote_splice_funding!(self, funding)
9990+
};
99679991

99689992
let funding_txo = self
99699993
.funding
@@ -9984,7 +10008,12 @@ where
998410008
let announcement_sigs =
998510009
self.get_announcement_sigs(node_signer, chain_hash, user_config, block_height, logger);
998610010

9987-
Some(SpliceFundingPromotion { funding_txo, monitor_update, announcement_sigs })
10011+
Some(SpliceFundingPromotion {
10012+
funding_txo,
10013+
monitor_update,
10014+
announcement_sigs,
10015+
discarded_funding,
10016+
})
998810017
}
998910018

999010019
/// When a transaction is confirmed, we check whether it is or spends the funding transaction
@@ -10066,16 +10095,17 @@ where
1006610095
&self.context.channel_id,
1006710096
);
1006810097

10069-
let (funding_txo, monitor_update, announcement_sigs) =
10098+
let (funding_txo, monitor_update, announcement_sigs, discarded_funding) =
1007010099
self.maybe_promote_splice_funding(
1007110100
node_signer, chain_hash, user_config, height, logger,
1007210101
).map(|splice_promotion| (
1007310102
Some(splice_promotion.funding_txo),
1007410103
splice_promotion.monitor_update,
1007510104
splice_promotion.announcement_sigs,
10076-
)).unwrap_or((None, None, None));
10105+
splice_promotion.discarded_funding,
10106+
)).unwrap_or((None, None, None, Vec::new()));
1007710107

10078-
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update)), announcement_sigs));
10108+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update, discarded_funding)), announcement_sigs));
1007910109
}
1008010110
}
1008110111
}
@@ -10227,7 +10257,7 @@ where
1022710257
log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
1022810258
debug_assert!(chain_node_signer.is_some());
1022910259

10230-
let (funding_txo, monitor_update, announcement_sigs) = chain_node_signer
10260+
let (funding_txo, monitor_update, announcement_sigs, discarded_funding) = chain_node_signer
1023110261
.and_then(|(chain_hash, node_signer, user_config)| {
1023210262
// We can only promote on blocks connected, which is when we expect
1023310263
// `chain_node_signer` to be `Some`.
@@ -10237,10 +10267,11 @@ where
1023710267
Some(splice_promotion.funding_txo),
1023810268
splice_promotion.monitor_update,
1023910269
splice_promotion.announcement_sigs,
10270+
splice_promotion.discarded_funding,
1024010271
))
10241-
.unwrap_or((None, None, None));
10272+
.unwrap_or((None, None, None, Vec::new()));
1024210273

10243-
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update)), timed_out_htlcs, announcement_sigs));
10274+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update, discarded_funding)), timed_out_htlcs, announcement_sigs));
1024410275
}
1024510276
}
1024610277

lightning/src/ln/channelmanager.rs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11230,19 +11230,30 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1123011230
insert_short_channel_id!(short_to_chan_info, chan);
1123111231
}
1123211232

11233-
let mut pending_events = self.pending_events.lock().unwrap();
11234-
pending_events.push_back((
11235-
events::Event::ChannelReady {
11236-
channel_id: chan.context.channel_id(),
11237-
user_channel_id: chan.context.get_user_id(),
11238-
counterparty_node_id: chan.context.get_counterparty_node_id(),
11239-
funding_txo: Some(
11240-
splice_promotion.funding_txo.into_bitcoin_outpoint(),
11241-
),
11242-
channel_type: chan.funding.get_channel_type().clone(),
11243-
},
11244-
None,
11245-
));
11233+
{
11234+
let mut pending_events = self.pending_events.lock().unwrap();
11235+
pending_events.push_back((
11236+
events::Event::ChannelReady {
11237+
channel_id: chan.context.channel_id(),
11238+
user_channel_id: chan.context.get_user_id(),
11239+
counterparty_node_id: chan.context.get_counterparty_node_id(),
11240+
funding_txo: Some(
11241+
splice_promotion.funding_txo.into_bitcoin_outpoint(),
11242+
),
11243+
channel_type: chan.funding.get_channel_type().clone(),
11244+
},
11245+
None,
11246+
));
11247+
splice_promotion.discarded_funding.into_iter().for_each(
11248+
|funding_info| {
11249+
let event = Event::DiscardFunding {
11250+
channel_id: chan.context.channel_id(),
11251+
funding_info,
11252+
};
11253+
pending_events.push_back((event, None));
11254+
},
11255+
);
11256+
}
1124611257

1124711258
if let Some(announcement_sigs) = splice_promotion.announcement_sigs {
1124811259
log_trace!(
@@ -13409,7 +13420,7 @@ where
1340913420
pub(super) enum FundingConfirmedMessage {
1341013421
Establishment(msgs::ChannelReady),
1341113422
#[cfg(splicing)]
13412-
Splice(msgs::SpliceLocked, Option<OutPoint>, Option<ChannelMonitorUpdate>),
13423+
Splice(msgs::SpliceLocked, Option<OutPoint>, Option<ChannelMonitorUpdate>, Vec<FundingInfo>),
1341313424
}
1341413425

1341513426
impl<
@@ -13485,7 +13496,7 @@ where
1348513496
}
1348613497
},
1348713498
#[cfg(splicing)]
13488-
Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update_opt)) => {
13499+
Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update_opt, discarded_funding)) => {
1348913500
let counterparty_node_id = funded_channel.context.get_counterparty_node_id();
1349013501
let channel_id = funded_channel.context.channel_id();
1349113502

@@ -13515,6 +13526,13 @@ where
1351513526
funding_txo: Some(funding_txo.into_bitcoin_outpoint()),
1351613527
channel_type: funded_channel.funding.get_channel_type().clone(),
1351713528
}, None));
13529+
discarded_funding.into_iter().for_each(|funding_info| {
13530+
let event = Event::DiscardFunding {
13531+
channel_id: funded_channel.context.channel_id(),
13532+
funding_info,
13533+
};
13534+
pending_events.push_back((event, None));
13535+
});
1351813536
}
1351913537

1352013538
pending_msg_events.push(MessageSendEvent::SendSpliceLocked {

0 commit comments

Comments
 (0)