1use 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
14type Result<T> = std::result::Result<T, ProtocolError>;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct ProtocolValidationRules {
20 pub max_block_size: u32,
22 pub max_tx_size: u32,
24 pub max_script_size: u32,
26 pub segwit_enabled: bool,
28 pub taproot_enabled: bool,
30 pub rbf_enabled: bool,
32 pub min_fee_rate: u64,
34 pub max_fee_rate: u64,
36}
37
38impl ProtocolValidationRules {
39 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 pub fn mainnet() -> Self {
50 Self {
51 max_block_size: 4_000_000, max_tx_size: 1_000_000, max_script_size: 10_000, segwit_enabled: true,
55 taproot_enabled: true,
56 rbf_enabled: true,
57 min_fee_rate: 1, max_fee_rate: 1_000_000, }
60 }
61
62 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 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, max_fee_rate: 1_000_000,
87 }
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct ProtocolValidationContext {
94 pub block_height: u64,
96 pub network_params: NetworkParameters,
98 pub validation_rules: ProtocolValidationRules,
100 pub median_time_past: u64,
105 pub network_time: u64,
110 pub context_data: HashMap<String, String>,
112}
113
114impl ProtocolValidationContext {
115 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 median_time_past: 0,
126 network_time: 0,
127 context_data: HashMap::new(),
128 })
129 }
130
131 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 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 pub fn validate_block_with_protocol(
155 &self,
156 block: &Block,
157 _utxos: &UtxoSet,
158 _height: u64,
159 context: &ProtocolValidationContext,
160 ) -> Result<ValidationResult> {
161 self.apply_protocol_validation(block, context)?;
163
164 Ok(ValidationResult::Valid)
165 }
166
167 pub fn validate_transaction_with_protocol(
169 &self,
170 tx: &Transaction,
171 context: &ProtocolValidationContext,
172 ) -> Result<ValidationResult> {
173 let consensus_result = self.consensus.validate_transaction(tx)?;
175
176 self.apply_transaction_protocol_validation(tx, context)?;
178
179 Ok(consensus_result)
180 }
181
182 fn apply_protocol_validation(
184 &self,
185 block: &Block,
186 context: &ProtocolValidationContext,
187 ) -> Result<()> {
188 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 if block.transactions.len() > 10000 {
207 return Err(ProtocolError::Validation(
209 "Too many transactions in block (max 10000)".into(),
210 ));
211 }
212
213 for tx in &block.transactions {
215 self.apply_transaction_protocol_validation(tx, context)?;
216 }
217
218 Ok(())
219 }
220
221 fn apply_transaction_protocol_validation(
223 &self,
224 tx: &Transaction,
225 context: &ProtocolValidationContext,
226 ) -> Result<()> {
227 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 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 fn calculate_block_size(&self, block: &Block) -> u32 {
271 let header_size = 80; let tx_count_size = 4; 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 fn calculate_transaction_size(&self, tx: &Transaction) -> u32 {
286 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); }
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 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 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); }
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 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 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 assert!(context.is_feature_enabled("segwit"));
403 assert!(context.is_feature_enabled("taproot"));
404 assert!(context.is_feature_enabled("rbf"));
405
406 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 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); }
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 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], 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 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 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 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 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 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 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 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 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 assert!(rules.max_block_size <= 10_000_000); assert!(rules.max_tx_size <= 5_000_000); assert!(rules.max_script_size <= 50_000); }
639}