Skip to content

Commit 9946027

Browse files
authored
Merge pull request StellarCommons#207 from abdulrcrtw/feat/simulation-network-load
feat(devkit/simulation): NetworkLoadConfig, throughput simulator, capacity pressure, fee curve JSON
2 parents fe95bd6 + 27fd3d4 commit 9946027

11 files changed

Lines changed: 169 additions & 2 deletions

File tree

packages/core/src/alerts/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl AlertManager {
6464
peak_fee: spike.peak_fee,
6565
baseline_fee: spike.baseline_fee,
6666
spike_ratio: spike.spike_ratio,
67-
start_time: spike.start_time.clone(),
67+
start_time: spike.start_time,
6868
duration_seconds: spike.duration.num_seconds().max(0),
6969
network: self.network.clone(),
7070
timestamp: Utc::now(),

packages/core/src/api/fees.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,14 @@ mod tests {
513513
p10: "100".into(),
514514
p20: "100".into(),
515515
p30: "120".into(),
516+
p40: "130".into(),
517+
p50: "150".into(),
518+
p60: "200".into(),
519+
p70: "250".into(),
520+
p80: "300".into(),
521+
p90: "500".into(),
522+
p95: "800".into(),
523+
p99: "1000".into(),
516524
p40: "140".into(),
517525
p50: "150".into(),
518526
p60: "200".into(),
@@ -537,6 +545,17 @@ mod tests {
537545
p10: "100".into(),
538546
p20: "100".into(),
539547
p30: "120".into(),
548+
p40: "130".into(),
549+
p50: "150".into(),
550+
p60: "200".into(),
551+
p70: "250".into(),
552+
p80: "300".into(),
553+
p90: "500".into(),
554+
p95: "800".into(),
555+
p99: "1000".into(),
556+
};
557+
let json = serde_json::to_value(&p).unwrap();
558+
for field in &["p10", "p20", "p50", "p80", "p90", "p95"] {
540559
p40: "140".into(),
541560
p50: "150".into(),
542561
p60: "200".into(),

packages/core/src/insights/calculator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ impl<T> CircularBuffer<T> {
4242

4343
/// Calculator for rolling averages across multiple time windows
4444
pub struct RollingAverageCalculator {
45+
#[allow(dead_code)]
4546
config: AverageConfig,
4647
windows: HashMap<TimeWindow, CircularBuffer<FeeDataPoint>>,
4748
time_windows: Vec<TimeWindow>,

packages/core/src/insights/detector.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ impl CongestionDetector {
206206

207207
// Sort fees by timestamp to process in chronological order
208208
let mut sorted_fees = fees.to_vec();
209-
sorted_fees.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
209+
sorted_fees.sort_by_key(|a| a.timestamp);
210210

211211
let mut current_spike: Option<FeeSpike> = None;
212212

packages/core/src/insights/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ mod tests {
3939
)
4040
}
4141

42+
#[allow(dead_code)]
4243
fn time_window_strategy() -> impl Strategy<Value = TimeWindow> {
4344
(
4445
prop::collection::vec("[a-z]+", 1..10).prop_map(|words| words.join("_")),

packages/core/src/repository.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,14 @@ mod tests {
496496
p10: "100".into(),
497497
p20: "100".into(),
498498
p30: "120".into(),
499+
p40: "130".into(),
500+
p50: "150".into(),
501+
p60: "200".into(),
502+
p70: "250".into(),
503+
p80: "300".into(),
504+
p90: "500".into(),
505+
p95: "800".into(),
506+
p99: "1000".into(),
499507
p40: "140".into(),
500508
p50: "150".into(),
501509
p60: "200".into(),

packages/core/src/scheduler.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub async fn run_fee_polling_with_retry(
9898
}
9999

100100
/// Execute a single poll cycle with retry and optional persistence.
101+
#[allow(clippy::too_many_arguments)]
101102
async fn poll_once(
102103
horizon_provider: &Arc<dyn FeeDataProvider + Send + Sync>,
103104
history_store: &Arc<RwLock<FeeHistoryStore>>,

packages/core/src/services/horizon.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,14 @@ mod tests {
264264
"p10": "100",
265265
"p20": "100",
266266
"p30": "120",
267+
"p40": "130",
268+
"p50": "150",
269+
"p60": "200",
270+
"p70": "250",
271+
"p80": "300",
272+
"p90": "500",
273+
"p95": "800",
274+
"p99": "1000"
267275
"p40": "140",
268276
"p50": "150",
269277
"p60": "200",
@@ -278,6 +286,8 @@ mod tests {
278286
assert_eq!(fc.max, "5000");
279287
assert_eq!(fc.avg, "213");
280288
assert_eq!(fc.p10, "100");
289+
assert_eq!(fc.p50, "150");
290+
assert_eq!(fc.p80, "300");
281291
assert_eq!(fc.p20, "100");
282292
assert_eq!(fc.p30, "120");
283293
assert_eq!(fc.p40, "140");
@@ -301,6 +311,14 @@ mod tests {
301311
"p10": "100",
302312
"p20": "100",
303313
"p30": "120",
314+
"p40": "130",
315+
"p50": "150",
316+
"p60": "200",
317+
"p70": "250",
318+
"p80": "300",
319+
"p90": "500",
320+
"p95": "800",
321+
"p99": "1000"
304322
"p40": "140",
305323
"p50": "150",
306324
"p60": "200",

packages/devkit/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ description = "Developer toolkit for testing and simulating the Stellar fee trac
66

77
[dependencies]
88
axum = "0.7"
9+
serde = { version = "1", features = ["derive"] }
910
rand = { version = "0.8", features = ["small_rng"] }
1011
serde_json = "1"
1112
thiserror = "1"

packages/devkit/src/simulation/fee_model.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,87 @@ impl FeeModel {
9797
/// Models for simulating Stellar transaction fee behaviour.
9898
pub struct FeeModel;
9999

100+
/// A fee curve snapshot matching the Horizon `fee_stats` shape (#119).
101+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
102+
pub struct FeeCurve {
103+
pub last_ledger: String,
104+
pub last_ledger_base_fee: String,
105+
pub ledger_capacity_usage: String,
106+
pub fee_charged: FeePercentiles,
107+
pub max_fee: FeePercentiles,
108+
}
109+
110+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
111+
pub struct FeePercentiles {
112+
pub max: String,
113+
pub min: String,
114+
pub mode: String,
115+
pub p10: String,
116+
pub p20: String,
117+
pub p30: String,
118+
pub p40: String,
119+
pub p50: String,
120+
pub p60: String,
121+
pub p70: String,
122+
pub p80: String,
123+
pub p90: String,
124+
pub p95: String,
125+
pub p99: String,
126+
}
127+
128+
impl FeeModel {
129+
/// Generate a `FeeCurve` scaled by `pressure` (0.0–1.0) (#122).
130+
///
131+
/// At pressure 0 fees stay at `base_fee`; at pressure 1 they reach `max_fee`.
132+
pub fn generate(base_fee: u64, max_fee: u64, pressure: f64, ledger_seq: u64) -> FeeCurve {
133+
let pressure = pressure.clamp(0.0, 1.0);
134+
let fee = |pct: f64| -> String {
135+
let scaled = base_fee as f64 + (max_fee - base_fee) as f64 * pressure * pct;
136+
(scaled as u64).to_string()
137+
};
138+
139+
FeeCurve {
140+
last_ledger: ledger_seq.to_string(),
141+
last_ledger_base_fee: base_fee.to_string(),
142+
ledger_capacity_usage: format!("{:.2}", pressure),
143+
fee_charged: FeePercentiles {
144+
min: base_fee.to_string(),
145+
max: fee(1.0),
146+
mode: fee(0.6),
147+
p10: fee(0.1),
148+
p20: fee(0.2),
149+
p30: fee(0.3),
150+
p40: fee(0.4),
151+
p50: fee(0.5),
152+
p60: fee(0.6),
153+
p70: fee(0.7),
154+
p80: fee(0.8),
155+
p90: fee(0.9),
156+
p95: fee(0.95),
157+
p99: fee(0.99),
158+
},
159+
max_fee: FeePercentiles {
160+
min: base_fee.to_string(),
161+
max: fee(1.0),
162+
mode: fee(0.7),
163+
p10: fee(0.15),
164+
p20: fee(0.25),
165+
p30: fee(0.35),
166+
p40: fee(0.45),
167+
p50: fee(0.55),
168+
p60: fee(0.65),
169+
p70: fee(0.75),
170+
p80: fee(0.85),
171+
p90: fee(0.92),
172+
p95: fee(0.97),
173+
p99: fee(1.0),
174+
},
175+
}
176+
}
177+
178+
/// Serialise a `FeeCurve` to a JSON string (#119).
179+
pub fn to_json(curve: &FeeCurve) -> Result<String, serde_json::Error> {
180+
serde_json::to_string(curve)
100181
impl FeeModel {
101182
/// Generate fee points for a single config.
102183
pub fn generate(config: &FeeModelConfig) -> Vec<FeePoint> {

0 commit comments

Comments
 (0)