Skip to main content

Purpose and Scope

This document describes the block and header structure, encoding formats, hash calculation, Merkle root construction, and WBFT-specific Extra data structures used in the StableNet blockchain.
It focuses on how blocks are composed and validated, and what data is included in the header during the consensus process.
Related documents:

Block Structure Overview

A block consists of a header containing metadata, a body containing transactions, and optional withdrawals and uncles lists.
In StableNet (WBFT-based), uncles are not used and are always empty.

Block Type

Key fields of the Block structure (core/types/block.go):
FieldTypeDescription
header*HeaderBlock header containing metadata and Merkle roots
transactionsTransactionsList of transactions included in the block
uncles[]*HeaderUncle block headers (not used)
withdrawalsWithdrawalsWithdrawals list (optional after Shanghai)
hashatomic.ValueCached block hash
sizeatomic.ValueCached RLP-encoded size
Blocks are treated as immutable objects after creation.
Internal data is copied during construction, and cached hash and size values never change afterward.

Header Structure

The Header is the core metadata of a block and contains cryptographic commitments to the block contents and execution results.

Core Header Fields

FieldTypeDescription
ParentHashcommon.HashParent block header hash
UncleHashcommon.HashUncle list hash (EmptyUncleHash in StableNet)
Coinbasecommon.AddressBlock fee recipient address (validator address)
Rootcommon.HashState trie root after block execution
TxHashcommon.HashTransaction Merkle root
ReceiptHashcommon.HashReceipt Merkle root
BloomBloomLog bloom filter
Difficulty*big.IntBlock difficulty (WBFT: always 1)
Number*big.IntBlock number
GasLimituint64Block gas limit
GasUseduint64Total gas used
Timeuint64Block timestamp
Extra[]byteWBFT consensus data (RLP-encoded)
MixDigestcommon.HashPoW field (unused in WBFT)
NonceBlockNoncePoW field (unused in WBFT)
StableNet specifics:
  • Difficulty is set to 1 to identify WBFT blocks
  • Coinbase is the validator’s consensus signing address and may differ from the operator address
  • Extra contains WBFT round data, aggregated signatures, gas tip, and epoch information
  • GasLimit is operated as a fixed value defined by chain parameters

Fork-Specific Optional Fields

The following fields exist only after specific forks and are included in RLP encoding only when non-nil.
FieldEIPForkDescription
BaseFeeEIP-1559LondonBlock base fee
WithdrawalsHashEIP-4895ShanghaiWithdrawals Merkle root
BlobGasUsedEIP-4844CancunBlob gas usage
ExcessBlobGasEIP-4844CancunExcess blob gas
ParentBeaconRootEIP-4788CancunBeacon chain parent root

Body Structure

The block body contains executable data:
  • Transactions: Transaction list
  • Uncles: Uncle headers (not used)
  • Withdrawals: Withdrawals list (after the relevant fork)

RLP Encoding

Both blocks and headers use Recursive Length Prefix (RLP) encoding.

Block Encoding

A block is encoded as the following RLP list:
[header, transactions, uncles, withdrawals]
For pre-Shanghai blocks, the withdrawals element is omitted.

Header Encoding

Headers are RLP-encoded in the defined field order.
Optional fields are included only when present.

Block and Header Hashing

Standard Hashing

The block hash is calculated only from the header.
block_hash = keccak256(rlp(header))
The computed hash is cached to avoid repeated calculation.

WBFT-Specific Header Hashing

In WBFT, consensus signatures are included in the Extra field.
Therefore, signatures must be excluded from the hash used for signing.
This is handled by WBFTFilteredHeader():
  • Removes PreparedSeal and CommittedSeal
  • Preserves round number, epoch info, and gas tip
  • Applied only when Difficulty == 1
This prevents circular dependencies between signatures and hashes.

Round-Dependent Hashes

During WBFT consensus rounds, a round-specific hash is used.
Even for the same block proposal, different rounds result in different signing hashes.

WBFT Extra Data Structure

WBFT consensus data is stored in the header’s Extra field as RLP-encoded data.

WBFTExtra Structure

FieldTypeDescription
VanityData[]byteVanity data
RandaoReveal[]byteBLS signature for randomness
PrevRounduint32Previous consensus round
PrevPreparedSeal*WBFTAggregatedSealPrevious prepare aggregated seal
PrevCommittedSeal*WBFTAggregatedSealPrevious commit aggregated seal
Rounduint32Current consensus round
PreparedSeal*WBFTAggregatedSealCurrent prepare aggregated seal
CommittedSeal*WBFTAggregatedSealCurrent commit aggregated seal
GasTip*big.IntGovernance-defined gas tip
EpochInfo*EpochInfoNext epoch info (epoch blocks only)
Fields with nil values are excluded from RLP encoding.

WBFTAggregatedSeal

Represents a BLS-aggregated signature from multiple validators.
FieldTypeDescription
SealersSealerSetBitmap of signing validators
Signature[]byteAggregated BLS signature

EpochInfo

Included only in epoch blocks and defines the validator set for the next epoch.
FieldTypeDescription
Candidates[]*CandidateAll validator candidates
Validators[]uint32Active validator indices
BLSPublicKeys[][]byteValidator BLS public keys

Merkle Root Calculation

TxHash

Calculated by building a Merkle Patricia Trie from the transaction list.
An empty list uses EmptyTxsHash.

ReceiptHash

Calculated in the same way from the receipt list.
The bloom filter is derived from receipts.

UncleHash

The hash of the uncle header list.
In StableNet, this is always EmptyUncleHash.

WithdrawalsHash

Calculated from the withdrawals list in post-Shanghai blocks.

Block Construction

NewBlock Function

NewBlock() constructs a block using the following steps:
  1. Copy the header
  2. Compute the transaction Merkle root
  3. Compute the receipt root and bloom
  4. Compute the uncle hash
  5. Assemble the body data
For WBFT blocks:
  • Difficulty = 1
  • Extra contains the encoded WBFTExtra
  • EpochInfo is included only in epoch blocks
After Shanghai, NewBlockWithWithdrawals() is used.

Header Sanity Checks

Header.SanityCheck() prevents DoS attacks caused by malformed inputs.
FieldConstraint
Number64-bit range
DifficultyReasonable bit length
ExtraMaximum size limit
BaseFeeuint256 range

Block Size Calculation

The block size is the total RLP-encoded size and can be queried via Block.Size().
If blob transactions are included, sidecar data is also counted toward the size.