Skip to content

Commit 1a80eac

Browse files
committed
Track block hash, return via get_relevant_txids
Previously, `Confirm::get_relevant_txids()` only returned a list of transactions that have to be monitored for reorganization out of the chain. This interface however required double bookkeeping: while we internally keep track of the best block, height, etc, it would also require the user to keep track which transaction was previously confirmed in which block and to take actions based on any change, e.g, to reconfirm them when the block would be reorged-out and the transactions had been reconfirmed in another block. Here, we track the confirmation block hash internally and return it via `Confirm::get_relevant_txids()` to the user, which alleviates the requirement for double bookkeeping: the user can now simply check whether the given transaction is still confirmed and in the given block, and take action if not.
1 parent e55e0d5 commit 1a80eac

File tree

7 files changed

+162
-87
lines changed

7 files changed

+162
-87
lines changed

lightning/src/chain/chainmonitor.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
//! servicing [`ChannelMonitor`] updates from the client.
2525
2626
use bitcoin::blockdata::block::BlockHeader;
27-
use bitcoin::hash_types::Txid;
27+
use bitcoin::hash_types::{Txid, BlockHash};
2828

2929
use crate::chain;
3030
use crate::chain::{ChannelMonitorUpdateStatus, Filter, WatchedOutput};
@@ -544,7 +544,7 @@ where
544544
});
545545
}
546546

547-
fn get_relevant_txids(&self) -> Vec<Txid> {
547+
fn get_relevant_txids(&self) -> Vec<(Txid, Option<BlockHash>)> {
548548
let mut txids = Vec::new();
549549
let monitor_states = self.monitors.read().unwrap();
550550
for monitor_state in monitor_states.values() {

lightning/src/chain/channelmonitor.rs

Lines changed: 45 additions & 32 deletions
Large diffs are not rendered by default.

lightning/src/chain/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ pub trait Confirm {
171171
/// if they become available at the same time.
172172
fn best_block_updated(&self, header: &BlockHeader, height: u32);
173173

174-
/// Returns transactions that should be monitored for reorganization out of the chain.
174+
/// Returns transactions that should be monitored for reorganization out of the chain along
175+
/// with the hash of the block as part of which had been previously confirmed.
175176
///
176177
/// Will include any transactions passed to [`transactions_confirmed`] that have insufficient
177178
/// confirmations to be safe from a chain reorganization. Will not include any transactions
@@ -184,7 +185,7 @@ pub trait Confirm {
184185
///
185186
/// [`transactions_confirmed`]: Self::transactions_confirmed
186187
/// [`transaction_unconfirmed`]: Self::transaction_unconfirmed
187-
fn get_relevant_txids(&self) -> Vec<Txid>;
188+
fn get_relevant_txids(&self) -> Vec<(Txid, Option<BlockHash>)>;
188189
}
189190

190191
/// An enum representing the status of a channel monitor update persistence.

lightning/src/chain/onchaintx.rs

Lines changed: 81 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use bitcoin::blockdata::transaction::Transaction;
1616
use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint;
1717
use bitcoin::blockdata::script::Script;
1818

19-
use bitcoin::hash_types::Txid;
19+
use bitcoin::hash_types::{Txid, BlockHash};
2020

2121
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
2222
use bitcoin::secp256k1;
@@ -58,6 +58,7 @@ const MAX_ALLOC_SIZE: usize = 64*1024;
5858
struct OnchainEventEntry {
5959
txid: Txid,
6060
height: u32,
61+
block_hash: Option<BlockHash>, // Added as optional, will be filled in for any entry generated on 0.0.113 or after
6162
event: OnchainEvent,
6263
}
6364

@@ -92,6 +93,7 @@ impl Writeable for OnchainEventEntry {
9293
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
9394
write_tlv_fields!(writer, {
9495
(0, self.txid, required),
96+
(1, self.block_hash, option),
9597
(2, self.height, required),
9698
(4, self.event, required),
9799
});
@@ -103,14 +105,16 @@ impl MaybeReadable for OnchainEventEntry {
103105
fn read<R: io::Read>(reader: &mut R) -> Result<Option<Self>, DecodeError> {
104106
let mut txid = Txid::all_zeros();
105107
let mut height = 0;
108+
let mut block_hash = None;
106109
let mut event = None;
107110
read_tlv_fields!(reader, {
108111
(0, txid, required),
112+
(1, block_hash, option),
109113
(2, height, required),
110114
(4, event, ignorable),
111115
});
112116
if let Some(ev) = event {
113-
Ok(Some(Self { txid, height, event: ev }))
117+
Ok(Some(Self { txid, height, block_hash, event: ev }))
114118
} else {
115119
Ok(None)
116120
}
@@ -543,17 +547,22 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
543547

544548
/// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link
545549
/// for this channel, provide new relevant on-chain transactions and/or new claim requests.
546-
/// Formerly this was named `block_connected`, but it is now also used for claiming an HTLC output
547-
/// if we receive a preimage after force-close.
548-
/// `conf_height` represents the height at which the transactions in `txn_matched` were
549-
/// confirmed. This does not need to equal the current blockchain tip height, which should be
550-
/// provided via `cur_height`, however it must never be higher than `cur_height`.
551-
pub(crate) fn update_claims_view<B: Deref, F: Deref, L: Deref>(&mut self, txn_matched: &[&Transaction], requests: Vec<PackageTemplate>, conf_height: u32, cur_height: u32, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L)
552-
where B::Target: BroadcasterInterface,
553-
F::Target: FeeEstimator,
554-
L::Target: Logger,
550+
/// Together with `update_claims_view_from_matched_txn` this used to be named
551+
/// `block_connected`, but it is now also used for claiming an HTLC output if we receive a
552+
/// preimage after force-close.
553+
///
554+
/// `conf_height` represents the minimal height at which the request could get confirmed. This
555+
/// does not need to equal the current blockchain tip height, which should be provided via
556+
/// `cur_height`, however it must never be higher than `cur_height`.
557+
pub(crate) fn update_claims_view_from_requests<B: Deref, F: Deref, L: Deref>(
558+
&mut self, requests: Vec<PackageTemplate>, conf_height: u32, cur_height: u32,
559+
broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
560+
) where
561+
B::Target: BroadcasterInterface,
562+
F::Target: FeeEstimator,
563+
L::Target: Logger,
555564
{
556-
log_debug!(logger, "Updating claims view at height {} with {} matched transactions in block {} and {} claim requests", cur_height, txn_matched.len(), conf_height, requests.len());
565+
log_debug!(logger, "Updating claims view at height {} with {} claim requests", cur_height, requests.len());
557566
let mut preprocessed_requests = Vec::with_capacity(requests.len());
558567
let mut aggregated_request = None;
559568

@@ -634,6 +643,26 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
634643
}
635644
}
636645

646+
self.process_onchain_events_awaiting_threshold_conf(cur_height, logger);
647+
}
648+
649+
/// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link
650+
/// for this channel, provide new relevant on-chain transactions and/or new claim requests.
651+
/// Together with `update_claims_view_from_requests` this used to be named `block_connected`,
652+
/// but it is now also used for claiming an HTLC output if we receive a preimage after force-close.
653+
///
654+
/// `conf_height` represents the height at which the transactions in `txn_matched` were
655+
/// confirmed. This does not need to equal the current blockchain tip height, which should be
656+
/// provided via `cur_height`, however it must never be higher than `cur_height`.
657+
pub(crate) fn update_claims_view_from_matched_txn<B: Deref, F: Deref, L: Deref>(
658+
&mut self, txn_matched: &[&Transaction], conf_height: u32, conf_hash: BlockHash,
659+
cur_height: u32, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
660+
) where
661+
B::Target: BroadcasterInterface,
662+
F::Target: FeeEstimator,
663+
L::Target: Logger,
664+
{
665+
log_debug!(logger, "Updating claims view at height {} with {} matched transactions in block {}", cur_height, txn_matched.len(), conf_height);
637666
let mut bump_candidates = HashMap::new();
638667
for tx in txn_matched {
639668
// Scan all input to verify is one of the outpoint spent is of interest for us
@@ -661,6 +690,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
661690
let entry = OnchainEventEntry {
662691
txid: tx.txid(),
663692
height: conf_height,
693+
block_hash: Some(conf_hash),
664694
event: OnchainEvent::Claim { claim_request: first_claim_txid_height.0.clone() }
665695
};
666696
if !self.onchain_events_awaiting_threshold_conf.contains(&entry) {
@@ -701,6 +731,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
701731
let entry = OnchainEventEntry {
702732
txid: tx.txid(),
703733
height: conf_height,
734+
block_hash: Some(conf_hash),
704735
event: OnchainEvent::ContentiousOutpoint { package },
705736
};
706737
if !self.onchain_events_awaiting_threshold_conf.contains(&entry) {
@@ -709,34 +740,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
709740
}
710741
}
711742

712-
// After security delay, either our claim tx got enough confs or outpoint is definetely out of reach
713-
let onchain_events_awaiting_threshold_conf =
714-
self.onchain_events_awaiting_threshold_conf.drain(..).collect::<Vec<_>>();
715-
for entry in onchain_events_awaiting_threshold_conf {
716-
if entry.has_reached_confirmation_threshold(cur_height) {
717-
match entry.event {
718-
OnchainEvent::Claim { claim_request } => {
719-
// We may remove a whole set of claim outpoints here, as these one may have
720-
// been aggregated in a single tx and claimed so atomically
721-
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
722-
for outpoint in request.outpoints() {
723-
log_debug!(logger, "Removing claim tracking for {} due to maturation of claim tx {}.", outpoint, claim_request);
724-
self.claimable_outpoints.remove(&outpoint);
725-
#[cfg(anchors)]
726-
self.pending_claim_events.remove(&claim_request);
727-
}
728-
}
729-
},
730-
OnchainEvent::ContentiousOutpoint { package } => {
731-
log_debug!(logger, "Removing claim tracking due to maturation of claim tx for outpoints:");
732-
log_debug!(logger, " {:?}", package.outpoints());
733-
self.claimable_outpoints.remove(&package.outpoints()[0]);
734-
}
735-
}
736-
} else {
737-
self.onchain_events_awaiting_threshold_conf.push(entry);
738-
}
739-
}
743+
self.process_onchain_events_awaiting_threshold_conf(cur_height, logger);
740744

741745
// Check if any pending claim request must be rescheduled
742746
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
@@ -770,6 +774,39 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
770774
}
771775
}
772776

777+
fn process_onchain_events_awaiting_threshold_conf<L: Deref>(&mut self, cur_height: u32, logger: &L)
778+
where L::Target: Logger,
779+
{
780+
// After security delay, either our claim tx got enough confs or outpoint is definetely out of reach
781+
let onchain_events_awaiting_threshold_conf =
782+
self.onchain_events_awaiting_threshold_conf.drain(..).collect::<Vec<_>>();
783+
for entry in onchain_events_awaiting_threshold_conf {
784+
if entry.has_reached_confirmation_threshold(cur_height) {
785+
match entry.event {
786+
OnchainEvent::Claim { claim_request } => {
787+
// We may remove a whole set of claim outpoints here, as these one may have
788+
// been aggregated in a single tx and claimed so atomically
789+
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
790+
for outpoint in request.outpoints() {
791+
log_debug!(logger, "Removing claim tracking for {} due to maturation of claim tx {}.", outpoint, claim_request);
792+
self.claimable_outpoints.remove(&outpoint);
793+
#[cfg(anchors)]
794+
self.pending_claim_events.remove(&claim_request);
795+
}
796+
}
797+
},
798+
OnchainEvent::ContentiousOutpoint { package } => {
799+
log_debug!(logger, "Removing claim tracking due to maturation of claim tx for outpoints:");
800+
log_debug!(logger, " {:?}", package.outpoints());
801+
self.claimable_outpoints.remove(&package.outpoints()[0]);
802+
}
803+
}
804+
} else {
805+
self.onchain_events_awaiting_threshold_conf.push(entry);
806+
}
807+
}
808+
}
809+
773810
pub(crate) fn transaction_unconfirmed<B: Deref, F: Deref, L: Deref>(
774811
&mut self,
775812
txid: &Txid,
@@ -860,12 +897,12 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
860897
self.claimable_outpoints.get(outpoint).is_some()
861898
}
862899

863-
pub(crate) fn get_relevant_txids(&self) -> Vec<Txid> {
864-
let mut txids: Vec<Txid> = self.onchain_events_awaiting_threshold_conf
900+
pub(crate) fn get_relevant_txids(&self) -> Vec<(Txid, Option<BlockHash>)> {
901+
let mut txids: Vec<(Txid, Option<BlockHash>)> = self.onchain_events_awaiting_threshold_conf
865902
.iter()
866-
.map(|entry| entry.txid)
903+
.map(|entry| (entry.txid, entry.block_hash))
867904
.collect();
868-
txids.sort_unstable();
905+
txids.sort_unstable_by_key(|(txid, _)| *txid);
869906
txids.dedup();
870907
txids
871908
}

lightning/src/ln/channel.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4520,6 +4520,11 @@ impl<Signer: Sign> Channel<Signer> {
45204520
self.channel_transaction_parameters.funding_outpoint
45214521
}
45224522

4523+
/// Returns the block hash in which our funding transaction was confirmed.
4524+
pub fn get_funding_tx_confirmed_in(&self) -> Option<BlockHash> {
4525+
self.funding_tx_confirmed_in
4526+
}
4527+
45234528
fn get_holder_selected_contest_delay(&self) -> u16 {
45244529
self.channel_transaction_parameters.holder_selected_contest_delay
45254530
}

lightning/src/ln/channelmanager.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5853,12 +5853,12 @@ where
58535853
});
58545854
}
58555855

5856-
fn get_relevant_txids(&self) -> Vec<Txid> {
5856+
fn get_relevant_txids(&self) -> Vec<(Txid, Option<BlockHash>)> {
58575857
let channel_state = self.channel_state.lock().unwrap();
58585858
let mut res = Vec::with_capacity(channel_state.short_to_chan_info.len());
58595859
for chan in channel_state.by_id.values() {
5860-
if let Some(funding_txo) = chan.get_funding_txo() {
5861-
res.push(funding_txo.txid);
5860+
if let (Some(funding_txo), block_hash) = (chan.get_funding_txo(), chan.get_funding_tx_confirmed_in()) {
5861+
res.push((funding_txo.txid, block_hash));
58625862
}
58635863
}
58645864
res

lightning/src/ln/reorg_tests.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_
271271
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
272272
*nodes[0].connect_style.borrow_mut() = connect_style;
273273

274+
let chan_conf_height = core::cmp::max(nodes[0].best_block_info().1 + 1, nodes[1].best_block_info().1 + 1);
274275
let chan = create_announced_chan_between_nodes(&nodes, 0, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features());
275276

276277
let channel_state = nodes[0].node.channel_state.lock().unwrap();
@@ -281,15 +282,24 @@ fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_
281282
if !reorg_after_reload {
282283
if use_funding_unconfirmed {
283284
let relevant_txids = nodes[0].node.get_relevant_txids();
284-
assert_eq!(&relevant_txids[..], &[chan.3.txid()]);
285-
nodes[0].node.transaction_unconfirmed(&relevant_txids[0]);
285+
assert_eq!(relevant_txids.len(), 1);
286+
let block_hash_opt = relevant_txids[0].1;
287+
let expected_hash = nodes[0].get_block_header(chan_conf_height).block_hash();
288+
assert_eq!(block_hash_opt, Some(expected_hash));
289+
let txid = relevant_txids[0].0;
290+
assert_eq!(txid, chan.3.txid());
291+
nodes[0].node.transaction_unconfirmed(&txid);
286292
} else if connect_style == ConnectStyle::FullBlockViaListen {
287293
disconnect_blocks(&nodes[0], CHAN_CONFIRM_DEPTH - 1);
288294
assert_eq!(nodes[0].node.list_usable_channels().len(), 1);
289295
disconnect_blocks(&nodes[0], 1);
290296
} else {
291297
disconnect_all_blocks(&nodes[0]);
292298
}
299+
300+
let relevant_txids = nodes[0].node.get_relevant_txids();
301+
assert_eq!(relevant_txids.len(), 0);
302+
293303
handle_announce_close_broadcast_events(&nodes, 0, 1, true, "Channel closed because of an exception: Funding transaction was un-confirmed. Locked at 6 confs, now have 0 confs.");
294304
check_added_monitors!(nodes[1], 1);
295305
{
@@ -350,15 +360,24 @@ fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_
350360
if reorg_after_reload {
351361
if use_funding_unconfirmed {
352362
let relevant_txids = nodes[0].node.get_relevant_txids();
353-
assert_eq!(&relevant_txids[..], &[chan.3.txid()]);
354-
nodes[0].node.transaction_unconfirmed(&relevant_txids[0]);
363+
assert_eq!(relevant_txids.len(), 1);
364+
let block_hash_opt = relevant_txids[0].1;
365+
let expected_hash = nodes[0].get_block_header(chan_conf_height).block_hash();
366+
assert_eq!(block_hash_opt, Some(expected_hash));
367+
let txid = relevant_txids[0].0;
368+
assert_eq!(txid, chan.3.txid());
369+
nodes[0].node.transaction_unconfirmed(&txid);
355370
} else if connect_style == ConnectStyle::FullBlockViaListen {
356371
disconnect_blocks(&nodes[0], CHAN_CONFIRM_DEPTH - 1);
357372
assert_eq!(nodes[0].node.list_channels().len(), 1);
358373
disconnect_blocks(&nodes[0], 1);
359374
} else {
360375
disconnect_all_blocks(&nodes[0]);
361376
}
377+
378+
let relevant_txids = nodes[0].node.get_relevant_txids();
379+
assert_eq!(relevant_txids.len(), 0);
380+
362381
handle_announce_close_broadcast_events(&nodes, 0, 1, true, "Channel closed because of an exception: Funding transaction was un-confirmed. Locked at 6 confs, now have 0 confs.");
363382
check_added_monitors!(nodes[1], 1);
364383
{

0 commit comments

Comments
 (0)