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 }
47 }
48
49 pub fn mainnet() -> Self {
51 Self {
52 max_block_size: 4_000_000, max_tx_size: 1_000_000, max_script_size: 10_000, segwit_enabled: true,
56 taproot_enabled: true,
57 rbf_enabled: true,
58 min_fee_rate: 1, max_fee_rate: 1_000_000, }
61 }
62
63 pub fn testnet() -> Self {
65 Self {
66 max_block_size: 4_000_000,
67 max_tx_size: 1_000_000,
68 max_script_size: 10_000,
69 segwit_enabled: true,
70 taproot_enabled: true,
71 rbf_enabled: true,
72 min_fee_rate: 1,
73 max_fee_rate: 1_000_000,
74 }
75 }
76
77 pub fn regtest() -> Self {
79 Self {
80 max_block_size: 4_000_000,
81 max_tx_size: 1_000_000,
82 max_script_size: 10_000,
83 segwit_enabled: true,
84 taproot_enabled: true,
85 rbf_enabled: true,
86 min_fee_rate: 0, max_fee_rate: 1_000_000,
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94pub struct ProtocolValidationContext {
95 pub block_height: u64,
97 pub network_params: NetworkParameters,
99 pub validation_rules: ProtocolValidationRules,
101 pub feature_registry: FeatureRegistry,
106 pub median_time_past: u64,
111 pub network_time: u64,
116 pub context_data: HashMap<String, String>,
118}
119
120impl ProtocolValidationContext {
121 pub fn new(version: ProtocolVersion, block_height: u64) -> Result<Self> {
123 let network_params = NetworkParameters::for_version(version)?;
124 let validation_rules = ProtocolValidationRules::for_protocol(version);
125
126 let feature_registry = FeatureRegistry::for_protocol(version);
127 Ok(Self {
128 block_height,
129 network_params,
130 validation_rules,
131 feature_registry,
132 median_time_past: 0,
134 network_time: 0,
135 context_data: HashMap::new(),
136 })
137 }
138
139 pub fn is_feature_enabled(&self, feature: &str) -> bool {
144 self.feature_registry
145 .is_feature_active(feature, self.block_height, self.network_time)
146 }
147
148 pub fn get_max_size(&self, component: &str) -> u32 {
150 match component {
151 "block" => self.validation_rules.max_block_size,
152 "transaction" => self.validation_rules.max_tx_size,
153 "script" => self.validation_rules.max_script_size,
154 _ => 0,
155 }
156 }
157}
158
159impl BitcoinProtocolEngine {
160 pub fn validate_block_with_protocol(
162 &self,
163 block: &Block,
164 _utxos: &UtxoSet,
165 _height: u64,
166 context: &ProtocolValidationContext,
167 ) -> Result<ValidationResult> {
168 self.apply_protocol_validation(block, context)?;
170
171 Ok(ValidationResult::Valid)
172 }
173
174 pub fn validate_transaction_with_protocol(
176 &self,
177 tx: &Transaction,
178 context: &ProtocolValidationContext,
179 ) -> Result<ValidationResult> {
180 let consensus_result = self.consensus.validate_transaction(tx)?;
182
183 self.apply_transaction_protocol_validation(tx, context)?;
185
186 Ok(consensus_result)
187 }
188
189 fn apply_protocol_validation(
191 &self,
192 block: &Block,
193 context: &ProtocolValidationContext,
194 ) -> Result<()> {
195 let stripped_size = self.calculate_block_size(block);
206 let base_weight = stripped_size.saturating_mul(4);
207 if base_weight > context.validation_rules.max_block_size {
208 return Err(ProtocolError::Validation(
209 format!(
210 "Block base weight exceeds maximum: {} WU (max {} WU); stripped_size={}",
211 base_weight, context.validation_rules.max_block_size, stripped_size
212 )
213 .into(),
214 ));
215 }
216
217 if block.transactions.len() > 10000 {
219 return Err(ProtocolError::Validation(
221 "Too many transactions in block (max 10000)".into(),
222 ));
223 }
224
225 for tx in &block.transactions {
227 self.apply_transaction_protocol_validation(tx, context)?;
228 }
229
230 Ok(())
231 }
232
233 fn apply_transaction_protocol_validation(
235 &self,
236 tx: &Transaction,
237 context: &ProtocolValidationContext,
238 ) -> Result<()> {
239 let tx_size = self.calculate_transaction_size(tx);
241 if tx_size > context.validation_rules.max_tx_size {
242 return Err(ProtocolError::Validation(
243 format!(
244 "Transaction size exceeds maximum: {} bytes (max {} bytes)",
245 tx_size, context.validation_rules.max_tx_size
246 )
247 .into(),
248 ));
249 }
250
251 for input in &tx.inputs {
253 if input.script_sig.len() > context.validation_rules.max_script_size as usize {
254 return Err(ProtocolError::Validation(
255 format!(
256 "Script size exceeds maximum: {} bytes (max {} bytes)",
257 input.script_sig.len(),
258 context.validation_rules.max_script_size
259 )
260 .into(),
261 ));
262 }
263 }
264
265 for output in &tx.outputs {
266 if output.script_pubkey.len() > context.validation_rules.max_script_size as usize {
267 return Err(ProtocolError::Validation(
268 format!(
269 "Script size exceeds maximum: {} bytes (max {} bytes)",
270 output.script_pubkey.len(),
271 context.validation_rules.max_script_size
272 )
273 .into(),
274 ));
275 }
276 }
277
278 Ok(())
279 }
280
281 fn calculate_block_size(&self, block: &Block) -> u32 {
283 let header_size = 80; let tx_count_size = 4; let tx_sizes: u32 = block
288 .transactions
289 .iter()
290 .map(|tx| self.calculate_transaction_size(tx))
291 .sum();
292
293 header_size + tx_count_size + tx_sizes
294 }
295
296 fn calculate_transaction_size(&self, tx: &Transaction) -> u32 {
298 blvm_consensus::transaction::calculate_transaction_size(tx) as u32
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use blvm_consensus::types::{OutPoint, TransactionInput, TransactionOutput};
310 use blvm_consensus::{Block, BlockHeader, Transaction};
311
312 #[test]
313 fn test_validation_rules() {
314 let mainnet_rules = ProtocolValidationRules::mainnet();
315 assert_eq!(mainnet_rules.max_block_size, 4_000_000);
316 assert!(mainnet_rules.segwit_enabled);
317 assert!(mainnet_rules.taproot_enabled);
318
319 let regtest_rules = ProtocolValidationRules::regtest();
320 assert_eq!(regtest_rules.max_block_size, 4_000_000);
321 assert!(regtest_rules.segwit_enabled);
322 assert_eq!(regtest_rules.min_fee_rate, 0); }
324
325 #[test]
326 fn test_validation_rules_all_protocols() {
327 let mainnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::BitcoinV1);
328 let testnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Testnet3);
329 let regtest_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Regtest);
330
331 assert_eq!(mainnet_rules.max_block_size, testnet_rules.max_block_size);
333 assert_eq!(mainnet_rules.max_tx_size, testnet_rules.max_tx_size);
334 assert_eq!(mainnet_rules.max_script_size, testnet_rules.max_script_size);
335 assert_eq!(mainnet_rules.segwit_enabled, testnet_rules.segwit_enabled);
336 assert_eq!(mainnet_rules.taproot_enabled, testnet_rules.taproot_enabled);
337 assert_eq!(mainnet_rules.rbf_enabled, testnet_rules.rbf_enabled);
338 assert_eq!(mainnet_rules.min_fee_rate, testnet_rules.min_fee_rate);
339 assert_eq!(mainnet_rules.max_fee_rate, testnet_rules.max_fee_rate);
340
341 assert_eq!(regtest_rules.min_fee_rate, 0);
343 assert_eq!(regtest_rules.max_fee_rate, mainnet_rules.max_fee_rate);
344 }
345
346 #[test]
347 fn test_validation_rules_serialization() {
348 let mainnet_rules = ProtocolValidationRules::mainnet();
349 let json = serde_json::to_string(&mainnet_rules).unwrap();
350 let deserialized: ProtocolValidationRules = serde_json::from_str(&json).unwrap();
351
352 assert_eq!(mainnet_rules.max_block_size, deserialized.max_block_size);
353 assert_eq!(mainnet_rules.max_tx_size, deserialized.max_tx_size);
354 assert_eq!(mainnet_rules.max_script_size, deserialized.max_script_size);
355 assert_eq!(mainnet_rules.segwit_enabled, deserialized.segwit_enabled);
356 assert_eq!(mainnet_rules.taproot_enabled, deserialized.taproot_enabled);
357 assert_eq!(mainnet_rules.rbf_enabled, deserialized.rbf_enabled);
358 assert_eq!(mainnet_rules.min_fee_rate, deserialized.min_fee_rate);
359 assert_eq!(mainnet_rules.max_fee_rate, deserialized.max_fee_rate);
360 }
361
362 #[test]
363 fn test_validation_rules_equality() {
364 let mainnet1 = ProtocolValidationRules::mainnet();
365 let mainnet2 = ProtocolValidationRules::mainnet();
366 let testnet = ProtocolValidationRules::testnet();
367
368 assert_eq!(mainnet1, mainnet2);
369 assert_eq!(mainnet1, testnet); }
371
372 #[test]
373 fn test_validation_context() {
374 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
376 assert_eq!(context.block_height, 800_000);
377 assert!(context.is_feature_enabled("segwit"));
378 assert!(!context.is_feature_enabled("nonexistent"));
379 assert_eq!(context.get_max_size("block"), 4_000_000);
380 }
381
382 #[test]
383 fn test_validation_context_all_protocols() {
384 let mainnet_context =
389 ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
390 let testnet_context =
391 ProtocolValidationContext::new(ProtocolVersion::Testnet3, 2_100_000).unwrap();
392 let regtest_context = ProtocolValidationContext::new(ProtocolVersion::Regtest, 1).unwrap();
393
394 assert_eq!(mainnet_context.block_height, 800_000);
395 assert_eq!(testnet_context.block_height, 2_100_000);
396 assert_eq!(regtest_context.block_height, 1);
397
398 assert!(mainnet_context.is_feature_enabled("segwit"));
400 assert!(testnet_context.is_feature_enabled("segwit"));
401 assert!(regtest_context.is_feature_enabled("segwit"));
402
403 assert!(mainnet_context.is_feature_enabled("taproot"));
404 assert!(testnet_context.is_feature_enabled("taproot"));
405 assert!(regtest_context.is_feature_enabled("taproot"));
406
407 assert!(mainnet_context.is_feature_enabled("rbf"));
408 assert!(testnet_context.is_feature_enabled("rbf"));
409 assert!(regtest_context.is_feature_enabled("rbf"));
410 }
411
412 #[test]
413 fn test_validation_context_feature_queries() {
414 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 800_000).unwrap();
416
417 assert!(context.is_feature_enabled("segwit"));
419 assert!(context.is_feature_enabled("taproot"));
420 assert!(context.is_feature_enabled("rbf"));
421
422 assert!(!context.is_feature_enabled("nonexistent"));
424 assert!(!context.is_feature_enabled(""));
425 assert!(!context.is_feature_enabled("fast_mining"));
426 }
427
428 #[test]
429 fn test_validation_context_size_queries() {
430 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
431
432 assert_eq!(context.get_max_size("block"), 4_000_000);
433 assert_eq!(context.get_max_size("transaction"), 1_000_000);
434 assert_eq!(context.get_max_size("script"), 10_000);
435
436 assert_eq!(context.get_max_size("unknown"), 0);
438 }
439
440 #[test]
441 fn test_validation_context_serialization() {
442 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
443 let json = serde_json::to_string(&context).unwrap();
444 let deserialized: ProtocolValidationContext = serde_json::from_str(&json).unwrap();
445
446 assert_eq!(context.block_height, deserialized.block_height);
447 assert_eq!(
448 context.network_params.network_name,
449 deserialized.network_params.network_name
450 );
451 assert_eq!(
452 context.validation_rules.max_block_size,
453 deserialized.validation_rules.max_block_size
454 );
455 }
456
457 #[test]
458 fn test_validation_context_equality() {
459 let context1 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
460 let context2 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
461 let context3 = ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
462
463 assert_eq!(context1, context2);
464 assert_ne!(context1, context3); }
466
467 #[test]
468 fn test_block_size_validation() {
469 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
470 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
471
472 let coinbase_tx = Transaction {
474 version: 1,
475 inputs: blvm_consensus::tx_inputs![TransactionInput {
476 prevout: OutPoint {
477 hash: [0u8; 32],
478 index: 0xffffffff,
479 },
480 script_sig: vec![0x01, 0x00], sequence: 0xffffffff,
482 }],
483 outputs: blvm_consensus::tx_outputs![TransactionOutput {
484 value: 50_0000_0000,
485 script_pubkey: vec![blvm_consensus::opcodes::OP_1],
486 }],
487 lock_time: 0,
488 };
489
490 let merkle_root = blvm_consensus::mining::calculate_merkle_root(&[coinbase_tx.clone()])
492 .expect("Should calculate merkle root");
493
494 let small_block = Block {
495 header: BlockHeader {
496 version: 1,
497 prev_block_hash: [0u8; 32],
498 merkle_root,
499 timestamp: 1231006505,
500 bits: 0x1d00ffff,
501 nonce: 0,
502 },
503 transactions: vec![coinbase_tx].into_boxed_slice(),
504 };
505
506 let result =
508 engine.validate_block_with_protocol(&small_block, &UtxoSet::default(), 1000, &context);
509 assert!(result.is_ok());
510 }
511
512 #[test]
513 fn test_transaction_size_validation() {
514 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
515 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
516
517 let small_tx = Transaction {
519 version: 1,
520 inputs: vec![TransactionInput {
521 prevout: OutPoint {
522 hash: [0u8; 32],
523 index: 0,
524 },
525 script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
526 sequence: 0xffffffff,
527 }]
528 .into(),
529 outputs: vec![TransactionOutput {
530 value: 50_0000_0000,
531 script_pubkey: vec![
532 blvm_consensus::opcodes::OP_DUP,
533 blvm_consensus::opcodes::OP_HASH160,
534 blvm_consensus::opcodes::PUSH_20_BYTES,
535 0x00,
536 0x00,
537 0x00,
538 0x00,
539 0x00,
540 0x00,
541 0x00,
542 0x00,
543 0x00,
544 0x00,
545 0x00,
546 0x00,
547 0x00,
548 0x00,
549 0x00,
550 0x00,
551 0x00,
552 0x00,
553 0x00,
554 0x00,
555 blvm_consensus::opcodes::OP_EQUALVERIFY,
556 blvm_consensus::opcodes::OP_CHECKSIG,
557 ],
558 }]
559 .into(),
560 lock_time: 0,
561 };
562
563 let result = engine.validate_transaction_with_protocol(&small_tx, &context);
565 assert!(result.is_ok());
566 }
567
568 #[test]
569 fn test_script_size_validation() {
570 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
571 let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
572
573 let tx = Transaction {
575 version: 1,
576 inputs: vec![TransactionInput {
577 prevout: OutPoint {
578 hash: [0u8; 32],
579 index: 0,
580 },
581 script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
582 sequence: 0xffffffff,
583 }]
584 .into(),
585 outputs: vec![TransactionOutput {
586 value: 50_0000_0000,
587 script_pubkey: vec![
588 blvm_consensus::opcodes::OP_DUP,
589 blvm_consensus::opcodes::OP_HASH160,
590 blvm_consensus::opcodes::PUSH_20_BYTES,
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 0x00,
607 0x00,
608 0x00,
609 0x00,
610 0x00,
611 blvm_consensus::opcodes::OP_EQUALVERIFY,
612 blvm_consensus::opcodes::OP_CHECKSIG,
613 ],
614 }]
615 .into(),
616 lock_time: 0,
617 };
618
619 let result = engine.validate_transaction_with_protocol(&tx, &context);
621 assert!(result.is_ok());
622 }
623
624 #[test]
625 fn test_validation_context_data() {
626 let mut context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
627
628 context
630 .context_data
631 .insert("test_key".to_string(), "test_value".to_string());
632
633 assert_eq!(
634 context.context_data.get("test_key"),
635 Some(&"test_value".to_string())
636 );
637 assert_eq!(context.context_data.get("nonexistent"), None);
638 }
639
640 #[test]
641 fn test_validation_rules_boundary_values() {
642 let rules = ProtocolValidationRules::mainnet();
643
644 assert!(rules.max_block_size > 0);
646 assert!(rules.max_tx_size > 0);
647 assert!(rules.max_script_size > 0);
648 assert!(rules.max_fee_rate > rules.min_fee_rate);
649
650 assert!(rules.max_block_size <= 10_000_000); assert!(rules.max_tx_size <= 5_000_000); assert!(rules.max_script_size <= 50_000); }
655}