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
295    #[test]
296    fn test_validation_rules() {
297        let mainnet_rules = ProtocolValidationRules::mainnet();
298        assert_eq!(mainnet_rules.max_block_size, 4_000_000);
299        assert!(mainnet_rules.segwit_enabled);
300        assert!(mainnet_rules.taproot_enabled);
301
302        let regtest_rules = ProtocolValidationRules::regtest();
303        assert_eq!(regtest_rules.max_block_size, 4_000_000);
304        assert!(regtest_rules.segwit_enabled);
305        assert_eq!(regtest_rules.min_fee_rate, 0); // No minimum fee for testing
306    }
307
308    #[test]
309    fn test_validation_rules_all_protocols() {
310        let mainnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::BitcoinV1);
311        let testnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Testnet3);
312        let regtest_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Regtest);
313
314        // Mainnet and testnet should have same rules
315        assert_eq!(mainnet_rules.max_block_size, testnet_rules.max_block_size);
316        assert_eq!(mainnet_rules.max_tx_size, testnet_rules.max_tx_size);
317        assert_eq!(mainnet_rules.max_script_size, testnet_rules.max_script_size);
318        assert_eq!(mainnet_rules.segwit_enabled, testnet_rules.segwit_enabled);
319        assert_eq!(mainnet_rules.taproot_enabled, testnet_rules.taproot_enabled);
320        assert_eq!(mainnet_rules.rbf_enabled, testnet_rules.rbf_enabled);
321        assert_eq!(mainnet_rules.min_fee_rate, testnet_rules.min_fee_rate);
322        assert_eq!(mainnet_rules.max_fee_rate, testnet_rules.max_fee_rate);
323
324        // Regtest should have relaxed fee rules
325        assert_eq!(regtest_rules.min_fee_rate, 0);
326        assert_eq!(regtest_rules.max_fee_rate, mainnet_rules.max_fee_rate);
327    }
328
329    #[test]
330    fn test_validation_rules_serialization() {
331        let mainnet_rules = ProtocolValidationRules::mainnet();
332        let json = serde_json::to_string(&mainnet_rules).unwrap();
333        let deserialized: ProtocolValidationRules = serde_json::from_str(&json).unwrap();
334
335        assert_eq!(mainnet_rules.max_block_size, deserialized.max_block_size);
336        assert_eq!(mainnet_rules.max_tx_size, deserialized.max_tx_size);
337        assert_eq!(mainnet_rules.max_script_size, deserialized.max_script_size);
338        assert_eq!(mainnet_rules.segwit_enabled, deserialized.segwit_enabled);
339        assert_eq!(mainnet_rules.taproot_enabled, deserialized.taproot_enabled);
340        assert_eq!(mainnet_rules.rbf_enabled, deserialized.rbf_enabled);
341        assert_eq!(mainnet_rules.min_fee_rate, deserialized.min_fee_rate);
342        assert_eq!(mainnet_rules.max_fee_rate, deserialized.max_fee_rate);
343    }
344
345    #[test]
346    fn test_validation_rules_equality() {
347        let mainnet1 = ProtocolValidationRules::mainnet();
348        let mainnet2 = ProtocolValidationRules::mainnet();
349        let testnet = ProtocolValidationRules::testnet();
350
351        assert_eq!(mainnet1, mainnet2);
352        assert_eq!(mainnet1, testnet); // Mainnet and testnet should be identical
353    }
354
355    #[test]
356    fn test_validation_context() {
357        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
358        assert_eq!(context.block_height, 1000);
359        assert!(context.is_feature_enabled("segwit"));
360        assert!(!context.is_feature_enabled("nonexistent"));
361        assert_eq!(context.get_max_size("block"), 4_000_000);
362    }
363
364    #[test]
365    fn test_validation_context_all_protocols() {
366        let mainnet_context =
367            ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
368        let testnet_context =
369            ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
370        let regtest_context =
371            ProtocolValidationContext::new(ProtocolVersion::Regtest, 1000).unwrap();
372
373        // All should have same block height
374        assert_eq!(mainnet_context.block_height, 1000);
375        assert_eq!(testnet_context.block_height, 1000);
376        assert_eq!(regtest_context.block_height, 1000);
377
378        // All should support same features
379        assert!(mainnet_context.is_feature_enabled("segwit"));
380        assert!(testnet_context.is_feature_enabled("segwit"));
381        assert!(regtest_context.is_feature_enabled("segwit"));
382
383        assert!(mainnet_context.is_feature_enabled("taproot"));
384        assert!(testnet_context.is_feature_enabled("taproot"));
385        assert!(regtest_context.is_feature_enabled("taproot"));
386
387        assert!(mainnet_context.is_feature_enabled("rbf"));
388        assert!(testnet_context.is_feature_enabled("rbf"));
389        assert!(regtest_context.is_feature_enabled("rbf"));
390    }
391
392    #[test]
393    fn test_validation_context_feature_queries() {
394        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
395
396        // Test all supported features
397        assert!(context.is_feature_enabled("segwit"));
398        assert!(context.is_feature_enabled("taproot"));
399        assert!(context.is_feature_enabled("rbf"));
400
401        // Test unsupported features
402        assert!(!context.is_feature_enabled("nonexistent"));
403        assert!(!context.is_feature_enabled(""));
404        assert!(!context.is_feature_enabled("fast_mining"));
405    }
406
407    #[test]
408    fn test_validation_context_size_queries() {
409        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
410
411        assert_eq!(context.get_max_size("block"), 4_000_000);
412        assert_eq!(context.get_max_size("transaction"), 1_000_000);
413        assert_eq!(context.get_max_size("script"), 10_000);
414
415        // Test unknown component
416        assert_eq!(context.get_max_size("unknown"), 0);
417    }
418
419    #[test]
420    fn test_validation_context_serialization() {
421        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
422        let json = serde_json::to_string(&context).unwrap();
423        let deserialized: ProtocolValidationContext = serde_json::from_str(&json).unwrap();
424
425        assert_eq!(context.block_height, deserialized.block_height);
426        assert_eq!(
427            context.network_params.network_name,
428            deserialized.network_params.network_name
429        );
430        assert_eq!(
431            context.validation_rules.max_block_size,
432            deserialized.validation_rules.max_block_size
433        );
434    }
435
436    #[test]
437    fn test_validation_context_equality() {
438        let context1 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
439        let context2 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
440        let context3 = ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
441
442        assert_eq!(context1, context2);
443        assert_ne!(context1, context3); // Different network parameters
444    }
445
446    #[test]
447    fn test_block_size_validation() {
448        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
449        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
450
451        // Create a block that's within size limits with a valid coinbase transaction
452        let coinbase_tx = Transaction {
453            version: 1,
454            inputs: blvm_consensus::tx_inputs![TransactionInput {
455                prevout: OutPoint {
456                    hash: [0u8; 32],
457                    index: 0xffffffff,
458                },
459                script_sig: vec![0x01, 0x00], // Height 0
460                sequence: 0xffffffff,
461            }],
462            outputs: blvm_consensus::tx_outputs![TransactionOutput {
463                value: 50_0000_0000,
464                script_pubkey: vec![blvm_consensus::opcodes::OP_1],
465            }],
466            lock_time: 0,
467        };
468
469        // Calculate proper merkle root
470        let merkle_root = blvm_consensus::mining::calculate_merkle_root(&[coinbase_tx.clone()])
471            .expect("Should calculate merkle root");
472
473        let small_block = Block {
474            header: BlockHeader {
475                version: 1,
476                prev_block_hash: [0u8; 32],
477                merkle_root,
478                timestamp: 1231006505,
479                bits: 0x1d00ffff,
480                nonce: 0,
481            },
482            transactions: vec![coinbase_tx].into_boxed_slice(),
483        };
484
485        // This should pass validation
486        let result =
487            engine.validate_block_with_protocol(&small_block, &UtxoSet::default(), 1000, &context);
488        assert!(result.is_ok());
489    }
490
491    #[test]
492    fn test_transaction_size_validation() {
493        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
494        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
495
496        // Create a small transaction
497        let small_tx = Transaction {
498            version: 1,
499            inputs: vec![TransactionInput {
500                prevout: OutPoint {
501                    hash: [0u8; 32],
502                    index: 0,
503                },
504                script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
505                sequence: 0xffffffff,
506            }]
507            .into(),
508            outputs: vec![TransactionOutput {
509                value: 50_0000_0000,
510                script_pubkey: vec![
511                    blvm_consensus::opcodes::OP_DUP,
512                    blvm_consensus::opcodes::OP_HASH160,
513                    blvm_consensus::opcodes::PUSH_20_BYTES,
514                    0x00,
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                    blvm_consensus::opcodes::OP_EQUALVERIFY,
535                    blvm_consensus::opcodes::OP_CHECKSIG,
536                ],
537            }]
538            .into(),
539            lock_time: 0,
540        };
541
542        // This should pass validation
543        let result = engine.validate_transaction_with_protocol(&small_tx, &context);
544        assert!(result.is_ok());
545    }
546
547    #[test]
548    fn test_script_size_validation() {
549        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
550        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
551
552        // Create a transaction with small scripts
553        let tx = Transaction {
554            version: 1,
555            inputs: vec![TransactionInput {
556                prevout: OutPoint {
557                    hash: [0u8; 32],
558                    index: 0,
559                },
560                script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
561                sequence: 0xffffffff,
562            }]
563            .into(),
564            outputs: vec![TransactionOutput {
565                value: 50_0000_0000,
566                script_pubkey: vec![
567                    blvm_consensus::opcodes::OP_DUP,
568                    blvm_consensus::opcodes::OP_HASH160,
569                    blvm_consensus::opcodes::PUSH_20_BYTES,
570                    0x00,
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                    blvm_consensus::opcodes::OP_EQUALVERIFY,
591                    blvm_consensus::opcodes::OP_CHECKSIG,
592                ],
593            }]
594            .into(),
595            lock_time: 0,
596        };
597
598        // This should pass validation
599        let result = engine.validate_transaction_with_protocol(&tx, &context);
600        assert!(result.is_ok());
601    }
602
603    #[test]
604    fn test_validation_context_data() {
605        let mut context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
606
607        // Add some context data
608        context
609            .context_data
610            .insert("test_key".to_string(), "test_value".to_string());
611
612        assert_eq!(
613            context.context_data.get("test_key"),
614            Some(&"test_value".to_string())
615        );
616        assert_eq!(context.context_data.get("nonexistent"), None);
617    }
618
619    #[test]
620    fn test_validation_rules_boundary_values() {
621        let rules = ProtocolValidationRules::mainnet();
622
623        // Test boundary values
624        assert!(rules.max_block_size > 0);
625        assert!(rules.max_tx_size > 0);
626        assert!(rules.max_script_size > 0);
627        assert!(rules.max_fee_rate > rules.min_fee_rate);
628
629        // Test that limits are reasonable
630        assert!(rules.max_block_size <= 10_000_000); // Not unreasonably large
631        assert!(rules.max_tx_size <= 5_000_000); // Not unreasonably large
632        assert!(rules.max_script_size <= 50_000); // Not unreasonably large
633    }
634}