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.

FieldDescription
headerThe EEVM.Block.Header
transactionsSigned transactions (see EEVM.Transaction), in order
ommersUncle headers; always empty after the Merge
withdrawalsEEVM.Block.Withdrawals from Shanghai on, nil before
  • 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, EIP-7685 requests), and checks the header's gas_used, logs_bloom, receipts_root, state_root, and requests_hash 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: a full :config (hardfork, chain id, custom precompiles) or the :hardfork shorthand (default :cancun); :chain_id is deprecated in favour of the config's. :block_hashes maps recent block numbers to their 32-byte hashes for BLOCKHASH. From Prague, :deposit_contract_address names the chain's EIP-6110 deposit contract (default: mainnet's, EEVM.Block.Requests.mainnet_deposit_contract/0). tracer: true traces every transaction: result.trace is a list with one EEVM.Tracer per transaction, in block order (like geth's debug_traceBlock), so transaction boundaries stay visible.

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, and fork transitions inside a chain.

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, the EIP-4895 withdrawals, and from Prague the EIP-7685 requests (EIP-6110 deposits, then the EIP-7002 and EIP-7251 system calls; see EEVM.Block.Requests). Finally checks the header's gas_used, logs_bloom, receipts_root, state_root, and requests_hash 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()) => <<_::256>>},
  deposit_contract_address: non_neg_integer(),
  tracer: boolean(),
  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: <<_::256>> | 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()) => <<_::256>>},
  deposit_contract_address: non_neg_integer(),
  tracer: boolean()
]

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 (default: 12 seconds, one mainnet slot, after the parent's), :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, including {:error, :simulation_config} for a config with a simulation switch set.

From Prague the state must hold the EIP-7002 and EIP-7251 request contracts, as on mainnet; give a new chain its system contracts with EEVM.SystemContracts.genesis_accounts/1.

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, the EIP-4895 withdrawals, and from Prague the EIP-7685 requests (EIP-6110 deposits, then the EIP-7002 and EIP-7251 system calls; see EEVM.Block.Requests). Finally checks the header's gas_used, logs_bloom, receipts_root, state_root, and requests_hash against the result.

With tracer: true, each transaction is traced and result.trace lists one EEVM.Tracer per transaction, in block order; system calls are not traced.

Returns {:ok, %EEVM.Block.Result{}} whose db is the state after the block and whose requests are the block's requests. 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}}; a request that can't be collected fails it with that 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. The config sets no simulation switch (EEVM.Config.simulation?/1), else :simulation_config: blocks never run with relaxed rules.
  2. From Osaka, the encoded block is at most max_rlp_block_size/0 bytes (EIP-7934), else :rlp_block_size_exceeded.
  3. The header is a valid child of parent (EEVM.Block.HeaderValidator; the checks and their errors are listed in EEVM.Error).
  4. 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.