Skip to content

Commit 0bf41ab

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 3bdf268 commit 0bf41ab

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)]
@@ -4732,6 +4738,49 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
47324738
}
47334739
}
47344740

4741+
// A splice transaction has confirmed. We can't promote the splice's scope until we see
4742+
// the corresponding monitor update for it, but we track the txid so we know which
4743+
// holder commitment transaction we may need to broadcast.
4744+
if let Some(alternative_funding) = self.pending_funding.iter()
4745+
.find(|funding| funding.funding_txid() == txid)
4746+
{
4747+
debug_assert!(self.funding_spend_confirmed.is_none());
4748+
debug_assert!(
4749+
!self.onchain_events_awaiting_threshold_conf.iter()
4750+
.any(|e| matches!(e.event, OnchainEvent::FundingSpendConfirmation { .. }))
4751+
);
4752+
debug_assert_eq!(
4753+
self.funding.funding_outpoint().into_bitcoin_outpoint(),
4754+
tx.input[0].previous_output
4755+
);
4756+
4757+
let (desc, msg) = if alternative_funding.channel_parameters.splice_parent_funding_txid.is_some() {
4758+
("Splice", "splice_locked")
4759+
} else {
4760+
("RBF", "channel_ready")
4761+
};
4762+
let action = if self.no_further_updates_allowed() {
4763+
if self.holder_tx_signed {
4764+
", broadcasting post-splice holder commitment transaction".to_string()
4765+
} else {
4766+
"".to_string()
4767+
}
4768+
} else {
4769+
format!(", waiting for `{}` exchange", msg)
4770+
};
4771+
log_info!(logger, "{desc} for channel {} confirmed with txid {txid}{action}", self.channel_id());
4772+
4773+
self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry {
4774+
txid,
4775+
transaction: Some((*tx).clone()),
4776+
height,
4777+
block_hash: Some(block_hash),
4778+
event: OnchainEvent::AlternativeFundingConfirmation {},
4779+
});
4780+
4781+
continue 'tx_iter;
4782+
}
4783+
47354784
if tx.input.len() == 1 {
47364785
// Assuming our keys were not leaked (in which case we're screwed no matter what),
47374786
// commitment transactions and HTLC transactions will all only ever have one input
@@ -4863,7 +4912,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
48634912
let unmatured_htlcs: Vec<_> = self.onchain_events_awaiting_threshold_conf
48644913
.iter()
48654914
.filter_map(|entry| match &entry.event {
4866-
OnchainEvent::HTLCUpdate { source, .. } => Some(source),
4915+
OnchainEvent::HTLCUpdate { source, .. } => Some(source.clone()),
48674916
_ => None,
48684917
})
48694918
.collect();
@@ -4878,7 +4927,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
48784927
#[cfg(debug_assertions)]
48794928
{
48804929
debug_assert!(
4881-
!unmatured_htlcs.contains(&&source),
4930+
!unmatured_htlcs.contains(&source),
48824931
"An unmature HTLC transaction conflicts with a maturing one; failed to \
48834932
call either transaction_unconfirmed for the conflicting transaction \
48844933
or block_disconnected for a block containing it.");
@@ -4925,6 +4974,21 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
49254974
self.funding_spend_confirmed = Some(entry.txid);
49264975
self.confirmed_commitment_tx_counterparty_output = commitment_tx_to_counterparty_output;
49274976
},
4977+
OnchainEvent::AlternativeFundingConfirmation {} => {
4978+
// An alternative funding transaction has irrevocably confirmed. Locate the
4979+
// corresponding scope and promote it if the monitor is no longer allowing
4980+
// updates. Otherwise, we expect it to be promoted via
4981+
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`].
4982+
if self.no_further_updates_allowed() {
4983+
let funding_txid = entry.transaction
4984+
.expect("Transactions are always present for AlternativeFundingConfirmation entries")
4985+
.compute_txid();
4986+
debug_assert_ne!(self.funding.funding_txid(), funding_txid);
4987+
if let Err(_) = self.promote_funding(funding_txid) {
4988+
log_error!(logger, "Missing scope for alternative funding confirmation with txid {}", entry.txid);
4989+
}
4990+
}
4991+
},
49284992
}
49294993
}
49304994

0 commit comments

Comments
 (0)