Introduction to Ethereum blockchain.
Detail visualize/ simplify and explanation from Yellow paper. Thank you for the amazing illustration from Ethereum EVM Illustrated.
Quicknote: After The Merge → Ethereum will have 2 service working together. Execution Layer and consensus layer. Yellow paper is define most the the Execution layer part.
“Ethereum, taken as a whole, can be viewed as a transaction-based state machine” - Ethereum Yellow Paper
It begins with the first state - “genesis state” and transition state by state and become the current Ethereum chains.
The transactions = the VALID link between 2 states and include a lot of information.

We can present this formally by:
$$
\sigma_{t+1} = \Upsilon(\sigma_t,T)
$$
Where:
- $\Upsilon$ is the Ethereum state transition function. It represents the rules, the computing
- $\sigma$ is the state. $\sigma_t$ the Ethereum state at the specific time $t$
- $T$ is the transaction. E.g: An instruction from a user or a smart contract
Together this simple formula represent all the Ethereum power. Which $\Upsilon$ allows components to carry out computation, $\sigma$ allows components to store arbitrary state between transactions.

Block($B$) is the collection of transactions - a packages of data.
The chaining function($\prod$) is using a cryptographic hash - as a mean of reference transactions together with the previous block and an identifier for the final state.
$$
\sigma_{t+1} = \prod(\sigma_t, B)
$$
$$
B = (...,(T_0, T_1, ...),...)
$$
$$
\prod(\sigma, B) = \Upsilon(\Upsilon(\sigma, T_0), T_1)\ldots
$$
From the viewpoint of the states Ethereum can be seen as a state chain.

From the viewpoint of the implementation Ethereum can also be seen as a chain of blocks, so it is BLOCKCHAIN.

From the viewpoint of the ledger, Ethereum can also be seen as a stack of transactions
From $\Upsilon$ and $\sigma$ we can compute anything, but to limit and calculate the right amount of computational power within the network → there is a need for a common currency to evaluate the power.
Ethereum define an intrinsic currency - Ether - ETH along with other sub-denominations of Ether. Conversion: 1 Ether = $10^{18}$ Wei
| Multiplier |
Name |
| $10^0$ |
Wei |
| $10^9$ |
Gwei |
| $10^{12}$ |
Szabo |
| $10^{15}$ |
Finney |
| $10^{18}$ |
Ether |
Since the system is decentralised → anyone can create a new block from older existing block, and when there was disagreement between the part from root (genesis block) to the leaf (newest block), a fork is created → a new branch with a different rule.
A fork can be created on purpose by a developer when their disagree with the current state of the chain. Like the ETH classic and current ETH chains, or can be forked by developer to include and update new features.
The newest fork and what is blog is currently describe is the shanghai fork - start from block 17034870.
Furthermore we can read more at Ethereum Roadmap.
Occasionally actors do not agree on a protocol change, and a permanent fork occurs. In order to distinguish between diverged blockchains, chain ID is introduced and the main Ethereum network will have chain id $\beta = 1$.
Definition: the world state is a mapping from 160-bit addresses to account states. It is referenced through Ethereum’s trie structure rather than stored as one flat mapping on-chain.
A TRIE stores the global account state, and contract accounts may also have their own storage tries.

TRIE is an immutable data structure, so previous states can be referenced efficiently by their root hash.

Nonce: A scalar value equal to the number of transactions sent by an EOA or the number of contract creations performed by a contract. For account $a$ in state $\sigma$, denote it by $\sigma[a]_n$.
Balance: A scalar value equal to the number of wei held by the account. Denote it by $\sigma[a]_b$.
StorageRoot: A 256-bit hash of the root node of the trie that encodes the account’s storage contents. Denote it by $\sigma[a]_s$.
CodeHash: The hash of the EVM bytecode associated with this account. Denote it by $\sigma[a]_c$. If some byte sequence $b$ is the account code, then $KEC(b) = \sigma[a]_c$.
Here is the implementation in Go Ethereum (geth):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Account represents an Ethereum account and its attached data.
// This type is used to specify accounts in the genesis block state, and
// is also useful for JSON encoding/decoding of accounts.
type Account struct {
Code []byte `json:"code,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
Balance *big.Int `json:"balance" gencodec:"required"`
Nonce uint64 `json:"nonce,omitempty"`
}
// StateAccount is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
type StateAccount struct {
Nonce uint64
Balance *uint256.Int
Root common.Hash // merkle root of the storage trie
CodeHash []byte
} // Store on chain
|
In practice, we usually work with account mappings and code rather than manipulating trie roots directly, so we use the following equivalence:
$$
\operatorname{TRIE}(L_I^*(\sigma[a]_s)) \equiv \sigma[a]_s
$$
$$
L_I((k,v)) \equiv (KEC(k), RLP(v))
$$
where
$$
k \in \mathbb{B}_{32}, \qquad v \in \mathbb{N}
$$
About the codeHash field: if $\sigma[a]_c = KEC(())$, then the account has no deployed code, so it is a simple account rather than a contract account.
We define world-state collapse function $L_s$:
$$
L_s(\sigma) \equiv \lbrace p(a) : \sigma[a] \neq \varnothing \rbrace
$$
$$
p(a) \equiv (KEC(a), RLP((\sigma[a]_n, \sigma[a]_b, \sigma[a]_s, \sigma[a]_c)))
$$
$L_s$ is used together with the TRIE function to derive the identity, or root hash, of the world state.
$$
\forall a:\ \sigma[a] = \varnothing \vee (a \in \mathbb{B}_{20} \wedge v(\sigma[a]))
$$
$$
v(x) = x_n \in \mathbb{N}_{256} \wedge x_b \in \mathbb{N}_{256} \wedge x_s \in \mathbb{B}_{32} \wedge x_c \in \mathbb{B}_{32}
$$
An EMPTY account has no code, nonce 0, and balance 0.
$$
EMPTY(\sigma,a) \equiv \sigma[a]_c = KEC(()) \wedge \sigma[a]_n = 0 \wedge \sigma[a]_b = 0
$$
A DEAD account is either absent from the world state or empty.
$$
DEAD(\sigma,a)\equiv \sigma[a] = \varnothing \vee EMPTY(\sigma,a)
$$
|
EOA |
Contract Account |
| Nonce |
Yes |
Yes |
| Balance |
Yes |
Yes |
| StorageRoot |
Empty trie root |
Storage trie root |
| CodeHash |
Empty code hash by default; may be affected by EIP-7702 delegation semantics |
Hash of deployed contract bytecode |
Note: EIP-7702 allows EOAs to temporarily delegate execution semantics in a way that makes them behave more like programmable accounts.
Definition: a single cryptographically-signed instruction constructed by an actor externally to the scope of Ethereum. There are three transactions type and 2 subtypes of transactions: message call and creation of new accounts with code (contract creation)
| Fields: |
Meaning |
Symbol |
Transaction Type |
Boundaries |
| type |
Transaction type |
$T_x$ |
0,1,2 |
${0,1,2}$ |
| nonce |
Scalar - number of transaction by the sender. |
$T_n$ |
0,1,2 |
$\mathbb{N}_{256}$ |
| to |
160-bit address. Message call: recipient. Contract creation: empty value |
$T_t$ |
0,1,2 |
$\mathbb{B}$ or $\mathbb{B}_{20}$ |
| value |
Number of Wei. |
$T_v$ |
0,1,2 |
$\mathbb{N}_{256}$ |
| r,s |
Signature → determine the sender. |
$T_r$ and $T_s$ |
0,1,2 |
$\mathbb{N}_{256}$ |
| accessList |
List of access entries to warm up. Access entry: $E=(E_a,E_s)$. Touple of account address ,storage keys. |
$T_A$ |
1,2 |
|
| chainId |
Chain ID . Must be equal to the network chain ID $\beta$ |
$T_c$ |
1,2 |
$\beta$ |
| yParity |
Signature Y parity |
$T_y$ |
1,2 |
${0,1}$ |
| w |
Combine chainId, yParity. $T_w=27+T_y$ or $T_w = 2*\beta+35+T_y$ |
$T_w$ |
0 |
$\mathbb{N}_{256}$ |
| maxFeePerGas |
Scalar = maximum number of Wei to be paid per unit of gas for all computation costs. |
$T_m$ |
2 |
$\mathbb{N}_{256}$ |
| maxPriorityFeePerGas |
Scalar = the maximum number of Wei to be paid to the recipient as an incentive to include the transaction |
$T_f$ |
2 |
$\mathbb{N}_{256}$ |
| gasPrice |
Scalar = number of Wei pay per unit of gas |
$T_p$ |
0,1 |
$\mathbb{N}_{256}$ |
| init |
Deployment Code |
$T_i$ |
0,1,2. Contract creation transaction |
|
| data |
Calldata |
$T_d$ |
0,1,2. Message call transaction |
|
We assume all components are interpreted by the RLP as integer values, with the exception of the access list $T_A$ and the arbitrary length byte arrays $T_i$ and $T_d$.
Example of Transaction on the Test net:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
[
{
"hash": "0x7f1ae3e9953390a3a079b735d17685c7b52ead2ce343fd48ae0b00f48834f53d",
"transactionType": "CREATE",
"contractName": "X",
"contractAddress": "0x001c0b8f9b25aa4bcdf5abcde5f34cf2a6d697c4",
"function": null,
"arguments": [
"0x6d178640DCe98B57F95695264319A43C88A08c17"
],
"transaction": {
"from": "0x025c1db463adf52d59d2f95c4be24c4475d190fc",
"gas": "0x6616a",
"value": "0x0",
"input": "0x608060....(deployment + runtime bytecode) ... 08c17",
"nonce": "0xfb",
"chainId": "0xaa36a7"
},
"additionalContracts": [],
"isFixedGasLimit": false
},
{
"hash": "0x09d37c99ed770825d195c5b98bbfd6863fefe40cbee00cc3511f22485ea77215",
"transactionType": "CALL",
"contractName": "X",
"contractAddress": "0x001c0b8f9b25aa4bcdf5abcde5f34cf2a6d697c4",
"function": "Exploit()",
"arguments": [],
"transaction": {
"from": "0x025c1db463adf52d59d2f95c4be24c4475d190fc",
"to": "0x001c0b8f9b25aa4bcdf5abcde5f34cf2a6d697c4",
"gas": "0x1898f",
"value": "0x71afd498d0000",
"input": "0xb44ee64a",
"nonce": "0xfc",
"chainId": "0xaa36a7"
},
"additionalContracts": [],
"isFixedGasLimit": false
}
]
|
A withdrawal $W$ → a tuple of data describing a consensus layer validator’s withdrawal of some amount of its staked Ether. A withdrawal is created and validated in the consensus layer → push to the execution layer to proceed.
A withdrawal composes:
| Field |
Meaning |
Symbol |
Boundaries |
| globalindex |
Increase from 0 - unique identifier for the withdrawal |
$W_g$ |
$\mathbb{N}_{64}$ |
| validatorIndex |
index of the consensus layer validator |
$W_v$ |
$\mathbb{B}_{20}$ |
| recipient |
20-bytes address receive Ether from the withdrawal |
$W_r$ |
$\mathbb{N}_{64}$ |
| amount |
Scalar (≠0) = amount of Ether demoninated in Gwei($10^9$ Wei) |
$W_a$ |
$\mathbb{N}_{64}$ |
Withdrawal serialisation is defined as:
$$
L_W(W) \equiv (W_i, W_v, W_a, W_V)
$$
Quick note: after Ethereum merge, changing from PoW to PoS. Ethereum now have 2 different layer: the consensus layer and the execution layer. See Execution and Consensus Layers.
The block is the collection of information (block header) $H$ + Information of the transactions $T$ + $U$ (deprecated) + $W$ ← collection of validator’s withdrawal push from the consensus layer.
A Block $B$ formally define as:
$$
B \equiv (B_H, B_T, B_U, B_W)
$$
| Field |
Meaning |
Symbol |
Boundaries |
| parentHash |
$H_p =KEC(ParentBlockHeader)$ |
$H_p$ |
$\mathbb{B}_{32}$ |
| ommersHash |
256-bit hash field. Deprecated now = $KEC(RLP(()))$ |
$H_o$ |
$\mathbb{B}_{32}$ |
| beneficiary |
160-bit address to which priority fees from this block is transferred |
$H_c$ |
$\mathbb{B}_{20}$ |
| stateRoot |
256-bit hash of the root node of the state TRIE after all transactions exec and withdrawals - (it all about the WORLD STATES and accounts states) |
$H_r$ |
$\mathbb{B}_{32}$ |
| transactionsRoot |
256-bit hash root node of the TRIE populated with each transactions in the transactions list (all about the TRANSACTION) |
$H_t$ |
$\mathbb{B}_{32}$ |
| receiptsRoot |
256-bit hash root note of the TRIE populated with the receipts of each transaction in the transactions list |
$H_e$ |
$\mathbb{B}_{32}$ |
| logsBloom |
The Bloom filter composed from indexable information - log entries from receipts of transactions |
$H_b$ |
$\mathbb{B}_{256}$ |
| difficulty |
Scalar - deprecated - now equal 0 |
$H_d$ |
$\mathbb{N}$ |
| number |
Scalar = number of ancestor blocks. Genesis block has number = 0 |
$H_i$ |
$\mathbb{N}$ |
| gasLimit |
Scalar = limit of gas expenditure / block |
$H_l$ |
$\mathbb{N}$ |
| gasUsed |
Scalar = total gas used in transactions for the block |
$H_g$ |
$\mathbb{N}$ |
| timestamp |
Scalar = unix time at this block’s inception |
$H_s$ |
$\mathbb{N}_{256}$ |
| extraData |
Byte array contain data relevant to the block must be less than 32 bytes |
$H_x$ |
$\mathbb{B}$ |
| prevRandao |
lastest RANDAO mix of the post beacon state |
$H_a$ |
$\mathbb{B}_{32}$ |
| nonce |
64 bit value deprecated . Current value = 0x0...0 |
$H_n$ |
$\mathbb{B}_{8}$ |
| baseFeePerGas |
Scalar = base amount of Wei burned for each unit of gas consumed |
$H_f$ |
$\mathbb{N}$ |
| withdrawalsRoot |
256-bit hash root node of the TRIE populated with each withdrawal operations |
$H_w$ |
$\mathbb{B}_{32}$ |
Go struct for the block header:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// Header represents a block header in the Ethereum blockchain.
type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase common.Address `json:"miner"`
Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *big.Int `json:"difficulty" gencodec:"required"`
Number *big.Int `json:"number" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Time uint64 `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash"`
Nonce BlockNonce `json:"nonce"`
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
// WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers.
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`
// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`
// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
// SlotNumber was added by EIP-7843 and is ignored in legacy headers.
SlotNumber *uint64 `json:"slotNumber" rlp:"optional"`
}
|
Note: RANDAO = pseudorandom value gen by validators on the Ethereum consensus layer. See Ethereum Consensus Specs.
Because there may be useful information to form a zk proof, index and search. The receipt is encoded as $B_R[i]$ for the $i$ transaction (i-index in the array) → placed in an index-keyed TRIE → root as $H_e$.
We can view it in Geth:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// Receipt represents the results of a transaction.
type Receipt struct {
// Consensus fields: These fields are defined by the Yellow Paper
Type uint8 `json:"type,omitempty"`
PostState []byte `json:"root"`
Status uint64 `json:"status"`
CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"`
// Implementation fields: These fields are added by geth when processing a transaction.
TxHash common.Hash `json:"transactionHash" gencodec:"required"`
ContractAddress common.Address `json:"contractAddress"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
EffectiveGasPrice *big.Int `json:"effectiveGasPrice"` // required, but tag omitted for backwards compatibility
BlobGasUsed uint64 `json:"blobGasUsed,omitempty"`
BlobGasPrice *big.Int `json:"blobGasPrice,omitempty"`
// Inclusion information: These fields provide information about the inclusion of the
// transaction corresponding to this receipt.
BlockHash common.Hash `json:"blockHash,omitempty"`
BlockNumber *big.Int `json:"blockNumber,omitempty"`
TransactionIndex uint `json:"transactionIndex"`
}
|
Transaction Receipt $R$ have form:
$$
R \equiv (R_x, R_z, R_u, R_b, R_l)
$$
$$
L_R(R) \equiv (R_z, R_u, R_b, R_l)
$$
Where:
- $R_x$ = Type of transaction.
- $R_z$ = Status code.
0x00 for revert. 0x01 for succeed.
- $R_u$ = Cumulative gas used in the block immediately after the specific transaction finishes executing.
- $R_l$ = Set of logs created through execution.
- $R_b$ = Bloom filter.
$R_x$ equal to the type of the corresponding transaction. $L_R$ → transform into RLP-serialized byte array. $R_z$ must be non negative number
$$
\begin{aligned}
R_z &\in \mathbb{N} \\
R_u &\in \mathbb{N} \quad \wedge \quad R_b \in \mathbb{B}_{256}
\end{aligned}
$$
1
2
3
4
5
6
7
|
const (
LegacyTxType = 0x00
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x03
SetCodeTxType = 0x04
)
|
$R_l$ is the series of log entries. $(O_0,O_1,….)$.
Log entries $O=tuple(O_a,(O_{t0},O_{t1},…),O_d)$
- $O_a$ = logger address $O_a \in ,mathbb{B}_{20}$
- $O_t$ = log topics (can be null) $\forall x \in O_t : x \in \mathbb{B}_{32}$
- $O_d$ = number of bytes of data. $O_d \in \mathbb{B}$.
$R_b$ is the Bloom filter which is a 256-byte hash value. Detail in Appendix.
The receipts for the above transactions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
"receipts": [
{
"status": "0x1",
"cumulativeGasUsed": "0x78ac72",
"logs": [],
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"type": "0x2",
"transactionHash": "0x7f1ae3e9953390a3a079b735d17685c7b52ead2ce343fd48ae0b00f48834f53d",
"transactionIndex": "0x51",
"blockHash": "0x20eb35c61787030cb79f608745ce23e421331d1520c7b7c04421d530cf039ca7",
"blockNumber": "0x9b777c",
"gasUsed": "0x4e879",
"effectiveGasPrice": "0x3d09a0bf",
"from": "0x025c1db463adf52d59d2f95c4be24c4475d190fc",
"to": null,
"contractAddress": "0x001c0b8f9b25aa4bcdf5abcde5f34cf2a6d697c4"
},
{
"status": "0x1",
"cumulativeGasUsed": "0x79b98c",
"logs": [],
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"type": "0x2",
"transactionHash": "0x09d37c99ed770825d195c5b98bbfd6863fefe40cbee00cc3511f22485ea77215",
"transactionIndex": "0x52",
"blockHash": "0x20eb35c61787030cb79f608745ce23e421331d1520c7b7c04421d530cf039ca7",
"blockNumber": "0x9b777c",
"gasUsed": "0x10d1a",
"effectiveGasPrice": "0x3d09a0bf",
"from": "0x025c1db463adf52d59d2f95c4be24c4475d190fc",
"to": "0x001c0b8f9b25aa4bcdf5abcde5f34cf2a6d697c4",
"contractAddress": null
}
]
|
Must satisfied these constraints:
- Ommers field - $B_U$ must be empty array + Block Header -$B_H$ must be consistent with Transaction - $B_T$ and Withdrawal - $B_W$
- State Root - $H_r$ must match the resultant state after executing all transactions → all withdrawals in order from the based state $\sigma$
- $H_t,H_e,H_b,H_w$ - Transaction Root, receiptsRoot, logsBloom,withdrawalsRoot must be correctly derived from the transactions, transactions receipts, resulting logs, withdrawals respectively.
In formal definition:
$$
B_U \equiv ()
$$
$$
H_r \equiv TRIE(L_s(\prod(\sigma, B)))
$$
$$
H_t \equiv TRIE(\{\, \forall i < \lVert B_T \rVert,\ i \in \mathbb{N} : p_T(i, B_T[i]) \,\})
$$
$$
H_e \equiv TRIE(\{\, \forall i < \lVert B_R \rVert,\ i \in \mathbb{N} : p_R(i, B_R[i]) \,\})
$$
$$
H_w \equiv TRIE(\{\, \forall i < \lVert B_W \rVert,\ i \in \mathbb{N} : p_W(i, B_W[i]) \,\})
$$
$$
H_b \equiv \bigvee_{r \in B_R}(r_b)
$$
$p_T,p_R,p_W$ each will be the pairwise RLP transformations but with special case for EIP-2718.
Define $P(B_H)$ = Parent block of $B$:
$$
P(H) \equiv B' : KEC(RLP(B'_H)) = H_p
$$
Block number $H_i$ will be
$$
H_i \equiv P(H)_{H_i} + 1
$$
Starting with London release introduce baseFeePerGas $H_f$. $H_f$ = amount of wei burned per unit of gas consumed while executing transactions within the block. The function to calculate is define as $F(H)$.
- If current block = first block of the
London fork → base = 1000000000.
- If the parent gas limit $P(H)_{H_g}$ = the target gas ($\tau$) → the base fee unchanged.
- If $P(H)_{H_g}$ > ($\tau$) → the base fee increase by $v$.
- If $P(H)_{H_g}$ < ($\tau$) → the base fee decrease by $v$.
Where $\tau$ and $v$ define:
$$
\tau \equiv \left\lfloor \frac{P(H)_{H_l}}{2} \right\rfloor
$$
$$
v \equiv \max\left(1,\left\lfloor \frac{P(H)_{H_f}\bigl(P(H)_{H_g} - \tau\bigr)}{\tau \times 8} \right\rfloor\right)
$$
$$
v \equiv \left\lfloor \frac{P(H)_{H_f}\bigl(\tau - P(H)_{H_g}\bigr)}{\tau \times 8} \right\rfloor
$$
The concrete implementation is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
// CalcBaseFee calculates the basefee of the header.
func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
// If the current block is the first EIP-1559 block, return the InitialBaseFee.
if !config.IsLondon(parent.Number) {
return new(big.Int).SetUint64(params.InitialBaseFee)
}
parentGasTarget := parent.GasLimit / config.ElasticityMultiplier() //( /2 )
// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
if parent.GasUsed == parentGasTarget {
return new(big.Int).Set(parent.BaseFee)
}
var (
num = new(big.Int)
denom = new(big.Int)
)
if parent.GasUsed > parentGasTarget {
// If the parent block used more gas than its target, the baseFee should increase.
// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parent.GasUsed - parentGasTarget)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) // DIV by 8
if num.Cmp(common.Big1) < 0 {
return num.Add(parent.BaseFee, common.Big1)
}
return num.Add(parent.BaseFee, num)
} else {
// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
// max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parentGasTarget - parent.GasUsed)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator()))
baseFee := num.Sub(parent.BaseFee, num)
if baseFee.Cmp(common.Big0) < 0 {
baseFee = common.Big0
}
return baseFee
}
}
|
The canonical gas limit $H_l$ is defined to follow these constraints:
$$
P(H)_{H_{l'}} - \left\lfloor \frac{P(H)_{H_{l'}}}{1024} \right\rfloor
< H_l <
P(H)_{H_{l'}} + \left\lfloor \frac{P(H)_{H_{l'}}}{1024} \right\rfloor
$$
$$
H_l \geq 5000
$$
Where:
$$
P(H)_{H_{l'}} \equiv
\begin{cases}
P(H)_{H_l}\,\rho, & \text{if } H_i = F_{\text{London}} \\
P(H)_{H_l}, & \text{if } H_i > F_{\text{London}}
\end{cases}
$$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// CalcGasLimit computes the gas limit of the next block after parent. It aims
// to keep the baseline gas close to the provided target, and increase it towards
// the target if the baseline gas is lower.
func CalcGasLimit(parentGasLimit, desiredLimit uint64) uint64 {
delta := parentGasLimit/params.GasLimitBoundDivisor - 1
limit := parentGasLimit
if desiredLimit < params.MinGasLimit {
desiredLimit = params.MinGasLimit
}
// If we're outside our allowed gas range, we try to hone towards them
if limit < desiredLimit {
limit = parentGasLimit + delta
if limit > desiredLimit {
limit = desiredLimit
}
return limit
}
if limit > desiredLimit {
limit = parentGasLimit - delta
if limit < desiredLimit {
limit = desiredLimit
}
}
return limit
}
|
$H_s$ is the timestamp of block $H$ and must fulfill:
$$
H_s > P(H)_{H_s}
$$
prevRando must be determined using information from the Beacon Chain (Consensus Layer). We define as PREVRANDAO().
After define all this - How we define the validity function $V(H)$ → we can easily see that it must follow all the constraint of all function above.
Formally define:
$$
\begin{aligned}
V(H) \equiv \quad & H_g \le H_l \quad \wedge \\
& H_l < P(H)_{H_{l'}} + \left\lfloor \frac{P(H)_{H_{l'}}}{1024} \right\rfloor \quad \wedge \\
& H_l > P(H)_{H_{l'}} - \left\lfloor \frac{P(H)_{H_{l'}}}{1024} \right\rfloor \quad \wedge \\
& H_l \geqslant 5000 \quad \wedge \\
& H_s > P(H)_{H_s} \quad \wedge \\
& H_i = P(H)_{H_i} + 1 \quad \wedge \\
& \|H_x\| \le 32 \quad \wedge \\
& H_f = F(H) \quad \wedge \\
& H_o = \mathrm{KEC}(\mathrm{RLP}(())) \quad \wedge \\
& H_d = 0 \quad \wedge \\
& H_n = \mathtt{0x0000000000000000} \quad \wedge \\
& H_a = \mathrm{PREVRANDAO}()
\end{aligned}
$$
Example for a block on main Ethereum network:
Main Net
baseFeePerGas 148677693
difficulty 0
extraData 0x546974616e2028746974616e6275696c6465722e78797a29
gasLimit 60000000
gasUsed 11473415
hash 0xa8b200fb93f6b36a21e636ae6c33e043e7aeec7179f89c0d1d855508eab7962f
logsBloom 0x0c20c24bc9220d94372c004bc4700092210410010c01ec08c28910c824261c19002c1540a108284700125f418a2345a01e50316c1c016c230ae923102b20a2069802b900108a122a183e0d8c0145a2300840633694f3c92882180404a0a404a09483000a1a202a0015448c1801a6a99b9261482062829600a7c01690681b23000c208e184ca1e50901417100040e134245038e854300180e46b18744241c04324622142902b02059665311e818c85428008242840609c0008f68a2024ea838404a5cc8ae40825c103001434220e618c505be62675c2290700885140a430fb30382343c2100d0212080e210c8098092012d1fb0504d90006a040a2282b6003101
miner 0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97
mixHash 0x9c7e169d9ff3e26a1ef455cffe13929ff6bf9f227c4b34a0766d8fe8333fd38c
nonce 0x0000000000000000
number 24747612
parentHash 0xd4d99eca1069bf6282b5fe58de1f77e336c75ebf88fb2ef01dad4ceaad9d412b
parentBeaconRoot 0x6e646d848224b6cce1b3b84b3e6b7b3e3a87fcfa4e3f019b1d2bf589df088215
transactionsRoot 0x15c4eee221a497d9da0bb24ff391c5812b9886f95adf86813a719f40837ee969
receiptsRoot 0xc2cc226bc26433034643217303118667416c2509b3ff5e292c0c1f26bb912e99
sha3Uncles 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347
size 61503
stateRoot 0x5b4abbb7977b06f950b4a0cc6061713af7be6eb3bcdc7a3592c70f2e49c856fb
timestamp 1774598999 (Fri, 27 Mar 2026 08:09:59 +0000)
withdrawalsRoot 0x587c1fa4c5c79ed7b852a2d1b1245e2b34b8a5d8016e139c308b862806838639
totalDifficulty 0
blobGasUsed 1048576
excessBlobGas 184989393
requestsHash 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
transactions: [
0x518337cfa421a43fd32bffbdb9050b20a86ce026a351d49e1b1fea45df1316d8
... truncated
0xceaf8ed0ea814dda246f6117c5fcda5a30e7ecf58fc843ccaeaf038f4998b41a
0xdb8e1be01d704a5452fda2f0a1d3e87f2ae16138c8b79e1a307d0690eb541f7a
0x8849e9c2f64b2943354421e2704149b3d2add962f621de21ac9409a78a7e71f7
]
Note: for backward compatibility: mixHash $H_m$ is prevRandao $H_a$.
Transision to Proof of Stake remove the miner. But in the block miner in here is the feeRecipient and collect this:
- The Base Fee is burned and disappears completely. The
miner address gets none
- The Priority Fee (Tip) paid by users to get their transactions included faster goes directly to this
miner address.
- MEV (Maximal Extractable Value) payouts also go to this address. If a searcher or builder rearranged transactions to make a profit (like an arbitrage trade), they pay a cut to the validator, which lands here.
Recursive Length Prefix (RLP) serialization is used extensively in Ethereum. RLP standardizes the transfer of data between nodes in a space-efficient format. It is a serialization method for encoding arbitrarily structured binary data.
RLP have these rule:
- Positive integer → convert to shortest byte array (big-endian interpretation = integer) → encode as a string
- Single byte value in
0x00 - 0x7f → remain the same.
- String is
0-55 bytes long → RLP encoding = ( 0x80 + len(string) ).to_string()+ string → value of the first bytes [0x80,0xb7]
- String > 55 bytes → RLP encoding = (
0xb7 + len(to_binary(string))).to_string() + len(string) + string
- String > $2^{64}$ → not encoded.
- List: if total length of RLP(list_ele) ≤ 55 bytes → RLP=(
0xc0 + len(RLP(list_ele)) + RLP(list_ele)
- List: RLP(list_ele) > 55 bytes → RLP = (
0xc0 + len(to_binary(RLP(list_ele)))+ len(RLP(list_ele)) + RLP(list_ele)
RLP Encode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
def rlp_encode(input):
if isinstance(input,str):
if len(input) == 1 and ord(input) < 0x80:
return input
return encode_length(len(input), 0x80) + input
elif isinstance(input, list):
output = ''
for item in input:
output += rlp_encode(item)
return encode_length(len(output), 0xc0) + output
def encode_length(L, offset):
if L < 56:
return chr(L + offset)
elif L < 256**8:
BL = to_binary(L)
return chr(len(BL) + offset + 55) + BL
raise Exception("input too long")
def to_binary(x):
if x == 0:
return ''
return to_binary(int(x / 256)) + chr(x % 256)
|
RLP decode:
Reverse engineer the encode. Furthermore, see Ethereum RLP Definition.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
def rlp_decode(input):
if len(input) == 0:
return
output = ''
(offset, dataLen, type) = decode_length(input)
if type is str:
output = instantiate_str(substr(input, offset, dataLen))
elif type is list:
output = instantiate_list(substr(input, offset, dataLen))
output += rlp_decode(substr(input, offset + dataLen))
return output
def decode_length(input):
length = len(input)
if length == 0:
raise Exception("input is null")
prefix = ord(input[0])
if prefix <= 0x7f:
return (0, 1, str)
elif prefix <= 0xb7 and length > prefix - 0x80:
strLen = prefix - 0x80
return (1, strLen, str)
elif prefix <= 0xbf and length > prefix - 0xb7 and length > prefix - 0xb7 + to_integer(substr(input, 1, prefix - 0xb7)):
lenOfStrLen = prefix - 0xb7
strLen = to_integer(substr(input, 1, lenOfStrLen))
return (1 + lenOfStrLen, strLen, str)
elif prefix <= 0xf7 and length > prefix - 0xc0:
listLen = prefix - 0xc0;
return (1, listLen, list)
elif prefix <= 0xff and length > prefix - 0xf7 and length > prefix - 0xf7 + to_integer(substr(input, 1, prefix - 0xf7)):
lenOfListLen = prefix - 0xf7
listLen = to_integer(substr(input, 1, lenOfListLen))
return (1 + lenOfListLen, listLen, list)
raise Exception("input does not conform to RLP encoding form")
def to_integer(b):
length = len(b)
if length == 0:
raise Exception("input is null")
elif length == 1:
return ord(b[0])
return ord(substr(b, -1)) + to_integer(substr(b, 0, -1)) * 256
|
For Ethereum the curve is secp256k1. We denote the following: sk=private key, pk = public key
G is the base point for the Curve.
$$
\begin{aligned}
0 &\le sk \le \text{order} \\
pk &= sk \cdot G
\end{aligned}
$$
The Address of Wallet will be: keccak256(pk)[-40:] ← take the last 40 bytes.
The CheckSum - Encoding Rule can be find here: EIP-55
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
def checksum_encode(addr): # Takes a 20-byte binary address as input
hex_addr = addr.hex()
checksummed_buffer = ""
# Treat the hex address as ascii/utf-8 for keccak256 hashing
hashed_address = eth_utils.keccak(text=hex_addr).hex()
# Iterate over each character in the hex address
for nibble_index, character in enumerate(hex_addr):
if character in "0123456789":
# We can't upper-case the decimal digits
checksummed_buffer += character
elif character in "abcdef":
# Check if the corresponding hex digit (nibble) in the hash is 8 or higher
hashed_address_nibble = int(hashed_address[nibble_index], 16)
if hashed_address_nibble > 7:
checksummed_buffer += character.upper()
else:
checksummed_buffer += character
else:
raise eth_utils.ValidationError(
f"Unrecognized hex character {character!r} at position {nibble_index}"
)
return "0x" + checksummed_buffer
|

Bloom filter function $M$ → compress log into 256-byte hash
$$
M(O) \equiv \bigvee_{x \in \{O_a\} \cup O_t}(M_{3:2048}(x))
$$
$$
M_{3:2048}(x),\ x \in B,\ \equiv y,\ \text{where } y \in \mathbb{B}_{256}
$$
$$
y = (0,0,\ldots,0) \quad \text{except that}
$$
$$
\forall i \in \{0,2,4\}: \ B_{2047 - m(x,i)}(y) = 1
$$
$$
m(x,i) \equiv KEC(x)[i,i+1] \bmod 2048
$$
Detail in GETH code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
// at /core/types/bloom9.go:42
const (
// BloomByteLength represents the number of bytes used in a header log bloom.
BloomByteLength = 256
// BloomBitLength represents the number of bits used in a header log bloom.
BloomBitLength = 8 * BloomByteLength
)
// Bloom represents a 2048 bit bloom filter.
type Bloom [BloomByteLength]byte
// CreateBloom creates a bloom filter out of the give Receipt (+Logs)
func CreateBloom(receipt *Receipt) Bloom {
var (
bin Bloom
buf [6]byte
)
for _, log := range receipt.Logs { // THIS is the M(0) loop to OR
bin.AddWithBuffer(log.Address.Bytes(), &buf)
for _, b := range log.Topics {
bin.AddWithBuffer(b[:], &buf)
}
}
return bin
}
// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes)
func (b *Bloom) AddWithBuffer(d []byte, buf *[6]byte) {
i1, v1, i2, v2, i3, v3 := bloomValues(d, buf)
b[i1] |= v1
b[i2] |= v2
b[i3] |= v3
}
// bloomValues returns the bytes (index-value pairs) to set for the given data
// M3:20489(x)
// It calculates the keccak256 hash of the given address or topic, and then calculates which three bits of bloom need to be set to 1 based on the first six bytes of the hash.
func bloomValues(data []byte, hashbuf *[6]byte) (uint, byte, uint, byte, uint, byte) {
sha := hasherPool.Get().(crypto.KeccakState)
sha.Reset()
sha.Write(data)
sha.Read(hashbuf[:])
hasherPool.Put(sha)
// The actual bits to flip
v1 := byte(1 << (hashbuf[1] & 0x7))
v2 := byte(1 << (hashbuf[3] & 0x7))
v3 := byte(1 << (hashbuf[5] & 0x7))
// The indices for the bytes to OR in
i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[0:])&0x7ff)>>3) - 1
i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1
i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1
return i1, v1, i2, v2, i3, v3
}
|
- Ethereum Yellow Paper
- Ethereum EVM Illustrated
- Ethereum Roadmap
- Execution and Consensus Layers
- Ethereum Consensus Specs
- Ethereum RLP Definition
- EIP-55