Skip to content

Commit

Permalink
Add ChannelMonitor::is_stale function
Browse files Browse the repository at this point in the history
  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.
  • Loading branch information
jbesraa committed Mar 29, 2024
1 parent 9cc0e98 commit 2b846f3
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 0 deletions.
44 changes: 44 additions & 0 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,9 @@ pub(crate) struct ChannelMonitorImpl<Signer: WriteableEcdsaChannelSigner> {
/// 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<u32>,
}

/// Transaction outputs to watch for on-chain spends.
Expand Down Expand Up @@ -1327,6 +1330,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
best_block,
counterparty_node_id: Some(counterparty_node_id),
initial_counterparty_commitment_info: None,
blanaces_empty_height: None,
})
}

Expand Down Expand Up @@ -1855,6 +1859,45 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
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<u32> {
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()
Expand Down Expand Up @@ -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,
})))
}
}
Expand Down
13 changes: 13 additions & 0 deletions lightning/src/ln/monitor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit 2b846f3

Please sign in to comment.