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    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        // The first 64 bytes are the first signature
481        let signature_bytes = &tx_data[..64];
482
483        // Convert to fixed-size array for signature utilities
484        let mut sig_array = [0u8; 64];
485        sig_array.copy_from_slice(signature_bytes);
486
487        // Convert to ts... format using existing utilities
488        let signature = tn_signature_to_string(&sig_array);
489
490        debug!("Extracted signature: {}", signature);
491        Ok(signature)
492    }
493
494    /// Extract all account mentions from block transactions
495    /// Returns a HashSet of base64-encoded account addresses
496    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                    // Continue processing other transactions
520                }
521            }
522        }
523
524        debug!(
525            "Extracted {} unique account addresses from block: {:?}",
526            accounts.len(),
527            accounts
528        );
529        Ok(accounts)
530    }
531
532    /// Extract account addresses from a single transaction
533    /// Returns ta... formatted account addresses found in the transaction
534    fn extract_transaction_accounts(tx_data: &[u8]) -> Result<Vec<String>, BlockParseError> {
535        if tx_data.len() < 176 {
536            // Minimum size for WireTxnHdrV1 (176 bytes)
537            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        // Extract fee_payer_pubkey (offset 112, 32 bytes)
549        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        // Extract program_pubkey (offset 144, 32 bytes)
569        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        // Extract additional account addresses from variable section
589        // This comes after the fixed header (176 bytes total for WireTxnHdrV1)
590        let header_size = 176;
591        if tx_data.len() > header_size {
592            // Get account counts from header
593            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            // Extract additional account addresses (32 bytes each)
602            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        // Create a mock transaction with 64 bytes of signature data
634        let mut transaction_data = vec![0u8; 100];
635        // Set some recognizable pattern in the signature bytes
636        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        // Verify it's in ts... format (90 characters starting with "ts")
646        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]; // Too short
660        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]; // Too small for header + footer
682        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        // Test with a block that has valid structure but invalid signatures
691        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        // Set block version in header
696        block_data[0] = 1; // block_version
697
698        let result = BlockParser::parse_block(&block_data);
699
700        // The result depends on whether all-zero signatures are considered valid
701        // Let's test both cases and verify we get a reasonable result
702        match result {
703            Ok(block_result) => {
704                // If it succeeds, verify the structure is correct
705                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                // If it fails, it should be due to cryptographic verification
711                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        // Create a minimal valid transaction structure for testing
724        // This is a simplified test that creates the minimum structure needed
725        let wire_header_size = mem::size_of::<WireTxnHdrV1>();
726        let mut tx_data = vec![0u8; wire_header_size];
727
728        // Set transaction version to 1 at the correct offset (after 64-byte signature)
729        tx_data[64] = 1; // transaction_version
730
731        // Set all account counts and instruction size to 0 (minimal transaction)
732        // readwrite_accounts_cnt at offset 66-67
733        tx_data[66] = 0;
734        tx_data[67] = 0;
735        // readonly_accounts_cnt at offset 68-69
736        tx_data[68] = 0;
737        tx_data[69] = 0;
738        // instr_data_sz at offset 70-71
739        tx_data[70] = 0;
740        tx_data[71] = 0;
741
742        // Test the helper function directly
743        let result = BlockParser::try_parse_transaction_at_offset(&tx_data);
744        // This might fail due to Transaction::from_wire validation, which is expected
745        // The important thing is that the function doesn't panic and handles the data correctly
746        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                // This is expected for invalid transaction data
753                // The test verifies the function handles invalid data gracefully
754            }
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]; // Too short for transaction header
769        let result = BlockParser::parse_transactions(&short_data);
770        assert!(result.is_ok());
771        assert_eq!(result.unwrap().len(), 0); // Should return empty list, not error
772    }
773}