Skip to content

Commit 44d1dfa

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 4356809 commit 44d1dfa

File tree

2 files changed

+95
-10
lines changed

2 files changed

+95
-10
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,7 +1659,8 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
16591659
/// as long as we examine both the current counterparty commitment transaction and, if it hasn't
16601660
/// been revoked yet, the previous one, we we will never "forget" to resolve an HTLC.
16611661
macro_rules! fail_unbroadcast_htlcs {
1662-
($self: expr, $commitment_tx_type: expr, $commitment_tx_conf_height: expr, $confirmed_htlcs_list: expr, $logger: expr) => { {
1662+
($self: expr, $commitment_tx_type: expr, $commitment_txid_confirmed: expr,
1663+
$commitment_tx_conf_height: expr, $confirmed_htlcs_list: expr, $logger: expr) => { {
16631664
macro_rules! check_htlc_fails {
16641665
($txid: expr, $commitment_tx: expr) => {
16651666
if let Some(ref latest_outpoints) = $self.counterparty_claimable_outpoints.get($txid) {
@@ -1693,7 +1694,7 @@ macro_rules! fail_unbroadcast_htlcs {
16931694
}
16941695
});
16951696
let entry = OnchainEventEntry {
1696-
txid: *$txid,
1697+
txid: $commitment_txid_confirmed,
16971698
height: $commitment_tx_conf_height,
16981699
event: OnchainEvent::HTLCUpdate {
16991700
source: (**source).clone(),
@@ -1702,8 +1703,9 @@ macro_rules! fail_unbroadcast_htlcs {
17021703
commitment_tx_output_idx: None,
17031704
},
17041705
};
1705-
log_trace!($logger, "Failing HTLC with payment_hash {} from {} counterparty commitment tx due to broadcast of {} commitment transaction, waiting for confirmation (at height {})",
1706-
log_bytes!(htlc.payment_hash.0), $commitment_tx, $commitment_tx_type, entry.confirmation_threshold());
1706+
log_trace!($logger, "Failing HTLC with payment_hash {} from {} counterparty commitment tx due to broadcast of {} commitment transaction {}, waiting for confirmation (at height {})",
1707+
log_bytes!(htlc.payment_hash.0), $commitment_tx, $commitment_tx_type,
1708+
$commitment_txid_confirmed, entry.confirmation_threshold());
17071709
$self.onchain_events_awaiting_threshold_conf.push(entry);
17081710
}
17091711
}
@@ -2100,7 +2102,8 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
21002102
}
21012103
self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number);
21022104

2103-
fail_unbroadcast_htlcs!(self, "revoked counterparty", height, [].iter().map(|a| *a), logger);
2105+
fail_unbroadcast_htlcs!(self, "revoked counterparty", commitment_txid, height,
2106+
[].iter().map(|a| *a), logger);
21042107
}
21052108
} else if let Some(per_commitment_data) = per_commitment_option {
21062109
// While this isn't useful yet, there is a potential race where if a counterparty
@@ -2116,7 +2119,10 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
21162119
self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number);
21172120

21182121
log_info!(logger, "Got broadcast of non-revoked counterparty commitment transaction {}", commitment_txid);
2119-
fail_unbroadcast_htlcs!(self, "counterparty", height, per_commitment_data.iter().map(|(a, b)| (a, b.as_ref().map(|b| b.as_ref()))), logger);
2122+
fail_unbroadcast_htlcs!(self, "counterparty", commitment_txid, height,
2123+
per_commitment_data.iter().map(|(htlc, htlc_source)|
2124+
(htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref()))
2125+
), logger);
21202126

21212127
let htlc_claim_reqs = self.get_counterparty_htlc_output_claim_reqs(commitment_number, commitment_txid, Some(tx));
21222128
for req in htlc_claim_reqs {
@@ -2272,15 +2278,19 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
22722278
let res = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, height);
22732279
let mut to_watch = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, tx);
22742280
append_onchain_update!(res, to_watch);
2275-
fail_unbroadcast_htlcs!(self, "latest holder", height, self.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())), logger);
2281+
fail_unbroadcast_htlcs!(self, "latest holder", commitment_txid, height,
2282+
self.current_holder_commitment_tx.htlc_outputs.iter()
2283+
.map(|(htlc, _, htlc_source)| (htlc, htlc_source.as_ref())), logger);
22762284
} else if let &Some(ref holder_tx) = &self.prev_holder_signed_commitment_tx {
22772285
if holder_tx.txid == commitment_txid {
22782286
is_holder_tx = true;
22792287
log_info!(logger, "Got broadcast of previous holder commitment tx {}, searching for available HTLCs to claim", commitment_txid);
22802288
let res = self.get_broadcasted_holder_claims(holder_tx, height);
22812289
let mut to_watch = self.get_broadcasted_holder_watch_outputs(holder_tx, tx);
22822290
append_onchain_update!(res, to_watch);
2283-
fail_unbroadcast_htlcs!(self, "previous holder", height, holder_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())), logger);
2291+
fail_unbroadcast_htlcs!(self, "previous holder", commitment_txid, height,
2292+
holder_tx.htlc_outputs.iter().map(|(htlc, _, htlc_source)| (htlc, htlc_source.as_ref())),
2293+
logger);
22842294
}
22852295
}
22862296

@@ -2561,7 +2571,8 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
25612571
matured_htlcs.push(source.clone());
25622572
}
25632573

2564-
log_debug!(logger, "HTLC {} failure update has got enough confirmations to be passed upstream", log_bytes!(payment_hash.0));
2574+
log_debug!(logger, "HTLC {} failure update in {} has got enough confirmations to be passed upstream",
2575+
log_bytes!(payment_hash.0), entry.txid);
25652576
self.pending_monitor_events.push(MonitorEvent::HTLCEvent(HTLCUpdate {
25662577
payment_hash,
25672578
payment_preimage: None,
@@ -2640,7 +2651,10 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
26402651
F::Target: FeeEstimator,
26412652
L::Target: Logger,
26422653
{
2643-
self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.txid != *txid);
2654+
self.onchain_events_awaiting_threshold_conf.retain(|ref entry| if entry.txid == *txid {
2655+
log_info!(logger, "Removing onchain event with txid {}", txid);
2656+
false
2657+
} else { true });
26442658
self.onchain_tx_handler.transaction_unconfirmed(txid, broadcaster, fee_estimator, logger);
26452659
}
26462660

lightning/src/ln/reorg_tests.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,77 @@ fn test_onchain_htlc_timeout_delay_remote_commitment() {
185185
do_test_onchain_htlc_reorg(false, false);
186186
}
187187

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

0 commit comments

Comments
 (0)