Sharding (the Braid)
Since version 2.0 (“Wee Big Shards”, SIP-5, active on mainnet since block 211,600 in May 2022) the Snowblossom chain is not one chain but a binary tree of chains called the Braid. A shard splits deterministically into two children when its blocks have been large enough for long enough; the children keep importing each other's blocks, value moves between them through committed root hashes, and the total reward per height stays the same however many shards exist. On mainnet only shard 0 is active so far; the public testnet has split into shards 1 and 2 and demonstrates the mechanism.
Shard ids
getMaxShardId = 62): up to 32 leaf shards. Testnet allows 0–512, the shard test networks 0–6 or 0–30.Helper functions in ShardUtil:
- generation(s)
- depth in the tree: 0 for shard 0, 1 for shards 1–2, 2 for 3–6, …
- inherit set(s)
- {s} plus, while the id is odd, its parent: inherit(1) = {0,1}, inherit(2) = {2}, inherit(3) = {0,1,3}, inherit(5) = {2,5}. The left (odd) child inherits everything that belonged to its parent.
- cover set(s)
- inherit(s) ∪ all descendants of s up to the max id. Outputs whose
target_shardis in the cover set live in shard s's UTXO. cover(0) is everything; cover(1) = {0,1,3,4,7…10,15…22,31…46}; cover(2) = {2,5,6,11…14,23…30,47…62}. - coordinator
- a shard whose inherit set contains 0: 0, 1, 3, 7, 15, 31, 63, … (2k−1). Exactly one coordinator is active at any time: the left-most leaf.
When a shard splits
ShardUtil.shardSplit(summary) is true - meaning “this block is the last of its shard and the next blocks must be in the two children” - when all of:
- the header is version 2;
tx_size_average > getShardForkThreshold()- mainnet 1,900,000 bytes (half the 3.8 MB max block), testnet 4,000,000;shard_length ≥ getMinShardLength()= 144 blocks (the shard has existed for a day);- the right child id ≤ max shard id (the shard is not a leaf).
tx_size_average is an exponential moving average (1 % weight per block on mainnet) of Σ(inner_data.size + 32) over the block's transactions, reset to 0 when a shard is born; shard_length counts blocks since the split. The decision depends only on the parent's last block summary, so it is deterministic and enforced: a block after a split-triggering block must carry a child id (“Must split”), and otherwise must keep the parent's id. Shards never merge - there is no code path for it.
The first blocks of the children
- Both children continue the parent's height numbering (
height = parent_last + 1) and both point at the parent's last block withprev_block_hash. Their coinbase carries the child'sshard_id. - UTXO: the left child (odd id) inherits the parent's entire UTXO root; the right child (even id) starts from an empty trie. Coins do not move at a split - outputs targeted at an ancestor shard are now spendable in the left lineage, and the right lineage fills with coinbases and outputs explicitly targeted at it.
- Difficulty: both children start at half the parent's difficulty (target × 2, the target average is doubled too); each then adjusts independently toward a 10-minute block time, so N leaf shards produce about N blocks per 10 minutes.
- Averages:
tx_size_averageandshard_lengthrestart at 0; block-time average carries over.
Keeping the braid together
Every v2 block imports the recent blocks of every other active shard: shard_import in the header lists them (shard → height → hash) and Block.imported_blocks carries their headers and exported outputs. Validation.checkShardBasics enforces:
- Ordering:
imported_blocksmust be exactly the header's import map flattened in ascending (shard, height) order, each header matching. - Validity: every imported header passes the full header checks including its proof of work.
- Continuity (“shard friends”): starting from the previous summary's known head per shard, each imported block must chain from that shard's current head (or, for a shard not yet seen, from its parent shard's head - this is how a new child is first accepted), height + 1, correct
prev_block_hash. A shard cannot be imported while one of its children is already known. - Braid completeness: recursively from shard 0, every shard must either have both children covered or be present with
build_height − its_height ≤ getMaxShardSkewHeight() = 6. In words: when you build block 1000, every other active shard must be imported at least up to height 994 (“Incomplete or old braid” otherwise). - Collisions: across the previous block's 30-entry history map, this header, and all imported headers and their own import maps, a (shard, height) pair must map to a single hash (“Block collision”). All shards are thereby forced onto one consistent braid.
- A shard's own cover set may appear neither in its export map nor in its import map.
The coordinator and “the Dance”
Cross-shard imports could conflict: shard 2 might import shard 5's block X while shard 1 imports a competing X′. The braid resolves this by giving one shard - the coordinator, the active shard on the 0-1-3-7 lineage - the role of ordering. Rules (in Dancer and BlockchainUtil.isBetter):
- A coordinator block may only import blocks that are themselves compliant.
- A non-coordinator block is compliant only if the highest coordinator block it sees is compliant and every block it imports lies in the chain of what that coordinator has already imported for that shard - non-coordinators follow the coordinator's choices.
- Head selection in a non-coordinator shard prefers the candidate that imports the higher coordinator block before looking at work.
Compliance is a block-building heuristic; the consensus rules are the ones in the previous section. Nodes build templates through ShardBlockForge, which every 15 s explores “block concepts” - for each shard, candidate next blocks importing the newest compliant foreign blocks - and prefers concepts that advance the shortest shard (BlockTemplate.advances_shard). MrPlow asks several nodes for templates and picks the one with the best reward per hash (see MrPlow).
Cross-shard transfers
- A transaction spends inputs from one shard only (they must be in the building shard's UTXO) and may send outputs anywhere via
target_shard. Fees stay with the shard that mined the transaction. - When the destination is in the shard's own cover set the output goes straight into its UTXO; otherwise it is collected into a small in-memory trie per target shard whose root becomes
shard_export_root_hash[target]. Validators recompute these tries and compare roots. - The importing block carries
ImportedBlock{header, import_outputs{shard → [raw_output, tx_id, out_idx]}}; for every shard in its cover set for which the source header has an export root, the list must be present, must hash to that root and every output'starget_shardmust match. Then the outputs are added to the importer's UTXO under their original keys. - Target does not exist yet (e.g.
target_shard = 7while only shard 0 exists): 7 is in cover(0), so the output simply lives in shard 0 - and later in 1, then 3 - until 7 is real. Existing UTXOs are never migrated. - Target is an ancestor (
target_shard = 0after the split - what every ordinary wallet produces): 0 is in inherit(1) but not in cover(2), so a shard-2 block exports it and the coordinator lineage imports it. Wallets that never set a target therefore keep all their coins on the 0-1-3-7 lineage, which is why today's wallets keep working unchanged after a split. - Node feature gap: the wallet cannot yet assemble a payment from inputs spread over several shards (“Split outputs required but not implemented”); balances are aggregated across shards, but a single send must be covered by one shard.
Rewards across shards
ShardUtil.getBlockReward divides the base reward R(height) so that the total per height never changes (math in 128-bit fixed point, rounding once). For a block in a shard of generation g:
direct = (3/4) · R(h) / 2^g
indirect = (1/4) · R(h) / 4^g
imports = Σ over imported blocks (shard i at height h_i): (1/4) · R(h_i) / 2^(g + generation(i))
coinbase = direct + indirect + imports + fees
Checks: one shard (g = 0, nothing to import) gets ¾R + ¼R = R. Two shards importing each other at the same height get ⅜R + 1/16R + 1/16R = ½R each. Four leaves get ¼R each. Unbalanced trees work out because each block of generation g carries weight 2−g (ShardUtilTest verifies the grand total equals ΣR). The quarter that is paid through imports is the incentive to import other shards promptly. Per-block work likewise includes the work of imported blocks.
Node operation with shards
shards=3,4innode.confrestricts a node to those shards; its interest set is each listed shard's cover set plus all of their ancestors (needed to find the entry point). Without the setting a node follows shard 0 and everything below it - the whole network - so no configuration change was required at activation.- The node opens a
BlockIngestor+ mempool per shard it has a head for, and opens children automatically when it validates a splitting block. A shard stops being a “building” shard once both children are active. NodeStatusexposesnetwork_active_shards,net_shard_head_map(network-wide heads),shard_head_map(local heads of building shards) andinterest_shards.head_summaryandGetFeeEstimate.fee_per_bytestill refer to shard 0; use the maps after a split.GetTransactionStatusonly consults shard 0's index.GetUTXONodeMultireturns the UTXO of every building shard.- Peers advertise their interest set in
PeerInfo.shard_id_set; the node tries to keep a few connections per shard (interest_peer_count, default 4) and gossips a tip per shard. - A node that does not validate some shard still needs that shard's
ImportedBlocks to validate its own imports. It accepts them only if the block hash was announced in a tip signed by a configuredtrustnet_signerskey - the trust network. A trustless replacement (UTXO proofs of foreign blocks from their headers) is sketched inshard-notes.mdbut not implemented;tx_data_size_sumwas added to the header so header-only nodes can at least recompute split decisions.
Quick reference
| Parameter | Mainnet | Testnet | testshard / demoshard / regshard |
|---|---|---|---|
| Activation height (header v2) | 211,600 (15 May 2022) | 170,000 | 10 |
| Max shard id | 62 (32 leaves) | 512 | 30 / 6 / 6 |
| Fork threshold (EMA tx bytes) | 1,900,000 | 4,000,000 | 2,000 / 2,000 / 0 |
| Min shard length | 144 blocks | 144 | 10 |
| Max skew | 6 blocks | 6 | 6 |
| Average weight (per mille) | 10 | 10 | 100 |