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

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.

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

Fields with nil values are excluded from RLP encoding.

WBFTAggregatedSeal

Represents a BLS-aggregated signature from multiple validators.

EpochInfo

Included only in epoch blocks and defines the validator set for the next epoch.

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.

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.