Implement TestNode to test MerkleNode
What changed, and why it matters
This commit only adds new test code. It introduces a fake test-only Merkle node type so the project's mutation-testing tool can exercise a default trait method that real node types override on common CPU platforms. There is no change to production code, no bug fix, and no security-relevant behavior change.
No security action needed. Treat as routine test-coverage improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds a TestNode struct and supporting TestLeaf inside #[cfg(test)] in primitives/src/merkle_tree.rs. TestNode implements the MerkleNode trait but does not override calculate_root, so it uses the default stack-based implementation. Tests compare its output against TxMerkleNode::calculate_root for empty, single, paired, duplicate, balanced, and unbalanced leaf sets. The commit message explicitly states the purpose is to improve mutant-killing coverage for the default calculate_root implementation. No production logic is modified.
Changed components
primitives/src/merkle_tree.rs (test module only)Inspect captured patch +77 / −0
diff --git a/primitives/src/merkle_tree.rs b/primitives/src/merkle_tree.rs
index 2b4253fb..f90d2926 100644
--- a/primitives/src/merkle_tree.rs
+++ b/primitives/src/merkle_tree.rs
@@ -196,6 +196,9 @@ impl MerkleNode for WitnessMerkleNode {
#[cfg(test)]
mod tests {
+ use hashes::HashEngine;
+
+ use super::MerkleNode;
use crate::hash_types::*;
// Helper to make a Txid, TxMerkleNode pair with a single number byte array
@@ -388,4 +391,78 @@ mod tests {
let root = WitnessMerkleNode::calculate_root([leaf, leaf].into_iter());
assert!(root.is_none(), "Duplicate witness leaves should return None");
}
+
+ // The tests below exercise the default trait `MerkleNode::calculate_root`
+ // implementation. On std+x86_64/aarch64, both `TxMerkleNode` and
+ // `WitnessMerkleNode` override `calculate_root` with the batched version,
+ // this is a test-only impl that uses the default `calculate_root` to kill
+ // mutants
+ #[derive(Clone, Copy, Eq, PartialEq)]
+ struct TestLeaf([u8; 32]);
+
+ impl AsRef<[u8]> for TestLeaf {
+ fn as_ref(&self) -> &[u8] { &self.0 }
+ }
+
+ impl crate::transaction::TxIdentifier for TestLeaf {}
+
+ #[derive(Clone, Copy, Debug, Eq, PartialEq)]
+ struct TestNode([u8; 32]);
+
+ impl super::MerkleNode for TestNode {
+ type Leaf = TestLeaf;
+
+ fn from_leaf(leaf: Self::Leaf) -> Self { Self(leaf.0) }
+
+ fn combine(&self, other: &Self) -> Self {
+ let mut engine = hashes::sha256d::Hash::engine();
+ engine.input(&self.0);
+ engine.input(&other.0);
+ Self(hashes::sha256d::Hash::from_engine(engine).to_byte_array())
+ }
+ }
+
+ // Asserts the default (stack-based) `TestNode::calculate_root` produces
+ // the same result as the optimized `TxMerkleNode::calculate_root`.
+ #[track_caller]
+ fn assert_roots_match(leaf_bytes: &[u8]) {
+ let test_root = TestNode::calculate_root(leaf_bytes.iter().map(|&b| TestLeaf([b; 32])));
+ let tx_root = TxMerkleNode::calculate_root(
+ leaf_bytes.iter().map(|&b| Txid::from_byte_array([b; 32])),
+ );
+ assert_eq!(test_root.map(|n| n.0), tx_root.map(TxMerkleNode::to_byte_array));
+ }
+
+ #[test]
+ fn calculate_root_empty() { assert_roots_match(&[]); }
+
+ #[test]
+ fn calculate_root_single_leaf() { assert_roots_match(&[1]); }
+
+ #[test]
+ fn calculate_root_two_leaves() { assert_roots_match(&[1, 2]); }
+
+ #[test]
+ fn calculate_root_duplicate_leaves() { assert_roots_match(&[3, 3]); }
+
+ #[test]
+ fn calculate_root_four_leaves() { assert_roots_match(&[1, 2, 3, 4]); }
+
+ #[test]
+ fn calculate_root_three_leaves_unbalanced() { assert_roots_match(&[1, 2, 3]); }
+
+ #[test]
+ fn calculate_root_five_leaves_unbalanced() { assert_roots_match(&[1, 2, 3, 4, 5]); }
+
+ #[test]
+ fn calculate_root_seven_leaves_unbalanced() { assert_roots_match(&[1, 2, 3, 4, 5, 6, 7]); }
+
+ #[test]
+ fn calculate_root_correct_root_value() {
+ assert_roots_match(&[10, 20]);
+ // Verify ordering matters: combine(a, b) != combine(b, a).
+ let (_, node1) = make_leaf_node(10);
+ let (_, node2) = make_leaf_node(20);
+ assert_ne!(node1.combine(&node2), node2.combine(&node1));
+ }
}
Why this scored 15/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.