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