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.

Deep dive ~30–40 min Insights: hyperscale.rs

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.

1 shard
1× capacity
4 shards
~4× local work
Cross-shard tax
provisions + ECs

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):

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.

Shard 01 · Ada
  1. Gate — admit; wait until no other in-flight tx shares her declared cells.
  2. Order — tx in a block; QC = custody receipt; locks engage. Success not decided.
  3. Provision — ship Ada-side state + multiproof; absorb Bo’s engagement echo.
  4. Execute — byte-identical inputs → same receipts; write Ada’s cells only.
  5. Seal — EC01; assemble with EC10; locks release.
Shard 10 · Bo
  1. Gate — same bytes admitted under the same hash-order rule.
  2. Order — independent commit; locks on Bo’s cells; result still unknown.
  3. Provision — verify Ada’s facts against QC’d header; echo commitment to payer.
  4. Execute — same engine, same inputs; write Bo’s cells only.
  5. 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
Time →
Declare Route from signed access
Gate No shared cells in flight
Order Commit on each chain
Provision Proven state / echoes
Execute Same inputs → same result
Seal Matching ECs

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.

crates/types/src/topology/snapshot.rs — cross-shard test
/// 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
}
crates/types/src/transaction/vm.rs — Routing (declared access)
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)>,
}
crates/types/src/shard/roots/provision_tx.rs — role-shaped fan-out
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
}
crates/execution/src/coordinator.rs — phase map (module docs)
//! 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