use crate::error::ProtocolError;
use crate::{BitcoinProtocolEngine, NetworkParameters, ProtocolVersion};
use crate::{Block, Transaction, ValidationResult};
use blvm_consensus::types::UtxoSet;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
type Result<T> = std::result::Result<T, ProtocolError>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProtocolValidationRules {
pub max_block_size: u32,
pub max_tx_size: u32,
pub max_script_size: u32,
pub segwit_enabled: bool,
pub taproot_enabled: bool,
pub rbf_enabled: bool,
pub min_fee_rate: u64,
pub max_fee_rate: u64,
}
impl ProtocolValidationRules {
pub fn for_protocol(version: ProtocolVersion) -> Self {
match version {
ProtocolVersion::BitcoinV1 => Self::mainnet(),
ProtocolVersion::Testnet3 => Self::testnet(),
ProtocolVersion::Regtest => Self::regtest(),
}
}
pub fn mainnet() -> Self {
Self {
max_block_size: 4_000_000, max_tx_size: 1_000_000, max_script_size: 10_000, segwit_enabled: true,
taproot_enabled: true,
rbf_enabled: true,
min_fee_rate: 1, max_fee_rate: 1_000_000, }
}
pub fn testnet() -> Self {
Self {
max_block_size: 4_000_000,
max_tx_size: 1_000_000,
max_script_size: 10_000,
segwit_enabled: true,
taproot_enabled: true,
rbf_enabled: true,
min_fee_rate: 1,
max_fee_rate: 1_000_000,
}
}
pub fn regtest() -> Self {
Self {
max_block_size: 4_000_000,
max_tx_size: 1_000_000,
max_script_size: 10_000,
segwit_enabled: true,
taproot_enabled: true,
rbf_enabled: true,
min_fee_rate: 0, max_fee_rate: 1_000_000,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProtocolValidationContext {
pub block_height: u64,
pub network_params: NetworkParameters,
pub validation_rules: ProtocolValidationRules,
pub median_time_past: u64,
pub network_time: u64,
pub context_data: HashMap<String, String>,
}
impl ProtocolValidationContext {
pub fn new(version: ProtocolVersion, block_height: u64) -> Result<Self> {
let network_params = NetworkParameters::for_version(version)?;
let validation_rules = ProtocolValidationRules::for_protocol(version);
Ok(Self {
block_height,
network_params,
validation_rules,
median_time_past: 0,
network_time: 0,
context_data: HashMap::new(),
})
}
pub fn is_feature_enabled(&self, feature: &str) -> bool {
match feature {
"segwit" => self.validation_rules.segwit_enabled,
"taproot" => self.validation_rules.taproot_enabled,
"rbf" => self.validation_rules.rbf_enabled,
_ => false,
}
}
pub fn get_max_size(&self, component: &str) -> u32 {
match component {
"block" => self.validation_rules.max_block_size,
"transaction" => self.validation_rules.max_tx_size,
"script" => self.validation_rules.max_script_size,
_ => 0,
}
}
}
impl BitcoinProtocolEngine {
pub fn validate_block_with_protocol(
&self,
block: &Block,
_utxos: &UtxoSet,
_height: u64,
context: &ProtocolValidationContext,
) -> Result<ValidationResult> {
self.apply_protocol_validation(block, context)?;
Ok(ValidationResult::Valid)
}
pub fn validate_transaction_with_protocol(
&self,
tx: &Transaction,
context: &ProtocolValidationContext,
) -> Result<ValidationResult> {
let consensus_result = self.consensus.validate_transaction(tx)?;
self.apply_transaction_protocol_validation(tx, context)?;
Ok(consensus_result)
}
fn apply_protocol_validation(
&self,
block: &Block,
context: &ProtocolValidationContext,
) -> Result<()> {
let block_size = self.calculate_block_size(block);
if block_size > context.validation_rules.max_block_size {
return Err(ProtocolError::Validation(
format!(
"Block size exceeds maximum: {} bytes (max {} bytes)",
block_size, context.validation_rules.max_block_size
)
.into(),
));
}
if block.transactions.len() > 10000 {
return Err(ProtocolError::Validation(
"Too many transactions in block (max 10000)".into(),
));
}
for tx in &block.transactions {
self.apply_transaction_protocol_validation(tx, context)?;
}
Ok(())
}
fn apply_transaction_protocol_validation(
&self,
tx: &Transaction,
context: &ProtocolValidationContext,
) -> Result<()> {
let tx_size = self.calculate_transaction_size(tx);
if tx_size > context.validation_rules.max_tx_size {
return Err(ProtocolError::Validation(
format!(
"Transaction size exceeds maximum: {} bytes (max {} bytes)",
tx_size, context.validation_rules.max_tx_size
)
.into(),
));
}
for input in &tx.inputs {
if input.script_sig.len() > context.validation_rules.max_script_size as usize {
return Err(ProtocolError::Validation(
format!(
"Script size exceeds maximum: {} bytes (max {} bytes)",
input.script_sig.len(),
context.validation_rules.max_script_size
)
.into(),
));
}
}
for output in &tx.outputs {
if output.script_pubkey.len() > context.validation_rules.max_script_size as usize {
return Err(ProtocolError::Validation(
format!(
"Script size exceeds maximum: {} bytes (max {} bytes)",
output.script_pubkey.len(),
context.validation_rules.max_script_size
)
.into(),
));
}
}
Ok(())
}
fn calculate_block_size(&self, block: &Block) -> u32 {
let header_size = 80; let tx_count_size = 4; let tx_sizes: u32 = block
.transactions
.iter()
.map(|tx| self.calculate_transaction_size(tx))
.sum();
header_size + tx_count_size + tx_sizes
}
fn calculate_transaction_size(&self, tx: &Transaction) -> u32 {
blvm_consensus::transaction::calculate_transaction_size(tx) as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
use blvm_consensus::types::{OutPoint, TransactionInput, TransactionOutput};
use blvm_consensus::{Block, BlockHeader, Transaction};
use std::collections::HashMap;
#[test]
fn test_validation_rules() {
let mainnet_rules = ProtocolValidationRules::mainnet();
assert_eq!(mainnet_rules.max_block_size, 4_000_000);
assert!(mainnet_rules.segwit_enabled);
assert!(mainnet_rules.taproot_enabled);
let regtest_rules = ProtocolValidationRules::regtest();
assert_eq!(regtest_rules.max_block_size, 4_000_000);
assert!(regtest_rules.segwit_enabled);
assert_eq!(regtest_rules.min_fee_rate, 0); }
#[test]
fn test_validation_rules_all_protocols() {
let mainnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::BitcoinV1);
let testnet_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Testnet3);
let regtest_rules = ProtocolValidationRules::for_protocol(ProtocolVersion::Regtest);
assert_eq!(mainnet_rules.max_block_size, testnet_rules.max_block_size);
assert_eq!(mainnet_rules.max_tx_size, testnet_rules.max_tx_size);
assert_eq!(mainnet_rules.max_script_size, testnet_rules.max_script_size);
assert_eq!(mainnet_rules.segwit_enabled, testnet_rules.segwit_enabled);
assert_eq!(mainnet_rules.taproot_enabled, testnet_rules.taproot_enabled);
assert_eq!(mainnet_rules.rbf_enabled, testnet_rules.rbf_enabled);
assert_eq!(mainnet_rules.min_fee_rate, testnet_rules.min_fee_rate);
assert_eq!(mainnet_rules.max_fee_rate, testnet_rules.max_fee_rate);
assert_eq!(regtest_rules.min_fee_rate, 0);
assert_eq!(regtest_rules.max_fee_rate, mainnet_rules.max_fee_rate);
}
#[test]
fn test_validation_rules_serialization() {
let mainnet_rules = ProtocolValidationRules::mainnet();
let json = serde_json::to_string(&mainnet_rules).unwrap();
let deserialized: ProtocolValidationRules = serde_json::from_str(&json).unwrap();
assert_eq!(mainnet_rules.max_block_size, deserialized.max_block_size);
assert_eq!(mainnet_rules.max_tx_size, deserialized.max_tx_size);
assert_eq!(mainnet_rules.max_script_size, deserialized.max_script_size);
assert_eq!(mainnet_rules.segwit_enabled, deserialized.segwit_enabled);
assert_eq!(mainnet_rules.taproot_enabled, deserialized.taproot_enabled);
assert_eq!(mainnet_rules.rbf_enabled, deserialized.rbf_enabled);
assert_eq!(mainnet_rules.min_fee_rate, deserialized.min_fee_rate);
assert_eq!(mainnet_rules.max_fee_rate, deserialized.max_fee_rate);
}
#[test]
fn test_validation_rules_equality() {
let mainnet1 = ProtocolValidationRules::mainnet();
let mainnet2 = ProtocolValidationRules::mainnet();
let testnet = ProtocolValidationRules::testnet();
assert_eq!(mainnet1, mainnet2);
assert_eq!(mainnet1, testnet); }
#[test]
fn test_validation_context() {
let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
assert_eq!(context.block_height, 1000);
assert!(context.is_feature_enabled("segwit"));
assert!(!context.is_feature_enabled("nonexistent"));
assert_eq!(context.get_max_size("block"), 4_000_000);
}
#[test]
fn test_validation_context_all_protocols() {
let mainnet_context =
ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
let testnet_context =
ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
let regtest_context =
ProtocolValidationContext::new(ProtocolVersion::Regtest, 1000).unwrap();
assert_eq!(mainnet_context.block_height, 1000);
assert_eq!(testnet_context.block_height, 1000);
assert_eq!(regtest_context.block_height, 1000);
assert!(mainnet_context.is_feature_enabled("segwit"));
assert!(testnet_context.is_feature_enabled("segwit"));
assert!(regtest_context.is_feature_enabled("segwit"));
assert!(mainnet_context.is_feature_enabled("taproot"));
assert!(testnet_context.is_feature_enabled("taproot"));
assert!(regtest_context.is_feature_enabled("taproot"));
assert!(mainnet_context.is_feature_enabled("rbf"));
assert!(testnet_context.is_feature_enabled("rbf"));
assert!(regtest_context.is_feature_enabled("rbf"));
}
#[test]
fn test_validation_context_feature_queries() {
let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
assert!(context.is_feature_enabled("segwit"));
assert!(context.is_feature_enabled("taproot"));
assert!(context.is_feature_enabled("rbf"));
assert!(!context.is_feature_enabled("nonexistent"));
assert!(!context.is_feature_enabled(""));
assert!(!context.is_feature_enabled("fast_mining"));
}
#[test]
fn test_validation_context_size_queries() {
let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
assert_eq!(context.get_max_size("block"), 4_000_000);
assert_eq!(context.get_max_size("transaction"), 1_000_000);
assert_eq!(context.get_max_size("script"), 10_000);
assert_eq!(context.get_max_size("unknown"), 0);
}
#[test]
fn test_validation_context_serialization() {
let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
let json = serde_json::to_string(&context).unwrap();
let deserialized: ProtocolValidationContext = serde_json::from_str(&json).unwrap();
assert_eq!(context.block_height, deserialized.block_height);
assert_eq!(
context.network_params.network_name,
deserialized.network_params.network_name
);
assert_eq!(
context.validation_rules.max_block_size,
deserialized.validation_rules.max_block_size
);
}
#[test]
fn test_validation_context_equality() {
let context1 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
let context2 = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
let context3 = ProtocolValidationContext::new(ProtocolVersion::Testnet3, 1000).unwrap();
assert_eq!(context1, context2);
assert_ne!(context1, context3); }
#[test]
fn test_block_size_validation() {
let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
let coinbase_tx = Transaction {
version: 1,
inputs: blvm_consensus::tx_inputs![TransactionInput {
prevout: OutPoint {
hash: [0u8; 32],
index: 0xffffffff,
},
script_sig: vec![0x01, 0x00], sequence: 0xffffffff,
}],
outputs: blvm_consensus::tx_outputs![TransactionOutput {
value: 50_0000_0000,
script_pubkey: vec![blvm_consensus::opcodes::OP_1],
}],
lock_time: 0,
};
let merkle_root = blvm_consensus::mining::calculate_merkle_root(&[coinbase_tx.clone()])
.expect("Should calculate merkle root");
let small_block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0u8; 32],
merkle_root,
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![coinbase_tx].into_boxed_slice(),
};
let result =
engine.validate_block_with_protocol(&small_block, &UtxoSet::default(), 1000, &context);
assert!(result.is_ok());
}
#[test]
fn test_transaction_size_validation() {
let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
let small_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0u8; 32],
index: 0,
},
script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 50_0000_0000,
script_pubkey: vec![
blvm_consensus::opcodes::OP_DUP,
blvm_consensus::opcodes::OP_HASH160,
blvm_consensus::opcodes::PUSH_20_BYTES,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
blvm_consensus::opcodes::OP_EQUALVERIFY,
blvm_consensus::opcodes::OP_CHECKSIG,
],
}]
.into(),
lock_time: 0,
};
let result = engine.validate_transaction_with_protocol(&small_tx, &context);
assert!(result.is_ok());
}
#[test]
fn test_script_size_validation() {
let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
let context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0u8; 32],
index: 0,
},
script_sig: vec![blvm_consensus::opcodes::PUSH_65_BYTES, 0x04],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 50_0000_0000,
script_pubkey: vec![
blvm_consensus::opcodes::OP_DUP,
blvm_consensus::opcodes::OP_HASH160,
blvm_consensus::opcodes::PUSH_20_BYTES,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
blvm_consensus::opcodes::OP_EQUALVERIFY,
blvm_consensus::opcodes::OP_CHECKSIG,
],
}]
.into(),
lock_time: 0,
};
let result = engine.validate_transaction_with_protocol(&tx, &context);
assert!(result.is_ok());
}
#[test]
fn test_validation_context_data() {
let mut context = ProtocolValidationContext::new(ProtocolVersion::BitcoinV1, 1000).unwrap();
context
.context_data
.insert("test_key".to_string(), "test_value".to_string());
assert_eq!(
context.context_data.get("test_key"),
Some(&"test_value".to_string())
);
assert_eq!(context.context_data.get("nonexistent"), None);
}
#[test]
fn test_validation_rules_boundary_values() {
let rules = ProtocolValidationRules::mainnet();
assert!(rules.max_block_size > 0);
assert!(rules.max_tx_size > 0);
assert!(rules.max_script_size > 0);
assert!(rules.max_fee_rate > rules.min_fee_rate);
assert!(rules.max_block_size <= 10_000_000); assert!(rules.max_tx_size <= 5_000_000); assert!(rules.max_script_size <= 50_000); }
}