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
This commit is contained in:
parent
e2ce0022e5
commit
4983193f63
4 changed files with 722 additions and 41 deletions
|
|
@ -105,12 +105,14 @@
|
||||||
|
|
||||||
pub mod difficulty;
|
pub mod difficulty;
|
||||||
pub mod genesis;
|
pub mod genesis;
|
||||||
|
pub mod network;
|
||||||
pub mod rewards;
|
pub mod rewards;
|
||||||
pub mod utxo;
|
pub mod utxo;
|
||||||
pub mod validation;
|
pub mod validation;
|
||||||
|
|
||||||
pub use difficulty::{DaaParams, DifficultyManager};
|
pub use difficulty::{DaaParams, DifficultyManager};
|
||||||
pub use genesis::{ChainConfig, Checkpoint, GenesisAllocation, GenesisError};
|
pub use genesis::{ChainConfig, Checkpoint, GenesisAllocation, GenesisError};
|
||||||
|
pub use network::{BpsMode, NetworkConfig};
|
||||||
pub use rewards::{BlockReward, RewardCalculator};
|
pub use rewards::{BlockReward, RewardCalculator};
|
||||||
pub use utxo::{UtxoDiff, UtxoEntry, UtxoError, UtxoSet};
|
pub use utxo::{UtxoDiff, UtxoEntry, UtxoError, UtxoSet};
|
||||||
pub use validation::{BlockValidator, TransactionValidator, ValidationError};
|
pub use validation::{BlockValidator, TransactionValidator, ValidationError};
|
||||||
|
|
|
||||||
436
crates/synor-consensus/src/network.rs
Normal file
436
crates/synor-consensus/src/network.rs
Normal file
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,16 @@
|
||||||
//! 3. **No Fixed Delay Assumption**: Learns actual network behavior
|
//! 3. **No Fixed Delay Assumption**: Learns actual network behavior
|
||||||
//! 4. **Faster Confirmation**: Converges faster under good network conditions
|
//! 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
|
//! # Algorithm Overview
|
||||||
//!
|
//!
|
||||||
//! DAGKnight maintains the core GHOSTDAG blue set selection but adds:
|
//! DAGKnight maintains the core GHOSTDAG blue set selection but adds:
|
||||||
|
|
@ -33,29 +43,56 @@ use crate::{
|
||||||
ghostdag::{GhostdagData, GhostdagError, GhostdagManager},
|
ghostdag::{GhostdagData, GhostdagError, GhostdagManager},
|
||||||
latency::{LatencyStats, LatencyTracker},
|
latency::{LatencyStats, LatencyTracker},
|
||||||
reachability::ReachabilityStore,
|
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.
|
/// Number of samples required before adapting k.
|
||||||
const MIN_SAMPLES_FOR_ADAPTATION: usize = 100;
|
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.
|
/// Safety margin multiplier for k calculation.
|
||||||
/// Higher values = more conservative (safer but lower throughput).
|
/// Higher values = more conservative (safer but lower throughput).
|
||||||
const SAFETY_MARGIN: f64 = 1.5;
|
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.
|
/// Confirmation confidence levels.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ConfirmationConfidence {
|
pub enum ConfirmationConfidence {
|
||||||
|
|
@ -108,30 +145,55 @@ pub struct DagKnightManager {
|
||||||
latency_tracker: Arc<LatencyTracker>,
|
latency_tracker: Arc<LatencyTracker>,
|
||||||
/// Current adaptive k value.
|
/// Current adaptive k value.
|
||||||
adaptive_k: RwLock<u8>,
|
adaptive_k: RwLock<u8>,
|
||||||
/// Block rate setting.
|
/// Block rate configuration.
|
||||||
|
block_rate_config: BlockRateConfig,
|
||||||
|
/// Block rate (blocks per second).
|
||||||
block_rate_bps: f64,
|
block_rate_bps: f64,
|
||||||
|
/// Adaptive k bounds for this configuration.
|
||||||
|
k_bounds: AdaptiveKBounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DagKnightManager {
|
impl DagKnightManager {
|
||||||
/// Creates a new DAGKnight manager.
|
/// Creates a new DAGKnight manager with standard 10 BPS configuration.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
dag: Arc<BlockDag>,
|
dag: Arc<BlockDag>,
|
||||||
reachability: Arc<ReachabilityStore>,
|
reachability: Arc<ReachabilityStore>,
|
||||||
|
) -> 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<BlockDag>,
|
||||||
|
reachability: Arc<ReachabilityStore>,
|
||||||
|
config: BlockRateConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ghostdag = Arc::new(GhostdagManager::new(dag.clone(), reachability.clone()));
|
let ghostdag = Arc::new(GhostdagManager::new(dag.clone(), reachability.clone()));
|
||||||
let latency_tracker = Arc::new(LatencyTracker::new());
|
let latency_tracker = Arc::new(LatencyTracker::new());
|
||||||
|
let k_bounds = AdaptiveKBounds::for_block_rate(config);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ghostdag,
|
ghostdag,
|
||||||
dag,
|
dag,
|
||||||
reachability,
|
reachability,
|
||||||
latency_tracker,
|
latency_tracker,
|
||||||
adaptive_k: RwLock::new(DEFAULT_K),
|
adaptive_k: RwLock::new(k_bounds.default_k),
|
||||||
block_rate_bps: BLOCK_RATE_BPS,
|
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(
|
pub fn with_block_rate(
|
||||||
dag: Arc<BlockDag>,
|
dag: Arc<BlockDag>,
|
||||||
reachability: Arc<ReachabilityStore>,
|
reachability: Arc<ReachabilityStore>,
|
||||||
|
|
@ -140,13 +202,25 @@ impl DagKnightManager {
|
||||||
let ghostdag = Arc::new(GhostdagManager::new(dag.clone(), reachability.clone()));
|
let ghostdag = Arc::new(GhostdagManager::new(dag.clone(), reachability.clone()));
|
||||||
let latency_tracker = Arc::new(LatencyTracker::new());
|
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 {
|
Self {
|
||||||
ghostdag,
|
ghostdag,
|
||||||
dag,
|
dag,
|
||||||
reachability,
|
reachability,
|
||||||
latency_tracker,
|
latency_tracker,
|
||||||
adaptive_k: RwLock::new(DEFAULT_K),
|
adaptive_k: RwLock::new(k_bounds.default_k),
|
||||||
|
block_rate_config: config,
|
||||||
block_rate_bps,
|
block_rate_bps,
|
||||||
|
k_bounds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,13 +230,26 @@ impl DagKnightManager {
|
||||||
dag: Arc<BlockDag>,
|
dag: Arc<BlockDag>,
|
||||||
reachability: Arc<ReachabilityStore>,
|
reachability: Arc<ReachabilityStore>,
|
||||||
) -> Self {
|
) -> 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<GhostdagManager>,
|
||||||
|
dag: Arc<BlockDag>,
|
||||||
|
reachability: Arc<ReachabilityStore>,
|
||||||
|
config: BlockRateConfig,
|
||||||
|
) -> Self {
|
||||||
|
let k_bounds = AdaptiveKBounds::for_block_rate(config);
|
||||||
Self {
|
Self {
|
||||||
ghostdag,
|
ghostdag,
|
||||||
dag,
|
dag,
|
||||||
reachability,
|
reachability,
|
||||||
latency_tracker: Arc::new(LatencyTracker::new()),
|
latency_tracker: Arc::new(LatencyTracker::new()),
|
||||||
adaptive_k: RwLock::new(DEFAULT_K),
|
adaptive_k: RwLock::new(k_bounds.default_k),
|
||||||
block_rate_bps: BLOCK_RATE_BPS,
|
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)
|
/// k = ceil(block_rate * network_delay * safety_margin)
|
||||||
///
|
///
|
||||||
/// This ensures that even with network delays, honest miners
|
/// 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) {
|
fn update_adaptive_k(&self) {
|
||||||
let stats = self.latency_tracker.get_stats();
|
let stats = self.latency_tracker.get_stats();
|
||||||
|
|
||||||
|
|
@ -237,10 +325,10 @@ impl DagKnightManager {
|
||||||
|
|
||||||
// Calculate k based on P95 delay (conservative)
|
// Calculate k based on P95 delay (conservative)
|
||||||
let delay_secs = stats.p95_delay_ms / 1000.0;
|
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
|
// Clamp to valid range for this block rate configuration
|
||||||
let new_k = calculated_k.clamp(MIN_ADAPTIVE_K, MAX_ADAPTIVE_K);
|
let new_k = (calculated_k as u8).clamp(self.k_bounds.min_k, self.k_bounds.max_k);
|
||||||
|
|
||||||
// Update if significantly different (avoid jitter)
|
// Update if significantly different (avoid jitter)
|
||||||
let current_k = *self.adaptive_k.read();
|
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 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);
|
let estimated_time = Duration::from_millis((blocks_needed as f64 * time_per_block_ms) as u64);
|
||||||
|
|
||||||
// Block is final if depth exceeds finality threshold
|
// Block is final if depth exceeds finality threshold for this block rate
|
||||||
let is_final = depth >= crate::FINALITY_DEPTH;
|
let is_final = depth >= self.finality_depth();
|
||||||
|
|
||||||
Ok(ConfirmationStatus {
|
Ok(ConfirmationStatus {
|
||||||
block_id: *block_id,
|
block_id: *block_id,
|
||||||
|
|
@ -377,7 +465,37 @@ impl DagKnightManager {
|
||||||
/// Resets the latency tracker (e.g., after network reconfiguration).
|
/// Resets the latency tracker (e.g., after network reconfiguration).
|
||||||
pub fn reset_latency_tracking(&self) {
|
pub fn reset_latency_tracking(&self) {
|
||||||
self.latency_tracker.reset();
|
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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let stats = self.latency_tracker.get_stats();
|
let stats = self.latency_tracker.get_stats();
|
||||||
f.debug_struct("DagKnightManager")
|
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("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("mean_delay_ms", &stats.mean_delay_ms)
|
||||||
.field("sample_count", &stats.sample_count)
|
.field("sample_count", &stats.sample_count)
|
||||||
.finish()
|
.finish()
|
||||||
|
|
@ -395,11 +515,33 @@ impl std::fmt::Debug for DagKnightManager {
|
||||||
|
|
||||||
/// Calculates the optimal k for a given network delay and block rate.
|
/// 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 {
|
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 delay_secs = network_delay_ms / 1000.0;
|
||||||
let k = (block_rate_bps * delay_secs * SAFETY_MARGIN).ceil() as u8;
|
let k = (block_rate_bps * delay_secs * SAFETY_MARGIN).ceil() as u16;
|
||||||
k.clamp(MIN_ADAPTIVE_K, MAX_ADAPTIVE_K)
|
(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.
|
/// Estimates throughput (TPS) for given network conditions.
|
||||||
|
|
@ -436,22 +578,68 @@ mod tests {
|
||||||
(dag, reachability, dagknight)
|
(dag, reachability, dagknight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_test_dag_with_config(config: BlockRateConfig) -> (Arc<BlockDag>, Arc<ReachabilityStore>, 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]
|
#[test]
|
||||||
fn test_initial_k() {
|
fn test_initial_k_standard() {
|
||||||
let (_, _, dagknight) = setup_test_dag();
|
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]
|
#[test]
|
||||||
fn test_calculate_optimal_k() {
|
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);
|
let k_low = calculate_optimal_k(100.0, 10.0);
|
||||||
assert!(k_low >= MIN_ADAPTIVE_K);
|
assert!(k_low >= bounds.min_k);
|
||||||
assert!(k_low <= MAX_ADAPTIVE_K);
|
assert!(k_low <= bounds.max_k);
|
||||||
|
|
||||||
// 1000ms delay at 10 BPS: k = ceil(10 * 1.0 * 1.5) = 15, above MIN
|
// 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);
|
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
|
// 3000ms delay at 10 BPS: k = ceil(10 * 3.0 * 1.5) = 45
|
||||||
let k_high = calculate_optimal_k(3000.0, 10.0);
|
let k_high = calculate_optimal_k(3000.0, 10.0);
|
||||||
|
|
@ -459,6 +647,21 @@ mod tests {
|
||||||
assert!(k_high > k_low);
|
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]
|
#[test]
|
||||||
fn test_estimate_throughput() {
|
fn test_estimate_throughput() {
|
||||||
// Good network: 10ms delay - orphan_rate = 0.01 * 10 = 0.1
|
// 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);
|
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]
|
#[test]
|
||||||
fn test_confidence_levels() {
|
fn test_confidence_levels() {
|
||||||
assert!(ConfirmationConfidence::VeryHigh.sigma_multiplier()
|
assert!(ConfirmationConfidence::VeryHigh.sigma_multiplier()
|
||||||
|
|
@ -480,4 +712,15 @@ mod tests {
|
||||||
assert!(ConfirmationConfidence::Medium.sigma_multiplier()
|
assert!(ConfirmationConfidence::Medium.sigma_multiplier()
|
||||||
> ConfirmationConfidence::Low.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ pub mod reachability;
|
||||||
|
|
||||||
pub use dag::{BlockDag, BlockRelations, DagError};
|
pub use dag::{BlockDag, BlockRelations, DagError};
|
||||||
pub use dagknight::{
|
pub use dagknight::{
|
||||||
calculate_optimal_k, estimate_throughput, ConfirmationConfidence, ConfirmationStatus,
|
calculate_optimal_k, calculate_optimal_k_for_config, estimate_throughput,
|
||||||
DagKnightManager,
|
AdaptiveKBounds, ConfirmationConfidence, ConfirmationStatus, DagKnightManager,
|
||||||
};
|
};
|
||||||
pub use ghostdag::{GhostdagData, GhostdagError, GhostdagManager};
|
pub use ghostdag::{GhostdagData, GhostdagError, GhostdagManager};
|
||||||
pub use latency::{LatencySample, LatencyStats, LatencyTracker};
|
pub use latency::{LatencySample, LatencyStats, LatencyTracker};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue