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