Skip to content

Commit b39c104

Browse files
authored
Merge pull request #4030 from wpaulino/splice-discard-funding
Emit DiscardFunding events for double spent splice transactions
2 parents 2772bfd + 51e342b commit b39c104

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
@@ -4032,6 +4032,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
40324032
}
40334033

40344034
fn promote_funding(&mut self, new_funding_txid: Txid) -> Result<(), ()> {
4035+
let prev_funding_txid = self.funding.funding_txid();
4036+
40354037
let new_funding = self
40364038
.pending_funding
40374039
.iter_mut()
@@ -4047,9 +4049,20 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
40474049
self.funding.prev_holder_commitment_tx.clone(),
40484050
);
40494051

4052+
let no_further_updates_allowed = self.no_further_updates_allowed();
4053+
40504054
// The swap above places the previous `FundingScope` into `pending_funding`.
40514055
for funding in self.pending_funding.drain(..) {
4052-
self.outputs_to_watch.remove(&funding.funding_txid());
4056+
let funding_txid = funding.funding_txid();
4057+
self.outputs_to_watch.remove(&funding_txid);
4058+
if no_further_updates_allowed && funding_txid != prev_funding_txid {
4059+
self.pending_events.push(Event::DiscardFunding {
4060+
channel_id: self.channel_id,
4061+
funding_info: crate::events::FundingInfo::OutPoint {
4062+
outpoint: funding.funding_outpoint(),
4063+
},
4064+
});
4065+
}
40534066
}
40544067
if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.take() {
40554068
// 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, EMPTY_SCRIPT_SIG_WEIGHT};
4141
use crate::events::ClosureReason;
42+
#[cfg(splicing)]
43+
use crate::events::FundingInfo;
4244
use crate::ln::chan_utils;
4345
use crate::ln::chan_utils::{
4446
get_commitment_transaction_number_obscure_factor, max_htlcs, second_stage_tx_fees_sat,
@@ -6408,16 +6410,35 @@ where
64086410

64096411
#[cfg(splicing)]
64106412
macro_rules! promote_splice_funding {
6411-
($self: expr, $funding: expr) => {
6413+
($self: expr, $funding: expr) => {{
6414+
let prev_funding_txid = $self.funding.get_funding_txid();
64126415
if let Some(scid) = $self.funding.short_channel_id {
64136416
$self.context.historical_scids.push(scid);
64146417
}
64156418
core::mem::swap(&mut $self.funding, $funding);
64166419
$self.interactive_tx_signing_session = None;
64176420
$self.pending_splice = None;
6418-
$self.pending_funding.clear();
64196421
$self.context.announcement_sigs_state = AnnouncementSigsState::NotSent;
6420-
};
6422+
6423+
// The swap above places the previous `FundingScope` into `pending_funding`.
6424+
let discarded_funding = $self
6425+
.pending_funding
6426+
.drain(..)
6427+
.filter(|funding| funding.get_funding_txid() != prev_funding_txid)
6428+
.map(|mut funding| {
6429+
funding
6430+
.funding_transaction
6431+
.take()
6432+
.map(|tx| FundingInfo::Tx { transaction: tx })
6433+
.unwrap_or_else(|| FundingInfo::OutPoint {
6434+
outpoint: funding
6435+
.get_funding_txo()
6436+
.expect("Negotiated splices must have a known funding outpoint"),
6437+
})
6438+
})
6439+
.collect::<Vec<_>>();
6440+
discarded_funding
6441+
}};
64216442
}
64226443

64236444
#[cfg(any(test, fuzzing))]
@@ -6500,6 +6521,7 @@ pub struct SpliceFundingPromotion {
65006521
pub funding_txo: OutPoint,
65016522
pub monitor_update: Option<ChannelMonitorUpdate>,
65026523
pub announcement_sigs: Option<msgs::AnnouncementSignatures>,
6524+
pub discarded_funding: Vec<FundingInfo>,
65036525
}
65046526

65056527
impl<SP: Deref> FundedChannel<SP>
@@ -9006,23 +9028,25 @@ where
90069028
log_trace!(logger, "Regenerating latest commitment update in channel {} with{} {} update_adds, {} update_fulfills, {} update_fails, and {} update_fail_malformeds",
90079029
&self.context.channel_id(), if update_fee.is_some() { " update_fee," } else { "" },
90089030
update_add_htlcs.len(), update_fulfill_htlcs.len(), update_fail_htlcs.len(), update_fail_malformed_htlcs.len());
9009-
let commitment_signed =
9010-
if let Ok(update) = self.send_commitment_no_state_update(logger) {
9011-
if self.context.signer_pending_commitment_update {
9012-
log_trace!(
9013-
logger,
9014-
"Commitment update generated: clearing signer_pending_commitment_update"
9015-
);
9016-
self.context.signer_pending_commitment_update = false;
9017-
}
9018-
update
9019-
} else {
9020-
if !self.context.signer_pending_commitment_update {
9021-
log_trace!(logger, "Commitment update awaiting signer: setting signer_pending_commitment_update");
9022-
self.context.signer_pending_commitment_update = true;
9023-
}
9024-
return Err(());
9025-
};
9031+
let commitment_signed = if let Ok(update) = self.send_commitment_no_state_update(logger) {
9032+
if self.context.signer_pending_commitment_update {
9033+
log_trace!(
9034+
logger,
9035+
"Commitment update generated: clearing signer_pending_commitment_update"
9036+
);
9037+
self.context.signer_pending_commitment_update = false;
9038+
}
9039+
update
9040+
} else {
9041+
if !self.context.signer_pending_commitment_update {
9042+
log_trace!(
9043+
logger,
9044+
"Commitment update awaiting signer: setting signer_pending_commitment_update"
9045+
);
9046+
self.context.signer_pending_commitment_update = true;
9047+
}
9048+
return Err(());
9049+
};
90269050
Ok(msgs::CommitmentUpdate {
90279051
update_add_htlcs,
90289052
update_fulfill_htlcs,
@@ -10326,16 +10350,16 @@ where
1032610350
&self.context.channel_id,
1032710351
);
1032810352

10329-
{
10353+
let discarded_funding = {
1033010354
// Scope `funding` since it is swapped within `promote_splice_funding` and we don't want
1033110355
// to unintentionally use it.
1033210356
let funding = self
1033310357
.pending_funding
1033410358
.iter_mut()
1033510359
.find(|funding| funding.get_funding_txid() == Some(splice_txid))
1033610360
.unwrap();
10337-
promote_splice_funding!(self, funding);
10338-
}
10361+
promote_splice_funding!(self, funding)
10362+
};
1033910363

1034010364
let funding_txo = self
1034110365
.funding
@@ -10356,7 +10380,12 @@ where
1035610380
let announcement_sigs =
1035710381
self.get_announcement_sigs(node_signer, chain_hash, user_config, block_height, logger);
1035810382

10359-
Some(SpliceFundingPromotion { funding_txo, monitor_update, announcement_sigs })
10383+
Some(SpliceFundingPromotion {
10384+
funding_txo,
10385+
monitor_update,
10386+
announcement_sigs,
10387+
discarded_funding,
10388+
})
1036010389
}
1036110390

1036210391
/// When a transaction is confirmed, we check whether it is or spends the funding transaction
@@ -10438,16 +10467,17 @@ where
1043810467
&self.context.channel_id,
1043910468
);
1044010469

10441-
let (funding_txo, monitor_update, announcement_sigs) =
10470+
let (funding_txo, monitor_update, announcement_sigs, discarded_funding) =
1044210471
self.maybe_promote_splice_funding(
1044310472
node_signer, chain_hash, user_config, height, logger,
1044410473
).map(|splice_promotion| (
1044510474
Some(splice_promotion.funding_txo),
1044610475
splice_promotion.monitor_update,
1044710476
splice_promotion.announcement_sigs,
10448-
)).unwrap_or((None, None, None));
10477+
splice_promotion.discarded_funding,
10478+
)).unwrap_or((None, None, None, Vec::new()));
1044910479

10450-
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update)), announcement_sigs));
10480+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update, discarded_funding)), announcement_sigs));
1045110481
}
1045210482
}
1045310483
}
@@ -10599,7 +10629,7 @@ where
1059910629
log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
1060010630
debug_assert!(chain_node_signer.is_some());
1060110631

10602-
let (funding_txo, monitor_update, announcement_sigs) = chain_node_signer
10632+
let (funding_txo, monitor_update, announcement_sigs, discarded_funding) = chain_node_signer
1060310633
.and_then(|(chain_hash, node_signer, user_config)| {
1060410634
// We can only promote on blocks connected, which is when we expect
1060510635
// `chain_node_signer` to be `Some`.
@@ -10609,10 +10639,11 @@ where
1060910639
Some(splice_promotion.funding_txo),
1061010640
splice_promotion.monitor_update,
1061110641
splice_promotion.announcement_sigs,
10642+
splice_promotion.discarded_funding,
1061210643
))
10613-
.unwrap_or((None, None, None));
10644+
.unwrap_or((None, None, None, Vec::new()));
1061410645

10615-
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update)), timed_out_htlcs, announcement_sigs));
10646+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update, discarded_funding)), timed_out_htlcs, announcement_sigs));
1061610647
}
1061710648
}
1061810649

lightning/src/ln/channelmanager.rs

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

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

1124411255
if let Some(announcement_sigs) = splice_promotion.announcement_sigs {
1124511256
log_trace!(
@@ -13406,7 +13417,7 @@ where
1340613417
pub(super) enum FundingConfirmedMessage {
1340713418
Establishment(msgs::ChannelReady),
1340813419
#[cfg(splicing)]
13409-
Splice(msgs::SpliceLocked, Option<OutPoint>, Option<ChannelMonitorUpdate>),
13420+
Splice(msgs::SpliceLocked, Option<OutPoint>, Option<ChannelMonitorUpdate>, Vec<FundingInfo>),
1341013421
}
1341113422

1341213423
impl<
@@ -13482,7 +13493,7 @@ where
1348213493
}
1348313494
},
1348413495
#[cfg(splicing)]
13485-
Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update_opt)) => {
13496+
Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update_opt, discarded_funding)) => {
1348613497
let counterparty_node_id = funded_channel.context.get_counterparty_node_id();
1348713498
let channel_id = funded_channel.context.channel_id();
1348813499

@@ -13512,6 +13523,13 @@ where
1351213523
funding_txo: Some(funding_txo.into_bitcoin_outpoint()),
1351313524
channel_type: funded_channel.funding.get_channel_type().clone(),
1351413525
}, None));
13526+
discarded_funding.into_iter().for_each(|funding_info| {
13527+
let event = Event::DiscardFunding {
13528+
channel_id: funded_channel.context.channel_id(),
13529+
funding_info,
13530+
};
13531+
pending_events.push_back((event, None));
13532+
});
1351513533
}
1351613534

1351713535
pending_msg_events.push(MessageSendEvent::SendSpliceLocked {

0 commit comments

Comments
 (0)