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> {
476 if tx_data.len() < 64 {
477 return Err("Transaction too short to contain a signature".to_string());
478 }
479
480 let signature_bytes = &tx_data[..64];
482
483 let mut sig_array = [0u8; 64];
485 sig_array.copy_from_slice(signature_bytes);
486
487 let signature = tn_signature_to_string(&sig_array);
489
490 debug!("Extracted signature: {}", signature);
491 Ok(signature)
492 }
493
494 pub fn extract_account_mentions(
497 transactions: &[Vec<u8>],
498 ) -> Result<HashSet<String>, BlockParseError> {
499 let mut accounts = HashSet::new();
500
501 debug!(
502 "Extracting account mentions from {} transactions",
503 transactions.len()
504 );
505
506 for (i, tx_data) in transactions.iter().enumerate() {
507 match Self::extract_transaction_accounts(tx_data) {
508 Ok(tx_accounts) => {
509 debug!(
510 "Transaction {} contains {} account references: {:?}",
511 i,
512 tx_accounts.len(),
513 tx_accounts
514 );
515 accounts.extend(tx_accounts);
516 }
517 Err(e) => {
518 warn!("Failed to extract accounts from transaction {}: {}", i, e);
519 }
521 }
522 }
523
524 debug!(
525 "Extracted {} unique account addresses from block: {:?}",
526 accounts.len(),
527 accounts
528 );
529 Ok(accounts)
530 }
531
532 fn extract_transaction_accounts(tx_data: &[u8]) -> Result<Vec<String>, BlockParseError> {
535 if tx_data.len() < 176 {
536 return Err(BlockParseError::AccountExtractionFailed(
538 "Transaction too small to contain header".to_string(),
539 ));
540 }
541
542 debug!(
543 "Extracting accounts from transaction of {} bytes",
544 tx_data.len()
545 );
546 let mut accounts = Vec::new();
547
548 let fee_payer_offset = 112;
550 if tx_data.len() >= fee_payer_offset + 32 {
551 let fee_payer_pubkey: [u8; 32] = tx_data[fee_payer_offset..fee_payer_offset + 32]
552 .try_into()
553 .map_err(|_| {
554 BlockParseError::AccountExtractionFailed(
555 "Failed to convert fee_payer_pubkey to [u8; 32]".to_string(),
556 )
557 })?;
558 let fee_payer_address = tn_pubkey_to_address_string(&fee_payer_pubkey);
559 debug!(
560 "Extracted fee_payer at offset {}: {:?} -> {}",
561 fee_payer_offset,
562 &fee_payer_pubkey[..8],
563 fee_payer_address
564 );
565 accounts.push(fee_payer_address);
566 }
567
568 let program_offset = 144;
570 if tx_data.len() >= program_offset + 32 {
571 let program_pubkey: [u8; 32] = tx_data[program_offset..program_offset + 32]
572 .try_into()
573 .map_err(|_| {
574 BlockParseError::AccountExtractionFailed(
575 "Failed to convert program_pubkey to [u8; 32]".to_string(),
576 )
577 })?;
578 let program_address = tn_pubkey_to_address_string(&program_pubkey);
579 debug!(
580 "Extracted program at offset {}: {:?} -> {}",
581 program_offset,
582 &program_pubkey[..8],
583 program_address
584 );
585 accounts.push(program_address);
586 }
587
588 let header_size = 176;
591 if tx_data.len() > header_size {
592 let readwrite_accounts_cnt = u16::from_le_bytes([tx_data[66], tx_data[67]]);
594 let readonly_accounts_cnt = u16::from_le_bytes([tx_data[68], tx_data[69]]);
595
596 debug!(
597 "Transaction has {} readwrite and {} readonly accounts",
598 readwrite_accounts_cnt, readonly_accounts_cnt
599 );
600
601 let additional_accounts_count =
603 (readwrite_accounts_cnt + readonly_accounts_cnt) as usize;
604 let additional_accounts_size = additional_accounts_count * 32;
605
606 if tx_data.len() >= header_size + additional_accounts_size {
607 for i in 0..additional_accounts_count {
608 let account_offset = header_size + (i * 32);
609 let account_pubkey: [u8; 32] = tx_data[account_offset..account_offset + 32]
610 .try_into()
611 .map_err(|_| {
612 BlockParseError::AccountExtractionFailed(format!(
613 "Failed to convert account_pubkey {} to [u8; 32]",
614 i
615 ))
616 })?;
617 let account_address = tn_pubkey_to_address_string(&account_pubkey);
618 accounts.push(account_address);
619 }
620 }
621 }
622
623 Ok(accounts)
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630
631 #[test]
632 fn test_extract_transaction_signature() {
633 let mut transaction_data = vec![0u8; 100];
635 for i in 0..64 {
637 transaction_data[i] = (i % 256) as u8;
638 }
639
640 let result = BlockParser::extract_transaction_signature(&transaction_data);
641 assert!(result.is_ok());
642
643 let signature = result.unwrap();
644 assert!(!signature.is_empty());
645 assert_eq!(
647 signature.len(),
648 90,
649 "Signature should be 90 characters in ts... format"
650 );
651 assert!(
652 signature.starts_with("ts"),
653 "Signature should start with 'ts'"
654 );
655 }
656
657 #[test]
658 fn test_extract_transaction_signature_too_short() {
659 let transaction_data = vec![0u8; 32]; let result = BlockParser::extract_transaction_signature(&transaction_data);
661 assert!(result.is_err());
662 assert_eq!(
663 result.unwrap_err(),
664 "Transaction too short to contain a signature"
665 );
666 }
667
668 #[test]
669 fn test_parse_empty_block() {
670 let empty_data = vec![];
671 let result = BlockParser::parse_block(&empty_data);
672 assert!(result.is_ok());
673 let block_result = result.unwrap();
674 assert_eq!(block_result.transactions.len(), 0);
675 assert_eq!(block_result.block_hash, [0u8; 32]);
676 assert_eq!(block_result.block_producer, [0u8; 32]);
677 }
678
679 #[test]
680 fn test_parse_block_too_small() {
681 let small_data = vec![0u8; 50]; let result = BlockParser::parse_block(&small_data);
683 assert!(result.is_err());
684 let error_msg = format!("{}", result.unwrap_err());
685 assert!(error_msg.contains("Block too small"));
686 }
687
688 #[test]
689 fn test_parse_block_header_footer_only() {
690 let header_size = std::mem::size_of::<TnBlockHeader>();
692 let footer_size = std::mem::size_of::<TnBlockFooter>();
693 let mut block_data = vec![0u8; header_size + footer_size];
694
695 block_data[0] = 1; let result = BlockParser::parse_block(&block_data);
699
700 match result {
703 Ok(block_result) => {
704 assert_eq!(block_result.transactions.len(), 0);
706 assert_eq!(block_result.block_producer, [0u8; 32]);
707 assert_eq!(block_result.block_hash.len(), 32);
708 }
709 Err(error) => {
710 let error_msg = format!("{}", error);
712 assert!(
713 error_msg.contains("signature")
714 || error_msg.contains("key")
715 || error_msg.contains("Invalid")
716 );
717 }
718 }
719 }
720
721 #[test]
722 fn test_try_parse_transaction_at_offset() {
723 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
726 let mut tx_data = vec![0u8; wire_header_size];
727
728 tx_data[64] = 1; tx_data[66] = 0;
734 tx_data[67] = 0;
735 tx_data[68] = 0;
737 tx_data[69] = 0;
738 tx_data[70] = 0;
740 tx_data[71] = 0;
741
742 let result = BlockParser::try_parse_transaction_at_offset(&tx_data);
744 match result {
747 Ok((size, data)) => {
748 assert_eq!(size, wire_header_size);
749 assert_eq!(data.len(), wire_header_size);
750 }
751 Err(_) => {
752 }
755 }
756 }
757
758 #[test]
759 fn test_parse_transactions_empty_data() {
760 let empty_data = vec![];
761 let result = BlockParser::parse_transactions(&empty_data);
762 assert!(result.is_ok());
763 assert_eq!(result.unwrap().len(), 0);
764 }
765
766 #[test]
767 fn test_parse_transactions_insufficient_data() {
768 let short_data = vec![0u8; 32]; let result = BlockParser::parse_transactions(&short_data);
770 assert!(result.is_ok());
771 assert_eq!(result.unwrap().len(), 0); }
773}