From 2b846f3a04716e0c32ca3b352a1861a015d7c4ff Mon Sep 17 00:00:00 2001 From: jbesraa Date: Fri, 29 Mar 2024 08:56:23 +0300 Subject: [PATCH] Add `ChannelMonitor::is_stale` function Checks if the monitor is stale, meaning it has not been updated with a new block data in a substantial amount of time and thus has no outputs to watch, ie this monitor is not expected to be able to claim any funds on chain. The first time this method is called it will save the current known height of the monitor if all funds are claimed. Otherwise, if we yet to claim all funds, it will return false. If all funds are claimed, it will return true if the monitor has not been updated with new block data in a substantial amount of time referred as `threshold` and `balances_empty_height` is set. --- lightning/src/chain/channelmonitor.rs | 44 +++++++++++++++++++++++++++ lightning/src/ln/monitor_tests.rs | 13 ++++++++ 2 files changed, 57 insertions(+) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 4352076e94d..1337e8c3d30 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -934,6 +934,9 @@ pub(crate) struct ChannelMonitorImpl { /// Ordering of tuple data: (their_per_commitment_point, feerate_per_kw, to_broadcaster_sats, /// to_countersignatory_sats) initial_counterparty_commitment_info: Option<(PublicKey, u32, u64, u64)>, + + /// The first block height at which we had no remaining claimable balances. + blanaces_empty_height: Option, } /// Transaction outputs to watch for on-chain spends. @@ -1327,6 +1330,7 @@ impl ChannelMonitor { best_block, counterparty_node_id: Some(counterparty_node_id), initial_counterparty_commitment_info: None, + blanaces_empty_height: None, }) } @@ -1855,6 +1859,45 @@ impl ChannelMonitor { spendable_outputs } + /// Checks if the monitor is stale, meaning it has not been updated with a new block data in a + /// substantial amount of time and thus has no outputs to watch, ie + /// this monitor is not expected to be able to claim any funds on chain. + /// + /// The first time this method is called it will save the current known height of the monitor + /// if all funds are claimed. Otherwise, if we yet to claim all funds, it will return false. If all funds + /// are claimed, it will return true if the monitor has not been updated with new block data in + /// a substantial amount of time referred as `threshold` and `balances_empty_height` is set. + pub(crate) fn is_stale(&self) -> bool { + let threshold = 2016; // TODO: Should this be configurable? + let is_all_funds_claimed = self.get_claimable_balances().is_empty(); + let current_height = self.current_best_block().height; + let inner = self.inner.lock().unwrap(); + let blanaces_empty_height = inner.blanaces_empty_height; + if let Some(blanaces_empty_height) = blanaces_empty_height { + // This if can be ture at least in the second time this method is called. we check if + // the monitor has not been updated with new block data to watch new ouputs in a + // substantial amount(2016 blocks) of time meaning the channel is probably closed and + // this monitor is not expected to be able to claim any funds on chain. + debug_assert!(is_all_funds_claimed, "Trying to check if monitor is stale before all funds are claimed. Aborting."); + return current_height > blanaces_empty_height + threshold; + } else if is_all_funds_claimed { + // If we claimed all funds, but `balances_empty_height` is None, we set it to the + // current height and start counting from there. + // This is to to make sure we don't consider the monitor stale on the first call to + // `is_stale` after all funds are claimed. + let mut inner = inner; + inner.blanaces_empty_height = Some(current_height); + return false; + } + // We still have funds to claim. + return false; + } + + #[cfg(test)] + pub fn balances_empty_height(&self) -> Option { + self.inner.lock().unwrap().blanaces_empty_height + } + #[cfg(test)] pub fn get_counterparty_payment_script(&self) -> ScriptBuf { self.inner.lock().unwrap().counterparty_payment_script.clone() @@ -4721,6 +4764,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP best_block, counterparty_node_id, initial_counterparty_commitment_info, + blanaces_empty_height: None, }))) } } diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index d5f0dc153fc..49cd6f8f414 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -257,8 +257,21 @@ fn do_chanmon_claim_value_coop_close(anchors: bool) { spendable_outputs_b ); + // Test that we can stale the channel monitor after we have claimed the funds and a threshold + // of 2016 blocks has passed. check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 1000000); check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 1000000); + + assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty()); + assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty()); + + assert!(get_monitor!(nodes[0], chan_id).get_claimable_balances().is_empty()); + assert_eq!(get_monitor!(nodes[0], chan_id).is_stale(), false); + assert!(get_monitor!(nodes[0], chan_id).balances_empty_height().is_some()); + connect_blocks(&nodes[0], 2016); + assert_eq!(get_monitor!(nodes[0], chan_id).is_stale(), false); + connect_blocks(&nodes[0], 1); + assert_eq!(get_monitor!(nodes[0], chan_id).is_stale(), true); } #[test]