By the end of this module, you will:
Hyperscale-rs is a consensus layer, not a full blockchain stack. This diagram shows the end-to-end path of a transaction from the user to finality, and which parts Hyperscale touches versus which are outside (e.g. wallet, Radix Engine semantics, network client). Hover over each step to see which crates implement it.
Level 1 placement: You’ve done Blockchain and Consensus in generic web3 modules; this module gives a single-shard Hyperscale map before systems theory. Deep dives: after this map, optional phase modules zoom into step groups (start with Phase 1: Submit → Mempool). Then: Distributed Systems, State Machines, OSS first PR, Overview, Crate Groups.
Where BFT comes in: Steps 1–6 get the tx into the system (user, network, node, cross-shard determination, mempool, and for cross-shard txs, travel to involved shards). BFT consensus runs from step 7 (proposer selection) through step 11 (block commit) per shard. Steps 12–14 are execution, then (for cross-shard) coordination & composition, then finality.
Single-shard vs multi-shard (canonical split): Steps 1–6 are the same. From step 7 onward, BFT runs per shard (each shard has its own proposer, block, votes, QC, commit). Single-shard: After step 11 (block commit), step 12 (execution) runs immediately—no provisioning. Multi-shard: After step 11, the execution crate registers the tx with ProvisionCoordinator and waits for ProvisioningComplete (quorum of StateProvisions from each required shard); then it runs the five-phase protocol (State Provisioning → Provision Verification → Deterministic Execution → Vote Aggregation → Finalization). There is no 2PC coordinator in hyperscale-rs; atomicity is provision-based. See Phase 4: Cross-Shard Transactions and Phase 3: Execution & Waves for detail.
Not in Hyperscale — wallet / client application.
Network client / RPC — outside. production receives on the node side.
Events and feeds the node.In Hyperscale-rs, the node sends the tx to other shards via the production network layer: libp2p Gossipsub with shard-scoped topics (e.g. hyperscale/{msg_type}/shard-{id}/1.0.0). RPC is only for client→node submission.
Cycles: live protocol uses execution’s ConflictDetector (crates/execution/src/conflict.rs); simulator post-run analysis uses crates/simulator/src/livelock.rs.
VoteSet.latest_qc is set to the new QC. (iv) The QC is not sent as a separate message—the next proposer has it by forming it from votes or from a received block header. (v) The next proposer builds block H+1 with parent_qc = QC(H) and broadcasts H+1; everyone then sees QC(H) in H+1’s header. Phase 2 — QC & two-chain lead-in.latest_qc, block build with parent_qcBlockHeader.parent_qc in block.rs, QuorumCertificateAction::PersistAndBroadcastVote, Action::VerifyAndBuildQuorumCertificate in action.rsLocations: ShardCoordinator in shard/src/coordinator.rs (QC bookkeeping, try_propose, commit pipeline). QC aggregation in action_handlers.rs + vote_set.rs (build_qc). Line numbers drift—search for the symbol names.
See module “Cross-Shard Transactions” for provision coordination and the five-phase protocol.
Tip: Hover over a step to see which crates implement it. Popup closes when you move the cursor away.
How the 14-step tx flow lines up with phase modules and the interactive BlockHeader + body cards (pistachio green = fields newly highlighted at that card). Full cards live in phase deep-dives — this table is the summary only.
A few common points to keep straight:
ShardCoordinator::try_propose runs after new mempool/QC/provision activity — it doesn’t “receive the tx” specially.try_propose and builds a block. The proposer broadcasts the block (header) to validators in that shard only, not to other shards.parent_qc = QC for previous block in S’s chain, plus txs from S’s mempool). It broadcasts that block (or header; full block is assembled via gossip) to validators in shard S only. So: one block per shard, broadcast inside that shard. Other shards have their own proposers and their own blocks at the same “logical” time.For how cross-shard atomicity works (provision-based protocol: StateProvision → ProvisionCoordinator → execution), how order is fixed (by ShardId, not manifest line order), and concepts (shards, proposer, NodeID, finality), see Phase 4 and Phase 3.
The codebase is grouped by transaction-flow progression. Read the groups in order (1 → 6); use the quizzes in the next module to check understanding.
| Group | Crates | One-liner |
|---|---|---|
| 1. First contact | production, node, mempool, types, core, messages | How Hyperscale receives a tx at the RPC and gets it into the right shards’ mempools. |
| 2. Sharding and routing | types, core, node | Who does what once a tx is decomposed into NodeIds and each shard is responsible for a slice of state. |
| 3. Proposing and building blocks | shard, mempool, types, core | How one validator becomes the proposer and assembles the next block from the mempool. |
| 4. Voting and committing | shard, types, core | How validators agree on a block and when it is finally committed (votes and QCs). |
| 5. Execution after commit | execution, engine, node, types, core | Who runs transactions after commit and how single-shard vs cross-shard paths diverge. |
| 6. Cross-shard: provisions & conflicts | provisions, execution (ConflictDetector), types, core; simulator diagnostics | How state moves between shards and how dependency cycles are resolved deterministically. |
Use the crate-groups table above as your workspace map; phase deep-dives and harness modules come next on the course index.
There is no standalone course module titled “Messaging and Transports.” Messaging and notifications are covered across this page (end-to-end path), State machines & event-driven architecture (how Event / Action and channels compose), the libp2p modules in the Hyperscale basic/intermediate/advanced tracks, and the file links in the tables below. Treat these tables as a reading map: concrete struct names, lock types (Arc<Mutex<HashSet<…>>> vs alternatives), and exact eviction rules can shift between commits — when in doubt, set breakpoints on the linked paths.
How to read the columns. Message source / destination are logical actors (threads, coordinators, peers). Transport is how bits move (RPC, gossip, in-process call). Lifecycle storage is where the payload or derived state usually sits until the next phase. Pruning / flushing / drainage is what makes that storage go away or become irrelevant. The last column, Handlers · registration · done, answers: (a) which functions / traits / closures participate, (b) when they are wired (skip the row if it is always the same boring answer), and (c) how the callee signals completion back into the pinned ShardLoop / state machine.
Source alignment. Handler tables below match network_handlers.rs and host.rs in the hyperscale-rs repo (data-file links open the cited crate paths).
Suggested pacing. Each subsection is sized for roughly 8–25 minutes. The full inventory is about 65–95 minutes if you read the registry plus all four flow chunks in order; skip what you already know.
Every typed inbound path registered on the Network trait is wired in network_handlers.rs and invoked from NodeHost::register_inbound_handlers → register_request_handler, register_gossip_handlers, register_notification_handlers. Registration happens once per node before the main loop processes traffic — on both genesis and resume paths (see host.rs / shard/lifecycle.rs); it is not repeated each round.
The Network trait (crates/network/src/traits.rs) names the dispatch hooks: GossipHandler::on_message, NotificationHandler::on_notification, RequestHandler::handle_request. Outbound Network::request(..., on_response) uses a per-call FnOnce(Result<R::Response, RequestError>) -> ResponseVerdict (not listed row-by-row here); that callback is typically registered at fetch time and completes by pushing a NodeInput onto the same event_sender channel as gossip.
| Registration API | Wire type | Trait dispatch | When registered | How “done” is signaled |
|---|---|---|---|---|
register_gossip_handler::<TransactionGossip> | TransactionGossip | GossipHandler::on_message | Once in register_inbound_handlers (before loop) | Returns GossipVerdict::Accept / Reject to the network stack; closure also send(NodeInput::TransactionGossipReceived) per tx. |
register_gossip_handler::<CommittedBlockHeaderGossip> | CommittedBlockHeaderGossip | GossipHandler::on_message | Same | GossipVerdict; may send(NodeInput::CommittedBlockGossipReceived) after filters. |
register_notification_handler::<BlockVoteNotification> | BlockVoteNotification | NotificationHandler::on_notification | Same | No return value; send(NodeInput::Protocol(BlockVoteReceived …)). |
register_notification_handler::<BlockHeaderNotification> | BlockHeaderNotification | NotificationHandler::on_notification | Same | Verify then send(ProtocolEvent::BlockHeaderReceived …) wrapped in NodeInput::Protocol; drop on bad sig. |
register_notification_handler::<ProvisionsNotification> | ProvisionsNotification | NotificationHandler::on_notification | Same | Verify shard + sig; send(ProtocolEvent::ProvisionsReceived …) or return early. |
register_notification_handler::<ExecutionVotesNotification> | ExecutionVotesNotification | NotificationHandler::on_notification | Same | Per-vote send(ProtocolEvent::ExecutionVoteReceived …). |
register_notification_handler::<ExecutionCertificatesNotification> | ExecutionCertificatesNotification | NotificationHandler::on_notification | Same | send(ProtocolEvent::ExecutionCertificatesReceived …). |
register_request_handler::<GetBlockRequest> | GetBlockRequest | RequestHandler::handle_request | Same | Returns GetBlockResponse synchronously to the requester (may run on blocking pool per adapter). |
register_request_handler::<GetTransactionsRequest> | GetTransactionsRequest | RequestHandler::handle_request | Same | Returns GetTransactionsResponse. |
register_request_handler::<GetProvisionsRequest> | GetProvisionsRequest | RequestHandler::handle_request | Same | Returns GetProvisionResponse; producer path uses mutex + Condvar for single-flight dedup. |
register_request_handler::<GetLocalProvisionsRequest> | GetLocalProvisionsRequest | RequestHandler::handle_request | Same | Returns GetLocalProvisionsResponse. |
register_request_handler::<GetFinalizedWavesRequest> | GetFinalizedWavesRequest | RequestHandler::handle_request | Same | Returns GetFinalizedWavesResponse. |
register_request_handler::<GetExecutionCertsRequest> | GetExecutionCertsRequest | RequestHandler::handle_request | Same | Returns GetExecutionCertsResponse. |
register_request_handler::<GetRemoteHeadersRequest> | GetRemoteHeadersRequest | RequestHandler::handle_request | Same | Returns remote-header sync payload. |
How a transaction first enters the process: user RPC vs peer gossip. Both converge on ShardLoop before anything synchronous runs in the state machine.
| Message source | Message destination | Transport | Lifecycle storage (type · location) | Pruning / flushing / drainage | Handlers · registration · done |
|---|---|---|---|---|---|
| User / wallet (signed transaction) | Validator RPC / gateway (production) | HTTP / JSON-RPC (or similar); TLS terminates outside the pure state machine | Ephemeral deserialization buffers on the RPC worker thread; decoded NotarizedTransaction (or equivalent) until handoff to the node I/O path |
RPC response returned to client; request-scoped allocations dropped. Failed validation returns an error without entering consensus queues. |
|
| RPC or gateway worker | ShardLoop (pinned / dedicated runner thread) | In-process queue: NodeInput (or equivalent) from the production runner — not libp2p |
Bounded in-memory queue between “network / RPC world” and the thread that drives the state machine; backpressure policy is deployment-specific | Drained as ShardLoop runs step; on shutdown, remaining items are dropped with teardown. Under overload, policy may drop or reject before enqueue (inspect runner). |
|
| Remote validator (same shard) | ShardLoop | libp2p gossipsub over QUIC (validator mesh, separate from wallet HTTP) → network_handlers → NodeInput::TransactionGossipReceived |
Adapter receive path + validation staging before a ProtocolEvent is emitted; dedupe / “already seen” structures often live near gossip ingress (pattern: set or map keyed by message id / hash — exact type varies) | Duplicates rejected early (never reach mempool). After acceptance, gossip-derived state may be short-lived; some paths tombstone or remember ids briefly for idempotency — verify in tx_validation.rs. |
|
The synchronous core: one event in, actions out. Validated transactions land in RAM pools that are separate from BFT vote maps and from long-term disk.
| Message source | Message destination | Transport | Lifecycle storage (type · location) | Pruning / flushing / drainage | Handlers · registration · done |
|---|---|---|---|---|---|
| Decoded network / RPC input | NodeStateMachine | Synchronous handle(event) -> Vec<Action> after ShardLoop dequeues (see StateMachine trait) |
No durable queue inside the pure state machine — only stack-local work and returned actions | Not applicable per event: actions are returned immediately to the runner for side effects. |
|
| Validation pipeline (post-signature / manifest checks) | MempoolCoordinator | In-process: ProtocolEvent::TransactionValidated path via state/participation/transactions.rs |
Per-shard pool structures in mempool coordinator — typically maps / sets / priority structures keyed by transaction id and account sequence rules | Removed when the transaction is included in a committed block, replaced under policy (e.g. same sender nonce), or evicted when pools exceed size / fee thresholds. |
|
| Mempool (ordered candidates) | ShardCoordinator (try_propose, proposal build) |
Direct calls from node composition into BFT; not a second network hop | Proposal construction buffers (pending txs pulled for the next block header / body) | Superseded when a different proposal wins the round, or when the view advances without committing that candidate. |
|
Shard-scoped gossip for headers and votes, then durable commit. This chunk is where “messages” look most like classical consensus traffic.
| Message source | Message destination | Transport | Lifecycle storage (type · location) | Pruning / flushing / drainage | Handlers · registration · done |
|---|---|---|---|---|---|
| Current round proposer | All validators on the shard | Gossip notifications: block header path (see messages::notification) | Encode buffers on send; peers hold BFT “pending proposal” / header caches until QC or timeout | Replaced by higher-round headers or cleared after QC commits the height; view-change paths discard stale pending work. |
|
| Each validator after verifying header | Proposer + peers | Gossip: BlockVote (and related notification variants in messages) |
Vote bookkeeping in BFT — e.g. vote_keeper.rs, pending.rs, vote_set.rs for QC formation | Pruned when QC is built for the height, or when round / view timers cause cleanup of stale vote maps. |
|
| BFT commit path | Local persistence + optional broadcast follow-ups | Action::Persist*, CommitBlock, and network actions emitted by the runner |
Committed blocks and certificates on disk (blockstore / DB — exact backend is node deployment); in-memory mirrors for hot path | Archival / compaction is operator policy, not mempool-style eviction. Unlike RAM pools, on-disk data is retained until configured pruning. |
|
What happens after commit, how shards coordinate without a 2PC coordinator, and auxiliary transports (timers, simulation, catch-up sync).
| Message source | Message destination | Transport | Lifecycle storage (type · location) | Pruning / flushing / drainage | Handlers · registration · done |
|---|---|---|---|---|---|
| Committed block / receipts | ExecutionCoordinator | Internal protocol events after commit; then calls into engine for Radix semantics | Execution wave state, receipt tiers (receipt model), coordinator tables until certificates finalize | Drained when the wave completes and transaction / receipt certificates are settled for the affected txs; conflict resolution may drop loser txs per ConflictDetector rules. |
|
| Shard A (provisions / receipts) | Peer shards + provision composition in node | Cross-shard messages over gossip (shard-scoped topics) + in-node provisions coordination — not a classic 2PC coordinator | ProvisionCoordinator-style in-memory maps: quorum of StateProvisions per required shard until ProvisioningComplete |
Drained after deterministic execution in fixed shard order and certificate aggregation, or along deterministic abort / conflict paths (see Cross-Shard module). |
|
| Wall clock / simulated time | Node + BFT timers | Timer driver → timers.rs → ProtocolEvent | Pending timer registrations keyed by round / phase (view-change, cleanup, proposal retry) | One-shot timers fire then unregister; many are cancelled on view advance so stale timeouts cannot commit old rounds. |
|
| Simulator workload driver | ShardLoop / fake network | Synthetic events from simulator / simulation instead of real sockets | Scheduled event lists and in-memory “network” queues | Emptied per simulation step or at scenario end; use livelock analyzer post-run for stuck txs. |
|
| Sync / fetch protocol (catch-up) | Peer nodes | Request/response over libp2p (see shard/protocol) — distinct from mempool gossip | Chunk buffers, block body staging, integrity checks before commit | Temporary buffers freed after successful import; failed sync attempts may retry with backoff (runner-specific). |
|
Why this is not a single “messaging crate” story. User ingress, gossip, shard notifications, execution sidecars, and persistence each use different transports and lifetimes. Gossip payloads live under hyperscale_types::network; node registers handlers in process/network_handlers.rs; mempool and shard own the longest-lived RAM pools until commit or eviction.
Use these steps to run Hyperscale and follow a transaction with a debugger. They assume you have the repo cloned and cargo build works.
Event and Action first. The flow is “event in → state machine → actions out; runner performs actions and feeds back events.” Skim the core event and action modules (see Suggested crates / files to study) so you know what to break on.cargo run for the production runner or sim binary). Submit one transaction and note its hash (from logs or RPC).SubmitTransaction / TransactionGossipReceived (ingress + validation) and ProtocolEvent::TransactionValidated (mempool), then BFT commit paths, then execution wave/certificate handling. Step through to see which crates handle each step.register_* registry, then the edge → mempool → BFT → execution chunks — to jump to “first contact” (RPC handler, submit path, gossip) and “actions to network” (e.g. BroadcastToShard handling) instead of reading the production crate top to bottom.Answer based on the diagram and concepts above. Pass threshold: 70%.
Use alongside the diagram popups above. Paths are relative to the hyperscale-rs repo root.
| Focus | Path |
|---|---|
| Event / Action enums | crates/core/src/protocol_event.rs, crates/core/src/action.rs |
| Submit + gossip ingress | crates/node/src/shard/mod.rs — SubmitTransaction, validation pipeline |
| Node state machine | crates/node/src/state/mod.rs, crates/node/src/state/participation/transactions.rs — ProtocolEvent::TransactionValidated; state/participation/shard.rs for commit/QC dispatch |
| Production RPC / runner | crates/production — submit path, runner |