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; 32];
15
16#[derive(Debug, Clone)]
18pub struct BlockParseResult {
19 pub block_hash: [u8; 32], 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 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 let body_size = mem::size_of::<TnBlockHeaderBody>();
216
217 let body_bytes =
219 unsafe { std::slice::from_raw_parts(&header.body as *const _ as *const u8, body_size) };
220
221 debug!(
222 "Verifying header signature over {} bytes of header body data",
223 body_bytes.len()
224 );
225
226 let signature = match Signature::from_slice(&header.block_header_sig) {
228 Ok(sig) => sig,
229 Err(e) => {
230 error!("Invalid header signature format: {}", e);
231 return Err(BlockParseError::HeaderSignatureInvalid(format!(
232 "Signature format error: {}",
233 e
234 )));
235 }
236 };
237
238 let verifying_key = VerifyingKey::from_bytes(&header.body.block_producer).map_err(|e| {
239 error!("Invalid producer public key format: {}", e);
240 BlockParseError::Ed25519KeyError(format!("Invalid producer public key: {}", e))
241 })?;
242
243 verifying_key.verify(body_bytes, &signature).map_err(|e| {
245 error!("Header signature verification failed: {}", e);
246 BlockParseError::HeaderSignatureInvalid(format!("Verification failed: {}", e))
247 })?;
248
249 debug!("Header signature verification successful");
250 Ok(())
251 }
252
253 fn verify_block_signature(
255 block_hash: &[u8; 32],
256 footer: &TnBlockFooter,
257 producer_key: &[u8; 32],
258 ) -> Result<(), BlockParseError> {
259 debug!("Starting block signature verification");
260
261 if footer.block_sig.iter().all(|&b| b == 0) {
263 debug!("Block signature is all zeros - treating as unsigned block");
264 return Err(BlockParseError::BlockSignatureInvalid(
265 "Block is not signed (all-zero signature)".to_string(),
266 ));
267 }
268
269 debug!("Verifying block signature against computed hash");
270
271 let signature = match Signature::from_slice(&footer.block_sig) {
273 Ok(sig) => sig,
274 Err(e) => {
275 error!("Invalid block signature format: {}", e);
276 return Err(BlockParseError::BlockSignatureInvalid(format!(
277 "Signature format error: {}",
278 e
279 )));
280 }
281 };
282
283 let verifying_key = VerifyingKey::from_bytes(producer_key).map_err(|e| {
284 error!("Invalid producer public key for block verification: {}", e);
285 BlockParseError::Ed25519KeyError(format!("Invalid producer public key: {}", e))
286 })?;
287
288 verifying_key.verify(block_hash, &signature).map_err(|e| {
290 error!("Block signature verification failed: {}", e);
291 BlockParseError::BlockSignatureInvalid(format!("Verification failed: {}", e))
292 })?;
293
294 debug!("Block signature verification successful");
295 Ok(())
296 }
297
298 fn parse_header_verified(data: &[u8]) -> Result<TnBlockHeader, BlockParseError> {
300 Self::parse_header(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
301 }
302
303 fn parse_footer_verified(data: &[u8]) -> Result<TnBlockFooter, BlockParseError> {
305 Self::parse_footer(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
306 }
307
308 #[deprecated(note = "Use parse_block instead")]
310 pub fn parse_block_legacy(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
311 if data.is_empty() {
312 return Ok(Vec::new());
313 }
314
315 debug!("Parsing block data of {} bytes", data.len());
316
317 let header_size = mem::size_of::<TnBlockHeader>();
319 let footer_size = mem::size_of::<TnBlockFooter>();
320
321 if data.len() < header_size + footer_size {
322 return Err(format!(
323 "Block too small: {} bytes, need at least {}",
324 data.len(),
325 header_size + footer_size
326 ));
327 }
328
329 let header = Self::parse_header(&data[..header_size])?;
331 debug!(
332 "Parsed block header: version={}, start_slot={}",
333 header.body.block_version, header.body.start_slot
334 );
335 if header.body.block_version != 1 {
336 return Err(format!(
337 "Unsupported block version: {}",
338 header.body.block_version
339 ));
340 }
341
342 let footer_start = data.len() - footer_size;
344 let footer = Self::parse_footer(&data[footer_start..])?;
345 debug!(
346 "Parsed block footer: attestor_payment={}",
347 footer.body.attestor_payment
348 );
349
350 let transactions_data = &data[header_size..footer_start];
352 debug!(
353 "Transaction data section: {} bytes",
354 transactions_data.len()
355 );
356
357 if transactions_data.is_empty() {
358 debug!("No transaction data in block");
359 return Ok(Vec::new());
360 }
361
362 let transactions = Self::parse_transactions(transactions_data)?;
364 debug!("Extracted {} transactions from block", transactions.len());
365
366 Ok(transactions)
367 }
368
369 fn parse_header(data: &[u8]) -> Result<TnBlockHeader, String> {
371 if data.len() < mem::size_of::<TnBlockHeader>() {
372 return Err("Insufficient data for block header".to_string());
373 }
374
375 let header = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockHeader) };
377
378 debug!(
379 "Block header: version={}, producer={:?}",
380 header.body.block_version,
381 tn_pubkey_to_address_string(&header.body.block_producer)
382 );
383 Ok(header)
384 }
385
386 fn parse_footer(data: &[u8]) -> Result<TnBlockFooter, String> {
388 if data.len() < mem::size_of::<TnBlockFooter>() {
389 return Err("Insufficient data for block footer".to_string());
390 }
391
392 let footer = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockFooter) };
393
394 debug!(
395 "Block footer: attestor_payment={}",
396 footer.body.attestor_payment
397 );
398 Ok(footer)
399 }
400
401 fn parse_transactions(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
403 let mut transactions = Vec::new();
404 let mut offset = 0;
405
406 while offset < data.len() {
408 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
410 if offset + wire_header_size > data.len() {
411 debug!(
412 "Remaining data too small for transaction header: {} bytes",
413 data.len() - offset
414 );
415 break;
416 }
417
418 let remaining_data = &data[offset..];
419
420 match Self::try_parse_transaction_at_offset(remaining_data) {
423 Ok((transaction_size, transaction_data)) => {
424 transactions.push(transaction_data);
425 debug!(
426 "Parsed transaction {} of size {} bytes",
427 transactions.len(),
428 transaction_size
429 );
430 offset += transaction_size;
431 }
432 Err(parse_error) => {
433 warn!(
434 "Failed to parse transaction at offset {}: {}",
435 offset, parse_error
436 );
437 return Err(parse_error);
438 }
439 }
440 }
441
442 Ok(transactions)
443 }
444
445 fn try_parse_transaction_at_offset(data: &[u8]) -> Result<(usize, Vec<u8>), String> {
447 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
449
450 if data.len() < wire_header_size {
451 return Err("Not enough data for transaction header".to_string());
452 }
453
454 let total_size = txn_lib::tn_txn_size(data).map_err(|e| e.to_string())?;
456
457 if data.len() < total_size {
458 return Err(format!(
459 "Not enough data for complete transaction: need {} bytes, have {}",
460 total_size,
461 data.len()
462 ));
463 }
464 let transaction_data = data[..total_size].to_vec();
466
467 if Transaction::from_wire(&transaction_data).is_none() {
469 return Err("Transaction::from_wire failed to parse transaction".to_string());
470 }
471 Ok((total_size, transaction_data))
472 }
473
474 pub fn extract_transaction_signature(tx_data: &[u8]) -> Result<String, String> {
477 if tx_data.len() < txn_lib::TN_TXN_SIGNATURE_SZ {
478 return Err("Transaction too short to contain a signature".to_string());
479 }
480
481 let sig_start = tx_data.len() - txn_lib::TN_TXN_SIGNATURE_SZ;
483 let signature_bytes = &tx_data[sig_start..];
484
485 let mut sig_array = [0u8; 64];
487 sig_array.copy_from_slice(signature_bytes);
488
489 let signature = tn_signature_to_string(&sig_array);
491
492 debug!("Extracted signature: {}", signature);
493 Ok(signature)
494 }
495
496 pub fn extract_account_mentions(
499 transactions: &[Vec<u8>],
500 ) -> Result<HashSet<String>, BlockParseError> {
501 let mut accounts = HashSet::new();
502
503 debug!(
504 "Extracting account mentions from {} transactions",
505 transactions.len()
506 );
507
508 for (i, tx_data) in transactions.iter().enumerate() {
509 match Self::extract_transaction_accounts(tx_data) {
510 Ok(tx_accounts) => {
511 debug!(
512 "Transaction {} contains {} account references: {:?}",
513 i,
514 tx_accounts.len(),
515 tx_accounts
516 );
517 accounts.extend(tx_accounts);
518 }
519 Err(e) => {
520 warn!("Failed to extract accounts from transaction {}: {}", i, e);
521 }
523 }
524 }
525
526 debug!(
527 "Extracted {} unique account addresses from block: {:?}",
528 accounts.len(),
529 accounts
530 );
531 Ok(accounts)
532 }
533
534 fn extract_transaction_accounts(tx_data: &[u8]) -> Result<Vec<String>, BlockParseError> {
537 let header_size = 112;
539 let signature_size = txn_lib::TN_TXN_SIGNATURE_SZ;
540 let min_txn_size = header_size + signature_size;
541 if tx_data.len() < min_txn_size {
542 return Err(BlockParseError::AccountExtractionFailed(
543 format!("Transaction too small: {} bytes, need at least {} (header={}, signature={})",
544 tx_data.len(), min_txn_size, header_size, signature_size),
545 ));
546 }
547
548 debug!(
549 "Extracting accounts from transaction of {} bytes",
550 tx_data.len()
551 );
552 let mut accounts = Vec::new();
553
554 let fee_payer_offset = 48;
556 if tx_data.len() >= fee_payer_offset + 32 {
557 let fee_payer_pubkey: [u8; 32] = tx_data[fee_payer_offset..fee_payer_offset + 32]
558 .try_into()
559 .map_err(|_| {
560 BlockParseError::AccountExtractionFailed(
561 "Failed to convert fee_payer_pubkey to [u8; 32]".to_string(),
562 )
563 })?;
564 let fee_payer_address = tn_pubkey_to_address_string(&fee_payer_pubkey);
565 debug!(
566 "Extracted fee_payer at offset {}: {:?} -> {}",
567 fee_payer_offset,
568 &fee_payer_pubkey[..8],
569 fee_payer_address
570 );
571 accounts.push(fee_payer_address);
572 }
573
574 let program_offset = 80;
576 if tx_data.len() >= program_offset + 32 {
577 let program_pubkey: [u8; 32] = tx_data[program_offset..program_offset + 32]
578 .try_into()
579 .map_err(|_| {
580 BlockParseError::AccountExtractionFailed(
581 "Failed to convert program_pubkey to [u8; 32]".to_string(),
582 )
583 })?;
584 let program_address = tn_pubkey_to_address_string(&program_pubkey);
585 debug!(
586 "Extracted program at offset {}: {:?} -> {}",
587 program_offset,
588 &program_pubkey[..8],
589 program_address
590 );
591 accounts.push(program_address);
592 }
593
594 let header_size = 112;
597 if tx_data.len() > header_size {
598 let readwrite_accounts_cnt = u16::from_le_bytes([tx_data[2], tx_data[3]]);
600 let readonly_accounts_cnt = u16::from_le_bytes([tx_data[4], tx_data[5]]);
601
602 debug!(
603 "Transaction has {} readwrite and {} readonly accounts",
604 readwrite_accounts_cnt, readonly_accounts_cnt
605 );
606
607 let additional_accounts_count =
609 (readwrite_accounts_cnt + readonly_accounts_cnt) as usize;
610 let additional_accounts_size = additional_accounts_count * 32;
611
612 if tx_data.len() >= header_size + additional_accounts_size {
613 for i in 0..additional_accounts_count {
614 let account_offset = header_size + (i * 32);
615 let account_pubkey: [u8; 32] = tx_data[account_offset..account_offset + 32]
616 .try_into()
617 .map_err(|_| {
618 BlockParseError::AccountExtractionFailed(format!(
619 "Failed to convert account_pubkey {} to [u8; 32]",
620 i
621 ))
622 })?;
623 let account_address = tn_pubkey_to_address_string(&account_pubkey);
624 accounts.push(account_address);
625 }
626 }
627 }
628
629 Ok(accounts)
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn test_extract_transaction_signature() {
639 let mut transaction_data = vec![0u8; 100];
641 let sig_start = transaction_data.len() - 64;
643 for i in 0..64 {
644 transaction_data[sig_start + i] = (i % 256) as u8;
645 }
646
647 let result = BlockParser::extract_transaction_signature(&transaction_data);
648 assert!(result.is_ok());
649
650 let signature = result.unwrap();
651 assert!(!signature.is_empty());
652 assert_eq!(
654 signature.len(),
655 90,
656 "Signature should be 90 characters in ts... format"
657 );
658 assert!(
659 signature.starts_with("ts"),
660 "Signature should start with 'ts'"
661 );
662 }
663
664 #[test]
665 fn test_extract_transaction_signature_too_short() {
666 let transaction_data = vec![0u8; 32]; let result = BlockParser::extract_transaction_signature(&transaction_data);
668 assert!(result.is_err());
669 assert_eq!(
670 result.unwrap_err(),
671 "Transaction too short to contain a signature"
672 );
673 }
674
675 #[test]
676 fn test_parse_empty_block() {
677 let empty_data = vec![];
678 let result = BlockParser::parse_block(&empty_data);
679 assert!(result.is_ok());
680 let block_result = result.unwrap();
681 assert_eq!(block_result.transactions.len(), 0);
682 assert_eq!(block_result.block_hash, [0u8; 32]);
683 assert_eq!(block_result.block_producer, [0u8; 32]);
684 }
685
686 #[test]
687 fn test_parse_block_too_small() {
688 let small_data = vec![0u8; 50]; let result = BlockParser::parse_block(&small_data);
690 assert!(result.is_err());
691 let error_msg = format!("{}", result.unwrap_err());
692 assert!(error_msg.contains("Block too small"));
693 }
694
695 #[test]
696 fn test_parse_block_header_footer_only() {
697 let header_size = std::mem::size_of::<TnBlockHeader>();
699 let footer_size = std::mem::size_of::<TnBlockFooter>();
700 let mut block_data = vec![0u8; header_size + footer_size];
701
702 block_data[0] = 1; let result = BlockParser::parse_block(&block_data);
706
707 match result {
710 Ok(block_result) => {
711 assert_eq!(block_result.transactions.len(), 0);
713 assert_eq!(block_result.block_producer, [0u8; 32]);
714 assert_eq!(block_result.block_hash.len(), 32);
715 }
716 Err(error) => {
717 let error_msg = format!("{}", error);
719 assert!(
720 error_msg.contains("signature")
721 || error_msg.contains("key")
722 || error_msg.contains("Invalid")
723 );
724 }
725 }
726 }
727
728 #[test]
729 fn test_try_parse_transaction_at_offset() {
730 let header_size = 112;
741 let signature_size = 64;
742 let min_txn_size = header_size + signature_size;
743 let mut tx_data = vec![0u8; min_txn_size];
744
745 tx_data[0] = 1; tx_data[2] = 0;
751 tx_data[3] = 0;
752 tx_data[4] = 0;
754 tx_data[5] = 0;
755 tx_data[6] = 0;
757 tx_data[7] = 0;
758
759 let result = BlockParser::try_parse_transaction_at_offset(&tx_data);
761 match result {
764 Ok((size, data)) => {
765 assert_eq!(size, min_txn_size);
766 assert_eq!(data.len(), min_txn_size);
767 }
768 Err(_) => {
769 }
772 }
773 }
774
775 #[test]
776 fn test_parse_transactions_empty_data() {
777 let empty_data = vec![];
778 let result = BlockParser::parse_transactions(&empty_data);
779 assert!(result.is_ok());
780 assert_eq!(result.unwrap().len(), 0);
781 }
782
783 #[test]
784 fn test_parse_transactions_insufficient_data() {
785 let short_data = vec![0u8; 32]; let result = BlockParser::parse_transactions(&short_data);
787 assert!(result.is_ok());
788 assert_eq!(result.unwrap().len(), 0); }
790}