Skip to content

Commit 8212264

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 8212264

File tree

2 files changed

+106
-14
lines changed

2 files changed

+106
-14
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,9 @@ impl OnchainEventEntry {
496496
// it's broadcastable when we see the previous block.
497497
conf_threshold = cmp::max(conf_threshold, self.height + csv as u32 - 1);
498498
},
499+
OnchainEvent::AlternativeFundingConfirmation { confirmation_depth } => {
500+
conf_threshold = cmp::max(conf_threshold, self.height + confirmation_depth - 1);
501+
},
499502
_ => {},
500503
}
501504
conf_threshold
@@ -565,6 +568,16 @@ enum OnchainEvent {
565568
/// output (and generate a SpendableOutput event).
566569
on_to_local_output_csv: Option<u16>,
567570
},
571+
/// An alternative funding transaction (due to a splice/RBF) has confirmed but is not yet
572+
/// locked, invalidating the previous funding transaction. Note that we wait to promote the
573+
/// corresponding `FundingScope` until we see a
574+
/// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] or if the alternative funding
575+
/// transaction is irrevocably confirmed.
576+
AlternativeFundingConfirmation {
577+
// The confirmation depth negotiated and required for the funding transaction to be
578+
// considered locked.
579+
confirmation_depth: u32,
580+
},
568581
}
569582

570583
impl Writeable for OnchainEventEntry {
@@ -609,6 +622,9 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
609622
(1, MaturingOutput) => {
610623
(0, descriptor, required),
611624
},
625+
(2, AlternativeFundingConfirmation) => {
626+
(1, confirmation_depth, required),
627+
},
612628
(3, FundingSpendConfirmation) => {
613629
(0, on_local_output_csv, option),
614630
(1, commitment_tx_to_counterparty_output, option),
@@ -618,7 +634,6 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
618634
(2, preimage, option),
619635
(4, on_to_local_output_csv, option),
620636
},
621-
622637
);
623638

624639
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -677,6 +692,7 @@ pub(crate) enum ChannelMonitorUpdateStep {
677692
channel_parameters: ChannelTransactionParameters,
678693
holder_commitment_tx: HolderCommitmentTransaction,
679694
counterparty_commitment_tx: CommitmentTransaction,
695+
confirmation_depth: u32,
680696
},
681697
RenegotiatedFundingLocked {
682698
funding_txid: Txid,
@@ -744,6 +760,7 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
744760
(1, channel_parameters, (required: ReadableArgs, None)),
745761
(3, holder_commitment_tx, required),
746762
(5, counterparty_commitment_tx, required),
763+
(7, confirmation_depth, required),
747764
},
748765
(12, RenegotiatedFundingLocked) => {
749766
(1, funding_txid, required),
@@ -1111,7 +1128,7 @@ impl_writeable_tlv_based!(FundingScope, {
11111128
#[derive(Clone, PartialEq)]
11121129
pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
11131130
funding: FundingScope,
1114-
pending_funding: Vec<FundingScope>,
1131+
pending_funding: Vec<(FundingScope, u32)>,
11151132

11161133
latest_update_id: u64,
11171134
commitment_transaction_number_obscure_factor: u64,
@@ -1944,7 +1961,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
19441961
{
19451962
let lock = self.inner.lock().unwrap();
19461963
let logger = WithChannelMonitor::from_impl(logger, &*lock, None);
1947-
for funding in core::iter::once(&lock.funding).chain(&lock.pending_funding) {
1964+
for funding in core::iter::once(&lock.funding)
1965+
.chain(lock.pending_funding.iter().map(|pending_funding| &pending_funding.0))
1966+
{
19481967
let funding_outpoint = funding.funding_outpoint();
19491968
log_trace!(&logger, "Registering funding outpoint {} with the filter to monitor confirmations", &funding_outpoint);
19501969
let script_pubkey = funding.channel_parameters.make_funding_redeemscript().to_p2wsh();
@@ -3313,8 +3332,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
33133332
current_funding_commitment_tx.per_commitment_point(),
33143333
);
33153334

3316-
for (pending_funding, commitment_tx) in
3317-
self.pending_funding.iter_mut().zip(commitment_txs.iter().skip(1))
3335+
for (pending_funding, commitment_tx) in self
3336+
.pending_funding
3337+
.iter_mut()
3338+
.map(|pending_funding| &mut pending_funding.0)
3339+
.zip(commitment_txs.iter().skip(1))
33183340
{
33193341
let commitment_txid = commitment_tx.trust().txid();
33203342
pending_funding.prev_counterparty_commitment_txid =
@@ -3410,8 +3432,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34103432
}
34113433

34123434
let mut other_commitment_tx = None::<&CommitmentTransaction>;
3413-
for (funding, commitment_tx) in
3414-
core::iter::once(&self.funding).chain(self.pending_funding.iter()).zip(commitment_txs)
3435+
for (funding, commitment_tx) in core::iter::once(&self.funding)
3436+
.chain(self.pending_funding.iter().map(|pending_funding| &pending_funding.0))
3437+
.zip(commitment_txs)
34153438
{
34163439
let trusted_tx = &commitment_tx.trust().built_transaction().transaction;
34173440
if trusted_tx.input.len() != 1 {
@@ -3466,7 +3489,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34663489
self.current_holder_commitment_number = current_funding_commitment_tx.commitment_number();
34673490
self.onchain_tx_handler.provide_latest_holder_tx(current_funding_commitment_tx.clone());
34683491
for (funding, mut commitment_tx) in core::iter::once(&mut self.funding)
3469-
.chain(self.pending_funding.iter_mut())
3492+
.chain(self.pending_funding.iter_mut().map(|pending_funding| &mut pending_funding.0))
34703493
.zip(commitment_txs.into_iter())
34713494
{
34723495
mem::swap(&mut commitment_tx, &mut funding.current_holder_commitment_tx);
@@ -3674,7 +3697,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
36743697
&mut self, logger: &WithChannelMonitor<L>,
36753698
channel_parameters: &ChannelTransactionParameters,
36763699
alternative_holder_commitment_tx: &HolderCommitmentTransaction,
3677-
alternative_counterparty_commitment_tx: &CommitmentTransaction,
3700+
alternative_counterparty_commitment_tx: &CommitmentTransaction, confirmation_depth: u32,
36783701
) -> Result<(), ()>
36793702
where
36803703
L::Target: Logger,
@@ -3750,7 +3773,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
37503773
if self
37513774
.pending_funding
37523775
.iter()
3753-
.any(|funding| funding.funding_txid() == alternative_funding_outpoint.txid)
3776+
.any(|funding| funding.0.funding_txid() == alternative_funding_outpoint.txid)
37543777
{
37553778
log_error!(
37563779
logger,
@@ -3788,7 +3811,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
37883811
alternative_funding_outpoint.txid,
37893812
vec![(alternative_funding_outpoint.index as u32, script_pubkey)],
37903813
);
3791-
self.pending_funding.push(alternative_funding);
3814+
self.pending_funding.push((alternative_funding, confirmation_depth));
37923815

37933816
Ok(())
37943817
}
@@ -3797,6 +3820,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
37973820
let new_funding = self
37983821
.pending_funding
37993822
.iter_mut()
3823+
.map(|pending_funding| &mut pending_funding.0)
38003824
.find(|funding| funding.funding_txid() == new_funding_txid);
38013825
if new_funding.is_none() {
38023826
return Err(());
@@ -3810,7 +3834,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
38103834
);
38113835

38123836
// The swap above places the previous `FundingScope` into `pending_funding`.
3813-
for funding in self.pending_funding.drain(..) {
3837+
for (funding, _) in self.pending_funding.drain(..) {
38143838
self.outputs_to_watch.remove(&funding.funding_txid());
38153839
}
38163840

@@ -3926,11 +3950,13 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
39263950
},
39273951
ChannelMonitorUpdateStep::RenegotiatedFunding {
39283952
channel_parameters, holder_commitment_tx, counterparty_commitment_tx,
3953+
confirmation_depth,
39293954
} => {
39303955
log_trace!(logger, "Updating ChannelMonitor with alternative holder and counterparty commitment transactions for funding txid {}",
39313956
channel_parameters.funding_outpoint.unwrap().txid);
39323957
if let Err(_) = self.renegotiated_funding(
39333958
logger, channel_parameters, holder_commitment_tx, counterparty_commitment_tx,
3959+
*confirmation_depth,
39343960
) {
39353961
ret = Err(());
39363962
}
@@ -4871,6 +4897,51 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
48714897
}
48724898
}
48734899

4900+
// A splice transaction has confirmed. We can't promote the splice's scope until we see
4901+
// the corresponding monitor update for it, but we track the txid so we know which
4902+
// holder commitment transaction we may need to broadcast.
4903+
if let Some((alternative_funding, confirmation_depth)) = self.pending_funding.iter()
4904+
.find(|funding| funding.0.funding_txid() == txid)
4905+
{
4906+
debug_assert!(self.funding_spend_confirmed.is_none());
4907+
debug_assert!(
4908+
!self.onchain_events_awaiting_threshold_conf.iter()
4909+
.any(|e| matches!(e.event, OnchainEvent::FundingSpendConfirmation { .. }))
4910+
);
4911+
4912+
let (desc, msg) = if alternative_funding.channel_parameters.splice_parent_funding_txid.is_some() {
4913+
debug_assert!(tx.input.iter().any(|input| {
4914+
let funding_outpoint = self.funding.funding_outpoint().into_bitcoin_outpoint();
4915+
input.previous_output == funding_outpoint
4916+
}));
4917+
("Splice", "splice_locked")
4918+
} else {
4919+
("RBF", "channel_ready")
4920+
};
4921+
let action = if self.no_further_updates_allowed() {
4922+
if self.holder_tx_signed {
4923+
", broadcasting post-splice holder commitment transaction".to_string()
4924+
} else {
4925+
"".to_string()
4926+
}
4927+
} else {
4928+
format!(", waiting for `{}` exchange", msg)
4929+
};
4930+
log_info!(logger, "{desc} for channel {} confirmed with txid {txid}{action}", self.channel_id());
4931+
4932+
self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry {
4933+
txid,
4934+
transaction: Some((*tx).clone()),
4935+
height,
4936+
block_hash: Some(block_hash),
4937+
event: OnchainEvent::AlternativeFundingConfirmation {
4938+
confirmation_depth: *confirmation_depth,
4939+
},
4940+
});
4941+
4942+
continue 'tx_iter;
4943+
}
4944+
48744945
if tx.input.len() == 1 {
48754946
// Assuming our keys were not leaked (in which case we're screwed no matter what),
48764947
// commitment transactions and HTLC transactions will all only ever have one input
@@ -5002,7 +5073,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
50025073
let unmatured_htlcs: Vec<_> = self.onchain_events_awaiting_threshold_conf
50035074
.iter()
50045075
.filter_map(|entry| match &entry.event {
5005-
OnchainEvent::HTLCUpdate { source, .. } => Some(source),
5076+
OnchainEvent::HTLCUpdate { source, .. } => Some(source.clone()),
50065077
_ => None,
50075078
})
50085079
.collect();
@@ -5017,7 +5088,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
50175088
#[cfg(debug_assertions)]
50185089
{
50195090
debug_assert!(
5020-
!unmatured_htlcs.contains(&&source),
5091+
!unmatured_htlcs.contains(&source),
50215092
"An unmature HTLC transaction conflicts with a maturing one; failed to \
50225093
call either transaction_unconfirmed for the conflicting transaction \
50235094
or block_disconnected for a block containing it.");
@@ -5064,6 +5135,21 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
50645135
self.funding_spend_confirmed = Some(entry.txid);
50655136
self.confirmed_commitment_tx_counterparty_output = commitment_tx_to_counterparty_output;
50665137
},
5138+
OnchainEvent::AlternativeFundingConfirmation { .. } => {
5139+
// An alternative funding transaction has irrevocably confirmed. Locate the
5140+
// corresponding scope and promote it if the monitor is no longer allowing
5141+
// updates. Otherwise, we expect it to be promoted via
5142+
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`].
5143+
if self.no_further_updates_allowed() {
5144+
let funding_txid = entry.transaction
5145+
.expect("Transactions are always present for AlternativeFundingConfirmation entries")
5146+
.compute_txid();
5147+
debug_assert_ne!(self.funding.funding_txid(), funding_txid);
5148+
if let Err(_) = self.promote_funding(funding_txid) {
5149+
log_error!(logger, "Missing scope for alternative funding confirmation with txid {}", entry.txid);
5150+
}
5151+
}
5152+
},
50675153
}
50685154
}
50695155

lightning/src/ln/channel.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6816,6 +6816,11 @@ where
68166816
);
68176817
}
68186818

6819+
let confirmation_depth = self
6820+
.context
6821+
.minimum_depth(pending_splice_funding)
6822+
.expect("ChannelContext::minimum_depth should be set for FundedChannel");
6823+
68196824
log_info!(logger, "Received splice initial commitment_signed from peer for channel {} with funding txid {}",
68206825
&self.context.channel_id(), pending_splice_funding.get_funding_txo().unwrap().txid);
68216826

@@ -6826,6 +6831,7 @@ where
68266831
channel_parameters: pending_splice_funding.channel_transaction_parameters.clone(),
68276832
holder_commitment_tx,
68286833
counterparty_commitment_tx,
6834+
confirmation_depth,
68296835
}],
68306836
channel_id: Some(self.context.channel_id()),
68316837
};

0 commit comments

Comments
 (0)