Skip to content

Commit fbc86cb

Browse files
authored
Merge pull request #2623 from wpaulino/htlc-claim-receive-preimage-after-close
Claim HTLCs with preimage from currently confirmed commitment
2 parents 5e871a7 + d82e6ba commit fbc86cb

File tree

2 files changed

+180
-12
lines changed

2 files changed

+180
-12
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,6 +2503,18 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
25032503
{
25042504
self.payment_preimages.insert(payment_hash.clone(), payment_preimage.clone());
25052505

2506+
let confirmed_spend_txid = self.funding_spend_confirmed.or_else(|| {
2507+
self.onchain_events_awaiting_threshold_conf.iter().find_map(|event| match event.event {
2508+
OnchainEvent::FundingSpendConfirmation { .. } => Some(event.txid),
2509+
_ => None,
2510+
})
2511+
});
2512+
let confirmed_spend_txid = if let Some(txid) = confirmed_spend_txid {
2513+
txid
2514+
} else {
2515+
return;
2516+
};
2517+
25062518
// If the channel is force closed, try to claim the output from this preimage.
25072519
// First check if a counterparty commitment transaction has been broadcasted:
25082520
macro_rules! claim_htlcs {
@@ -2512,14 +2524,24 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
25122524
}
25132525
}
25142526
if let Some(txid) = self.current_counterparty_commitment_txid {
2515-
if let Some(commitment_number) = self.counterparty_commitment_txn_on_chain.get(&txid) {
2516-
claim_htlcs!(*commitment_number, txid);
2527+
if txid == confirmed_spend_txid {
2528+
if let Some(commitment_number) = self.counterparty_commitment_txn_on_chain.get(&txid) {
2529+
claim_htlcs!(*commitment_number, txid);
2530+
} else {
2531+
debug_assert!(false);
2532+
log_error!(logger, "Detected counterparty commitment tx on-chain without tracking commitment number");
2533+
}
25172534
return;
25182535
}
25192536
}
25202537
if let Some(txid) = self.prev_counterparty_commitment_txid {
2521-
if let Some(commitment_number) = self.counterparty_commitment_txn_on_chain.get(&txid) {
2522-
claim_htlcs!(*commitment_number, txid);
2538+
if txid == confirmed_spend_txid {
2539+
if let Some(commitment_number) = self.counterparty_commitment_txn_on_chain.get(&txid) {
2540+
claim_htlcs!(*commitment_number, txid);
2541+
} else {
2542+
debug_assert!(false);
2543+
log_error!(logger, "Detected counterparty commitment tx on-chain without tracking commitment number");
2544+
}
25232545
return;
25242546
}
25252547
}
@@ -2530,13 +2552,22 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
25302552
// *we* sign a holder commitment transaction, not when e.g. a watchtower broadcasts one of our
25312553
// holder commitment transactions.
25322554
if self.broadcasted_holder_revokable_script.is_some() {
2533-
// Assume that the broadcasted commitment transaction confirmed in the current best
2534-
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC
2535-
// transactions.
2536-
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, self.best_block.height());
2537-
self.onchain_tx_handler.update_claims_view_from_requests(claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger);
2538-
if let Some(ref tx) = self.prev_holder_signed_commitment_tx {
2539-
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&tx, self.best_block.height());
2555+
let holder_commitment_tx = if self.current_holder_commitment_tx.txid == confirmed_spend_txid {
2556+
Some(&self.current_holder_commitment_tx)
2557+
} else if let Some(prev_holder_commitment_tx) = &self.prev_holder_signed_commitment_tx {
2558+
if prev_holder_commitment_tx.txid == confirmed_spend_txid {
2559+
Some(prev_holder_commitment_tx)
2560+
} else {
2561+
None
2562+
}
2563+
} else {
2564+
None
2565+
};
2566+
if let Some(holder_commitment_tx) = holder_commitment_tx {
2567+
// Assume that the broadcasted commitment transaction confirmed in the current best
2568+
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC
2569+
// transactions.
2570+
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&holder_commitment_tx, self.best_block.height());
25402571
self.onchain_tx_handler.update_claims_view_from_requests(claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger);
25412572
}
25422573
}

lightning/src/ln/reorg_tests.rs

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99

1010
//! Further functional tests which test blockchain reorganizations.
1111
12+
use crate::chain::chaininterface::LowerBoundedFeeEstimator;
1213
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS};
1314
use crate::chain::transaction::OutPoint;
1415
use crate::chain::Confirm;
15-
use crate::events::{Event, MessageSendEventsProvider, ClosureReason, HTLCDestination};
16+
use crate::events::{Event, MessageSendEventsProvider, ClosureReason, HTLCDestination, MessageSendEvent};
1617
use crate::ln::msgs::{ChannelMessageHandler, Init};
1718
use crate::util::test_utils;
1819
use crate::util::ser::Writeable;
@@ -617,3 +618,139 @@ fn test_to_remote_after_local_detection() {
617618
do_test_to_remote_after_local_detection(ConnectStyle::TransactionsFirstReorgsOnlyTip);
618619
do_test_to_remote_after_local_detection(ConnectStyle::FullBlockViaListen);
619620
}
621+
622+
#[test]
623+
fn test_htlc_preimage_claim_holder_commitment_after_counterparty_commitment_reorg() {
624+
// We detect a counterparty commitment confirm onchain, followed by a reorg and a confirmation
625+
// of a holder commitment. Then, if we learn of the preimage for an HTLC in both commitments,
626+
// test that we only claim the currently confirmed commitment.
627+
let chanmon_cfgs = create_chanmon_cfgs(2);
628+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
629+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None, None]);
630+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
631+
632+
let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
633+
634+
// Route an HTLC which we will claim onchain with the preimage.
635+
let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
636+
637+
// Force close with the latest counterparty commitment, confirm it, and reorg it with the latest
638+
// holder commitment.
639+
nodes[0].node.force_close_broadcasting_latest_txn(&chan_id, &nodes[1].node.get_our_node_id()).unwrap();
640+
check_closed_broadcast(&nodes[0], 1, true);
641+
check_added_monitors(&nodes[0], 1);
642+
check_closed_event(&nodes[0], 1, ClosureReason::HolderForceClosed, false, &[nodes[1].node.get_our_node_id()], 100000);
643+
644+
nodes[1].node.force_close_broadcasting_latest_txn(&chan_id, &nodes[0].node.get_our_node_id()).unwrap();
645+
check_closed_broadcast(&nodes[1], 1, true);
646+
check_added_monitors(&nodes[1], 1);
647+
check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed, false, &[nodes[0].node.get_our_node_id()], 100000);
648+
649+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
650+
assert_eq!(txn.len(), 1);
651+
let commitment_tx_a = txn.pop().unwrap();
652+
check_spends!(commitment_tx_a, funding_tx);
653+
654+
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
655+
assert_eq!(txn.len(), 1);
656+
let commitment_tx_b = txn.pop().unwrap();
657+
check_spends!(commitment_tx_b, funding_tx);
658+
659+
mine_transaction(&nodes[0], &commitment_tx_a);
660+
mine_transaction(&nodes[1], &commitment_tx_a);
661+
662+
disconnect_blocks(&nodes[0], 1);
663+
disconnect_blocks(&nodes[1], 1);
664+
665+
mine_transaction(&nodes[0], &commitment_tx_b);
666+
mine_transaction(&nodes[1], &commitment_tx_b);
667+
668+
// Provide the preimage now, such that we only claim from the holder commitment (since it's
669+
// currently confirmed) and not the counterparty's.
670+
get_monitor!(nodes[1], chan_id).provide_payment_preimage(
671+
&payment_hash, &payment_preimage, &nodes[1].tx_broadcaster,
672+
&LowerBoundedFeeEstimator(nodes[1].fee_estimator), &nodes[1].logger
673+
);
674+
675+
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
676+
assert_eq!(txn.len(), 1);
677+
let htlc_success_tx = txn.pop().unwrap();
678+
check_spends!(htlc_success_tx, commitment_tx_b);
679+
}
680+
681+
#[test]
682+
fn test_htlc_preimage_claim_prev_counterparty_commitment_after_current_counterparty_commitment_reorg() {
683+
// We detect a counterparty commitment confirm onchain, followed by a reorg and a
684+
// confirmation of the previous (still unrevoked) counterparty commitment. Then, if we learn
685+
// of the preimage for an HTLC in both commitments, test that we only claim the currently
686+
// confirmed commitment.
687+
let chanmon_cfgs = create_chanmon_cfgs(2);
688+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
689+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None, None]);
690+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
691+
692+
let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
693+
694+
// Route an HTLC which we will claim onchain with the preimage.
695+
let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
696+
697+
// Obtain the current commitment, which will become the previous after a fee update.
698+
let prev_commitment_a = &get_local_commitment_txn!(nodes[0], chan_id)[0];
699+
700+
*nodes[0].fee_estimator.sat_per_kw.lock().unwrap() *= 4;
701+
nodes[0].node.timer_tick_occurred();
702+
check_added_monitors(&nodes[0], 1);
703+
let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
704+
assert_eq!(msg_events.len(), 1);
705+
let (update_fee, commit_sig) = if let MessageSendEvent::UpdateHTLCs { node_id, mut updates } = msg_events.pop().unwrap() {
706+
assert_eq!(node_id, nodes[1].node.get_our_node_id());
707+
(updates.update_fee.take().unwrap(), updates.commitment_signed)
708+
} else {
709+
panic!("Unexpected message send event");
710+
};
711+
712+
// Handle the fee update on the other side, but don't send the last RAA such that the previous
713+
// commitment is still valid (unrevoked).
714+
nodes[1].node().handle_update_fee(&nodes[0].node.get_our_node_id(), &update_fee);
715+
let _last_revoke_and_ack = commitment_signed_dance!(nodes[1], nodes[0], commit_sig, false, true, false, true);
716+
717+
// Force close with the latest commitment, confirm it, and reorg it with the previous commitment.
718+
nodes[0].node.force_close_broadcasting_latest_txn(&chan_id, &nodes[1].node.get_our_node_id()).unwrap();
719+
check_closed_broadcast(&nodes[0], 1, true);
720+
check_added_monitors(&nodes[0], 1);
721+
check_closed_event(&nodes[0], 1, ClosureReason::HolderForceClosed, false, &[nodes[1].node.get_our_node_id()], 100000);
722+
723+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
724+
assert_eq!(txn.len(), 1);
725+
let current_commitment_a = txn.pop().unwrap();
726+
assert_ne!(current_commitment_a.txid(), prev_commitment_a.txid());
727+
check_spends!(current_commitment_a, funding_tx);
728+
729+
mine_transaction(&nodes[0], &current_commitment_a);
730+
mine_transaction(&nodes[1], &current_commitment_a);
731+
732+
check_closed_broadcast(&nodes[1], 1, true);
733+
check_added_monitors(&nodes[1], 1);
734+
check_closed_event(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false, &[nodes[0].node.get_our_node_id()], 100000);
735+
736+
disconnect_blocks(&nodes[0], 1);
737+
disconnect_blocks(&nodes[1], 1);
738+
739+
mine_transaction(&nodes[0], &prev_commitment_a);
740+
mine_transaction(&nodes[1], &prev_commitment_a);
741+
742+
// Provide the preimage now, such that we only claim from the previous commitment (since it's
743+
// currently confirmed) and not the latest.
744+
get_monitor!(nodes[1], chan_id).provide_payment_preimage(
745+
&payment_hash, &payment_preimage, &nodes[1].tx_broadcaster,
746+
&LowerBoundedFeeEstimator(nodes[1].fee_estimator), &nodes[1].logger
747+
);
748+
749+
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
750+
assert_eq!(txn.len(), 1);
751+
let htlc_preimage_tx = txn.pop().unwrap();
752+
check_spends!(htlc_preimage_tx, prev_commitment_a);
753+
// Make sure it was indeed a preimage claim and not a revocation claim since the previous
754+
// commitment (still unrevoked) is the currently confirmed closing transaction.
755+
assert_eq!(htlc_preimage_tx.input[0].witness.second_to_last().unwrap(), &payment_preimage.0[..]);
756+
}

0 commit comments

Comments
 (0)