Skip to main content

thru_base/
block_parser.rs

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};
7// use base64::prelude::*;
8
9use ed25519_dalek::{Signature, Verifier, VerifyingKey};
10
11/// Block format structures (from thru-uds/src/block_format.rs)
12pub type FdPubkey = [u8; 32];
13pub type FdSignature = [u8; 64];
14pub type FdBlake3Hash = [u8; 32];
15
16/// Result structure for block parsing with cryptographic verification
17#[derive(Debug, Clone)]
18pub struct BlockParseResult {
19    pub block_hash: [u8; 32],       // 256-bit Blake3 hash (first 32 bytes)
20    pub block_producer: [u8; 32],   // Block producer's public key
21    pub transactions: Vec<Vec<u8>>, // Existing transaction data
22}
23
24/// Comprehensive error handling for block parsing and cryptographic verification
25#[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
80/// Block parser for extracting transactions from UDS block data
81pub struct BlockParser;
82
83impl BlockParser {
84    /// Parse block data with cryptographic verification and extract transactions
85    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        // Block format: TnBlockHeader + Transactions + TnBlockFooter
100        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        // Parse TnBlockHeader from the beginning
112        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        // Verify header signature first (fail fast optimization)
121        Self::verify_header_signature(&header)?;
122        debug!("Block header signature verified successfully");
123
124        // Compute block hash (excluding block signature)
125        let block_hash = Self::compute_block_hash(data)?;
126        debug!("Block hash computed successfully");
127
128        // Parse TnBlockFooter from the end
129        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        // Verify block signature against computed hash
137        Self::verify_block_signature(&block_hash, &footer, &header.body.block_producer)?;
138        debug!("Block signature verified successfully");
139
140        // Extract transaction data between header and footer
141        let transactions_data = &data[header_size..footer_start];
142        debug!(
143            "Transaction data section: {} bytes",
144            transactions_data.len()
145        );
146
147        // Parse individual transactions from the middle section
148        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    /// Compute 256-bit Blake3 hash of block data excluding block signature and block hash
166    /// Matches C implementation: fd_blake3_append(&hasher, block_data, block_size - sizeof(fd_signature_t) - BLOCK_HASH_SIZE)
167    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        // Hash all data except the final 64 bytes (block_sig) and 32 bytes (block_hash)
177        // This matches the C implementation which excludes sizeof(fd_signature_t) + BLOCK_HASH_SIZE
178        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        // Use Blake3 256-bit output and truncate to 32 bytes
191        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    /// Verify block header signature using ed25519
201    /// Matches C implementation: signs/verifies only the header body, not the signature field
202    fn verify_header_signature(header: &TnBlockHeader) -> Result<(), BlockParseError> {
203        debug!("Starting header signature verification");
204
205        // Check if signature is all zeros (unsigned/test data)
206        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        // Sign/verify only the header body, excluding the signature field
214        // This matches C implementation: fd_ed25519_verify((uchar const *)&header->body, ...)
215        let body_size = mem::size_of::<TnBlockHeaderBody>();
216
217        // Convert header body to bytes for verification
218        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        // Convert signature and public key to ed25519-dalek types
227        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        // Verify the signature against the header body
244        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    /// Verify block signature against computed hash using producer's public key
254    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        // Check if signature is all zeros (unsigned/test data)
262        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        // Convert signature and public key to ed25519-dalek types
272        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        // Verify the signature against the block hash
289        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    /// Parse block header from data with error conversion
299    fn parse_header_verified(data: &[u8]) -> Result<TnBlockHeader, BlockParseError> {
300        Self::parse_header(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
301    }
302
303    /// Parse block footer from data with error conversion
304    fn parse_footer_verified(data: &[u8]) -> Result<TnBlockFooter, BlockParseError> {
305        Self::parse_footer(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
306    }
307
308    /// Legacy function for backward compatibility - will be removed after integration update
309    #[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        // Block format: TnBlockHeader + Transactions + TnBlockFooter
318        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        // Parse TnBlockHeader from the beginning
330        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        // Parse TnBlockFooter from the end
343        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        // Extract transaction data between header and footer
351        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        // Parse individual transactions from the middle section
363        let transactions = Self::parse_transactions(transactions_data)?;
364        debug!("Extracted {} transactions from block", transactions.len());
365
366        Ok(transactions)
367    }
368
369    /// Parse block header from data
370    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        // We'll do a simple byte copy since we're dealing with repr(C) structs
376        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    /// Parse block footer from data
387    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    /// Parse transactions from the middle section of block data
402    fn parse_transactions(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
403        let mut transactions = Vec::new();
404        let mut offset = 0;
405
406        // Parse individual transactions using Transaction::from_wire
407        while offset < data.len() {
408            // Check if we have enough data for the minimum transaction header
409            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            // Try to parse the transaction using Transaction::from_wire
421            // We need to find the actual transaction size by attempting to parse it
422            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    /// Try to parse a transaction at the given offset, returning the transaction size and data
446    fn try_parse_transaction_at_offset(data: &[u8]) -> Result<(usize, Vec<u8>), String> {
447        // We need to determine the transaction size by parsing the header and variable-length data
448        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        // Calculate total transaction size
455        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        // Extract the transaction data
465        let transaction_data = data[..total_size].to_vec();
466
467        // Verify the transaction can be parsed with Transaction::from_wire
468        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    /// Extract transaction signature from transaction data and convert to ts... format
475    /// The signature is at the END of the transaction (last 64 bytes).
476    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        // The signature is at the end   of the transaction (last 64 bytes)
482        let sig_start = tx_data.len() - txn_lib::TN_TXN_SIGNATURE_SZ;
483        let signature_bytes = &tx_data[sig_start..];
484
485        // Convert to fixed-size array for signature utilities
486        let mut sig_array = [0u8; 64];
487        sig_array.copy_from_slice(signature_bytes);
488
489        // Convert to ts... format using existing utilities
490        let signature = tn_signature_to_string(&sig_array);
491
492        debug!("Extracted signature: {}", signature);
493        Ok(signature)
494    }
495
496    /// Extract all account mentions from block transactions
497    /// Returns a HashSet of base64-encoded account addresses
498    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                    // Continue processing other transactions
522                }
523            }
524        }
525
526        debug!(
527            "Extracted {} unique account addresses from block: {:?}",
528            accounts.len(),
529            accounts
530        );
531        Ok(accounts)
532    }
533
534    /// Extract account addresses from a single transaction
535    /// Returns ta... formatted account addresses found in the transaction
536    fn extract_transaction_accounts(tx_data: &[u8]) -> Result<Vec<String>, BlockParseError> {
537        // Minimum size: 112-byte header + 64-byte trailing signature = 176 bytes
538        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        // Extract fee_payer_pubkey (offset 48, 32 bytes)
555        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        // Extract program_pubkey (offset 80, 32 bytes)
575        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        // Extract additional account addresses from variable section
595        // This comes after the fixed header (112 bytes total for WireTxnHdrV1)
596        let header_size = 112;
597        if tx_data.len() > header_size {
598            // Get account counts from header
599            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            // Extract additional account addresses (32 bytes each)
608            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        // Create a mock transaction with 64 bytes of signature data at the END
640        let mut transaction_data = vec![0u8; 100];
641        // Set some recognizable pattern in the signature bytes (last 64 bytes)
642        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        // Verify it's in ts... format (90 characters starting with "ts")
653        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]; // Too short
667        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]; // Too small for header + footer
689        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        // Test with a block that has valid structure but invalid signatures
698        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        // Set block version in header
703        block_data[0] = 1; // block_version
704
705        let result = BlockParser::parse_block(&block_data);
706
707        // The result depends on whether all-zero signatures are considered valid
708        // Let's test both cases and verify we get a reasonable result
709        match result {
710            Ok(block_result) => {
711                // If it succeeds, verify the structure is correct
712                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                // If it fails, it should be due to cryptographic verification
718                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        // Create a minimal valid transaction structure for testing
731        // wire format: 112-byte header (no signature prefix) + 64-byte trailing signature
732        // Transaction layout:
733        //   [0,1)   transaction_version
734        //   [1,2)   flags
735        //   [2,4)   readwrite_accounts_cnt
736        //   [4,6)   readonly_accounts_cnt
737        //   [6,8)   instr_data_sz
738        //   ... other fields ...
739        //   [last 64 bytes] signature at END
740        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        // Set transaction version to 1 at offset 0 (no signature prefix in new format)
746        tx_data[0] = 1; // transaction_version
747
748        // Set all account counts and instruction size to 0 (minimal transaction)
749        // readwrite_accounts_cnt at offset 2-3
750        tx_data[2] = 0;
751        tx_data[3] = 0;
752        // readonly_accounts_cnt at offset 4-5
753        tx_data[4] = 0;
754        tx_data[5] = 0;
755        // instr_data_sz at offset 6-7
756        tx_data[6] = 0;
757        tx_data[7] = 0;
758
759        // Test the helper function directly
760        let result = BlockParser::try_parse_transaction_at_offset(&tx_data);
761        // This might fail due to Transaction::from_wire validation, which is expected
762        // The important thing is that the function doesn't panic and handles the data correctly
763        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                // This is expected for invalid transaction data
770                // The test verifies the function handles invalid data gracefully
771            }
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]; // Too short for transaction header
786        let result = BlockParser::parse_transactions(&short_data);
787        assert!(result.is_ok());
788        assert_eq!(result.unwrap().len(), 0); // Should return empty list, not error
789    }
790}