Snowblossom Docs
Docs › Protocol › Blocks & consensus

Blocks & consensus

This page describes the blockchain itself: what a block contains, how its hash and merkle roots are computed, how the target adjusts after every block, how much a block pays and how nodes pick the best chain. The rules are implemented in lib/src/Validation.java, PowUtil.java, BlockchainUtil.java and ShardUtil.java; the proof-of-work function itself has its own page.

Units, supply and block reward

The unit is the SNOW; the protocol counts in flakes, with 1 SNOW = 1,000,000 flakes (Globals.SNOW_VALUE). Every value in a transaction, fee or coinbase is a 64-bit integer of flakes.

The base reward is computed by PowUtil.getBlockReward (“half every four years as per SIP-1”):

blocks_per_day    = 86400 / 600          = 144
blocks_four_years = 144 × 365 × 4        = 210,240
reward(height)    = 50 SNOW  >> (height ÷ 210,240)     // integer halving
EraHeightsRewardReached
10 – 210,23950 SNOW22 May 2018 (launch)
2210,240 – 420,47925 SNOW6 May 2022
3420,480 – 630,71912.5 SNOW12 May 2026 (current)
4630,720 – 840,9596.25 SNOW≈ 2030
Integer halving reaches 0 flakes after 26 eras; total issuance converges to just under 21,024,000 SNOW. There is no explicit supply constant in the code - the cap follows from the schedule. Circulating supply today: ≈ 15.9 M SNOW.

Since sharding (header version 2) the coinbase amount is computed by ShardUtil.getBlockReward, which divides the same reward among shards; for a single shard 0 it equals the base reward exactly. The coinbase must pay exactly reward + sum of fees - not less. See Sharding → rewards.

SIP-1 matters for history: at launch the halving interval was mistakenly one year; the vote to fix it to four years passed and was implemented in 1.1.2 before the first halving (see SIP-1).

The block header

A Block is a BlockHeader, a list of Transactions (the first is the coinbase) and, since version 2, a list of ImportedBlocks. The header fields:

#FieldSizeMeaning
1versionint321 before block 211,600, 2 from then on (mainnet). Both required and forbidden on the wrong side of the activation height.
2block_heightint32Previous height + 1; 0 for the genesis block.
3prev_block_hash32 BHash of the previous block (all zeros for genesis).
4merkle_root_hash32 BMerkle root of the transaction hashes (below).
5utxo_root_hash32 BRoot of the UTXO hashed trie after applying this block. This is what lets light clients verify balances - see the UTXO trie.
6nonce12 BExactly 12 bytes, chosen freely by the miner.
7timestampint64Milliseconds since the epoch. Must be strictly greater than the previous block's and at most 45 s in the future.
8target32 BBig-endian unsigned integer; the block hash must be below it. Not miner-chosen: it must equal the value the difficulty algorithm derives from the previous block and this timestamp, byte for byte.
9snow_fieldint32Index of the snow field used. Must be ≥ the field activated on the chain; larger is allowed.
10snow_hash32 BOutput of the proof of work = the block hash, used everywhere a block is referenced.
11pow_proof6 ×One SnowPowProof per PoW pass: the 16-byte word read plus its merkle path to the field root.
12shard_idint32v2: which shard this block belongs to (0 in v1).
13shard_export_root_hashmapv2: destination shard → root hash of a trie holding this block's outputs that target that shard.
14shard_importmapv2: shard → (height → block hash) of the foreign blocks this block imports.
15tx_data_size_sumint32v2: Σ over transactions of inner_data.size + 32; feeds the shard-split decision.
16tx_countint32v2: number of transactions including the coinbase.

What gets hashed

PowUtil.hashHeaderBits feeds a Skein-256-256 digest with, in this order:

  1. nonce (12 bytes)
  2. 20 bytes big-endian: version (4), block_height (4), timestamp (8), snow_field (4)
  3. prev_block_hash, merkle_root_hash, utxo_root_hash, target (32 bytes each)
  4. if version == 2: shard_id, tx_data_size_sum, tx_count (4 bytes each); then every export entry as shard id (4) + root (32); then every import entry, sorted by shard then height, as shard (4) + height (4) + hash (32).

The result is the starting context of the Stoat proof of work; six field lookups later the final context is snow_hash. Note that snow_hash and pow_proof are not themselves hashed - they are outputs.

Hash functions

PurposeAlgorithmOutput
Block hash, PoW context, transaction id, transaction merkle treeSkein-256-25632 bytes
Snow-field merkle tree and PoW proofsSkein-256-12816 bytes
Address (hash of an AddressSpec)Skein-256-16020 bytes
UTXO trie and chain-index trie nodesSHA-25632 bytes
Snow field generator seedSkein-1024-1024128 bytes

All are provided by BouncyCastle, registered once at start-up (Globals.addCryptoProvider).

Transactions in a block and the merkle root

  • A block has at least one transaction. Transaction 0 must be the coinbase; all others must not be.
  • The transaction id is Skein-256-256(inner_data); signatures are outside the hashed bytes (see Transactions).
  • DigestUtil.getMerkleRootForTxList: take the list of transaction ids; at each level hash adjacent pairs as Skein-256-256(left ‖ right); an odd trailing element is carried up unchanged (not duplicated as in Bitcoin). Repeat until one 32-byte value remains.
  • Coinbase rules: is_coinbase, no inputs, no signatures, fee = 0, remarks ≤ 100 bytes; coinbase_extras.block_height and .shard_id must equal the header's (this guarantees every coinbase is unique). Block 0's remark must start with the network's “block zero remark” - on mainnet the hash of Bitcoin block 523,850, 00000000000000000019d1562bd02674302db7ddd6ccdb77be3e6daaa8eb8a51, which is how the launch was made fair: nobody could mine before that Bitcoin block existed.
  • The coinbase outputs must sum to exactly reward + Σ fees. Miner pools split this among many outputs (direct payouts).
  • Coinbase extras also carry motions_approved / motions_rejected - the vote signalling - and free-form remarks (pool names, messages). No consensus rule reads the motions.

Target, difficulty and work

The target is a 256-bit number. A block is valid when its hash, read as a big-endian unsigned number, is strictly less than the target. For humans the code shows a logarithmic difficulty:

target(diff) = 2^(256 − diff)            // BlockchainUtil.getTargetForDiff
diff(target)  = 256 − log2(target)        // PowUtil.getDiffForTarget (display only)
expected hashes per block = 2^diff

So difficulty 38 needs on average 238 ≈ 2.7 × 1011 attempts, and +1 means twice the work. Mainnet's easiest allowed target is difficulty 25 (getMaxTarget); testnet starts at 22.

Expected hashes per block
-
Network hash rate at that block time
-
Target (hex, leading bytes)
-

Work and chain weight

Each block adds to the chain's cumulative work_sum (BlockchainUtil.getWorkForSummary):

work  = maxTarget × 1024 / target                 // own block
      + Σ maxTarget × 1024 / import_target        // every imported foreign block (v2)
work *= 4^activated_field                          // SIP-2 weighting
work_sum = prev.work_sum + work

The 4field factor (SIP-2) makes a chain that has activated a larger snow field always outweigh one that has not, which closes an attack where a miner keeps a private fork on the old field during a “snow storm”.

Difficulty adjustment

There is no retarget window. Every block's target is derived from two exponentially weighted running averages carried in the previous block's BlockSummary: blocktime_average_ms and target_average. With weight = 10 per mille on mainnet (100 on the small shard test networks) and decay = 1000 − weight, PowUtil.calcNextTarget computes:

delta_t          = new_block.timestamp − prev.timestamp
averaged_delta_t = (prev.blocktime_average × decay + delta_t × weight) / 1000
scale            = averaged_delta_t × 1000 / 600000 − 1000    // per-mille deviation from 10 min
scale            = clamp(scale / 2, −500, +500)                 // at most ±50 % per block
new_target       = prev.target_average + prev.target_average × scale / 1000
if the previous block triggers a shard split: new_target ×= 2    // children start at half difficulty
new_target       = min(new_target, maxTarget)

Because the new block's own timestamp enters the formula, the target keeps easing while no block is found - the longer the wait, the easier the next block, which makes recovery from hash-rate drops smooth. Validators recompute the target from the previous summary and demand byte-exact equality with the header. After the block is accepted its summary updates the averages:

block_time           = timestamp − prev.timestamp
blocktime_average_ms = (prev_average × decay + block_time × weight) / 1000
target_average       = (prev_target_average × decay + target × weight) / 1000

The target average also decides snow field activation: when it drops to or below the next field's activation target, activated_field increments, permanently.

Timestamps

  • A header's timestamp may be at most maxClockSkewMs = 45,000 ms in the future relative to the validating node's clock (“Block too far into future”).
  • It must be strictly greater than the previous block's timestamp.
  • Nodes check their clock against https://timecheck.snowblossom.org/time every 5 minutes and warn when off by more than 5 s. Keep NTP running on nodes and miners.

Validation, step by step

Two layers: checkBlockBasics needs nothing but the block and the network parameters; deepBlockValidation needs the database (previous summary, UTXO trie).

Stateless checks (checkBlockBasics)

  1. Version 1 or 2, matching the activation height; all hash fields 32 bytes, nonce 12, target 32.
  2. Timestamp not more than 45 s in the future.
  3. snow_field known to the network.
  4. Exactly six PoW proofs, each a valid merkle path to the field's root; the recomputed six-pass chain reproduces snow_hash; snow_hash < target.
  5. v1: shard_id = 0, no export/import maps. v2: export keys outside the shard's own cover set, import keys likewise, tx_data_size_sum > 0, tx_count > 0.
  6. At least one transaction; transaction 0 is a valid coinbase, the others valid non-coinbase (stateless transaction checks); the merkle root matches.

Stateful checks (deepBlockValidation)

  1. target equals calcNextTarget(prev_summary, timestamp) exactly.
  2. snow_field ≥ prev_summary.activated_field.
  3. Height = previous + 1 and timestamp strictly increasing (genesis: zero prev hash and height 0).
  4. Coinbase extras match height and shard; block-0 remark rule.
  5. v2: shard rules - forced split, import ordering, chain continuity of imports, braid completeness within 6 blocks, no (shard, height) collisions; imported outputs are added to the UTXO view; a right-hand child shard starts from an empty UTXO.
  6. Every transaction is applied against the UTXO view: inputs must exist, be spendable (time/height locks), match declared values (SIP-4), use signature types allowed at this height (SIP-6); inputs = outputs + fee.
  7. v2: the computed export tries' roots equal shard_export_root_hash; tx_data_size_sum and tx_count match.
  8. Coinbase outputs = ShardUtil.getBlockReward + Σ fees.
  9. The resulting UTXO root equals utxo_root_hash (UtxoUpdateBuffer.commitIfEqual - the trie is only written if it matches).
Block size is not a consensus rulegetMaxBlockSize() (3,800,000 bytes on mainnet, 8,000,000 on testnet) is used when building templates and to size gRPC messages (max block + 1,000,000 bytes), which is the practical ceiling. A single transaction is limited to 1,000,000 bytes by consensus.

Chain selection and reorganisations

BlockchainUtil.isBetter(current, candidate) decides whether a newly validated block becomes the head:

  1. No head yet → the candidate wins.
  2. If the shard is not a coordinator and both are in the same shard: the one that imports the higher coordinator block wins (see the coordinator).
  3. Higher work_sum wins.
  4. Ties: the older timestamp, then the lexicographically smaller hash.

Reorganisations are cheap by design. The UTXO set is a persistent hashed trie: every validated block - even one on a side chain - gets its own summary and its own UTXO root, nothing is ever overwritten. Switching heads is a pointer change plus a rewrite of the height → hash index back to the fork point (BlockIngestor.updateHeights), after which the mempool is re-evaluated against the new UTXO root and the new tip is gossiped to peers. There are no checkpoints; the chain is anchored only by work (and the SIP-2 field weighting).

Block summaries

Alongside every block the node stores a BlockSummary (local, not part of the protocol): work_sum, blocktime_average_ms, target_average, activated_field, total_transactions, the root of the chain-index trie (address/tx indexes), and for v2 the per-shard imported_shards heads, tx_size_average, shard_length, and a short shard_history_map. Summaries have a summary_version (currently 6); a node started with older summaries recomputes them at start-up (“Reindexing”), which is the one-time rebuild mentioned in the 2.0 upgrade notes. The summary is written last, after the block, its transactions and the UTXO nodes, so its presence is the commit marker that a block is completely stored.

Genesis

There is no hard-coded genesis hash. Block 0 is any block with a zero previous hash, height 0 and the correct remark prefix; its target is the network maximum. Mainnet's block 0 is 00000023…b664b7, mined on 22 May 2018 on field 0 (“cricket”). A fresh network (regtest) simply starts by mining a block 0.