Single-Shard Simulation E2E Tests: Improved/Innovative New Test Case

⏱️ 1.5–2 hours📊 Level 4Hands-on lab

Why this project

You already traced single_shard_tx_sim on paper in End-to-End Tests. Here you change the real test in the repo: add visible trace points, add a committed-height check across validators (and learn when strict equality matches what a single run_until snapshot can show), and relate SimulationStats to how much simulated time you allow. Everything runs under cargo test with --nocapture so you see effects immediately—no extra harness.

Prerequisites

Files you will touch

Step 1 — Baseline run

From the hyperscale-rs repository root:

cargo test -p hyperscale-simulation --test scenarios single_shard_tx_sim -- --nocapture

Confirm the test passes. Scroll the output once: note genesis message, transaction hash, “initial consensus”, mempool status, and the final pass banner. You will align your own prints with this rhythm.

Step 2 — Trace markers

In single_shard_tx_sim (scenarios.rs), wrap the portable scenario so you can see setup vs drive:

#[test]
fn single_shard_tx_sim() {
    let mut cluster = SimCluster::new(&liveness_config(), 42);
    println!("[lab] cluster ready (4 hosts, 1 shard)");
    single_shard_tx(&mut cluster);
    println!("[lab] single_shard_tx returned");
}

Optional deeper cut: add markers inside hyperscale_scenarios::single_shard_tx around c.submit(...) and the accept assertion (portable body shared with production CI). Re-run the same cargo test command and confirm [lab] cluster ready appears before the scenario completes.

Step 3 — Committed heights across validators

Cluster::committed_height(shard) returns the cluster-wide max tip. To see per-host lag, use host_committed_height(host, shard) on SimCluster (FaultableCluster trait).

Important caveat

Why you might see [2, 2, 2, 1] (or similar) mid-run

Commits propagate through the simulated network with nonzero latency. A snapshot while messages are still in flight can leave one host one height behind. That usually means eventual sync, not a fork.

Takeaway: requiring strict equality at an arbitrary midpoint is stricter than the harness guarantees. Prefer asserting after the scenario’s own await_tx_terminal succeeds, or document a weaker max−min ≤ 1 check.

After single_shard_tx(&mut cluster) returns (tx accepted), collect per-host heights:

use hyperscale_types::ShardId;
let heights: Vec<u64> = (0..4usize)
    .map(|host| {
        cluster
            .host_committed_height(host, ShardId::ROOT)
            .expect("host seats ROOT")
            .inner()
    })
    .collect();
println!("[lab] per-host committed heights: {:?}", heights);
assert!(
    heights.iter().all(|&h| h == heights[0]),
    "validators not aligned on committed height: {:?}",
    heights
);

Requirements: always print the vector; if equality fails right after accept, try one more short cluster.run_until(...) budget or document max−min ≤ 1 as your lab note.

Step 4 — SimulationStats vs time

Still inside the test (after height checks), peek at the underlying runner stats:

println!("[lab] stats: {:?}", cluster.runner().stats()); // or access via SimCluster fields if public

If runner() is not exposed, open crates/simulation/src/runner.rs and note which SimulationStats fields (events_processed, messages_sent, timers_set) the harness updates — then temporarily log them from a local branch of SimCluster or the scenario body.

Compare a full single_shard_tx run to a deliberately starved budget (e.g. call run_until with a tiny Budget before submit and observe the accept assertion fail). Restore the normal scenario before you finish.

Step 5 — Stretch (optional)

What “done” looks like

Next

When ready, continue to production harness (Part II) or open #18 for broader transaction/substate test coverage.

OSS stretch — transaction / substate tests (#18)

After this lab, generalize: pick a real transaction shape, add a focused deterministic test, and open a draft PR with Refs #18.

#18 — Add suite of transaction/substate tests