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; 5],
53 pub chain_id: u16,
54 pub block_producer: FdPubkey,
55 pub bond_amount_lock_up: u64,
56 pub expiry_timestamp: u64,
57 pub start_slot: u64,
58 pub expiry_after: u32,
59 pub max_block_size: u32,
60 pub max_compute_units: u64,
61 pub max_state_units: u32,
62 pub reserved: [u8; 4],
63 pub weight_slot: u64,
64 pub block_time_ns: u64,
65}
66
67#[repr(C)]
68#[derive(Clone, Copy, Debug)]
69pub struct TnBlockFooter {
70 pub body: TnBlockFooterBody,
71 pub block_hash: FdBlake3Hash,
72 pub block_sig: FdSignature,
73}
74
75#[repr(C)]
76#[derive(Clone, Copy, Debug)]
77pub struct TnBlockFooterBody {
78 pub attestor_payment: u64,
79}
80
81pub struct BlockParser;
83
84impl BlockParser {
85 pub fn parse_block(data: &[u8]) -> Result<BlockParseResult, BlockParseError> {
87 if data.is_empty() {
88 return Ok(BlockParseResult {
89 block_hash: [0u8; 32],
90 block_producer: [0u8; 32],
91 transactions: Vec::new(),
92 });
93 }
94
95 debug!(
96 "Parsing block data of {} bytes with cryptographic verification",
97 data.len()
98 );
99
100 let header_size = mem::size_of::<TnBlockHeader>();
102 let footer_size = mem::size_of::<TnBlockFooter>();
103
104 if data.len() < header_size + footer_size {
105 return Err(BlockParseError::InvalidBlockStructure(format!(
106 "Block too small: {} bytes, need at least {}",
107 data.len(),
108 header_size + footer_size
109 )));
110 }
111
112 let header = Self::parse_header_verified(&data[..header_size])?;
114 debug!(
115 "Parsed block header: version={}, start_slot={}, producer={}",
116 header.body.block_version,
117 header.body.start_slot,
118 tn_pubkey_to_address_string(&header.body.block_producer)
119 );
120
121 Self::verify_header_signature(&header)?;
123 debug!("Block header signature verified successfully");
124
125 let block_hash = Self::compute_block_hash(data)?;
127 debug!("Block hash computed successfully");
128
129 let footer_start = data.len() - footer_size;
131 let footer = Self::parse_footer_verified(&data[footer_start..])?;
132 debug!(
133 "Parsed block footer: attestor_payment={}",
134 footer.body.attestor_payment
135 );
136
137 Self::verify_block_signature(&block_hash, &footer, &header.body.block_producer)?;
139 debug!("Block signature verified successfully");
140
141 let transactions_data = &data[header_size..footer_start];
143 debug!(
144 "Transaction data section: {} bytes",
145 transactions_data.len()
146 );
147
148 let transactions = if transactions_data.is_empty() {
150 debug!("No transaction data in block");
151 Vec::new()
152 } else {
153 Self::parse_transactions(transactions_data)
154 .map_err(|e| BlockParseError::InvalidBlockStructure(e))?
155 };
156
157 debug!("Extracted {} transactions from block", transactions.len());
158
159 Ok(BlockParseResult {
160 block_hash,
161 block_producer: header.body.block_producer,
162 transactions,
163 })
164 }
165
166 fn compute_block_hash(data: &[u8]) -> Result<[u8; 32], BlockParseError> {
169 let footer_size = mem::size_of::<TnBlockFooter>();
170
171 if data.len() < footer_size {
172 return Err(BlockParseError::HashComputationFailed(
173 "Block too small to contain footer".to_string(),
174 ));
175 }
176
177 let sig_size = mem::size_of::<FdSignature>();
180 let hash_size = mem::size_of::<FdBlake3Hash>();
181 let hash_data_end = data.len() - sig_size - hash_size;
182 let hash_data = &data[..hash_data_end];
183
184 debug!(
185 "Computing Blake3 hash over {} bytes (excluding {} byte signature and {} byte hash)",
186 hash_data.len(),
187 sig_size,
188 hash_size
189 );
190
191 let mut hasher = blake3::Hasher::new();
193 hasher.update(hash_data);
194
195 let hash_output = *hasher.finalize().as_bytes();
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; 32],
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 fn parse_header(data: &[u8]) -> Result<TnBlockHeader, String> {
311 if data.len() < mem::size_of::<TnBlockHeader>() {
312 return Err("Insufficient data for block header".to_string());
313 }
314
315 let header = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockHeader) };
317
318 debug!(
319 "Block header: version={}, producer={:?}",
320 header.body.block_version,
321 tn_pubkey_to_address_string(&header.body.block_producer)
322 );
323 Ok(header)
324 }
325
326 fn parse_footer(data: &[u8]) -> Result<TnBlockFooter, String> {
328 if data.len() < mem::size_of::<TnBlockFooter>() {
329 return Err("Insufficient data for block footer".to_string());
330 }
331
332 let footer = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockFooter) };
333
334 debug!(
335 "Block footer: attestor_payment={}",
336 footer.body.attestor_payment
337 );
338 Ok(footer)
339 }
340
341 fn parse_transactions(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
343 let mut transactions = Vec::new();
344 let mut offset = 0;
345
346 while offset < data.len() {
348 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
350 if offset + wire_header_size > data.len() {
351 debug!(
352 "Remaining data too small for transaction header: {} bytes",
353 data.len() - offset
354 );
355 break;
356 }
357
358 let remaining_data = &data[offset..];
359
360 match Self::try_parse_transaction_at_offset(remaining_data) {
363 Ok((transaction_size, transaction_data)) => {
364 transactions.push(transaction_data);
365 debug!(
366 "Parsed transaction {} of size {} bytes",
367 transactions.len(),
368 transaction_size
369 );
370 offset += transaction_size;
371 }
372 Err(parse_error) => {
373 warn!(
374 "Failed to parse transaction at offset {}: {}",
375 offset, parse_error
376 );
377 return Err(parse_error);
378 }
379 }
380 }
381
382 Ok(transactions)
383 }
384
385 fn try_parse_transaction_at_offset(data: &[u8]) -> Result<(usize, Vec<u8>), String> {
387 let wire_header_size = mem::size_of::<WireTxnHdrV1>();
389
390 if data.len() < wire_header_size {
391 return Err("Not enough data for transaction header".to_string());
392 }
393
394 let total_size = txn_lib::tn_txn_size(data).map_err(|e| e.to_string())?;
396
397 if data.len() < total_size {
398 return Err(format!(
399 "Not enough data for complete transaction: need {} bytes, have {}",
400 total_size,
401 data.len()
402 ));
403 }
404 let transaction_data = data[..total_size].to_vec();
406
407 if Transaction::from_wire(&transaction_data).is_none() {
409 return Err("Transaction::from_wire failed to parse transaction".to_string());
410 }
411 Ok((total_size, transaction_data))
412 }
413
414 pub fn extract_transaction_signature(tx_data: &[u8]) -> Result<String, String> {
417 if tx_data.len() < txn_lib::TN_TXN_SIGNATURE_SZ {
418 return Err("Transaction too short to contain a signature".to_string());
419 }
420
421 let sig_start = tx_data.len() - txn_lib::TN_TXN_SIGNATURE_SZ;
423 let signature_bytes = &tx_data[sig_start..];
424
425 let mut sig_array = [0u8; 64];
427 sig_array.copy_from_slice(signature_bytes);
428
429 let signature = tn_signature_to_string(&sig_array);
431
432 debug!("Extracted signature: {}", signature);
433 Ok(signature)
434 }
435
436 pub fn extract_account_mentions(
439 transactions: &[Vec<u8>],
440 ) -> Result<HashSet<String>, BlockParseError> {
441 let mut accounts = HashSet::new();
442
443 debug!(
444 "Extracting account mentions from {} transactions",
445 transactions.len()
446 );
447
448 for (i, tx_data) in transactions.iter().enumerate() {
449 match Self::extract_transaction_accounts(tx_data) {
450 Ok(tx_accounts) => {
451 debug!(
452 "Transaction {} contains {} account references: {:?}",
453 i,
454 tx_accounts.len(),
455 tx_accounts
456 );
457 accounts.extend(tx_accounts);
458 }
459 Err(e) => {
460 warn!("Failed to extract accounts from transaction {}: {}", i, e);
461 }
463 }
464 }
465
466 debug!(
467 "Extracted {} unique account addresses from block: {:?}",
468 accounts.len(),
469 accounts
470 );
471 Ok(accounts)
472 }
473
474 fn extract_transaction_accounts(tx_data: &[u8]) -> Result<Vec<String>, BlockParseError> {
477 let header_size = 112;
479 let signature_size = txn_lib::TN_TXN_SIGNATURE_SZ;
480 let min_txn_size = header_size + signature_size;
481 if tx_data.len() < min_txn_size {
482 return Err(BlockParseError::AccountExtractionFailed(format!(
483 "Transaction too small: {} bytes, need at least {} (header={}, signature={})",
484 tx_data.len(),
485 min_txn_size,
486 header_size,
487 signature_size
488 )));
489 }
490
491 debug!(
492 "Extracting accounts from transaction of {} bytes",
493 tx_data.len()
494 );
495 let mut accounts = Vec::new();
496
497 let fee_payer_offset = 48;
499 if tx_data.len() >= fee_payer_offset + 32 {
500 let fee_payer_pubkey: [u8; 32] = tx_data[fee_payer_offset..fee_payer_offset + 32]
501 .try_into()
502 .map_err(|_| {
503 BlockParseError::AccountExtractionFailed(
504 "Failed to convert fee_payer_pubkey to [u8; 32]".to_string(),
505 )
506 })?;
507 let fee_payer_address = tn_pubkey_to_address_string(&fee_payer_pubkey);
508 debug!(
509 "Extracted fee_payer at offset {}: {:?} -> {}",
510 fee_payer_offset,
511 &fee_payer_pubkey[..8],
512 fee_payer_address
513 );
514 accounts.push(fee_payer_address);
515 }
516
517 let program_offset = 80;
519 if tx_data.len() >= program_offset + 32 {
520 let program_pubkey: [u8; 32] = tx_data[program_offset..program_offset + 32]
521 .try_into()
522 .map_err(|_| {
523 BlockParseError::AccountExtractionFailed(
524 "Failed to convert program_pubkey to [u8; 32]".to_string(),
525 )
526 })?;
527 let program_address = tn_pubkey_to_address_string(&program_pubkey);
528 debug!(
529 "Extracted program at offset {}: {:?} -> {}",
530 program_offset,
531 &program_pubkey[..8],
532 program_address
533 );
534 accounts.push(program_address);
535 }
536
537 let header_size = 112;
540 if tx_data.len() > header_size {
541 let readwrite_accounts_cnt = u16::from_le_bytes([tx_data[2], tx_data[3]]);
543 let readonly_accounts_cnt = u16::from_le_bytes([tx_data[4], tx_data[5]]);
544
545 debug!(
546 "Transaction has {} readwrite and {} readonly accounts",
547 readwrite_accounts_cnt, readonly_accounts_cnt
548 );
549
550 let additional_accounts_count =
552 (readwrite_accounts_cnt + readonly_accounts_cnt) as usize;
553 let additional_accounts_size = additional_accounts_count * 32;
554
555 if tx_data.len() >= header_size + additional_accounts_size {
556 for i in 0..additional_accounts_count {
557 let account_offset = header_size + (i * 32);
558 let account_pubkey: [u8; 32] = tx_data[account_offset..account_offset + 32]
559 .try_into()
560 .map_err(|_| {
561 BlockParseError::AccountExtractionFailed(format!(
562 "Failed to convert account_pubkey {} to [u8; 32]",
563 i
564 ))
565 })?;
566 let account_address = tn_pubkey_to_address_string(&account_pubkey);
567 accounts.push(account_address);
568 }
569 }
570 }
571
572 Ok(accounts)
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn test_block_header_body_chain_id_layout() {
582 let body = TnBlockHeaderBody {
583 block_version: 1,
584 padding: [0; 5],
585 chain_id: 0x1234,
586 block_producer: [0; 32],
587 bond_amount_lock_up: 0,
588 expiry_timestamp: 0,
589 start_slot: 0,
590 expiry_after: 0,
591 max_block_size: 0,
592 max_compute_units: 0,
593 max_state_units: 0,
594 reserved: [0; 4],
595 weight_slot: 0,
596 block_time_ns: 0,
597 };
598
599 let base = &body as *const _ as usize;
600 let chain_id = &body.chain_id as *const _ as usize;
601 assert_eq!(chain_id - base, 6);
602 assert_eq!(body.chain_id, 0x1234);
603 assert_eq!(std::mem::size_of::<TnBlockHeaderBody>(), 104);
604 }
605
606 #[test]
607 fn test_extract_transaction_signature() {
608 let mut transaction_data = vec![0u8; 100];
610 let sig_start = transaction_data.len() - 64;
612 for i in 0..64 {
613 transaction_data[sig_start + i] = (i % 256) as u8;
614 }
615
616 let result = BlockParser::extract_transaction_signature(&transaction_data);
617 assert!(result.is_ok());
618
619 let signature = result.unwrap();
620 assert!(!signature.is_empty());
621 assert_eq!(
623 signature.len(),
624 90,
625 "Signature should be 90 characters in ts... format"
626 );
627 assert!(
628 signature.starts_with("ts"),
629 "Signature should start with 'ts'"
630 );
631 }
632
633 #[test]
634 fn test_extract_transaction_signature_too_short() {
635 let transaction_data = vec![0u8; 32]; let result = BlockParser::extract_transaction_signature(&transaction_data);
637 assert!(result.is_err());
638 assert_eq!(
639 result.unwrap_err(),
640 "Transaction too short to contain a signature"
641 );
642 }
643
644 #[test]
645 fn test_parse_empty_block() {
646 let empty_data = vec![];
647 let result = BlockParser::parse_block(&empty_data);
648 assert!(result.is_ok());
649 let block_result = result.unwrap();
650 assert_eq!(block_result.transactions.len(), 0);
651 assert_eq!(block_result.block_hash, [0u8; 32]);
652 assert_eq!(block_result.block_producer, [0u8; 32]);
653 }
654
655 #[test]
656 fn test_parse_block_too_small() {
657 let small_data = vec![0u8; 50]; let result = BlockParser::parse_block(&small_data);
659 assert!(result.is_err());
660 let error_msg = format!("{}", result.unwrap_err());
661 assert!(error_msg.contains("Block too small"));
662 }
663
664 #[test]
665 fn test_parse_block_header_footer_only() {
666 let header_size = std::mem::size_of::<TnBlockHeader>();
668 let footer_size = std::mem::size_of::<TnBlockFooter>();
669 let mut block_data = vec![0u8; header_size + footer_size];
670
671 block_data[0] = 1; let result = BlockParser::parse_block(&block_data);
675
676 match result {
679 Ok(block_result) => {
680 assert_eq!(block_result.transactions.len(), 0);
682 assert_eq!(block_result.block_producer, [0u8; 32]);
683 assert_eq!(block_result.block_hash.len(), 32);
684 }
685 Err(error) => {
686 let error_msg = format!("{}", error);
688 assert!(
689 error_msg.contains("signature")
690 || error_msg.contains("key")
691 || error_msg.contains("Invalid")
692 );
693 }
694 }
695 }
696
697 #[test]
698 fn test_try_parse_transaction_at_offset() {
699 let header_size = 112;
710 let signature_size = 64;
711 let min_txn_size = header_size + signature_size;
712 let mut tx_data = vec![0u8; min_txn_size];
713
714 tx_data[0] = 1; tx_data[2] = 0;
720 tx_data[3] = 0;
721 tx_data[4] = 0;
723 tx_data[5] = 0;
724 tx_data[6] = 0;
726 tx_data[7] = 0;
727
728 let result = BlockParser::try_parse_transaction_at_offset(&tx_data);
730 match result {
733 Ok((size, data)) => {
734 assert_eq!(size, min_txn_size);
735 assert_eq!(data.len(), min_txn_size);
736 }
737 Err(_) => {
738 }
741 }
742 }
743
744 #[test]
745 fn test_parse_transactions_empty_data() {
746 let empty_data = vec![];
747 let result = BlockParser::parse_transactions(&empty_data);
748 assert!(result.is_ok());
749 assert_eq!(result.unwrap().len(), 0);
750 }
751
752 #[test]
753 fn test_parse_transactions_insufficient_data() {
754 let short_data = vec![0u8; 32]; let result = BlockParser::parse_transactions(&short_data);
756 assert!(result.is_ok());
757 assert_eq!(result.unwrap().len(), 0); }
759}