1use crate::tn_public_address::tn_pubkey_to_address_string;
2use crate::tn_signature_encoding::tn_signature_to_string;
3use crate::txn_lib::{self, Transaction, WireTxnHdrV1};
4use blake3;
5use std::{collections::HashSet, mem};
6use tracing::{debug, error, warn};
7pub type FdPubkey = [u8; 32];
12pub type FdSignature = [u8; 64];
13pub type FdBlake3Hash = [u8; 32];
14
15#[derive(Debug, Clone)]
17pub struct BlockParseResult {
18 pub block_hash: [u8; 32], pub block_producer: [u8; 32], pub transactions: Vec<Vec<u8>>, }
22
23#[derive(Debug, thiserror::Error)]
25pub enum BlockParseError {
26 #[error("Invalid block structure: {0}")]
27 InvalidBlockStructure(String),
28 #[error("Blake3 hash computation failed: {0}")]
29 HashComputationFailed(String),
30 #[error("Header signature verification failed: {0}")]
31 HeaderSignatureInvalid(String),
32 #[error("Block signature verification failed: {0}")]
33 BlockSignatureInvalid(String),
34 #[error("Ed25519 key error: {0}")]
35 Ed25519KeyError(String),
36 #[error("Account extraction failed: {0}")]
37 AccountExtractionFailed(String),
38}
39
40#[repr(C)]
41#[derive(Clone, Copy, Debug)]
42pub struct TnBlockHeader {
43 pub block_header_sig: FdSignature,
44 pub body: TnBlockHeaderBody,
45}
46
47#[repr(C)]
48#[derive(Clone, Copy, Debug)]
49pub struct TnBlockHeaderBody {
50 pub block_version: u8,
51 pub padding: [u8; 5],
52 pub chain_id: u16,
53 pub block_producer: FdPubkey,
54 pub bond_amount_lock_up: u64,
55 pub expiry_timestamp: u64,
56 pub start_slot: u64,
57 pub expiry_after: u32,
58 pub max_block_size: u32,
59 pub max_compute_units: u64,
60 pub max_state_units: u32,
61 pub reserved: [u8; 4],
62 pub weight_slot: u64,
63 pub block_time_ns: u64,
64}
65
66#[repr(C)]
67#[derive(Clone, Copy, Debug)]
68pub struct TnBlockFooter {
69 pub body: TnBlockFooterBody,
70 pub block_hash: FdBlake3Hash,
71 pub block_sig: FdSignature,
72}
73
74#[repr(C)]
75#[derive(Clone, Copy, Debug)]
76pub struct TnBlockFooterBody {
77 pub attestor_payment: u64,
78}
79
80pub struct BlockParser;
82
83impl BlockParser {
84 pub fn parse_block(data: &[u8]) -> Result<BlockParseResult, BlockParseError> {
86 if data.is_empty() {
87 return Ok(BlockParseResult {
88 block_hash: [0u8; 32],
89 block_producer: [0u8; 32],
90 transactions: Vec::new(),
91 });
92 }
93
94 debug!(
95 "Parsing block data of {} bytes with cryptographic verification",
96 data.len()
97 );
98
99 let header_size = mem::size_of::<TnBlockHeader>();
101 let footer_size = mem::size_of::<TnBlockFooter>();
102
103 if data.len() < header_size + footer_size {
104 return Err(BlockParseError::InvalidBlockStructure(format!(
105 "Block too small: {} bytes, need at least {}",
106 data.len(),
107 header_size + footer_size
108 )));
109 }
110
111 let header = Self::parse_header_verified(&data[..header_size])?;
113 debug!(
114 "Parsed block header: version={}, start_slot={}, producer={}",
115 header.body.block_version,
116 header.body.start_slot,
117 tn_pubkey_to_address_string(&header.body.block_producer)
118 );
119
120 Self::verify_header_signature(&header)?;
122 debug!("Block header signature verified successfully");
123
124 let block_hash = Self::compute_block_hash(data)?;
126 debug!("Block hash computed successfully");
127
128 let footer_start = data.len() - footer_size;
130 let footer = Self::parse_footer_verified(&data[footer_start..])?;
131 debug!(
132 "Parsed block footer: attestor_payment={}",
133 footer.body.attestor_payment
134 );
135
136 Self::verify_block_signature(&block_hash, &footer, &header.body.block_producer)?;
138 debug!("Block signature verified successfully");
139
140 let transactions_data = &data[header_size..footer_start];
142 debug!(
143 "Transaction data section: {} bytes",
144 transactions_data.len()
145 );
146
147 let transactions = if transactions_data.is_empty() {
149 debug!("No transaction data in block");
150 Vec::new()
151 } else {
152 Self::parse_transactions(transactions_data)
153 .map_err(|e| BlockParseError::InvalidBlockStructure(e))?
154 };
155
156 debug!("Extracted {} transactions from block", transactions.len());
157
158 Ok(BlockParseResult {
159 block_hash,
160 block_producer: header.body.block_producer,
161 transactions,
162 })
163 }
164
165 fn compute_block_hash(data: &[u8]) -> Result<[u8; 32], BlockParseError> {
168 let footer_size = mem::size_of::<TnBlockFooter>();
169
170 if data.len() < footer_size {
171 return Err(BlockParseError::HashComputationFailed(
172 "Block too small to contain footer".to_string(),
173 ));
174 }
175
176 let sig_size = mem::size_of::<FdSignature>();
179 let hash_size = mem::size_of::<FdBlake3Hash>();
180 let hash_data_end = data.len() - sig_size - hash_size;
181 let hash_data = &data[..hash_data_end];
182
183 debug!(
184 "Computing Blake3 hash over {} bytes (excluding {} byte signature and {} byte hash)",
185 hash_data.len(),
186 sig_size,
187 hash_size
188 );
189
190 let mut hasher = blake3::Hasher::new();
192 hasher.update(hash_data);
193
194 let hash_output = *hasher.finalize().as_bytes();
195
196 debug!("Blake3 hash computation completed successfully");
197 Ok(hash_output)
198 }
199
200 fn verify_header_signature(header: &TnBlockHeader) -> Result<(), BlockParseError> {
203 debug!("Starting header signature verification");
204
205 if header.block_header_sig.iter().all(|&b| b == 0) {
207 debug!("Header signature is all zeros - treating as unsigned block");
208 return Err(BlockParseError::HeaderSignatureInvalid(
209 "Block header is not signed (all-zero signature)".to_string(),
210 ));
211 }
212
213 if header.body.padding.iter().any(|&b| b != 0) {
218 return Err(BlockParseError::HeaderSignatureInvalid(
219 "non-zero header body padding".to_string(),
220 ));
221 }
222 if header.body.reserved.iter().any(|&b| b != 0) {
223 return Err(BlockParseError::HeaderSignatureInvalid(
224 "non-zero header body reserved".to_string(),
225 ));
226 }
227
228 let body_size = mem::size_of::<TnBlockHeaderBody>();
231
232 let body_bytes =
234 unsafe { std::slice::from_raw_parts(&header.body as *const _ as *const u8, body_size) };
235
236 debug!(
237 "Verifying header signature over {} bytes of header body data",
238 body_bytes.len()
239 );
240
241 crate::tn_signature::verify(
244 crate::tn_signature::SignatureDomain::BlockHeader,
245 body_bytes,
246 &header.block_header_sig,
247 &header.body.block_producer,
248 )
249 .map_err(|e| {
250 error!("Header signature verification failed: {}", e);
251 BlockParseError::HeaderSignatureInvalid(format!("Verification failed: {}", e))
252 })?;
253
254 debug!("Header signature verification successful");
255 Ok(())
256 }
257
258 fn verify_block_signature(
260 block_hash: &[u8; 32],
261 footer: &TnBlockFooter,
262 producer_key: &[u8; 32],
263 ) -> Result<(), BlockParseError> {
264 debug!("Starting block signature verification");
265
266 if footer.block_sig.iter().all(|&b| b == 0) {
268 debug!("Block signature is all zeros - treating as unsigned block");
269 return Err(BlockParseError::BlockSignatureInvalid(
270 "Block is not signed (all-zero signature)".to_string(),
271 ));
272 }
273
274 debug!("Verifying block signature against computed hash");
275
276 crate::tn_signature::verify(
279 crate::tn_signature::SignatureDomain::Block,
280 block_hash,
281 &footer.block_sig,
282 producer_key,
283 )
284 .map_err(|e| {
285 error!("Block signature verification failed: {}", e);
286 BlockParseError::BlockSignatureInvalid(format!("Verification failed: {}", e))
287 })?;
288
289 debug!("Block signature verification successful");
290 Ok(())
291 }
292
293 fn parse_header_verified(data: &[u8]) -> Result<TnBlockHeader, BlockParseError> {
295 Self::parse_header(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
296 }
297
298 fn parse_footer_verified(data: &[u8]) -> Result<TnBlockFooter, BlockParseError> {
300 Self::parse_footer(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
301 }
302
303 fn parse_header(data: &[u8]) -> Result<TnBlockHeader, String> {
305 if data.len() < mem::size_of::<TnBlockHeader>() {
306 return Err("Insufficient data for block header".to_string());
307 }
308
309 let header = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockHeader) };
311
312 debug!(
313 "Block header: version={}, producer={:?}",
314 header.body.block_version,
315 tn_pubkey_to_address_string(&header.body.block_producer)
316 );
317 Ok(header)
318 }
319
320 fn parse_footer(data: &[u8]) -> Result<TnBlockFooter, String> {
322 if data.len() < mem::size_of::<TnBlockFooter>() {
323 return Err("Insufficient data for block footer".to_string());
324 }
325
326 let footer = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockFooter) };
327
328 debug!(
329 "Block footer: attestor_payment={}",
330 footer.body.attestor_payment
331 );
332 Ok(footer)
333 }
334
335 fn parse_transactions(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
337 let mut transactions = Vec::new();
338 let mut offset = 0;
339
340 while offset < data.len() {
342 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
344 if offset + wire_header_size > data.len() {
345 debug!(
346 "Remaining data too small for transaction header: {} bytes",
347 data.len() - offset
348 );
349 break;
350 }
351
352 let remaining_data = &data[offset..];
353
354 match Self::try_parse_transaction_at_offset(remaining_data) {
357 Ok((transaction_size, transaction_data)) => {
358 transactions.push(transaction_data);
359 debug!(
360 "Parsed transaction {} of size {} bytes",
361 transactions.len(),
362 transaction_size
363 );
364 offset += transaction_size;
365 }
366 Err(parse_error) => {
367 warn!(
368 "Failed to parse transaction at offset {}: {}",
369 offset, parse_error
370 );
371 return Err(parse_error);
372 }
373 }
374 }
375
376 Ok(transactions)
377 }
378
379 fn try_parse_transaction_at_offset(data: &[u8]) -> Result<(usize, Vec<u8>), String> {
381 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
383
384 if data.len() < wire_header_size {
385 return Err("Not enough data for transaction header".to_string());
386 }
387
388 let total_size = txn_lib::tn_txn_size(data).map_err(|e| e.to_string())?;
390
391 if data.len() < total_size {
392 return Err(format!(
393 "Not enough data for complete transaction: need {} bytes, have {}",
394 total_size,
395 data.len()
396 ));
397 }
398 let transaction_data = data[..total_size].to_vec();
400
401 if Transaction::from_wire(&transaction_data).is_none() {
403 return Err("Transaction::from_wire failed to parse transaction".to_string());
404 }
405 Ok((total_size, transaction_data))
406 }
407
408 pub fn extract_transaction_signature(tx_data: &[u8]) -> Result<String, String> {
411 if tx_data.len() < txn_lib::TN_TXN_SIGNATURE_SZ {
412 return Err("Transaction too short to contain a signature".to_string());
413 }
414
415 let sig_start = tx_data.len() - txn_lib::TN_TXN_SIGNATURE_SZ;
417 let signature_bytes = &tx_data[sig_start..];
418
419 let mut sig_array = [0u8; 64];
421 sig_array.copy_from_slice(signature_bytes);
422
423 let signature = tn_signature_to_string(&sig_array);
425
426 debug!("Extracted signature: {}", signature);
427 Ok(signature)
428 }
429
430 pub fn extract_account_mentions(
433 transactions: &[Vec<u8>],
434 ) -> Result<HashSet<String>, BlockParseError> {
435 let mut accounts = HashSet::new();
436
437 debug!(
438 "Extracting account mentions from {} transactions",
439 transactions.len()
440 );
441
442 for (i, tx_data) in transactions.iter().enumerate() {
443 match Self::extract_transaction_accounts(tx_data) {
444 Ok(tx_accounts) => {
445 debug!(
446 "Transaction {} contains {} account references: {:?}",
447 i,
448 tx_accounts.len(),
449 tx_accounts
450 );
451 accounts.extend(tx_accounts);
452 }
453 Err(e) => {
454 warn!("Failed to extract accounts from transaction {}: {}", i, e);
455 }
457 }
458 }
459
460 debug!(
461 "Extracted {} unique account addresses from block: {:?}",
462 accounts.len(),
463 accounts
464 );
465 Ok(accounts)
466 }
467
468 fn extract_transaction_accounts(tx_data: &[u8]) -> Result<Vec<String>, BlockParseError> {
471 let header_size = 112;
473 let signature_size = txn_lib::TN_TXN_SIGNATURE_SZ;
474 let min_txn_size = header_size + signature_size;
475 if tx_data.len() < min_txn_size {
476 return Err(BlockParseError::AccountExtractionFailed(format!(
477 "Transaction too small: {} bytes, need at least {} (header={}, signature={})",
478 tx_data.len(),
479 min_txn_size,
480 header_size,
481 signature_size
482 )));
483 }
484
485 debug!(
486 "Extracting accounts from transaction of {} bytes",
487 tx_data.len()
488 );
489 let mut accounts = Vec::new();
490
491 let fee_payer_offset = 48;
493 if tx_data.len() >= fee_payer_offset + 32 {
494 let fee_payer_pubkey: [u8; 32] = tx_data[fee_payer_offset..fee_payer_offset + 32]
495 .try_into()
496 .map_err(|_| {
497 BlockParseError::AccountExtractionFailed(
498 "Failed to convert fee_payer_pubkey to [u8; 32]".to_string(),
499 )
500 })?;
501 let fee_payer_address = tn_pubkey_to_address_string(&fee_payer_pubkey);
502 debug!(
503 "Extracted fee_payer at offset {}: {:?} -> {}",
504 fee_payer_offset,
505 &fee_payer_pubkey[..8],
506 fee_payer_address
507 );
508 accounts.push(fee_payer_address);
509 }
510
511 let program_offset = 80;
513 if tx_data.len() >= program_offset + 32 {
514 let program_pubkey: [u8; 32] = tx_data[program_offset..program_offset + 32]
515 .try_into()
516 .map_err(|_| {
517 BlockParseError::AccountExtractionFailed(
518 "Failed to convert program_pubkey to [u8; 32]".to_string(),
519 )
520 })?;
521 let program_address = tn_pubkey_to_address_string(&program_pubkey);
522 debug!(
523 "Extracted program at offset {}: {:?} -> {}",
524 program_offset,
525 &program_pubkey[..8],
526 program_address
527 );
528 accounts.push(program_address);
529 }
530
531 let header_size = 112;
534 if tx_data.len() > header_size {
535 let readwrite_accounts_cnt = u16::from_le_bytes([tx_data[2], tx_data[3]]);
537 let readonly_accounts_cnt = u16::from_le_bytes([tx_data[4], tx_data[5]]);
538
539 debug!(
540 "Transaction has {} readwrite and {} readonly accounts",
541 readwrite_accounts_cnt, readonly_accounts_cnt
542 );
543
544 let additional_accounts_count =
546 (readwrite_accounts_cnt + readonly_accounts_cnt) as usize;
547 let additional_accounts_size = additional_accounts_count * 32;
548
549 if tx_data.len() >= header_size + additional_accounts_size {
550 for i in 0..additional_accounts_count {
551 let account_offset = header_size + (i * 32);
552 let account_pubkey: [u8; 32] = tx_data[account_offset..account_offset + 32]
553 .try_into()
554 .map_err(|_| {
555 BlockParseError::AccountExtractionFailed(format!(
556 "Failed to convert account_pubkey {} to [u8; 32]",
557 i
558 ))
559 })?;
560 let account_address = tn_pubkey_to_address_string(&account_pubkey);
561 accounts.push(account_address);
562 }
563 }
564 }
565
566 Ok(accounts)
567 }
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn test_block_header_body_chain_id_layout() {
576 let body = TnBlockHeaderBody {
577 block_version: 1,
578 padding: [0; 5],
579 chain_id: 0x1234,
580 block_producer: [0; 32],
581 bond_amount_lock_up: 0,
582 expiry_timestamp: 0,
583 start_slot: 0,
584 expiry_after: 0,
585 max_block_size: 0,
586 max_compute_units: 0,
587 max_state_units: 0,
588 reserved: [0; 4],
589 weight_slot: 0,
590 block_time_ns: 0,
591 };
592
593 let base = &body as *const _ as usize;
594 let chain_id = &body.chain_id as *const _ as usize;
595 assert_eq!(chain_id - base, 6);
596 assert_eq!(body.chain_id, 0x1234);
597 assert_eq!(std::mem::size_of::<TnBlockHeaderBody>(), 104);
598 }
599
600 #[test]
601 fn test_extract_transaction_signature() {
602 let mut transaction_data = vec![0u8; 100];
604 let sig_start = transaction_data.len() - 64;
606 for i in 0..64 {
607 transaction_data[sig_start + i] = (i % 256) as u8;
608 }
609
610 let result = BlockParser::extract_transaction_signature(&transaction_data);
611 assert!(result.is_ok());
612
613 let signature = result.unwrap();
614 assert!(!signature.is_empty());
615 assert_eq!(
617 signature.len(),
618 90,
619 "Signature should be 90 characters in ts... format"
620 );
621 assert!(
622 signature.starts_with("ts"),
623 "Signature should start with 'ts'"
624 );
625 }
626
627 #[test]
628 fn test_extract_transaction_signature_too_short() {
629 let transaction_data = vec![0u8; 32]; let result = BlockParser::extract_transaction_signature(&transaction_data);
631 assert!(result.is_err());
632 assert_eq!(
633 result.unwrap_err(),
634 "Transaction too short to contain a signature"
635 );
636 }
637
638 #[test]
639 fn test_parse_empty_block() {
640 let empty_data = vec![];
641 let result = BlockParser::parse_block(&empty_data);
642 assert!(result.is_ok());
643 let block_result = result.unwrap();
644 assert_eq!(block_result.transactions.len(), 0);
645 assert_eq!(block_result.block_hash, [0u8; 32]);
646 assert_eq!(block_result.block_producer, [0u8; 32]);
647 }
648
649 #[test]
650 fn test_parse_block_too_small() {
651 let small_data = vec![0u8; 50]; let result = BlockParser::parse_block(&small_data);
653 assert!(result.is_err());
654 let error_msg = format!("{}", result.unwrap_err());
655 assert!(error_msg.contains("Block too small"));
656 }
657
658 #[test]
659 fn test_parse_block_header_footer_only() {
660 let header_size = std::mem::size_of::<TnBlockHeader>();
662 let footer_size = std::mem::size_of::<TnBlockFooter>();
663 let mut block_data = vec![0u8; header_size + footer_size];
664
665 block_data[0] = 1; let result = BlockParser::parse_block(&block_data);
669
670 match result {
673 Ok(block_result) => {
674 assert_eq!(block_result.transactions.len(), 0);
676 assert_eq!(block_result.block_producer, [0u8; 32]);
677 assert_eq!(block_result.block_hash.len(), 32);
678 }
679 Err(error) => {
680 let error_msg = format!("{}", error);
682 assert!(
683 error_msg.contains("signature")
684 || error_msg.contains("key")
685 || error_msg.contains("Invalid")
686 );
687 }
688 }
689 }
690
691 #[test]
692 fn test_try_parse_transaction_at_offset() {
693 let header_size = 112;
704 let signature_size = 64;
705 let min_txn_size = header_size + signature_size;
706 let mut tx_data = vec![0u8; min_txn_size];
707
708 tx_data[0] = 1; tx_data[2] = 0;
714 tx_data[3] = 0;
715 tx_data[4] = 0;
717 tx_data[5] = 0;
718 tx_data[6] = 0;
720 tx_data[7] = 0;
721
722 let result = BlockParser::try_parse_transaction_at_offset(&tx_data);
724 match result {
727 Ok((size, data)) => {
728 assert_eq!(size, min_txn_size);
729 assert_eq!(data.len(), min_txn_size);
730 }
731 Err(_) => {
732 }
735 }
736 }
737
738 #[test]
739 fn test_parse_transactions_empty_data() {
740 let empty_data = vec![];
741 let result = BlockParser::parse_transactions(&empty_data);
742 assert!(result.is_ok());
743 assert_eq!(result.unwrap().len(), 0);
744 }
745
746 #[test]
747 fn test_parse_transactions_insufficient_data() {
748 let short_data = vec![0u8; 32]; let result = BlockParser::parse_transactions(&short_data);
750 assert!(result.is_ok());
751 assert_eq!(result.unwrap().len(), 0); }
753}