Skip to content

Commit ad99d9a

Browse files
committed
Detect channel alternative funding transaction confirmation
Whether it's a splice, or a dual-funded RBF, we need to know which funding transaction out of all of the negotiated ones is currently confirmed in case we need to broadcast the holder commitment.
1 parent ecce268 commit ad99d9a

File tree

1 file changed

+91
-3
lines changed

1 file changed

+91
-3
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,13 @@ enum OnchainEvent {
565565
/// output (and generate a SpendableOutput event).
566566
on_to_local_output_csv: Option<u16>,
567567
},
568+
/// An alternative funding transaction (due to a splice/RBF) has confirmed but can no longer be
569+
/// locked now as the monitor is no longer allowing updates. Note that we wait to promote the
570+
/// corresponding `FundingScope` until we see a
571+
/// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`], but this event is only applicable
572+
/// once [`ChannelMonitor::no_further_updates_allowed`] returns true. We promote the
573+
/// `FundingScope` once the funding transaction is irrevocably confirmed.
574+
AlternativeFundingConfirmation {},
568575
}
569576

570577
impl Writeable for OnchainEventEntry {
@@ -609,6 +616,7 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
609616
(1, MaturingOutput) => {
610617
(0, descriptor, required),
611618
},
619+
(2, AlternativeFundingConfirmation) => {},
612620
(3, FundingSpendConfirmation) => {
613621
(0, on_local_output_csv, option),
614622
(1, commitment_tx_to_counterparty_output, option),
@@ -618,7 +626,6 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
618626
(2, preimage, option),
619627
(4, on_to_local_output_csv, option),
620628
},
621-
622629
);
623630

624631
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -1280,6 +1287,15 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
12801287
// commitment transactions, their ordering with respect to each other must remain the same.
12811288
current_holder_htlc_data: CommitmentHTLCData,
12821289
prev_holder_htlc_data: Option<CommitmentHTLCData>,
1290+
1291+
// Upon confirmation, tracks the txid and confirmation height of a renegotiated funding
1292+
// transaction found in `Self::pending_funding`. Used to determine which commitment we should
1293+
// broadcast when necessary.
1294+
//
1295+
// "Alternative" in this context means a `FundingScope` other than the currently locked one
1296+
// found at `Self::funding`. We don't use the term "renegotiated", as the currently locked
1297+
// `FundingScope` could be one that was renegotiated.
1298+
alternative_funding_confirmed: Option<(Txid, u32)>,
12831299
}
12841300

12851301
// Macro helper to access holder commitment HTLC data (including both non-dust and dust) while
@@ -1555,6 +1571,7 @@ impl<Signer: EcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signer> {
15551571
(29, self.initial_counterparty_commitment_tx, option),
15561572
(31, self.funding.channel_parameters, required),
15571573
(32, self.pending_funding, optional_vec),
1574+
(34, self.alternative_funding_confirmed, option),
15581575
});
15591576

15601577
Ok(())
@@ -1780,6 +1797,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
17801797
// There are never any HTLCs in the initial commitment transaction
17811798
current_holder_htlc_data: CommitmentHTLCData::new(),
17821799
prev_holder_htlc_data: None,
1800+
1801+
alternative_funding_confirmed: None,
17831802
})
17841803
}
17851804

@@ -3815,6 +3834,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
38153834
for funding in self.pending_funding.drain(..) {
38163835
self.outputs_to_watch.remove(&funding.funding_txid());
38173836
}
3837+
if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.take() {
3838+
debug_assert_eq!(alternative_funding_txid, new_funding_txid);
3839+
}
38183840

38193841
Ok(())
38203842
}
@@ -4873,6 +4895,44 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
48734895
}
48744896
}
48754897

4898+
// A splice/dual-funded RBF transaction has confirmed. We can't promote the
4899+
// `FundingScope` scope until we see the
4900+
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] for it, but we track the txid
4901+
// so we know which holder commitment transaction we may need to broadcast.
4902+
if let Some(_alternative_funding) = self
4903+
.pending_funding
4904+
.iter()
4905+
.find(|funding| funding.funding_txid() == txid)
4906+
{
4907+
debug_assert!(self.alternative_funding_confirmed.is_none());
4908+
debug_assert!(
4909+
!self.onchain_events_awaiting_threshold_conf.iter()
4910+
.any(|e| matches!(e.event, OnchainEvent::AlternativeFundingConfirmation {}))
4911+
);
4912+
debug_assert!(self.funding_spend_confirmed.is_none());
4913+
debug_assert!(
4914+
!self.onchain_events_awaiting_threshold_conf.iter()
4915+
.any(|e| matches!(e.event, OnchainEvent::FundingSpendConfirmation { .. }))
4916+
);
4917+
4918+
self.alternative_funding_confirmed = Some((txid, height));
4919+
4920+
if self.no_further_updates_allowed() {
4921+
// We can no longer rely on
4922+
// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] to promote the
4923+
// scope; do so when the funding is no longer under reorg risk.
4924+
self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry {
4925+
txid,
4926+
transaction: Some((*tx).clone()),
4927+
height,
4928+
block_hash: Some(block_hash),
4929+
event: OnchainEvent::AlternativeFundingConfirmation {},
4930+
});
4931+
}
4932+
4933+
continue 'tx_iter;
4934+
}
4935+
48764936
if tx.input.len() == 1 {
48774937
// Assuming our keys were not leaked (in which case we're screwed no matter what),
48784938
// commitment transactions and HTLC transactions will all only ever have one input
@@ -5004,7 +5064,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
50045064
let unmatured_htlcs: Vec<_> = self.onchain_events_awaiting_threshold_conf
50055065
.iter()
50065066
.filter_map(|entry| match &entry.event {
5007-
OnchainEvent::HTLCUpdate { source, .. } => Some(source),
5067+
OnchainEvent::HTLCUpdate { source, .. } => Some(source.clone()),
50085068
_ => None,
50095069
})
50105070
.collect();
@@ -5019,7 +5079,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
50195079
#[cfg(debug_assertions)]
50205080
{
50215081
debug_assert!(
5022-
!unmatured_htlcs.contains(&&source),
5082+
!unmatured_htlcs.contains(&source),
50235083
"An unmature HTLC transaction conflicts with a maturing one; failed to \
50245084
call either transaction_unconfirmed for the conflicting transaction \
50255085
or block_disconnected for a block containing it.");
@@ -5066,6 +5126,16 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
50665126
self.funding_spend_confirmed = Some(entry.txid);
50675127
self.confirmed_commitment_tx_counterparty_output = commitment_tx_to_counterparty_output;
50685128
},
5129+
OnchainEvent::AlternativeFundingConfirmation {} => {
5130+
// An alternative funding transaction has irrevocably confirmed and we're no
5131+
// longer allowing monitor updates, so promote the `FundingScope` now.
5132+
debug_assert!(self.no_further_updates_allowed());
5133+
debug_assert_ne!(self.funding.funding_txid(), entry.txid);
5134+
if let Err(_) = self.promote_funding(entry.txid) {
5135+
debug_assert!(false);
5136+
log_error!(logger, "Missing scope for alternative funding confirmation with txid {}", entry.txid);
5137+
}
5138+
},
50695139
}
50705140
}
50715141

@@ -5178,6 +5248,13 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
51785248
//- maturing spendable output has transaction paying us has been disconnected
51795249
self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height < height);
51805250

5251+
// TODO: Replace with `take_if` once our MSRV is >= 1.80.
5252+
if let Some((_, conf_height)) = self.alternative_funding_confirmed.as_ref() {
5253+
if *conf_height == height {
5254+
self.alternative_funding_confirmed.take();
5255+
}
5256+
}
5257+
51815258
let bounded_fee_estimator = LowerBoundedFeeEstimator::new(fee_estimator);
51825259
let conf_target = self.closure_conf_target();
51835260
self.onchain_tx_handler.block_disconnected(
@@ -5217,6 +5294,13 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
52175294

52185295
debug_assert!(!self.onchain_events_awaiting_threshold_conf.iter().any(|ref entry| entry.txid == *txid));
52195296

5297+
// TODO: Replace with `take_if` once our MSRV is >= 1.80.
5298+
if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.as_ref() {
5299+
if alternative_funding_txid == txid {
5300+
self.alternative_funding_confirmed.take();
5301+
}
5302+
}
5303+
52205304
let conf_target = self.closure_conf_target();
52215305
self.onchain_tx_handler.transaction_unconfirmed(
52225306
txid, broadcaster, conf_target, &self.destination_script, fee_estimator, logger
@@ -5870,6 +5954,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
58705954
let mut first_negotiated_funding_txo = RequiredWrapper(None);
58715955
let mut channel_parameters = None;
58725956
let mut pending_funding = None;
5957+
let mut alternative_funding_confirmed = None;
58735958
read_tlv_fields!(reader, {
58745959
(1, funding_spend_confirmed, option),
58755960
(3, htlcs_resolved_on_chain, optional_vec),
@@ -5888,6 +5973,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
58885973
(29, initial_counterparty_commitment_tx, option),
58895974
(31, channel_parameters, (option: ReadableArgs, None)),
58905975
(32, pending_funding, optional_vec),
5976+
(34, alternative_funding_confirmed, option),
58915977
});
58925978
if let Some(payment_preimages_with_info) = payment_preimages_with_info {
58935979
if payment_preimages_with_info.len() != payment_preimages.len() {
@@ -6057,6 +6143,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
60576143

60586144
current_holder_htlc_data,
60596145
prev_holder_htlc_data,
6146+
6147+
alternative_funding_confirmed,
60606148
})))
60616149
}
60626150
}

0 commit comments

Comments
 (0)