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 the
229    /// stripped block: non-witness bytes at ×4. It cannot catch a block that exceeds the
230    /// limit only because of witness data. Prefer
231    /// [`apply_protocol_validation_with_witnesses`] when witness data is available.
232    fn apply_protocol_validation(
233        &self,
234        block: &Block,
235        context: &ProtocolValidationContext,
236    ) -> Result<()> {
237        self.apply_protocol_validation_with_witnesses(block, None, context)
238    }
239
240    /// Apply protocol-specific validation rules with optional witness data.
241    ///
242    /// Block weight is [`blvm_consensus::segwit::calculate_block_weight_from_nested`]:
243    /// non-witness bytes at ×4, and the marker, flag, stack counts, and witness
244    /// elements at ×1. A missing witness list is the stripped block.
245    fn apply_protocol_validation_with_witnesses(
246        &self,
247        block: &Block,
248        witnesses: Option<&[Vec<blvm_consensus::segwit::Witness>]>,
249        context: &ProtocolValidationContext,
250    ) -> Result<()> {
251        let witness_list = witnesses.unwrap_or(&[]);
252        let weight =
253            blvm_consensus::segwit::calculate_block_weight_from_nested(block, witness_list)?;
254        if weight > context.validation_rules.max_block_size as u64 {
255            return Err(ProtocolError::Validation(
256                format!(
257                    "Block weight exceeds maximum: {} WU (max {} WU)",
258                    weight, context.validation_rules.max_block_size
259                )
260                .into(),
261            ));
262        }
263
264        for tx in &block.transactions {
265            self.apply_transaction_protocol_validation(tx, context)?;
266        }
267
268        Ok(())
269    }
270
271    /// Apply protocol-specific transaction validation
272    fn apply_transaction_protocol_validation(
273        &self,
274        tx: &Transaction,
275        context: &ProtocolValidationContext,
276    ) -> Result<()> {
277        // Check transaction size limits
278        let tx_size = self.calculate_transaction_size(tx);
279        if tx_size > context.validation_rules.max_tx_size {
280            return Err(ProtocolError::Validation(
281                format!(
282                    "Transaction size exceeds maximum: {} bytes (max {} bytes)",
283                    tx_size, context.validation_rules.max_tx_size
284                )
285                .into(),
286            ));
287        }
288
289        // Check script size limits
290        for input in &tx.inputs {
291            if input.script_sig.len() > context.validation_rules.max_script_size as usize {
292                return Err(ProtocolError::Validation(
293                    format!(
294                        "Script size exceeds maximum: {} bytes (max {} bytes)",
295                        input.script_sig.len(),
296                        context.validation_rules.max_script_size
297                    )
298                    .into(),
299                ));
300            }
301        }
302
303        for output in &tx.outputs {
304            if output.script_pubkey.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                        output.script_pubkey.len(),
309                        context.validation_rules.max_script_size
310                    )
311                    .into(),
312                ));
313            }
314        }
315
316        Ok(())
317    }
318
319    /// Calculate block size in bytes
320    /// Calculate transaction size in bytes
321    fn calculate_transaction_size(&self, tx: &Transaction) -> u32 {
322        // Use canonical serialization-based size from consensus layer (TX_NO_WITNESS size).
323        //
324        // This keeps protocol-level size limits aligned with the exact serialization
325        // used for consensus checks and transaction size tests.
326        blvm_consensus::transaction::calculate_transaction_size(tx) as u32
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use super::*;
333    use blvm_consensus::types::{OutPoint, TransactionInput, TransactionOutput};
334    use blvm_consensus::{Block, BlockHeader, Transaction};
335
336    #[test]
337    fn test_validation_rules() {
338        let mainnet_rules = ProtocolValidationRules::mainnet();
339        assert_eq!(mainnet_rules.max_block_size, 4_000_000);
340        assert!(mainnet_rules.segwit_enabled);
341        assert!(mainnet_rules.taproot_enabled);
342
343        let regtest_rules = ProtocolValidationRules::regtest();
344        assert_eq!(regtest_rules.max_block_size, 4_000_000);
345        assert!(regtest_rules.segwit_enabled);
346        assert_eq!(regtest_rules.min_fee_rate, 0); // No minimum fee for testing
347    }
348
349    #[test]
350    fn test_validation_rules_all_protocols() {
351        let mainnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::BitcoinV1);
352        let testnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Testnet3);
353        let regtest_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Regtest);
354
355        // Mainnet and testnet should have same rules
356        assert_eq!(mainnet_rules.max_block_size, testnet_rules.max_block_size);
357        assert_eq!(mainnet_rules.max_tx_size, testnet_rules.max_tx_size);
358        assert_eq!(mainnet_rules.max_script_size, testnet_rules.max_script_size);
359        assert_eq!(mainnet_rules.segwit_enabled, testnet_rules.segwit_enabled);
360        assert_eq!(mainnet_rules.taproot_enabled, testnet_rules.taproot_enabled);
361        assert_eq!(mainnet_rules.rbf_enabled, testnet_rules.rbf_enabled);
362        assert_eq!(mainnet_rules.min_fee_rate, testnet_rules.min_fee_rate);
363        assert_eq!(mainnet_rules.max_fee_rate, testnet_rules.max_fee_rate);
364
365        // Regtest should have relaxed fee rules
366        assert_eq!(regtest_rules.min_fee_rate, 0);
367        assert_eq!(regtest_rules.max_fee_rate, mainnet_rules.max_fee_rate);
368    }
369
370    #[test]
371    fn test_validation_rules_serialization() {
372        let mainnet_rules = ProtocolValidationRules::mainnet();
373        let json = serde_json::to_string(&mainnet_rules).unwrap();
374        let deserialized: ProtocolValidationRules = serde_json::from_str(&json).unwrap();
375
376        assert_eq!(mainnet_rules.max_block_size, deserialized.max_block_size);
377        assert_eq!(mainnet_rules.max_tx_size, deserialized.max_tx_size);
378        assert_eq!(mainnet_rules.max_script_size, deserialized.max_script_size);
379        assert_eq!(mainnet_rules.segwit_enabled, deserialized.segwit_enabled);
380        assert_eq!(mainnet_rules.taproot_enabled, deserialized.taproot_enabled);
381        assert_eq!(mainnet_rules.rbf_enabled, deserialized.rbf_enabled);
382        assert_eq!(mainnet_rules.min_fee_rate, deserialized.min_fee_rate);
383        assert_eq!(mainnet_rules.max_fee_rate, deserialized.max_fee_rate);
384    }
385
386    #[test]
387    fn test_validation_rules_equality() {
388        let mainnet1 = ProtocolValidationRules::mainnet();
389        let mainnet2 = ProtocolValidationRules::mainnet();
390        let testnet = ProtocolValidationRules::testnet();
391
392        assert_eq!(mainnet1, mainnet2);
393        assert_eq!(mainnet1, testnet); // Mainnet and testnet should be identical
394    }
395
396    #[test]
397    fn test_validation_context() {
398        // Use a height past both segwit (481824) and taproot (709632) activation.
399        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
400        assert_eq!(context.block_height, 800_000);
401        assert!(context.is_feature_enabled("segwit"));
402        assert!(!context.is_feature_enabled("nonexistent"));
403        assert_eq!(context.get_max_size("block"), 4_000_000);
404    }
405
406    #[test]
407    fn test_validation_context_all_protocols() {
408        // Per-network heights past segwit+taproot activation:
409        //   mainnet: taproot at 709_632   → use 800_000
410        //   testnet3: taproot at 2_016_000 → use 2_100_000
411        //   regtest: all features AlwaysActive → any height works
412        let mainnet_context =
413            ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
414        let testnet_context =
415            ProtocolValidationContext::new(ProtocolVersion::Testnet3, 2_100_000).unwrap();
416        let regtest_context = ProtocolValidationContext::new(ProtocolVersion::Regtest, 1).unwrap();
417
418        assert_eq!(mainnet_context.block_height, 800_000);
419        assert_eq!(testnet_context.block_height, 2_100_000);
420        assert_eq!(regtest_context.block_height, 1);
421
422        // All should support same features
423        assert!(mainnet_context.is_feature_enabled("segwit"));
424        assert!(testnet_context.is_feature_enabled("segwit"));
425        assert!(regtest_context.is_feature_enabled("segwit"));
426
427        assert!(mainnet_context.is_feature_enabled("taproot"));
428        assert!(testnet_context.is_feature_enabled("taproot"));
429        assert!(regtest_context.is_feature_enabled("taproot"));
430
431        assert!(mainnet_context.is_feature_enabled("rbf"));
432        assert!(testnet_context.is_feature_enabled("rbf"));
433        assert!(regtest_context.is_feature_enabled("rbf"));
434    }
435
436    #[test]
437    fn test_validation_context_feature_queries() {
438        // Height past both segwit (481824) and taproot (709632) so all features are active.
439        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
440
441        // Test all supported features
442        assert!(context.is_feature_enabled("segwit"));
443        assert!(context.is_feature_enabled("taproot"));
444        assert!(context.is_feature_enabled("rbf"));
445
446        // Test unsupported features
447        assert!(!context.is_feature_enabled("nonexistent"));
448        assert!(!context.is_feature_enabled(""));
449        assert!(!context.is_feature_enabled("fast_mining"));
450    }
451
452    #[test]
453    fn test_validation_context_size_queries() {
454        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
455
456        assert_eq!(context.get_max_size("block"), 4_000_000);
457        assert_eq!(context.get_max_size("transaction"), 1_000_000);
458        assert_eq!(context.get_max_size("script"), 10_000);
459
460        // Test unknown component
461        assert_eq!(context.get_max_size("unknown"), 0);
462    }
463
464    #[test]
465    fn test_validation_context_serialization() {
466        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
467        let json = serde_json::to_string(&context).unwrap();
468        let deserialized: ProtocolValidationContext = serde_json::from_str(&json).unwrap();
469
470        assert_eq!(context.block_height, deserialized.block_height);
471        assert_eq!(
472            context.network_params.network_name,
473            deserialized.network_params.network_name
474        );
475        assert_eq!(
476            context.validation_rules.max_block_size,
477            deserialized.validation_rules.max_block_size
478        );
479    }
480
481    #[test]
482    fn test_validation_context_equality() {
483        let context1 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
484        let context2 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
485        let context3 = ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
486
487        assert_eq!(context1, context2);
488        assert_ne!(context1, context3); // Different network parameters
489    }
490
491    #[test]
492    fn test_block_size_validation() {
493        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
494        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
495
496        // Create a block that's within size limits with a valid coinbase transaction
497        let coinbase_tx = Transaction {
498            version: 1,
499            inputs: blvm_consensus::tx_inputs![TransactionInput {
500                prevout: OutPoint {
501                    hash: [0u8; 32],
502                    index: 0xffffffff,
503                },
504                script_sig: vec![0x01, 0x00], // Height 0
505                sequence: 0xffffffff,
506            }],
507            outputs: blvm_consensus::tx_outputs![TransactionOutput {
508                value: 50_0000_0000,
509                script_pubkey: vec![blvm_consensus::opcodes::OP_1],
510            }],
511            lock_time: 0,
512        };
513
514        // Calculate proper merkle root
515        let merkle_root = blvm_consensus::mining::calculate_merkle_root(&[coinbase_tx.clone()])
516            .expect("Should calculate merkle root");
517
518        let small_block = Block {
519            header: BlockHeader {
520                version: 1,
521                prev_block_hash: [0u8; 32],
522                merkle_root,
523                timestamp: 1231006505,
524                bits: 0x1d00ffff,
525                nonce: 0,
526            },
527            transactions: vec![coinbase_tx].into_boxed_slice(),
528        };
529
530        // This should pass validation
531        let result =
532            engine.validate_block_with_protocol(&small_block, &UtxoSet::default(), 1000, &context);
533        assert!(result.is_ok());
534    }
535
536    #[test]
537    fn test_transaction_size_validation() {
538        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
539        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
540
541        // Create a small transaction
542        let small_tx = Transaction {
543            version: 1,
544            inputs: vec![TransactionInput {
545                prevout: OutPoint {
546                    hash: [0u8; 32],
547                    index: 0,
548                },
549                script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
550                sequence: 0xffffffff,
551            }]
552            .into(),
553            outputs: vec![TransactionOutput {
554                value: 50_0000_0000,
555                script_pubkey: vec![
556                    blvm_consensus::opcodes::OP_DUP,
557                    blvm_consensus::opcodes::OP_HASH160,
558                    blvm_consensus::opcodes::PUSH_20_BYTES,
559                    0x00,
560                    0x00,
561                    0x00,
562                    0x00,
563                    0x00,
564                    0x00,
565                    0x00,
566                    0x00,
567                    0x00,
568                    0x00,
569                    0x00,
570                    0x00,
571                    0x00,
572                    0x00,
573                    0x00,
574                    0x00,
575                    0x00,
576                    0x00,
577                    0x00,
578                    0x00,
579                    blvm_consensus::opcodes::OP_EQUALVERIFY,
580                    blvm_consensus::opcodes::OP_CHECKSIG,
581                ],
582            }]
583            .into(),
584            lock_time: 0,
585        };
586
587        // This should pass validation
588        let result = engine.validate_transaction_with_protocol(&small_tx, &context);
589        assert!(result.is_ok());
590    }
591
592    #[test]
593    fn test_script_size_validation() {
594        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
595        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
596
597        // Create a transaction with small scripts
598        let tx = Transaction {
599            version: 1,
600            inputs: vec![TransactionInput {
601                prevout: OutPoint {
602                    hash: [0u8; 32],
603                    index: 0,
604                },
605                script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
606                sequence: 0xffffffff,
607            }]
608            .into(),
609            outputs: vec![TransactionOutput {
610                value: 50_0000_0000,
611                script_pubkey: vec![
612                    blvm_consensus::opcodes::OP_DUP,
613                    blvm_consensus::opcodes::OP_HASH160,
614                    blvm_consensus::opcodes::PUSH_20_BYTES,
615                    0x00,
616                    0x00,
617                    0x00,
618                    0x00,
619                    0x00,
620                    0x00,
621                    0x00,
622                    0x00,
623                    0x00,
624                    0x00,
625                    0x00,
626                    0x00,
627                    0x00,
628                    0x00,
629                    0x00,
630                    0x00,
631                    0x00,
632                    0x00,
633                    0x00,
634                    0x00,
635                    blvm_consensus::opcodes::OP_EQUALVERIFY,
636                    blvm_consensus::opcodes::OP_CHECKSIG,
637                ],
638            }]
639            .into(),
640            lock_time: 0,
641        };
642
643        // This should pass validation
644        let result = engine.validate_transaction_with_protocol(&tx, &context);
645        assert!(result.is_ok());
646    }
647
648    #[test]
649    fn test_validation_context_data() {
650        let mut context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
651
652        // Add some context data
653        context
654            .context_data
655            .insert("test_key".to_string(), "test_value".to_string());
656
657        assert_eq!(
658            context.context_data.get("test_key"),
659            Some(&"test_value".to_string())
660        );
661        assert_eq!(context.context_data.get("nonexistent"), None);
662    }
663
664    #[test]
665    fn test_validation_rules_boundary_values() {
666        let rules = ProtocolValidationRules::mainnet();
667
668        // Test boundary values
669        assert!(rules.max_block_size > 0);
670        assert!(rules.max_tx_size > 0);
671        assert!(rules.max_script_size > 0);
672        assert!(rules.max_fee_rate > rules.min_fee_rate);
673
674        // Test that limits are reasonable
675        assert!(rules.max_block_size <= 10_000_000); // Not unreasonably large
676        assert!(rules.max_tx_size <= 5_000_000); // Not unreasonably large
677        assert!(rules.max_script_size <= 50_000); // Not unreasonably large
678    }
679
680    #[test]
681    fn test_block_weight_counts_witness_element_bytes() {
682        use blvm_consensus::opcodes::OP_1;
683        use blvm_consensus::{
684            Block, BlockHeader, OutPoint, Transaction, TransactionInput, TransactionOutput,
685        };
686
687        let engine = BitcoinProtocolEngine::new(ProtocolVersion::Regtest).unwrap();
688        let mut context = ProtocolValidationContext::new(ProtocolVersion::Regtest, 1).unwrap();
689        context.validation_rules.max_block_size = 5_000;
690
691        let coinbase = Transaction {
692            version: 1,
693            inputs: vec![TransactionInput {
694                prevout: OutPoint {
695                    hash: [0u8; 32],
696                    index: 0xffffffff,
697                },
698                script_sig: vec![OP_1].into(),
699                sequence: 0xffffffff,
700            }]
701            .into(),
702            outputs: vec![TransactionOutput {
703                value: 50_0000_0000,
704                script_pubkey: vec![OP_1].into(),
705            }]
706            .into(),
707            lock_time: 0,
708        };
709        let tx = Transaction {
710            version: 2,
711            inputs: vec![TransactionInput {
712                prevout: OutPoint {
713                    hash: [1u8; 32],
714                    index: 0,
715                },
716                script_sig: vec![OP_1].into(),
717                sequence: 0xffffffff,
718            }]
719            .into(),
720            outputs: vec![TransactionOutput {
721                value: 1_000,
722                script_pubkey: vec![OP_1].into(),
723            }]
724            .into(),
725            lock_time: 0,
726        };
727        let block = Block {
728            header: BlockHeader {
729                version: 1,
730                prev_block_hash: [0u8; 32],
731                merkle_root: [0u8; 32],
732                timestamp: 1_700_000_000,
733                bits: 0x207fffff,
734                nonce: 0,
735            },
736            transactions: vec![coinbase, tx].into(),
737        };
738        // One heavy witness element (9000 bytes) — stack depth alone would be 1 WU (REV-P-07).
739        let heavy_elem = vec![0u8; 9000];
740        let witnesses: Vec<Vec<blvm_consensus::segwit::Witness>> =
741            vec![vec![vec![]], vec![vec![heavy_elem]]];
742
743        let result = engine.validate_block_with_protocol_and_witnesses(
744            &block,
745            &witnesses,
746            &UtxoSet::default(),
747            1,
748            &context,
749        );
750        assert!(
751            result.is_err(),
752            "heavy witness bytes must exceed reduced max_block_size weight limit"
753        );
754    }
755
756    #[test]
757    fn protocol_block_weight_matches_consensus_including_witness_framing() {
758        use blvm_consensus::opcodes::OP_1;
759        use blvm_consensus::{
760            Block, BlockHeader, OutPoint, Transaction, TransactionInput, TransactionOutput,
761        };
762
763        let engine = BitcoinProtocolEngine::new(ProtocolVersion::Regtest).unwrap();
764        let mut context = ProtocolValidationContext::new(ProtocolVersion::Regtest, 1).unwrap();
765        let coinbase = Transaction {
766            version: 1,
767            inputs: vec![TransactionInput {
768                prevout: OutPoint {
769                    hash: [0u8; 32],
770                    index: 0xffffffff,
771                },
772                script_sig: vec![OP_1].into(),
773                sequence: 0xffffffff,
774            }]
775            .into(),
776            outputs: vec![TransactionOutput {
777                value: 50_0000_0000,
778                script_pubkey: vec![OP_1].into(),
779            }]
780            .into(),
781            lock_time: 0,
782        };
783        let tx = Transaction {
784            version: 2,
785            inputs: vec![TransactionInput {
786                prevout: OutPoint {
787                    hash: [1u8; 32],
788                    index: 0,
789                },
790                script_sig: vec![OP_1].into(),
791                sequence: 0xffffffff,
792            }]
793            .into(),
794            outputs: vec![TransactionOutput {
795                value: 1_000,
796                script_pubkey: vec![OP_1].into(),
797            }]
798            .into(),
799            lock_time: 0,
800        };
801        let block = Block {
802            header: BlockHeader {
803                version: 1,
804                prev_block_hash: [0u8; 32],
805                merkle_root: [0u8; 32],
806                timestamp: 1_700_000_000,
807                bits: 0x207fffff,
808                nonce: 0,
809            },
810            transactions: vec![coinbase, tx].into(),
811        };
812        let witnesses: Vec<Vec<blvm_consensus::segwit::Witness>> =
813            vec![vec![vec![]], vec![vec![vec![0x11]]]];
814        let weight = blvm_consensus::segwit::calculate_block_weight_from_nested(&block, &witnesses)
815            .expect("witness stacks match the inputs");
816        context.validation_rules.max_block_size = weight as u32;
817        assert!(
818            engine
819                .validate_block_with_protocol_and_witnesses(
820                    &block,
821                    &witnesses,
822                    &UtxoSet::default(),
823                    1,
824                    &context,
825                )
826                .is_ok(),
827            "weight equal to the consensus weight is accepted"
828        );
829        context.validation_rules.max_block_size = (weight - 1) as u32;
830        assert!(
831            engine
832                .validate_block_with_protocol_and_witnesses(
833                    &block,
834                    &witnesses,
835                    &UtxoSet::default(),
836                    1,
837                    &context,
838                )
839                .is_err(),
840            "one weight unit under the consensus weight is rejected"
841        );
842    }
843}