diff --git a/lib-blockchain/docs/architecture.md b/lib-blockchain/docs/architecture.md index c234e22b..ea5bd565 100644 --- a/lib-blockchain/docs/architecture.md +++ b/lib-blockchain/docs/architecture.md @@ -328,6 +328,33 @@ Web4 Architecture: └─────────────────────────────────────────────────┘ ``` +## Consensus Architecture + +### Adaptive Difficulty Adjustment + +The blockchain uses governance-controlled difficulty parameters through `DifficultyConfig`: + +- **Configurable Parameters**: Target timespan, adjustment interval, and adjustment factors +- **Default Settings**: 14-day timespan, 2016-block interval, 4x max adjustment (Bitcoin-style) +- **Governance Updates**: Parameters can be modified via DAO proposals using `set_difficulty_config()` +- **Audit Trail**: `last_updated_at_height` tracks parameter changes + +```rust +pub struct DifficultyConfig { + pub target_timespan: u64, // Default: 14 days (1,209,600 seconds) + pub adjustment_interval: u64, // Default: 2016 blocks + pub max_decrease_factor: u64, // Default: 4 (difficulty can decrease by 4x max) + pub max_increase_factor: u64, // Default: 4 (difficulty can increase by 4x max) + pub last_updated_at_height: u64, +} +``` + +**Key Methods:** +- `target_block_time()` - Calculates target seconds per block (default: 600s) +- `clamp_timespan()` - Prevents extreme difficulty changes +- `validate()` - Ensures parameters are within safe ranges +- `adjust_difficulty_with_config()` - Calculates new difficulty using config parameters + ## Economic Architecture ### Universal Basic Income System diff --git a/lib-blockchain/src/blockchain.rs b/lib-blockchain/src/blockchain.rs index e6ecb229..9f48acc1 100644 --- a/lib-blockchain/src/blockchain.rs +++ b/lib-blockchain/src/blockchain.rs @@ -7,7 +7,7 @@ use std::collections::{HashMap, HashSet}; use anyhow::Result; use serde::{Serialize, Deserialize}; use tracing::{info, warn, error, debug}; -use crate::types::{Hash, Difficulty}; +use crate::types::{Hash, Difficulty, DifficultyConfig}; use crate::transaction::{Transaction, TransactionInput, TransactionOutput, IdentityTransactionData}; use crate::types::transaction_type::TransactionType; use crate::block::Block; @@ -40,6 +40,8 @@ pub struct Blockchain { pub height: u64, /// Current mining difficulty pub difficulty: Difficulty, + /// Difficulty adjustment configuration (governance-controlled) + pub difficulty_config: DifficultyConfig, /// Total work done (cumulative difficulty) pub total_work: u128, /// UTXO set for transaction validation @@ -162,6 +164,7 @@ impl Blockchain { blocks: vec![genesis_block.clone()], height: 0, difficulty: Difficulty::from_bits(crate::INITIAL_DIFFICULTY), + difficulty_config: DifficultyConfig::default(), total_work: 0, utxo_set: HashMap::new(), nullifier_set: HashSet::new(), @@ -798,8 +801,8 @@ impl Blockchain { tokio::runtime::Handle::current().block_on(async { let coord = coordinator.read().await; let interval = coord.get_difficulty_adjustment_interval().await; - let config = coord.get_difficulty_config().await; - (interval, config.target_timespan) + let target_timespan = coord.get_difficulty_target_timespan().await; + (interval, target_timespan) }) }) } else { @@ -913,6 +916,33 @@ impl Blockchain { self.height } + /// Get the current difficulty configuration + pub fn get_difficulty_config(&self) -> &DifficultyConfig { + &self.difficulty_config + } + + /// Update the difficulty configuration (for governance updates) + /// + /// This method validates the new configuration before applying it. + /// The `last_updated_at_height` field will be set to the current blockchain height. + /// + /// # Errors + /// Returns an error if the configuration parameters are invalid. + pub fn set_difficulty_config(&mut self, mut config: DifficultyConfig) -> Result<()> { + config.validate().map_err(|e| anyhow::anyhow!("Invalid difficulty config: {}", e))?; + config.last_updated_at_height = self.height; + info!( + "Updating difficulty config at height {}: target_timespan={}, adjustment_interval={}, max_decrease={}, max_increase={}", + self.height, + config.target_timespan, + config.adjustment_interval, + config.max_difficulty_decrease_factor, + config.max_difficulty_increase_factor + ); + self.difficulty_config = config; + Ok(()) + } + /// Check if a nullifier has been used pub fn is_nullifier_used(&self, nullifier: &Hash) -> bool { self.nullifier_set.contains(nullifier) diff --git a/lib-blockchain/src/integration/consensus_integration.rs b/lib-blockchain/src/integration/consensus_integration.rs index d0619366..18eb7e97 100644 --- a/lib-blockchain/src/integration/consensus_integration.rs +++ b/lib-blockchain/src/integration/consensus_integration.rs @@ -260,11 +260,21 @@ impl BlockchainConsensusCoordinator { } /// Get the current difficulty configuration + /// + /// **Note**: This clones the entire DifficultyConfig. For better performance, + /// use specific field getters like `get_difficulty_target_timespan()` when you + /// only need individual fields. pub async fn get_difficulty_config(&self) -> DifficultyConfig { let manager = self.difficulty_manager.read().await; manager.config().clone() } - + + /// Get the target timespan for difficulty adjustment without cloning the entire config + pub async fn get_difficulty_target_timespan(&self) -> u64 { + let manager = self.difficulty_manager.read().await; + manager.target_timespan() + } + /// Calculate new difficulty using the consensus-owned algorithm /// /// This is the entry point for blockchain difficulty adjustment. diff --git a/lib-blockchain/src/types/difficulty.rs b/lib-blockchain/src/types/difficulty.rs index d2ac7992..74718288 100644 --- a/lib-blockchain/src/types/difficulty.rs +++ b/lib-blockchain/src/types/difficulty.rs @@ -5,6 +5,7 @@ use crate::types::Hash; use serde::{Serialize, Deserialize}; +use tracing::warn; /// Difficulty representation for proof-of-work #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] @@ -71,6 +72,150 @@ impl std::fmt::Display for Difficulty { } } +/// Governance-controlled difficulty adjustment parameters for PoUW consensus +/// +/// This struct stores configurable parameters that control how mining difficulty +/// adjusts over time. These parameters can be updated through governance proposals +/// to adapt to changing network conditions. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct DifficultyConfig { + /// Target timespan for difficulty adjustment period (in seconds) + /// Default: 14 days (1,209,600 seconds) + pub target_timespan: u64, + + /// Number of blocks between difficulty adjustments + /// Default: 2016 blocks (Bitcoin-style) + pub adjustment_interval: u64, + + /// Maximum factor by which difficulty can decrease per adjustment + /// Value of 4 means difficulty can decrease by at most 4x per adjustment + /// (i.e., actual_timespan is clamped to at most target_timespan * 4) + /// Default: 4 + pub max_difficulty_decrease_factor: u64, + + /// Maximum factor by which difficulty can increase per adjustment + /// Value of 4 means difficulty can increase by at most 4x per adjustment + /// (i.e., actual_timespan is clamped to at least target_timespan / 4) + /// Default: 4 + pub max_difficulty_increase_factor: u64, + + /// Block height at which this configuration was last updated + /// Used for governance tracking and auditability + pub last_updated_at_height: u64, +} + +impl DifficultyConfig { + /// Create a new DifficultyConfig with default values + pub fn new() -> Self { + Self::default() + } + + /// Create a new DifficultyConfig with custom parameters + /// + /// Validates all parameters before creating the configuration. + /// Returns an error if any parameters are invalid. + /// + /// # Errors + /// Returns a string error if validation fails (e.g., zero values, values exceeding limits) + pub fn with_params( + target_timespan: u64, + adjustment_interval: u64, + max_difficulty_decrease_factor: u64, + max_difficulty_increase_factor: u64, + last_updated_at_height: u64, + ) -> Result { + let config = Self { + target_timespan, + adjustment_interval, + max_difficulty_decrease_factor, + max_difficulty_increase_factor, + last_updated_at_height, + }; + config.validate()?; + Ok(config) + } + + /// Get the target block time in seconds + /// Calculated as target_timespan / adjustment_interval + pub fn target_block_time(&self) -> u64 { + if self.adjustment_interval == 0 { + warn!("DifficultyConfig has adjustment_interval=0, using default 10 minute block time"); + return 600; // Default to 10 minutes if misconfigured + } + self.target_timespan / self.adjustment_interval + } + + /// Calculate the clamped timespan for difficulty adjustment + /// + /// This prevents extreme difficulty changes by clamping the actual timespan + /// to be within [target_timespan / max_difficulty_increase_factor, target_timespan * max_difficulty_decrease_factor] + pub fn clamp_timespan(&self, actual_timespan: u64) -> u64 { + // If blocks came too fast, difficulty increases (timespan clamped to minimum) + let min_timespan = self.target_timespan / self.max_difficulty_increase_factor.max(1); + // If blocks came too slow, difficulty decreases (timespan clamped to maximum) + let max_timespan = self.target_timespan.saturating_mul(self.max_difficulty_decrease_factor); + + actual_timespan.clamp(min_timespan, max_timespan) + } + + /// Validate the configuration parameters + /// + /// Returns an error if any parameters are invalid or would cause issues + pub fn validate(&self) -> Result<(), String> { + if self.target_timespan == 0 { + return Err("target_timespan must be greater than 0".to_string()); + } + + if self.adjustment_interval == 0 { + return Err("adjustment_interval must be greater than 0".to_string()); + } + + if self.max_difficulty_decrease_factor == 0 { + return Err("max_difficulty_decrease_factor must be greater than 0".to_string()); + } + + if self.max_difficulty_increase_factor == 0 { + return Err("max_difficulty_increase_factor must be greater than 0".to_string()); + } + + // Check for reasonable limits + if self.target_timespan > 365 * 24 * 60 * 60 { + return Err("target_timespan cannot exceed 1 year".to_string()); + } + + if self.adjustment_interval > 1_000_000 { + return Err("adjustment_interval cannot exceed 1,000,000 blocks".to_string()); + } + + if self.max_difficulty_decrease_factor > 100 || self.max_difficulty_increase_factor > 100 { + return Err("difficulty adjustment factors cannot exceed 100".to_string()); + } + + Ok(()) + } +} + +impl Default for DifficultyConfig { + fn default() -> Self { + Self { + // 14 days in seconds (same as Bitcoin) + target_timespan: 14 * 24 * 60 * 60, + + // 2016 blocks (Bitcoin-style) + adjustment_interval: 2016, + + // Maximum 4x decrease per adjustment (difficulty can quarter) + max_difficulty_decrease_factor: 4, + + // Maximum 4x increase per adjustment (difficulty can quadruple) + max_difficulty_increase_factor: 4, + + // Genesis block + last_updated_at_height: 0, + } + } +} + /// Calculate target from difficulty bits (Bitcoin-style) pub fn calculate_target(difficulty_bits: u32) -> [u8; 32] { let mut target = [0u8; 32]; @@ -134,27 +279,43 @@ pub fn min_target() -> [u8; 32] { target } -/// Difficulty adjustment calculation -pub fn adjust_difficulty( +/// Difficulty adjustment calculation using DifficultyConfig +/// +/// This is the preferred method that uses governance-controlled parameters. +pub fn adjust_difficulty_with_config( current_difficulty: u32, actual_timespan: u64, - target_timespan: u64, + config: &DifficultyConfig, ) -> u32 { - // Clamp the adjustment to prevent extreme changes - let max_adjustment = target_timespan * 4; - let min_adjustment = target_timespan / 4; - - let clamped_timespan = actual_timespan - .max(min_adjustment) - .min(max_adjustment); - + let clamped_timespan = config.clamp_timespan(actual_timespan); + // Calculate new difficulty - let new_difficulty = (current_difficulty as u64 * target_timespan / clamped_timespan) as u32; - + let new_difficulty = + (current_difficulty as u64 * config.target_timespan / clamped_timespan) as u32; + // Ensure difficulty doesn't go to zero new_difficulty.max(1) } +/// Difficulty adjustment calculation (legacy function for backward compatibility) +/// +/// For new code, prefer using `adjust_difficulty_with_config` with a `DifficultyConfig`. +pub fn adjust_difficulty( + current_difficulty: u32, + actual_timespan: u64, + target_timespan: u64, +) -> u32 { + // Use default config factors for backward compatibility + let config = DifficultyConfig { + target_timespan, + adjustment_interval: 2016, // Not used in this calculation + max_difficulty_decrease_factor: 4, + max_difficulty_increase_factor: 4, + last_updated_at_height: 0, + }; + adjust_difficulty_with_config(current_difficulty, actual_timespan, &config) +} + /// Calculate work done for a given difficulty pub fn difficulty_to_work(difficulty: u32) -> u128 { if difficulty == 0 { @@ -214,4 +375,146 @@ mod tests { assert!(min_diff.bits() > max_diff.bits()); // Lower bits = higher difficulty } + + #[test] + fn test_difficulty_config_default() { + let config = DifficultyConfig::default(); + + assert_eq!(config.target_timespan, 14 * 24 * 60 * 60); // 14 days + assert_eq!(config.adjustment_interval, 2016); + assert_eq!(config.max_difficulty_decrease_factor, 4); + assert_eq!(config.max_difficulty_increase_factor, 4); + assert_eq!(config.last_updated_at_height, 0); + } + + #[test] + fn test_difficulty_config_target_block_time() { + let config = DifficultyConfig::default(); + + // 14 days / 2016 blocks = 600 seconds (10 minutes) + assert_eq!(config.target_block_time(), 600); + } + + #[test] + fn test_difficulty_config_clamp_timespan() { + let config = DifficultyConfig::default(); + + // Target timespan is 14 days + let target = 14 * 24 * 60 * 60; + + // Test clamping at minimum (target / 4) + let too_fast = 1000; + let clamped = config.clamp_timespan(too_fast); + assert_eq!(clamped, target / 4); + + // Test clamping at maximum (target * 4) + let too_slow = 100 * 24 * 60 * 60; + let clamped = config.clamp_timespan(too_slow); + assert_eq!(clamped, target * 4); + + // Test no clamping for normal values + let normal = target; + let clamped = config.clamp_timespan(normal); + assert_eq!(clamped, normal); + } + + #[test] + fn test_difficulty_config_validation() { + // Valid config should pass + let valid_config = DifficultyConfig::default(); + assert!(valid_config.validate().is_ok()); + + // Invalid target_timespan + let mut invalid = DifficultyConfig::default(); + invalid.target_timespan = 0; + assert!(invalid.validate().is_err()); + + // Invalid adjustment_interval + let mut invalid = DifficultyConfig::default(); + invalid.adjustment_interval = 0; + assert!(invalid.validate().is_err()); + + // Invalid max_difficulty_decrease_factor + let mut invalid = DifficultyConfig::default(); + invalid.max_difficulty_decrease_factor = 0; + assert!(invalid.validate().is_err()); + + // Invalid max_difficulty_increase_factor + let mut invalid = DifficultyConfig::default(); + invalid.max_difficulty_increase_factor = 0; + assert!(invalid.validate().is_err()); + } + + #[test] + fn test_difficulty_config_serialization() { + let config = DifficultyConfig::default(); + + // Serialize to JSON + let json = serde_json::to_string(&config).unwrap(); + + // Deserialize back + let deserialized: DifficultyConfig = serde_json::from_str(&json).unwrap(); + + // Should be equal + assert_eq!(config, deserialized); + } + + #[test] + fn test_difficulty_config_with_params() { + let config = DifficultyConfig::with_params( + 7 * 24 * 60 * 60, // 7 days + 1008, // Half of Bitcoin's interval + 2, // Max 2x decrease + 2, // Max 2x increase + 1000, // Updated at block 1000 + ).expect("Valid parameters should not fail"); + + assert_eq!(config.target_timespan, 7 * 24 * 60 * 60); + assert_eq!(config.adjustment_interval, 1008); + assert_eq!(config.max_difficulty_decrease_factor, 2); + assert_eq!(config.max_difficulty_increase_factor, 2); + assert_eq!(config.last_updated_at_height, 1000); + } + + #[test] + fn test_difficulty_config_with_params_invalid() { + // Test that with_params validates parameters + + // Invalid: target_timespan = 0 + assert!(DifficultyConfig::with_params(0, 1008, 2, 2, 0).is_err()); + + // Invalid: adjustment_interval = 0 + assert!(DifficultyConfig::with_params(7 * 24 * 60 * 60, 0, 2, 2, 0).is_err()); + + // Invalid: max_difficulty_decrease_factor = 0 + assert!(DifficultyConfig::with_params(7 * 24 * 60 * 60, 1008, 0, 2, 0).is_err()); + + // Invalid: max_difficulty_increase_factor = 0 + assert!(DifficultyConfig::with_params(7 * 24 * 60 * 60, 1008, 2, 0, 0).is_err()); + + // Invalid: target_timespan exceeds 1 year + assert!(DifficultyConfig::with_params(365 * 24 * 60 * 60 + 1, 1008, 2, 2, 0).is_err()); + } + + #[test] + fn test_adjust_difficulty_with_config() { + let config = DifficultyConfig::default(); + let current_difficulty = 1000u32; + + // If blocks come too fast (half the expected time), difficulty should increase + let fast_timespan = config.target_timespan / 2; + let new_difficulty = adjust_difficulty_with_config(current_difficulty, fast_timespan, &config); + assert!(new_difficulty > current_difficulty); + + // If blocks come too slow (double the expected time), difficulty should decrease + let slow_timespan = config.target_timespan * 2; + let new_difficulty = adjust_difficulty_with_config(current_difficulty, slow_timespan, &config); + assert!(new_difficulty < current_difficulty); + + // Extreme fast should be clamped to 4x max increase + let very_fast = 1000u64; // Much faster than target + let new_difficulty = adjust_difficulty_with_config(current_difficulty, very_fast, &config); + // Due to clamping, max increase is 4x + assert!(new_difficulty <= current_difficulty * 4); + } } diff --git a/lib-blockchain/src/types/mod.rs b/lib-blockchain/src/types/mod.rs index f214f577..ebf9020d 100644 --- a/lib-blockchain/src/types/mod.rs +++ b/lib-blockchain/src/types/mod.rs @@ -29,7 +29,7 @@ pub mod message_type; // Re-export blockchain core types pub use transaction_type::*; pub use hash::*; -pub use difficulty::*; +pub use difficulty::{Difficulty, DifficultyConfig, calculate_target, meets_difficulty, target_to_difficulty, max_target, min_target, adjust_difficulty, adjust_difficulty_with_config, difficulty_to_work}; pub use mining::*; // Re-export DAO and SOV economy types diff --git a/zhtp/src/unified_server.rs b/zhtp/src/unified_server.rs index a84471bb..92f7451e 100644 --- a/zhtp/src/unified_server.rs +++ b/zhtp/src/unified_server.rs @@ -321,14 +321,6 @@ impl ZhtpUnifiedServer { )); } - // Validate port numbers are in valid range - if quic_port > 65535 { - return Err(anyhow::anyhow!( - "QUIC port ({}) exceeds maximum valid port (65535)", - quic_port - )); - } - info!("Creating ZHTP Unified Server (ID: {})", server_id); info!("Port: {} (HTTP + UDP + WiFi + Bootstrap)", port); info!("Discovery port: {}, QUIC port: {}", discovery_port, quic_port);