Skip to content

Commit 0dde7c1

Browse files
committed
Correct handling of reorg'd-out revoked counterparty transactions
Previously, while processing a confirmed revoked counterparty commitment transaction, we'd populate `OnchainEvent`s for live HTLCs with a `txid` source of the txid of the latest counterparty commitment transactions, not the confirmed revoked one. This meant that, if the user is using `transaction_unconfirmed` to notify us of reorg information, we'd end up not removing the entry if the revoked commitment transaction was reorg'd out. This would ultimately cause us to spuriously resolve the HTLC(s) as the chain advanced, even though we were doing so based on a now-reorged-out transaction. Luckily the fix is simple - set the correct txid in the `OnchainEventEntry`. We also take this opportunity to update logging in a few places with the txid of the transaction causing an event.
1 parent 1fd6c6f commit 0dde7c1

File tree

2 files changed

+90
-10
lines changed

2 files changed

+90
-10
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,7 +1650,8 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
16501650
/// as long as we examine both the current counterparty commitment transaction and, if it hasn't
16511651
/// been revoked yet, the previous one, we we will never "forget" to resolve an HTLC.
16521652
macro_rules! fail_unbroadcast_htlcs {
1653-
($self: expr, $commitment_tx_type: expr, $commitment_tx_conf_height: expr, $confirmed_htlcs_list: expr, $logger: expr) => { {
1653+
($self: expr, $commitment_tx_type: expr, $commitment_txid_confirmed: expr,
1654+
$commitment_tx_conf_height: expr, $confirmed_htlcs_list: expr, $logger: expr) => { {
16541655
macro_rules! check_htlc_fails {
16551656
($txid: expr, $commitment_tx: expr) => {
16561657
if let Some(ref latest_outpoints) = $self.counterparty_claimable_outpoints.get($txid) {
@@ -1684,7 +1685,7 @@ macro_rules! fail_unbroadcast_htlcs {
16841685
}
16851686
});
16861687
let entry = OnchainEventEntry {
1687-
txid: *$txid,
1688+
txid: $commitment_txid_confirmed,
16881689
height: $commitment_tx_conf_height,
16891690
event: OnchainEvent::HTLCUpdate {
16901691
source: (**source).clone(),
@@ -1693,8 +1694,9 @@ macro_rules! fail_unbroadcast_htlcs {
16931694
commitment_tx_output_idx: None,
16941695
},
16951696
};
1696-
log_trace!($logger, "Failing HTLC with payment_hash {} from {} counterparty commitment tx due to broadcast of {} commitment transaction, waiting for confirmation (at height {})",
1697-
log_bytes!(htlc.payment_hash.0), $commitment_tx, $commitment_tx_type, entry.confirmation_threshold());
1697+
log_trace!($logger, "Failing HTLC with payment_hash {} from {} counterparty commitment tx due to broadcast of {} commitment transaction {}, waiting for confirmation (at height {})",
1698+
log_bytes!(htlc.payment_hash.0), $commitment_tx, $commitment_tx_type,
1699+
$commitment_txid_confirmed, entry.confirmation_threshold());
16981700
$self.onchain_events_awaiting_threshold_conf.push(entry);
16991701
}
17001702
}
@@ -2091,7 +2093,8 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
20912093
}
20922094
self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number);
20932095

2094-
fail_unbroadcast_htlcs!(self, "revoked counterparty", height, [].iter().map(|a| *a), logger);
2096+
fail_unbroadcast_htlcs!(self, "revoked counterparty", commitment_txid, height,
2097+
[].iter().map(|a| *a), logger);
20952098
}
20962099
} else if let Some(per_commitment_data) = per_commitment_option {
20972100
// While this isn't useful yet, there is a potential race where if a counterparty
@@ -2107,7 +2110,8 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
21072110
self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number);
21082111

21092112
log_info!(logger, "Got broadcast of non-revoked counterparty commitment transaction {}", commitment_txid);
2110-
fail_unbroadcast_htlcs!(self, "counterparty", height, per_commitment_data.iter().map(|(a, b)| (a, b.as_ref().map(|b| b.as_ref()))), logger);
2113+
fail_unbroadcast_htlcs!(self, "counterparty", commitment_txid, height,
2114+
per_commitment_data.iter().map(|(a, b)| (a, b.as_ref().map(|b| b.as_ref()))), logger);
21112115

21122116
let htlc_claim_reqs = self.get_counterparty_htlc_output_claim_reqs(commitment_number, commitment_txid, Some(tx));
21132117
for req in htlc_claim_reqs {
@@ -2263,15 +2267,17 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
22632267
let res = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, height);
22642268
let mut to_watch = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, tx);
22652269
append_onchain_update!(res, to_watch);
2266-
fail_unbroadcast_htlcs!(self, "latest holder", height, self.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())), logger);
2270+
fail_unbroadcast_htlcs!(self, "latest holder", commitment_txid, height,
2271+
self.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())), logger);
22672272
} else if let &Some(ref holder_tx) = &self.prev_holder_signed_commitment_tx {
22682273
if holder_tx.txid == commitment_txid {
22692274
is_holder_tx = true;
22702275
log_info!(logger, "Got broadcast of previous holder commitment tx {}, searching for available HTLCs to claim", commitment_txid);
22712276
let res = self.get_broadcasted_holder_claims(holder_tx, height);
22722277
let mut to_watch = self.get_broadcasted_holder_watch_outputs(holder_tx, tx);
22732278
append_onchain_update!(res, to_watch);
2274-
fail_unbroadcast_htlcs!(self, "previous holder", height, holder_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())), logger);
2279+
fail_unbroadcast_htlcs!(self, "previous holder", commitment_txid, height,
2280+
holder_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())), logger);
22752281
}
22762282
}
22772283

@@ -2552,7 +2558,8 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
25522558
matured_htlcs.push(source.clone());
25532559
}
25542560

2555-
log_debug!(logger, "HTLC {} failure update has got enough confirmations to be passed upstream", log_bytes!(payment_hash.0));
2561+
log_debug!(logger, "HTLC {} failure update in {} has got enough confirmations to be passed upstream",
2562+
log_bytes!(payment_hash.0), entry.txid);
25562563
self.pending_monitor_events.push(MonitorEvent::HTLCEvent(HTLCUpdate {
25572564
payment_hash,
25582565
payment_preimage: None,
@@ -2631,7 +2638,10 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
26312638
F::Target: FeeEstimator,
26322639
L::Target: Logger,
26332640
{
2634-
self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.txid != *txid);
2641+
self.onchain_events_awaiting_threshold_conf.retain(|ref entry| if entry.txid == *txid {
2642+
log_info!(logger, "Removing onchain event with txid {}", txid);
2643+
false
2644+
} else { true });
26352645
self.onchain_tx_handler.transaction_unconfirmed(txid, broadcaster, fee_estimator, logger);
26362646
}
26372647

lightning/src/ln/reorg_tests.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,76 @@ fn test_onchain_htlc_timeout_delay_remote_commitment() {
184184
do_test_onchain_htlc_reorg(false, false);
185185
}
186186

187+
#[test]
188+
fn test_counterparty_revoked_reorg() {
189+
// Test what happens when a revoked counterparty transaction is broadcast but then reorg'd out
190+
// of the main chain. Specifically, HTLCs in the latest commitment transaction which are not
191+
// included in the revoked commitment transaction should not be considered failed, and should
192+
// still be claim-from-able after the reorg.
193+
let chanmon_cfgs = create_chanmon_cfgs(2);
194+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
195+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
196+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
197+
198+
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 500_000_000, InitFeatures::known(), InitFeatures::known());
199+
200+
// Get the initial commitment transaction for broadcast, before any HTLCs are added at all.
201+
let revoked_local_txn = get_local_commitment_txn!(nodes[0], chan.2);
202+
assert_eq!(revoked_local_txn.len(), 1);
203+
204+
// Now add two HTLCs in each direction, one dust and one not.
205+
route_payment(&nodes[0], &[&nodes[1]], 5_000_000);
206+
route_payment(&nodes[0], &[&nodes[1]], 5_000);
207+
let payment_preimage_3 = route_payment(&nodes[1], &[&nodes[0]], 4_000_000).0;
208+
let payment_hash_4 = route_payment(&nodes[1], &[&nodes[0]], 4_000).1;
209+
210+
nodes[0].node.claim_funds(payment_preimage_3);
211+
let _ = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
212+
check_added_monitors!(nodes[0], 1);
213+
214+
let mut unrevoked_local_txn = get_local_commitment_txn!(nodes[0], chan.2);
215+
assert_eq!(unrevoked_local_txn.len(), 3); // commitment + 2 HTLC txn
216+
// Sort the unrevoked transactions in reverse order, ie commitment tx, then HTLC 1 then HTLC 3
217+
unrevoked_local_txn.sort_unstable_by_key(|tx| 1_000_000 - tx.output.iter().map(|outp| outp.value).sum::<u64>());
218+
219+
// Now mine A's old commitment transaction, which should close the channel, but take no action
220+
// on any of the HTLCs, at least until we get six confirmations (which we won't get).
221+
mine_transaction(&nodes[1], &revoked_local_txn[0]);
222+
check_added_monitors!(nodes[1], 1);
223+
check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
224+
check_closed_broadcast!(nodes[1], true);
225+
226+
// Connect up to one block before the revoked transaction would be considered final, then do a
227+
// reorg that disconnects the full chain and goes up to the height at which the revoked
228+
// transaction would be final.
229+
let theoretical_conf_height = nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1;
230+
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
231+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
232+
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
233+
234+
disconnect_all_blocks(&nodes[1]);
235+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
236+
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
237+
238+
connect_blocks(&nodes[1], theoretical_conf_height);
239+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
240+
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
241+
242+
// Now connect A's latest commitment transaction instead and resolve the HTLCs
243+
mine_transaction(&nodes[1], &unrevoked_local_txn[0]);
244+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
245+
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
246+
247+
// Connect the HTLC claim transaction for HTLC 3
248+
mine_transaction(&nodes[1], &unrevoked_local_txn[2]);
249+
expect_payment_sent!(nodes[1], payment_preimage_3);
250+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
251+
252+
// Connect blocks to confirm the unrevoked commitment transaction
253+
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
254+
expect_payment_failed!(nodes[1], payment_hash_4, true);
255+
}
256+
187257
fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_unconfirmed: bool, connect_style: ConnectStyle) {
188258
// After creating a chan between nodes, we disconnect all blocks previously seen to force a
189259
// channel close on nodes[0] side. We also use this to provide very basic testing of logic

0 commit comments

Comments
 (0)