|
| 1 | +//! Adapter implementing `bitcoin_rs_consensus::DeploymentContext` |
| 2 | +//! over `bitcoin_rs_chain::BlockTree`. |
| 3 | +//! |
| 4 | +//! The consensus crate stays storage-agnostic via the |
| 5 | +//! `DeploymentContext` trait; this adapter is the node-crate-side |
| 6 | +//! implementation that lets `bitcoin_rs_consensus::compute_state` query |
| 7 | +//! historical block versions + MTPs against the in-memory block tree. |
| 8 | +
|
| 9 | +use bitcoin_rs_chain::{BlockTree, node::NodeId}; |
| 10 | +use bitcoin_rs_consensus::DeploymentContext; |
| 11 | + |
| 12 | +/// Read-only adapter over a `BlockTree` rooted at a chosen tip. |
| 13 | +/// |
| 14 | +/// All lookups walk backward from `start_tip_id` via parent pointers. |
| 15 | +/// Callers typically pass the current chain tip as `start_tip_id` so |
| 16 | +/// the adapter answers queries against the active chain. |
| 17 | +pub struct BlockTreeContext<'a> { |
| 18 | + tree: &'a BlockTree, |
| 19 | + start_tip_id: NodeId, |
| 20 | +} |
| 21 | + |
| 22 | +impl<'a> BlockTreeContext<'a> { |
| 23 | + /// Constructs an adapter anchored at `start_tip_id` within `tree`. |
| 24 | + #[must_use] |
| 25 | + pub const fn new(tree: &'a BlockTree, start_tip_id: NodeId) -> Self { |
| 26 | + Self { tree, start_tip_id } |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +impl DeploymentContext for BlockTreeContext<'_> { |
| 31 | + fn block_version(&self, height: u32) -> Option<i32> { |
| 32 | + let node_id = self.tree.node_at_height_from(self.start_tip_id, height)?; |
| 33 | + let node = self.tree.node(node_id).ok()?; |
| 34 | + Some(node.header.version.to_consensus()) |
| 35 | + } |
| 36 | + |
| 37 | + fn median_time_past(&self, height: u32, window: usize) -> Option<u32> { |
| 38 | + let node_id = self.tree.node_at_height_from(self.start_tip_id, height)?; |
| 39 | + self.tree.median_time_past_at(node_id, window) |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +#[cfg(test)] |
| 44 | +mod tests { |
| 45 | + use bitcoin::block::{Header, Version}; |
| 46 | + use bitcoin::hashes::Hash as _; |
| 47 | + use bitcoin::{BlockHash, CompactTarget, TxMerkleNode}; |
| 48 | + use bitcoin_rs_chain::{BlockTree, node::NodeStatus}; |
| 49 | + use bitcoin_rs_consensus::DeploymentContext; |
| 50 | + |
| 51 | + use super::BlockTreeContext; |
| 52 | + |
| 53 | + fn synthetic_header(prev_blockhash: BlockHash, time: u32) -> Header { |
| 54 | + Header { |
| 55 | + version: Version::ONE, |
| 56 | + prev_blockhash, |
| 57 | + merkle_root: TxMerkleNode::all_zeros(), |
| 58 | + time, |
| 59 | + bits: CompactTarget::from_consensus(0x207f_ffff), |
| 60 | + nonce: 0, |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + #[test] |
| 65 | + fn block_version_returns_header_version_at_height() -> Result<(), Box<dyn std::error::Error>> { |
| 66 | + let mut tree = BlockTree::new(); |
| 67 | + let header_0 = synthetic_header(BlockHash::all_zeros(), 1_000_000); |
| 68 | + let header_0_hash = header_0.block_hash(); |
| 69 | + tree.insert_header(header_0, NodeStatus::HeaderValid)?; |
| 70 | + let header_1 = synthetic_header(header_0_hash, 1_000_600); |
| 71 | + let tip = tree.insert_header(header_1, NodeStatus::HeaderValid)?; |
| 72 | + let ctx = BlockTreeContext::new(&tree, tip); |
| 73 | + |
| 74 | + assert_eq!(ctx.block_version(0), Some(1)); |
| 75 | + assert_eq!(ctx.block_version(1), Some(1)); |
| 76 | + assert_eq!(ctx.block_version(99), None); |
| 77 | + Ok(()) |
| 78 | + } |
| 79 | + |
| 80 | + #[test] |
| 81 | + fn median_time_past_returns_window_median() -> Result<(), Box<dyn std::error::Error>> { |
| 82 | + let mut tree = BlockTree::new(); |
| 83 | + let mut prev = BlockHash::all_zeros(); |
| 84 | + let mut tip = None; |
| 85 | + for i in 0..11_u32 { |
| 86 | + let header = synthetic_header(prev, 1_000_000 + i * 600); |
| 87 | + prev = header.block_hash(); |
| 88 | + tip = Some(tree.insert_header(header, NodeStatus::HeaderValid)?); |
| 89 | + } |
| 90 | + let Some(tip) = tip else { |
| 91 | + panic!("chain has 11 blocks should yield a tip"); |
| 92 | + }; |
| 93 | + let ctx = BlockTreeContext::new(&tree, tip); |
| 94 | + let Some(mtp) = ctx.median_time_past(10, 11) else { |
| 95 | + panic!("chain has 11 blocks should yield a median time past"); |
| 96 | + }; |
| 97 | + |
| 98 | + assert_eq!(mtp, 1_003_000); |
| 99 | + Ok(()) |
| 100 | + } |
| 101 | +} |
0 commit comments