diff --git a/packages/devkit/src/simulation/network_load.rs b/packages/devkit/src/simulation/network_load.rs index 57bc07a..8cb8787 100644 --- a/packages/devkit/src/simulation/network_load.rs +++ b/packages/devkit/src/simulation/network_load.rs @@ -4,6 +4,15 @@ use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; /// Configuration for the seeded random network load generator. +/// +/// # Fields +/// +/// * `min_tx` – Minimum transactions per ledger. +/// * `max_tx` – Maximum transactions per ledger. +/// * `ledger_capacity` – Maximum transactions the network can process per ledger (capacity limit). +/// * `ledger_interval_ms` – Time between ledger closes in milliseconds. +/// * `duration_secs` – Total simulation duration in seconds. +/// * `seed` – Optional RNG seed for reproducibility. pub struct NetworkLoadConfig { /// Minimum transactions per ledger. pub min_tx: u64, @@ -13,6 +22,8 @@ pub struct NetworkLoadConfig { pub ledger_capacity: u64, /// Time between ledger closes in milliseconds. pub ledger_interval_ms: u64, + /// Total simulation duration in seconds. + pub duration_secs: u64, /// Optional RNG seed for reproducibility. pub seed: Option, } @@ -24,6 +35,7 @@ impl Default for NetworkLoadConfig { max_tx: 1000, ledger_capacity: 1000, ledger_interval_ms: 5000, + duration_secs: 3600, seed: None, } } @@ -35,6 +47,11 @@ impl NetworkLoadConfig { let mid = (self.min_tx + self.max_tx) as f64 / 2.0; (mid / self.ledger_capacity as f64).clamp(0.0, 1.0) } + + /// Number of ledger events in `duration_secs` based on `ledger_interval_ms`. + pub fn ledger_count(&self) -> usize { + (self.duration_secs * 1000 / self.ledger_interval_ms) as usize + } } /// A single simulated ledger produced by the throughput simulator. @@ -69,6 +86,8 @@ impl NetworkLoad { } /// Simulate `count` ledger closes, returning full `SimulatedLedger` records. + /// + /// Resolves #243. pub fn simulate(&mut self, count: usize) -> Vec { self.generate(count) .into_iter() @@ -85,9 +104,18 @@ impl NetworkLoad { .collect() } + /// Simulate for the full `duration_secs` configured in `NetworkLoadConfig`. + /// + /// Returns one `SimulatedLedger` per ledger close interval. + /// Resolves #243. + pub fn run(&mut self) -> Vec { + self.simulate(self.config.ledger_count()) + } + /// Returns a fee multiplier (1.0–3.0) based on hour of day (0–23). /// /// Models diurnal congestion: peak around hour 14 (2pm UTC), trough around hour 2 (2am UTC). + /// Resolves #245. pub fn diurnal_multiplier(hour: u8) -> f64 { // Simple sinusoidal: peak at hour 14, trough at hour 2 let angle = std::f64::consts::PI * (hour as f64 - 2.0) / 12.0; @@ -95,7 +123,39 @@ impl NetworkLoad { } /// Apply diurnal multiplier to a base fee given the hour of day. + /// + /// Resolves #245. pub fn diurnal_fee(base_fee: u64, hour: u8) -> u64 { (base_fee as f64 * Self::diurnal_multiplier(hour)).round() as u64 } + + /// Scale a tx count by the diurnal multiplier for the given hour. + /// + /// During peak hours (08:00–20:00 UTC) the multiplier is >1.0, increasing + /// the effective transaction volume. + /// + /// Resolves #245. + pub fn diurnal_tx_count(tx_count: u64, hour: u8) -> u64 { + (tx_count as f64 * Self::diurnal_multiplier(hour)).round() as u64 + } +} + +/// Scale a fee upward as capacity pressure approaches 1.0. +/// +/// The fee increases linearly from `base_fee` at pressure 0.0 to +/// `base_fee * max_multiple` at pressure 1.0. +/// +/// # Examples +/// +/// ``` +/// use stellar_devkit::simulation::network_load::capacity_pressure_fee; +/// +/// assert_eq!(capacity_pressure_fee(100, 0.0, 10), 100); +/// assert_eq!(capacity_pressure_fee(100, 1.0, 10), 1000); +/// assert_eq!(capacity_pressure_fee(100, 0.5, 10), 550); +/// ``` +pub fn capacity_pressure_fee(base_fee: u64, pressure: f64, max_multiple: u64) -> u64 { + let pressure = pressure.clamp(0.0, 1.0); + let scaled = base_fee as f64 * (1.0 + pressure * (max_multiple.saturating_sub(1)) as f64); + (scaled.round() as u64).max(1) }