Skip to main content

blvm_primitives/serialization/
block.rs

1//! Block header wire format serialization/deserialization
2//!
3//! Bitcoin block header wire format specification.
4//! Must match consensus serialization exactly for consensus compatibility.
5
6use super::transaction::{
7    deserialize_transaction_with_witness, serialize_transaction, serialize_transaction_with_witness,
8};
9use super::varint::{decode_varint, encode_varint};
10use crate::error::{ConsensusError, Result};
11use crate::types::*;
12use std::borrow::Cow;
13
14/// Error type for block parsing failures
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum BlockParseError {
17    InsufficientBytes,
18    InvalidVersion,
19    InvalidTimestamp,
20    InvalidBits,
21    InvalidNonce,
22    InvalidTransactionCount,
23    InvalidWitnessMarker,
24}
25
26impl std::fmt::Display for BlockParseError {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            BlockParseError::InsufficientBytes => {
30                write!(f, "Insufficient bytes to parse block header")
31            }
32            BlockParseError::InvalidVersion => write!(f, "Invalid block version"),
33            BlockParseError::InvalidTimestamp => write!(f, "Invalid block timestamp"),
34            BlockParseError::InvalidBits => write!(f, "Invalid block bits"),
35            BlockParseError::InvalidNonce => write!(f, "Invalid block nonce"),
36            BlockParseError::InvalidTransactionCount => write!(f, "Invalid transaction count"),
37            BlockParseError::InvalidWitnessMarker => write!(f, "Invalid witness marker"),
38        }
39    }
40}
41
42impl std::error::Error for BlockParseError {}
43
44/// Serialize a block header to Bitcoin wire format
45pub fn serialize_block_header(header: &BlockHeader) -> Vec<u8> {
46    let mut result = Vec::with_capacity(80);
47    result.extend_from_slice(&(header.version as i32).to_le_bytes());
48    result.extend_from_slice(&header.prev_block_hash);
49    result.extend_from_slice(&header.merkle_root);
50    result.extend_from_slice(&(header.timestamp as u32).to_le_bytes());
51    result.extend_from_slice(&(header.bits as u32).to_le_bytes());
52    result.extend_from_slice(&(header.nonce as u32).to_le_bytes());
53    assert_eq!(result.len(), 80);
54    result
55}
56
57/// Deserialize a block header from Bitcoin wire format
58pub fn deserialize_block_header(data: &[u8]) -> Result<BlockHeader> {
59    if data.len() < 80 {
60        return Err(ConsensusError::Serialization(Cow::Owned(
61            BlockParseError::InsufficientBytes.to_string(),
62        )));
63    }
64
65    let mut offset = 0;
66
67    let version = i32::from_le_bytes([
68        data[offset],
69        data[offset + 1],
70        data[offset + 2],
71        data[offset + 3],
72    ]) as i64;
73    offset += 4;
74
75    let mut prev_block_hash = [0u8; 32];
76    prev_block_hash.copy_from_slice(&data[offset..offset + 32]);
77    offset += 32;
78
79    let mut merkle_root = [0u8; 32];
80    merkle_root.copy_from_slice(&data[offset..offset + 32]);
81    offset += 32;
82
83    let timestamp = u32::from_le_bytes([
84        data[offset],
85        data[offset + 1],
86        data[offset + 2],
87        data[offset + 3],
88    ]) as u64;
89    offset += 4;
90
91    let bits = u32::from_le_bytes([
92        data[offset],
93        data[offset + 1],
94        data[offset + 2],
95        data[offset + 3],
96    ]) as u64;
97    offset += 4;
98
99    let nonce = u32::from_le_bytes([
100        data[offset],
101        data[offset + 1],
102        data[offset + 2],
103        data[offset + 3],
104    ]) as u64;
105
106    Ok(BlockHeader {
107        version,
108        prev_block_hash,
109        merkle_root,
110        timestamp,
111        bits,
112        nonce,
113    })
114}
115
116/// Deserialize a complete block from Bitcoin wire format (including witness data)
117pub fn deserialize_block_with_witnesses(data: &[u8]) -> Result<(Block, Vec<Vec<Witness>>)> {
118    if data.len() < 80 {
119        return Err(ConsensusError::Serialization(Cow::Owned(
120            BlockParseError::InsufficientBytes.to_string(),
121        )));
122    }
123
124    let mut offset = 0;
125
126    let header = deserialize_block_header(&data[offset..offset + 80])?;
127    offset += 80;
128
129    let (tx_count, varint_len) = decode_varint(&data[offset..])?;
130    offset += varint_len;
131
132    if tx_count == 0 {
133        return Err(ConsensusError::Serialization(Cow::Owned(
134            BlockParseError::InvalidTransactionCount.to_string(),
135        )));
136    }
137
138    let mut transactions = Vec::new();
139    let mut all_witnesses: Vec<Vec<Witness>> = Vec::new();
140
141    for _ in 0..tx_count {
142        let (tx, input_witnesses, bytes_consumed) =
143            deserialize_transaction_with_witness(&data[offset..])?;
144        offset += bytes_consumed;
145        transactions.push(tx);
146        all_witnesses.push(input_witnesses);
147    }
148
149    while all_witnesses.len() < transactions.len() {
150        all_witnesses.push(Vec::new());
151    }
152
153    Ok((
154        Block::from_parts(header, transactions.into_boxed_slice()),
155        all_witnesses,
156    ))
157}
158
159/// Borrowed output scripts from a wire block, without building transactions or witnesses.
160pub fn output_script_pubkey_slices_from_wire_block(data: &[u8]) -> Result<Vec<&[u8]>> {
161    if data.len() < 80 {
162        return Err(ConsensusError::Serialization(Cow::Owned(
163            BlockParseError::InsufficientBytes.to_string(),
164        )));
165    }
166    let mut offset = 80;
167    let (tx_count, varint_len) = decode_varint(&data[offset..])?;
168    offset += varint_len;
169    if tx_count == 0 {
170        return Err(ConsensusError::Serialization(Cow::Owned(
171            BlockParseError::InvalidTransactionCount.to_string(),
172        )));
173    }
174    let mut scripts = Vec::new();
175    for _ in 0..tx_count {
176        let (tx_scripts, consumed) =
177            super::transaction::output_script_pubkey_slices_from_wire_tx(&data[offset..])?;
178        offset += consumed;
179        scripts.extend(tx_scripts);
180    }
181    Ok(scripts)
182}
183
184/// Serialize a complete block to Bitcoin wire format (including witness data)
185pub fn serialize_block_with_witnesses(
186    block: &Block,
187    witnesses: &[Vec<Witness>],
188    include_witness: bool,
189) -> Vec<u8> {
190    let mut result = Vec::new();
191
192    result.extend_from_slice(&serialize_block_header(&block.header));
193    result.extend_from_slice(&encode_varint(block.transactions.len() as u64));
194
195    // Witness marker and stack belong inside each transaction, not after the
196    // transaction count and not after every transaction body.
197    for (i, tx) in block.transactions.iter().enumerate() {
198        let tx_witnesses = witnesses.get(i);
199        let use_witness = include_witness
200            && tx_witnesses.is_some_and(|stacks| {
201                stacks.len() == tx.inputs.len() && stacks.iter().any(|stack| !stack.is_empty())
202            });
203        if use_witness {
204            result.extend_from_slice(
205                &serialize_transaction_with_witness(tx, tx_witnesses.unwrap())
206                    .expect("witness count matches inputs"),
207            );
208        } else {
209            result.extend_from_slice(&serialize_transaction(tx));
210        }
211    }
212
213    result
214}
215
216/// Serialize a block without witness data (convenience for non-SegWit blocks)
217pub fn serialize_block(block: &Block) -> Vec<u8> {
218    let witnesses: Vec<Vec<Witness>> = block.transactions.iter().map(|_| Vec::new()).collect();
219    serialize_block_with_witnesses(block, &witnesses, false)
220}
221
222/// Validate that a serialized block size matches the size implied by the Block + Witness data
223pub fn validate_block_serialized_size(
224    block: &Block,
225    witnesses: &[Vec<Witness>],
226    include_witness: bool,
227    provided_size: usize,
228) -> bool {
229    let serialized = serialize_block_with_witnesses(block, witnesses, include_witness);
230    serialized.len() == provided_size
231}
232
233#[cfg(test)]
234mod output_script_tests {
235    use super::*;
236    use crate::types::{BlockHeader, OutPoint, Transaction, TransactionInput, TransactionOutput};
237
238    #[test]
239    fn wire_output_scripts_match_the_decoded_block() {
240        let legacy = Transaction {
241            version: 1,
242            inputs: crate::tx_inputs![TransactionInput {
243                prevout: OutPoint {
244                    hash: [1; 32],
245                    index: 0,
246                },
247                script_sig: vec![0x51, 0x52],
248                sequence: 0xffff_ffff,
249            }],
250            outputs: crate::tx_outputs![
251                TransactionOutput {
252                    value: 50,
253                    script_pubkey: vec![0x76, 0xa9],
254                },
255                TransactionOutput {
256                    value: 1,
257                    script_pubkey: vec![],
258                },
259            ],
260            lock_time: 0,
261        };
262        let segwit = Transaction {
263            version: 2,
264            inputs: crate::tx_inputs![TransactionInput {
265                prevout: OutPoint {
266                    hash: [2; 32],
267                    index: 1,
268                },
269                script_sig: vec![],
270                sequence: 0xffff_fffe,
271            }],
272            outputs: crate::tx_outputs![TransactionOutput {
273                value: 25,
274                script_pubkey: vec![0x00, 0x14, 0xaa],
275            }],
276            lock_time: 7,
277        };
278        let header = BlockHeader {
279            version: 1,
280            prev_block_hash: [0; 32],
281            merkle_root: [3; 32],
282            timestamp: 1_231_006_505,
283            bits: 0x1d00_ffff,
284            nonce: 9,
285        };
286        let block =
287            crate::types::Block::from_parts(header, vec![legacy, segwit].into_boxed_slice());
288        let witnesses = vec![vec![Vec::new()], vec![vec![vec![0x30, 0x01]]]];
289        let bytes = serialize_block_with_witnesses(&block, &witnesses, true);
290        let scripts = output_script_pubkey_slices_from_wire_block(&bytes).unwrap();
291        let (decoded, _) = deserialize_block_with_witnesses(&bytes).unwrap();
292        let expected: Vec<Vec<u8>> = decoded
293            .transactions
294            .iter()
295            .flat_map(|tx| tx.outputs.iter().map(|output| output.script_pubkey.clone()))
296            .collect();
297        assert_eq!(
298            scripts,
299            expected.iter().map(Vec::as_slice).collect::<Vec<_>>()
300        );
301        assert_eq!(
302            scripts,
303            vec![&[0x76, 0xa9][..], &[][..], &[0x00, 0x14, 0xaa][..]]
304        );
305    }
306}