Skip to content

Commit 496eb45

Browse files
committed
Create SpendableOutputs events no matter the chain::Confirm order
We had a user who pointed out that we weren't creating `SpendableOutputs` events when we should have been after they called `ChannelMonitor::best_block_updated` with a block well after a CSV locktime and then called `ChannelMonitor::transactions_confirmed` with the transaction which we should have been spending (with a block height/hash a ways in the past). This was due to `ChannelMonitor::transactions_confirmed` only calling `ChannelMonitor::block_confirmed` with the height at which the transactions were confirmed, resulting in all checks being done against that, not the current height. Further, in the same scenario, we also would not fail-back and HTLC where the HTLC-Timeout transaction was confirmed more than ANTI_REORG_DELAY blocks ago. To address this, we use the best block height for confirmation threshold checks in `ChannelMonitor::block_confirmed` and pass both the confirmation and current heights through to `OnchainTx::update_claims_view`, using each as appropriate. Fixes #962.
1 parent 599c74c commit 496eb45

File tree

4 files changed

+130
-33
lines changed

4 files changed

+130
-33
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,7 +1331,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
13311331
macro_rules! claim_htlcs {
13321332
($commitment_number: expr, $txid: expr) => {
13331333
let htlc_claim_reqs = self.get_counterparty_htlc_output_claim_reqs($commitment_number, $txid, None);
1334-
self.onchain_tx_handler.update_claims_view(&Vec::new(), htlc_claim_reqs, self.best_block.height(), broadcaster, fee_estimator, logger);
1334+
self.onchain_tx_handler.update_claims_view(&Vec::new(), htlc_claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger);
13351335
}
13361336
}
13371337
if let Some(txid) = self.current_counterparty_commitment_txid {
@@ -1354,10 +1354,10 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
13541354
// holder commitment transactions.
13551355
if self.broadcasted_holder_revokable_script.is_some() {
13561356
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, 0);
1357-
self.onchain_tx_handler.update_claims_view(&Vec::new(), claim_reqs, self.best_block.height(), broadcaster, fee_estimator, logger);
1357+
self.onchain_tx_handler.update_claims_view(&Vec::new(), claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger);
13581358
if let Some(ref tx) = self.prev_holder_signed_commitment_tx {
13591359
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&tx, 0);
1360-
self.onchain_tx_handler.update_claims_view(&Vec::new(), claim_reqs, self.best_block.height(), broadcaster, fee_estimator, logger);
1360+
self.onchain_tx_handler.update_claims_view(&Vec::new(), claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger);
13611361
}
13621362
}
13631363
}
@@ -1926,7 +1926,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
19261926

19271927
if height > self.best_block.height() {
19281928
self.best_block = BestBlock::new(block_hash, height);
1929-
self.block_confirmed(height, vec![], vec![], vec![], broadcaster, fee_estimator, logger)
1929+
self.block_confirmed(height, vec![], vec![], vec![], &broadcaster, &fee_estimator, &logger)
19301930
} else if block_hash != self.best_block.block_hash() {
19311931
self.best_block = BestBlock::new(block_hash, height);
19321932
self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height <= height);
@@ -2008,33 +2008,42 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
20082008
self.best_block = BestBlock::new(block_hash, height);
20092009
}
20102010

2011-
self.block_confirmed(height, txn_matched, watch_outputs, claimable_outpoints, broadcaster, fee_estimator, logger)
2011+
self.block_confirmed(height, txn_matched, watch_outputs, claimable_outpoints, &broadcaster, &fee_estimator, &logger)
20122012
}
20132013

2014+
/// Update state for new block(s)/transaction(s) confirmed. Note that the caller must update
2015+
/// `self.best_block` before calling if a new best blockchain tip is available. More
2016+
/// concretely, `self.best_block` must never be at a lower height than `conf_height`, avoiding
2017+
/// complexity especially in `OnchainTx::update_claims_view`.
2018+
///
2019+
/// `conf_height` should be set to the height at which any new transaction(s)/block(s) were
2020+
/// confirmed at, even if it is not the current best height.
20142021
fn block_confirmed<B: Deref, F: Deref, L: Deref>(
20152022
&mut self,
2016-
height: u32,
2023+
conf_height: u32,
20172024
txn_matched: Vec<&Transaction>,
20182025
mut watch_outputs: Vec<TransactionOutputs>,
20192026
mut claimable_outpoints: Vec<PackageTemplate>,
2020-
broadcaster: B,
2021-
fee_estimator: F,
2022-
logger: L,
2027+
broadcaster: &B,
2028+
fee_estimator: &F,
2029+
logger: &L,
20232030
) -> Vec<TransactionOutputs>
20242031
where
20252032
B::Target: BroadcasterInterface,
20262033
F::Target: FeeEstimator,
20272034
L::Target: Logger,
20282035
{
2029-
let should_broadcast = self.would_broadcast_at_height(height, &logger);
2036+
debug_assert!(self.best_block.height() >= conf_height);
2037+
2038+
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);
20302039
if should_broadcast {
20312040
let funding_outp = HolderFundingOutput::build(self.funding_redeemscript.clone());
2032-
let commitment_package = PackageTemplate::build_package(self.funding_info.0.txid.clone(), self.funding_info.0.index as u32, PackageSolvingData::HolderFundingOutput(funding_outp), height, false, height);
2041+
let commitment_package = PackageTemplate::build_package(self.funding_info.0.txid.clone(), self.funding_info.0.index as u32, PackageSolvingData::HolderFundingOutput(funding_outp), self.best_block.height(), false, self.best_block.height());
20332042
claimable_outpoints.push(commitment_package);
20342043
self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));
20352044
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
20362045
self.holder_tx_signed = true;
2037-
let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, height);
2046+
let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, self.best_block.height());
20382047
let new_outputs = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, &commitment_tx);
20392048
if !new_outputs.is_empty() {
20402049
watch_outputs.push((self.current_holder_commitment_tx.txid.clone(), new_outputs));
@@ -2047,7 +2056,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
20472056
self.onchain_events_awaiting_threshold_conf.drain(..).collect::<Vec<_>>();
20482057
let mut onchain_events_reaching_threshold_conf = Vec::new();
20492058
for entry in onchain_events_awaiting_threshold_conf {
2050-
if entry.has_reached_confirmation_threshold(height) {
2059+
if entry.has_reached_confirmation_threshold(self.best_block.height()) {
20512060
onchain_events_reaching_threshold_conf.push(entry);
20522061
} else {
20532062
self.onchain_events_awaiting_threshold_conf.push(entry);
@@ -2102,7 +2111,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
21022111
}
21032112
}
21042113

2105-
self.onchain_tx_handler.update_claims_view(&txn_matched, claimable_outpoints, height, &&*broadcaster, &&*fee_estimator, &&*logger);
2114+
self.onchain_tx_handler.update_claims_view(&txn_matched, claimable_outpoints, conf_height, self.best_block.height(), broadcaster, fee_estimator, logger);
21062115

21072116
// Determine new outputs to watch by comparing against previously known outputs to watch,
21082117
// updating the latter in the process.

lightning/src/chain/onchaintx.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -343,15 +343,15 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
343343
/// (CSV or CLTV following cases). In case of high-fee spikes, claim tx may stuck in the mempool, so you need to bump its feerate quickly using Replace-By-Fee or Child-Pay-For-Parent.
344344
/// Panics if there are signing errors, because signing operations in reaction to on-chain events
345345
/// are not expected to fail, and if they do, we may lose funds.
346-
fn generate_claim_tx<F: Deref, L: Deref>(&mut self, height: u32, cached_request: &PackageTemplate, fee_estimator: &F, logger: &L) -> Option<(Option<u32>, u64, Transaction)>
346+
fn generate_claim_tx<F: Deref, L: Deref>(&mut self, cur_height: u32, cached_request: &PackageTemplate, fee_estimator: &F, logger: &L) -> Option<(Option<u32>, u64, Transaction)>
347347
where F::Target: FeeEstimator,
348348
L::Target: Logger,
349349
{
350350
if cached_request.outpoints().len() == 0 { return None } // But don't prune pending claiming request yet, we may have to resurrect HTLCs
351351

352352
// Compute new height timer to decide when we need to regenerate a new bumped version of the claim tx (if we
353353
// didn't receive confirmation of it before, or not enough reorg-safe depth on top of it).
354-
let new_timer = Some(cached_request.get_height_timer(height));
354+
let new_timer = Some(cached_request.get_height_timer(cur_height));
355355
if cached_request.is_malleable() {
356356
let predicted_weight = cached_request.package_weight(&self.destination_script);
357357
if let Some((output_value, new_feerate)) = cached_request.compute_package_output(predicted_weight, fee_estimator, logger) {
@@ -377,12 +377,15 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
377377
/// for this channel, provide new relevant on-chain transactions and/or new claim requests.
378378
/// Formerly this was named `block_connected`, but it is now also used for claiming an HTLC output
379379
/// if we receive a preimage after force-close.
380-
pub(crate) fn update_claims_view<B: Deref, F: Deref, L: Deref>(&mut self, txn_matched: &[&Transaction], requests: Vec<PackageTemplate>, height: u32, broadcaster: &B, fee_estimator: &F, logger: &L)
380+
/// `conf_height` represents the height at which the transactions in `txn_matched` were
381+
/// confirmed. This does not need to equal the current blockchain tip height, which should be
382+
/// provided via `cur_height`, however it must never be higher than `cur_height`.
383+
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: &F, logger: &L)
381384
where B::Target: BroadcasterInterface,
382385
F::Target: FeeEstimator,
383386
L::Target: Logger,
384387
{
385-
log_debug!(logger, "Updating claims view at height {} with {} matched transactions and {} claim requests", height, txn_matched.len(), requests.len());
388+
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());
386389
let mut preprocessed_requests = Vec::with_capacity(requests.len());
387390
let mut aggregated_request = None;
388391

@@ -401,17 +404,17 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
401404
continue;
402405
}
403406

404-
if req.package_timelock() > height + 1 {
405-
log_info!(logger, "Delaying claim of package until its timelock at {} (current height {}), the following outpoints are spent:", req.package_timelock(), height);
407+
if req.package_timelock() > cur_height + 1 {
408+
log_info!(logger, "Delaying claim of package until its timelock at {} (current height {}), the following outpoints are spent:", req.package_timelock(), cur_height);
406409
for outpoint in req.outpoints() {
407410
log_info!(logger, " Outpoint {}", outpoint);
408411
}
409412
self.locktimed_packages.entry(req.package_timelock()).or_insert(Vec::new()).push(req);
410413
continue;
411414
}
412415

413-
log_trace!(logger, "Test if outpoint can be aggregated with expiration {} against {}", req.timelock(), height + CLTV_SHARED_CLAIM_BUFFER);
414-
if req.timelock() <= height + CLTV_SHARED_CLAIM_BUFFER || !req.aggregable() {
416+
log_trace!(logger, "Test if outpoint can be aggregated with expiration {} against {}", req.timelock(), cur_height + CLTV_SHARED_CLAIM_BUFFER);
417+
if req.timelock() <= cur_height + CLTV_SHARED_CLAIM_BUFFER || !req.aggregable() {
415418
// Don't aggregate if outpoint package timelock is soon or marked as non-aggregable
416419
preprocessed_requests.push(req);
417420
} else if aggregated_request.is_none() {
@@ -425,8 +428,8 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
425428
preprocessed_requests.push(req);
426429
}
427430

428-
// Claim everything up to and including height + 1
429-
let remaining_locked_packages = self.locktimed_packages.split_off(&(height + 2));
431+
// Claim everything up to and including cur_height + 1
432+
let remaining_locked_packages = self.locktimed_packages.split_off(&(cur_height + 2));
430433
for (pop_height, mut entry) in self.locktimed_packages.iter_mut() {
431434
log_trace!(logger, "Restoring delayed claim of package(s) at their timelock at {}.", pop_height);
432435
preprocessed_requests.append(&mut entry);
@@ -436,13 +439,13 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
436439
// Generate claim transactions and track them to bump if necessary at
437440
// height timer expiration (i.e in how many blocks we're going to take action).
438441
for mut req in preprocessed_requests {
439-
if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &req, &*fee_estimator, &*logger) {
442+
if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(cur_height, &req, &*fee_estimator, &*logger) {
440443
req.set_timer(new_timer);
441444
req.set_feerate(new_feerate);
442445
let txid = tx.txid();
443446
for k in req.outpoints() {
444447
log_info!(logger, "Registering claiming request for {}:{}", k.txid, k.vout);
445-
self.claimable_outpoints.insert(k.clone(), (txid, height));
448+
self.claimable_outpoints.insert(k.clone(), (txid, conf_height));
446449
}
447450
self.pending_claim_requests.insert(txid, req);
448451
log_info!(logger, "Broadcasting onchain {}", log_tx!(tx));
@@ -476,7 +479,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
476479
() => {
477480
let entry = OnchainEventEntry {
478481
txid: tx.txid(),
479-
height,
482+
height: conf_height,
480483
event: OnchainEvent::Claim { claim_request: first_claim_txid_height.0.clone() }
481484
};
482485
if !self.onchain_events_awaiting_threshold_conf.contains(&entry) {
@@ -516,7 +519,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
516519
for package in claimed_outputs_material.drain(..) {
517520
let entry = OnchainEventEntry {
518521
txid: tx.txid(),
519-
height,
522+
height: conf_height,
520523
event: OnchainEvent::ContentiousOutpoint { package },
521524
};
522525
if !self.onchain_events_awaiting_threshold_conf.contains(&entry) {
@@ -529,7 +532,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
529532
let onchain_events_awaiting_threshold_conf =
530533
self.onchain_events_awaiting_threshold_conf.drain(..).collect::<Vec<_>>();
531534
for entry in onchain_events_awaiting_threshold_conf {
532-
if entry.has_reached_confirmation_threshold(height) {
535+
if entry.has_reached_confirmation_threshold(cur_height) {
533536
match entry.event {
534537
OnchainEvent::Claim { claim_request } => {
535538
// We may remove a whole set of claim outpoints here, as these one may have
@@ -555,7 +558,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
555558
// Check if any pending claim request must be rescheduled
556559
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
557560
if let Some(h) = request.timer() {
558-
if height >= h {
561+
if cur_height >= h {
559562
bump_candidates.insert(*first_claim_txid, (*request).clone());
560563
}
561564
}
@@ -564,7 +567,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
564567
// Build, bump and rebroadcast tx accordingly
565568
log_trace!(logger, "Bumping {} candidates", bump_candidates.len());
566569
for (first_claim_txid, request) in bump_candidates.iter() {
567-
if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &request, &*fee_estimator, &*logger) {
570+
if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(cur_height, &request, &*fee_estimator, &*logger) {
568571
log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx));
569572
broadcaster.broadcast_transaction(&bump_tx);
570573
if let Some(request) = self.pending_claim_requests.get_mut(first_claim_txid) {

lightning/src/ln/functional_test_utils.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
221221
pub fn best_block_info(&self) -> (BlockHash, u32) {
222222
self.blocks.lock().unwrap().last().map(|(a, b)| (a.block_hash(), *b)).unwrap()
223223
}
224+
pub fn get_block_header(&self, height: u32) -> BlockHeader {
225+
self.blocks.lock().unwrap()[height as usize].0
226+
}
224227
}
225228

226229
impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> {

0 commit comments

Comments
 (0)