1use 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
15type Result<T> = std::result::Result<T, ProtocolError>;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct ProtocolValidationRules {
21 pub max_block_size: u32,
23 pub max_tx_size: u32,
25 pub max_script_size: u32,
27 pub segwit_enabled: bool,
29 pub taproot_enabled: bool,
31 pub rbf_enabled: bool,
33 pub min_fee_rate: u64,
35 pub max_fee_rate: u64,
37}
38
39impl ProtocolValidationRules {
40 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 pub fn mainnet() -> Self {
53 Self {
54 max_block_size: 4_000_000, max_tx_size: 1_000_000, max_script_size: 10_000, segwit_enabled: true,
58 taproot_enabled: true,
59 rbf_enabled: true,
60 min_fee_rate: 1, max_fee_rate: 1_000_000, }
63 }
64
65 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 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, max_fee_rate: 1_000_000,
90 }
91 }
92
93 pub fn testnet4() -> Self {
95 Self::testnet()
96 }
97
98 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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct ProtocolValidationContext {
116 pub block_height: u64,
118 pub network_params: NetworkParameters,
120 pub validation_rules: ProtocolValidationRules,
122 pub feature_registry: FeatureRegistry,
127 pub median_time_past: u64,
132 pub network_time: u64,
137 pub context_data: HashMap<String, String>,
139}
140
141impl ProtocolValidationContext {
142 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 median_time_past: 0,
155 network_time: 0,
156 context_data: HashMap::new(),
157 })
158 }
159
160 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 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 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 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 pub fn validate_transaction_with_protocol(
213 &self,
214 tx: &Transaction,
215 context: &ProtocolValidationContext,
216 ) -> Result<ValidationResult> {
217 let consensus_result = self.consensus.validate_transaction(tx)?;
219
220 self.apply_transaction_protocol_validation(tx, context)?;
222
223 Ok(consensus_result)
224 }
225
226 fn apply_protocol_validation(
234 &self,
235 block: &Block,
236 context: &ProtocolValidationContext,
237 ) -> Result<()> {
238 self.apply_protocol_validation_with_witnesses(block, None, context)
239 }
240
241 fn apply_protocol_validation_with_witnesses(
249 &self,
250 block: &Block,
251 witnesses: Option<&[Vec<blvm_consensus::segwit::Witness>]>,
252 context: &ProtocolValidationContext,
253 ) -> Result<()> {
254 let stripped_size = self.calculate_block_size(block);
256 let weight = if let Some(wit) = witnesses {
257 let witness_bytes: u64 = wit
259 .iter()
260 .flat_map(|tx_wit| tx_wit.iter())
261 .flat_map(|stack| stack.iter())
262 .map(|elem| elem.len() as u64)
263 .sum();
264 stripped_size as u64 * 4 + witness_bytes
265 } else {
266 stripped_size as u64 * 4
267 };
268 if weight > context.validation_rules.max_block_size as u64 {
269 return Err(ProtocolError::Validation(
270 format!(
271 "Block weight exceeds maximum: {} WU (max {} WU)",
272 weight, context.validation_rules.max_block_size
273 )
274 .into(),
275 ));
276 }
277
278 if block.transactions.len() > 10000 {
279 return Err(ProtocolError::Validation(
280 "Too many transactions in block (max 10000)".into(),
281 ));
282 }
283
284 for tx in &block.transactions {
285 self.apply_transaction_protocol_validation(tx, context)?;
286 }
287
288 Ok(())
289 }
290
291 fn apply_transaction_protocol_validation(
293 &self,
294 tx: &Transaction,
295 context: &ProtocolValidationContext,
296 ) -> Result<()> {
297 let tx_size = self.calculate_transaction_size(tx);
299 if tx_size > context.validation_rules.max_tx_size {
300 return Err(ProtocolError::Validation(
301 format!(
302 "Transaction size exceeds maximum: {} bytes (max {} bytes)",
303 tx_size, context.validation_rules.max_tx_size
304 )
305 .into(),
306 ));
307 }
308
309 for input in &tx.inputs {
311 if input.script_sig.len() > context.validation_rules.max_script_size as usize {
312 return Err(ProtocolError::Validation(
313 format!(
314 "Script size exceeds maximum: {} bytes (max {} bytes)",
315 input.script_sig.len(),
316 context.validation_rules.max_script_size
317 )
318 .into(),
319 ));
320 }
321 }
322
323 for output in &tx.outputs {
324 if output.script_pubkey.len() > context.validation_rules.max_script_size as usize {
325 return Err(ProtocolError::Validation(
326 format!(
327 "Script size exceeds maximum: {} bytes (max {} bytes)",
328 output.script_pubkey.len(),
329 context.validation_rules.max_script_size
330 )
331 .into(),
332 ));
333 }
334 }
335
336 Ok(())
337 }
338
339 fn calculate_block_size(&self, block: &Block) -> u32 {
341 let header_size = 80; let tx_count_size = 4; let tx_sizes: u32 = block
345 .transactions
346 .iter()
347 .map(|tx| self.calculate_transaction_size(tx))
348 .sum();
349
350 header_size + tx_count_size + tx_sizes
351 }
352
353 fn calculate_transaction_size(&self, tx: &Transaction) -> u32 {
355 blvm_consensus::transaction::calculate_transaction_size(tx) as u32
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366 use blvm_consensus::types::{OutPoint, TransactionInput, TransactionOutput};
367 use blvm_consensus::{Block, BlockHeader, Transaction};
368
369 #[test]
370 fn test_validation_rules() {
371 let mainnet_rules = ProtocolValidationRules::mainnet();
372 assert_eq!(mainnet_rules.max_block_size, 4_000_000);
373 assert!(mainnet_rules.segwit_enabled);
374 assert!(mainnet_rules.taproot_enabled);
375
376 let regtest_rules = ProtocolValidationRules::regtest();
377 assert_eq!(regtest_rules.max_block_size, 4_000_000);
378 assert!(regtest_rules.segwit_enabled);
379 assert_eq!(regtest_rules.min_fee_rate, 0); }
381
382 #[test]
383 fn test_validation_rules_all_protocols() {
384 let mainnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::BitcoinV1);
385 let testnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Testnet3);
386 let regtest_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Regtest);
387
388 assert_eq!(mainnet_rules.max_block_size, testnet_rules.max_block_size);
390 assert_eq!(mainnet_rules.max_tx_size, testnet_rules.max_tx_size);
391 assert_eq!(mainnet_rules.max_script_size, testnet_rules.max_script_size);
392 assert_eq!(mainnet_rules.segwit_enabled, testnet_rules.segwit_enabled);
393 assert_eq!(mainnet_rules.taproot_enabled, testnet_rules.taproot_enabled);
394 assert_eq!(mainnet_rules.rbf_enabled, testnet_rules.rbf_enabled);
395 assert_eq!(mainnet_rules.min_fee_rate, testnet_rules.min_fee_rate);
396 assert_eq!(mainnet_rules.max_fee_rate, testnet_rules.max_fee_rate);
397
398 assert_eq!(regtest_rules.min_fee_rate, 0);
400 assert_eq!(regtest_rules.max_fee_rate, mainnet_rules.max_fee_rate);
401 }
402
403 #[test]
404 fn test_validation_rules_serialization() {
405 let mainnet_rules = ProtocolValidationRules::mainnet();
406 let json = serde_json::to_string(&mainnet_rules).unwrap();
407 let deserialized: ProtocolValidationRules = serde_json::from_str(&json).unwrap();
408
409 assert_eq!(mainnet_rules.max_block_size, deserialized.max_block_size);
410 assert_eq!(mainnet_rules.max_tx_size, deserialized.max_tx_size);
411 assert_eq!(mainnet_rules.max_script_size, deserialized.max_script_size);
412 assert_eq!(mainnet_rules.segwit_enabled, deserialized.segwit_enabled);
413 assert_eq!(mainnet_rules.taproot_enabled, deserialized.taproot_enabled);
414 assert_eq!(mainnet_rules.rbf_enabled, deserialized.rbf_enabled);
415 assert_eq!(mainnet_rules.min_fee_rate, deserialized.min_fee_rate);
416 assert_eq!(mainnet_rules.max_fee_rate, deserialized.max_fee_rate);
417 }
418
419 #[test]
420 fn test_validation_rules_equality() {
421 let mainnet1 = ProtocolValidationRules::mainnet();
422 let mainnet2 = ProtocolValidationRules::mainnet();
423 let testnet = ProtocolValidationRules::testnet();
424
425 assert_eq!(mainnet1, mainnet2);
426 assert_eq!(mainnet1, testnet); }
428
429 #[test]
430 fn test_validation_context() {
431 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
433 assert_eq!(context.block_height, 800_000);
434 assert!(context.is_feature_enabled("segwit"));
435 assert!(!context.is_feature_enabled("nonexistent"));
436 assert_eq!(context.get_max_size("block"), 4_000_000);
437 }
438
439 #[test]
440 fn test_validation_context_all_protocols() {
441 let mainnet_context =
446 ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
447 let testnet_context =
448 ProtocolValidationContext::new(ProtocolVersion::Testnet3, 2_100_000).unwrap();
449 let regtest_context = ProtocolValidationContext::new(ProtocolVersion::Regtest, 1).unwrap();
450
451 assert_eq!(mainnet_context.block_height, 800_000);
452 assert_eq!(testnet_context.block_height, 2_100_000);
453 assert_eq!(regtest_context.block_height, 1);
454
455 assert!(mainnet_context.is_feature_enabled("segwit"));
457 assert!(testnet_context.is_feature_enabled("segwit"));
458 assert!(regtest_context.is_feature_enabled("segwit"));
459
460 assert!(mainnet_context.is_feature_enabled("taproot"));
461 assert!(testnet_context.is_feature_enabled("taproot"));
462 assert!(regtest_context.is_feature_enabled("taproot"));
463
464 assert!(mainnet_context.is_feature_enabled("rbf"));
465 assert!(testnet_context.is_feature_enabled("rbf"));
466 assert!(regtest_context.is_feature_enabled("rbf"));
467 }
468
469 #[test]
470 fn test_validation_context_feature_queries() {
471 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
473
474 assert!(context.is_feature_enabled("segwit"));
476 assert!(context.is_feature_enabled("taproot"));
477 assert!(context.is_feature_enabled("rbf"));
478
479 assert!(!context.is_feature_enabled("nonexistent"));
481 assert!(!context.is_feature_enabled(""));
482 assert!(!context.is_feature_enabled("fast_mining"));
483 }
484
485 #[test]
486 fn test_validation_context_size_queries() {
487 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
488
489 assert_eq!(context.get_max_size("block"), 4_000_000);
490 assert_eq!(context.get_max_size("transaction"), 1_000_000);
491 assert_eq!(context.get_max_size("script"), 10_000);
492
493 assert_eq!(context.get_max_size("unknown"), 0);
495 }
496
497 #[test]
498 fn test_validation_context_serialization() {
499 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
500 let json = serde_json::to_string(&context).unwrap();
501 let deserialized: ProtocolValidationContext = serde_json::from_str(&json).unwrap();
502
503 assert_eq!(context.block_height, deserialized.block_height);
504 assert_eq!(
505 context.network_params.network_name,
506 deserialized.network_params.network_name
507 );
508 assert_eq!(
509 context.validation_rules.max_block_size,
510 deserialized.validation_rules.max_block_size
511 );
512 }
513
514 #[test]
515 fn test_validation_context_equality() {
516 let context1 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
517 let context2 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
518 let context3 = ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
519
520 assert_eq!(context1, context2);
521 assert_ne!(context1, context3); }
523
524 #[test]
525 fn test_block_size_validation() {
526 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
527 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
528
529 let coinbase_tx = Transaction {
531 version: 1,
532 inputs: blvm_consensus::tx_inputs![TransactionInput {
533 prevout: OutPoint {
534 hash: [0u8; 32],
535 index: 0xffffffff,
536 },
537 script_sig: vec![0x01, 0x00], sequence: 0xffffffff,
539 }],
540 outputs: blvm_consensus::tx_outputs![TransactionOutput {
541 value: 50_0000_0000,
542 script_pubkey: vec![blvm_consensus::opcodes::OP_1],
543 }],
544 lock_time: 0,
545 };
546
547 let merkle_root = blvm_consensus::mining::calculate_merkle_root(&[coinbase_tx.clone()])
549 .expect("Should calculate merkle root");
550
551 let small_block = Block {
552 header: BlockHeader {
553 version: 1,
554 prev_block_hash: [0u8; 32],
555 merkle_root,
556 timestamp: 1231006505,
557 bits: 0x1d00ffff,
558 nonce: 0,
559 },
560 transactions: vec![coinbase_tx].into_boxed_slice(),
561 };
562
563 let result =
565 engine.validate_block_with_protocol(&small_block, &UtxoSet::default(), 1000, &context);
566 assert!(result.is_ok());
567 }
568
569 #[test]
570 fn test_transaction_size_validation() {
571 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
572 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
573
574 let small_tx = Transaction {
576 version: 1,
577 inputs: vec![TransactionInput {
578 prevout: OutPoint {
579 hash: [0u8; 32],
580 index: 0,
581 },
582 script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
583 sequence: 0xffffffff,
584 }]
585 .into(),
586 outputs: vec![TransactionOutput {
587 value: 50_0000_0000,
588 script_pubkey: vec![
589 blvm_consensus::opcodes::OP_DUP,
590 blvm_consensus::opcodes::OP_HASH160,
591 blvm_consensus::opcodes::PUSH_20_BYTES,
592 0x00,
593 0x00,
594 0x00,
595 0x00,
596 0x00,
597 0x00,
598 0x00,
599 0x00,
600 0x00,
601 0x00,
602 0x00,
603 0x00,
604 0x00,
605 0x00,
606 0x00,
607 0x00,
608 0x00,
609 0x00,
610 0x00,
611 0x00,
612 blvm_consensus::opcodes::OP_EQUALVERIFY,
613 blvm_consensus::opcodes::OP_CHECKSIG,
614 ],
615 }]
616 .into(),
617 lock_time: 0,
618 };
619
620 let result = engine.validate_transaction_with_protocol(&small_tx, &context);
622 assert!(result.is_ok());
623 }
624
625 #[test]
626 fn test_script_size_validation() {
627 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
628 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
629
630 let tx = Transaction {
632 version: 1,
633 inputs: vec![TransactionInput {
634 prevout: OutPoint {
635 hash: [0u8; 32],
636 index: 0,
637 },
638 script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
639 sequence: 0xffffffff,
640 }]
641 .into(),
642 outputs: vec![TransactionOutput {
643 value: 50_0000_0000,
644 script_pubkey: vec![
645 blvm_consensus::opcodes::OP_DUP,
646 blvm_consensus::opcodes::OP_HASH160,
647 blvm_consensus::opcodes::PUSH_20_BYTES,
648 0x00,
649 0x00,
650 0x00,
651 0x00,
652 0x00,
653 0x00,
654 0x00,
655 0x00,
656 0x00,
657 0x00,
658 0x00,
659 0x00,
660 0x00,
661 0x00,
662 0x00,
663 0x00,
664 0x00,
665 0x00,
666 0x00,
667 0x00,
668 blvm_consensus::opcodes::OP_EQUALVERIFY,
669 blvm_consensus::opcodes::OP_CHECKSIG,
670 ],
671 }]
672 .into(),
673 lock_time: 0,
674 };
675
676 let result = engine.validate_transaction_with_protocol(&tx, &context);
678 assert!(result.is_ok());
679 }
680
681 #[test]
682 fn test_validation_context_data() {
683 let mut context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
684
685 context
687 .context_data
688 .insert("test_key".to_string(), "test_value".to_string());
689
690 assert_eq!(
691 context.context_data.get("test_key"),
692 Some(&"test_value".to_string())
693 );
694 assert_eq!(context.context_data.get("nonexistent"), None);
695 }
696
697 #[test]
698 fn test_validation_rules_boundary_values() {
699 let rules = ProtocolValidationRules::mainnet();
700
701 assert!(rules.max_block_size > 0);
703 assert!(rules.max_tx_size > 0);
704 assert!(rules.max_script_size > 0);
705 assert!(rules.max_fee_rate > rules.min_fee_rate);
706
707 assert!(rules.max_block_size <= 10_000_000); assert!(rules.max_tx_size <= 5_000_000); assert!(rules.max_script_size <= 50_000); }
712
713 #[test]
714 fn test_block_weight_counts_witness_element_bytes() {
715 use blvm_consensus::opcodes::OP_1;
716 use blvm_consensus::{
717 Block, BlockHeader, OutPoint, Transaction, TransactionInput, TransactionOutput,
718 };
719
720 let engine = BitcoinProtocolEngine::new(ProtocolVersion::Regtest).unwrap();
721 let mut context = ProtocolValidationContext::new(ProtocolVersion::Regtest, 1).unwrap();
722 context.validation_rules.max_block_size = 5_000;
723
724 let coinbase = Transaction {
725 version: 1,
726 inputs: vec![TransactionInput {
727 prevout: OutPoint {
728 hash: [0u8; 32],
729 index: 0xffffffff,
730 },
731 script_sig: vec![OP_1].into(),
732 sequence: 0xffffffff,
733 }]
734 .into(),
735 outputs: vec![TransactionOutput {
736 value: 50_0000_0000,
737 script_pubkey: vec![OP_1].into(),
738 }]
739 .into(),
740 lock_time: 0,
741 };
742 let tx = Transaction {
743 version: 2,
744 inputs: vec![TransactionInput {
745 prevout: OutPoint {
746 hash: [1u8; 32],
747 index: 0,
748 },
749 script_sig: vec![OP_1].into(),
750 sequence: 0xffffffff,
751 }]
752 .into(),
753 outputs: vec![TransactionOutput {
754 value: 1_000,
755 script_pubkey: vec![OP_1].into(),
756 }]
757 .into(),
758 lock_time: 0,
759 };
760 let block = Block {
761 header: BlockHeader {
762 version: 1,
763 prev_block_hash: [0u8; 32],
764 merkle_root: [0u8; 32],
765 timestamp: 1_700_000_000,
766 bits: 0x207fffff,
767 nonce: 0,
768 },
769 transactions: vec![coinbase, tx].into(),
770 };
771 let heavy_elem = vec![0u8; 9000];
773 let witnesses: Vec<Vec<blvm_consensus::segwit::Witness>> =
774 vec![vec![vec![]], vec![vec![heavy_elem]]];
775
776 let result = engine.validate_block_with_protocol_and_witnesses(
777 &block,
778 &witnesses,
779 &UtxoSet::default(),
780 1,
781 &context,
782 );
783 assert!(
784 result.is_err(),
785 "heavy witness bytes must exceed reduced max_block_size weight limit"
786 );
787 }
788}