Make `ChannelMonitor` round-trip tests more robust
What changed, and why it matters
This commit only changes test code and the visibility of an equality-checking feature. It makes a test comparison ignore a field that gets rewritten when data is loaded back from disk, and it hides the equality trait from public use because the comparison is fragile. There is no runtime security fix here.
No action required for security. Reviewers may want to confirm the referenced 0.1 field-repair fix is itself sound, but this commit is purely test/ API-hardening.
Security signals we found
No security fix in runtime code
Equality comparison hidden from public API
Test-only workaround for serialization round-trip behavior
References a prior field-repair fix in PackageTemplate deserialization
Evidence from the diff
The commit modifies ChannelMonitor and PackageTemplate equality logic. It removes #[derive(PartialEq)] from PackageTemplate and writes a manual PartialEq that, under #[cfg(test)], treats counterparty_spendable_height == 0 as equal to the original value for revoked received HTLCs whose cltv_expiry is non-zero. This mirrors a reload-time normalization already present in the PackageTemplate Readable implementation. It also gates ChannelMonitor’s PartialEq implementation behind the _test_utils feature or test cfg, making it non-public. The stated goal is to stop round-trip serialization tests from failing after a 0.1 field-repair fix.
Changed components
lightning/src/chain/channelmonitor.rslightning/src/chain/package.rsInspect captured patch +61 / −5
diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index d46cfaa..74a836c 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -77,7 +77,7 @@ use crate::util::ser::{
use crate::prelude::*;
use crate::io::{self, Error};
-use crate::sync::{LockTestExt, Mutex};
+use crate::sync::Mutex;
use core::ops::Deref;
use core::{cmp, mem};
@@ -1376,18 +1376,30 @@ macro_rules! holder_commitment_htlcs {
/// Transaction outputs to watch for on-chain spends.
pub type TransactionOutputs = (Txid, Vec<(u32, TxOut)>);
+// Because we have weird workarounds for `ChannelMonitor` equality checks in `OnchainTxHandler` and
+// `PackageTemplate` the equality implementation isn't really fit for public consumption. Instead,
+// we only expose it during tests.
+#[cfg(any(feature = "_test_utils", test))]
impl<Signer: EcdsaChannelSigner> PartialEq for ChannelMonitor<Signer>
where
Signer: PartialEq,
{
- #[rustfmt::skip]
fn eq(&self, other: &Self) -> bool {
+ use crate::sync::LockTestExt;
// We need some kind of total lockorder. Absent a better idea, we sort by position in
// memory and take locks in that order (assuming that we can't move within memory while a
// lock is held).
let ord = ((self as *const _) as usize) < ((other as *const _) as usize);
- let a = if ord { self.inner.unsafe_well_ordered_double_lock_self() } else { other.inner.unsafe_well_ordered_double_lock_self() };
- let b = if ord { other.inner.unsafe_well_ordered_double_lock_self() } else { self.inner.unsafe_well_ordered_double_lock_self() };
+ let a = if ord {
+ self.inner.unsafe_well_ordered_double_lock_self()
+ } else {
+ other.inner.unsafe_well_ordered_double_lock_self()
+ };
+ let b = if ord {
+ other.inner.unsafe_well_ordered_double_lock_self()
+ } else {
+ self.inner.unsafe_well_ordered_double_lock_self()
+ };
a.eq(&b)
}
}
diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs
index 3de690e..bdc5774 100644
--- a/lightning/src/chain/package.rs
+++ b/lightning/src/chain/package.rs
@@ -1093,7 +1093,7 @@ enum PackageMalleability {
///
/// As packages are time-sensitive, we fee-bump and rebroadcast them at scheduled intervals.
/// Failing to confirm a package translate as a loss of funds for the user.
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, Eq)]
pub struct PackageTemplate {
// List of onchain outputs and solving data to generate satisfying witnesses.
inputs: Vec<(BitcoinOutPoint, PackageSolvingData)>,
@@ -1122,6 +1122,50 @@ pub struct PackageTemplate {
height_timer: u32,
}
+impl PartialEq for PackageTemplate {
+ fn eq(&self, o: &Self) -> bool {
+ if self.inputs != o.inputs
+ || self.malleability != o.malleability
+ || self.feerate_previous != o.feerate_previous
+ || self.height_timer != o.height_timer
+ {
+ return false;
+ }
+ #[cfg(test)]
+ {
+ // In some cases we may reset `counterparty_spendable_height` to zero on reload, which
+ // can cause our test assertions that ChannelMonitors round-trip exactly to trip. Here
+ // we allow exactly the same case as we tweak in the `PackageTemplate` `Readable`
+ // implementation.
+ if self.counterparty_spendable_height == 0 {
+ for (_, input) in self.inputs.iter() {
+ if let PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput {
+ htlc, ..
+ }) = input
+ {
+ if !htlc.offered && htlc.cltv_expiry != 0 {
+ return true;
+ }
+ }
+ }
+ }
+ if o.counterparty_spendable_height == 0 {
+ for (_, input) in o.inputs.iter() {
+ if let PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput {
+ htlc, ..
+ }) = input
+ {
+ if !htlc.offered && htlc.cltv_expiry != 0 {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ self.counterparty_spendable_height == o.counterparty_spendable_height
+ }
+}
+
impl PackageTemplate {
#[rustfmt::skip]
pub(crate) fn can_merge_with(&self, other: &PackageTemplate, cur_height: u32) -> bool {
Why this scored 16/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.