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 weight limits (BIP141).
189        // `max_block_size` is in weight units (4,000,000 WU = 4MB weight).
190        // We only have stripped (no-witness) bytes here; each stripped byte costs 4 WU, so
191        // `stripped_bytes * 4` is a conservative upper bound on block weight.
192        // Full weight (with witness discount) is computed later when witness data is available.
193        let stripped_size = self.calculate_block_size(block);
194        let block_weight_upper = stripped_size.saturating_mul(4);
195        if block_weight_upper > context.validation_rules.max_block_size {
196            return Err(ProtocolError::Validation(
197                format!(
198                    "Block weight exceeds maximum: {} WU (max {} WU); stripped_size={}",
199                    block_weight_upper, context.validation_rules.max_block_size, stripped_size
200                )
201                .into(),
202            ));
203        }
204
205        // Check transaction count limits
206        if block.transactions.len() > 10000 {
207            // Reasonable limit
208            return Err(ProtocolError::Validation(
209                "Too many transactions in block (max 10000)".into(),
210            ));
211        }
212
213        // Validate each transaction with protocol rules
214        for tx in &block.transactions {
215            self.apply_transaction_protocol_validation(tx, context)?;
216        }
217
218        Ok(())
219    }
220
221    /// Apply protocol-specific transaction validation
222    fn apply_transaction_protocol_validation(
223        &self,
224        tx: &Transaction,
225        context: &ProtocolValidationContext,
226    ) -> Result<()> {
227        // Check transaction size limits
228        let tx_size = self.calculate_transaction_size(tx);
229        if tx_size > context.validation_rules.max_tx_size {
230            return Err(ProtocolError::Validation(
231                format!(
232                    "Transaction size exceeds maximum: {} bytes (max {} bytes)",
233                    tx_size, context.validation_rules.max_tx_size
234                )
235                .into(),
236            ));
237        }
238
239        // Check script size limits
240        for input in &tx.inputs {
241            if input.script_sig.len() > context.validation_rules.max_script_size as usize {
242                return Err(ProtocolError::Validation(
243                    format!(
244                        "Script size exceeds maximum: {} bytes (max {} bytes)",
245                        input.script_sig.len(),
246                        context.validation_rules.max_script_size
247                    )
248                    .into(),
249                ));
250            }
251        }
252
253        for output in &tx.outputs {
254            if output.script_pubkey.len() > context.validation_rules.max_script_size as usize {
255                return Err(ProtocolError::Validation(
256                    format!(
257                        "Script size exceeds maximum: {} bytes (max {} bytes)",
258                        output.script_pubkey.len(),
259                        context.validation_rules.max_script_size
260                    )
261                    .into(),
262                ));
263            }
264        }
265
266        Ok(())
267    }
268
269    /// Calculate block size in bytes
270    fn calculate_block_size(&self, block: &Block) -> u32 {
271        // Simplified size calculation
272        // In reality, this would include proper serialization
273        let header_size = 80; // Block header is always 80 bytes
274        let tx_count_size = 4; // Varint for transaction count
275        let tx_sizes: u32 = block
276            .transactions
277            .iter()
278            .map(|tx| self.calculate_transaction_size(tx))
279            .sum();
280
281        header_size + tx_count_size + tx_sizes
282    }
283
284    /// Calculate transaction size in bytes
285    fn calculate_transaction_size(&self, tx: &Transaction) -> u32 {
286        // Use canonical serialization-based size from consensus layer (TX_NO_WITNESS size).
287        //
288        // This keeps protocol-level size limits aligned with the exact serialization
289        // used for consensus checks and transaction size tests.
290        blvm_consensus::transaction::calculate_transaction_size(tx) as u32
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297    use blvm_consensus::types::{OutPoint, TransactionInput, TransactionOutput};
298    use blvm_consensus::{Block, BlockHeader, Transaction};
299
300    #[test]
301    fn test_validation_rules() {
302        let mainnet_rules = ProtocolValidationRules::mainnet();
303        assert_eq!(mainnet_rules.max_block_size, 4_000_000);
304        assert!(mainnet_rules.segwit_enabled);
305        assert!(mainnet_rules.taproot_enabled);
306
307        let regtest_rules = ProtocolValidationRules::regtest();
308        assert_eq!(regtest_rules.max_block_size, 4_000_000);
309        assert!(regtest_rules.segwit_enabled);
310        assert_eq!(regtest_rules.min_fee_rate, 0); // No minimum fee for testing
311    }
312
313    #[test]
314    fn test_validation_rules_all_protocols() {
315        let mainnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::BitcoinV1);
316        let testnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Testnet3);
317        let regtest_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Regtest);
318
319        // Mainnet and testnet should have same rules
320        assert_eq!(mainnet_rules.max_block_size, testnet_rules.max_block_size);
321        assert_eq!(mainnet_rules.max_tx_size, testnet_rules.max_tx_size);
322        assert_eq!(mainnet_rules.max_script_size, testnet_rules.max_script_size);
323        assert_eq!(mainnet_rules.segwit_enabled, testnet_rules.segwit_enabled);
324        assert_eq!(mainnet_rules.taproot_enabled, testnet_rules.taproot_enabled);
325        assert_eq!(mainnet_rules.rbf_enabled, testnet_rules.rbf_enabled);
326        assert_eq!(mainnet_rules.min_fee_rate, testnet_rules.min_fee_rate);
327        assert_eq!(mainnet_rules.max_fee_rate, testnet_rules.max_fee_rate);
328
329        // Regtest should have relaxed fee rules
330        assert_eq!(regtest_rules.min_fee_rate, 0);
331        assert_eq!(regtest_rules.max_fee_rate, mainnet_rules.max_fee_rate);
332    }
333
334    #[test]
335    fn test_validation_rules_serialization() {
336        let mainnet_rules = ProtocolValidationRules::mainnet();
337        let json = serde_json::to_string(&mainnet_rules).unwrap();
338        let deserialized: ProtocolValidationRules = serde_json::from_str(&json).unwrap();
339
340        assert_eq!(mainnet_rules.max_block_size, deserialized.max_block_size);
341        assert_eq!(mainnet_rules.max_tx_size, deserialized.max_tx_size);
342        assert_eq!(mainnet_rules.max_script_size, deserialized.max_script_size);
343        assert_eq!(mainnet_rules.segwit_enabled, deserialized.segwit_enabled);
344        assert_eq!(mainnet_rules.taproot_enabled, deserialized.taproot_enabled);
345        assert_eq!(mainnet_rules.rbf_enabled, deserialized.rbf_enabled);
346        assert_eq!(mainnet_rules.min_fee_rate, deserialized.min_fee_rate);
347        assert_eq!(mainnet_rules.max_fee_rate, deserialized.max_fee_rate);
348    }
349
350    #[test]
351    fn test_validation_rules_equality() {
352        let mainnet1 = ProtocolValidationRules::mainnet();
353        let mainnet2 = ProtocolValidationRules::mainnet();
354        let testnet = ProtocolValidationRules::testnet();
355
356        assert_eq!(mainnet1, mainnet2);
357        assert_eq!(mainnet1, testnet); // Mainnet and testnet should be identical
358    }
359
360    #[test]
361    fn test_validation_context() {
362        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
363        assert_eq!(context.block_height, 1000);
364        assert!(context.is_feature_enabled("segwit"));
365        assert!(!context.is_feature_enabled("nonexistent"));
366        assert_eq!(context.get_max_size("block"), 4_000_000);
367    }
368
369    #[test]
370    fn test_validation_context_all_protocols() {
371        let mainnet_context =
372            ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
373        let testnet_context =
374            ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
375        let regtest_context =
376            ProtocolValidationContext::new(ProtocolVersion::Regtest, 1000).unwrap();
377
378        // All should have same block height
379        assert_eq!(mainnet_context.block_height, 1000);
380        assert_eq!(testnet_context.block_height, 1000);
381        assert_eq!(regtest_context.block_height, 1000);
382
383        // All should support same features
384        assert!(mainnet_context.is_feature_enabled("segwit"));
385        assert!(testnet_context.is_feature_enabled("segwit"));
386        assert!(regtest_context.is_feature_enabled("segwit"));
387
388        assert!(mainnet_context.is_feature_enabled("taproot"));
389        assert!(testnet_context.is_feature_enabled("taproot"));
390        assert!(regtest_context.is_feature_enabled("taproot"));
391
392        assert!(mainnet_context.is_feature_enabled("rbf"));
393        assert!(testnet_context.is_feature_enabled("rbf"));
394        assert!(regtest_context.is_feature_enabled("rbf"));
395    }
396
397    #[test]
398    fn test_validation_context_feature_queries() {
399        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
400
401        // Test all supported features
402        assert!(context.is_feature_enabled("segwit"));
403        assert!(context.is_feature_enabled("taproot"));
404        assert!(context.is_feature_enabled("rbf"));
405
406        // Test unsupported features
407        assert!(!context.is_feature_enabled("nonexistent"));
408        assert!(!context.is_feature_enabled(""));
409        assert!(!context.is_feature_enabled("fast_mining"));
410    }
411
412    #[test]
413    fn test_validation_context_size_queries() {
414        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
415
416        assert_eq!(context.get_max_size("block"), 4_000_000);
417        assert_eq!(context.get_max_size("transaction"), 1_000_000);
418        assert_eq!(context.get_max_size("script"), 10_000);
419
420        // Test unknown component
421        assert_eq!(context.get_max_size("unknown"), 0);
422    }
423
424    #[test]
425    fn test_validation_context_serialization() {
426        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
427        let json = serde_json::to_string(&context).unwrap();
428        let deserialized: ProtocolValidationContext = serde_json::from_str(&json).unwrap();
429
430        assert_eq!(context.block_height, deserialized.block_height);
431        assert_eq!(
432            context.network_params.network_name,
433            deserialized.network_params.network_name
434        );
435        assert_eq!(
436            context.validation_rules.max_block_size,
437            deserialized.validation_rules.max_block_size
438        );
439    }
440
441    #[test]
442    fn test_validation_context_equality() {
443        let context1 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
444        let context2 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
445        let context3 = ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
446
447        assert_eq!(context1, context2);
448        assert_ne!(context1, context3); // Different network parameters
449    }
450
451    #[test]
452    fn test_block_size_validation() {
453        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
454        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
455
456        // Create a block that's within size limits with a valid coinbase transaction
457        let coinbase_tx = Transaction {
458            version: 1,
459            inputs: blvm_consensus::tx_inputs![TransactionInput {
460                prevout: OutPoint {
461                    hash: [0u8; 32],
462                    index: 0xffffffff,
463                },
464                script_sig: vec![0x01, 0x00], // Height 0
465                sequence: 0xffffffff,
466            }],
467            outputs: blvm_consensus::tx_outputs![TransactionOutput {
468                value: 50_0000_0000,
469                script_pubkey: vec![blvm_consensus::opcodes::OP_1],
470            }],
471            lock_time: 0,
472        };
473
474        // Calculate proper merkle root
475        let merkle_root = blvm_consensus::mining::calculate_merkle_root(&[coinbase_tx.clone()])
476            .expect("Should calculate merkle root");
477
478        let small_block = Block {
479            header: BlockHeader {
480                version: 1,
481                prev_block_hash: [0u8; 32],
482                merkle_root,
483                timestamp: 1231006505,
484                bits: 0x1d00ffff,
485                nonce: 0,
486            },
487            transactions: vec![coinbase_tx].into_boxed_slice(),
488        };
489
490        // This should pass validation
491        let result =
492            engine.validate_block_with_protocol(&small_block, &UtxoSet::default(), 1000, &context);
493        assert!(result.is_ok());
494    }
495
496    #[test]
497    fn test_transaction_size_validation() {
498        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
499        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
500
501        // Create a small transaction
502        let small_tx = Transaction {
503            version: 1,
504            inputs: vec![TransactionInput {
505                prevout: OutPoint {
506                    hash: [0u8; 32],
507                    index: 0,
508                },
509                script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
510                sequence: 0xffffffff,
511            }]
512            .into(),
513            outputs: vec![TransactionOutput {
514                value: 50_0000_0000,
515                script_pubkey: vec![
516                    blvm_consensus::opcodes::OP_DUP,
517                    blvm_consensus::opcodes::OP_HASH160,
518                    blvm_consensus::opcodes::PUSH_20_BYTES,
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                    0x00,
536                    0x00,
537                    0x00,
538                    0x00,
539                    blvm_consensus::opcodes::OP_EQUALVERIFY,
540                    blvm_consensus::opcodes::OP_CHECKSIG,
541                ],
542            }]
543            .into(),
544            lock_time: 0,
545        };
546
547        // This should pass validation
548        let result = engine.validate_transaction_with_protocol(&small_tx, &context);
549        assert!(result.is_ok());
550    }
551
552    #[test]
553    fn test_script_size_validation() {
554        let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
555        let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
556
557        // Create a transaction with small scripts
558        let tx = Transaction {
559            version: 1,
560            inputs: vec![TransactionInput {
561                prevout: OutPoint {
562                    hash: [0u8; 32],
563                    index: 0,
564                },
565                script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
566                sequence: 0xffffffff,
567            }]
568            .into(),
569            outputs: vec![TransactionOutput {
570                value: 50_0000_0000,
571                script_pubkey: vec![
572                    blvm_consensus::opcodes::OP_DUP,
573                    blvm_consensus::opcodes::OP_HASH160,
574                    blvm_consensus::opcodes::PUSH_20_BYTES,
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                    0x00,
592                    0x00,
593                    0x00,
594                    0x00,
595                    blvm_consensus::opcodes::OP_EQUALVERIFY,
596                    blvm_consensus::opcodes::OP_CHECKSIG,
597                ],
598            }]
599            .into(),
600            lock_time: 0,
601        };
602
603        // This should pass validation
604        let result = engine.validate_transaction_with_protocol(&tx, &context);
605        assert!(result.is_ok());
606    }
607
608    #[test]
609    fn test_validation_context_data() {
610        let mut context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
611
612        // Add some context data
613        context
614            .context_data
615            .insert("test_key".to_string(), "test_value".to_string());
616
617        assert_eq!(
618            context.context_data.get("test_key"),
619            Some(&"test_value".to_string())
620        );
621        assert_eq!(context.context_data.get("nonexistent"), None);
622    }
623
624    #[test]
625    fn test_validation_rules_boundary_values() {
626        let rules = ProtocolValidationRules::mainnet();
627
628        // Test boundary values
629        assert!(rules.max_block_size > 0);
630        assert!(rules.max_tx_size > 0);
631        assert!(rules.max_script_size > 0);
632        assert!(rules.max_fee_rate > rules.min_fee_rate);
633
634        // Test that limits are reasonable
635        assert!(rules.max_block_size <= 10_000_000); // Not unreasonably large
636        assert!(rules.max_tx_size <= 5_000_000); // Not unreasonably large
637        assert!(rules.max_script_size <= 50_000); // Not unreasonably large
638    }
639}