Skip to content

Commit 9a1649c

Browse files
jbesraaTheBlueMatt
authored andcommitted
Add ChannelMonitor::is_fully_resolved function
Checks if the monitor is fully resolved. Resolved monitor is one that has claimed all of its ouputs and balacnes. This function returns true only if `get_claimable_balances` has been empty for at least 2016 blocks.
1 parent 1d2a27d commit 9a1649c

File tree

1 file changed

+54
-0
lines changed

1 file changed

+54
-0
lines changed

lightning/src/chain/channelmonitor.rs

+54
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,9 @@ pub(crate) struct ChannelMonitorImpl<Signer: WriteableEcdsaChannelSigner> {
935935
/// Ordering of tuple data: (their_per_commitment_point, feerate_per_kw, to_broadcaster_sats,
936936
/// to_countersignatory_sats)
937937
initial_counterparty_commitment_info: Option<(PublicKey, u32, u64, u64)>,
938+
939+
/// The first block height at which we had no remaining claimable balances.
940+
balances_empty_height: Option<u32>,
938941
}
939942

940943
/// Transaction outputs to watch for on-chain spends.
@@ -1145,6 +1148,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signe
11451148
(15, self.counterparty_fulfilled_htlcs, required),
11461149
(17, self.initial_counterparty_commitment_info, option),
11471150
(19, self.channel_id, required),
1151+
(21, self.balances_empty_height, option),
11481152
});
11491153

11501154
Ok(())
@@ -1328,6 +1332,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
13281332
best_block,
13291333
counterparty_node_id: Some(counterparty_node_id),
13301334
initial_counterparty_commitment_info: None,
1335+
balances_empty_height: None,
13311336
})
13321337
}
13331338

@@ -1856,6 +1861,52 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
18561861
spendable_outputs
18571862
}
18581863

1864+
/// Checks if the monitor is fully resolved. Resolved monitor is one that has claimed all of
1865+
/// its outputs and balances (i.e. [`Self::get_claimable_balances`] returns an empty set).
1866+
///
1867+
/// This function returns true only if [`Self::get_claimable_balances`] has been empty for at least
1868+
/// 2016 blocks as an additional protection against any bugs resulting in spuriously empty balance sets.
1869+
pub fn is_fully_resolved<L: Logger>(&self, logger: &L) -> bool {
1870+
let mut is_all_funds_claimed = self.get_claimable_balances().is_empty();
1871+
let current_height = self.current_best_block().height;
1872+
let mut inner = self.inner.lock().unwrap();
1873+
1874+
if is_all_funds_claimed {
1875+
if !inner.funding_spend_seen {
1876+
debug_assert!(false, "We should see funding spend by the time a monitor clears out");
1877+
is_all_funds_claimed = false;
1878+
}
1879+
}
1880+
1881+
match (inner.balances_empty_height, is_all_funds_claimed) {
1882+
(Some(balances_empty_height), true) => {
1883+
// Claimed all funds, check if reached the blocks threshold.
1884+
const BLOCKS_THRESHOLD: u32 = 4032; // ~four weeks
1885+
return current_height >= balances_empty_height + BLOCKS_THRESHOLD;
1886+
},
1887+
(Some(_), false) => {
1888+
// previously assumed we claimed all funds, but we have new funds to claim.
1889+
// Should not happen in practice.
1890+
debug_assert!(false, "Thought we were done claiming funds, but claimable_balances now has entries");
1891+
log_error!(logger,
1892+
"WARNING: LDK thought it was done claiming all the available funds in the ChannelMonitor for channel {}, but later decided it had more to claim. This is potentially an important bug in LDK, please report it at https://github.com/lightningdevkit/rust-lightning/issues/new",
1893+
inner.get_funding_txo().0);
1894+
inner.balances_empty_height = None;
1895+
false
1896+
},
1897+
(None, true) => {
1898+
// Claimed all funds but `balances_empty_height` is None. It is set to the
1899+
// current block height.
1900+
inner.balances_empty_height = Some(current_height);
1901+
false
1902+
},
1903+
(None, false) => {
1904+
// Have funds to claim.
1905+
false
1906+
},
1907+
}
1908+
}
1909+
18591910
#[cfg(test)]
18601911
pub fn get_counterparty_payment_script(&self) -> ScriptBuf {
18611912
self.inner.lock().unwrap().counterparty_payment_script.clone()
@@ -4632,6 +4683,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
46324683
let mut spendable_txids_confirmed = Some(Vec::new());
46334684
let mut counterparty_fulfilled_htlcs = Some(new_hash_map());
46344685
let mut initial_counterparty_commitment_info = None;
4686+
let mut balances_empty_height = None;
46354687
let mut channel_id = None;
46364688
read_tlv_fields!(reader, {
46374689
(1, funding_spend_confirmed, option),
@@ -4644,6 +4696,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
46444696
(15, counterparty_fulfilled_htlcs, option),
46454697
(17, initial_counterparty_commitment_info, option),
46464698
(19, channel_id, option),
4699+
(21, balances_empty_height, option),
46474700
});
46484701

46494702
// `HolderForceClosedWithInfo` replaced `HolderForceClosed` in v0.0.122. If we have both
@@ -4722,6 +4775,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
47224775
best_block,
47234776
counterparty_node_id,
47244777
initial_counterparty_commitment_info,
4778+
balances_empty_height,
47254779
})))
47264780
}
47274781
}

0 commit comments

Comments
 (0)