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 + MAX_TRANSACTIONS_PER_BLOCK * 32 + 32 + 16 + 8 + 8 }
81
82 pub const fn min_serialized_size() -> usize {
83 8 + 32 + 16 + 8 + 8 }
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 serialized.extend_from_slice(&self.previous_block_hash.to_array());
102
103 serialized.extend_from_slice(&self.timestamp.to_le_bytes());
105
106 serialized.extend_from_slice(&self.block_height.to_le_bytes());
108
109 serialized.extend_from_slice(&self.bitcoin_block_height.to_le_bytes());
111
112 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 let previous_block_hash = read_hash(data, &mut cursor)?;
126
127 let timestamp = read_u128(data, &mut cursor)?;
129
130 let block_height = read_u64(data, &mut cursor)?;
132
133 let bitcoin_block_height = read_u64(data, &mut cursor)?;
135
136 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
245pub struct BlockSizeTracker {
247 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 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 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 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 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}