By the end of this module, you will:
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 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.
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.
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.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.
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!
| Step | Where to add logging | Example 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 20RUST_LOG=debug β your debug! and existing debug logs appear.--seed 42 β reproducible run.-s 1 β one shard (simplest path).-v 4 β 4 validators (BFT quorum).-d 5 β 5 seconds.--tps 20 β low TPS so logs are easier to follow.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
| Step | What you see |
|---|---|
| 1 | Workload submits tx to shard nodes |
| 2 | Simulation runner receives SubmitTransaction and gossips |
| 3 | Inbound gossip tx queued for validation (ShardLoop) |
| 4 | ProtocolEvent::TransactionValidated handed to mempool |
| 5 | Tx added to pool (existing or your debug) |
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!
| Step | Where to add logging | Example 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
| Step | What you see |
|---|---|
| 1 | BFT message received, forwarded to node |
| 2 | BFT event handled (vote in/out) |
| 3 | SendMessage (vote) emitted |
| 4 | SendMessage executed (vote on wire) |
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!
| Step | Where to add logging | Example 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
| Step | What you see |
|---|---|
| 1 | BlockCommitted, entering execution |
| 2 | Applying committed block |
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.
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.
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.
cargo build --release (same as for sim). This builds the hyperscale-validator (or equivalent production binary) and the launch scripts use it.--clean to start fresh.Process-based (fastest for iteration):
./scripts/launch-cluster.shThis starts a cluster of validator nodes as background processes on your host. Options:
--shards <N> β Number of shards (default: 2).--validators-per-shard <M> β Validators per shard (default: 4).--clean β Wipe data directories before starting.--monitoring β Start Prometheus and Grafana for metrics.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.shRuns 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.shTo run validators across several physical hosts or VMs, use the distributed workflow (see the repoβs README_DISTRIBUTED.md):
./scripts/generate-distributed-config.sh --hosts "IP1,IP2,..." --nodes-per-host N. This creates keys and config.toml per node under distributed-cluster-data/.hyperscale-validator binary and each hostβs config directory to the corresponding machine../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.
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 30sSummary: 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.
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.
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 |