Skip to main content

blvm_protocol/
validation.rs

1//! Protocol validation interface
2//!
3//! This module provides protocol-specific validation that extends
4//! the pure mathematical consensus rules with network-specific
5//! and protocol-specific validation logic.
6
7use crate::error::ProtocolError;
8use crate::features::FeatureRegistry;
9use crate::{BitcoinProtocolEngine, NetworkParameters, ProtocolVersion};
10use crate::{Block, Transaction, ValidationResult};
11use blvm_consensus::types::UtxoSet;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15// Protocol-specific Result type
16type Result<T> = std::result::Result<T, ProtocolError>;
17
18/// Protocol-specific validation rules
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct ProtocolValidationRules {
21    /// Maximum block size for this protocol
22    pub max_block_size: u32,
23    /// Maximum transaction size for this protocol
24    pub max_tx_size: u32,
25    /// Maximum script size for this protocol
26    pub max_script_size: u32,
27    /// Whether SegWit is enabled
28    pub segwit_enabled: bool,
29    /// Whether Taproot is enabled
30    pub taproot_enabled: bool,
31    /// Whether RBF (Replace-By-Fee) is enabled
32    pub rbf_enabled: bool,
33    /// Minimum transaction fee rate
34    pub min_fee_rate: u64,
35    /// Maximum transaction fee rate
36    pub max_fee_rate: u64,
37}
38
39impl ProtocolValidationRules {
40    /// Get validation rules for a specific protocol version
41    pub fn for_protocol(version: ProtocolVersion) -> Self {
42        match version {
43            ProtocolVersion::BitcoinV1 => Self::mainnet(),
44            ProtocolVersion::Testnet3 => Self::testnet(),
45            ProtocolVersion::Regtest => Self::regtest(),
46        }
47    }
48
49    /// Mainnet validation rules (strict production rules)
50    pub fn mainnet() -> Self {
51        Self {
52            max_block_size: 4_000_000, // 4MB block size limit
53            max_tx_size: 1_000_000,    // 1MB transaction size limit
54            max_script_size: 10_000,   // 10KB script size limit
55            segwit_enabled: true,
56            taproot_enabled: true,
57            rbf_enabled: true,
58            min_fee_rate: 1,         // 1 sat/vB minimum
59            max_fee_rate: 1_000_000, // 1M sat/vB maximum
60        }
61    }
62
63    /// Testnet validation rules (same as mainnet but with testnet parameters)
64    pub fn testnet() -> Self {
65        Self {
66            max_block_size: 4_000_000,
67            max_tx_size: 1_000_000,
68            max_script_size: 10_000,
69            segwit_enabled: true,
70            taproot_enabled: true,
71            rbf_enabled: true,
72            min_fee_rate: 1,
73            max_fee_rate: 1_000_000,
74        }
75    }
76
77    /// Regtest validation rules (relaxed for testing)
78    pub fn regtest() -> Self {
79        Self {
80            max_block_size: 4_000_000,
81            max_tx_size: 1_000_000,
82            max_script_size: 10_000,
83            segwit_enabled: true,
84            taproot_enabled: true,
85            rbf_enabled: true,
86            min_fee_rate: 0, // No minimum fee for testing
87            max_fee_rate: 1_000_000,
88        }
89    }
90}
91
92/// Protocol-specific validation context
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94pub struct ProtocolValidationContext {
95    /// Current block height
96    pub block_height: u64,
97    /// Current network parameters
98    pub network_params: NetworkParameters,
99    /// Protocol validation rules
100    pub validation_rules: ProtocolValidationRules,
101    /// Feature activation registry for this protocol version.
102    ///
103    /// Used by `is_feature_enabled` to derive activation status from
104    /// `block_height` / `network_time` rather than static booleans.
105    pub feature_registry: FeatureRegistry,
106    /// Median time-past used for time-based validation (BIP113)
107    ///
108    /// This is populated by the node using recent headers and is threaded down
109    /// to consensus for timestamp validation.
110    pub median_time_past: u64,
111    /// Current adjusted network time (Unix timestamp)
112    ///
113    /// This is populated by the node from its time source and used by consensus
114    /// to enforce future timestamp limits.
115    pub network_time: u64,
116    /// Additional context data
117    pub context_data: HashMap<String, String>,
118}
119
120impl ProtocolValidationContext {
121    /// Create validation context for a protocol version
122    pub fn new(version: ProtocolVersion, block_height: u64) -> Result<Self> {
123        let network_params = NetworkParameters::for_version(version)?;
124        let validation_rules = ProtocolValidationRules::for_protocol(version);
125
126        let feature_registry = FeatureRegistry::for_protocol(version);
127        Ok(Self {
128            block_height,
129            network_params,
130            validation_rules,
131            feature_registry,
132            // Default to zero; callers that care about time must set these explicitly.
133            median_time_past: 0,
134            network_time: 0,
135            context_data: HashMap::new(),
136        })
137    }
138
139    /// Check if a feature is enabled at the current block height and network time.
140    ///
141    /// Queries the `FeatureRegistry` so activation reflects the actual deployment
142    /// height/timestamp rather than static booleans that ignore `block_height`.
143    pub fn is_feature_enabled(&self, feature: &str) -> bool {
144        self.feature_registry
145            .is_feature_active(feature, self.block_height, self.network_time)
146    }
147
148    /// Get maximum allowed size for a component
149    pub fn get_max_size(&self, component: &str) -> u32 {
150        match component {
151            "block" => self.validation_rules.max_block_size,
152            "transaction" => self.validation_rules.max_tx_size,
153            "script" => self.validation_rules.max_script_size,
154            _ => 0,
155        }
156    }
157}
158
159impl BitcoinProtocolEngine {
160    /// Validate a block with protocol-specific rules (witness-blind).
161    ///
162    /// The block weight check is a lower bound only.  Use
163    /// [`validate_block_with_protocol_and_witnesses`] for accurate BIP141 weight enforcement.
164    pub fn validate_block_with_protocol(
165        &self,
166        block: &Block,
167        _utxos: &UtxoSet,
168        _height: u64,
169        context: &ProtocolValidationContext,
170    ) -> Result<ValidationResult> {
171        self.apply_protocol_validation(block, context)?;
172        Ok(ValidationResult::Valid)
173    }
174
175    /// Validate a block with protocol-specific rules and witness data.
176    ///
177    /// Performs a full BIP141 weight check using the provided witnesses.
178    pub fn validate_block_with_protocol_and_witnesses(
179        &self,
180        block: &Block,
181        witnesses: &[Vec<blvm_consensus::segwit::Witness>],
182        _utxos: &UtxoSet,
183        _height: u64,
184        context: &ProtocolValidationContext,
185    ) -> Result<ValidationResult> {
186        self.apply_protocol_validation_with_witnesses(block, Some(witnesses), context)?;
187        Ok(ValidationResult::Valid)
188    }
189
190    /// Validate a transaction with protocol-specific rules
191    pub fn validate_transaction_with_protocol(
192        &self,
193        tx: &Transaction,
194        context: &ProtocolValidationContext,
195    ) -> Result<ValidationResult> {
196        // First, run consensus validation
197        let consensus_result = self.consensus.validate_transaction(tx)?;
198
199        // Then, apply protocol-specific validation
200        self.apply_transaction_protocol_validation(tx, context)?;
201
202        Ok(consensus_result)
203    }
204
205    /// Apply protocol-specific validation rules (witness-blind; lower-bound weight only).
206    ///
207    /// This variant does not have witness data available, so the block weight check is a
208    /// lower bound: it rejects blocks whose **base weight** (`stripped_size × 4`) alone
209    /// exceeds the 4 MWU limit, but cannot catch blocks that are within the base limit yet
210    /// exceed 4 MWU due to large witness data.  Prefer
211    /// [`apply_protocol_validation_with_witnesses`] when witness data is available.
212    fn apply_protocol_validation(
213        &self,
214        block: &Block,
215        context: &ProtocolValidationContext,
216    ) -> Result<()> {
217        self.apply_protocol_validation_with_witnesses(block, None, context)
218    }
219
220    /// Apply protocol-specific validation rules with optional witness data.
221    ///
222    /// When `witnesses` is `Some`, the BIP141 weight check uses the full formula:
223    /// `weight = stripped_size × 4 + total_witness_size`, which correctly rejects
224    /// blocks with heavy witness stacks that would otherwise slip under the base check.
225    ///
226    /// When `witnesses` is `None` the check falls back to `stripped_size × 4` (lower bound).
227    fn apply_protocol_validation_with_witnesses(
228        &self,
229        block: &Block,
230        witnesses: Option<&[Vec<blvm_consensus::segwit::Witness>]>,
231        context: &ProtocolValidationContext,
232    ) -> Result<()> {
233        // BIP141 block weight check.
234        let stripped_size = self.calculate_block_size(block);
235        let weight = if let Some(wit) = witnesses {
236            // Full BIP141 weight: base × 4 + witness bytes (counted at ×1).
237            let witness_bytes: u64 = wit
238                .iter()
239                .flat_map(|tx_wit| tx_wit.iter())
240                .map(|w| w.len() as u64)
241                .sum();
242            stripped_size as u64 * 4 + witness_bytes
243        } else {
244            stripped_size as u64 * 4
245        };
246        if weight > context.validation_rules.max_block_size as u64 {
247            return Err(ProtocolError::Validation(
248                format!(
249                    "Block weight exceeds maximum: {} WU (max {} WU)",
250                    weight, context.validation_rules.max_block_size
251                )
252                .into(),
253            ));
254        }
255
256        if block.transactions.len() > 10000 {
257            return Err(ProtocolError::Validation(
258                "Too many transactions in block (max 10000)".into(),
259            ));
260        }
261
262        for tx in &block.transactions {
263            self.apply_transaction_protocol_validation(tx, context)?;
264        }
265
266        Ok(())
267    }
268
269    /// Apply protocol-specific transaction validation
270    fn apply_transaction_protocol_validation(
271        &self,
272        tx: &Transaction,
273        context: &ProtocolValidationContext,
274    ) -> Result<()> {
275        // Check transaction size limits
276        let tx_size = self.calculate_transaction_size(tx);
277        if tx_size > context.validation_rules.max_tx_size {
278            return Err(ProtocolError::Validation(
279                format!(
280                    "Transaction size exceeds maximum: {} bytes (max {} bytes)",
281                    tx_size, context.validation_rules.max_tx_size
282                )
283                .into(),
284            ));
285        }
286
287        // Check script size limits
288        for input in &tx.inputs {
289            if input.script_sig.len() > context.validation_rules.max_script_size as usize {
290                return Err(ProtocolError::Validation(
291                    format!(
292                        "Script size exceeds maximum: {} bytes (max {} bytes)",
293                        input.script_sig.len(),
294                        context.validation_rules.max_script_size
295                    )
296                    .into(),
297                ));
298            }
299        }
300
301        for output in &tx.outputs {
302            if output.script_pubkey.len() > context.validation_rules.max_script_size as usize {
303                return Err(ProtocolError::Validation(
304                    format!(
305                        "Script size exceeds maximum: {} bytes (max {} bytes)",
306                        output.script_pubkey.len(),
307                        context.validation_rules.max_script_size
308                    )
309                    .into(),
310                ));
311            }
312        }
313
314        Ok(())
315    }
316
317    /// Calculate block size in bytes
318    fn calculate_block_size(&self, block: &Block) -> u32 {
319        // Simplified size calculation
320        // In reality, this would include proper serialization
321        let header_size = 80; // Block header is always 80 bytes
322        let tx_count_size = 4; // Varint for transaction count
323        let tx_sizes: u32 = block
324            .transactions
325            .iter()
326            .map(|tx| self.calculate_transaction_size(tx))
327            .sum();
328
329        header_size + tx_count_size + tx_sizes
330    }
331
332    /// Calculate transaction size in bytes
333    fn calculate_transaction_size(&self, tx: &Transaction) -> u32 {
334        // Use canonical serialization-based size from consensus layer (TX_NO_WITNESS size).
335        //
336        // This keeps protocol-level size limits aligned with the exact serialization
337        // used for consensus checks and transaction size tests.
338        blvm_consensus::transaction::calculate_transaction_size(tx) as u32
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345    use blvm_consensus::types::{OutPoint, TransactionInput, TransactionOutput};
346    use blvm_consensus::{Block, BlockHeader, Transaction};
347
348    #[test]
349    fn test_validation_rules() {
350        let mainnet_rules = ProtocolValidationRules::mainnet();
351        assert_eq!(mainnet_rules.max_block_size, 4_000_000);
352        assert!(mainnet_rules.segwit_enabled);
353        assert!(mainnet_rules.taproot_enabled);
354
355        let regtest_rules = ProtocolValidationRules::regtest();
356        assert_eq!(regtest_rules.max_block_size, 4_000_000);
357        assert!(regtest_rules.segwit_enabled);
358        assert_eq!(regtest_rules.min_fee_rate, 0); // No minimum fee for testing
359    }
360
361    #[test]
362    fn test_validation_rules_all_protocols() {
363        let mainnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::BitcoinV1);
364        let testnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Testnet3);
365        let regtest_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Regtest);
366
367        // Mainnet and testnet should have same rules
368        assert_eq!(mainnet_rules.max_block_size, testnet_rules.max_block_size);
369        assert_eq!(mainnet_rules.max_tx_size, testnet_rules.max_tx_size);
370        assert_eq!(mainnet_rules.max_script_size, testnet_rules.max_script_size);
371        assert_eq!(mainnet_rules.segwit_enabled, testnet_rules.segwit_enabled);
372        assert_eq!(mainnet_rules.taproot_enabled, testnet_rules.taproot_enabled);
373        assert_eq!(mainnet_rules.rbf_enabled, testnet_rules.rbf_enabled);
374        assert_eq!(mainnet_rules.min_fee_rate, testnet_rules.min_fee_rate);
375        assert_eq!(mainnet_rules.max_fee_rate, testnet_rules.max_fee_rate);
376
377        // Regtest should have relaxed fee rules
378        assert_eq!(regtest_rules.min_fee_rate, 0);
379        assert_eq!(regtest_rules.max_fee_rate, mainnet_rules.max_fee_rate);
380    }
381
382    #[test]
383    fn test_validation_rules_serialization() {
384        let mainnet_rules = ProtocolValidationRules::mainnet();
385        let json = serde_json::to_string(&mainnet_rules).unwrap();
386        let deserialized: ProtocolValidationRules = serde_json::from_str(&json).unwrap();
387
388        assert_eq!(mainnet_rules.max_block_size, deserialized.max_block_size);
389        assert_eq!(mainnet_rules.max_tx_size, deserialized.max_tx_size);
390        assert_eq!(mainnet_rules.max_script_size, deserialized.max_script_size);
391        assert_eq!(mainnet_rules.segwit_enabled, deserialized.segwit_enabled);
392        assert_eq!(mainnet_rules.taproot_enabled, deserialized.taproot_enabled);
393        assert_eq!(mainnet_rules.rbf_enabled, deserialized.rbf_enabled);
394        assert_eq!(mainnet_rules.min_fee_rate, deserialized.min_fee_rate);
395        assert_eq!(mainnet_rules.max_fee_rate, deserialized.max_fee_rate);
396    }
397
398    #[test]
399    fn test_validation_rules_equality() {
400        let mainnet1 = ProtocolValidationRules::mainnet();
401        let mainnet2 = ProtocolValidationRules::mainnet();
402        let testnet = ProtocolValidationRules::testnet();
403
404        assert_eq!(mainnet1, mainnet2);
405        assert_eq!(mainnet1, testnet); // Mainnet and testnet should be identical
406    }
407
408    #[test]
409    fn test_validation_context() {
410        // Use a height past both segwit (481824) and taproot (709632) activation.
411        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
412        assert_eq!(context.block_height, 800_000);
413        assert!(context.is_feature_enabled("segwit"));
414        assert!(!context.is_feature_enabled("nonexistent"));
415        assert_eq!(context.get_max_size("block"), 4_000_000);
416    }
417
418    #[test]
419    fn test_validation_context_all_protocols() {
420        // Per-network heights past segwit+taproot activation:
421        //   mainnet: taproot at 709_632   → use 800_000
422        //   testnet3: taproot at 2_016_000 → use 2_100_000
423        //   regtest: all features AlwaysActive → any height works
424        let mainnet_context =
425            ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
426        let testnet_context =
427            ProtocolValidationContext::new(ProtocolVersion::Testnet3, 2_100_000).unwrap();
428        let regtest_context = ProtocolValidationContext::new(ProtocolVersion::Regtest, 1).unwrap();
429
430        assert_eq!(mainnet_context.block_height, 800_000);
431        assert_eq!(testnet_context.block_height, 2_100_000);
432        assert_eq!(regtest_context.block_height, 1);
433
434        // All should support same features
435        assert!(mainnet_context.is_feature_enabled("segwit"));
436        assert!(testnet_context.is_feature_enabled("segwit"));
437        assert!(regtest_context.is_feature_enabled("segwit"));
438
439        assert!(mainnet_context.is_feature_enabled("taproot"));
440        assert!(testnet_context.is_feature_enabled("taproot"));
441        assert!(regtest_context.is_feature_enabled("taproot"));
442
443        assert!(mainnet_context.is_feature_enabled("rbf"));
444        assert!(testnet_context.is_feature_enabled("rbf"));
445        assert!(regtest_context.is_feature_enabled("rbf"));
446    }
447
448    #[test]
449    fn test_validation_context_feature_queries() {
450        // Height past both segwit (481824) and taproot (709632) so all features are active.
451        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
452
453        // Test all supported features
454        assert!(context.is_feature_enabled("segwit"));
455        assert!(context.is_feature_enabled("taproot"));
456        assert!(context.is_feature_enabled("rbf"));
457
458        // Test unsupported features
459        assert!(!context.is_feature_enabled("nonexistent"));
460        assert!(!context.is_feature_enabled(""));
461        assert!(!context.is_feature_enabled("fast_mining"));
462    }
463
464    #[test]
465    fn test_validation_context_size_queries() {
466        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
467
468        assert_eq!(context.get_max_size("block"), 4_000_000);
469        assert_eq!(context.get_max_size("transaction"), 1_000_000);
470        assert_eq!(context.get_max_size("script"), 10_000);
471
472        // Test unknown component
473        assert_eq!(context.get_max_size("unknown"), 0);
474    }
475
476    #[test]
477    fn test_validation_context_serialization() {
478        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
479        let json = serde_json::to_string(&context).unwrap();
480        let deserialized: ProtocolValidationContext = serde_json::from_str(&json).unwrap();
481
482        assert_eq!(context.block_height, deserialized.block_height);
483        assert_eq!(
484            context.network_params.network_name,
485            deserialized.network_params.network_name
486        );
487        assert_eq!(
488            context.validation_rules.max_block_size,
489            deserialized.validation_rules.max_block_size
490        );
491    }
492
493    #[test]
494    fn test_validation_context_equality() {
495        let context1 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
496        let context2 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
497        let context3 = ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
498
499        assert_eq!(context1, context2);
500        assert_ne!(context1, context3); // Different network parameters
501    }
502
503    #[test]
504    fn test_block_size_validation() {
505        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
506        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
507
508        // Create a block that's within size limits with a valid coinbase transaction
509        let coinbase_tx = Transaction {
510            version: 1,
511            inputs: blvm_consensus::tx_inputs![TransactionInput {
512                prevout: OutPoint {
513                    hash: [0u8; 32],
514                    index: 0xffffffff,
515                },
516                script_sig: vec![0x01, 0x00], // Height 0
517                sequence: 0xffffffff,
518            }],
519            outputs: blvm_consensus::tx_outputs![TransactionOutput {
520                value: 50_0000_0000,
521                script_pubkey: vec![blvm_consensus::opcodes::OP_1],
522            }],
523            lock_time: 0,
524        };
525
526        // Calculate proper merkle root
527        let merkle_root = blvm_consensus::mining::calculate_merkle_root(&[coinbase_tx.clone()])
528            .expect("Should calculate merkle root");
529
530        let small_block = Block {
531            header: BlockHeader {
532                version: 1,
533                prev_block_hash: [0u8; 32],
534                merkle_root,
535                timestamp: 1231006505,
536                bits: 0x1d00ffff,
537                nonce: 0,
538            },
539            transactions: vec![coinbase_tx].into_boxed_slice(),
540        };
541
542        // This should pass validation
543        let result =
544            engine.validate_block_with_protocol(&small_block, &UtxoSet::default(), 1000, &context);
545        assert!(result.is_ok());
546    }
547
548    #[test]
549    fn test_transaction_size_validation() {
550        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
551        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
552
553        // Create a small transaction
554        let small_tx = Transaction {
555            version: 1,
556            inputs: vec![TransactionInput {
557                prevout: OutPoint {
558                    hash: [0u8; 32],
559                    index: 0,
560                },
561                script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
562                sequence: 0xffffffff,
563            }]
564            .into(),
565            outputs: vec![TransactionOutput {
566                value: 50_0000_0000,
567                script_pubkey: vec![
568                    blvm_consensus::opcodes::OP_DUP,
569                    blvm_consensus::opcodes::OP_HASH160,
570                    blvm_consensus::opcodes::PUSH_20_BYTES,
571                    0x00,
572                    0x00,
573                    0x00,
574                    0x00,
575                    0x00,
576                    0x00,
577                    0x00,
578                    0x00,
579                    0x00,
580                    0x00,
581                    0x00,
582                    0x00,
583                    0x00,
584                    0x00,
585                    0x00,
586                    0x00,
587                    0x00,
588                    0x00,
589                    0x00,
590                    0x00,
591                    blvm_consensus::opcodes::OP_EQUALVERIFY,
592                    blvm_consensus::opcodes::OP_CHECKSIG,
593                ],
594            }]
595            .into(),
596            lock_time: 0,
597        };
598
599        // This should pass validation
600        let result = engine.validate_transaction_with_protocol(&small_tx, &context);
601        assert!(result.is_ok());
602    }
603
604    #[test]
605    fn test_script_size_validation() {
606        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
607        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
608
609        // Create a transaction with small scripts
610        let tx = Transaction {
611            version: 1,
612            inputs: vec![TransactionInput {
613                prevout: OutPoint {
614                    hash: [0u8; 32],
615                    index: 0,
616                },
617                script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
618                sequence: 0xffffffff,
619            }]
620            .into(),
621            outputs: vec![TransactionOutput {
622                value: 50_0000_0000,
623                script_pubkey: vec![
624                    blvm_consensus::opcodes::OP_DUP,
625                    blvm_consensus::opcodes::OP_HASH160,
626                    blvm_consensus::opcodes::PUSH_20_BYTES,
627                    0x00,
628                    0x00,
629                    0x00,
630                    0x00,
631                    0x00,
632                    0x00,
633                    0x00,
634                    0x00,
635                    0x00,
636                    0x00,
637                    0x00,
638                    0x00,
639                    0x00,
640                    0x00,
641                    0x00,
642                    0x00,
643                    0x00,
644                    0x00,
645                    0x00,
646                    0x00,
647                    blvm_consensus::opcodes::OP_EQUALVERIFY,
648                    blvm_consensus::opcodes::OP_CHECKSIG,
649                ],
650            }]
651            .into(),
652            lock_time: 0,
653        };
654
655        // This should pass validation
656        let result = engine.validate_transaction_with_protocol(&tx, &context);
657        assert!(result.is_ok());
658    }
659
660    #[test]
661    fn test_validation_context_data() {
662        let mut context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
663
664        // Add some context data
665        context
666            .context_data
667            .insert("test_key".to_string(), "test_value".to_string());
668
669        assert_eq!(
670            context.context_data.get("test_key"),
671            Some(&"test_value".to_string())
672        );
673        assert_eq!(context.context_data.get("nonexistent"), None);
674    }
675
676    #[test]
677    fn test_validation_rules_boundary_values() {
678        let rules = ProtocolValidationRules::mainnet();
679
680        // Test boundary values
681        assert!(rules.max_block_size > 0);
682        assert!(rules.max_tx_size > 0);
683        assert!(rules.max_script_size > 0);
684        assert!(rules.max_fee_rate > rules.min_fee_rate);
685
686        // Test that limits are reasonable
687        assert!(rules.max_block_size <= 10_000_000); // Not unreasonably large
688        assert!(rules.max_tx_size <= 5_000_000); // Not unreasonably large
689        assert!(rules.max_script_size <= 50_000); // Not unreasonably large
690    }
691}