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; 64];
15
16/// Result structure for block parsing with cryptographic verification
17#[derive(Debug, Clone)]
18pub struct BlockParseResult {
19    pub block_hash: [u8; 64],       // 512-bit Blake3 hash
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 block_time_ns: u64,
63}
64
65#[repr(C)]
66#[derive(Clone, Copy, Debug)]
67pub struct TnBlockFooter {
68    pub body: TnBlockFooterBody,
69    pub block_hash: FdBlake3Hash,
70    pub block_sig: FdSignature,
71}
72
73#[repr(C)]
74#[derive(Clone, Copy, Debug)]
75pub struct TnBlockFooterBody {
76    pub attestor_payment: u64,
77}
78
79/// Block parser for extracting transactions from UDS block data
80pub struct BlockParser;
81
82impl BlockParser {
83    /// Parse block data with cryptographic verification and extract transactions
84    pub fn parse_block(data: &[u8]) -> Result<BlockParseResult, BlockParseError> {
85        if data.is_empty() {
86            return Ok(BlockParseResult {
87                block_hash: [0u8; 64],
88                block_producer: [0u8; 32],
89                transactions: Vec::new(),
90            });
91        }
92
93        debug!(
94            "Parsing block data of {} bytes with cryptographic verification",
95            data.len()
96        );
97
98        // Block format: TnBlockHeader + Transactions + TnBlockFooter
99        let header_size = mem::size_of::<TnBlockHeader>();
100        let footer_size = mem::size_of::<TnBlockFooter>();
101
102        if data.len() < header_size + footer_size {
103            return Err(BlockParseError::InvalidBlockStructure(format!(
104                "Block too small: {} bytes, need at least {}",
105                data.len(),
106                header_size + footer_size
107            )));
108        }
109
110        // Parse TnBlockHeader from the beginning
111        let header = Self::parse_header_verified(&data[..header_size])?;
112        debug!(
113            "Parsed block header: version={}, start_slot={}, producer={}",
114            header.body.block_version,
115            header.body.start_slot,
116            tn_pubkey_to_address_string(&header.body.block_producer)
117        );
118
119        // Verify header signature first (fail fast optimization)
120        Self::verify_header_signature(&header)?;
121        debug!("Block header signature verified successfully");
122
123        // Compute block hash (excluding block signature)
124        let block_hash = Self::compute_block_hash(data)?;
125        debug!("Block hash computed successfully");
126
127        // Parse TnBlockFooter from the end
128        let footer_start = data.len() - footer_size;
129        let footer = Self::parse_footer_verified(&data[footer_start..])?;
130        debug!(
131            "Parsed block footer: attestor_payment={}",
132            footer.body.attestor_payment
133        );
134
135        // Verify block signature against computed hash
136        Self::verify_block_signature(&block_hash, &footer, &header.body.block_producer)?;
137        debug!("Block signature verified successfully");
138
139        // Extract transaction data between header and footer
140        let transactions_data = &data[header_size..footer_start];
141        debug!(
142            "Transaction data section: {} bytes",
143            transactions_data.len()
144        );
145
146        // Parse individual transactions from the middle section
147        let transactions = if transactions_data.is_empty() {
148            debug!("No transaction data in block");
149            Vec::new()
150        } else {
151            Self::parse_transactions(transactions_data)
152                .map_err(|e| BlockParseError::InvalidBlockStructure(e))?
153        };
154
155        debug!("Extracted {} transactions from block", transactions.len());
156
157        Ok(BlockParseResult {
158            block_hash,
159            block_producer: header.body.block_producer,
160            transactions,
161        })
162    }
163
164    /// Compute 512-bit Blake3 hash of block data excluding block signature and block hash
165    /// Matches C implementation: fd_blake3_append(&hasher, block_data, block_size - sizeof(fd_signature_t) - BLOCK_HASH_SIZE)
166    fn compute_block_hash(data: &[u8]) -> Result<[u8; 64], BlockParseError> {
167        let footer_size = mem::size_of::<TnBlockFooter>();
168
169        if data.len() < footer_size {
170            return Err(BlockParseError::HashComputationFailed(
171                "Block too small to contain footer".to_string(),
172            ));
173        }
174
175        // Hash all data except the final 64 bytes (block_sig) and 64 bytes (block_hash)
176        // This matches the C implementation which excludes sizeof(fd_signature_t) + BLOCK_HASH_SIZE
177        let sig_size = size_of::<FdSignature>();
178        let hash_size = size_of::<FdBlake3Hash>();
179        let hash_data_end = data.len() - sig_size - hash_size;
180        let hash_data = &data[..hash_data_end];
181
182        debug!(
183            "Computing Blake3 hash over {} bytes (excluding {} byte signature and {} byte hash)",
184            hash_data.len(),
185            sig_size,
186            hash_size
187        );
188
189        // Use Blake3 XOF (extendable output function) for 512-bit output
190        let mut hasher = blake3::Hasher::new();
191        hasher.update(hash_data);
192
193        let mut hash_output = [0u8; 64];
194        let mut output_reader = hasher.finalize_xof();
195        output_reader.fill(&mut hash_output);
196
197        debug!("Blake3 hash computation completed successfully");
198        Ok(hash_output)
199    }
200
201    /// Verify block header signature using ed25519
202    /// Matches C implementation: signs/verifies only the header body, not the signature field
203    fn verify_header_signature(header: &TnBlockHeader) -> Result<(), BlockParseError> {
204        debug!("Starting header signature verification");
205
206        // Check if signature is all zeros (unsigned/test data)
207        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        // Sign/verify only the header body, excluding the signature field
215        // This matches C implementation: fd_ed25519_verify((uchar const *)&header->body, ...)
216        let body_size = mem::size_of::<TnBlockHeaderBody>();
217
218        // Convert header body to bytes for verification
219        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        // Convert signature and public key to ed25519-dalek types
228        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        // Verify the signature against the header body
245        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    /// Verify block signature against computed hash using producer's public key
255    fn verify_block_signature(
256        block_hash: &[u8; 64],
257        footer: &TnBlockFooter,
258        producer_key: &[u8; 32],
259    ) -> Result<(), BlockParseError> {
260        debug!("Starting block signature verification");
261
262        // Check if signature is all zeros (unsigned/test data)
263        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        // Convert signature and public key to ed25519-dalek types
273        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        // Verify the signature against the block hash
290        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    /// Parse block header from data with error conversion
300    fn parse_header_verified(data: &[u8]) -> Result<TnBlockHeader, BlockParseError> {
301        Self::parse_header(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
302    }
303
304    /// Parse block footer from data with error conversion
305    fn parse_footer_verified(data: &[u8]) -> Result<TnBlockFooter, BlockParseError> {
306        Self::parse_footer(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
307    }
308
309    /// Legacy function for backward compatibility - will be removed after integration update
310    #[deprecated(note = "Use parse_block instead")]
311    pub fn parse_block_legacy(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
312        if data.is_empty() {
313            return Ok(Vec::new());
314        }
315
316        debug!("Parsing block data of {} bytes", data.len());
317
318        // Block format: TnBlockHeader + Transactions + TnBlockFooter
319        let header_size = mem::size_of::<TnBlockHeader>();
320        let footer_size = mem::size_of::<TnBlockFooter>();
321
322        if data.len() < header_size + footer_size {
323            return Err(format!(
324                "Block too small: {} bytes, need at least {}",
325                data.len(),
326                header_size + footer_size
327            ));
328        }
329
330        // Parse TnBlockHeader from the beginning
331        let header = Self::parse_header(&data[..header_size])?;
332        debug!(
333            "Parsed block header: version={}, start_slot={}",
334            header.body.block_version, header.body.start_slot
335        );
336        if header.body.block_version != 1 {
337            return Err(format!(
338                "Unsupported block version: {}",
339                header.body.block_version
340            ));
341        }
342
343        // Parse TnBlockFooter from the end
344        let footer_start = data.len() - footer_size;
345        let footer = Self::parse_footer(&data[footer_start..])?;
346        debug!(
347            "Parsed block footer: attestor_payment={}",
348            footer.body.attestor_payment
349        );
350
351        // Extract transaction data between header and footer
352        let transactions_data = &data[header_size..footer_start];
353        debug!(
354            "Transaction data section: {} bytes",
355            transactions_data.len()
356        );
357
358        if transactions_data.is_empty() {
359            debug!("No transaction data in block");
360            return Ok(Vec::new());
361        }
362
363        // Parse individual transactions from the middle section
364        let transactions = Self::parse_transactions(transactions_data)?;
365        debug!("Extracted {} transactions from block", transactions.len());
366
367        Ok(transactions)
368    }
369
370    /// Parse block header from data
371    fn parse_header(data: &[u8]) -> Result<TnBlockHeader, String> {
372        if data.len() < mem::size_of::<TnBlockHeader>() {
373            return Err("Insufficient data for block header".to_string());
374        }
375
376        // We'll do a simple byte copy since we're dealing with repr(C) structs
377        let header = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockHeader) };
378
379        debug!(
380            "Block header: version={}, producer={:?}",
381            header.body.block_version,
382            tn_pubkey_to_address_string(&header.body.block_producer)
383        );
384        Ok(header)
385    }
386
387    /// Parse block footer from data
388    fn parse_footer(data: &[u8]) -> Result<TnBlockFooter, String> {
389        if data.len() < mem::size_of::<TnBlockFooter>() {
390            return Err("Insufficient data for block footer".to_string());
391        }
392
393        let footer = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockFooter) };
394
395        debug!(
396            "Block footer: attestor_payment={}",
397            footer.body.attestor_payment
398        );
399        Ok(footer)
400    }
401
402    /// Parse transactions from the middle section of block data
403    fn parse_transactions(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
404        let mut transactions = Vec::new();
405        let mut offset = 0;
406
407        // Parse individual transactions using Transaction::from_wire
408        while offset < data.len() {
409            // Check if we have enough data for the minimum transaction header
410            let wire_header_size = mem::size_of::<WireTxnHdrV1>();
411            if offset + wire_header_size > data.len() {
412                debug!(
413                    "Remaining data too small for transaction header: {} bytes",
414                    data.len() - offset
415                );
416                break;
417            }
418
419            let remaining_data = &data[offset..];
420
421            // Try to parse the transaction using Transaction::from_wire
422            // We need to find the actual transaction size by attempting to parse it
423            match Self::try_parse_transaction_at_offset(remaining_data) {
424                Ok((transaction_size, transaction_data)) => {
425                    transactions.push(transaction_data);
426                    debug!(
427                        "Parsed transaction {} of size {} bytes",
428                        transactions.len(),
429                        transaction_size
430                    );
431                    offset += transaction_size;
432                }
433                Err(parse_error) => {
434                    warn!(
435                        "Failed to parse transaction at offset {}: {}",
436                        offset, parse_error
437                    );
438                    return Err(parse_error);
439                }
440            }
441        }
442
443        Ok(transactions)
444    }
445
446    /// Try to parse a transaction at the given offset, returning the transaction size and data
447    fn try_parse_transaction_at_offset(data: &[u8]) -> Result<(usize, Vec<u8>), String> {
448        // We need to determine the transaction size by parsing the header and variable-length data
449        let wire_header_size = mem::size_of::<WireTxnHdrV1>();
450
451        if data.len() < wire_header_size {
452            return Err("Not enough data for transaction header".to_string());
453        }
454
455        // Calculate total transaction size
456        let total_size = txn_lib::tn_txn_size(data).map_err(|e| e.to_string())?;
457
458        if data.len() < total_size {
459            return Err(format!(
460                "Not enough data for complete transaction: need {} bytes, have {}",
461                total_size,
462                data.len()
463            ));
464        }
465        // Extract the transaction data
466        let transaction_data = data[..total_size].to_vec();
467
468        // Verify the transaction can be parsed with Transaction::from_wire
469        if Transaction::from_wire(&transaction_data).is_none() {
470            return Err("Transaction::from_wire failed to parse transaction".to_string());
471        }
472        Ok((total_size, transaction_data))
473    }
474
475    /// Extract transaction signature from transaction data and convert to ts... format
476    pub fn extract_transaction_signature(tx_data: &[u8]) -> Result<String, String> {
477        if tx_data.len() < 64 {
478            return Err("Transaction too short to contain a signature".to_string());
479        }
480
481        // The first 64 bytes are the first signature
482        let signature_bytes = &tx_data[..64];
483
484        // Convert to fixed-size array for signature utilities
485        let mut sig_array = [0u8; 64];
486        sig_array.copy_from_slice(signature_bytes);
487
488        // Convert to ts... format using existing utilities
489        let signature = tn_signature_to_string(&sig_array);
490
491        debug!("Extracted signature: {}", signature);
492        Ok(signature)
493    }
494
495    /// Extract all account mentions from block transactions
496    /// Returns a HashSet of base64-encoded account addresses
497    pub fn extract_account_mentions(
498        transactions: &[Vec<u8>],
499    ) -> Result<HashSet<String>, BlockParseError> {
500        let mut accounts = HashSet::new();
501
502        debug!(
503            "Extracting account mentions from {} transactions",
504            transactions.len()
505        );
506
507        for (i, tx_data) in transactions.iter().enumerate() {
508            match Self::extract_transaction_accounts(tx_data) {
509                Ok(tx_accounts) => {
510                    debug!(
511                        "Transaction {} contains {} account references: {:?}",
512                        i,
513                        tx_accounts.len(),
514                        tx_accounts
515                    );
516                    accounts.extend(tx_accounts);
517                }
518                Err(e) => {
519                    warn!("Failed to extract accounts from transaction {}: {}", i, e);
520                    // Continue processing other transactions
521                }
522            }
523        }
524
525        debug!(
526            "Extracted {} unique account addresses from block: {:?}",
527            accounts.len(),
528            accounts
529        );
530        Ok(accounts)
531    }
532
533    /// Extract account addresses from a single transaction
534    /// Returns ta... formatted account addresses found in the transaction
535    fn extract_transaction_accounts(tx_data: &[u8]) -> Result<Vec<String>, BlockParseError> {
536        if tx_data.len() < 176 {
537            // Minimum size for WireTxnHdrV1 (176 bytes)
538            return Err(BlockParseError::AccountExtractionFailed(
539                "Transaction too small to contain header".to_string(),
540            ));
541        }
542
543        debug!(
544            "Extracting accounts from transaction of {} bytes",
545            tx_data.len()
546        );
547        let mut accounts = Vec::new();
548
549        // Extract fee_payer_pubkey (offset 112, 32 bytes)
550        let fee_payer_offset = 112;
551        if tx_data.len() >= fee_payer_offset + 32 {
552            let fee_payer_pubkey: [u8; 32] = tx_data[fee_payer_offset..fee_payer_offset + 32]
553                .try_into()
554                .map_err(|_| {
555                    BlockParseError::AccountExtractionFailed(
556                        "Failed to convert fee_payer_pubkey to [u8; 32]".to_string(),
557                    )
558                })?;
559            let fee_payer_address = tn_pubkey_to_address_string(&fee_payer_pubkey);
560            debug!(
561                "Extracted fee_payer at offset {}: {:?} -> {}",
562                fee_payer_offset,
563                &fee_payer_pubkey[..8],
564                fee_payer_address
565            );
566            accounts.push(fee_payer_address);
567        }
568
569        // Extract program_pubkey (offset 144, 32 bytes)
570        let program_offset = 144;
571        if tx_data.len() >= program_offset + 32 {
572            let program_pubkey: [u8; 32] = tx_data[program_offset..program_offset + 32]
573                .try_into()
574                .map_err(|_| {
575                    BlockParseError::AccountExtractionFailed(
576                        "Failed to convert program_pubkey to [u8; 32]".to_string(),
577                    )
578                })?;
579            let program_address = tn_pubkey_to_address_string(&program_pubkey);
580            debug!(
581                "Extracted program at offset {}: {:?} -> {}",
582                program_offset,
583                &program_pubkey[..8],
584                program_address
585            );
586            accounts.push(program_address);
587        }
588
589        // Extract additional account addresses from variable section
590        // This comes after the fixed header (176 bytes total for WireTxnHdrV1)
591        let header_size = 176;
592        if tx_data.len() > header_size {
593            // Get account counts from header
594            let readwrite_accounts_cnt = u16::from_le_bytes([tx_data[66], tx_data[67]]);
595            let readonly_accounts_cnt = u16::from_le_bytes([tx_data[68], tx_data[69]]);
596
597            debug!(
598                "Transaction has {} readwrite and {} readonly accounts",
599                readwrite_accounts_cnt, readonly_accounts_cnt
600            );
601
602            // Extract additional account addresses (32 bytes each)
603            let additional_accounts_count =
604                (readwrite_accounts_cnt + readonly_accounts_cnt) as usize;
605            let additional_accounts_size = additional_accounts_count * 32;
606
607            if tx_data.len() >= header_size + additional_accounts_size {
608                for i in 0..additional_accounts_count {
609                    let account_offset = header_size + (i * 32);
610                    let account_pubkey: [u8; 32] = tx_data[account_offset..account_offset + 32]
611                        .try_into()
612                        .map_err(|_| {
613                            BlockParseError::AccountExtractionFailed(format!(
614                                "Failed to convert account_pubkey {} to [u8; 32]",
615                                i
616                            ))
617                        })?;
618                    let account_address = tn_pubkey_to_address_string(&account_pubkey);
619                    accounts.push(account_address);
620                }
621            }
622        }
623
624        Ok(accounts)
625    }
626}
627
628#[cfg(test)]
629mod tests {
630    use super::*;
631
632    #[test]
633    fn test_extract_transaction_signature() {
634        // Create a mock transaction with 64 bytes of signature data
635        let mut transaction_data = vec![0u8; 100];
636        // Set some recognizable pattern in the signature bytes
637        for i in 0..64 {
638            transaction_data[i] = (i % 256) as u8;
639        }
640
641        let result = BlockParser::extract_transaction_signature(&transaction_data);
642        assert!(result.is_ok());
643
644        let signature = result.unwrap();
645        assert!(!signature.is_empty());
646        // Verify it's in ts... format (90 characters starting with "ts")
647        assert_eq!(
648            signature.len(),
649            90,
650            "Signature should be 90 characters in ts... format"
651        );
652        assert!(
653            signature.starts_with("ts"),
654            "Signature should start with 'ts'"
655        );
656    }
657
658    #[test]
659    fn test_extract_transaction_signature_too_short() {
660        let transaction_data = vec![0u8; 32]; // Too short
661        let result = BlockParser::extract_transaction_signature(&transaction_data);
662        assert!(result.is_err());
663        assert_eq!(
664            result.unwrap_err(),
665            "Transaction too short to contain a signature"
666        );
667    }
668
669    #[test]
670    fn test_parse_empty_block() {
671        let empty_data = vec![];
672        let result = BlockParser::parse_block(&empty_data);
673        assert!(result.is_ok());
674        let block_result = result.unwrap();
675        assert_eq!(block_result.transactions.len(), 0);
676        assert_eq!(block_result.block_hash, [0u8; 64]);
677        assert_eq!(block_result.block_producer, [0u8; 32]);
678    }
679
680    #[test]
681    fn test_parse_block_too_small() {
682        let small_data = vec![0u8; 50]; // Too small for header + footer
683        let result = BlockParser::parse_block(&small_data);
684        assert!(result.is_err());
685        let error_msg = format!("{}", result.unwrap_err());
686        assert!(error_msg.contains("Block too small"));
687    }
688
689    #[test]
690    fn test_parse_block_header_footer_only() {
691        // Test with a block that has valid structure but invalid signatures
692        let header_size = std::mem::size_of::<TnBlockHeader>();
693        let footer_size = std::mem::size_of::<TnBlockFooter>();
694        let mut block_data = vec![0u8; header_size + footer_size];
695
696        // Set block version in header
697        block_data[0] = 1; // block_version
698
699        let result = BlockParser::parse_block(&block_data);
700
701        // The result depends on whether all-zero signatures are considered valid
702        // Let's test both cases and verify we get a reasonable result
703        match result {
704            Ok(block_result) => {
705                // If it succeeds, verify the structure is correct
706                assert_eq!(block_result.transactions.len(), 0);
707                assert_eq!(block_result.block_producer, [0u8; 32]);
708                assert_eq!(block_result.block_hash.len(), 64);
709            }
710            Err(error) => {
711                // If it fails, it should be due to cryptographic verification
712                let error_msg = format!("{}", error);
713                assert!(
714                    error_msg.contains("signature")
715                        || error_msg.contains("key")
716                        || error_msg.contains("Invalid")
717                );
718            }
719        }
720    }
721
722    #[test]
723    fn test_try_parse_transaction_at_offset() {
724        // Create a minimal valid transaction structure for testing
725        // This is a simplified test that creates the minimum structure needed
726        let wire_header_size = mem::size_of::<WireTxnHdrV1>();
727        let mut tx_data = vec![0u8; wire_header_size];
728
729        // Set transaction version to 1 at the correct offset (after 64-byte signature)
730        tx_data[64] = 1; // transaction_version
731
732        // Set all account counts and instruction size to 0 (minimal transaction)
733        // readwrite_accounts_cnt at offset 66-67
734        tx_data[66] = 0;
735        tx_data[67] = 0;
736        // readonly_accounts_cnt at offset 68-69
737        tx_data[68] = 0;
738        tx_data[69] = 0;
739        // instr_data_sz at offset 70-71
740        tx_data[70] = 0;
741        tx_data[71] = 0;
742
743        // Test the helper function directly
744        let result = BlockParser::try_parse_transaction_at_offset(&tx_data);
745        // This might fail due to Transaction::from_wire validation, which is expected
746        // The important thing is that the function doesn't panic and handles the data correctly
747        match result {
748            Ok((size, data)) => {
749                assert_eq!(size, wire_header_size);
750                assert_eq!(data.len(), wire_header_size);
751            }
752            Err(_) => {
753                // This is expected for invalid transaction data
754                // The test verifies the function handles invalid data gracefully
755            }
756        }
757    }
758
759    #[test]
760    fn test_parse_transactions_empty_data() {
761        let empty_data = vec![];
762        let result = BlockParser::parse_transactions(&empty_data);
763        assert!(result.is_ok());
764        assert_eq!(result.unwrap().len(), 0);
765    }
766
767    #[test]
768    fn test_parse_transactions_insufficient_data() {
769        let short_data = vec![0u8; 32]; // Too short for transaction header
770        let result = BlockParser::parse_transactions(&short_data);
771        assert!(result.is_ok());
772        assert_eq!(result.unwrap().len(), 0); // Should return empty list, not error
773    }
774}