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 for tx in &block.transactions {
279 self.apply_transaction_protocol_validation(tx, context)?;
280 }
281
282 Ok(())
283 }
284
285 fn apply_transaction_protocol_validation(
287 &self,
288 tx: &Transaction,
289 context: &ProtocolValidationContext,
290 ) -> Result<()> {
291 let tx_size = self.calculate_transaction_size(tx);
293 if tx_size > context.validation_rules.max_tx_size {
294 return Err(ProtocolError::Validation(
295 format!(
296 "Transaction size exceeds maximum: {} bytes (max {} bytes)",
297 tx_size, context.validation_rules.max_tx_size
298 )
299 .into(),
300 ));
301 }
302
303 for input in &tx.inputs {
305 if input.script_sig.len() > context.validation_rules.max_script_size as usize {
306 return Err(ProtocolError::Validation(
307 format!(
308 "Script size exceeds maximum: {} bytes (max {} bytes)",
309 input.script_sig.len(),
310 context.validation_rules.max_script_size
311 )
312 .into(),
313 ));
314 }
315 }
316
317 for output in &tx.outputs {
318 if output.script_pubkey.len() > context.validation_rules.max_script_size as usize {
319 return Err(ProtocolError::Validation(
320 format!(
321 "Script size exceeds maximum: {} bytes (max {} bytes)",
322 output.script_pubkey.len(),
323 context.validation_rules.max_script_size
324 )
325 .into(),
326 ));
327 }
328 }
329
330 Ok(())
331 }
332
333 fn calculate_block_size(&self, block: &Block) -> u32 {
335 let header_size = 80; let tx_count_size = 4; let tx_sizes: u32 = block
339 .transactions
340 .iter()
341 .map(|tx| self.calculate_transaction_size(tx))
342 .sum();
343
344 header_size + tx_count_size + tx_sizes
345 }
346
347 fn calculate_transaction_size(&self, tx: &Transaction) -> u32 {
349 blvm_consensus::transaction::calculate_transaction_size(tx) as u32
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use blvm_consensus::types::{OutPoint, TransactionInput, TransactionOutput};
361 use blvm_consensus::{Block, BlockHeader, Transaction};
362
363 #[test]
364 fn test_validation_rules() {
365 let mainnet_rules = ProtocolValidationRules::mainnet();
366 assert_eq!(mainnet_rules.max_block_size, 4_000_000);
367 assert!(mainnet_rules.segwit_enabled);
368 assert!(mainnet_rules.taproot_enabled);
369
370 let regtest_rules = ProtocolValidationRules::regtest();
371 assert_eq!(regtest_rules.max_block_size, 4_000_000);
372 assert!(regtest_rules.segwit_enabled);
373 assert_eq!(regtest_rules.min_fee_rate, 0); }
375
376 #[test]
377 fn test_validation_rules_all_protocols() {
378 let mainnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::BitcoinV1);
379 let testnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Testnet3);
380 let regtest_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Regtest);
381
382 assert_eq!(mainnet_rules.max_block_size, testnet_rules.max_block_size);
384 assert_eq!(mainnet_rules.max_tx_size, testnet_rules.max_tx_size);
385 assert_eq!(mainnet_rules.max_script_size, testnet_rules.max_script_size);
386 assert_eq!(mainnet_rules.segwit_enabled, testnet_rules.segwit_enabled);
387 assert_eq!(mainnet_rules.taproot_enabled, testnet_rules.taproot_enabled);
388 assert_eq!(mainnet_rules.rbf_enabled, testnet_rules.rbf_enabled);
389 assert_eq!(mainnet_rules.min_fee_rate, testnet_rules.min_fee_rate);
390 assert_eq!(mainnet_rules.max_fee_rate, testnet_rules.max_fee_rate);
391
392 assert_eq!(regtest_rules.min_fee_rate, 0);
394 assert_eq!(regtest_rules.max_fee_rate, mainnet_rules.max_fee_rate);
395 }
396
397 #[test]
398 fn test_validation_rules_serialization() {
399 let mainnet_rules = ProtocolValidationRules::mainnet();
400 let json = serde_json::to_string(&mainnet_rules).unwrap();
401 let deserialized: ProtocolValidationRules = serde_json::from_str(&json).unwrap();
402
403 assert_eq!(mainnet_rules.max_block_size, deserialized.max_block_size);
404 assert_eq!(mainnet_rules.max_tx_size, deserialized.max_tx_size);
405 assert_eq!(mainnet_rules.max_script_size, deserialized.max_script_size);
406 assert_eq!(mainnet_rules.segwit_enabled, deserialized.segwit_enabled);
407 assert_eq!(mainnet_rules.taproot_enabled, deserialized.taproot_enabled);
408 assert_eq!(mainnet_rules.rbf_enabled, deserialized.rbf_enabled);
409 assert_eq!(mainnet_rules.min_fee_rate, deserialized.min_fee_rate);
410 assert_eq!(mainnet_rules.max_fee_rate, deserialized.max_fee_rate);
411 }
412
413 #[test]
414 fn test_validation_rules_equality() {
415 let mainnet1 = ProtocolValidationRules::mainnet();
416 let mainnet2 = ProtocolValidationRules::mainnet();
417 let testnet = ProtocolValidationRules::testnet();
418
419 assert_eq!(mainnet1, mainnet2);
420 assert_eq!(mainnet1, testnet); }
422
423 #[test]
424 fn test_validation_context() {
425 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
427 assert_eq!(context.block_height, 800_000);
428 assert!(context.is_feature_enabled("segwit"));
429 assert!(!context.is_feature_enabled("nonexistent"));
430 assert_eq!(context.get_max_size("block"), 4_000_000);
431 }
432
433 #[test]
434 fn test_validation_context_all_protocols() {
435 let mainnet_context =
440 ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
441 let testnet_context =
442 ProtocolValidationContext::new(ProtocolVersion::Testnet3, 2_100_000).unwrap();
443 let regtest_context = ProtocolValidationContext::new(ProtocolVersion::Regtest, 1).unwrap();
444
445 assert_eq!(mainnet_context.block_height, 800_000);
446 assert_eq!(testnet_context.block_height, 2_100_000);
447 assert_eq!(regtest_context.block_height, 1);
448
449 assert!(mainnet_context.is_feature_enabled("segwit"));
451 assert!(testnet_context.is_feature_enabled("segwit"));
452 assert!(regtest_context.is_feature_enabled("segwit"));
453
454 assert!(mainnet_context.is_feature_enabled("taproot"));
455 assert!(testnet_context.is_feature_enabled("taproot"));
456 assert!(regtest_context.is_feature_enabled("taproot"));
457
458 assert!(mainnet_context.is_feature_enabled("rbf"));
459 assert!(testnet_context.is_feature_enabled("rbf"));
460 assert!(regtest_context.is_feature_enabled("rbf"));
461 }
462
463 #[test]
464 fn test_validation_context_feature_queries() {
465 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
467
468 assert!(context.is_feature_enabled("segwit"));
470 assert!(context.is_feature_enabled("taproot"));
471 assert!(context.is_feature_enabled("rbf"));
472
473 assert!(!context.is_feature_enabled("nonexistent"));
475 assert!(!context.is_feature_enabled(""));
476 assert!(!context.is_feature_enabled("fast_mining"));
477 }
478
479 #[test]
480 fn test_validation_context_size_queries() {
481 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
482
483 assert_eq!(context.get_max_size("block"), 4_000_000);
484 assert_eq!(context.get_max_size("transaction"), 1_000_000);
485 assert_eq!(context.get_max_size("script"), 10_000);
486
487 assert_eq!(context.get_max_size("unknown"), 0);
489 }
490
491 #[test]
492 fn test_validation_context_serialization() {
493 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
494 let json = serde_json::to_string(&context).unwrap();
495 let deserialized: ProtocolValidationContext = serde_json::from_str(&json).unwrap();
496
497 assert_eq!(context.block_height, deserialized.block_height);
498 assert_eq!(
499 context.network_params.network_name,
500 deserialized.network_params.network_name
501 );
502 assert_eq!(
503 context.validation_rules.max_block_size,
504 deserialized.validation_rules.max_block_size
505 );
506 }
507
508 #[test]
509 fn test_validation_context_equality() {
510 let context1 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
511 let context2 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
512 let context3 = ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
513
514 assert_eq!(context1, context2);
515 assert_ne!(context1, context3); }
517
518 #[test]
519 fn test_block_size_validation() {
520 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
521 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
522
523 let coinbase_tx = Transaction {
525 version: 1,
526 inputs: blvm_consensus::tx_inputs![TransactionInput {
527 prevout: OutPoint {
528 hash: [0u8; 32],
529 index: 0xffffffff,
530 },
531 script_sig: vec![0x01, 0x00], sequence: 0xffffffff,
533 }],
534 outputs: blvm_consensus::tx_outputs![TransactionOutput {
535 value: 50_0000_0000,
536 script_pubkey: vec![blvm_consensus::opcodes::OP_1],
537 }],
538 lock_time: 0,
539 };
540
541 let merkle_root = blvm_consensus::mining::calculate_merkle_root(&[coinbase_tx.clone()])
543 .expect("Should calculate merkle root");
544
545 let small_block = Block {
546 header: BlockHeader {
547 version: 1,
548 prev_block_hash: [0u8; 32],
549 merkle_root,
550 timestamp: 1231006505,
551 bits: 0x1d00ffff,
552 nonce: 0,
553 },
554 transactions: vec![coinbase_tx].into_boxed_slice(),
555 };
556
557 let result =
559 engine.validate_block_with_protocol(&small_block, &UtxoSet::default(), 1000, &context);
560 assert!(result.is_ok());
561 }
562
563 #[test]
564 fn test_transaction_size_validation() {
565 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
566 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
567
568 let small_tx = Transaction {
570 version: 1,
571 inputs: vec![TransactionInput {
572 prevout: OutPoint {
573 hash: [0u8; 32],
574 index: 0,
575 },
576 script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
577 sequence: 0xffffffff,
578 }]
579 .into(),
580 outputs: vec![TransactionOutput {
581 value: 50_0000_0000,
582 script_pubkey: vec![
583 blvm_consensus::opcodes::OP_DUP,
584 blvm_consensus::opcodes::OP_HASH160,
585 blvm_consensus::opcodes::PUSH_20_BYTES,
586 0x00,
587 0x00,
588 0x00,
589 0x00,
590 0x00,
591 0x00,
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 blvm_consensus::opcodes::OP_EQUALVERIFY,
607 blvm_consensus::opcodes::OP_CHECKSIG,
608 ],
609 }]
610 .into(),
611 lock_time: 0,
612 };
613
614 let result = engine.validate_transaction_with_protocol(&small_tx, &context);
616 assert!(result.is_ok());
617 }
618
619 #[test]
620 fn test_script_size_validation() {
621 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
622 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
623
624 let tx = Transaction {
626 version: 1,
627 inputs: vec![TransactionInput {
628 prevout: OutPoint {
629 hash: [0u8; 32],
630 index: 0,
631 },
632 script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
633 sequence: 0xffffffff,
634 }]
635 .into(),
636 outputs: vec![TransactionOutput {
637 value: 50_0000_0000,
638 script_pubkey: vec![
639 blvm_consensus::opcodes::OP_DUP,
640 blvm_consensus::opcodes::OP_HASH160,
641 blvm_consensus::opcodes::PUSH_20_BYTES,
642 0x00,
643 0x00,
644 0x00,
645 0x00,
646 0x00,
647 0x00,
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 blvm_consensus::opcodes::OP_EQUALVERIFY,
663 blvm_consensus::opcodes::OP_CHECKSIG,
664 ],
665 }]
666 .into(),
667 lock_time: 0,
668 };
669
670 let result = engine.validate_transaction_with_protocol(&tx, &context);
672 assert!(result.is_ok());
673 }
674
675 #[test]
676 fn test_validation_context_data() {
677 let mut context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
678
679 context
681 .context_data
682 .insert("test_key".to_string(), "test_value".to_string());
683
684 assert_eq!(
685 context.context_data.get("test_key"),
686 Some(&"test_value".to_string())
687 );
688 assert_eq!(context.context_data.get("nonexistent"), None);
689 }
690
691 #[test]
692 fn test_validation_rules_boundary_values() {
693 let rules = ProtocolValidationRules::mainnet();
694
695 assert!(rules.max_block_size > 0);
697 assert!(rules.max_tx_size > 0);
698 assert!(rules.max_script_size > 0);
699 assert!(rules.max_fee_rate > rules.min_fee_rate);
700
701 assert!(rules.max_block_size <= 10_000_000); assert!(rules.max_tx_size <= 5_000_000); assert!(rules.max_script_size <= 50_000); }
706
707 #[test]
708 fn test_block_weight_counts_witness_element_bytes() {
709 use blvm_consensus::opcodes::OP_1;
710 use blvm_consensus::{
711 Block, BlockHeader, OutPoint, Transaction, TransactionInput, TransactionOutput,
712 };
713
714 let engine = BitcoinProtocolEngine::new(ProtocolVersion::Regtest).unwrap();
715 let mut context = ProtocolValidationContext::new(ProtocolVersion::Regtest, 1).unwrap();
716 context.validation_rules.max_block_size = 5_000;
717
718 let coinbase = Transaction {
719 version: 1,
720 inputs: vec![TransactionInput {
721 prevout: OutPoint {
722 hash: [0u8; 32],
723 index: 0xffffffff,
724 },
725 script_sig: vec![OP_1].into(),
726 sequence: 0xffffffff,
727 }]
728 .into(),
729 outputs: vec![TransactionOutput {
730 value: 50_0000_0000,
731 script_pubkey: vec![OP_1].into(),
732 }]
733 .into(),
734 lock_time: 0,
735 };
736 let tx = Transaction {
737 version: 2,
738 inputs: vec![TransactionInput {
739 prevout: OutPoint {
740 hash: [1u8; 32],
741 index: 0,
742 },
743 script_sig: vec![OP_1].into(),
744 sequence: 0xffffffff,
745 }]
746 .into(),
747 outputs: vec![TransactionOutput {
748 value: 1_000,
749 script_pubkey: vec![OP_1].into(),
750 }]
751 .into(),
752 lock_time: 0,
753 };
754 let block = Block {
755 header: BlockHeader {
756 version: 1,
757 prev_block_hash: [0u8; 32],
758 merkle_root: [0u8; 32],
759 timestamp: 1_700_000_000,
760 bits: 0x207fffff,
761 nonce: 0,
762 },
763 transactions: vec![coinbase, tx].into(),
764 };
765 let heavy_elem = vec![0u8; 9000];
767 let witnesses: Vec<Vec<blvm_consensus::segwit::Witness>> =
768 vec![vec![vec![]], vec![vec![heavy_elem]]];
769
770 let result = engine.validate_block_with_protocol_and_witnesses(
771 &block,
772 &witnesses,
773 &UtxoSet::default(),
774 1,
775 &context,
776 );
777 assert!(
778 result.is_err(),
779 "heavy witness bytes must exceed reduced max_block_size weight limit"
780 );
781 }
782}