Skip to main content

brk_types/
block.rs

1use std::borrow::Cow;
2
3use bitcoin::hashes::{Hash, HashEngine};
4use derive_more::Deref;
5
6use crate::BlkMetadata;
7
8use super::{BlockHash, Height};
9
10/// Raw block bytes and per-tx offsets for fast txid hashing.
11/// Present when block was parsed from blk*.dat files, absent for RPC blocks.
12#[derive(Debug)]
13struct RawBlockData {
14    bytes: Vec<u8>,
15    /// Per-tx byte offset within `bytes`.
16    tx_offsets: Vec<u32>,
17}
18
19#[derive(Debug, Deref)]
20pub struct Block {
21    height: Height,
22    hash: BlockHash,
23    #[deref]
24    block: bitcoin::Block,
25    raw: Option<RawBlockData>,
26}
27
28impl Block {
29    pub fn height(&self) -> Height {
30        self.height
31    }
32
33    pub fn hash(&self) -> &BlockHash {
34        &self.hash
35    }
36
37    /// Compute total_size and weight in a single pass (2N tx serializations
38    /// instead of 3N from calling `total_size()` + `weight()` separately,
39    /// since `weight()` internally calls both `base_size()` and `total_size()`).
40    pub fn total_size_and_weight(&self) -> (usize, usize) {
41        let overhead =
42            bitcoin::block::Header::SIZE + bitcoin::VarInt::from(self.txdata.len()).size();
43        let mut total_size = overhead;
44        let mut weight_wu = overhead * 4;
45        for (i, tx) in self.txdata.iter().enumerate() {
46            let base = tx.base_size();
47            let total = self
48                .raw_tx_bytes(i)
49                .map_or_else(|| tx.total_size(), |raw| raw.len());
50            total_size += total;
51            weight_wu += base * 3 + total;
52        }
53        (total_size, weight_wu)
54    }
55
56    pub fn set_raw_data(&mut self, bytes: Vec<u8>, tx_offsets: Vec<u32>) {
57        self.raw = Some(RawBlockData { bytes, tx_offsets });
58    }
59
60    /// Compute txid, base_size, and total_size for the transaction at `index`.
61    /// Uses raw bytes (fast path) when available, falls back to re-serialization.
62    pub fn compute_tx_id_and_sizes(&self, index: usize) -> (bitcoin::Txid, u32, u32) {
63        let tx = &self.txdata[index];
64        if let Some(raw) = self.raw_tx_bytes(index) {
65            let total_size = raw.len() as u32;
66            let is_segwit = raw[4] == 0x00;
67            let base_size = if is_segwit {
68                tx.base_size() as u32
69            } else {
70                total_size
71            };
72            let txid = Self::hash_raw_tx(raw, base_size);
73            debug_assert_eq!(txid, tx.compute_txid(), "raw txid mismatch at tx {index}");
74            (txid, base_size, total_size)
75        } else {
76            (
77                tx.compute_txid(),
78                tx.base_size() as u32,
79                tx.total_size() as u32,
80            )
81        }
82    }
83
84    /// Returns raw transaction bytes for the given tx index, if available.
85    fn raw_tx_bytes(&self, index: usize) -> Option<&[u8]> {
86        let raw = self.raw.as_ref()?;
87        let start = raw.tx_offsets[index] as usize;
88        let end = raw
89            .tx_offsets
90            .get(index + 1)
91            .map_or(raw.bytes.len(), |&off| off as usize);
92        Some(&raw.bytes[start..end])
93    }
94
95    /// Hash raw transaction bytes directly (SHA256d), avoiding re-serialization.
96    ///
97    /// For segwit (`raw[4] == 0x00`): hashes version + inputs/outputs + locktime,
98    /// skipping marker, flag, and witness data.
99    /// For legacy: hashes entire raw bytes.
100    fn hash_raw_tx(raw: &[u8], base_size: u32) -> bitcoin::Txid {
101        let mut engine = bitcoin::Txid::engine();
102        if raw[4] == 0x00 {
103            let io_len = base_size as usize - 8;
104            engine.input(&raw[..4]);
105            engine.input(&raw[6..6 + io_len]);
106            engine.input(&raw[raw.len() - 4..]);
107        } else {
108            engine.input(raw);
109        }
110        bitcoin::Txid::from_engine(engine)
111    }
112
113    pub fn coinbase_tag(&self) -> Cow<'_, str> {
114        String::from_utf8_lossy(
115            self.txdata
116                .first()
117                .and_then(|tx| tx.input.first())
118                .unwrap()
119                .script_sig
120                .as_bytes(),
121        )
122    }
123}
124
125impl From<(Height, bitcoin::Block)> for Block {
126    #[inline]
127    fn from((height, block): (Height, bitcoin::Block)) -> Self {
128        Self::from((height, block.block_hash(), block))
129    }
130}
131
132impl From<(Height, bitcoin::BlockHash, bitcoin::Block)> for Block {
133    #[inline]
134    fn from((height, hash, block): (Height, bitcoin::BlockHash, bitcoin::Block)) -> Self {
135        Self::from((height, BlockHash::from(hash), block))
136    }
137}
138
139impl From<(Height, BlockHash, bitcoin::Block)> for Block {
140    #[inline]
141    fn from((height, hash, block): (Height, BlockHash, bitcoin::Block)) -> Self {
142        Self {
143            height,
144            hash,
145            block,
146            raw: None,
147        }
148    }
149}
150
151impl From<ReadBlock> for Block {
152    #[inline]
153    fn from(value: ReadBlock) -> Self {
154        value.block
155    }
156}
157
158#[derive(Debug, Deref)]
159pub struct ReadBlock {
160    #[deref]
161    block: Block,
162    metadata: BlkMetadata,
163    tx_metadata: Vec<BlkMetadata>,
164}
165
166impl From<(Block, BlkMetadata, Vec<BlkMetadata>)> for ReadBlock {
167    #[inline]
168    fn from((block, metadata, tx_metadata): (Block, BlkMetadata, Vec<BlkMetadata>)) -> Self {
169        Self {
170            block,
171            metadata,
172            tx_metadata,
173        }
174    }
175}
176
177impl ReadBlock {
178    pub fn metadata(&self) -> &BlkMetadata {
179        &self.metadata
180    }
181
182    pub fn tx_metadata(&self) -> &Vec<BlkMetadata> {
183        &self.tx_metadata
184    }
185
186    pub fn inner(self) -> Block {
187        self.block
188    }
189}