HyperScale · Articles
Multi-Sharded Transactions
Split the work, not the world. One signed intent across shards must finish the same way everywhere—or nowhere—without async half-states, optimistic rollbacks, or bridge islands. This article follows that claim from first principles through a concrete payment, then through the exits the protocol refuses to leave unsignposted.
1. Why shards exist
A single chain has a hard ceiling: every validator re-executes every transaction, so adding validators adds security, never throughput. Sharding splits state and committees into parallel groups—but then a transaction touching two shards needs both to agree, and the usual answers all compromise.
| Usual fix | What users inherit |
|---|---|
| Async messages / sagas | Visible half-done states—“your swap left one shard before it arrived at the other.” |
| Optimistic execute + rollback | Finality becomes “probably final”; contracts must survive being un-run. |
| Walled gardens | Composability only inside a shard; ecosystems fragment into bridge islands. |
Framing from hyperscale.rs §01 — the problem HyperScale refuses.
HyperScale’s answer: a transaction declares the state it touches, every shard that owns any of it participates, and the intent
commits atomically on all of them or none—synchronous, composable, final.
Placement is prefix-based, not random hashing of txs: a binary ShardTrie walks owner address bits to a leaf.
Contiguous owners stay together; a single account’s substates never straddle a boundary—so the world stays one tree even as leaves multiply.
Throughput scales with shards for local work; cross-shard traffic pays an explicit pipeline cost—slower than local, never partial.
2. What a multi-shard transaction is
A transaction is multi-shard (cross-shard) when its derived effect set touches more than one leaf of the shard trie. HyperScale derives that set locally from the signed envelope—never from a sender-claimed shard list—so you cannot advertise a placement your content does not earn.
| Notion | Meaning | In code |
|---|---|---|
| Touched shards | Every shard whose prefixes appear in the tx’s routing | all_shards_for_transaction |
| Write / consensus participation | Shards that own cells the tx mutates | write_prefixes → local writes |
| Provision dependency | Shards that must ship prior values of fresh reads / RMW keys | provision_prefixes |
| Fee payer shard | Where the fee account lives; engagement evidence fans out from here | fee_payer → trie walk |
Crucially: every touched shard executes the full manifest. Each persists only the writes it owns. Shard A does not “call” Shard B like an RPC—both run the same intent against a merged view of local state plus proven remote inputs.
One-line test If Alice’s vault is on shard A and Bob’s on shard B, a transfer that debits Alice and credits Bob is multi-shard—even though each vault “lives” on only one shard—because the intent’s prefixes span both leaves.
3. Atomic composability (and why this is not 2PC)
Atomic composability means one user intent that spans shards ends with the same terminal outcome on every participant—accept with identical receipt hashes, or abort everywhere—with BFT finality on each shard’s chain. Manifest lines still compose in order inside one engine session; the protocol’s job is to make that session safe when the inputs live in different committees.
The Two Generals problem says you cannot make both sides certain by messengers alone. HyperScale refuses that game: nobody decides the outcome; everyone computes it from quorum-certified chain facts both sides can eventually hold. A provision is a sunrise, not a handshake—a merkle proof against a committed block is either visible or not; there is nothing to acknowledge. Certificates attest an answer already computed; they do not choose one. Divergent certificates simply cannot assemble into a valid commit—atomicity is enforced by the shape of the artifact. (The generals.)
| Classic 2PC | HyperScale | |
|---|---|---|
| Who decides? | Coordinator tallies yes/no | Pure function of committed facts |
| Coordinator failure | Can block with locks held | No coordinator to crash |
| Outcome open? | Hangs until votes land | Never “could go either way” once inputs are fixed |
| Participant | Often one fragile process | BFT committee; even death resolves via settled-set fence |
| Finalization rule | Collect prepares/commits | Success needs every shard’s success; any abort is terminal for all |
4. Order ≠ result — three engines, one clock
HyperScale runs three agreements, each shaped for its job (aerial view §02):
- Shard consensus — many HotStuff-2 chains order transactions in parallel. Shards never wait on each other to progress; that is where linear scaling comes from.
- Execution consensus — a lighter quorum certifies what the transactions did. Ordering a tx and agreeing on its result are different problems.
- Beacon consensus — one slow chain never sees a user tx; it publishes the schedule: who governs which shard at which moment.
Splitting order from execution is the quiet enabler of multi-shard work: a shard can commit to running a cross-shard transaction (locks engaged, tx in history) before it can know the result. The result arrives later, once every counterparty’s proven state is in hand and the deterministic engine has run.
The glue is a clock made of votes: every QC carries an averaged, clamped timestamp no minority of liars can meaningfully move. Deadlines, committee lookups, and retention windows read that attested time—not anyone’s wall clock (the clock). Cross-shard proofs also assume each shard has one history: quorum intersection makes conflicting commits impossible under the honesty bound (the overlap)—a forked shard would poison every provision drawn from it.
5. Working example — one payment across two shards
Follow Ada → Bo the way the journey does. Ada’s account lives on Shard 01; Bo’s on Shard 10. Two independent chains that never pause for each other—and the money must arrive entirely on both, or not at all. Ada’s fee payer is on 01, so engagement evidence fans out from there.
What Ada never sees Two committees, proofs fording the gap, the identical computation running twice, certificates matching outcome vectors. The apparatus exists so a sharded world feels like one machine.
- Gate — admit; wait until no other in-flight tx shares her declared cells.
- Order — tx in a block; QC = custody receipt; locks engage. Success not decided.
- Provision — ship Ada-side state + multiproof; absorb Bo’s engagement echo.
- Execute — byte-identical inputs → same receipts; write Ada’s cells only.
- Seal — EC01; assemble with EC10; locks release.
- Gate — same bytes admitted under the same hash-order rule.
- Order — independent commit; locks on Bo’s cells; result still unknown.
- Provision — verify Ada’s facts against QC’d header; echo commitment to payer.
- Execute — same engine, same inputs; write Bo’s cells only.
- Seal — EC10; success only if both ECs agree on the result hash.
Two clocks
Inside the VM, instructions run in manifest order. Across the network: both shards order first, then exchange pre-tx facts, then execute. Neither waits for the other’s permission—facts cross; decisions don’t need to.
| Stop | Shard 01 | Shard 10 | Shared truth |
|---|---|---|---|
| Order | Tx committed; roots announce 10 | Tx committed; echo owed to 01 | Same hash ordered twice—result undecided |
| Provision | Proven Ada state → 10 | Verify vs QC’d header; DA fetch if gossip drops | Facts, not trusted messages |
| Execute | Full manifest; write Ada | Full manifest; write Bo | Identical receipt hashes (sunrise) |
| Seal | EC01 + EC10 | EC10 + EC01 | Accept both or Abort both |
Finalization is asymmetric on purpose: success requires a success from every participating shard; an abort from any one is terminal for all. No reconciliation phase, no “pending” limbo, no rollback window.
6. Three ingredients
Declared access
Before submit, the itinerary is written into the transaction: every account (prefix) it will touch.
Nothing is discovered en route. That declaration routes the payment, bounds what it may write, and lets shards reason about conflicts without executing
(asterisk: you must say what you touch).
In code: a locally derived Routing—read/write keys, provision keys, modes—never a sender-claimed shard list.
Provisions (facts, not claims)
A Provisions bundle carries the exact remote cells needed under a multiproof against a quorum-signed block.
Trust no node—verify against the sender’s certified header. Headers pre-announce shipments so absence is actionable; if gossip drops a bundle, fetch from the committee obligated to serve it (archive / DA path).
Role-shaped fan-out still applies: payer and read-set owners attest toward every participant; others send an engagement echo to the payer.
Execution certificates & finalization
After a tick runs on byte-identical inputs, validators sign what they computed—an attestation, not a vote.
2f+1 on the same receipt root yields an ExecutionCertificate.
A Finalization assembles matching ECs from every participant; locks release; the user sees one terminal status.
7. End-to-end pipeline (absolute beginning → end)
| Stage | What happens | Home |
|---|---|---|
| 1. Sign & submit | Wallet signs; RPC hits a node; host fans out admit/gossip to hosted touched shards. | node process I/O |
| 2. Route & park | Routing derived; non-payer legs may park until payer engagement evidence commits. | mempool |
| 3. Propose & commit | Shard leaders include the tx; header commits provision_tx_roots; BFT QC seals height H. |
shard + types roots |
| 4. Build / verify provisions | Source builds multiproofed bundles; destination verifies against remote QC’d header + announced roots. | provisions |
| 5. Compose tick | Candidates enter when provisioned, engagement settled, and provisional cells compatible. | execution candidates |
| 6. Execute | Engine runs full manifest on local view @ H plus provision overlay; each shard writes owned cells only. | engine + storage view |
| 7. Certify | Execution votes → EC; remote ECs fetched/gossiped. | execution |
| 8. Finalize | Finalization commits; receipts applied; mempool releases; user sees terminal status. |
types + mempool locks |
Vocabulary note
Older write-ups say “waves.” The live protocol’s execution identity is a tick (TickId { shard, height }) and a Finalization. Same story; sharper names.
8. Code that matters
Snippets trimmed from the protocol sources. Ellipsis hides ceremony.
/// Every shard a transaction touches via its derived effect sets.
pub fn all_shards_for_transaction(&self, tx: &Transaction) -> Vec<ShardId> {
tx.routing()
.all_prefixes()
.into_iter()
.map(|prefix| self.shard_for_prefix(prefix))
.collect::<BTreeSet<_>>()
.into_iter()
.collect()
}
pub fn is_cross_shard_transaction(&self, tx: &Transaction) -> bool {
self.all_shards_for_transaction(tx).len() > 1
}
pub struct Routing {
pub read_keys: Vec<DeclaredKey>,
pub write_keys: Vec<DeclaredKey>,
pub read_prefixes: Vec<Address>,
pub write_prefixes: Vec<Address>,
/// Fresh reads + RMW priors counterparts must carry.
pub provision_keys: Vec<DeclaredKey>,
pub provision_prefixes: Vec<Address>,
pub declared_modes: Vec<(DeclaredKey, Mode)>,
}
pub fn compute(
local_shard: ShardId,
topology_snapshot: &TopologySnapshot,
transactions: &[Arc<Verifiable<Transaction>>],
) -> Self {
// ...
for tx in transactions {
if topology_snapshot.is_single_shard_transaction(tx) {
continue;
}
let payer_shard = trie.shard_for_prefix(tx.body().fee_payer);
let owns_read_set = tx.routing().provision_prefixes.iter()
.any(|prefix| trie.shard_for_prefix(*prefix) == local_shard);
if payer_shard != local_shard && !owns_read_set {
// Engagement echo toward the payer only.
per_target.entry(payer_shard).or_default().push(Hash::from(tx.hash()));
continue;
}
for shard in topology_snapshot.all_shards_for_transaction(tx) {
if shard == local_shard { continue; }
per_target.entry(shard).or_default().push(Hash::from(tx.hash()));
}
}
// ... merkle each bucket → ProvisionTxRoot
}
//! Phase 1: State Provisioning — bundles + proofs between shards
//! Phase 2: Tick-Atomic Execution — compose_tick → engine batch
//! Phase 3: Vote Aggregation — ExecutionVote → ExecutionCertificate
//! Phase 5: Finalization — local EC + remote ECs → Finalization
9. Why this is a big deal
Other families buy throughput with footnotes users feel: half-done swaps, challenge windows, bridge risk (see asterisks). HyperScale’s claim is the matrix’s hard columns—atomic cross-shard, one composable world, final means final, scales with shard count—with fine print moved to the programming model and security sizing, not the user-visible path.
| HyperScale asterisk | What it means | What it does not mean |
|---|---|---|
| Declared access | You name state up front | Not “composability only within one shard” |
| Cross-shard latency | Pipeline spans blocks on multiple chains | Not a half-done state while waiting |
| Committee honesty | Sampling must keep each committee under ⅓ corrupt | Not “finality is probabilistic for the user” |
Product sentence: sharding without surrendering atomic composability—paid for with declared access, proven state transfer, and certified deterministic execution. Ada signs once; she never orchestrates a bridge.
10. What can go wrong — and how it is overcome
The happy path is short. Character shows in the detours—each ending in the same identical verdict on every shard (journey · detours). If happy-path outcomes are deterministic but abort paths depend on who heard what, the Two Generals are back.
| Detour | What happens | Sunrise (deterministic exit) |
|---|---|---|
| Account busy at the gate | Another in-flight tx holds a declared cell | Wait in line; contention costs latency, never correctness |
| Genuine cross-shard tangle | Deadlock across shards from committed facts | Both sides apply the same rule (e.g. lower hash wins); loser aborts before run |
| Provisions never come | Gossip loss / stalled counterparty | Deadline on the voted clock → whole batch aborts identically |
| Shard dying mid-flight | Counterparty terminates in a reshape | Settled-set fence: finalize iff the dying chain settled it; else abort—never message luck |
| Missing / bad provision artifact | Root mismatch vs header announcement | Drop; expected-provision fetch from obligated committee |
| Remote header lag | Cannot verify multiproof yet | Sync QC’d header before accept |
| Execution divergence | Local receipt ≠ peers | Mark divergent; sync canonical Finalization |
| Network partition | Shard BFT below quorum | Halt rather than fork; deadline abort if stuck; heal resumes |
Design posture An abort is a first-class terminal outcome sealed inside certificates with the same finality as success—not a half-state. Prefer abort everywhere over a half-applied credit.
11. Closing map
Shards exist to parallelize work without splitting the user’s world. A multi-shard transaction declares its route, orders independently on each chain, exchanges proven facts, computes one result twice, and seals only when certificates match. Atomic composability is computation over shared sunrise facts—not 2PC negotiation.
Ada→Bo is the smallest complete story. Everything harder (DeFi legs, reshape fences, partitions) reuses that spine.
| Read next | Why |
|---|---|
| hyperscale.rs/journey | Narrative payment with every detour |
| hyperscale.rs/generals | Why messengers are refused; why certificates aren’t votes |
| hyperscale.rs/asterisks | Where the fine print deliberately lives |
docs/04-atomic-commitment.md |
Canonical protocol write-up in the repository docs |
| Phase 4 module | Course drill paired with this article |