Skip to content

Commit 0e7e0e7

Browse files
committed
Detect channel spend by splice transaction
A `ChannelMonitor` will always consider a channel closed once a confirmed spend for the funding transaction is detected. This is no longer the case with splicing, as the channel will remain open and capable of accepting updates while its funding transaction is being replaced.
1 parent 54a6e0d commit 0e7e0e7

File tree

1 file changed

+67
-3
lines changed

1 file changed

+67
-3
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,12 @@ enum OnchainEvent {
565565
/// output (and generate a SpendableOutput event).
566566
on_to_local_output_csv: Option<u16>,
567567
},
568+
/// The txid of an alternative funding transaction (due to a splice) that has confirmed but is
569+
/// not yet locked, invalidating the previous funding transaction as it now spent. Note that we
570+
/// wait to promote the corresponding `FundingScope` until we see a
571+
/// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] or if the alternative funding
572+
/// transaction is irrevocably confirmed.
573+
AlternativeFundingConfirmation {},
568574
}
569575

570576
impl Writeable for OnchainEventEntry {
@@ -609,6 +615,7 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
609615
(1, MaturingOutput) => {
610616
(0, descriptor, required),
611617
},
618+
(2, AlternativeFundingConfirmation) => {},
612619
(3, FundingSpendConfirmation) => {
613620
(0, on_local_output_csv, option),
614621
(1, commitment_tx_to_counterparty_output, option),
@@ -618,7 +625,6 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
618625
(2, preimage, option),
619626
(4, on_to_local_output_csv, option),
620627
},
621-
622628
);
623629

624630
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -4871,6 +4877,49 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
48714877
}
48724878
}
48734879

4880+
// A splice transaction has confirmed. We can't promote the splice's scope until we see
4881+
// the corresponding monitor update for it, but we track the txid so we know which
4882+
// holder commitment transaction we may need to broadcast.
4883+
if let Some(alternative_funding) = self.pending_funding.iter()
4884+
.find(|funding| funding.funding_txid() == txid)
4885+
{
4886+
debug_assert!(self.funding_spend_confirmed.is_none());
4887+
debug_assert!(
4888+
!self.onchain_events_awaiting_threshold_conf.iter()
4889+
.any(|e| matches!(e.event, OnchainEvent::FundingSpendConfirmation { .. }))
4890+
);
4891+
debug_assert_eq!(
4892+
self.funding.funding_outpoint().into_bitcoin_outpoint(),
4893+
tx.input[0].previous_output
4894+
);
4895+
4896+
let (desc, msg) = if alternative_funding.channel_parameters.splice_parent_funding_txid.is_some() {
4897+
("Splice", "splice_locked")
4898+
} else {
4899+
("RBF", "channel_ready")
4900+
};
4901+
let action = if self.no_further_updates_allowed() {
4902+
if self.holder_tx_signed {
4903+
", broadcasting post-splice holder commitment transaction".to_string()
4904+
} else {
4905+
"".to_string()
4906+
}
4907+
} else {
4908+
format!(", waiting for `{}` exchange", msg)
4909+
};
4910+
log_info!(logger, "{desc} for channel {} confirmed with txid {txid}{action}", self.channel_id());
4911+
4912+
self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry {
4913+
txid,
4914+
transaction: Some((*tx).clone()),
4915+
height,
4916+
block_hash: Some(block_hash),
4917+
event: OnchainEvent::AlternativeFundingConfirmation {},
4918+
});
4919+
4920+
continue 'tx_iter;
4921+
}
4922+
48744923
if tx.input.len() == 1 {
48754924
// Assuming our keys were not leaked (in which case we're screwed no matter what),
48764925
// commitment transactions and HTLC transactions will all only ever have one input
@@ -5002,7 +5051,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
50025051
let unmatured_htlcs: Vec<_> = self.onchain_events_awaiting_threshold_conf
50035052
.iter()
50045053
.filter_map(|entry| match &entry.event {
5005-
OnchainEvent::HTLCUpdate { source, .. } => Some(source),
5054+
OnchainEvent::HTLCUpdate { source, .. } => Some(source.clone()),
50065055
_ => None,
50075056
})
50085057
.collect();
@@ -5017,7 +5066,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
50175066
#[cfg(debug_assertions)]
50185067
{
50195068
debug_assert!(
5020-
!unmatured_htlcs.contains(&&source),
5069+
!unmatured_htlcs.contains(&source),
50215070
"An unmature HTLC transaction conflicts with a maturing one; failed to \
50225071
call either transaction_unconfirmed for the conflicting transaction \
50235072
or block_disconnected for a block containing it.");
@@ -5064,6 +5113,21 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
50645113
self.funding_spend_confirmed = Some(entry.txid);
50655114
self.confirmed_commitment_tx_counterparty_output = commitment_tx_to_counterparty_output;
50665115
},
5116+
OnchainEvent::AlternativeFundingConfirmation {} => {
5117+
// An alternative funding transaction has irrevocably confirmed. Locate the
5118+
// corresponding scope and promote it if the monitor is no longer allowing
5119+
// updates. Otherwise, we expect it to be promoted via
5120+
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`].
5121+
if self.no_further_updates_allowed() {
5122+
let funding_txid = entry.transaction
5123+
.expect("Transactions are always present for AlternativeFundingConfirmation entries")
5124+
.compute_txid();
5125+
debug_assert_ne!(self.funding.funding_txid(), funding_txid);
5126+
if let Err(_) = self.promote_funding(funding_txid) {
5127+
log_error!(logger, "Missing scope for alternative funding confirmation with txid {}", entry.txid);
5128+
}
5129+
}
5130+
},
50675131
}
50685132
}
50695133

0 commit comments

Comments
 (0)