Skip to content

Commit 511fdc8

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 c8b394c commit 511fdc8

File tree

2 files changed

+145
-31
lines changed

2 files changed

+145
-31
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 139 additions & 31 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),
@@ -684,6 +698,7 @@ pub(crate) enum ChannelMonitorUpdateStep {
684698
channel_parameters: ChannelTransactionParameters,
685699
holder_commitment_tx: HolderCommitmentTransaction,
686700
counterparty_commitment_tx: CommitmentTransaction,
701+
confirmation_depth: u32,
687702
},
688703
RenegotiatedFundingLocked {
689704
funding_txid: Txid,
@@ -751,6 +766,7 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
751766
(1, channel_parameters, (required: ReadableArgs, None)),
752767
(3, holder_commitment_tx, required),
753768
(5, counterparty_commitment_tx, required),
769+
(7, confirmation_depth, required),
754770
},
755771
(12, RenegotiatedFundingLocked) => {
756772
(1, funding_txid, required),
@@ -1293,6 +1309,11 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
12931309
prev_holder_htlc_data: Option<CommitmentHTLCData>,
12941310

12951311
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,
12961317
}
12971318

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

1574+
let wait_for_0conf_funding_reorg_safety = self.wait_for_0conf_funding_reorg_safety.then(|| ());
1575+
15531576
write_tlv_fields!(writer, {
15541577
(1, self.funding_spend_confirmed, option),
15551578
(3, self.htlcs_resolved_on_chain, required_vec),
@@ -1569,6 +1592,7 @@ impl<Signer: EcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signer> {
15691592
(31, self.funding.channel_parameters, required),
15701593
(32, self.pending_funding, optional_vec),
15711594
(34, self.alternative_funding_confirmed, option),
1595+
(36, wait_for_0conf_funding_reorg_safety, option),
15721596
});
15731597

15741598
Ok(())
@@ -1796,6 +1820,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
17961820
prev_holder_htlc_data: None,
17971821

17981822
alternative_funding_confirmed: None,
1823+
wait_for_0conf_funding_reorg_safety: false,
17991824
})
18001825
}
18011826

@@ -3417,6 +3442,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34173442
self.update_holder_commitment_data(vec![holder_commitment_tx], htlc_data, claimed_htlcs)
34183443
}
34193444

3445+
// TODO(splicing): Consider `wait_for_0conf_funding_reorg_safety`.
34203446
fn verify_matching_commitment_transactions<
34213447
'a,
34223448
I: ExactSizeIterator<Item = &'a CommitmentTransaction>,
@@ -3704,7 +3730,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
37043730
&mut self, logger: &WithChannelMonitor<L>,
37053731
channel_parameters: &ChannelTransactionParameters,
37063732
alternative_holder_commitment_tx: &HolderCommitmentTransaction,
3707-
alternative_counterparty_commitment_tx: &CommitmentTransaction,
3733+
alternative_counterparty_commitment_tx: &CommitmentTransaction, confirmation_depth: u32,
37083734
) -> Result<(), ()>
37093735
where
37103736
L::Target: Logger,
@@ -3793,14 +3819,16 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
37933819
if let Some(parent_funding_txid) = channel_parameters.splice_parent_funding_txid.as_ref() {
37943820
// Only one splice can be negotiated at a time after we've exchanged `channel_ready`
37953821
// (implying our funding is confirmed) that spends our currently locked funding.
3796-
if !self.pending_funding.is_empty() {
3822+
if !self.pending_funding.is_empty() && !self.wait_for_0conf_funding_reorg_safety {
37973823
log_error!(
37983824
logger,
37993825
"Negotiated splice while channel is pending channel_ready/splice_locked"
38003826
);
38013827
return Err(());
38023828
}
3803-
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+
{
38043832
log_error!(
38053833
logger,
38063834
"Negotiated splice that does not spend currently locked funding transaction"
@@ -3820,6 +3848,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
38203848
);
38213849
self.pending_funding.push(alternative_funding);
38223850

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+
38233856
Ok(())
38243857
}
38253858

@@ -3831,20 +3864,28 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
38313864
if new_funding.is_none() {
38323865
return Err(());
38333866
}
3834-
let mut new_funding = new_funding.unwrap();
3867+
let new_funding = new_funding.unwrap();
38353868

3836-
mem::swap(&mut self.funding, &mut new_funding);
3869+
mem::swap(&mut self.funding, new_funding);
38373870
self.onchain_tx_handler.update_after_renegotiated_funding_locked(
38383871
self.funding.current_holder_commitment_tx.clone(),
38393872
self.funding.prev_holder_commitment_tx.clone(),
38403873
);
38413874

3875+
if self.wait_for_0conf_funding_reorg_safety {
3876+
return Ok(());
3877+
}
3878+
38423879
// The swap above places the previous `FundingScope` into `pending_funding`.
38433880
for funding in self.pending_funding.drain(..) {
38443881
self.outputs_to_watch.remove(&funding.funding_txid());
38453882
}
38463883
self.alternative_funding_confirmed.take();
38473884

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+
38483889
Ok(())
38493890
}
38503891

@@ -3957,11 +3998,13 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
39573998
},
39583999
ChannelMonitorUpdateStep::RenegotiatedFunding {
39594000
channel_parameters, holder_commitment_tx, counterparty_commitment_tx,
4001+
confirmation_depth,
39604002
} => {
39614003
log_trace!(logger, "Updating ChannelMonitor with alternative holder and counterparty commitment transactions for funding txid {}",
39624004
channel_parameters.funding_outpoint.unwrap().txid);
39634005
if let Err(_) = self.renegotiated_funding(
39644006
logger, channel_parameters, holder_commitment_tx, counterparty_commitment_tx,
4007+
*confirmation_depth,
39654008
) {
39664009
ret = Err(());
39674010
}
@@ -4922,20 +4965,33 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
49224965
}
49234966
}
49244967

4925-
// A splice/dual-funded RBF transaction has confirmed. We can't promote the
4926-
// `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
49274970
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] for it, but we track the txid
49284971
// so we know which holder commitment transaction we may need to broadcast.
4929-
if let Some(alternative_funding) = self.pending_funding.iter()
4972+
if let Some((confirmed_funding, is_funding_locked)) = self
4973+
.pending_funding
4974+
.iter()
49304975
.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+
})
49314982
{
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+
);
49324988
debug_assert!(self.funding_spend_confirmed.is_none());
49334989
debug_assert!(
49344990
!self.onchain_events_awaiting_threshold_conf.iter()
49354991
.any(|e| matches!(e.event, OnchainEvent::FundingSpendConfirmation { .. }))
49364992
);
49374993

4938-
let (desc, msg) = if alternative_funding.channel_parameters.splice_parent_funding_txid.is_some() {
4994+
let (desc, msg) = if confirmed_funding.is_splice() {
49394995
debug_assert!(tx.input.iter().any(|input| {
49404996
let funding_outpoint = self.funding.funding_outpoint().into_bitcoin_outpoint();
49414997
input.previous_output == funding_outpoint
@@ -4944,43 +5000,77 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
49445000
} else {
49455001
("RBF", "channel_ready")
49465002
};
4947-
let action = if self.no_further_updates_allowed() {
4948-
if self.holder_tx_signed {
4949-
", broadcasting post-splice holder commitment transaction".to_string()
4950-
} else {
4951-
"".to_string()
4952-
}
4953-
} 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 {
49545006
format!(", waiting for `{msg}` exchange")
5007+
} else {
5008+
"".to_string()
49555009
};
49565010
log_info!(logger, "{desc} for channel {} confirmed with txid {txid}{action}", self.channel_id());
49575011

4958-
self.alternative_funding_confirmed = Some((txid, height));
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);
49595018

4960-
if self.no_further_updates_allowed() {
4961-
// We can no longer rely on
4962-
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] to promote the
4963-
// scope, do so when the funding is no longer under reorg risk.
4964-
self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry {
5019+
let event_entry = OnchainEventEntry {
49655020
txid,
49665021
transaction: Some((*tx).clone()),
49675022
height,
49685023
block_hash: Some(block_hash),
4969-
event: OnchainEvent::AlternativeFundingConfirmation {},
4970-
});
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+
}
49715057
}
49725058

4973-
if self.holder_tx_signed {
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+
{
49745068
// Cancel any previous claims that are no longer valid as they stemmed from a
49755069
// different funding transaction.
49765070
let alternative_holder_commitment_txid =
4977-
alternative_funding.current_holder_commitment_tx.trust().txid();
5071+
confirmed_funding.current_holder_commitment_tx.trust().txid();
49785072
self.cancel_prev_commitment_claims(&logger, &alternative_holder_commitment_txid);
49795073

4980-
// Queue claims for the alternative holder commitment since it is the only one
4981-
// that can currently confirm so far (until we see a reorg of its funding
4982-
// transaction).
4983-
//
49845074
// It's possible we process a counterparty commitment within this same block
49855075
// that would invalidate our holder commitment. If we were to broadcast our
49865076
// holder commitment now, we wouldn't be able to cancel it via our usual
@@ -5207,6 +5297,21 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
52075297
log_error!(logger, "Missing scope for alternative funding confirmation with txid {}", entry.txid);
52085298
}
52095299
},
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+
},
52105315
}
52115316
}
52125317

@@ -6071,6 +6176,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
60716176
let mut channel_parameters = None;
60726177
let mut pending_funding = None;
60736178
let mut alternative_funding_confirmed = None;
6179+
let mut wait_for_0conf_funding_reorg_safety: Option<()> = None;
60746180
read_tlv_fields!(reader, {
60756181
(1, funding_spend_confirmed, option),
60766182
(3, htlcs_resolved_on_chain, optional_vec),
@@ -6090,6 +6196,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
60906196
(31, channel_parameters, (option: ReadableArgs, None)),
60916197
(32, pending_funding, optional_vec),
60926198
(34, alternative_funding_confirmed, option),
6199+
(36, wait_for_0conf_funding_reorg_safety, option),
60936200
});
60946201
if let Some(payment_preimages_with_info) = payment_preimages_with_info {
60956202
if payment_preimages_with_info.len() != payment_preimages.len() {
@@ -6261,6 +6368,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
62616368
prev_holder_htlc_data,
62626369

62636370
alternative_funding_confirmed,
6371+
wait_for_0conf_funding_reorg_safety: wait_for_0conf_funding_reorg_safety.is_some(),
62646372
})))
62656373
}
62666374
}

lightning/src/ln/channel.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6842,6 +6842,11 @@ where
68426842
);
68436843
}
68446844

6845+
let confirmation_depth = self
6846+
.context
6847+
.minimum_depth(pending_splice_funding)
6848+
.expect("ChannelContext::minimum_depth should be set for FundedChannel");
6849+
68456850
log_info!(logger, "Received splice initial commitment_signed from peer for channel {} with funding txid {}",
68466851
&self.context.channel_id(), pending_splice_funding.get_funding_txo().unwrap().txid);
68476852

@@ -6852,6 +6857,7 @@ where
68526857
channel_parameters: pending_splice_funding.channel_transaction_parameters.clone(),
68536858
holder_commitment_tx,
68546859
counterparty_commitment_tx,
6860+
confirmation_depth,
68556861
}],
68566862
channel_id: Some(self.context.channel_id()),
68576863
};

0 commit comments

Comments
 (0)