After Phases 1–4: how commitments differ from signatures, how each *_root is computed, and how blocks chain without one global tx Merkle. Next: Timers & chain time.
body.certificates.body.provisions, provision roots in the header.Header *_root fields are commitments in the blockchain sense: one hash that binds the header to a specific collection (tx list, wave receipts, state trie, …). They answer “What is inside this package?” — not “Who approved it?” (that is digital signatures on votes). This is narrower than a ZK commitment scheme (hide-then-reveal); here the body is usually public and peers recompute the root.
state_root; Merkle proofs for single leaves).| Commitment (header root) | Signature (vote / QC) | |
|---|---|---|
| Job | Content fingerprint — change one leaf, root changes | Proof that this validator key approved a specific message |
| Made with | Hash / Merkle root / JMT root (anyone can recompute from public data) | Private key → BLS signature; peers verify with registered public key |
| Analogy | Seal on the envelope whose pattern is determined by the letter inside | Personal stamp — proves who approved; still tied to a specific signed payload, not “anything in the box” |
| Hyperscale example | transaction_root, certificate_root, state_root | BlockVote on block_hash → aggregated into QC |
How verify works (BlockVote): rebuild the canonical signing bytes (DOMAIN_BLOCK_VOTE + network + shard + height + round + block_hash), look up the voter’s BLS public key from ValidatorId, run verify_bls(message, pubkey, signature) → accept or reject. The block_hash already digests the header (including all commitment roots), so the stamp is on this exact proposal package, not on each tx individually.
block_hash. Separate steps recompute Merkle roots, replay certificates for state_root, and run execution rules.
| Name | Layer | Where | Commits to / proves | Purpose |
|---|---|---|---|---|
| state_root | Execution | Header | JMT root after applying this block’s body.certificates — not a Merkle over txs |
Execution meaning: “applying these certs yields this ledger.” Consensus attests the claim indirectly — BlockVotes sign block_hash, which digests the header including state_root. |
| transaction_root | Consensus (inputs) | Header | Merkle root over tx hashes in body.transactions |
“Which user txs are on this page, in what order?” Does not prove execution results. |
| QuorumCertificate (QC) | Consensus | Child header as parent_qc; CommittedBlockHeader gossip |
2f+1 block votes on one block_hash |
“Did a quorum agree this block is valid to chain?” Enables two-chain commit. |
Transaction certificate (in FinalizedWave) |
Execution | body.certificates |
Per-tx / per-wave execution outcomes (success/fail, receipt hashes) | “What happened when we ran txs from earlier blocks?” Feeds the next block’s state_root. |
Three different “yes” messages — each with its own signed payload and aggregation story:
| Question | One validator signs | Quorum becomes | Layer |
|---|---|---|---|
| “Is this block package OK to chain?” | BlockVote on block_hash |
QC | Consensus |
| “Did we all run this wave the same way?” | Execution vote on receipt / outcome root | EC (execution certificate) | Execution |
| “Is cross-shard proof present & verified?” | StateProvision payloads + header/QC checks |
Tracked in ProvisionCoordinator (not one global cert) | Cross-shard |
FinalizedWave: QC proves quorum agreed on a block hash (ordering + header commitments). FinalizedWave in body.certificates proves execution finished for a wave — stapled on a block, often later than the block that first listed those txs. body.certificates is not a pile of QCs or provision blobs.
certificate_root sharp edge: leaves are each wave’s receipt_hash (outcome + event root), not wave ids or raw tx hashes. The header root is a summary; body.certificates carries the full FinalizedWave objects used to recompute state_root.
Teaching name vs code: course copy still says transaction certificate for “execution proof in the body.” In Rust that role is FinalizedWave (wraps WaveCertificate + receipts) — wave-granularity, not one cert per tx.
*_root is computedMost Merkle roots share one recipe: pad leaves to the next power of two with Hash::ZERO, pair siblings, parent = Blake3(left_bytes || right_bytes) via Hash::from_parts, repeat until one hash remains. Order matters — swap leaves, different root.
transaction_rootLeaves = tx hashes in hash-ascending block order (sorted by TxHash value — NOT submit time) 0 txs → ZERO 1 tx → root = that tx_hash 2 txs → root = Blake3(hash1_bytes || hash2_bytes) 3 txs → pad [h1,h2,h3,ZERO] → pair → pair → root
Hash-ascending = strict increasing TxHash order so every validator builds the same leaf list. Proposer sorts before packaging; peers reject unsorted bodies.
state_root — JMT replaystate_root = JMT(parent_state_root, apply(body.certificates)). Replay each FinalizedWave’s receipts against the state trie — not a Merkle over txs. Worked example (block 40 → 41).
| Field | Leaves | Empty |
|---|---|---|
certificate_root | Each wave’s receipt_hash | ZERO |
local_receipt_root | Per-tx consensus receipt hashes | ZERO |
provision_root | ProvisionHash per batch in body | ZERO |
provision_tx_roots | Per remote shard: Merkle over tx hashes needing that shard’s proof | Empty map |
state_root example — block 40 → block 41Walkthrough on one shard: what raw material goes in, how many hashes run, and how JMT differs from transaction_root. Toy key paths use 4 bits for drawing; production JMT uses ~256 levels.
| Block | What happened |
|---|---|
| 40 (committed) | Body had TxA, TxB. transaction_root(40) = Merkle over their hashes. Execution ran after commit. |
| 41 (proposed) | Leader packages new txs TxC, TxD and staples FinalizedWave₁ (outcomes for block 40’s wave). Both roots computed at propose time, before votes. |
parent_state_root = 0xaaa… (from committed block 40 header)
parent_block_hash = hash(block 40)
parent_qc = QC₄₀
body.transactions = [ TxC, TxD ] ← NEW inputs (transaction_root)
body.certificates = [ FinalizedWave₁ ] ← execution DONE for block 40
WaveCertificate (quorum ECs for wave ∅)
receipts:
StoredReceipt(TxA) → Succeeded + database_updates
StoredReceipt(TxB) → Failed (no writes)
Code path: build_proposal → prepare_block_commit(parent_state_root, certificates)
Merge database_updates from each receipt in body.certificates (simplified toy keys):
| Tx | Result | Writes |
|---|---|---|
| TxA | Success | K_alice balance 100 → 70; K_bob balance 50 → 80 |
| TxB | Failed | none |
k = 2 substate keys change. Real keys are (partition, sort_key) mapped into the JMT; same idea — sparse updates along key paths only.
transaction_root on block 41 (Merkle — same propose pass)leaves (hash-ascending): [ hash(TxC), hash(TxD) ]
ROOT_tx (= transaction_root for block 41)
/ \
hash(TxC) hash(TxD)
Blake3 hash ops: 1 pairing
K_alice = 1010 K_bob = 1101
Before (parent_state_root = 0xaaa…):
R₀ (0xaaa…)
/ \
(sparse — only occupied paths stored; rest = ∅ default hashes)
After apply FinalizedWave₁ (only Alice & Bob paths touched):
R₁ (= state_root for block 41)
/ \
... ...
path to 1010 → Alice = 70
path to 1101 → Bob = 80
You never materialize all leaf slots in a full tree. In this 4-bit toy, depth = 4 → only 2⁴ = 16 possible leaf positions — still only paths to 1010 and 1101 are stored. In production (~256-bit keys), depth ≈ 256 → 2²⁵⁶ possible paths — same sparse rule: touch only changed keys, ∅ elsewhere.
| Root | Input size (example) | Hash work (order of magnitude) |
|---|---|---|
transaction_root | 2 new txs | 1 Blake3 pairing |
state_root (toy depth 4) | 2 key writes | ≈ 2 × 4 = 8 rehashes on touched paths |
state_root (production depth ~256) | 2 key writes | ≈ 2 × 256 ≈ 512 rehashes (plus snapshot / cache I/O) |
certificate_root | 1 FinalizedWave | 1 leaf → root equals that receipt_hash (or one Merkle level if padded with others) |
Dominated by JMT replay, not tx Merkle — scales with # of writes × tree depth, not total accounts in the world.
BlockHeader {
height: 41
parent_block_hash, parent_qc (from block 40)
transaction_root = Merkle( hash(TxC), hash(TxD) ) ← inputs on THIS page
state_root = JMT(0xaaa…, apply FinalizedWave₁) ← ledger AFTER certs
certificate_root = Merkle( receipt_hash(FinalizedWave₁) )
waves = [ … ] ← wave ids for TxC/TxD run AFTER block 41 commits
}
Verify before BlockVote:
1. Recompute transaction_root from body.transactions
2. prepare_block_commit(parent_state_root, body.certificates) → state_root
3. Match → sign block_hash
Block 40 committed [TxA, TxB] → execute → FinalizedWave₁ ready
Block 41 proposal
parent_state_root + body.certificates ──► prepare_block_commit ──► state_root
body.transactions ──────────────────────► TransactionRoot::compute ──► transaction_root
│
▼
BlockHeader → block_hash → BlockVote → QC
transaction_root(41) covers TxC, TxD (future execution). state_root(41) updates from TxA, TxB outcomes in FinalizedWave₁. Certs usually describe earlier txs; new txs get a state_root bump on a later block when their wave finalizes.
Every *_root fingerprints this block only. History chains via parent_block_hash + parent_qc — not one global tx Merkle over mempool + proposals + finalized history.
transaction_roottransaction_root — Merkle root over the tx hashes listed in this block only, in block order (hash-ascending in hyperscale-rs).| Bucket | In a block yet? | In transaction_root? |
In this block’s state_root? |
|---|---|---|---|
| Mempool — not proposed | No | No | No |
| In proposed / committed block H | Yes | Yes — root(H) covers those tx hashes | Usually no — execution runs after commit |
| Executed — outcomes in a later block | Txs were in an earlier block | That earlier block’s root | Yes — via body.certificates on the block that applies them |
Leader packages body.transactions, computes roots, gossips proposal. Peers recompute roots before BlockVote. Until commit, not canonical — view change can swap tx set and transaction_root at the same height/round. Incremental packaging: when provisions or finalized waves land mid-height, the proposal latch can trigger a new proposal with updated provision_root / certs — not “one proposal forever.”
Follow one tx through layers — wall-clock submit time is irrelevant; block order is.
Mempool tx hash exists; no header root yet Block H proposed & committed transaction_root(H) = Merkle(…, hash(tx), …) BlockVote → QC → commit state_root(H) usually unchanged for this tx (certs describe OLDER work) After commit on H wave runs tx → Radix → execution votes → EC Block H+k (later) body.certificates includes FinalizedWave for that wave certificate_root(H+k) = Merkle(receipt_hash, …) state_root(H+k) updates ledger — tx outcome now on the whiteboard
Cross-shard: same tx may need provisions from remote shards first; finalization can span more than one block (source commit → provisions → execute → cert on a later page). Explorer “one logical tx” ≠ always one block height on every shard.
Hover dotted terms for glossary cards. Block anatomy cards in Phases 2–4 use the same block-field-* entries.
| Field | Layer | What it commits to | One-line purpose |
|---|---|---|---|
parent_qc |
Consensus | QC for parent block | Chains blocks: child carries proof parent was quorum-certified. |
parent_block_hash |
Consensus | Hash of parent block | “This page continues that previous page” (identity link, not vote proof). |
height / round |
Consensus | Slot index / attempt at that height | Page number on this shard; round bumps when leader stalls (timers). |
certificate_root |
Execution (summary) | Merkle over each FinalizedWave’s receipt_hash in body |
Fingerprint of which finished waves are listed — not the full proofs. |
local_receipt_root |
Execution (detail) | Per-tx receipt hashes for txs in wave certs | Finer per-tx delta commitment; often ZERO if unused. |
provision_root |
Cross-shard | Merkle over ProvisionHash of each batch in body.provisions |
Which cross-shard proof packages are included on this page. |
provision_tx_roots |
Cross-shard | BoundedBTreeMap<ShardId, ProvisionTxRoot> — one Merkle per remote shard over tx hashes on this page that need that shard’s proof |
Per other team: which txs on our page still need their signed proof. |
waves |
Execution | Wave ids assigned at commit for in-flight execution | Labels for grading bundles running after this page became official. |
transaction_root |
Consensus | Tx hash list in body | Ordered membership of user txs on this block. |
state_root |
Execution | State after applying body certificates | Whiteboard fingerprint after execution outcomes applied. |
| Body field | Purpose |
|---|---|
transactions | Full signed user txs; transaction_root commits to this list. |
certificates (FinalizedWave) | Full execution proofs for completed waves; used to recompute state_root. |
provisions | Full cross-shard proof batches; provision_root commits to their hashes. |
| One validator signs… | Aggregated into… | Layer | Purpose |
|---|---|---|---|
| BlockVote (“I accept this block”) | QuorumCertificate | Consensus | Quorum agreement on block hash before/at commit. |
| Execution vote / StateVoteBlock | ExecutionCertificate (EC) | Execution | Quorum agreement on wave execution outcome (receipt root) per shard. |
| (bundle) | FinalizedWave |
Execution | Wave done locally + remote shard coverage → stapled on a later block. |
StateProvision / Provisions |
Verified by ProvisionCoordinator | Cross-shard | Provable substate from a committed remote block so this shard can execute. |
Not one central issuer: every node collects votes and can build QC/EC locally; QCs mostly propagate via parent_qc on the next block. Per-shard height: each shard group advances its own height on commit — there is no single global “blockchain height” across all shards.
ProvisionCoordinator is a per-validator pipeline of cooperating sub-machines — not a single “provision map”:
state_root for proofs).StateProvision batches.Header provision_tx_roots is the static obligation map on a proposed page (“which txs on this block still need shard X’s proof”). provision_root is the Merkle over actual provision batches included in body.provisions once satisfied. No 2PC coordinator — each shard runs BFT + provisions independently.
Block proposed transaction_root → commits to txs IN this block state_root → claims state AFTER applying certs IN this block (from PRIOR execution) BlockVote → QC → commit parent_qc on next block → proof this block was quorum-agreed After commit txs → waves → Radix → execution votes → EC → FinalizedWave later block: body.certificates + certificate_root + updated state_root Cross-shard extra provision_tx_roots → which txs need remote proof provisions → verify → body.provisions + provision_root then wave can dispatch & finalize
| Confusion | Reality |
|---|---|
| QC vs transaction certificate | QC = block agreed; cert = execution result. |
transaction_root vs state_root | Inputs vs outcome of running prior work. |
certificate_root vs body.certificates | Header summary vs full proof objects. |
provision_root vs provision_tx_roots | Packages included vs which txs still need remote proof. |
| Commit block H vs certs in block H | Certs in H usually describe execution from earlier txs; state_root on H updates when those certs apply. |
| Commitment vs signature | Roots = what is in the package; votes/QC = who agreed to that block_hash. |
| One global tx Merkle over history + pending | No — each block has its own transaction_root; mempool and in-flight execution stay outside header roots until packaged. |
| Signature verify alone | Proves who attested to block_hash — separate steps recompute roots and replay execution. |
certificate_root leaves | receipt_hash per FinalizedWave — not wave id, not tx hash list. |
body.certificates contents | FinalizedWave execution proofs — not QCs, not raw provisions. |
| EC acronym | Execution certificate — quorum on a wave outcome; different message/domain from BlockVote. |
| Global block height | Per shard group — compare heights only within the same shard. |
| Cross-shard “done in one block” | Often N ≥ 1 blocks across shards (commit → provision → execute → cert). |
| ProvisionCoordinator | Multi-stage pipeline on each node — not one central map or 2PC coordinator. |
Transaction certificate vs WaveCertificate | Teaching = per-tx framing; code = WaveCertificate inside FinalizedWave. |
Sim livelock.rs vs live ConflictDetector | Post-run diagnostics vs runtime overlap breaking (Phase 4). |
parent_qcfinalize_wave, certificate_rootprovision_root, cross-shard finalization