Skip to main content

arch_sdk/types/
block.rs

1use std::{array::TryFromSliceError, str::FromStr};
2
3use arch_program::hash::Hash;
4use bitcode::{Decode, Encode};
5use borsh::{BorshDeserialize, BorshSerialize};
6#[cfg(feature = "fuzzing")]
7use libfuzzer_sys::arbitrary;
8use serde::{Deserialize, Serialize};
9
10use super::ProcessedTransaction;
11pub const MAX_TRANSACTIONS_PER_BLOCK: usize = 1024;
12
13#[derive(Debug, thiserror::Error, Clone, PartialEq)]
14pub enum BlockParseError {
15    #[error("Invalid bytes")]
16    InvalidBytes,
17    #[error("Invalid string")]
18    InvalidString,
19    #[error("Invalid u64")]
20    InvalidU64,
21    #[error("Invalid u128")]
22    InvalidU128,
23    #[error("Invalid transactions length")]
24    InvalidTransactionsLength,
25    #[error("try from slice error")]
26    TryFromSliceError,
27}
28
29impl From<TryFromSliceError> for BlockParseError {
30    fn from(_e: TryFromSliceError) -> Self {
31        BlockParseError::TryFromSliceError
32    }
33}
34
35#[derive(
36    Clone,
37    Debug,
38    Serialize,
39    Deserialize,
40    BorshSerialize,
41    BorshDeserialize,
42    PartialEq,
43    Encode,
44    Decode,
45    Eq,
46)]
47#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))]
48pub struct Block {
49    pub transactions: Vec<Hash>,
50    pub previous_block_hash: Hash,
51    pub timestamp: u128,
52    pub block_height: u64,
53    pub bitcoin_block_height: u64,
54}
55
56impl Block {
57    pub fn new(
58        transactions: Vec<Hash>,
59        previous_block_hash: Hash,
60        timestamp: u128,
61        block_height: u64,
62        bitcoin_block_height: u64,
63    ) -> Self {
64        Self {
65            transactions,
66            previous_block_hash,
67            timestamp,
68            block_height,
69            bitcoin_block_height,
70        }
71    }
72
73    pub const fn max_serialized_size() -> usize {
74        8 // transaction_count
75        + MAX_TRANSACTIONS_PER_BLOCK * 32 // transactions
76        + 32 // previous_block_hash
77        + 16 // timestamp
78        + 8 // block_height
79        + 8 // bitcoin_block_height
80    }
81
82    pub const fn min_serialized_size() -> usize {
83        8 // transaction_count
84        + 32 // previous_block_hash
85        + 16 // timestamp
86        + 8 // block_height
87        + 8 // bitcoin_block_height
88    }
89
90    pub fn hash(&self) -> Hash {
91        let serialized_block = self.to_vec();
92        let hash_string = sha256::digest(sha256::digest(serialized_block));
93        Hash::from_str(&hash_string).expect("SHA256 always produces valid hex")
94    }
95
96    pub fn to_vec(&self) -> Vec<u8> {
97        let capacity = 32 + 16 + 8 + 8 + 8 + (self.transactions.len() * 32);
98        let mut serialized = Vec::with_capacity(capacity);
99
100        // Serialize previous_block_hash
101        serialized.extend_from_slice(&self.previous_block_hash.to_array());
102
103        // Serialize timestamp
104        serialized.extend_from_slice(&self.timestamp.to_le_bytes());
105
106        // Serialize block height
107        serialized.extend_from_slice(&self.block_height.to_le_bytes());
108
109        // Serialize bitcoin block height
110        serialized.extend_from_slice(&self.bitcoin_block_height.to_le_bytes());
111
112        // Serialize transactions
113        serialized.extend_from_slice(&(self.transactions.len() as u64).to_le_bytes());
114        for transaction in &self.transactions {
115            serialized.extend_from_slice(&transaction.to_array());
116        }
117
118        serialized
119    }
120
121    pub fn from_vec(data: &[u8]) -> Result<Self, BlockParseError> {
122        let mut cursor = 0;
123
124        // Deserialize previous_block_hash
125        let previous_block_hash = read_hash(data, &mut cursor)?;
126
127        // Deserialize timestamp
128        let timestamp = read_u128(data, &mut cursor)?;
129
130        // Deserialize block height
131        let block_height = read_u64(data, &mut cursor)?;
132
133        // Deserialize bitcoin_block_height
134        let bitcoin_block_height = read_u64(data, &mut cursor)?;
135
136        // Deserialize transactions
137        let transactions_len = read_u64(data, &mut cursor)?;
138
139        if transactions_len > MAX_TRANSACTIONS_PER_BLOCK as u64 {
140            return Err(BlockParseError::InvalidTransactionsLength);
141        }
142        let mut transactions = Vec::with_capacity(transactions_len as usize);
143        for _ in 0..transactions_len {
144            let tx_hash = read_hash(data, &mut cursor)?;
145            transactions.push(tx_hash);
146        }
147
148        Ok(Block {
149            transactions,
150            previous_block_hash,
151            timestamp,
152            block_height,
153            bitcoin_block_height,
154        })
155    }
156}
157
158pub struct BlockBuilder {
159    transactions: Vec<Hash>,
160    previous_block_hash: Hash,
161    timestamp: u128,
162    block_height: u64,
163    bitcoin_block_height: u64,
164}
165
166impl BlockBuilder {
167    pub fn new(
168        previous_block_hash: Hash,
169        timestamp: u128,
170        block_height: u64,
171        bitcoin_block_height: u64,
172    ) -> Self {
173        Self {
174            transactions: Vec::new(),
175            previous_block_hash,
176            timestamp,
177            block_height,
178            bitcoin_block_height,
179        }
180    }
181
182    pub fn add_transaction(&mut self, transaction: Hash) {
183        self.transactions.push(transaction);
184    }
185
186    pub fn block_height(&self) -> u64 {
187        self.block_height
188    }
189
190    pub fn bitcoin_block_height(&self) -> u64 {
191        self.bitcoin_block_height
192    }
193
194    pub fn timestamp(&self) -> u128 {
195        self.timestamp
196    }
197
198    pub fn previous_block_hash(&self) -> Hash {
199        self.previous_block_hash
200    }
201
202    pub fn transactions(&self) -> &[Hash] {
203        &self.transactions
204    }
205
206    pub fn build(self) -> Block {
207        Block::new(
208            self.transactions,
209            self.previous_block_hash,
210            self.timestamp,
211            self.block_height,
212            self.bitcoin_block_height,
213        )
214    }
215}
216
217fn read_hash(data: &[u8], cursor: &mut usize) -> Result<Hash, BlockParseError> {
218    if *cursor + 32 > data.len() {
219        return Err(BlockParseError::InvalidBytes);
220    }
221    let result: [u8; 32] = data[*cursor..*cursor + 32].try_into()?;
222    let result = Hash::from(result);
223    *cursor += 32;
224    Ok(result)
225}
226
227fn read_u64(data: &[u8], cursor: &mut usize) -> Result<u64, BlockParseError> {
228    if *cursor + 8 > data.len() {
229        return Err(BlockParseError::InvalidBytes);
230    }
231    let result = u64::from_le_bytes(data[*cursor..*cursor + 8].try_into()?);
232    *cursor += 8;
233    Ok(result)
234}
235
236fn read_u128(data: &[u8], cursor: &mut usize) -> Result<u128, BlockParseError> {
237    if *cursor + 16 > data.len() {
238        return Err(BlockParseError::InvalidBytes);
239    }
240    let result = u128::from_le_bytes(data[*cursor..*cursor + 16].try_into()?);
241    *cursor += 16;
242    Ok(result)
243}
244
245/// Tracker for the size of the serialized block.
246pub struct BlockSizeTracker {
247    /// Current size of the serialized block.
248    cur_size: usize,
249}
250
251impl BlockSizeTracker {
252    pub fn new() -> Self {
253        Self {
254            cur_size: Block::min_serialized_size(),
255        }
256    }
257
258    /// Update the size with an additional transaction.
259    pub fn add_transaction(&mut self) {
260        let serialized = Hash::from([0_u8; 32]).to_array();
261        self.cur_size += serialized.len();
262    }
263
264    /// Get the current size of the serialized block.
265    pub fn get_size(&self) -> usize {
266        self.cur_size
267    }
268}
269
270impl Default for BlockSizeTracker {
271    fn default() -> Self {
272        Self::new()
273    }
274}
275
276#[derive(
277    Clone,
278    Debug,
279    Serialize,
280    Deserialize,
281    BorshSerialize,
282    BorshDeserialize,
283    PartialEq,
284    Encode,
285    Decode,
286)]
287pub struct FullBlock {
288    pub transactions: Vec<ProcessedTransaction>,
289    pub previous_block_hash: Hash,
290    pub timestamp: u128,
291    pub block_height: u64,
292    pub bitcoin_block_height: u64,
293}
294
295impl From<(Block, Vec<ProcessedTransaction>)> for FullBlock {
296    fn from(value: (Block, Vec<ProcessedTransaction>)) -> Self {
297        FullBlock {
298            transactions: value.1,
299            previous_block_hash: value.0.previous_block_hash,
300            timestamp: value.0.timestamp,
301            block_height: value.0.block_height,
302            bitcoin_block_height: value.0.bitcoin_block_height,
303        }
304    }
305}
306
307impl FullBlock {
308    pub fn hash(&self) -> Hash {
309        // Create Block without cloning the entire FullBlock
310        let block = Block {
311            transactions: self.transactions.iter().map(|t| t.txid()).collect(),
312            previous_block_hash: self.previous_block_hash,
313            timestamp: self.timestamp,
314            bitcoin_block_height: self.bitcoin_block_height,
315            block_height: self.block_height,
316        };
317        block.hash()
318    }
319
320    pub fn to_vec(&self) -> Vec<u8> {
321        // Create Block without cloning the entire FullBlock
322        let block = Block {
323            transactions: self.transactions.iter().map(|t| t.txid()).collect(),
324            previous_block_hash: self.previous_block_hash,
325            timestamp: self.timestamp,
326            bitcoin_block_height: self.bitcoin_block_height,
327            block_height: self.block_height,
328        };
329        block.to_vec()
330    }
331}
332
333impl From<FullBlock> for Block {
334    fn from(value: FullBlock) -> Self {
335        Block {
336            transactions: value.transactions.into_iter().map(|t| t.txid()).collect(),
337            previous_block_hash: value.previous_block_hash,
338            timestamp: value.timestamp,
339            bitcoin_block_height: value.bitcoin_block_height,
340            block_height: value.block_height,
341        }
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use rand::Rng;
349
350    const GENESIS_BLOCK_PREVIOUS_HASH: &str =
351        "0000000000000000000000000000000000000000000000000000000000000000";
352
353    pub(crate) fn random_bytes<const N: usize>() -> [u8; N] {
354        let mut rng = rand::thread_rng();
355        let mut ret = [0; N];
356        rng.fill(&mut ret[..]);
357        ret
358    }
359
360    #[test]
361    fn test_block_serialization_deserialization() {
362        let original_block = Block {
363            transactions: vec![
364                Hash::from_str("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
365                    .unwrap(),
366                Hash::from_str("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321")
367                    .unwrap(),
368            ],
369            previous_block_hash: Hash::from_str(GENESIS_BLOCK_PREVIOUS_HASH).unwrap(),
370            timestamp: 1630000000,
371            block_height: 100,
372            bitcoin_block_height: 100,
373        };
374
375        let serialized_data = original_block.to_vec();
376        let deserialized_block = Block::from_vec(&serialized_data).expect("Deserialization failed");
377
378        assert_eq!(
379            original_block.previous_block_hash,
380            deserialized_block.previous_block_hash
381        );
382        assert_eq!(original_block.transactions, deserialized_block.transactions);
383        assert_eq!(original_block.timestamp, deserialized_block.timestamp);
384    }
385
386    #[test]
387    fn test_block_hash() {
388        let block = Block {
389            transactions: vec![
390                Hash::from_str("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
391                    .unwrap(),
392                Hash::from_str("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321")
393                    .unwrap(),
394            ],
395            previous_block_hash: Hash::from_str(GENESIS_BLOCK_PREVIOUS_HASH).unwrap(),
396            timestamp: 1630000000,
397            block_height: 100,
398            bitcoin_block_height: 100,
399        };
400
401        let hash = block.hash();
402        assert!(
403            !hash.to_string().is_empty(),
404            "Block hash should not be empty"
405        );
406        assert_eq!(
407            hash.to_string().len(),
408            64,
409            "Block hash should be 64 characters long"
410        );
411    }
412
413    #[test]
414    fn test_max_serialized_size() {
415        let block = Block {
416            transactions: vec![
417                Hash::from_str(GENESIS_BLOCK_PREVIOUS_HASH).unwrap();
418                MAX_TRANSACTIONS_PER_BLOCK
419            ],
420            previous_block_hash: Hash::from_str(GENESIS_BLOCK_PREVIOUS_HASH).unwrap(),
421            timestamp: 1630000000,
422            block_height: 100,
423            bitcoin_block_height: 100,
424        };
425        let serialized_data = block.to_vec();
426        assert_eq!(serialized_data.len(), Block::max_serialized_size());
427    }
428
429    #[test]
430    fn test_min_serialized_size() {
431        let block = Block {
432            transactions: vec![],
433            previous_block_hash: Hash::from_str(GENESIS_BLOCK_PREVIOUS_HASH).unwrap(),
434            timestamp: 1630000000,
435            block_height: 100,
436            bitcoin_block_height: 100,
437        };
438        let serialized_data = block.to_vec();
439        assert_eq!(serialized_data.len(), Block::min_serialized_size());
440    }
441
442    #[test]
443    fn test_size_tracker() {
444        let mut tracker = BlockSizeTracker::new();
445        assert_eq!(tracker.get_size(), Block::min_serialized_size());
446
447        let mut block = Block {
448            transactions: vec![],
449            previous_block_hash: Hash::from_str(GENESIS_BLOCK_PREVIOUS_HASH).unwrap(),
450            timestamp: 1630000000,
451            block_height: 100,
452            bitcoin_block_height: 100,
453        };
454
455        for _ in 0..10 {
456            let tx_id = Hash::from(random_bytes::<32>());
457            block.transactions.push(tx_id);
458            tracker.add_transaction();
459
460            let serialized_data = block.to_vec();
461            assert_eq!(serialized_data.len(), tracker.get_size());
462        }
463    }
464}