Hyperscale-rs Overview & Setup

⏱️ Duration: 1.5-2 hours πŸ“Š Difficulty: Basic 🎯 Hyperscale-rs Specific

Learning Objectives

By the end of this module, you will:

1. Intro to the project: what it is and links

Hyperscale (the protocol)

Hyperscale is the BFT consensus protocol designed for high-throughput, sharded blockchain networks. It defines how validators agree on blocks, handle cross-shard coordination, and achieve finality. The protocol and the Rust implementation live in the same repository.

Hyperscale-rs (the implementation)

Hyperscale-rs is the Rust implementation: a Byzantine Fault Tolerant (BFT) consensus system for high-throughput, sharded networks. It is not a full blockchain platform β€” it is the consensus layer that decides which transactions get included and in what order. Execution, storage, and user-facing apps are separate.

Key links: Open the repository for code and protocol docs. README, trait, and node-state paths are listed in Suggested crates / files to study at the end of this module.

Design: pure consensus (no I/O in core), state machine model, event-driven (events in β†’ actions out), deterministic so the same code runs in simulation and production.

2. Trace flows end-to-end

The system is "events in, actions out": the node receives Events and returns Actions; the runner does I/O (network, storage). Below are three flows with step-by-step instructions: where to add tracing::debug!, the command to run the sim, and the log order you should see. Do the prerequisite once, then pick one flow at a time.

Prerequisite: Build and run (do this first)

  1. Clone: git clone --recurse-submodules https://github.com/flightofthefox/hyperscale-rs.git, then cd hyperscale-rs. If you already cloned without submodules: git submodule update --init --recursive.
  2. Build: cargo build --release. Wait until it finishes without errors.

Tracing: Use tracing::debug! (or debug! with use tracing::debug). Run the sim with RUST_LOG=debug so these logs show up. Remove or comment out the lines before committing.

Flow 1: Transaction submit (workload β†’ sim runner β†’ node β†’ mempool)

Overview: The simulator workload submits a transaction via schedule_initial_event(..., NodeInput::SubmitTransaction { tx }); the node's ShardLoop receives it, gossips to shards, and runs the validation batch pipeline. Raw gossip arrives as NodeInput::TransactionGossipReceived (stays inside ShardLoop), valid txs surface as NodeInput::TransactionValidated, which becomes ProtocolEvent::TransactionValidated for NodeStateMachine; mempool admission happens in on_transaction_gossip. You see submit β†’ validation β†’ protocol event β†’ pool.

1. Where to add debug!

StepWhere to add loggingExample debug!
1 Simulator workload: inside for tx in batch, just before per-node schedule_initial_event, after tx and target_shard exist. tracing::debug!(tx_hash = ?hash, target_shard = target_shard.0, nodes = ?shard_nodes, "sim: workload submitting tx to shard nodes");
2 Node I/O loop: NodeInput::SubmitTransaction arm, top of block (shard set + gossip). tracing::debug!(tx_hash = ?tx_hash, shards = ?shards, "shard_loop: SubmitTransaction received, gossiping to shards");
3 ShardLoop::step: NodeInput::TransactionGossipReceived arm (crates/node/src/shard/mod.rs + shard/mempool/validation.rs) β€” queues batched validation for inbound gossip; locally submitted txs skip this arm because step 2 already queued validation. tracing::debug!(tx_hash = ?tx_hash, "shard_loop: gossip tx received, queued for validation");
4 Node state: ProtocolEvent::TransactionValidated handled in crates/node/src/state/participation/transactions.rs β†’ mempool.on_transaction_gossip. tracing::debug!(tx_hash = ?tx.hash(), submitted_locally = submitted_locally, "node state: TransactionValidated -> mempool");
5 Mempool on_transaction_gossip, right after let hash = tx.hash(); (codebase already has trace! there; add debug! if you want it visible at RUST_LOG=debug). tracing::debug!(tx_hash = ?hash, submitted_locally, "mempool: adding tx via gossip");

File paths for each step β†’ Suggested crates / files to study (Flow 1).

2. Command to run (tx submit in simulation)

RUST_LOG=debug cargo run --release --bin hyperscale-sim -- --seed 42 -s 1 -v 4 -d 5 --tps 20

3. Optional shorter run

To see just a few submissions: RUST_LOG=debug cargo run --release --bin hyperscale-sim -- --seed 42 -s 1 -v 4 -d 2 --tps 5 (2 seconds, 5 TPS).

4. Summary β€” log order for each tx

StepWhat you see
1Workload submits tx to shard nodes
2Simulation runner receives SubmitTransaction and gossips
3Inbound gossip tx queued for validation (ShardLoop)
4ProtocolEvent::TransactionValidated handed to mempool
5Tx added to pool (existing or your debug)

Flow 2: Block vote (message in β†’ BFT β†’ vote out)

Overview: A vote for a block arrives from the network (or the node produces its own vote). The simulation runner decodes it and feeds an event into the node; the BFT layer processes it and may emit Action::SendMessage (vote); the runner sends it over the simulated network. You see the consensus vote round-trip.

1. Where to add debug!

StepWhere to add loggingExample debug!
1 Simulation runner: where BFT messages become node events (handler / injection). tracing::debug!(node = node_index, "sim runner: BFT message received, forwarding to node");
2 Node state: BFT / vote match arm entry. tracing::debug!("node state: BFT event handled (vote in/out)");
3 Where Action::SendMessage is produced for a vote (BFT or node). tracing::debug!("bft: emitting SendMessage (vote)");
4 Simulation runner: Action::SendMessage dispatch to network. tracing::debug!(node = node_index, "sim runner: SendMessage executed (vote on wire)");

Paths β†’ study table (Flow 2).

2. Command to run

Same as Flow 1 so that proposals and votes happen: RUST_LOG=debug cargo run --release --bin hyperscale-sim -- --seed 42 -s 1 -v 4 -d 5 --tps 20. Votes occur as the sim runs; watch for your four log lines in order.

3. Summary

StepWhat you see
1BFT message received, forwarded to node
2BFT event handled (vote in/out)
3SendMessage (vote) emitted
4SendMessage executed (vote on wire)

Flow 3: Block commit (commit β†’ execution)

Overview: BFT agrees on a block and commits it. The node receives a "block committed" event; the state machine hands off to the execution layer, which runs the transactions in that block. You see consensus output becoming execution input.

1. Where to add debug!

StepWhere to add loggingExample debug!
1 Node state: BlockCommitted (or equivalent) match arm. tracing::debug!(height = ?height, "node state: BlockCommitted, entering execution"); (adjust fields to what exists locally.)
2 Execution entry for committed block (β€œapply block” path). tracing::debug!("execution: applying committed block");

Paths β†’ study table (Flow 3).

2. Command to run

Same as Flow 1: RUST_LOG=debug cargo run --release --bin hyperscale-sim -- --seed 42 -s 1 -v 4 -d 5 --tps 20. Blocks commit as the sim runs; you should see "BlockCommitted, entering execution" then "execution: applying committed block" when a block commits.

3. Summary

StepWhat you see
1BlockCommitted, entering execution
2Applying committed block

Understanding simulator output

When you run the sim, you may see metrics like these. Hover the terms for definitions:

Full picture: The complete end-to-end flow β€” from user signing a transaction to finality β€” is covered in a dedicated Basic module: Transaction Flow: User to Finality. Use that module for the full diagram and to map every step to crates.

3. Running real nodes (production) vs simulation

The rest of this module uses the simulator (hyperscale-sim): one process, simulated time, in-memory network. To run real validator nodes (separate processes, real libp2p, real RocksDB, real RPC), you use the production runner and launch scripts. Below: what it takes and how to do it.

Production threading (brief)

Validators use the same ShardLoop / state machine as simulation, but in production that loop runs on a dedicated std::thread (the shard-loop thread), optionally pinned to a CPU core for steadier scheduling, while Tokio runs libp2p, timers, and related async work; heavy work is often offloaded to thread pools and results return via channels. This is not a special β€œtest-only” loopβ€”it is how live nodes advance consensus. For a full explanation (run_shard_loop, core_affinity::set_for_current, simulation vs production), see E2E harnesses (simulation + production). A compact β€œthreading ladder” of terms lives in State Machines & Event-Driven Architecture.

Requirements

How to run a local cluster (one machine)

Process-based (fastest for iteration):

./scripts/launch-cluster.sh

This starts a cluster of validator nodes as background processes on your host. Options:

At the end, the script prints the RPC ports for each node (e.g. 8080, 8081, …). You need these for the spammer or for submitting transactions.

Docker-based (closer to production):

./scripts/launch-docker-compose.sh

Runs the cluster inside Docker containers. Ensure Docker has at least 8–10 GB RAM. Options include --shards, --validators-per-shard, --memory, --cpus, --latency. Stop with:

./scripts/stop-docker-compose.sh

How to run a distributed cluster (multiple machines)

To run validators across several physical hosts or VMs, use the distributed workflow (see the repo’s README_DISTRIBUTED.md):

  1. Generate configs on one machine: ./scripts/generate-distributed-config.sh --hosts "IP1,IP2,..." --nodes-per-host N. This creates keys and config.toml per node under distributed-cluster-data/.
  2. Copy the hyperscale-validator binary and each host’s config directory to the corresponding machine.
  3. Start each validator on its host: ./hyperscale-validator --config /path/to/node-X/config.toml.

Prerequisites: Rust (for building and generating configs), SSH to all hosts, and ports 9000 (UDP/TCP) and 8080 (TCP) open as needed for P2P and RPC.

Load testing real nodes

After the cluster is up, use the hyperscale-spammer to send traffic to the RPC endpoints. Use the ports printed by the launch script (they depend on shards and validators):

./target/release/hyperscale-spammer run \
  --endpoints "http://localhost:8080,http://localhost:8081" \
  --num-shards 2 \
  --validators-per-shard 4 \
  --tps 100 \
  --duration 30s

Summary: Simulation = one binary (hyperscale-sim), no real network or disk. Real nodes = launch script or Docker/distributed + hyperscale-validator per node, real libp2p and RocksDB; then use the spammer or your own RPC client to drive load. For exact flags and troubleshooting, see the repo README.md and README_DISTRIBUTED.md.

Important crates for overall flows

When studying single-shard and multi-shard flows, focus on node, mempool, shard (per-shard consensus), beacon (validator set / topology), execution, and types; multi-shard adds provisions and ConflictDetector in crates/execution/src/conflict.rs. For simulation tracing, use simulator and simulation (NodeHost, ShardEvent queue) β€” paths in the study table.

See Transaction Flow for the stage diagram and crate-group table.

Suggested crates / files to study

Paths relative to the hyperscale-rs repo root. Use with the trace flows in section 2.

Flow / topic Path
README README.md
Core skim crates/core/src/traits.rs, crates/node/src/state/mod.rs
Flow 1 β€” step 1 crates/simulator/src/runner.rs β€” workload schedule_initial_event
Flow 1 β€” steps 2–4 crates/node/src/shard/mod.rs, crates/node/src/shard/mempool/validation.rs (SubmitTransaction, gossip ingress, validation batches), crates/node/src/process/network_handlers.rs (wire β†’ TransactionGossipReceived), crates/node/src/state/participation/transactions.rs (ProtocolEvent::TransactionValidated β†’ mempool)
Flow 1 β€” step 5 crates/mempool/src/coordinator.rs
Flow 1 summary step 2 crates/simulation/src/runner.rs β€” SubmitTransaction path
Flow 2 crates/simulation/src/runner.rs, crates/node/src/state/mod.rs, crates/shard
Flow 3 crates/node/src/state/mod.rs, crates/execution
Conceptual crates node, mempool, shard, beacon, execution, types, provisions, execution/conflict.rs, simulator, simulation