EEVM.Block (eevm v0.1.0)

Copy Markdown View Source

A full execution-layer block — header, transactions, ommers, and withdrawals — and the public path to build, encode, validate, and execute one.

  • decode/1 and encode/1 convert to and from canonical RLP.
  • validate/3 runs every check that needs no state: the EIP-7934 size limit, EEVM.Block.HeaderValidator against the parent, and the body against the header's commitments.
  • execute/4 validates the block, applies it to a database (system calls, each transaction through EEVM.Handler, withdrawals), and checks the header's gas_used, logs_bloom, receipts_root, and state_root against the result.
  • build/4 produces a child block of a parent: it derives the header, executes the transactions, fills in every commitment, and hashes it.

Options follow EEVM.Handler.execute/4: :hardfork (default :cancun) or a full :config, and :chain_id (default 1). :block_hashes maps recent block numbers to their hashes for BLOCKHASH.

Errors are {:error, reason} with the reasons catalogued in EEVM.Error as EEVM.Error.block_error/0.

Example

iex> alias EEVM.Block
iex> parent = EEVM.Block.Header.new(gas_limit: 30_000_000, base_fee_per_gas: 7, hash: <<1::256>>)
iex> db = EEVM.Database.InMemory.new()
iex> {:ok, block, _result} = Block.build(parent, [], db, hardfork: :shanghai, timestamp: 12)
iex> {:ok, decoded} = block |> Block.encode() |> Block.decode()
iex> decoded == block
true
iex> {:ok, result} = Block.execute(decoded, parent, db, hardfork: :shanghai)
iex> result.gas_used
0

Not covered: ommer (uncle) validation and rewards, fork transitions inside a chain, and EIP-7685 request computation. requests_hash is carried and round-tripped but not recomputed; build/4 sets the empty-requests hash.

Summary

Functions

Builds the child of parent holding transactions (signed wire transactions, in order) and executes it on db.

Decodes a canonical RLP block. The header's hash is set from its bytes.

Encodes block as canonical RLP: [header, transactions, ommers], plus withdrawals when it isn't nil.

Validates block with validate/3, then applies it to db: the EIP-4788 and EIP-2935 system calls, each transaction through EEVM.Handler, and the EIP-4895 withdrawals. Finally checks the header's gas_used, logs_bloom, receipts_root, and state_root against the result.

Returns MAX_RLP_BLOCK_SIZE, the largest encoded block valid from Osaka (EIP-7934).

Checks block against parent without touching state, in this order

Types

build_opts()

@type build_opts() :: [
  hardfork: EEVM.HardforkConfig.spec_id(),
  config: EEVM.Config.t(),
  chain_id: non_neg_integer(),
  block_hashes: %{required(non_neg_integer()) => non_neg_integer()},
  timestamp: non_neg_integer(),
  coinbase: non_neg_integer(),
  gas_limit: non_neg_integer(),
  prev_randao: non_neg_integer(),
  extra_data: binary(),
  parent_beacon_block_root: non_neg_integer(),
  withdrawals: [EEVM.Block.Withdrawal.t()]
]

opts()

@type opts() :: [
  hardfork: EEVM.HardforkConfig.spec_id(),
  config: EEVM.Config.t(),
  chain_id: non_neg_integer(),
  block_hashes: %{required(non_neg_integer()) => non_neg_integer()}
]

t()

@type t() :: %EEVM.Block{
  header: EEVM.Block.Header.t(),
  ommers: [EEVM.Block.Header.t()],
  transactions: [transaction()],
  withdrawals: [EEVM.Block.Withdrawal.t()] | nil
}

transaction()

Functions

build(parent, transactions, db, opts)

Builds the child of parent holding transactions (signed wire transactions, in order) and executes it on db.

The header takes number, parent_hash, the EIP-1559 base fee, and the EIP-4844 excess blob gas from parent, and every commitment from the execution result; hash is computed. Fork-dependent fields are set exactly for the forks that define them.

Options are those of execute/4 plus header attributes: :timestamp (required), :coinbase, :gas_limit (default: the parent's), :prev_randao, :extra_data, :parent_beacon_block_root, and :withdrawals (from Shanghai).

Returns {:ok, block, result} or the first error execute/4 would report.

decode(bytes)

@spec decode(binary()) :: {:ok, t()} | {:error, :invalid_block_encoding}

Decodes a canonical RLP block. The header's hash is set from its bytes.

Returns {:error, :invalid_block_encoding} when the bytes aren't a canonical encoding of a block (malformed RLP, a wrong-sized field, a non-canonical integer, a withdrawal field over 64 bits, or an undecodable transaction).

encode(block)

@spec encode(t()) :: binary()

Encodes block as canonical RLP: [header, transactions, ommers], plus withdrawals when it isn't nil.

Legacy transactions are embedded as RLP lists and typed transactions as their EIP-2718 envelope bytes.

execute(block, parent, db, opts \\ [])

@spec execute(t(), EEVM.Block.Header.t(), EEVM.Database.t(), opts()) ::
  {:ok, EEVM.Block.Result.t()} | {:error, EEVM.Error.block_error()}

Validates block with validate/3, then applies it to db: the EIP-4788 and EIP-2935 system calls, each transaction through EEVM.Handler, and the EIP-4895 withdrawals. Finally checks the header's gas_used, logs_bloom, receipts_root, and state_root against the result.

Returns {:ok, %EEVM.Block.Result{}} whose post_state_db is the state after the block. A transaction that EEVM.Handler rejects, or whose gas limit exceeds the gas left in the block, fails the block with {:error, {:tx_failed, index, reason}}.

max_rlp_block_size()

@spec max_rlp_block_size() :: pos_integer()

Returns MAX_RLP_BLOCK_SIZE, the largest encoded block valid from Osaka (EIP-7934).

validate(block, parent, opts \\ [])

@spec validate(t(), EEVM.Block.Header.t(), opts()) ::
  :ok | {:error, EEVM.Error.block_error()}

Checks block against parent without touching state, in this order:

  1. From Osaka, the encoded block is at most max_rlp_block_size/0 bytes (EIP-7934), else :rlp_block_size_exceeded.
  2. EEVM.Block.HeaderValidator.validate/3.
  3. The body matches the header: ommers_hash, transactions_root, withdrawals_root (withdrawals are present exactly from Shanghai), and from Cancun the total blob gas of the transactions equals blob_gas_used.