Skip to content

Commit 0a427c2

Browse files
rabbitson87claude
andcommitted
test(rpc): cover the block-tree derived record, which had no test at all
A mutation audit of the header change found that dropping the header from `Context::header_record` entirely failed no test. That path builds a BlockRecord from the block tree rather than from applied-block bytes, and this change gave it a `serialize(&node.header).try_into().ok()` whose failure mode is a silently absent header -- where the old String field would at least have carried something. It was the one part of the change nothing exercised. `tree_derived_record_carries_the_header` seeds a tree node, resolves the record through `record_for_hash` without pushing anything into `blocks`, and asserts both the raw bytes and the rendered hex match the header the tree holds. The rest of the change is well covered, and the audit says so rather than assuming it: neutering `header_from_block_bytes` fails five tests, three of them pre-existing `getblock` tests, and emptying `header_hex()` fails the round-trip test. The new test now fails under the mutation that found the gap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent a53523d commit 0a427c2

1 file changed

Lines changed: 45 additions & 0 deletions

File tree

crates/rpc/src/context.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,51 @@ mod tests {
10461046
assert!(record.header_bytes().is_none());
10471047
assert!(record.header_hex().is_empty());
10481048
}
1049+
1050+
/// Covers the record the block tree derives, which had no test at all.
1051+
///
1052+
/// `header_record` builds its header with `try_into().ok()`, so a length that
1053+
/// does not fit yields `None` and the header vanishes silently — where the
1054+
/// old `String` field would at least have carried something. A mutation that
1055+
/// dropped the header from this path failed no test before this one existed.
1056+
#[test]
1057+
fn tree_derived_record_carries_the_header() {
1058+
use bitcoin::block::Version;
1059+
use bitcoin::hashes::Hash as _;
1060+
use bitcoin::{BlockHash, CompactTarget, TxMerkleNode};
1061+
use bitcoin_rs_chain::NodeStatus;
1062+
1063+
let ctx = Context::new();
1064+
let header = bitcoin::block::Header {
1065+
version: Version::ONE,
1066+
prev_blockhash: BlockHash::all_zeros(),
1067+
merkle_root: TxMerkleNode::all_zeros(),
1068+
time: 1_000_000,
1069+
bits: CompactTarget::from_consensus(0x207f_ffff),
1070+
nonce: 7,
1071+
};
1072+
let hash = {
1073+
let mut tree = ctx.block_tree.write();
1074+
let id = tree
1075+
.insert_node(None, header, NodeStatus::Active)
1076+
.expect("genesis inserts");
1077+
tree.node(id).expect("inserted node").hash
1078+
};
1079+
1080+
// Nothing was pushed into `blocks`, so the record can only come from the
1081+
// tree.
1082+
let record = ctx.record_for_hash(hash).expect("tree resolves the hash");
1083+
1084+
assert_eq!(
1085+
record.header_bytes().map(|bytes| bytes.as_slice()),
1086+
Some(serialize(&header).as_slice()),
1087+
"the tree-derived record must carry the header the tree holds"
1088+
);
1089+
assert_eq!(
1090+
record.header_hex(),
1091+
serialize(&header).to_lower_hex_string()
1092+
);
1093+
}
10491094
#[test]
10501095
fn block_by_height_returns_record_after_add_block() {
10511096
use bitcoin_rs_primitives::Hash256;

0 commit comments

Comments
 (0)