use std::time::Duration;
use zakura_chain::block;
use super::{
assert_core_invariants, fuzz_config, invariant_report, run_scenario, run_trace,
CommitBurstStall, CommitProfile, Degrade, DegradeMode, FuzzOutcome, IdleGap, InvariantReport,
LatencyDist, PeerSpec, Scenario, ServeProfile, TipEvent, TipEventKind,
};
use crate::zakura::{
ZakuraBlockSyncConfig, DESERIALIZED_MEM_FACTOR, MIN_BS_CHECKPOINT_SUBMITTED_BLOCK_APPLIES,
};
async fn run_checked(
name: &str,
scenario: Scenario,
outstanding_slack: u64,
) -> (FuzzOutcome, InvariantReport) {
let (mut capture, trace) = run_trace(name).expect("trace capture opens");
let outcome = run_scenario(&scenario, trace)
.await
.expect("scenario runs without harness error");
capture.flush().await;
let reader = capture
.reader()
.expect("trace reader loads the flushed run");
let report = invariant_report(&reader);
tracing::info!(
scenario = name,
committed = outcome.committed_tip.0,
target = outcome.target.0,
state_samples = report.state_samples,
max_outstanding = report.max_outstanding,
peak_budget_reserved = report.peak_budget_reserved,
final_budget_reserved = report.final_budget_reserved,
peak_active_pipeline_decoded_attributed_memory_bytes =
report.peak_active_pipeline_decoded_attributed_memory_bytes,
final_active_pipeline_decoded_attributed_memory_bytes =
report.final_active_pipeline_decoded_attributed_memory_bytes,
protocol_rejects = report.protocol_rejects,
floor_bypass_requests = report.floor_bypass_requests,
total_requests = report.total_requests,
max_requests_without_block_progress = report.max_requests_without_block_progress,
max_unproven_requests_without_block_progress =
report.max_unproven_requests_without_block_progress,
min_reliability_permille = report.min_reliability_permille,
"blocksync fuzz scenario complete",
);
assert_core_invariants(&scenario, &outcome, &report, outstanding_slack);
capture.finish().await.expect("capture discards cleanly");
(outcome, report)
}
fn retry_config() -> ZakuraBlockSyncConfig {
ZakuraBlockSyncConfig {
request_timeout: Duration::from_secs(2),
..fuzz_config()
}
}
fn target(blocks: u32) -> block::Height {
block::Height(blocks)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_steady() {
let blocks = 300;
let scenario = Scenario::new(
blocks,
0x57ea_0001,
fuzz_config(),
vec![
PeerSpec::fast(1, target(blocks)),
PeerSpec::fast(2, target(blocks)),
PeerSpec::fast(3, target(blocks)),
],
);
run_checked("fuzz_steady", scenario, 32).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_steady_bytes_unit() {
let blocks = 300;
let config = ZakuraBlockSyncConfig {
bbr_cwnd_unit: crate::zakura::CwndUnit::Bytes,
..fuzz_config()
};
let scenario = Scenario::new(
blocks,
0x57ea_0008,
config,
vec![
PeerSpec::fast(1, target(blocks)),
PeerSpec::fast(2, target(blocks)),
PeerSpec::fast(3, target(blocks)),
],
);
run_checked("fuzz_steady_bytes_unit", scenario, 32).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_one_slow_peer_hol() {
let blocks = 300;
let config = ZakuraBlockSyncConfig {
max_blocks_per_response: 1,
..fuzz_config()
};
let slow = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile::byte_rate(Duration::from_millis(80), 64 * 1024),
);
let carrier = |id| {
PeerSpec::with_serve(
id,
target(blocks),
ServeProfile::byte_rate(Duration::from_millis(2), 50 * 1024 * 1024),
)
};
let mut scenario = Scenario::new(
blocks,
0x57ea_0002,
config,
vec![slow, carrier(2), carrier(3)],
);
scenario.target_block_bytes = Some(32 * 1024);
scenario.deadline = Duration::from_secs(60);
let (_, report) = run_checked("fuzz_one_slow_peer_hol", scenario, 32).await;
assert_eq!(
report.protocol_rejects, 0,
"the slow-but-progressing peer must not be reaped (it serves ~every 0.5 s)",
);
}
#[ignore = "needs high-fidelity Committer<MockVerifier> for mid-sync reorg epoch/reset semantics"]
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_reorg() {
let blocks = 600;
let peer = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile::slow(Duration::from_millis(0), Duration::from_millis(2)),
);
let mut scenario = Scenario::new(
blocks,
0x57ea_0003,
retry_config(),
vec![peer, PeerSpec::fast(2, target(blocks))],
);
scenario.timeline = vec![TipEvent {
at: Duration::from_millis(300),
kind: TipEventKind::VerifiedReset(block::Height(50)),
}];
scenario.deadline = Duration::from_secs(30);
let result = tokio::spawn(async move { run_checked("fuzz_reorg", scenario, 32).await }).await;
assert!(
result.is_err_and(|error| error.is_panic()),
"mock reorg scenario should keep failing until the high-fidelity committer tier lands",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_idle_peers() {
let blocks = 300;
let withholder = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile {
withhold: Some((block::Height(150), block::Height(180))),
..ServeProfile::fast()
},
);
let scenario = Scenario::new(
blocks,
0x57ea_0004,
retry_config(),
vec![withholder, PeerSpec::fast(2, target(blocks))],
);
run_checked("fuzz_idle_peers", scenario, 32).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_silent_dropping_peer() {
let blocks = 300;
let config = ZakuraBlockSyncConfig {
max_blocks_per_response: 1,
..retry_config()
};
let flaky = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile {
drop_probability: 0.5,
..ServeProfile::fast()
},
);
let mut scenario = Scenario::new(
blocks,
0x57ea_000d,
config,
vec![flaky, PeerSpec::fast(2, target(blocks))],
);
scenario.deadline = Duration::from_secs(60);
let (_, report) = run_checked("fuzz_silent_dropping_peer", scenario, 32).await;
assert!(
report.total_requests > usize::try_from(blocks).expect("block count fits usize"),
"silent drops must force re-requests: issued {} requests for {} blocks",
report.total_requests,
blocks,
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_reliability_discounts_dropping_carrier() {
let blocks = 120;
let half = block::Height(blocks / 2);
let dropping = |id| {
PeerSpec::with_serve(
id,
target(blocks),
ServeProfile {
drop_probability: 0.3,
..ServeProfile::fast()
},
)
};
let mut fast = PeerSpec::fast(3, half);
fast.servable_high = half;
let config = ZakuraBlockSyncConfig {
request_timeout: Duration::from_secs(4),
..retry_config()
};
let mut scenario = Scenario::new(
blocks,
0x57ea_00c0,
config,
vec![dropping(1), dropping(2), fast],
);
scenario.deadline = Duration::from_secs(90);
let (_, report) =
run_checked("fuzz_reliability_discounts_dropping_carrier", scenario, 32).await;
assert!(
report.min_reliability_permille < 1000,
"a request-dropping carrier must lower its measured reliability (the goodput \
discount folded into its BBR cwnd), got {}/1000",
report.min_reliability_permille,
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_silent_peer_request_cap() {
let blocks = 96;
let probe_requests = 1;
let request_cap = 8;
let config = ZakuraBlockSyncConfig {
max_blocks_per_response: 1,
request_timeout: Duration::from_millis(100),
floor_rescue_timeout: Duration::from_millis(25),
initial_block_probe_requests: probe_requests,
max_requests_without_block_progress: request_cap,
..fuzz_config()
};
let mut silent = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile {
drop_probability: 1.0,
..ServeProfile::fast()
},
);
silent.max_inflight_requests = 64;
let mut healthy = PeerSpec::fast(2, target(blocks));
healthy.connect_at = Duration::from_millis(700);
let mut scenario = Scenario::new(blocks, 0x57ea_000e, config, vec![silent, healthy]);
scenario.target_block_bytes = Some(16 * 1024);
scenario.deadline = Duration::from_secs(10);
let (_, report) = run_checked("fuzz_silent_peer_request_cap", scenario, 32).await;
assert!(
report.protocol_rejects >= 1,
"the fully silent peer must be disconnected by no-progress liveness",
);
assert_eq!(
report.max_unproven_requests_without_block_progress,
u64::from(probe_requests),
"the silent peer should receive exactly the configured initial probe budget",
);
}
fn degrade_config() -> ZakuraBlockSyncConfig {
ZakuraBlockSyncConfig {
max_blocks_per_response: 1,
request_timeout: Duration::from_millis(100),
floor_rescue_timeout: Duration::from_millis(25),
initial_block_probe_requests: 1,
max_requests_without_block_progress: 8,
..fuzz_config()
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_peer_wedges_after_progress_is_disconnected() {
let blocks = 400;
let waverer = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile {
bandwidth_bytes_per_sec: Some(4 * 1024 * 1024),
degrade: Some(Degrade {
at: Duration::from_millis(250),
mode: DegradeMode::GoSilent,
}),
..ServeProfile::fast()
},
);
let mut healthy = PeerSpec::fast(2, target(blocks));
healthy.connect_at = Duration::from_millis(1_200);
let mut scenario = Scenario::new(
blocks,
0x57ea_00f0,
degrade_config(),
vec![waverer, healthy],
);
scenario.target_block_bytes = Some(16 * 1024);
scenario.deadline = Duration::from_secs(20);
let (_, report) = run_checked(
"fuzz_peer_wedges_after_progress_is_disconnected",
scenario,
32,
)
.await;
assert!(
report.protocol_rejects >= 1,
"a peer that wedges after making progress must be disconnected, got {} rejects",
report.protocol_rejects,
);
assert!(
report.max_requests_without_block_progress >= 2,
"the disconnected peer should have been proven (streak past the initial probe), got {}",
report.max_requests_without_block_progress,
);
assert!(
report.min_reliability_permille < 1000,
"the wedged peer's reliability must fall as its requests stop delivering, got {}/1000",
report.min_reliability_permille,
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_peer_that_stops_reading_is_disconnected() {
let blocks = 400;
let wedger = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile {
bandwidth_bytes_per_sec: Some(4 * 1024 * 1024),
degrade: Some(Degrade {
at: Duration::from_millis(250),
mode: DegradeMode::Wedge,
}),
..ServeProfile::fast()
},
);
let mut scenario = Scenario::new(blocks, 0x57ea_dead, degrade_config(), vec![wedger]);
scenario.target_block_bytes = Some(16 * 1024);
scenario.transport_queue_depth = Some(4);
scenario.deadline = Duration::from_secs(5);
let (mut capture, trace) =
run_trace("fuzz_peer_that_stops_reading_is_disconnected").expect("trace capture opens");
let _outcome = run_scenario(&scenario, trace)
.await
.expect("scenario runs without harness error");
capture.flush().await;
let reader = capture
.reader()
.expect("trace reader loads the flushed run");
let report = invariant_report(&reader);
capture.finish().await.expect("capture discards cleanly");
assert!(
report.protocol_rejects >= 1,
"a peer that stops reading our stream must still be disconnected at the liveness \
deadline, got {} rejects",
report.protocol_rejects,
);
assert!(
report.max_requests_without_block_progress >= 2,
"the disconnected peer should have been proven, got {}",
report.max_requests_without_block_progress,
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_peer_slows_radically_is_kept() {
let blocks = 120;
let mut slowing = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile {
first_block_latency: LatencyDist::Fixed(Duration::from_millis(30)),
bandwidth_bytes_per_sec: Some(64 * 1024 * 1024),
degrade: Some(Degrade {
at: Duration::from_millis(300),
mode: DegradeMode::SlowTo {
base_rtt: Duration::from_millis(60),
bandwidth_bytes_per_sec: 512 * 1024,
},
}),
..ServeProfile::fast()
},
);
slowing.max_inflight_requests = 24;
let config = ZakuraBlockSyncConfig {
bbr_cwnd_unit: crate::zakura::CwndUnit::Bytes,
max_blocks_per_response: 1,
request_timeout: Duration::from_secs(2),
..fuzz_config()
};
let mut scenario = Scenario::new(blocks, 0x57ea_00f1, config, vec![slowing]);
scenario.target_block_bytes = Some(16 * 1024);
scenario.deadline = Duration::from_secs(60);
let (_, report) = run_checked("fuzz_peer_slows_radically_is_kept", scenario, 32).await;
assert_eq!(
report.protocol_rejects, 0,
"a peer that only slowed down (still delivering) must not be disconnected",
);
assert!(
report.final_reliability_permille >= 300,
"a slow-but-delivering peer's reliability must stay well clear of the sealed range \
(settled {}/1000, trough {}/1000)",
report.final_reliability_permille,
report.min_reliability_permille,
);
assert!(
report.final_reliability_permille > report.min_reliability_permille,
"reliability must recover from its transition trough (settled {} vs trough {})",
report.final_reliability_permille,
report.min_reliability_permille,
);
assert!(
report.final_cwnd_bytes > 0,
"the slowed peer must keep a (weaker) window, not be sealed to zero",
);
assert!(
report.peak_cwnd_bytes > report.final_cwnd_bytes,
"the slowed peer's window must adapt downward (peak {} → settled {})",
report.peak_cwnd_bytes,
report.final_cwnd_bytes,
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_range_unavailable_penalizes_reliability() {
let blocks = 300;
let config = ZakuraBlockSyncConfig {
max_blocks_per_response: 1,
request_timeout: Duration::from_secs(25),
floor_rescue_timeout: Duration::from_secs(25),
..fuzz_config()
};
let withholder = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile {
withhold: Some((block::Height(100), block::Height(200))),
..ServeProfile::byte_rate(Duration::from_millis(2), 50 * 1024 * 1024)
},
);
let coverer = PeerSpec::with_serve(
2,
target(blocks),
ServeProfile::byte_rate(Duration::from_millis(40), 50 * 1024 * 1024),
);
let mut scenario = Scenario::new(blocks, 0x57ea_00f2, config, vec![withholder, coverer]);
scenario.target_block_bytes = Some(16 * 1024);
scenario.deadline = Duration::from_secs(30);
let (_, report) =
run_checked("fuzz_range_unavailable_penalizes_reliability", scenario, 32).await;
assert!(
report.min_reliability_permille < 1000,
"a peer that answers RangeUnavailable for advertised heights must be charged a \
reliability failure, got {}/1000",
report.min_reliability_permille,
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_churn_storm() {
let blocks = 300;
let stable = PeerSpec::fast(1, target(blocks));
let mut churn_a = PeerSpec::fast(2, target(blocks));
churn_a.disconnect_at = Some(Duration::from_millis(100));
let mut churn_b = PeerSpec::fast(3, target(blocks));
churn_b.connect_at = Duration::from_millis(50);
churn_b.disconnect_at = Some(Duration::from_millis(150));
let mut churn_c = PeerSpec::fast(4, target(blocks));
churn_c.connect_at = Duration::from_millis(100);
churn_c.disconnect_at = Some(Duration::from_millis(200));
let mut scenario = Scenario::new(
blocks,
0x57ea_0005,
fuzz_config(),
vec![stable, churn_a, churn_b, churn_c],
);
scenario.deadline = Duration::from_secs(60);
run_checked("fuzz_churn_storm", scenario, 32).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_large_to_small() {
let blocks = 400;
let jittery = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile {
per_block_latency: LatencyDist::Uniform {
low: Duration::ZERO,
high: Duration::from_millis(1),
},
idle_gap: Some(IdleGap {
every_responses: 25,
duration: Duration::from_millis(5),
}),
..ServeProfile::fast()
},
);
let mut scenario = Scenario::new(
blocks,
0x57ea_0006,
fuzz_config(),
vec![jittery, PeerSpec::fast(2, target(blocks))],
);
scenario.initial_best_header = block::Height(100);
scenario.timeline = vec![
TipEvent {
at: Duration::from_millis(200),
kind: TipEventKind::GrowTo(block::Height(200)),
},
TipEvent {
at: Duration::from_millis(500),
kind: TipEventKind::GrowTo(block::Height(350)),
},
TipEvent {
at: Duration::from_millis(800),
kind: TipEventKind::HeaderReanchor(block::Height(250)),
},
TipEvent {
at: Duration::from_millis(1_100),
kind: TipEventKind::GrowTo(block::Height(400)),
},
];
scenario.deadline = Duration::from_secs(90);
run_checked("fuzz_large_to_small", scenario, 32).await;
}
fn byte_window_config(min_cwnd_bytes: u64) -> ZakuraBlockSyncConfig {
ZakuraBlockSyncConfig {
bbr_cwnd_unit: crate::zakura::CwndUnit::Bytes,
bbr_min_cwnd_bytes: min_cwnd_bytes,
max_blocks_per_response: 1,
..fuzz_config()
}
}
async fn run_byte_size_run(
name: &str,
seed: u64,
blocks: u32,
body_bytes: usize,
config: ZakuraBlockSyncConfig,
serve: ServeProfile,
) -> InvariantReport {
let mut scenario = Scenario::new(
blocks,
seed,
config,
vec![
PeerSpec::with_serve(1, target(blocks), serve),
PeerSpec::with_serve(2, target(blocks), serve),
],
);
scenario.target_block_bytes = Some(body_bytes);
scenario.deadline = Duration::from_secs(30);
let (_, report) = run_checked(name, scenario, 64).await;
report
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_mixed_block_sizes() {
let blocks = 400;
let config = byte_window_config(512 * 1024);
let serve = ServeProfile::byte_rate(Duration::from_millis(2), 20 * 1024 * 1024);
let small = run_byte_size_run(
"fuzz_mixed_block_sizes_small",
0x57ea_0009,
blocks,
8 * 1024,
config.clone(),
serve,
)
.await;
let large = run_byte_size_run(
"fuzz_mixed_block_sizes_large",
0x57ea_000a,
blocks,
64 * 1024,
config,
serve,
)
.await;
let small_request_density =
u128::from(small.peak_cwnd_requests).saturating_mul(u128::from(large.peak_cwnd_bytes));
let large_request_density =
u128::from(large.peak_cwnd_requests).saturating_mul(u128::from(small.peak_cwnd_bytes));
assert!(
small_request_density > large_request_density.saturating_mul(4),
"small bodies should admit many more single-block requests per byte of cwnd \
(small={} reqs @ {} B cwnd, large={} reqs @ {} B cwnd)",
small.peak_cwnd_requests,
small.peak_cwnd_bytes,
large.peak_cwnd_requests,
large.peak_cwnd_bytes,
);
for (label, run) in [("small", &small), ("large", &large)] {
let bound = run.peak_cwnd_bytes.saturating_mul(2);
assert!(
run.peak_inflight_bytes <= bound,
"{label}: in-flight reserved bytes {} must stay bounded by ~the byte cwnd \
(cwnd={} B, bound={} B)",
run.peak_inflight_bytes,
run.peak_cwnd_bytes,
bound,
);
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_commit_stall() {
let blocks = 400;
let body_bytes = 32 * 1024usize;
let byte_ceiling: u64 = 2 * 1024 * 1024;
let resident_budget = byte_ceiling * DESERIALIZED_MEM_FACTOR;
let config = ZakuraBlockSyncConfig {
max_reorder_lookahead_bytes: resident_budget,
max_blocks_per_response: 1,
..fuzz_config()
};
let mut scenario = Scenario::new(
blocks,
0x57ea_000c,
config,
vec![
PeerSpec::fast(1, target(blocks)),
PeerSpec::fast(2, target(blocks)),
],
);
scenario.target_block_bytes = Some(body_bytes);
scenario.commit = CommitProfile {
per_commit_delay: Duration::from_millis(1),
burst: Some(CommitBurstStall {
every_commits: 40,
duration: Duration::from_millis(120),
}),
};
scenario.deadline = Duration::from_secs(60);
let (_, report) = run_checked("fuzz_commit_stall", scenario, 64).await;
let window_slack = (MIN_BS_CHECKPOINT_SUBMITTED_BLOCK_APPLIES as u64)
.saturating_mul(body_bytes as u64)
.saturating_mul(DESERIALIZED_MEM_FACTOR);
let margin = (body_bytes as u64)
.saturating_mul(16)
.saturating_mul(DESERIALIZED_MEM_FACTOR);
let peak_retained_resident = report
.peak_retained_pipeline_wire_bytes
.saturating_mul(DESERIALIZED_MEM_FACTOR);
let bound = resident_budget
.saturating_add(window_slack)
.saturating_add(margin);
assert!(
peak_retained_resident <= bound,
"peak retained resident cost {} must stay within the {} B budget \
(+{} B commit-window slack, +{} B margin); the apply backlog is bounded by \
the resident gate, not unbounded by the commit stall",
peak_retained_resident,
resident_budget,
window_slack,
margin,
);
assert!(
peak_retained_resident >= resident_budget / 2,
"the commit stall should have created real retained-memory pressure \
(retained {} of the {} B budget)",
peak_retained_resident,
resident_budget,
);
tracing::info!(
peak_retained_pipeline_wire_bytes = report.peak_retained_pipeline_wire_bytes,
peak_retained_resident,
final_budget_reserved = report.final_budget_reserved,
resident_budget,
"commit_stall retention backpressure observation",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_commit_stall_resident_plateau() {
let blocks = 1_200;
let body_bytes = 32 * 1024usize;
let resident_budget: u64 = 8 * 1024 * 1024;
let config = ZakuraBlockSyncConfig {
max_inflight_block_bytes: 64 * 1024 * 1024,
max_reorder_lookahead_bytes: resident_budget,
max_blocks_per_response: 1,
..fuzz_config()
};
let mut scenario = Scenario::new(
blocks,
0x57ea_000d,
config,
vec![
PeerSpec::fast(1, target(blocks)),
PeerSpec::fast(2, target(blocks)),
],
);
scenario.target_block_bytes = Some(body_bytes);
scenario.commit = CommitProfile {
per_commit_delay: Duration::from_millis(1),
burst: Some(CommitBurstStall {
every_commits: 40,
duration: Duration::from_millis(120),
}),
};
scenario.deadline = Duration::from_secs(120);
let (_, report) = run_checked("fuzz_commit_stall_resident_plateau", scenario, 64).await;
let window_slack = (MIN_BS_CHECKPOINT_SUBMITTED_BLOCK_APPLIES as u64)
.saturating_mul(body_bytes as u64)
.saturating_mul(DESERIALIZED_MEM_FACTOR);
let margin = (body_bytes as u64)
.saturating_mul(16)
.saturating_mul(DESERIALIZED_MEM_FACTOR);
let peak_retained_resident = report
.peak_retained_pipeline_wire_bytes
.saturating_mul(DESERIALIZED_MEM_FACTOR);
let bound = resident_budget
.saturating_add(window_slack)
.saturating_add(margin);
assert!(
peak_retained_resident <= bound,
"peak retained resident cost {} must stay within the {} B budget \
(+{} B commit-window slack, +{} B margin)",
peak_retained_resident,
resident_budget,
window_slack,
margin,
);
assert!(
peak_retained_resident >= resident_budget / 2,
"the commit stall should have created real retained-memory pressure \
(retained {} of the {} B budget)",
peak_retained_resident,
resident_budget,
);
tracing::info!(
peak_retained_pipeline_wire_bytes = report.peak_retained_pipeline_wire_bytes,
peak_retained_resident,
resident_budget,
window_slack,
"commit_stall resident-plateau observation",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_commit_stall_resident_plateau_multiblock() {
let blocks = 1_200;
let body_bytes = 32 * 1024usize;
let resident_budget: u64 = 8 * 1024 * 1024;
let config = ZakuraBlockSyncConfig {
max_inflight_block_bytes: 64 * 1024 * 1024,
max_reorder_lookahead_bytes: resident_budget,
..fuzz_config()
};
let mut scenario = Scenario::new(
blocks,
0x57ea_000e,
config,
vec![
PeerSpec::fast(1, target(blocks)),
PeerSpec::fast(2, target(blocks)),
],
);
scenario.target_block_bytes = Some(body_bytes);
scenario.commit = CommitProfile {
per_commit_delay: Duration::from_millis(1),
burst: Some(CommitBurstStall {
every_commits: 40,
duration: Duration::from_millis(120),
}),
};
scenario.deadline = Duration::from_secs(120);
let (_, report) = run_checked(
"fuzz_commit_stall_resident_plateau_multiblock",
scenario,
64,
)
.await;
let window_slack = (MIN_BS_CHECKPOINT_SUBMITTED_BLOCK_APPLIES as u64)
.saturating_mul(body_bytes as u64)
.saturating_mul(DESERIALIZED_MEM_FACTOR);
let margin = (body_bytes as u64)
.saturating_mul(16)
.saturating_mul(DESERIALIZED_MEM_FACTOR);
let peak_retained_resident = report
.peak_retained_pipeline_wire_bytes
.saturating_mul(DESERIALIZED_MEM_FACTOR);
let bound = resident_budget
.saturating_add(window_slack)
.saturating_add(margin);
assert!(
peak_retained_resident <= bound,
"peak retained resident cost {} must stay within the {} B budget \
(+{} B commit-window slack, +{} B margin) with multi-block responses",
peak_retained_resident,
resident_budget,
window_slack,
margin,
);
assert!(
peak_retained_resident >= resident_budget / 2,
"the commit stall should have created real retained-memory pressure \
(retained {} of the {} B budget)",
peak_retained_resident,
resident_budget,
);
tracing::info!(
peak_retained_pipeline_wire_bytes = report.peak_retained_pipeline_wire_bytes,
peak_retained_resident,
resident_budget,
window_slack,
"commit_stall multi-block resident-plateau observation",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_high_bw_fast_peer() {
let blocks = 500;
let config = byte_window_config(256 * 1024);
let peer = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile::byte_rate(Duration::from_millis(20), 100 * 1024 * 1024),
);
let mut scenario = Scenario::new(blocks, 0x57ea_000b, config, vec![peer]);
scenario.target_block_bytes = Some(16 * 1024);
scenario.deadline = Duration::from_secs(30);
let (_, report) = run_checked("fuzz_high_bw_fast_peer", scenario, 64).await;
assert!(
report.peak_cwnd_bytes >= 256 * 1024,
"byte cwnd should be traced at or above the floor, got {} B",
report.peak_cwnd_bytes,
);
assert!(
report.peak_inflight_bytes > 0,
"byte in-flight occupancy should be traced",
);
tracing::info!(
peak_cwnd_bytes = report.peak_cwnd_bytes,
peak_inflight_bytes = report.peak_inflight_bytes,
max_outstanding = report.max_outstanding,
"high_bw_fast_peer byte-window observation",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_lossy_peer() {
let blocks = 300;
let lossy = PeerSpec::with_serve(
1,
target(blocks),
ServeProfile {
drop_probability: 0.3,
..ServeProfile::fast()
},
);
let mut scenario = Scenario::new(
blocks,
0x57ea_000d,
retry_config(),
vec![lossy, PeerSpec::fast(2, target(blocks))],
);
scenario.deadline = Duration::from_secs(60);
run_checked("fuzz_lossy_peer", scenario, 32).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_reorder() {
let blocks = 300;
let reorder_serve = ServeProfile {
reorder: true,
..ServeProfile::fast()
};
let scenario = Scenario::new(
blocks,
0x57ea_000e,
fuzz_config(),
vec![
PeerSpec::with_serve(1, target(blocks), reorder_serve),
PeerSpec::with_serve(2, target(blocks), reorder_serve),
],
);
run_checked("fuzz_reorder", scenario, 32).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn fuzz_multi_peer_tight_budget() {
let blocks = 400;
let body_bytes = 32 * 1024usize;
let byte_ceiling: u64 = 3 * 1024 * 1024;
let config = ZakuraBlockSyncConfig {
max_inflight_block_bytes: byte_ceiling,
max_blocks_per_response: 1,
..fuzz_config()
};
let peers: Vec<_> = (1u8..=8)
.map(|id| PeerSpec::fast(id, target(blocks)))
.collect();
let mut scenario = Scenario::new(blocks, 0x57ea_000f, config, peers);
scenario.target_block_bytes = Some(body_bytes);
scenario.commit = CommitProfile {
per_commit_delay: Duration::from_millis(1),
burst: None,
};
scenario.deadline = Duration::from_secs(60);
let (_, report) = run_checked("fuzz_multi_peer_tight_budget", scenario, 128).await;
assert!(
report.peak_budget_reserved >= byte_ceiling / 2,
"the tight budget should bind under 8-peer contention (reserved {} of {} B)",
report.peak_budget_reserved,
byte_ceiling,
);
}