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::{deserialize_transaction_with_witness, serialize_transaction};
7use super::varint::{decode_varint, encode_varint};
8use crate::error::{ConsensusError, Result};
9use crate::types::*;
10use std::borrow::Cow;
11
12/// Error type for block parsing failures
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum BlockParseError {
15    InsufficientBytes,
16    InvalidVersion,
17    InvalidTimestamp,
18    InvalidBits,
19    InvalidNonce,
20    InvalidTransactionCount,
21    InvalidWitnessMarker,
22}
23
24impl std::fmt::Display for BlockParseError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            BlockParseError::InsufficientBytes => {
28                write!(f, "Insufficient bytes to parse block header")
29            }
30            BlockParseError::InvalidVersion => write!(f, "Invalid block version"),
31            BlockParseError::InvalidTimestamp => write!(f, "Invalid block timestamp"),
32            BlockParseError::InvalidBits => write!(f, "Invalid block bits"),
33            BlockParseError::InvalidNonce => write!(f, "Invalid block nonce"),
34            BlockParseError::InvalidTransactionCount => write!(f, "Invalid transaction count"),
35            BlockParseError::InvalidWitnessMarker => write!(f, "Invalid witness marker"),
36        }
37    }
38}
39
40impl std::error::Error for BlockParseError {}
41
42/// Serialize a block header to Bitcoin wire format
43pub fn serialize_block_header(header: &BlockHeader) -> Vec<u8> {
44    let mut result = Vec::with_capacity(80);
45    result.extend_from_slice(&(header.version as i32).to_le_bytes());
46    result.extend_from_slice(&header.prev_block_hash);
47    result.extend_from_slice(&header.merkle_root);
48    result.extend_from_slice(&(header.timestamp as u32).to_le_bytes());
49    result.extend_from_slice(&(header.bits as u32).to_le_bytes());
50    result.extend_from_slice(&(header.nonce as u32).to_le_bytes());
51    assert_eq!(result.len(), 80);
52    result
53}
54
55/// Deserialize a block header from Bitcoin wire format
56pub fn deserialize_block_header(data: &[u8]) -> Result<BlockHeader> {
57    if data.len() < 80 {
58        return Err(ConsensusError::Serialization(Cow::Owned(
59            BlockParseError::InsufficientBytes.to_string(),
60        )));
61    }
62
63    let mut offset = 0;
64
65    let version = i32::from_le_bytes([
66        data[offset],
67        data[offset + 1],
68        data[offset + 2],
69        data[offset + 3],
70    ]) as i64;
71    offset += 4;
72
73    let mut prev_block_hash = [0u8; 32];
74    prev_block_hash.copy_from_slice(&data[offset..offset + 32]);
75    offset += 32;
76
77    let mut merkle_root = [0u8; 32];
78    merkle_root.copy_from_slice(&data[offset..offset + 32]);
79    offset += 32;
80
81    let timestamp = u32::from_le_bytes([
82        data[offset],
83        data[offset + 1],
84        data[offset + 2],
85        data[offset + 3],
86    ]) as u64;
87    offset += 4;
88
89    let bits = u32::from_le_bytes([
90        data[offset],
91        data[offset + 1],
92        data[offset + 2],
93        data[offset + 3],
94    ]) as u64;
95    offset += 4;
96
97    let nonce = u32::from_le_bytes([
98        data[offset],
99        data[offset + 1],
100        data[offset + 2],
101        data[offset + 3],
102    ]) as u64;
103
104    Ok(BlockHeader {
105        version,
106        prev_block_hash,
107        merkle_root,
108        timestamp,
109        bits,
110        nonce,
111    })
112}
113
114/// Deserialize a complete block from Bitcoin wire format (including witness data)
115pub fn deserialize_block_with_witnesses(data: &[u8]) -> Result<(Block, Vec<Vec<Witness>>)> {
116    if data.len() < 80 {
117        return Err(ConsensusError::Serialization(Cow::Owned(
118            BlockParseError::InsufficientBytes.to_string(),
119        )));
120    }
121
122    let mut offset = 0;
123
124    let header = deserialize_block_header(&data[offset..offset + 80])?;
125    offset += 80;
126
127    let (tx_count, varint_len) = decode_varint(&data[offset..])?;
128    offset += varint_len;
129
130    if tx_count == 0 {
131        return Err(ConsensusError::Serialization(Cow::Owned(
132            BlockParseError::InvalidTransactionCount.to_string(),
133        )));
134    }
135
136    let mut transactions = Vec::new();
137    let mut all_witnesses: Vec<Vec<Witness>> = Vec::new();
138
139    for _ in 0..tx_count {
140        let (tx, input_witnesses, bytes_consumed) =
141            deserialize_transaction_with_witness(&data[offset..])?;
142        offset += bytes_consumed;
143        transactions.push(tx);
144        all_witnesses.push(input_witnesses);
145    }
146
147    while all_witnesses.len() < transactions.len() {
148        all_witnesses.push(Vec::new());
149    }
150
151    Ok((
152        Block {
153            header,
154            transactions: transactions.into_boxed_slice(),
155        },
156        all_witnesses,
157    ))
158}
159
160/// Serialize a complete block to Bitcoin wire format (including witness data)
161pub fn serialize_block_with_witnesses(
162    block: &Block,
163    witnesses: &[Vec<Witness>],
164    include_witness: bool,
165) -> Vec<u8> {
166    let mut result = Vec::new();
167
168    result.extend_from_slice(&serialize_block_header(&block.header));
169    result.extend_from_slice(&encode_varint(block.transactions.len() as u64));
170
171    let has_witness = include_witness
172        && witnesses
173            .iter()
174            .any(|tx_witnesses| tx_witnesses.iter().any(|w| !w.is_empty()));
175
176    if has_witness {
177        result.push(0x00);
178        result.push(0x01);
179    }
180
181    for tx in block.transactions.iter() {
182        result.extend_from_slice(&serialize_transaction(tx));
183    }
184
185    if has_witness {
186        for tx_witnesses in witnesses.iter().take(block.transactions.len()) {
187            for witness in tx_witnesses {
188                result.extend_from_slice(&encode_varint(witness.len() as u64));
189                for element in witness {
190                    result.extend_from_slice(&encode_varint(element.len() as u64));
191                    result.extend_from_slice(element);
192                }
193            }
194        }
195    }
196
197    result
198}
199
200/// Serialize a block without witness data (convenience for non-SegWit blocks)
201pub fn serialize_block(block: &Block) -> Vec<u8> {
202    let witnesses: Vec<Vec<Witness>> = block.transactions.iter().map(|_| Vec::new()).collect();
203    serialize_block_with_witnesses(block, &witnesses, false)
204}
205
206/// Validate that a serialized block size matches the size implied by the Block + Witness data
207pub fn validate_block_serialized_size(
208    block: &Block,
209    witnesses: &[Vec<Witness>],
210    include_witness: bool,
211    provided_size: usize,
212) -> bool {
213    let serialized = serialize_block_with_witnesses(block, witnesses, include_witness);
214    serialized.len() == provided_size
215}