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