From 4983193f63dd2c45b11c8cb838950c2a322ce407 Mon Sep 17 00:00:00 2001 From: Gulshan Yadav Date: Mon, 19 Jan 2026 20:10:05 +0530 Subject: [PATCH] feat(dag): add Phase 13 M1 - DAGKnight 32/100 BPS support - Add BlockRateConfig enum with Standard (10 BPS), Enhanced (32 BPS), and Maximum (100 BPS) presets - Add AdaptiveKBounds with scaled k ranges per block rate: - Standard: k 8-64, default 18 - Enhanced: k 16-128, default 32 - Maximum: k 50-255, default 64 - Add DagKnightManager::with_config() constructor for block rate selection - Update adaptive k calculation to use configurable bounds - Add NetworkConfig module in synor-consensus with: - BpsMode enum and NetworkConfig struct - DAA window, finality depth, pruning depth scaling - BPS comparison table generator - Add comprehensive tests for all block rate configurations --- crates/synor-consensus/src/lib.rs | 2 + crates/synor-consensus/src/network.rs | 436 ++++++++++++++++++++++++++ crates/synor-dag/src/dagknight.rs | 321 ++++++++++++++++--- crates/synor-dag/src/lib.rs | 4 +- 4 files changed, 722 insertions(+), 41 deletions(-) create mode 100644 crates/synor-consensus/src/network.rs diff --git a/crates/synor-consensus/src/lib.rs b/crates/synor-consensus/src/lib.rs index e19539a..9197abd 100644 --- a/crates/synor-consensus/src/lib.rs +++ b/crates/synor-consensus/src/lib.rs @@ -105,12 +105,14 @@ pub mod difficulty; pub mod genesis; +pub mod network; pub mod rewards; pub mod utxo; pub mod validation; pub use difficulty::{DaaParams, DifficultyManager}; pub use genesis::{ChainConfig, Checkpoint, GenesisAllocation, GenesisError}; +pub use network::{BpsMode, NetworkConfig}; pub use rewards::{BlockReward, RewardCalculator}; pub use utxo::{UtxoDiff, UtxoEntry, UtxoError, UtxoSet}; pub use validation::{BlockValidator, TransactionValidator, ValidationError}; diff --git a/crates/synor-consensus/src/network.rs b/crates/synor-consensus/src/network.rs new file mode 100644 index 0000000..0695766 --- /dev/null +++ b/crates/synor-consensus/src/network.rs @@ -0,0 +1,436 @@ +//! Network configuration with BPS (Blocks Per Second) presets. +//! +//! Synor supports multiple block rate configurations: +//! - **Standard (10 BPS)**: Default configuration, 100ms block time +//! - **Fast (32 BPS)**: High-throughput mode, ~31ms block time +//! - **Ultra (100 BPS)**: Maximum throughput, 10ms block time +//! +//! # Selecting a Configuration +//! +//! Higher BPS provides faster transaction confirmation but requires: +//! - Better network connectivity (lower latency) +//! - More powerful nodes (higher processing requirements) +//! - Adjusted DAA window sizes +//! +//! ```rust +//! use synor_consensus::network::{NetworkConfig, BpsMode}; +//! +//! // Create standard 10 BPS configuration +//! let config = NetworkConfig::standard(); +//! +//! // Or use a preset +//! let fast_config = NetworkConfig::from_bps_mode(BpsMode::Fast32); +//! ``` + +use std::time::Duration; + +/// Blocks per second mode. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum BpsMode { + /// Standard mode: 10 blocks per second (100ms block time) + /// - Suitable for most network conditions + /// - Requires ~100ms P95 network latency + Standard10, + + /// Fast mode: 32 blocks per second (~31ms block time) + /// - High-throughput for well-connected networks + /// - Requires ~50ms P95 network latency + Fast32, + + /// Ultra mode: 100 blocks per second (10ms block time) + /// - Maximum throughput for data center deployments + /// - Requires ~20ms P95 network latency + Ultra100, + + /// Custom BPS configuration + Custom(u64), +} + +impl BpsMode { + /// Returns the blocks per second for this mode. + pub fn bps(&self) -> u64 { + match self { + BpsMode::Standard10 => 10, + BpsMode::Fast32 => 32, + BpsMode::Ultra100 => 100, + BpsMode::Custom(bps) => *bps, + } + } + + /// Returns the target block time in milliseconds. + pub fn block_time_ms(&self) -> u64 { + 1000 / self.bps() + } + + /// Returns the recommended minimum network latency (P95) in ms. + pub fn recommended_latency_ms(&self) -> u64 { + match self { + BpsMode::Standard10 => 100, + BpsMode::Fast32 => 50, + BpsMode::Ultra100 => 20, + BpsMode::Custom(bps) => 1000 / bps / 2, // Half of block time + } + } +} + +impl Default for BpsMode { + fn default() -> Self { + BpsMode::Standard10 + } +} + +impl std::fmt::Display for BpsMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BpsMode::Standard10 => write!(f, "Standard (10 BPS)"), + BpsMode::Fast32 => write!(f, "Fast (32 BPS)"), + BpsMode::Ultra100 => write!(f, "Ultra (100 BPS)"), + BpsMode::Custom(bps) => write!(f, "Custom ({} BPS)", bps), + } + } +} + +/// Network configuration parameters. +#[derive(Clone, Debug)] +pub struct NetworkConfig { + /// BPS mode + pub bps_mode: BpsMode, + + /// Blocks per second + pub blocks_per_second: u64, + + /// Target block time in milliseconds + pub target_block_time_ms: u64, + + /// DAA window size (number of blocks) + /// Adjusted to cover approximately the same time period across BPS modes + pub daa_window_size: u64, + + /// GHOSTDAG k parameter (for standard GHOSTDAG compatibility) + pub ghostdag_k: u8, + + /// DAGKnight adaptive k minimum + pub dagknight_k_min: u8, + + /// DAGKnight adaptive k maximum + pub dagknight_k_max: u8, + + /// Finality depth (blocks before considered final) + pub finality_depth: u64, + + /// Pruning depth (blocks to keep before pruning) + pub pruning_depth: u64, + + /// Merge set size limit (max parents per block) + pub merge_set_size_limit: u64, + + /// Expected network delay (for initial k calculation) + pub expected_delay_ms: u64, +} + +impl NetworkConfig { + /// Creates a standard 10 BPS configuration. + pub fn standard() -> Self { + Self::from_bps_mode(BpsMode::Standard10) + } + + /// Creates a fast 32 BPS configuration. + pub fn fast() -> Self { + Self::from_bps_mode(BpsMode::Fast32) + } + + /// Creates an ultra 100 BPS configuration. + pub fn ultra() -> Self { + Self::from_bps_mode(BpsMode::Ultra100) + } + + /// Creates a configuration from a BPS mode. + pub fn from_bps_mode(mode: BpsMode) -> Self { + match mode { + BpsMode::Standard10 => Self { + bps_mode: mode, + blocks_per_second: 10, + target_block_time_ms: 100, + daa_window_size: 2641, // ~264s window + ghostdag_k: 18, // For 10 BPS + dagknight_k_min: 8, + dagknight_k_max: 64, + finality_depth: 864, // ~86 seconds + pruning_depth: 864_000, // ~24 hours + merge_set_size_limit: 180, + expected_delay_ms: 100, + }, + BpsMode::Fast32 => Self { + bps_mode: mode, + blocks_per_second: 32, + target_block_time_ms: 31, // ~31.25ms + daa_window_size: 8461, // ~264s window at 32 BPS + ghostdag_k: 58, // Scaled for 32 BPS + dagknight_k_min: 16, // Higher min for faster blocks + dagknight_k_max: 128, // Higher max for adaptation + finality_depth: 2765, // ~86 seconds at 32 BPS + pruning_depth: 2_764_800, // ~24 hours at 32 BPS + merge_set_size_limit: 576, // 32/10 * 180 + expected_delay_ms: 50, + }, + BpsMode::Ultra100 => Self { + bps_mode: mode, + blocks_per_second: 100, + target_block_time_ms: 10, + daa_window_size: 26410, // ~264s window at 100 BPS + ghostdag_k: 180, // Scaled for 100 BPS + dagknight_k_min: 50, // Higher min for very fast blocks + dagknight_k_max: 255, // u8 max - very high for adaptation + finality_depth: 8640, // ~86 seconds at 100 BPS + pruning_depth: 8_640_000, // ~24 hours at 100 BPS + merge_set_size_limit: 1800, // 100/10 * 180 + expected_delay_ms: 20, + }, + BpsMode::Custom(bps) => { + let scale = bps as f64 / 10.0; + Self { + bps_mode: mode, + blocks_per_second: bps, + target_block_time_ms: 1000 / bps, + daa_window_size: (2641.0 * scale) as u64, + ghostdag_k: (18.0 * scale).min(255.0) as u8, + dagknight_k_min: (8.0 * scale.sqrt()) as u8, + dagknight_k_max: (64.0 * scale).min(255.0) as u8, + finality_depth: (864.0 * scale) as u64, + pruning_depth: (864_000.0 * scale) as u64, + merge_set_size_limit: (180.0 * scale) as u64, + expected_delay_ms: (100.0 / scale).max(10.0) as u64, + } + } + } + } + + /// Creates a configuration with custom BPS. + pub fn with_bps(bps: u64) -> Self { + Self::from_bps_mode(BpsMode::Custom(bps)) + } + + /// Returns the block time as a Duration. + pub fn block_time(&self) -> Duration { + Duration::from_millis(self.target_block_time_ms) + } + + /// Calculates the recommended k for given network latency. + /// + /// Formula: k = ceil(bps * delay_seconds * safety_margin) + pub fn calculate_k_for_latency(&self, latency_ms: f64) -> u8 { + const SAFETY_MARGIN: f64 = 1.5; + let delay_seconds = latency_ms / 1000.0; + let k = (self.blocks_per_second as f64 * delay_seconds * SAFETY_MARGIN).ceil() as u8; + k.clamp(self.dagknight_k_min, self.dagknight_k_max) + } + + /// Estimates transaction throughput (TPS) for given average tx/block. + pub fn estimate_tps(&self, avg_tx_per_block: u64) -> f64 { + self.blocks_per_second as f64 * avg_tx_per_block as f64 + } + + /// Estimates confirmation time at a given confidence level. + /// + /// Based on finality depth and block rate. + pub fn estimate_confirmation_time(&self, confidence_sigma: f64) -> Duration { + // Confirmation requires depth proportional to sigma + let required_depth = (self.finality_depth as f64 * confidence_sigma / 3.0) as u64; + let blocks_time_ms = required_depth * self.target_block_time_ms; + Duration::from_millis(blocks_time_ms) + } + + /// Validates network conditions are suitable for this configuration. + /// + /// Returns true if the observed latency is acceptable. + pub fn is_latency_acceptable(&self, observed_p95_latency_ms: u64) -> bool { + observed_p95_latency_ms <= self.expected_delay_ms * 2 + } + + /// Creates DAA parameters for this configuration. + pub fn to_daa_params(&self) -> crate::DaaParams { + crate::DaaParams { + target_time_ms: self.target_block_time_ms, + window_size: self.daa_window_size, + max_adjustment_factor: 4.0, + min_difficulty: 1, + } + } +} + +impl Default for NetworkConfig { + fn default() -> Self { + Self::standard() + } +} + +/// Comparison table for BPS modes. +pub fn bps_comparison_table() -> String { + let standard = NetworkConfig::standard(); + let fast = NetworkConfig::fast(); + let ultra = NetworkConfig::ultra(); + + let mut table = String::from( + "| Property | Standard (10 BPS) | Fast (32 BPS) | Ultra (100 BPS) |\n\ + |----------|-------------------|---------------|------------------|\n" + ); + + // Block Time + table.push_str(&format!( + "| Block Time | {}ms | {}ms | {}ms |\n", + standard.target_block_time_ms, fast.target_block_time_ms, ultra.target_block_time_ms + )); + + // DAA Window + table.push_str(&format!( + "| DAA Window | {} blocks | {} blocks | {} blocks |\n", + standard.daa_window_size, fast.daa_window_size, ultra.daa_window_size + )); + + // GHOSTDAG K + table.push_str(&format!( + "| GHOSTDAG K | {} | {} | {} |\n", + standard.ghostdag_k, fast.ghostdag_k, ultra.ghostdag_k + )); + + // Finality Depth + table.push_str(&format!( + "| Finality Depth | {} blocks | {} blocks | {} blocks |\n", + standard.finality_depth, fast.finality_depth, ultra.finality_depth + )); + + // ~Finality Time + let std_time = standard.finality_depth * standard.target_block_time_ms / 1000; + let fast_time = fast.finality_depth * fast.target_block_time_ms / 1000; + let ultra_time = ultra.finality_depth * ultra.target_block_time_ms / 1000; + table.push_str(&format!( + "| ~Finality Time | ~{}s | ~{}s | ~{}s |\n", + std_time, fast_time, ultra_time + )); + + // Recommended Latency + table.push_str(&format!( + "| Rec. Latency | ≤{}ms | ≤{}ms | ≤{}ms |\n", + standard.expected_delay_ms, fast.expected_delay_ms, ultra.expected_delay_ms + )); + + // Estimated TPS + table.push_str(&format!( + "| Est. TPS @1000tx/block | {:.0} | {:.0} | {:.0} |\n", + standard.estimate_tps(1000), fast.estimate_tps(1000), ultra.estimate_tps(1000) + )); + + table +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bps_modes() { + assert_eq!(BpsMode::Standard10.bps(), 10); + assert_eq!(BpsMode::Fast32.bps(), 32); + assert_eq!(BpsMode::Ultra100.bps(), 100); + assert_eq!(BpsMode::Custom(50).bps(), 50); + } + + #[test] + fn test_block_times() { + assert_eq!(BpsMode::Standard10.block_time_ms(), 100); + assert_eq!(BpsMode::Fast32.block_time_ms(), 31); // 1000/32 = 31.25 + assert_eq!(BpsMode::Ultra100.block_time_ms(), 10); + } + + #[test] + fn test_standard_config() { + let config = NetworkConfig::standard(); + assert_eq!(config.blocks_per_second, 10); + assert_eq!(config.target_block_time_ms, 100); + assert_eq!(config.ghostdag_k, 18); + } + + #[test] + fn test_fast_config() { + let config = NetworkConfig::fast(); + assert_eq!(config.blocks_per_second, 32); + assert!(config.target_block_time_ms <= 32); // ~31.25ms + assert!(config.ghostdag_k > 18); // Should be higher than standard + } + + #[test] + fn test_ultra_config() { + let config = NetworkConfig::ultra(); + assert_eq!(config.blocks_per_second, 100); + assert_eq!(config.target_block_time_ms, 10); + assert!(config.ghostdag_k > 100); // Much higher for ultra + } + + #[test] + fn test_calculate_k_for_latency() { + let config = NetworkConfig::standard(); + + // Low latency = lower k (but clamped to min) + // 10ms: k = ceil(10 * 0.01 * 1.5) = 1 -> clamped to min (8) + let k_low = config.calculate_k_for_latency(10.0); + assert!(k_low >= config.dagknight_k_min); + assert_eq!(k_low, config.dagknight_k_min); // Should hit min clamp + + // Medium latency = above min + // 1000ms: k = ceil(10 * 1.0 * 1.5) = 15 -> above min + let k_medium = config.calculate_k_for_latency(1000.0); + assert!(k_medium > config.dagknight_k_min); + + // High latency = higher k + // 3000ms: k = ceil(10 * 3.0 * 1.5) = 45 -> much higher + let k_high = config.calculate_k_for_latency(3000.0); + assert!(k_high > k_medium); + assert!(k_high <= config.dagknight_k_max); + } + + #[test] + fn test_estimate_tps() { + let standard = NetworkConfig::standard(); + let ultra = NetworkConfig::ultra(); + + // At 100 tx/block: + // Standard: 10 * 100 = 1000 TPS + // Ultra: 100 * 100 = 10000 TPS + assert_eq!(standard.estimate_tps(100), 1000.0); + assert_eq!(ultra.estimate_tps(100), 10000.0); + } + + #[test] + fn test_latency_acceptable() { + let config = NetworkConfig::standard(); // expects 100ms + + assert!(config.is_latency_acceptable(50)); // Good + assert!(config.is_latency_acceptable(100)); // OK + assert!(config.is_latency_acceptable(200)); // Still OK (2x limit) + assert!(!config.is_latency_acceptable(300)); // Too high + } + + #[test] + fn test_custom_bps() { + let config = NetworkConfig::with_bps(50); + assert_eq!(config.blocks_per_second, 50); + assert_eq!(config.target_block_time_ms, 20); + } + + #[test] + fn test_daa_params_conversion() { + let config = NetworkConfig::fast(); + let daa_params = config.to_daa_params(); + + assert_eq!(daa_params.target_time_ms, config.target_block_time_ms); + assert_eq!(daa_params.window_size, config.daa_window_size); + } + + #[test] + fn test_comparison_table() { + let table = bps_comparison_table(); + assert!(table.contains("Standard")); + assert!(table.contains("Fast")); + assert!(table.contains("Ultra")); + } +} diff --git a/crates/synor-dag/src/dagknight.rs b/crates/synor-dag/src/dagknight.rs index 37ce2ea..d2e6d98 100644 --- a/crates/synor-dag/src/dagknight.rs +++ b/crates/synor-dag/src/dagknight.rs @@ -11,6 +11,16 @@ //! 3. **No Fixed Delay Assumption**: Learns actual network behavior //! 4. **Faster Confirmation**: Converges faster under good network conditions //! +//! # Block Rate Configurations +//! +//! DAGKnight supports multiple block rates via `BlockRateConfig`: +//! - **Standard (10 BPS)**: 100ms blocks, k range 8-64 (default GHOSTDAG) +//! - **Enhanced (32 BPS)**: ~31ms blocks, k range 16-128 (Phase 13 upgrade) +//! - **Maximum (100 BPS)**: 10ms blocks, k range 50-256 (stretch goal) +//! +//! Higher block rates require better network conditions and scale k bounds +//! proportionally to maintain security under increased parallel block creation. +//! //! # Algorithm Overview //! //! DAGKnight maintains the core GHOSTDAG blue set selection but adds: @@ -33,29 +43,56 @@ use crate::{ ghostdag::{GhostdagData, GhostdagError, GhostdagManager}, latency::{LatencyStats, LatencyTracker}, reachability::ReachabilityStore, - BlockId, BlueScore, GHOSTDAG_K, + BlockId, BlockRateConfig, BlueScore, GHOSTDAG_K, }; -/// Minimum adaptive k value (security lower bound). -const MIN_ADAPTIVE_K: u8 = 8; - -/// Maximum adaptive k value (performance upper bound). -const MAX_ADAPTIVE_K: u8 = 64; - -/// Default k when insufficient latency data is available. -const DEFAULT_K: u8 = GHOSTDAG_K; - /// Number of samples required before adapting k. const MIN_SAMPLES_FOR_ADAPTATION: usize = 100; -/// Block rate (blocks per second) - used for k calculation. -/// At 10 BPS with 100ms block time, this is the baseline. -const BLOCK_RATE_BPS: f64 = 10.0; - /// Safety margin multiplier for k calculation. /// Higher values = more conservative (safer but lower throughput). const SAFETY_MARGIN: f64 = 1.5; +/// K parameter bounds for different block rates. +/// Higher block rates need higher k to accommodate network latency. +#[derive(Clone, Copy, Debug)] +pub struct AdaptiveKBounds { + /// Minimum k (security lower bound). + pub min_k: u8, + /// Maximum k (performance upper bound). + pub max_k: u8, + /// Default k when insufficient data. + pub default_k: u8, +} + +impl AdaptiveKBounds { + /// Creates bounds for the given block rate configuration. + pub const fn for_block_rate(config: BlockRateConfig) -> Self { + match config { + // Standard 10 BPS: k 8-64, default 18 + BlockRateConfig::Standard => Self { + min_k: 8, + max_k: 64, + default_k: GHOSTDAG_K, + }, + // Enhanced 32 BPS: k 16-128, default 32 + // Scaled: min * 3.2, max * 2 (with safety margin) + BlockRateConfig::Enhanced => Self { + min_k: 16, + max_k: 128, + default_k: 32, + }, + // Maximum 100 BPS: k 50-256, default 64 + // Requires extremely low latency (data center grade) + BlockRateConfig::Maximum => Self { + min_k: 50, + max_k: 255, // u8 max + default_k: 64, + }, + } + } +} + /// Confirmation confidence levels. #[derive(Clone, Copy, Debug, PartialEq)] pub enum ConfirmationConfidence { @@ -108,30 +145,55 @@ pub struct DagKnightManager { latency_tracker: Arc, /// Current adaptive k value. adaptive_k: RwLock, - /// Block rate setting. + /// Block rate configuration. + block_rate_config: BlockRateConfig, + /// Block rate (blocks per second). block_rate_bps: f64, + /// Adaptive k bounds for this configuration. + k_bounds: AdaptiveKBounds, } impl DagKnightManager { - /// Creates a new DAGKnight manager. + /// Creates a new DAGKnight manager with standard 10 BPS configuration. pub fn new( dag: Arc, reachability: Arc, + ) -> Self { + Self::with_config(dag, reachability, BlockRateConfig::Standard) + } + + /// Creates a DAGKnight manager with the specified block rate configuration. + /// + /// # Block Rate Configurations + /// + /// - `Standard` (10 BPS): Default configuration, suitable for most networks + /// - `Enhanced` (32 BPS): High-throughput mode, requires ~50ms P95 latency + /// - `Maximum` (100 BPS): Ultra-high throughput, requires data center conditions + pub fn with_config( + dag: Arc, + reachability: Arc, + config: BlockRateConfig, ) -> Self { let ghostdag = Arc::new(GhostdagManager::new(dag.clone(), reachability.clone())); let latency_tracker = Arc::new(LatencyTracker::new()); + let k_bounds = AdaptiveKBounds::for_block_rate(config); Self { ghostdag, dag, reachability, latency_tracker, - adaptive_k: RwLock::new(DEFAULT_K), - block_rate_bps: BLOCK_RATE_BPS, + adaptive_k: RwLock::new(k_bounds.default_k), + block_rate_config: config, + block_rate_bps: config.bps(), + k_bounds, } } - /// Creates a DAGKnight manager with custom block rate. + /// Creates a DAGKnight manager with custom block rate (for testing/advanced use). + /// + /// Prefer `with_config()` for standard configurations. This method allows + /// custom BPS values but uses Standard k bounds - scale manually if needed. pub fn with_block_rate( dag: Arc, reachability: Arc, @@ -140,13 +202,25 @@ impl DagKnightManager { let ghostdag = Arc::new(GhostdagManager::new(dag.clone(), reachability.clone())); let latency_tracker = Arc::new(LatencyTracker::new()); + // Determine closest config for k bounds + let config = if block_rate_bps <= 15.0 { + BlockRateConfig::Standard + } else if block_rate_bps <= 50.0 { + BlockRateConfig::Enhanced + } else { + BlockRateConfig::Maximum + }; + let k_bounds = AdaptiveKBounds::for_block_rate(config); + Self { ghostdag, dag, reachability, latency_tracker, - adaptive_k: RwLock::new(DEFAULT_K), + adaptive_k: RwLock::new(k_bounds.default_k), + block_rate_config: config, block_rate_bps, + k_bounds, } } @@ -156,13 +230,26 @@ impl DagKnightManager { dag: Arc, reachability: Arc, ) -> Self { + Self::from_ghostdag_with_config(ghostdag, dag, reachability, BlockRateConfig::Standard) + } + + /// Creates a DAGKnight manager wrapping an existing GHOSTDAG manager with config. + pub fn from_ghostdag_with_config( + ghostdag: Arc, + dag: Arc, + reachability: Arc, + config: BlockRateConfig, + ) -> Self { + let k_bounds = AdaptiveKBounds::for_block_rate(config); Self { ghostdag, dag, reachability, latency_tracker: Arc::new(LatencyTracker::new()), - adaptive_k: RwLock::new(DEFAULT_K), - block_rate_bps: BLOCK_RATE_BPS, + adaptive_k: RwLock::new(k_bounds.default_k), + block_rate_config: config, + block_rate_bps: config.bps(), + k_bounds, } } @@ -226,7 +313,8 @@ impl DagKnightManager { /// k = ceil(block_rate * network_delay * safety_margin) /// /// This ensures that even with network delays, honest miners - /// can create blocks that fit within the k-cluster. + /// can create blocks that fit within the k-cluster. Higher block + /// rates (32/100 BPS) use scaled k bounds to maintain security. fn update_adaptive_k(&self) { let stats = self.latency_tracker.get_stats(); @@ -237,10 +325,10 @@ impl DagKnightManager { // Calculate k based on P95 delay (conservative) let delay_secs = stats.p95_delay_ms / 1000.0; - let calculated_k = (self.block_rate_bps * delay_secs * SAFETY_MARGIN).ceil() as u8; + let calculated_k = (self.block_rate_bps * delay_secs * SAFETY_MARGIN).ceil() as u16; - // Clamp to valid range - let new_k = calculated_k.clamp(MIN_ADAPTIVE_K, MAX_ADAPTIVE_K); + // Clamp to valid range for this block rate configuration + let new_k = (calculated_k as u8).clamp(self.k_bounds.min_k, self.k_bounds.max_k); // Update if significantly different (avoid jitter) let current_k = *self.adaptive_k.read(); @@ -302,8 +390,8 @@ impl DagKnightManager { let time_per_block_ms = 1000.0 / self.block_rate_bps; let estimated_time = Duration::from_millis((blocks_needed as f64 * time_per_block_ms) as u64); - // Block is final if depth exceeds finality threshold - let is_final = depth >= crate::FINALITY_DEPTH; + // Block is final if depth exceeds finality threshold for this block rate + let is_final = depth >= self.finality_depth(); Ok(ConfirmationStatus { block_id: *block_id, @@ -377,7 +465,37 @@ impl DagKnightManager { /// Resets the latency tracker (e.g., after network reconfiguration). pub fn reset_latency_tracking(&self) { self.latency_tracker.reset(); - *self.adaptive_k.write() = DEFAULT_K; + *self.adaptive_k.write() = self.k_bounds.default_k; + } + + /// Gets the current block rate configuration. + pub fn block_rate_config(&self) -> BlockRateConfig { + self.block_rate_config + } + + /// Gets the adaptive k bounds for this configuration. + pub fn k_bounds(&self) -> AdaptiveKBounds { + self.k_bounds + } + + /// Gets the block rate in blocks per second. + pub fn block_rate_bps(&self) -> f64 { + self.block_rate_bps + } + + /// Gets the finality depth for this configuration. + pub fn finality_depth(&self) -> u64 { + self.block_rate_config.finality_depth() + } + + /// Gets the merge depth for this configuration. + pub fn merge_depth(&self) -> u64 { + self.block_rate_config.merge_depth() + } + + /// Gets the pruning depth for this configuration. + pub fn pruning_depth(&self) -> u64 { + self.block_rate_config.pruning_depth() } } @@ -385,8 +503,10 @@ impl std::fmt::Debug for DagKnightManager { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let stats = self.latency_tracker.get_stats(); f.debug_struct("DagKnightManager") - .field("adaptive_k", &*self.adaptive_k.read()) + .field("block_rate_config", &self.block_rate_config) .field("block_rate_bps", &self.block_rate_bps) + .field("adaptive_k", &*self.adaptive_k.read()) + .field("k_bounds", &format!("{}-{}", self.k_bounds.min_k, self.k_bounds.max_k)) .field("mean_delay_ms", &stats.mean_delay_ms) .field("sample_count", &stats.sample_count) .finish() @@ -395,11 +515,33 @@ impl std::fmt::Debug for DagKnightManager { /// Calculates the optimal k for a given network delay and block rate. /// -/// This is a utility function for network analysis. +/// This is a utility function for network analysis. The k bounds are +/// automatically scaled based on the block rate configuration. pub fn calculate_optimal_k(network_delay_ms: f64, block_rate_bps: f64) -> u8 { + // Determine the appropriate k bounds for this block rate + let config = if block_rate_bps <= 15.0 { + BlockRateConfig::Standard + } else if block_rate_bps <= 50.0 { + BlockRateConfig::Enhanced + } else { + BlockRateConfig::Maximum + }; + let bounds = AdaptiveKBounds::for_block_rate(config); + let delay_secs = network_delay_ms / 1000.0; - let k = (block_rate_bps * delay_secs * SAFETY_MARGIN).ceil() as u8; - k.clamp(MIN_ADAPTIVE_K, MAX_ADAPTIVE_K) + let k = (block_rate_bps * delay_secs * SAFETY_MARGIN).ceil() as u16; + (k as u8).clamp(bounds.min_k, bounds.max_k) +} + +/// Calculates the optimal k for a specific block rate configuration. +pub fn calculate_optimal_k_for_config( + network_delay_ms: f64, + config: BlockRateConfig, +) -> u8 { + let bounds = AdaptiveKBounds::for_block_rate(config); + let delay_secs = network_delay_ms / 1000.0; + let k = (config.bps() * delay_secs * SAFETY_MARGIN).ceil() as u16; + (k as u8).clamp(bounds.min_k, bounds.max_k) } /// Estimates throughput (TPS) for given network conditions. @@ -436,22 +578,68 @@ mod tests { (dag, reachability, dagknight) } + fn setup_test_dag_with_config(config: BlockRateConfig) -> (Arc, Arc, DagKnightManager) { + let genesis = make_block_id(0); + let dag = Arc::new(BlockDag::new(genesis, 0)); + let reachability = Arc::new(ReachabilityStore::new(genesis)); + let dagknight = DagKnightManager::with_config(dag.clone(), reachability.clone(), config); + (dag, reachability, dagknight) + } + #[test] - fn test_initial_k() { + fn test_initial_k_standard() { let (_, _, dagknight) = setup_test_dag(); - assert_eq!(dagknight.adaptive_k(), DEFAULT_K); + let bounds = AdaptiveKBounds::for_block_rate(BlockRateConfig::Standard); + assert_eq!(dagknight.adaptive_k(), bounds.default_k); + assert_eq!(dagknight.block_rate_bps(), 10.0); + } + + #[test] + fn test_initial_k_enhanced() { + let (_, _, dagknight) = setup_test_dag_with_config(BlockRateConfig::Enhanced); + let bounds = AdaptiveKBounds::for_block_rate(BlockRateConfig::Enhanced); + assert_eq!(dagknight.adaptive_k(), bounds.default_k); + assert_eq!(dagknight.block_rate_bps(), 32.0); + assert_eq!(dagknight.k_bounds().min_k, 16); + assert_eq!(dagknight.k_bounds().max_k, 128); + } + + #[test] + fn test_initial_k_maximum() { + let (_, _, dagknight) = setup_test_dag_with_config(BlockRateConfig::Maximum); + let bounds = AdaptiveKBounds::for_block_rate(BlockRateConfig::Maximum); + assert_eq!(dagknight.adaptive_k(), bounds.default_k); + assert_eq!(dagknight.block_rate_bps(), 100.0); + assert_eq!(dagknight.k_bounds().min_k, 50); + assert_eq!(dagknight.k_bounds().max_k, 255); + } + + #[test] + fn test_adaptive_k_bounds_scaling() { + let standard = AdaptiveKBounds::for_block_rate(BlockRateConfig::Standard); + let enhanced = AdaptiveKBounds::for_block_rate(BlockRateConfig::Enhanced); + let maximum = AdaptiveKBounds::for_block_rate(BlockRateConfig::Maximum); + + // Higher block rates should have higher k bounds + assert!(enhanced.min_k > standard.min_k); + assert!(enhanced.max_k > standard.max_k); + assert!(maximum.min_k > enhanced.min_k); + assert!(maximum.max_k > enhanced.max_k); } #[test] fn test_calculate_optimal_k() { - // 100ms delay at 10 BPS: k = ceil(10 * 0.1 * 1.5) = 2, clamped to MIN_ADAPTIVE_K (8) + // Standard mode (10 BPS) + let bounds = AdaptiveKBounds::for_block_rate(BlockRateConfig::Standard); + + // 100ms delay at 10 BPS: k = ceil(10 * 0.1 * 1.5) = 2, clamped to min_k (8) let k_low = calculate_optimal_k(100.0, 10.0); - assert!(k_low >= MIN_ADAPTIVE_K); - assert!(k_low <= MAX_ADAPTIVE_K); + assert!(k_low >= bounds.min_k); + assert!(k_low <= bounds.max_k); // 1000ms delay at 10 BPS: k = ceil(10 * 1.0 * 1.5) = 15, above MIN let k_medium = calculate_optimal_k(1000.0, 10.0); - assert!(k_medium >= MIN_ADAPTIVE_K); + assert!(k_medium >= bounds.min_k); // 3000ms delay at 10 BPS: k = ceil(10 * 3.0 * 1.5) = 45 let k_high = calculate_optimal_k(3000.0, 10.0); @@ -459,6 +647,21 @@ mod tests { assert!(k_high > k_low); } + #[test] + fn test_calculate_optimal_k_for_config() { + // Test Enhanced mode (32 BPS) requires higher k for same delay + let k_standard = calculate_optimal_k_for_config(100.0, BlockRateConfig::Standard); + let k_enhanced = calculate_optimal_k_for_config(100.0, BlockRateConfig::Enhanced); + let k_maximum = calculate_optimal_k_for_config(100.0, BlockRateConfig::Maximum); + + // Higher block rates calculate higher k for same delay + // Standard: ceil(10 * 0.1 * 1.5) = 2 -> clamped to 8 + // Enhanced: ceil(32 * 0.1 * 1.5) = 5 -> clamped to 16 + // Maximum: ceil(100 * 0.1 * 1.5) = 15 -> clamped to 50 + assert!(k_enhanced >= k_standard); + assert!(k_maximum >= k_enhanced); + } + #[test] fn test_estimate_throughput() { // Good network: 10ms delay - orphan_rate = 0.01 * 10 = 0.1 @@ -471,6 +674,35 @@ mod tests { assert!(tps_good > tps_poor, "tps_good={} should be > tps_poor={}", tps_good, tps_poor); } + #[test] + fn test_throughput_by_config() { + // At same network conditions, higher BPS = higher theoretical TPS + let tps_10 = estimate_throughput(10.0, 100, 20.0); // 10 BPS + let tps_32 = estimate_throughput(32.0, 100, 20.0); // 32 BPS + let tps_100 = estimate_throughput(100.0, 100, 20.0); // 100 BPS + + // Higher block rates give higher TPS (with network overhead) + assert!(tps_32 > tps_10); + assert!(tps_100 > tps_32); + } + + #[test] + fn test_finality_depth_scaling() { + let (_, _, standard) = setup_test_dag_with_config(BlockRateConfig::Standard); + let (_, _, enhanced) = setup_test_dag_with_config(BlockRateConfig::Enhanced); + let (_, _, maximum) = setup_test_dag_with_config(BlockRateConfig::Maximum); + + // All configs should have ~2.4 hours of finality time + let standard_time_hrs = standard.finality_depth() as f64 / 10.0 / 3600.0; + let enhanced_time_hrs = enhanced.finality_depth() as f64 / 32.0 / 3600.0; + let maximum_time_hrs = maximum.finality_depth() as f64 / 100.0 / 3600.0; + + // Should all be approximately 2.4 hours (allow some variance) + assert!((standard_time_hrs - 2.4).abs() < 0.1, "standard: {}", standard_time_hrs); + assert!((enhanced_time_hrs - 2.4).abs() < 0.1, "enhanced: {}", enhanced_time_hrs); + assert!((maximum_time_hrs - 2.4).abs() < 0.1, "maximum: {}", maximum_time_hrs); + } + #[test] fn test_confidence_levels() { assert!(ConfirmationConfidence::VeryHigh.sigma_multiplier() @@ -480,4 +712,15 @@ mod tests { assert!(ConfirmationConfidence::Medium.sigma_multiplier() > ConfirmationConfidence::Low.sigma_multiplier()); } + + #[test] + fn test_block_rate_config_values() { + assert_eq!(BlockRateConfig::Standard.bps(), 10.0); + assert_eq!(BlockRateConfig::Enhanced.bps(), 32.0); + assert_eq!(BlockRateConfig::Maximum.bps(), 100.0); + + assert_eq!(BlockRateConfig::Standard.block_time_ms(), 100); + assert_eq!(BlockRateConfig::Enhanced.block_time_ms(), 31); + assert_eq!(BlockRateConfig::Maximum.block_time_ms(), 10); + } } diff --git a/crates/synor-dag/src/lib.rs b/crates/synor-dag/src/lib.rs index bd9c680..816cda5 100644 --- a/crates/synor-dag/src/lib.rs +++ b/crates/synor-dag/src/lib.rs @@ -32,8 +32,8 @@ pub mod reachability; pub use dag::{BlockDag, BlockRelations, DagError}; pub use dagknight::{ - calculate_optimal_k, estimate_throughput, ConfirmationConfidence, ConfirmationStatus, - DagKnightManager, + calculate_optimal_k, calculate_optimal_k_for_config, estimate_throughput, + AdaptiveKBounds, ConfirmationConfidence, ConfirmationStatus, DagKnightManager, }; pub use ghostdag::{GhostdagData, GhostdagError, GhostdagManager}; pub use latency::{LatencySample, LatencyStats, LatencyTracker};