Skip to content

Commit 2b846f3

Browse files
committed
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.
1 parent 9cc0e98 commit 2b846f3

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,9 @@ pub(crate) struct ChannelMonitorImpl<Signer: WriteableEcdsaChannelSigner> {
934934
/// Ordering of tuple data: (their_per_commitment_point, feerate_per_kw, to_broadcaster_sats,
935935
/// to_countersignatory_sats)
936936
initial_counterparty_commitment_info: Option<(PublicKey, u32, u64, u64)>,
937+
938+
/// The first block height at which we had no remaining claimable balances.
939+
blanaces_empty_height: Option<u32>,
937940
}
938941

939942
/// Transaction outputs to watch for on-chain spends.
@@ -1327,6 +1330,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
13271330
best_block,
13281331
counterparty_node_id: Some(counterparty_node_id),
13291332
initial_counterparty_commitment_info: None,
1333+
blanaces_empty_height: None,
13301334
})
13311335
}
13321336

@@ -1855,6 +1859,45 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
18551859
spendable_outputs
18561860
}
18571861

1862+
/// Checks if the monitor is stale, meaning it has not been updated with a new block data in a
1863+
/// substantial amount of time and thus has no outputs to watch, ie
1864+
/// this monitor is not expected to be able to claim any funds on chain.
1865+
///
1866+
/// The first time this method is called it will save the current known height of the monitor
1867+
/// if all funds are claimed. Otherwise, if we yet to claim all funds, it will return false. If all funds
1868+
/// are claimed, it will return true if the monitor has not been updated with new block data in
1869+
/// a substantial amount of time referred as `threshold` and `balances_empty_height` is set.
1870+
pub(crate) fn is_stale(&self) -> bool {
1871+
let threshold = 2016; // TODO: Should this be configurable?
1872+
let is_all_funds_claimed = self.get_claimable_balances().is_empty();
1873+
let current_height = self.current_best_block().height;
1874+
let inner = self.inner.lock().unwrap();
1875+
let blanaces_empty_height = inner.blanaces_empty_height;
1876+
if let Some(blanaces_empty_height) = blanaces_empty_height {
1877+
// This if can be ture at least in the second time this method is called. we check if
1878+
// the monitor has not been updated with new block data to watch new ouputs in a
1879+
// substantial amount(2016 blocks) of time meaning the channel is probably closed and
1880+
// this monitor is not expected to be able to claim any funds on chain.
1881+
debug_assert!(is_all_funds_claimed, "Trying to check if monitor is stale before all funds are claimed. Aborting.");
1882+
return current_height > blanaces_empty_height + threshold;
1883+
} else if is_all_funds_claimed {
1884+
// If we claimed all funds, but `balances_empty_height` is None, we set it to the
1885+
// current height and start counting from there.
1886+
// This is to to make sure we don't consider the monitor stale on the first call to
1887+
// `is_stale` after all funds are claimed.
1888+
let mut inner = inner;
1889+
inner.blanaces_empty_height = Some(current_height);
1890+
return false;
1891+
}
1892+
// We still have funds to claim.
1893+
return false;
1894+
}
1895+
1896+
#[cfg(test)]
1897+
pub fn balances_empty_height(&self) -> Option<u32> {
1898+
self.inner.lock().unwrap().blanaces_empty_height
1899+
}
1900+
18581901
#[cfg(test)]
18591902
pub fn get_counterparty_payment_script(&self) -> ScriptBuf {
18601903
self.inner.lock().unwrap().counterparty_payment_script.clone()
@@ -4721,6 +4764,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
47214764
best_block,
47224765
counterparty_node_id,
47234766
initial_counterparty_commitment_info,
4767+
blanaces_empty_height: None,
47244768
})))
47254769
}
47264770
}

lightning/src/ln/monitor_tests.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,21 @@ fn do_chanmon_claim_value_coop_close(anchors: bool) {
257257
spendable_outputs_b
258258
);
259259

260+
// Test that we can stale the channel monitor after we have claimed the funds and a threshold
261+
// of 2016 blocks has passed.
260262
check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 1000000);
261263
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 1000000);
264+
265+
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
266+
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
267+
268+
assert!(get_monitor!(nodes[0], chan_id).get_claimable_balances().is_empty());
269+
assert_eq!(get_monitor!(nodes[0], chan_id).is_stale(), false);
270+
assert!(get_monitor!(nodes[0], chan_id).balances_empty_height().is_some());
271+
connect_blocks(&nodes[0], 2016);
272+
assert_eq!(get_monitor!(nodes[0], chan_id).is_stale(), false);
273+
connect_blocks(&nodes[0], 1);
274+
assert_eq!(get_monitor!(nodes[0], chan_id).is_stale(), true);
262275
}
263276

264277
#[test]

0 commit comments

Comments
 (0)