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};
7use ed25519_dalek::{Signature, Verifier, VerifyingKey};
10
11pub type FdPubkey = [u8; 32];
13pub type FdSignature = [u8; 64];
14pub type FdBlake3Hash = [u8; 64];
15
16#[derive(Debug, Clone)]
18pub struct BlockParseResult {
19 pub block_hash: [u8; 64], pub block_producer: [u8; 32], pub transactions: Vec<Vec<u8>>, }
23
24#[derive(Debug, thiserror::Error)]
26pub enum BlockParseError {
27 #[error("Invalid block structure: {0}")]
28 InvalidBlockStructure(String),
29 #[error("Blake3 hash computation failed: {0}")]
30 HashComputationFailed(String),
31 #[error("Header signature verification failed: {0}")]
32 HeaderSignatureInvalid(String),
33 #[error("Block signature verification failed: {0}")]
34 BlockSignatureInvalid(String),
35 #[error("Ed25519 key error: {0}")]
36 Ed25519KeyError(String),
37 #[error("Account extraction failed: {0}")]
38 AccountExtractionFailed(String),
39}
40
41#[repr(C)]
42#[derive(Clone, Copy, Debug)]
43pub struct TnBlockHeader {
44 pub block_header_sig: FdSignature,
45 pub body: TnBlockHeaderBody,
46}
47
48#[repr(C)]
49#[derive(Clone, Copy, Debug)]
50pub struct TnBlockHeaderBody {
51 pub block_version: u8,
52 pub padding: [u8; 7],
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 block_time_ns: u64,
63}
64
65#[repr(C)]
66#[derive(Clone, Copy, Debug)]
67pub struct TnBlockFooter {
68 pub body: TnBlockFooterBody,
69 pub block_hash: FdBlake3Hash,
70 pub block_sig: FdSignature,
71}
72
73#[repr(C)]
74#[derive(Clone, Copy, Debug)]
75pub struct TnBlockFooterBody {
76 pub attestor_payment: u64,
77}
78
79pub struct BlockParser;
81
82impl BlockParser {
83 pub fn parse_block(data: &[u8]) -> Result<BlockParseResult, BlockParseError> {
85 if data.is_empty() {
86 return Ok(BlockParseResult {
87 block_hash: [0u8; 64],
88 block_producer: [0u8; 32],
89 transactions: Vec::new(),
90 });
91 }
92
93 debug!(
94 "Parsing block data of {} bytes with cryptographic verification",
95 data.len()
96 );
97
98 let header_size = mem::size_of::<TnBlockHeader>();
100 let footer_size = mem::size_of::<TnBlockFooter>();
101
102 if data.len() < header_size + footer_size {
103 return Err(BlockParseError::InvalidBlockStructure(format!(
104 "Block too small: {} bytes, need at least {}",
105 data.len(),
106 header_size + footer_size
107 )));
108 }
109
110 let header = Self::parse_header_verified(&data[..header_size])?;
112 debug!(
113 "Parsed block header: version={}, start_slot={}, producer={}",
114 header.body.block_version,
115 header.body.start_slot,
116 tn_pubkey_to_address_string(&header.body.block_producer)
117 );
118
119 Self::verify_header_signature(&header)?;
121 debug!("Block header signature verified successfully");
122
123 let block_hash = Self::compute_block_hash(data)?;
125 debug!("Block hash computed successfully");
126
127 let footer_start = data.len() - footer_size;
129 let footer = Self::parse_footer_verified(&data[footer_start..])?;
130 debug!(
131 "Parsed block footer: attestor_payment={}",
132 footer.body.attestor_payment
133 );
134
135 Self::verify_block_signature(&block_hash, &footer, &header.body.block_producer)?;
137 debug!("Block signature verified successfully");
138
139 let transactions_data = &data[header_size..footer_start];
141 debug!(
142 "Transaction data section: {} bytes",
143 transactions_data.len()
144 );
145
146 let transactions = if transactions_data.is_empty() {
148 debug!("No transaction data in block");
149 Vec::new()
150 } else {
151 Self::parse_transactions(transactions_data)
152 .map_err(|e| BlockParseError::InvalidBlockStructure(e))?
153 };
154
155 debug!("Extracted {} transactions from block", transactions.len());
156
157 Ok(BlockParseResult {
158 block_hash,
159 block_producer: header.body.block_producer,
160 transactions,
161 })
162 }
163
164 fn compute_block_hash(data: &[u8]) -> Result<[u8; 64], BlockParseError> {
167 let footer_size = mem::size_of::<TnBlockFooter>();
168
169 if data.len() < footer_size {
170 return Err(BlockParseError::HashComputationFailed(
171 "Block too small to contain footer".to_string(),
172 ));
173 }
174
175 let sig_size = size_of::<FdSignature>();
178 let hash_size = size_of::<FdBlake3Hash>();
179 let hash_data_end = data.len() - sig_size - hash_size;
180 let hash_data = &data[..hash_data_end];
181
182 debug!(
183 "Computing Blake3 hash over {} bytes (excluding {} byte signature and {} byte hash)",
184 hash_data.len(),
185 sig_size,
186 hash_size
187 );
188
189 let mut hasher = blake3::Hasher::new();
191 hasher.update(hash_data);
192
193 let mut hash_output = [0u8; 64];
194 let mut output_reader = hasher.finalize_xof();
195 output_reader.fill(&mut hash_output);
196
197 debug!("Blake3 hash computation completed successfully");
198 Ok(hash_output)
199 }
200
201 fn verify_header_signature(header: &TnBlockHeader) -> Result<(), BlockParseError> {
204 debug!("Starting header signature verification");
205
206 if header.block_header_sig.iter().all(|&b| b == 0) {
208 debug!("Header signature is all zeros - treating as unsigned block");
209 return Err(BlockParseError::HeaderSignatureInvalid(
210 "Block header is not signed (all-zero signature)".to_string(),
211 ));
212 }
213
214 let body_size = mem::size_of::<TnBlockHeaderBody>();
217
218 let body_bytes =
220 unsafe { std::slice::from_raw_parts(&header.body as *const _ as *const u8, body_size) };
221
222 debug!(
223 "Verifying header signature over {} bytes of header body data",
224 body_bytes.len()
225 );
226
227 let signature = match Signature::from_slice(&header.block_header_sig) {
229 Ok(sig) => sig,
230 Err(e) => {
231 error!("Invalid header signature format: {}", e);
232 return Err(BlockParseError::HeaderSignatureInvalid(format!(
233 "Signature format error: {}",
234 e
235 )));
236 }
237 };
238
239 let verifying_key = VerifyingKey::from_bytes(&header.body.block_producer).map_err(|e| {
240 error!("Invalid producer public key format: {}", e);
241 BlockParseError::Ed25519KeyError(format!("Invalid producer public key: {}", e))
242 })?;
243
244 verifying_key.verify(body_bytes, &signature).map_err(|e| {
246 error!("Header signature verification failed: {}", e);
247 BlockParseError::HeaderSignatureInvalid(format!("Verification failed: {}", e))
248 })?;
249
250 debug!("Header signature verification successful");
251 Ok(())
252 }
253
254 fn verify_block_signature(
256 block_hash: &[u8; 64],
257 footer: &TnBlockFooter,
258 producer_key: &[u8; 32],
259 ) -> Result<(), BlockParseError> {
260 debug!("Starting block signature verification");
261
262 if footer.block_sig.iter().all(|&b| b == 0) {
264 debug!("Block signature is all zeros - treating as unsigned block");
265 return Err(BlockParseError::BlockSignatureInvalid(
266 "Block is not signed (all-zero signature)".to_string(),
267 ));
268 }
269
270 debug!("Verifying block signature against computed hash");
271
272 let signature = match Signature::from_slice(&footer.block_sig) {
274 Ok(sig) => sig,
275 Err(e) => {
276 error!("Invalid block signature format: {}", e);
277 return Err(BlockParseError::BlockSignatureInvalid(format!(
278 "Signature format error: {}",
279 e
280 )));
281 }
282 };
283
284 let verifying_key = VerifyingKey::from_bytes(producer_key).map_err(|e| {
285 error!("Invalid producer public key for block verification: {}", e);
286 BlockParseError::Ed25519KeyError(format!("Invalid producer public key: {}", e))
287 })?;
288
289 verifying_key.verify(block_hash, &signature).map_err(|e| {
291 error!("Block signature verification failed: {}", e);
292 BlockParseError::BlockSignatureInvalid(format!("Verification failed: {}", e))
293 })?;
294
295 debug!("Block signature verification successful");
296 Ok(())
297 }
298
299 fn parse_header_verified(data: &[u8]) -> Result<TnBlockHeader, BlockParseError> {
301 Self::parse_header(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
302 }
303
304 fn parse_footer_verified(data: &[u8]) -> Result<TnBlockFooter, BlockParseError> {
306 Self::parse_footer(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
307 }
308
309 #[deprecated(note = "Use parse_block instead")]
311 pub fn parse_block_legacy(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
312 if data.is_empty() {
313 return Ok(Vec::new());
314 }
315
316 debug!("Parsing block data of {} bytes", data.len());
317
318 let header_size = mem::size_of::<TnBlockHeader>();
320 let footer_size = mem::size_of::<TnBlockFooter>();
321
322 if data.len() < header_size + footer_size {
323 return Err(format!(
324 "Block too small: {} bytes, need at least {}",
325 data.len(),
326 header_size + footer_size
327 ));
328 }
329
330 let header = Self::parse_header(&data[..header_size])?;
332 debug!(
333 "Parsed block header: version={}, start_slot={}",
334 header.body.block_version, header.body.start_slot
335 );
336 if header.body.block_version != 1 {
337 return Err(format!(
338 "Unsupported block version: {}",
339 header.body.block_version
340 ));
341 }
342
343 let footer_start = data.len() - footer_size;
345 let footer = Self::parse_footer(&data[footer_start..])?;
346 debug!(
347 "Parsed block footer: attestor_payment={}",
348 footer.body.attestor_payment
349 );
350
351 let transactions_data = &data[header_size..footer_start];
353 debug!(
354 "Transaction data section: {} bytes",
355 transactions_data.len()
356 );
357
358 if transactions_data.is_empty() {
359 debug!("No transaction data in block");
360 return Ok(Vec::new());
361 }
362
363 let transactions = Self::parse_transactions(transactions_data)?;
365 debug!("Extracted {} transactions from block", transactions.len());
366
367 Ok(transactions)
368 }
369
370 fn parse_header(data: &[u8]) -> Result<TnBlockHeader, String> {
372 if data.len() < mem::size_of::<TnBlockHeader>() {
373 return Err("Insufficient data for block header".to_string());
374 }
375
376 let header = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockHeader) };
378
379 debug!(
380 "Block header: version={}, producer={:?}",
381 header.body.block_version,
382 tn_pubkey_to_address_string(&header.body.block_producer)
383 );
384 Ok(header)
385 }
386
387 fn parse_footer(data: &[u8]) -> Result<TnBlockFooter, String> {
389 if data.len() < mem::size_of::<TnBlockFooter>() {
390 return Err("Insufficient data for block footer".to_string());
391 }
392
393 let footer = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockFooter) };
394
395 debug!(
396 "Block footer: attestor_payment={}",
397 footer.body.attestor_payment
398 );
399 Ok(footer)
400 }
401
402 fn parse_transactions(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
404 let mut transactions = Vec::new();
405 let mut offset = 0;
406
407 while offset < data.len() {
409 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
411 if offset + wire_header_size > data.len() {
412 debug!(
413 "Remaining data too small for transaction header: {} bytes",
414 data.len() - offset
415 );
416 break;
417 }
418
419 let remaining_data = &data[offset..];
420
421 match Self::try_parse_transaction_at_offset(remaining_data) {
424 Ok((transaction_size, transaction_data)) => {
425 transactions.push(transaction_data);
426 debug!(
427 "Parsed transaction {} of size {} bytes",
428 transactions.len(),
429 transaction_size
430 );
431 offset += transaction_size;
432 }
433 Err(parse_error) => {
434 warn!(
435 "Failed to parse transaction at offset {}: {}",
436 offset, parse_error
437 );
438 return Err(parse_error);
439 }
440 }
441 }
442
443 Ok(transactions)
444 }
445
446 fn try_parse_transaction_at_offset(data: &[u8]) -> Result<(usize, Vec<u8>), String> {
448 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
450
451 if data.len() < wire_header_size {
452 return Err("Not enough data for transaction header".to_string());
453 }
454
455 let total_size = txn_lib::tn_txn_size(data).map_err(|e| e.to_string())?;
457
458 if data.len() < total_size {
459 return Err(format!(
460 "Not enough data for complete transaction: need {} bytes, have {}",
461 total_size,
462 data.len()
463 ));
464 }
465 let transaction_data = data[..total_size].to_vec();
467
468 if Transaction::from_wire(&transaction_data).is_none() {
470 return Err("Transaction::from_wire failed to parse transaction".to_string());
471 }
472 Ok((total_size, transaction_data))
473 }
474
475 pub fn extract_transaction_signature(tx_data: &[u8]) -> Result<String, String> {
477 if tx_data.len() < 64 {
478 return Err("Transaction too short to contain a signature".to_string());
479 }
480
481 let signature_bytes = &tx_data[..64];
483
484 let mut sig_array = [0u8; 64];
486 sig_array.copy_from_slice(signature_bytes);
487
488 let signature = tn_signature_to_string(&sig_array);
490
491 debug!("Extracted signature: {}", signature);
492 Ok(signature)
493 }
494
495 pub fn extract_account_mentions(
498 transactions: &[Vec<u8>],
499 ) -> Result<HashSet<String>, BlockParseError> {
500 let mut accounts = HashSet::new();
501
502 debug!(
503 "Extracting account mentions from {} transactions",
504 transactions.len()
505 );
506
507 for (i, tx_data) in transactions.iter().enumerate() {
508 match Self::extract_transaction_accounts(tx_data) {
509 Ok(tx_accounts) => {
510 debug!(
511 "Transaction {} contains {} account references: {:?}",
512 i,
513 tx_accounts.len(),
514 tx_accounts
515 );
516 accounts.extend(tx_accounts);
517 }
518 Err(e) => {
519 warn!("Failed to extract accounts from transaction {}: {}", i, e);
520 }
522 }
523 }
524
525 debug!(
526 "Extracted {} unique account addresses from block: {:?}",
527 accounts.len(),
528 accounts
529 );
530 Ok(accounts)
531 }
532
533 fn extract_transaction_accounts(tx_data: &[u8]) -> Result<Vec<String>, BlockParseError> {
536 if tx_data.len() < 176 {
537 return Err(BlockParseError::AccountExtractionFailed(
539 "Transaction too small to contain header".to_string(),
540 ));
541 }
542
543 debug!(
544 "Extracting accounts from transaction of {} bytes",
545 tx_data.len()
546 );
547 let mut accounts = Vec::new();
548
549 let fee_payer_offset = 112;
551 if tx_data.len() >= fee_payer_offset + 32 {
552 let fee_payer_pubkey: [u8; 32] = tx_data[fee_payer_offset..fee_payer_offset + 32]
553 .try_into()
554 .map_err(|_| {
555 BlockParseError::AccountExtractionFailed(
556 "Failed to convert fee_payer_pubkey to [u8; 32]".to_string(),
557 )
558 })?;
559 let fee_payer_address = tn_pubkey_to_address_string(&fee_payer_pubkey);
560 debug!(
561 "Extracted fee_payer at offset {}: {:?} -> {}",
562 fee_payer_offset,
563 &fee_payer_pubkey[..8],
564 fee_payer_address
565 );
566 accounts.push(fee_payer_address);
567 }
568
569 let program_offset = 144;
571 if tx_data.len() >= program_offset + 32 {
572 let program_pubkey: [u8; 32] = tx_data[program_offset..program_offset + 32]
573 .try_into()
574 .map_err(|_| {
575 BlockParseError::AccountExtractionFailed(
576 "Failed to convert program_pubkey to [u8; 32]".to_string(),
577 )
578 })?;
579 let program_address = tn_pubkey_to_address_string(&program_pubkey);
580 debug!(
581 "Extracted program at offset {}: {:?} -> {}",
582 program_offset,
583 &program_pubkey[..8],
584 program_address
585 );
586 accounts.push(program_address);
587 }
588
589 let header_size = 176;
592 if tx_data.len() > header_size {
593 let readwrite_accounts_cnt = u16::from_le_bytes([tx_data[66], tx_data[67]]);
595 let readonly_accounts_cnt = u16::from_le_bytes([tx_data[68], tx_data[69]]);
596
597 debug!(
598 "Transaction has {} readwrite and {} readonly accounts",
599 readwrite_accounts_cnt, readonly_accounts_cnt
600 );
601
602 let additional_accounts_count =
604 (readwrite_accounts_cnt + readonly_accounts_cnt) as usize;
605 let additional_accounts_size = additional_accounts_count * 32;
606
607 if tx_data.len() >= header_size + additional_accounts_size {
608 for i in 0..additional_accounts_count {
609 let account_offset = header_size + (i * 32);
610 let account_pubkey: [u8; 32] = tx_data[account_offset..account_offset + 32]
611 .try_into()
612 .map_err(|_| {
613 BlockParseError::AccountExtractionFailed(format!(
614 "Failed to convert account_pubkey {} to [u8; 32]",
615 i
616 ))
617 })?;
618 let account_address = tn_pubkey_to_address_string(&account_pubkey);
619 accounts.push(account_address);
620 }
621 }
622 }
623
624 Ok(accounts)
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631
632 #[test]
633 fn test_extract_transaction_signature() {
634 let mut transaction_data = vec![0u8; 100];
636 for i in 0..64 {
638 transaction_data[i] = (i % 256) as u8;
639 }
640
641 let result = BlockParser::extract_transaction_signature(&transaction_data);
642 assert!(result.is_ok());
643
644 let signature = result.unwrap();
645 assert!(!signature.is_empty());
646 assert_eq!(
648 signature.len(),
649 90,
650 "Signature should be 90 characters in ts... format"
651 );
652 assert!(
653 signature.starts_with("ts"),
654 "Signature should start with 'ts'"
655 );
656 }
657
658 #[test]
659 fn test_extract_transaction_signature_too_short() {
660 let transaction_data = vec![0u8; 32]; let result = BlockParser::extract_transaction_signature(&transaction_data);
662 assert!(result.is_err());
663 assert_eq!(
664 result.unwrap_err(),
665 "Transaction too short to contain a signature"
666 );
667 }
668
669 #[test]
670 fn test_parse_empty_block() {
671 let empty_data = vec![];
672 let result = BlockParser::parse_block(&empty_data);
673 assert!(result.is_ok());
674 let block_result = result.unwrap();
675 assert_eq!(block_result.transactions.len(), 0);
676 assert_eq!(block_result.block_hash, [0u8; 64]);
677 assert_eq!(block_result.block_producer, [0u8; 32]);
678 }
679
680 #[test]
681 fn test_parse_block_too_small() {
682 let small_data = vec![0u8; 50]; let result = BlockParser::parse_block(&small_data);
684 assert!(result.is_err());
685 let error_msg = format!("{}", result.unwrap_err());
686 assert!(error_msg.contains("Block too small"));
687 }
688
689 #[test]
690 fn test_parse_block_header_footer_only() {
691 let header_size = std::mem::size_of::<TnBlockHeader>();
693 let footer_size = std::mem::size_of::<TnBlockFooter>();
694 let mut block_data = vec![0u8; header_size + footer_size];
695
696 block_data[0] = 1; let result = BlockParser::parse_block(&block_data);
700
701 match result {
704 Ok(block_result) => {
705 assert_eq!(block_result.transactions.len(), 0);
707 assert_eq!(block_result.block_producer, [0u8; 32]);
708 assert_eq!(block_result.block_hash.len(), 64);
709 }
710 Err(error) => {
711 let error_msg = format!("{}", error);
713 assert!(
714 error_msg.contains("signature")
715 || error_msg.contains("key")
716 || error_msg.contains("Invalid")
717 );
718 }
719 }
720 }
721
722 #[test]
723 fn test_try_parse_transaction_at_offset() {
724 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
727 let mut tx_data = vec![0u8; wire_header_size];
728
729 tx_data[64] = 1; tx_data[66] = 0;
735 tx_data[67] = 0;
736 tx_data[68] = 0;
738 tx_data[69] = 0;
739 tx_data[70] = 0;
741 tx_data[71] = 0;
742
743 let result = BlockParser::try_parse_transaction_at_offset(&tx_data);
745 match result {
748 Ok((size, data)) => {
749 assert_eq!(size, wire_header_size);
750 assert_eq!(data.len(), wire_header_size);
751 }
752 Err(_) => {
753 }
756 }
757 }
758
759 #[test]
760 fn test_parse_transactions_empty_data() {
761 let empty_data = vec![];
762 let result = BlockParser::parse_transactions(&empty_data);
763 assert!(result.is_ok());
764 assert_eq!(result.unwrap().len(), 0);
765 }
766
767 #[test]
768 fn test_parse_transactions_insufficient_data() {
769 let short_data = vec![0u8; 32]; let result = BlockParser::parse_transactions(&short_data);
771 assert!(result.is_ok());
772 assert_eq!(result.unwrap().len(), 0); }
774}