Skip to content

Commit 60d97d0

Browse files
committed
Wait for reorg safety when promoting zero conf funding scopes
If we don't, it's possible that an alternative funding transaction confirms instead of the one that was locked and we're not able to broadcast a holder commitment to recover our funds. Note that if another funding transaction confirms other than the latest zero conf negotiated one, then we must force close the channel, as commitment updates are only made for the latest zero conf negotiated funding transaction as it's considered "locked"
1 parent f8926b9 commit 60d97d0

File tree

1 file changed

+138
-27
lines changed

1 file changed

+138
-27
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 138 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,9 @@ enum OnchainEvent {
531531
},
532532
/// An output waiting on [`ANTI_REORG_DELAY`] confirmations before we hand the user the
533533
/// [`SpendableOutputDescriptor`].
534-
MaturingOutput { descriptor: SpendableOutputDescriptor },
534+
MaturingOutput {
535+
descriptor: SpendableOutputDescriptor,
536+
},
535537
/// A spend of the funding output, either a commitment transaction or a cooperative closing
536538
/// transaction.
537539
FundingSpendConfirmation {
@@ -572,6 +574,17 @@ enum OnchainEvent {
572574
/// once [`ChannelMonitor::no_further_updates_allowed`] returns true. We promote the
573575
/// `FundingScope` once the funding transaction is irrevocably confirmed.
574576
AlternativeFundingConfirmation {},
577+
// We've negotiated and locked (via a [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`]) a
578+
// zero conf funding transcation. If confirmations were required, we'd remove all alternative
579+
// funding transactions and their associated holder commitment transactions, as we assume they
580+
// can no longer confirm. However, with zero conf funding transactions, we cannot guarantee
581+
// this. Ultimately an alternative funding transaction could actually end up on chain, and we
582+
// must still be able to claim our funds from it.
583+
//
584+
// This event will only be generated when a
585+
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] is applied for a zero conf funding
586+
// transaction.
587+
ZeroConfFundingReorgSafety {},
575588
}
576589

577590
impl Writeable for OnchainEventEntry {
@@ -621,6 +634,7 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
621634
(0, on_local_output_csv, option),
622635
(1, commitment_tx_to_counterparty_output, option),
623636
},
637+
(4, ZeroConfFundingReorgSafety) => {},
624638
(5, HTLCSpendConfirmation) => {
625639
(0, commitment_tx_output_idx, required),
626640
(2, preimage, option),
@@ -1295,6 +1309,11 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
12951309
prev_holder_htlc_data: Option<CommitmentHTLCData>,
12961310

12971311
alternative_funding_confirmed: Option<(Txid, u32)>,
1312+
1313+
// If a funding transaction has been renegotiated for the channel and it requires zero
1314+
// confirmations to be considered locked, we wait for `ANTI_REORG_DELAY` confirmations until we
1315+
// no longer track alternative funding candidates.
1316+
wait_for_0conf_funding_reorg_safety: bool,
12981317
}
12991318

13001319
// Macro helper to access holder commitment HTLC data (including both non-dust and dust) while
@@ -1552,6 +1571,8 @@ impl<Signer: EcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signer> {
15521571
_ => self.pending_monitor_events.clone(),
15531572
};
15541573

1574+
let wait_for_0conf_funding_reorg_safety = self.wait_for_0conf_funding_reorg_safety.then(|| ());
1575+
15551576
write_tlv_fields!(writer, {
15561577
(1, self.funding_spend_confirmed, option),
15571578
(3, self.htlcs_resolved_on_chain, required_vec),
@@ -1571,6 +1592,7 @@ impl<Signer: EcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signer> {
15711592
(31, self.funding.channel_parameters, required),
15721593
(32, self.pending_funding, optional_vec),
15731594
(34, self.alternative_funding_confirmed, option),
1595+
(36, wait_for_0conf_funding_reorg_safety, option),
15741596
});
15751597

15761598
Ok(())
@@ -1798,6 +1820,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
17981820
prev_holder_htlc_data: None,
17991821

18001822
alternative_funding_confirmed: None,
1823+
wait_for_0conf_funding_reorg_safety: false,
18011824
})
18021825
}
18031826

@@ -3419,6 +3442,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34193442
self.update_holder_commitment_data(vec![holder_commitment_tx], htlc_data, claimed_htlcs)
34203443
}
34213444

3445+
// TODO(splicing): Consider `wait_for_0conf_funding_reorg_safety`.
34223446
fn verify_matching_commitment_transactions<
34233447
'a,
34243448
I: ExactSizeIterator<Item = &'a CommitmentTransaction>,
@@ -3795,14 +3819,16 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
37953819
if let Some(parent_funding_txid) = channel_parameters.splice_parent_funding_txid.as_ref() {
37963820
// Only one splice can be negotiated at a time after we've exchanged `channel_ready`
37973821
// (implying our funding is confirmed) that spends our currently locked funding.
3798-
if !self.pending_funding.is_empty() {
3822+
if !self.pending_funding.is_empty() && !self.wait_for_0conf_funding_reorg_safety {
37993823
log_error!(
38003824
logger,
38013825
"Negotiated splice while channel is pending channel_ready/splice_locked"
38023826
);
38033827
return Err(());
38043828
}
3805-
if *parent_funding_txid != self.funding.funding_txid() {
3829+
if *parent_funding_txid != self.funding.funding_txid()
3830+
|| alternative_funding_outpoint.txid != self.funding.funding_txid()
3831+
{
38063832
log_error!(
38073833
logger,
38083834
"Negotiated splice that does not spend currently locked funding transaction"
@@ -3822,6 +3848,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
38223848
);
38233849
self.pending_funding.push(alternative_funding);
38243850

3851+
// FIXME: Wait for anything below `ANTI_REORG_DELAY`, not just zero conf?
3852+
if !self.wait_for_0conf_funding_reorg_safety {
3853+
self.wait_for_0conf_funding_reorg_safety = confirmation_depth == 0;
3854+
}
3855+
38253856
Ok(())
38263857
}
38273858

@@ -3841,12 +3872,20 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
38413872
self.funding.prev_holder_commitment_tx.clone(),
38423873
);
38433874

3875+
if self.wait_for_0conf_funding_reorg_safety {
3876+
return Ok(());
3877+
}
3878+
38443879
// The swap above places the previous `FundingScope` into `pending_funding`.
38453880
for funding in self.pending_funding.drain(..) {
38463881
self.outputs_to_watch.remove(&funding.funding_txid());
38473882
}
38483883
self.alternative_funding_confirmed.take();
38493884

3885+
// Make sure we're no longer waiting for zero conf funding reorg safety if a non-zero conf
3886+
// splice is negotiated and locked after a zero conf one.
3887+
self.wait_for_0conf_funding_reorg_safety = false;
3888+
38503889
Ok(())
38513890
}
38523891

@@ -4926,20 +4965,33 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
49264965
}
49274966
}
49284967

4929-
// A splice/dual-funded RBF transaction has confirmed. We can't promote the
4930-
// `FundingScope` scope until we see the
4968+
// A splice/dual-funded RBF transaction has confirmed. If it's not zero conf, we can't
4969+
// promote the `FundingScope` scope until we see the
49314970
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] for it, but we track the txid
49324971
// so we know which holder commitment transaction we may need to broadcast.
4933-
if let Some(alternative_funding) = self.pending_funding.iter()
4972+
if let Some((confirmed_funding, is_funding_locked)) = self
4973+
.pending_funding
4974+
.iter()
49344975
.find(|funding| funding.funding_txid() == txid)
4976+
.map(|funding| (funding, false))
4977+
.or_else(|| {
4978+
(self.funding.funding_txid() == txid).then(|| &self.funding)
4979+
.filter(|_| self.wait_for_0conf_funding_reorg_safety)
4980+
.map(|funding| (funding, true))
4981+
})
49354982
{
4983+
debug_assert!(self.alternative_funding_confirmed.is_none());
4984+
debug_assert!(
4985+
!self.onchain_events_awaiting_threshold_conf.iter()
4986+
.any(|e| matches!(e.event, OnchainEvent::AlternativeFundingConfirmation {}))
4987+
);
49364988
debug_assert!(self.funding_spend_confirmed.is_none());
49374989
debug_assert!(
49384990
!self.onchain_events_awaiting_threshold_conf.iter()
49394991
.any(|e| matches!(e.event, OnchainEvent::FundingSpendConfirmation { .. }))
49404992
);
49414993

4942-
let (desc, msg) = if alternative_funding.channel_parameters.splice_parent_funding_txid.is_some() {
4994+
let (desc, msg) = if confirmed_funding.is_splice() {
49434995
debug_assert!(tx.input.iter().any(|input| {
49444996
let funding_outpoint = self.funding.funding_outpoint().into_bitcoin_outpoint();
49454997
input.previous_output == funding_outpoint
@@ -4948,36 +5000,77 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
49485000
} else {
49495001
("RBF", "channel_ready")
49505002
};
4951-
let action = if self.no_further_updates_allowed() {
4952-
if self.holder_tx_signed {
4953-
", broadcasting post-splice holder commitment transaction".to_string()
4954-
} else {
4955-
"".to_string()
4956-
}
4957-
} else {
5003+
let action = if self.holder_tx_signed {
5004+
", broadcasting post-splice holder commitment transaction".to_string()
5005+
} else if !self.no_further_updates_allowed() && !is_funding_locked {
49585006
format!(", waiting for `{msg}` exchange")
5007+
} else {
5008+
"".to_string()
49595009
};
49605010
log_info!(logger, "{desc} for channel {} confirmed with txid {txid}{action}", self.channel_id());
49615011

4962-
self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry {
4963-
txid,
4964-
transaction: Some((*tx).clone()),
4965-
height,
4966-
block_hash: Some(block_hash),
4967-
event: OnchainEvent::AlternativeFundingConfirmation {},
4968-
});
5012+
if is_funding_locked {
5013+
// We can only have already processed the
5014+
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] if we're zero conf,
5015+
// but we still track the alternative funding transactions until we're no longer
5016+
// under reorg risk.
5017+
debug_assert!(self.wait_for_0conf_funding_reorg_safety);
49695018

4970-
if self.holder_tx_signed {
5019+
let event_entry = OnchainEventEntry {
5020+
txid,
5021+
transaction: Some((*tx).clone()),
5022+
height,
5023+
block_hash: Some(block_hash),
5024+
event: OnchainEvent::ZeroConfFundingReorgSafety {},
5025+
};
5026+
5027+
if let Some(event) = self.onchain_events_awaiting_threshold_conf
5028+
.iter_mut()
5029+
.find(|e| matches!(e.event, OnchainEvent::ZeroConfFundingReorgSafety {}))
5030+
{
5031+
// Since channels may chain multiple zero conf splices, we only want to track
5032+
// one event overall.
5033+
*event = event_entry;
5034+
} else {
5035+
self.onchain_events_awaiting_threshold_conf.push(event_entry);
5036+
}
5037+
} else {
5038+
debug_assert!(
5039+
!self.onchain_events_awaiting_threshold_conf.iter()
5040+
.any(|e| matches!(e.event, OnchainEvent::ZeroConfFundingReorgSafety {}))
5041+
);
5042+
5043+
self.alternative_funding_confirmed = Some((txid, height));
5044+
5045+
if self.no_further_updates_allowed() {
5046+
// We can no longer rely on
5047+
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] to promote the
5048+
// scope, do so when the funding is no longer under reorg risk.
5049+
self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry {
5050+
txid,
5051+
transaction: Some((*tx).clone()),
5052+
height,
5053+
block_hash: Some(block_hash),
5054+
event: OnchainEvent::AlternativeFundingConfirmation {},
5055+
});
5056+
}
5057+
}
5058+
5059+
// If we've previously broadcast a holder commitment, queue claims for the
5060+
// alternative holder commitment now that it is the only one that can confirm (until
5061+
// we see a reorg of its funding transaction). We also choose to broadcast if our
5062+
// intended funding transaction was locked with zero confirmations, but another
5063+
// funding transaction has now confirmed, since commitment updates were only made to
5064+
// the locked funding.
5065+
if self.holder_tx_signed ||
5066+
(self.wait_for_0conf_funding_reorg_safety && !is_funding_locked)
5067+
{
49715068
// Cancel any previous claims that are no longer valid as they stemmed from a
49725069
// different funding transaction.
49735070
let alternative_holder_commitment_txid =
4974-
alternative_funding.current_holder_commitment_tx.trust().txid();
5071+
confirmed_funding.current_holder_commitment_tx.trust().txid();
49755072
self.cancel_prev_commitment_claims(&logger, &alternative_holder_commitment_txid);
49765073

4977-
// Queue claims for the alternative holder commitment since it is the only one
4978-
// that can currently confirm so far (until we see a reorg of its funding
4979-
// transaction).
4980-
//
49815074
// It's possible we process a counterparty commitment within this same block
49825075
// that would invalidate our holder commitment. If we were to broadcast our
49835076
// holder commitment now, we wouldn't be able to cancel it via our usual
@@ -5204,6 +5297,21 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
52045297
log_error!(logger, "Missing scope for alternative funding confirmation with txid {}", entry.txid);
52055298
}
52065299
},
5300+
OnchainEvent::ZeroConfFundingReorgSafety {} => {
5301+
if self.wait_for_0conf_funding_reorg_safety {
5302+
debug_assert_eq!(self.funding.funding_txid(), entry.txid);
5303+
debug_assert!(self.alternative_funding_confirmed.is_none());
5304+
self
5305+
.pending_funding
5306+
.drain(..)
5307+
.for_each(|funding| {
5308+
self.outputs_to_watch.remove(&funding.funding_txid());
5309+
});
5310+
self.wait_for_0conf_funding_reorg_safety = false;
5311+
} else {
5312+
debug_assert!(false, "These events can only be generated when wait_for_0conf_funding_reorg_safety is set");
5313+
}
5314+
},
52075315
}
52085316
}
52095317

@@ -6057,6 +6165,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
60576165
let mut channel_parameters = None;
60586166
let mut pending_funding = None;
60596167
let mut alternative_funding_confirmed = None;
6168+
let mut wait_for_0conf_funding_reorg_safety: Option<()> = None;
60606169
read_tlv_fields!(reader, {
60616170
(1, funding_spend_confirmed, option),
60626171
(3, htlcs_resolved_on_chain, optional_vec),
@@ -6076,6 +6185,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
60766185
(31, channel_parameters, (option: ReadableArgs, None)),
60776186
(32, pending_funding, optional_vec),
60786187
(34, alternative_funding_confirmed, option),
6188+
(36, wait_for_0conf_funding_reorg_safety, option),
60796189
});
60806190
if let Some(payment_preimages_with_info) = payment_preimages_with_info {
60816191
if payment_preimages_with_info.len() != payment_preimages.len() {
@@ -6247,6 +6357,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
62476357
prev_holder_htlc_data,
62486358

62496359
alternative_funding_confirmed,
6360+
wait_for_0conf_funding_reorg_safety: wait_for_0conf_funding_reorg_safety.is_some(),
62506361
})))
62516362
}
62526363
}

0 commit comments

Comments
 (0)