Skip to content

Commit 720bce6

Browse files
joostjagerclaude
andcommitted
chanmon_consistency: sync stale monitors without lowering node height
The settlement resync (3e576df9a) reset node_height to the minimum of the node and monitor heights so sync_with_chain_state would re-deliver missed blocks to stale monitors. However, this also fed those low-height blocks to the ChannelManager, which interpreted them as the funding transaction being un-confirmed (get_funding_tx_confirmations returns 0 when height < funding_tx_confirmation_height). This triggered spurious force-closes, leading to multiple force-close cycles with different commitment transactions and stale htlcs_resolved_on_chain entries that caused an assertion failure in get_htlc_balance. Fix by syncing stale monitors directly through their ChainMonitor methods for the missed block range, without touching node_height or the ChannelManager. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9de0830 commit 720bce6

1 file changed

Lines changed: 26 additions & 10 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2875,21 +2875,37 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
28752875
nodes[1].signer_unblocked(None);
28762876
nodes[2].signer_unblocked(None);
28772877

2878-
// After a node reload with an older monitor, node_height may be
2879-
// ahead of the monitor's best_block. Reset to the minimum so
2880-
// sync_with_chain_state re-delivers missed blocks during settlement.
2881-
for (node, monitor, node_height) in [
2882-
(&nodes[0], &monitor_a, &mut node_height_a),
2883-
(&nodes[1], &monitor_b, &mut node_height_b),
2884-
(&nodes[2], &monitor_c, &mut node_height_c),
2878+
// After a node reload with an older monitor, the monitor may
2879+
// be behind node_height. Sync only the monitors (not the
2880+
// ChannelManager) for the missed blocks to avoid the
2881+
// ChannelManager interpreting lower heights as a reorg.
2882+
for (monitor, node_height) in [
2883+
(&monitor_a, &node_height_a),
2884+
(&monitor_b, &node_height_b),
2885+
(&monitor_c, &node_height_c),
28852886
] {
2886-
let mut min = std::cmp::min(*node_height, node.current_best_block().height);
2887+
let mut min_monitor_height = *node_height;
28872888
for chan_id in monitor.chain_monitor.list_monitors() {
28882889
if let Ok(mon) = monitor.chain_monitor.get_monitor(chan_id) {
2889-
min = std::cmp::min(min, mon.current_best_block().height);
2890+
min_monitor_height = std::cmp::min(
2891+
min_monitor_height,
2892+
mon.current_best_block().height,
2893+
);
2894+
}
2895+
}
2896+
let mut h = min_monitor_height;
2897+
while h < *node_height {
2898+
h += 1;
2899+
let (header, txn) = chain_state.block_at(h);
2900+
let txdata: Vec<_> =
2901+
txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect();
2902+
if !txdata.is_empty() {
2903+
monitor
2904+
.chain_monitor
2905+
.transactions_confirmed(header, &txdata, h);
28902906
}
2907+
monitor.chain_monitor.best_block_updated(header, h);
28912908
}
2892-
*node_height = min;
28932909
}
28942910

28952911
macro_rules! process_all_events {

0 commit comments

Comments
 (0)