ethereum.forks.bpo5.forkethereum.forks.amsterdam.fork

Ethereum Specification.

.. contents:: Table of Contents :backlinks: none :local:

Introduction

Entry point for the Ethereum specification.

BASE_FEE_MAX_CHANGE_DENOMINATOR

107
BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8)

ELASTICITY_MULTIPLIER

108
ELASTICITY_MULTIPLIER = Uint(2)

EMPTY_OMMER_HASH

109
EMPTY_OMMER_HASH = keccak256(rlp.encode([]))

SYSTEM_ADDRESS

110
SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe")

BEACON_ROOTS_ADDRESS

111
BEACON_ROOTS_ADDRESS = hex_to_address(
112
    "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02"
113
)

SYSTEM_TRANSACTION_GAS

102
SYSTEM_TRANSACTION_GAS = Uint(30000000)
114
SYSTEM_TRANSACTION_GAS = ExecutionGas(Uint(30000000))

MAX_BLOB_GAS_PER_BLOCK

103
MAX_BLOB_GAS_PER_BLOCK: Final[U64] = (
104
    GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB
105
)

VERSIONED_HASH_VERSION_KZG

106
VERSIONED_HASH_VERSION_KZG = b"\x01"

SYSTEM_MAX_SSTORES_PER_CALL

Upper bound on the number of new storage slots a single system call is expected to write.

115
SYSTEM_MAX_SSTORES_PER_CALL = Uint(16)

GWEI_TO_WEI

120
GWEI_TO_WEI = U256(10**9)

WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS

122
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
123
    "0x00000961Ef480Eb55e80D19ad83579A64c007002"
124
)

CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS

125
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
126
    "0x0000BBdDc7CE488642fb579F8B00f3a590007251"
127
)

BUILDER_DEPOSIT_CONTRACT_ADDRESS

128
BUILDER_DEPOSIT_CONTRACT_ADDRESS = hex_to_address(
129
    "0x0000BFF46984E3725691FA540A8C7589300D8282"
130
)

BUILDER_EXIT_CONTRACT_ADDRESS

131
BUILDER_EXIT_CONTRACT_ADDRESS = hex_to_address(
132
    "0x000064D678505AD48F8CCB093BC65613800E8282"
133
)

HISTORY_STORAGE_ADDRESS

134
HISTORY_STORAGE_ADDRESS = hex_to_address(
135
    "0x0000F90827F1C53a10cb7A02335B175320002935"
136
)

MAX_BLOCK_SIZE

137
MAX_BLOCK_SIZE = 10_485_760

SAFETY_MARGIN

138
SAFETY_MARGIN = 2_097_152

MAX_RLP_BLOCK_SIZE

139
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN

BLOB_COUNT_LIMIT

120
BLOB_COUNT_LIMIT = 6

ChainContext

Chain context needed for block execution.

142
@final
143
@slotted_freezable
144
@dataclass
class ChainContext:

chain_id

Identify the chain for transaction signature recovery.

150
    chain_id: U64

block_hashes

Recent ancestor hashes (up to 256) for the BLOCKHASH opcode.

153
    block_hashes: List[Hash32]

parent_header

Parent header used for header validation and system contracts.

156
    parent_header: Header | PreviousHeader

BlockChain

History and current state of the block chain.

160
@final
161
@dataclass
class BlockChain:

blocks

167
    blocks: List[Block]

state

168
    state: State

chain_id

169
    chain_id: U64

apply_fork

Transforms the state from the previous hard fork (old) into the block chain object for this hard fork and returns it.

When forks need to implement an irregular state transition, this function is used to handle the irregularity. See the :ref:DAO Fork <dao-fork> for an example.

Parameters

old : Previous block chain object.

Returns

new : BlockChain Upgraded block chain object for this hard fork.

def apply_fork(​old: BlockChain​) -> BlockChain:
173
    <snip>
192
    return old

get_last_256_block_hashes

Obtain the list of hashes of the previous 256 blocks in order of increasing block number.

This function will return less hashes for the first 256 blocks.

The BLOCKHASH opcode needs to access the latest hashes on the chain, therefore this function retrieves them.

Parameters

chain : History and current state.

Returns

recent_block_hashes : List[Hash32] Hashes of the recent 256 blocks in order of increasing block number.

def get_last_256_block_hashes(​chain: BlockChain​) -> List[Hash32]:
196
    <snip>
216
    recent_blocks = chain.blocks[-255:]
217
    if len(recent_blocks) == 0:
218
        return []
219
220
    recent_block_hashes = []
221
222
    for block in recent_blocks:
223
        prev_block_hash = block.header.parent_hash
224
        recent_block_hashes.append(prev_block_hash)
225
226
    # We are computing the hash only for the most recent block and not for
227
    # the rest of the blocks as they have successors which have the hash of
228
    # the current block as parent hash.
229
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
230
    recent_block_hashes.append(most_recent_block_hash)
231
232
    return recent_block_hashes

state_transition

Attempts to apply a block to an existing block chain.

All parts of the block's contents need to be verified before being added to the chain. Blocks are verified by ensuring that the contents of the block make logical sense with the contents of the parent block. The information in the block's header must also match the corresponding information in the block.

To implement Ethereum, in theory clients are only required to store the most recent 255 blocks of the chain since as far as execution is concerned, only those blocks are accessed. Practically, however, clients should store more blocks to handle reorgs.

Parameters

chain : History and current state. block : Block to apply to chain.

def state_transition(​chain: BlockChain, ​​block: Block​) -> None:
236
    <snip>
221
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
222
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
223
224
    validate_header(chain, block.header)
225
    if block.ommers != ():
226
        raise InvalidBlock
227
228
    block_state = BlockState(pre_state=chain.state)
229
230
    block_env = vm.BlockEnvironment(
258
    chain_context = ChainContext(
259
        chain_id=chain.chain_id,
232
        state=block_state,
233
        block_gas_limit=block.header.gas_limit,
260
        block_hashes=get_last_256_block_hashes(chain),
235
        coinbase=block.header.coinbase,
236
        number=block.header.number,
237
        base_fee_per_gas=block.header.base_fee_per_gas,
238
        time=block.header.timestamp,
239
        prev_randao=block.header.prev_randao,
240
        excess_blob_gas=block.header.excess_blob_gas,
241
        parent_beacon_block_root=block.header.parent_beacon_block_root,
261
        parent_header=chain.blocks[-1].header,
262
    )
263
244
    block_output = apply_body(
245
        block_env=block_env,
246
        transactions=block.transactions,
247
        withdrawals=block.withdrawals,
248
    )
249
    block_diff = extract_block_diff(block_state)
250
    block_state_root = chain.state.compute_state_root(block_diff)
251
    transactions_root = root(block_output.transactions_trie)
252
    receipt_root = root(block_output.receipts_trie)
253
    block_logs_bloom = logs_bloom(block_output.block_logs)
254
    withdrawals_root = root(block_output.withdrawals_trie)
255
    requests_hash = compute_requests_hash(block_output.requests)
256
257
    if block_output.block_gas_used != block.header.gas_used:
258
        raise InvalidBlock(
259
            f"{block_output.block_gas_used} != {block.header.gas_used}"
260
        )
261
    if transactions_root != block.header.transactions_root:
262
        raise InvalidBlock
263
    if block_state_root != block.header.state_root:
264
        raise InvalidBlock
265
    if receipt_root != block.header.receipt_root:
266
        raise InvalidBlock
267
    if block_logs_bloom != block.header.bloom:
268
        raise InvalidBlock
269
    if withdrawals_root != block.header.withdrawals_root:
270
        raise InvalidBlock
271
    if block_output.blob_gas_used != block.header.blob_gas_used:
272
        raise InvalidBlock
273
    if requests_hash != block.header.requests_hash:
274
        raise InvalidBlock
264
    block_diff = execute_block(block, chain.state, chain_context)
265
266
    apply_changes_to_state(chain.state, block_diff)
267
    chain.blocks.append(block)
268
    if len(chain.blocks) > 255:
269
        # Real clients have to store more blocks to deal with reorgs, but the
270
        # protocol only requires the last 255
271
        chain.blocks = chain.blocks[-255:]

execute_block

Execute a block and validate the resulting roots against the header.

This method is idempotent.

Parameters

block : Block to validate and execute. pre_state : Pre-execution state provider. chain_context : Chain context that the block may need during execution.

Returns

block_diff : BlockDiff Account, storage, and code changes produced by block execution.

def execute_block(​block: Block, ​​pre_state: State, ​​chain_context: ChainContext​) -> BlockDiff:
279
    <snip>
299
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
300
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
301
302
    parent_header = chain_context.parent_header
303
    validate_header(parent_header, block.header)
304
305
    if block.ommers != ():
306
        raise InvalidBlock
307
308
    block_state = BlockState(pre_state=pre_state)
309
310
    block_env = vm.BlockEnvironment(
311
        chain_id=chain_context.chain_id,
312
        state=block_state,
313
        block_gas_limit=block.header.gas_limit,
314
        block_hashes=chain_context.block_hashes,
315
        coinbase=block.header.coinbase,
316
        number=block.header.number,
317
        base_fee_per_gas=block.header.base_fee_per_gas,
318
        time=block.header.timestamp,
319
        prev_randao=block.header.prev_randao,
320
        excess_blob_gas=block.header.excess_blob_gas,
321
        parent_beacon_block_root=block.header.parent_beacon_block_root,
322
        block_access_list_builder=BlockAccessListBuilder(),
323
        slot_number=block.header.slot_number,
324
    )
325
326
    block_output = apply_body(
327
        block_env=block_env,
328
        transactions=block.transactions,
329
        withdrawals=block.withdrawals,
330
    )
331
    block_diff = extract_block_diff(block_state)
332
    block_state_root = pre_state.compute_state_root(block_diff)
333
    transactions_root = root(block_output.transactions_trie)
334
    receipt_root = root(block_output.receipts_trie)
335
    block_logs_bloom = logs_bloom(block_output.block_logs)
336
    withdrawals_root = root(block_output.withdrawals_trie)
337
    requests_hash = compute_requests_hash(block_output.requests)
338
    computed_block_access_list_hash = hash_block_access_list(
339
        block_output.block_access_list
340
    )
341
342
    block_gas_used = max(
343
        block_output.block_gas_used,
344
        block_output.block_state_gas_used,
345
    )
346
    if block_gas_used != block.header.gas_used:
347
        raise InvalidBlock(f"{block_gas_used} != {block.header.gas_used}")
348
    if transactions_root != block.header.transactions_root:
349
        raise InvalidBlock
350
    if block_state_root != block.header.state_root:
351
        raise InvalidBlock
352
    if receipt_root != block.header.receipt_root:
353
        raise InvalidBlock
354
    if block_logs_bloom != block.header.bloom:
355
        raise InvalidBlock
356
    if withdrawals_root != block.header.withdrawals_root:
357
        raise InvalidBlock
358
    if block_output.blob_gas_used != block.header.blob_gas_used:
359
        raise InvalidBlock
360
    if requests_hash != block.header.requests_hash:
361
        raise InvalidBlock
362
    if computed_block_access_list_hash != block.header.block_access_list_hash:
363
        raise InvalidBlock("Invalid block access list hash")
364
365
    return block_diff

calculate_base_fee_per_gas

Calculates the base fee per gas for the block.

Parameters

block_gas_limit : Gas limit of the block for which the base fee is being calculated. parent_gas_limit : Gas limit of the parent block. parent_gas_used : Gas used in the parent block. parent_base_fee_per_gas : Base fee per gas of the parent block.

Returns

base_fee_per_gas : Uint Base fee per gas for the block.

def calculate_base_fee_per_gas(​block_gas_limit: Uint, ​​parent_gas_limit: Uint, ​​parent_gas_used: Uint, ​​parent_base_fee_per_gas: Uint​) -> Uint:
374
    <snip>
394
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
395
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
396
        raise InvalidBlock
397
398
    if parent_gas_used == parent_gas_target:
399
        expected_base_fee_per_gas = parent_base_fee_per_gas
400
    elif parent_gas_used > parent_gas_target:
401
        gas_used_delta = parent_gas_used - parent_gas_target
402
403
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
404
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
405
406
        base_fee_per_gas_delta = max(
407
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
408
            Uint(1),
409
        )
410
411
        expected_base_fee_per_gas = (
412
            parent_base_fee_per_gas + base_fee_per_gas_delta
413
        )
414
    else:
415
        gas_used_delta = parent_gas_target - parent_gas_used
416
417
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
418
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
419
420
        base_fee_per_gas_delta = (
421
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
422
        )
423
424
        expected_base_fee_per_gas = (
425
            parent_base_fee_per_gas - base_fee_per_gas_delta
426
        )
427
428
    return Uint(expected_base_fee_per_gas)

validate_header

Verifies a block header.Verify a block header against its parent.

In order to consider a block's header valid, the logic for the quantities in the header should match the logic for the block itself. For example the header timestamp should be greater than the block's parent timestamp because the block was created after the parent block. Additionally, the block's number should be directly following the parent block's number since it is the next block in the sequence.

Parameters

chain :parent_header : History and current state.Header of the parent block. header : Header to check for correctness.

def validate_header(​chainparent_header: BlockChainHeader | PreviousHeader, ​​header: Header​) -> None:
434
    <snip>
452
    if header.number < Uint(1):
453
        raise InvalidBlock
368
369
    parent_header = chain.blocks[-1].header
454
455
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
456
    if header.excess_blob_gas != excess_blob_gas:
457
        raise InvalidBlock
458
459
    if header.gas_used > header.gas_limit:
460
        raise InvalidBlock
461
462
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
463
        header.gas_limit,
464
        parent_header.gas_limit,
465
        parent_header.gas_used,
466
        parent_header.base_fee_per_gas,
467
    )
468
    if expected_base_fee_per_gas != header.base_fee_per_gas:
469
        raise InvalidBlock
470
    if header.timestamp <= parent_header.timestamp:
471
        raise InvalidBlock
472
    if header.number != parent_header.number + Uint(1):
473
        raise InvalidBlock
474
    if len(header.extra_data) > 32:
475
        raise InvalidBlock
476
    if header.difficulty != 0:
477
        raise InvalidBlock
478
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
479
        raise InvalidBlock
480
    if header.ommers_hash != EMPTY_OMMER_HASH:
481
        raise InvalidBlock
482
483
    block_parent_hash = keccak256(rlp.encode(parent_header))
484
    if header.parent_hash != block_parent_hash:
485
        raise InvalidBlock

check_transaction

Check if the transaction is includable in the block.Admit a raw transaction and build its execution environment.

Recover the sender, statically validate the transaction, and check that it is includable in the block, in that order, so that a transaction invalid in several ways reports the earliest failure.

Parameters

block_env : The block scoped environment. block_output : The block output for the current block. tx : The transaction. tx_state :index : The transaction state tracker.The index of the current transaction.

Returns

sender_address :tx_env : The sender of the transaction. effective_gas_price : The price to charge for gas when the transaction is executed. blob_versioned_hashes : The blob versioned hashes of the transaction. tx_blob_gas_used: The blob gas used by the transaction.The environment for executing the transaction.

Raises

InvalidBlock : If the transaction is not includable. InvalidSignatureError : If the transaction's signature is invalid. InsufficientTransactionGasError : If the transaction does not provide enough gas to cover its intrinsic cost. TransactionGasLimitExceededError : If the transaction's gas limit exceeds TX_MAX_TOTAL_GAS_LIMIT. GasUsedExceedsLimitError : If the gas used by the transaction exceeds the block's gas limit. NonceMismatchError : If the nonce of the transaction is not equal to the sender's nonce. InsufficientBalanceError : If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : If the maximum fee per blob gas is insufficient for the transaction. BlobGasLimitExceededError : If the blob gas used by the transaction exceeds the block's blob gas limit. InvalidBlobVersionedHashError : If the transaction contains a blob versioned hash with an invalid version. NoBlobDataError : If the transaction is a type 3 but has no blobs. BlobCountExceededError : If the transaction is a type 3 and has more blobs than the limit. TransactionTypeContractCreationError: If the transaction type is not allowed to create contracts. EmptyAuthorizationListError : If the transaction is a SetCodeTransaction and the authorization list is empty.

def check_transaction(​block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo5.vm.BlockOutputethereum.forks.amsterdam.vm.BlockOutput, ​​tx: Transaction, ​​tx_stateindex: TransactionStateUint​) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]ethereum.forks.amsterdam.vm.TransactionEnvironment:
494
    <snip>
468
    gas_available = block_env.block_gas_limit - block_output.block_gas_used
469
    blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used
545
    sender = recover_sender(tx)
546
    intrinsic = validate_transaction(tx, sender)
547
    tx_state = TransactionState(parent=block_env.state)
548
471
    if tx.gas > gas_available:
472
        raise GasUsedExceedsLimitError("gas used exceeds limit")
549
    check_block_gas_capacity(
550
        block_env, block_output, tx.gas, calculate_total_blob_gas(tx)
551
    )
552
474
    tx_blob_gas_used = calculate_total_blob_gas(tx)
475
    if tx_blob_gas_used > blob_gas_available:
476
        raise BlobGasLimitExceededError("blob gas limit exceeded")
553
    sender_account = get_account(tx_state, sender)
554
478
    tx_chain_id = chain_id(tx)
479
    if tx_chain_id is not None and tx_chain_id != block_env.chain_id:
480
        raise WrongChainIdError(
481
            expected=block_env.chain_id,
482
            actual=tx_chain_id,
483
        )
484
485
    sender_address = recover_sender(tx)
486
    sender_account = get_account(tx_state, sender_address)
487
488
    if isinstance(tx, FeeMarketCapableTransaction):
489
        if tx.max_fee_per_gas < block_env.base_fee_per_gas:
490
            raise InsufficientMaxFeePerGasError(
491
                tx.max_fee_per_gas, block_env.base_fee_per_gas
492
            )
493
494
        priority_fee_per_gas = min(
495
            tx.max_priority_fee_per_gas,
496
            tx.max_fee_per_gas - block_env.base_fee_per_gas,
497
        )
498
        effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas
499
        max_gas_fee = tx.gas * tx.max_fee_per_gas
500
    else:
501
        if tx.gas_price < block_env.base_fee_per_gas:
502
            raise InvalidBlock
503
        effective_gas_price = tx.gas_price
504
        max_gas_fee = tx.gas * tx.gas_price
555
    effective_gas_price = calculate_effective_gas_price(
556
        tx, block_env.base_fee_per_gas
557
    )
558
    max_gas_fee = calculate_max_gas_fee(tx, tx.gas)
559
560
    if isinstance(tx, BlobTransaction):
507
        blob_count = len(tx.blob_versioned_hashes)
508
        if blob_count == 0:
509
            raise NoBlobDataError("no blob data in transaction")
510
        if blob_count > BLOB_COUNT_LIMIT:
511
            raise BlobCountExceededError(
512
                f"Tx has {blob_count} blobs. Max allowed: {BLOB_COUNT_LIMIT}"
513
            )
514
        for blob_versioned_hash in tx.blob_versioned_hashes:
515
            if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG:
516
                raise InvalidBlobVersionedHashError(
517
                    "invalid blob versioned hash"
518
                )
519
520
        blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas)
521
        if Uint(tx.max_fee_per_blob_gas) < blob_gas_price:
522
            raise InsufficientMaxFeePerBlobGasError(
523
                "insufficient max fee per blob gas"
524
            )
561
        check_max_fee_per_blob_gas(
562
            tx.blob_versioned_hashes,
563
            tx.max_fee_per_blob_gas,
564
            block_env.excess_blob_gas,
565
        )
566
567
        max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint(
568
            tx.max_fee_per_blob_gas
569
        )
570
        blob_versioned_hashes = tx.blob_versioned_hashes
571
    else:
572
        blob_versioned_hashes = ()
573
533
    if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
534
        if not isinstance(tx.to, Address):
535
            raise TransactionTypeContractCreationError(tx)
536
537
    if isinstance(tx, SetCodeTransaction):
538
        if not any(tx.authorizations):
539
            raise EmptyAuthorizationListError("empty authorization list")
540
541
    if sender_account.nonce > Uint(tx.nonce):
542
        raise NonceMismatchError("nonce too low")
543
    elif sender_account.nonce < Uint(tx.nonce):
544
        raise NonceMismatchError("nonce too high")
574
    check_nonce(tx, sender_account.nonce)
575
576
    if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value):
577
        raise InsufficientBalanceError("insufficient sender balance")
578
    sender_code = get_code(tx_state, sender_account.code_hash)
579
    if sender_account.code_hash != EMPTY_CODE_HASH and not is_valid_delegation(
580
        sender_code
581
    ):
582
        raise InvalidSenderError("not EOA")
583
554
    return (
555
        sender_address,
556
        effective_gas_price,
557
        blob_versioned_hashes,
558
        tx_blob_gas_used,
559
    )
584
    # Split the EVM gas into an execution-gas grant (capped by the
585
    # remaining execution-gas budget) and a state gas reservoir.
586
    allocation = allocate_evm_gas(tx.gas, intrinsic)
587
588
    access_list_addresses = set()
589
    access_list_storage_keys = set()
590
    if has_access_list(tx):
591
        for access in tx.access_list:
592
            access_list_addresses.add(access.account)
593
            for slot in access.slots:
594
                access_list_storage_keys.add((access.account, slot))
595
596
    authorizations: Tuple[Authorization, ...] = ()
597
    if isinstance(tx, SetCodeTransaction):
598
        authorizations = tx.authorizations
599
600
    if isinstance(tx.to, Bytes0):
601
        is_create = True
602
        # A creation's frame runs at the address the contract
603
        # deploys to.
604
        recipient = compute_contract_address(sender, sender_account.nonce)
605
    else:
606
        is_create = False
607
        recipient = tx.to
608
609
    accounts_with_paid_writes = {sender}
610
    if is_create or tx.value > U256(0):
611
        accounts_with_paid_writes.add(recipient)
612
613
    return vm.TransactionEnvironment(
614
        origin=sender,
615
        recipient=recipient,
616
        is_create=is_create,
617
        data=tx.data,
618
        value=tx.value,
619
        gas_limit=tx.gas,
620
        effective_gas_price=effective_gas_price,
621
        execution_gas_grant=allocation.execution_gas,
622
        state_gas_reservoir=allocation.state_gas_reservoir,
623
        calldata_floor=intrinsic.calldata_floor,
624
        access_list_addresses=access_list_addresses,
625
        access_list_storage_keys=access_list_storage_keys,
626
        accounts_with_paid_writes=accounts_with_paid_writes,
627
        state=tx_state,
628
        blob_versioned_hashes=blob_versioned_hashes,
629
        authorizations=authorizations,
630
        index_in_block=index,
631
        tx_hash=get_transaction_hash(encode_transaction(tx)),
632
    )

make_receipt

Make the receipt for a transaction that was executed.

Parameters

tx : The executed transaction. error : Error in the top level frame of the transaction, if any. cumulative_gas_used : The total gas used so far in the block after the transaction was executed.executed. This is the gas used after refunds. logs : The logs produced by the transaction.

Returns

receipt : The receipt for the transaction.

def make_receipt(​tx: Transaction, ​​error: Optional[EthereumException], ​​cumulative_gas_used: Uint, ​​logs: Tuple[Log, ...]​) -> Bytes | Receipt:
641
    <snip>
662
    receipt = Receipt(
663
        succeeded=error is None,
664
        cumulative_gas_used=cumulative_gas_used,
665
        bloom=logs_bloom(logs),
666
        logs=logs,
667
    )
668
669
    return encode_receipt(tx, receipt)

process_checked_system_transaction

Process a system transaction and raise an error if the contract does not contain code or if the transaction fails.

Parameters

block_env : The block scoped environment. target_address : Address of the contract to call. data : Data to pass to the contract.

Returns

system_tx_output : MessageCallOutputTransactionOutput Output of processing the system transaction.The settled output of the system transaction.

def process_checked_system_transaction(​block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes​) -> MessageCallOutputTransactionOutput:
677
    <snip>
696
    # Pre-check that the system contract has code. We use a throwaway
697
    # TransactionState here that is *never* propagated back to BlockState
698
    # (no incorporate_tx_into_block call); the same get_account / get_code
699
    # lookups are performed and properly tracked by
700
    # process_unchecked_system_transaction below, which this function
701
    # always calls. Reading via a TransactionState (rather than directly
702
    # against pre_state) lets us see system contracts deployed earlier in
703
    # the same block — see EIP-7002 and EIP-7251 for this edge case.
704
    untracked_state = TransactionState(parent=block_env.state)
705
    system_contract_code = get_code(
706
        untracked_state,
707
        get_account(untracked_state, target_address).code_hash,
708
    )
709
710
    if len(system_contract_code) == 0:
711
        raise InvalidBlock(
712
            f"System contract address {target_address.hex()} does not "
713
            "contain code"
714
        )
715
716
    system_tx_output = process_unchecked_system_transaction(
717
        block_env,
718
        target_address,
719
        data,
720
    )
721
722
    if system_tx_output.error:
723
        raise InvalidBlock(
724
            f"System contract ({target_address.hex()}) call failed: "
725
            f"{system_tx_output.error}"
726
        )
727
728
    return system_tx_output

process_unchecked_system_transaction

Process a system transaction without checking if the contract contains code or if the transaction fails.

Parameters

block_env : The block scoped environment. target_address : Address of the contract to call. data : Data to pass to the contract.

Returns

system_tx_output : MessageCallOutputTransactionOutput Output of processing the system transaction.The settled output of the system transaction.

def process_unchecked_system_transaction(​block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes​) -> MessageCallOutputTransactionOutput:
736
    <snip>
755
    system_tx_state = TransactionState(parent=block_env.state)
683
    system_contract_code = get_code(
684
        system_tx_state,
685
        get_account(system_tx_state, target_address).code_hash,
686
    )
756
757
    tx_env = vm.TransactionEnvironment(
758
        origin=SYSTEM_ADDRESS,
690
        gas_price=block_env.base_fee_per_gas,
691
        gas=SYSTEM_TRANSACTION_GAS,
759
        recipient=target_address,
760
        is_create=False,
761
        data=data,
762
        value=U256(0),
763
        gas_limit=SYSTEM_TRANSACTION_GAS,
764
        effective_gas_price=block_env.base_fee_per_gas,
765
        execution_gas_grant=SYSTEM_TRANSACTION_GAS,
766
        state_gas_reservoir=StateGas(
767
            StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL
768
        ),
769
        calldata_floor=Uint(0),
770
        access_list_addresses=set(),
693
        access_list_storage_keys=set(),
771
        access_list_storage_keys=set(),
772
        # A system transaction charges no gas, so no write is paid for.
773
        accounts_with_paid_writes=set(),
774
        state=system_tx_state,
775
        blob_versioned_hashes=(),
776
        authorizations=(),
777
        index_in_block=None,
778
        tx_hash=None,
779
    )
780
701
    system_tx_message = Message(
702
        block_env=block_env,
703
        tx_env=tx_env,
704
        caller=SYSTEM_ADDRESS,
705
        target=target_address,
706
        gas=SYSTEM_TRANSACTION_GAS,
707
        value=U256(0),
708
        data=data,
709
        code=system_contract_code,
710
        depth=Uint(0),
711
        current_target=target_address,
712
        code_address=target_address,
713
        should_transfer_value=False,
714
        is_static=False,
715
        accessed_addresses=set(),
716
        accessed_storage_keys=set(),
717
        disable_precompiles=False,
718
        parent_evm=None,
719
    )
781
    system_tx_output = process_top_level(block_env, tx_env)
782
721
    system_tx_output = process_message_call(system_tx_message)
722
723
    incorporate_tx_into_block(system_tx_state)
783
    incorporate_tx_into_block(
784
        system_tx_state, block_env.block_access_list_builder
785
    )
786
787
    return system_tx_output

apply_body

Executes a block.

Many of the contents of a block are stored in data structures called tries. There is a transactions trie which is similar to a ledger of the transactions stored in the current block. There is also a receipts trie which stores the results of executing a transaction, like the post state and gas used. This function creates and executes the block that is to be added to the chain.

Parameters

block_env : The block scoped environment. transactions : Transactions included in the block. withdrawals : Withdrawals to be processed in the current block.

Returns

block_output : The block output for the current block.

def apply_body(​block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​transactions: Tuple[LegacyTransaction | Bytes, ...], ​​withdrawals: Tuple[Withdrawal, ...]​) -> ethereum.forks.bpo5.vm.BlockOutputethereum.forks.amsterdam.vm.BlockOutput:
795
    <snip>
820
    block_output = vm.BlockOutput()
821
822
    process_unchecked_system_transaction(
823
        block_env=block_env,
824
        target_address=BEACON_ROOTS_ADDRESS,
825
        data=block_env.parent_beacon_block_root,
826
    )
827
828
    process_unchecked_system_transaction(
829
        block_env=block_env,
830
        target_address=HISTORY_STORAGE_ADDRESS,
831
        data=block_env.block_hashes[-1],  # The parent hash
832
    )
833
834
    for i, tx in enumerate(map(decode_transaction, transactions)):
835
        process_transaction(block_env, block_output, tx, Uint(i))
836
837
    # EIP-7928: Post-execution operations use index N+1
838
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
839
        ulen(transactions) + Uint(1)
840
    )
841
842
    process_withdrawals(block_env, block_output, withdrawals)
843
844
    process_general_purpose_requests(
845
        block_env=block_env,
846
        block_output=block_output,
847
    )
848
849
    block_output.block_access_list = build_block_access_list(
850
        block_env.block_access_list_builder, block_env.state
851
    )
852
853
    # Validate block access list gas limit constraint (EIP-7928)
854
    validate_block_access_list_gas_limit(
855
        block_access_list=block_output.block_access_list,
856
        block_gas_limit=block_env.block_gas_limit,
857
    )
858
859
    return block_output

process_general_purpose_requests

Process all the requests in the block.

Parameters

block_env : The execution environment for the Block. block_output : The block output for the current block.

def process_general_purpose_requests(​block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo5.vm.BlockOutputethereum.forks.amsterdam.vm.BlockOutput​) -> None:
866
    <snip>
877
    # Requests are to be in ascending order of request type
878
    deposit_requests = parse_deposit_requests(block_output)
879
    requests_from_execution = block_output.requests
880
    if len(deposit_requests) > 0:
881
        requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests)
882
883
    system_withdrawal_tx_output = process_checked_system_transaction(
884
        block_env=block_env,
885
        target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
886
        data=b"",
887
    )
888
889
    if len(system_withdrawal_tx_output.return_data) > 0:
890
        requests_from_execution.append(
891
            WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data
892
        )
893
894
    system_consolidation_tx_output = process_checked_system_transaction(
895
        block_env=block_env,
896
        target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
897
        data=b"",
898
    )
899
900
    if len(system_consolidation_tx_output.return_data) > 0:
901
        requests_from_execution.append(
902
            CONSOLIDATION_REQUEST_TYPE
903
            + system_consolidation_tx_output.return_data
904
        )
905
906
    system_builder_deposit_tx_output = process_checked_system_transaction(
907
        block_env=block_env,
908
        target_address=BUILDER_DEPOSIT_CONTRACT_ADDRESS,
909
        data=b"",
910
    )
911
912
    if len(system_builder_deposit_tx_output.return_data) > 0:
913
        requests_from_execution.append(
914
            BUILDER_DEPOSIT_REQUEST_TYPE
915
            + system_builder_deposit_tx_output.return_data
916
        )
917
918
    system_builder_exit_tx_output = process_checked_system_transaction(
919
        block_env=block_env,
920
        target_address=BUILDER_EXIT_CONTRACT_ADDRESS,
921
        data=b"",
922
    )
923
924
    if len(system_builder_exit_tx_output.return_data) > 0:
925
        requests_from_execution.append(
926
            BUILDER_EXIT_REQUEST_TYPE
927
            + system_builder_exit_tx_output.return_data
928
        )

update_sender_state

Debit the sender for the transaction's maximum possible gas fee.

Increment the sender's nonce and deduct the largest fee the transaction could incur -- its gas limit priced at the effective gas price, plus the blob fee resolved at inclusion -- up front. Execution later refunds whatever execution gas was not spent.

Parameters

block_env : The block's execution environment. tx_env : The transaction's execution environment. tx : The transaction being charged.

def update_sender_state(​block_env: ethereum.forks.amsterdam.vm.BlockEnvironment, ​​tx_env: ethereum.forks.amsterdam.vm.TransactionEnvironment, ​​tx: Transaction​) -> None:
936
    <snip>
954
    tx_state = tx_env.state
955
    sender = tx_env.origin
956
    sender_account = get_account(tx_state, sender)
957
958
    effective_gas_fee = tx_env.gas_limit * tx_env.effective_gas_price
959
    if isinstance(tx, BlobTransaction):
960
        blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx)
961
    else:
962
        blob_gas_fee = Uint(0)
963
964
    increment_nonce(tx_state, sender)
965
966
    sender_balance_after_gas_fee = (
967
        Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
968
    )
969
    set_account_balance(tx_state, sender, U256(sender_balance_after_gas_fee))

disburse_gas_fees

Refund the payer's unspent gas and pay the priority fee.

Return the gas the transaction did not use to the payer that fronted the maximum fee at inclusion, priced at the effective gas price, and credit the coinbase with the priority fee on the gas that was used.

Parameters

block_env : The block scoped environment. tx_env : The transaction's execution environment. settlement : The settled gas amounts. payer : The account that fronted the maximum gas fee and receives the refund.

def disburse_gas_fees(​block_env: ethereum.forks.amsterdam.vm.BlockEnvironment, ​​tx_env: ethereum.forks.amsterdam.vm.TransactionEnvironment, ​​settlement: TransactionGasSettlement, ​​payer: Address​) -> None:
978
    <snip>
999
    tx_state = tx_env.state
1000
    gas_refund_amount = settlement.gas_left * tx_env.effective_gas_price
1001
1002
    priority_fee_per_gas = (
1003
        tx_env.effective_gas_price - block_env.base_fee_per_gas
1004
    )
1005
    transaction_fee = settlement.gas_used * priority_fee_per_gas
1006
1007
    create_ether(tx_state, payer, U256(gas_refund_amount))
1008
    create_ether(tx_state, block_env.coinbase, U256(transaction_fee))

process_transaction

Execute a transaction against the provided environment.

This function processes the actions needed to execute a transaction. It decrements the sender's account balance after calculating the gas fee and refunds them the proper amount after execution. Calling contracts, deploying code, and incrementing nonces are all examples of actions that happen within this function or from a call made within this function.

Accounts that are marked for deletion are processed and destroyed after execution.

Parameters

block_env : Environment for the Ethereum Virtual Machine. block_output : The block output for the current block. tx : Transaction to execute. index: Index of the transaction in the block.

def process_transaction(​block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo5.vm.BlockOutputethereum.forks.amsterdam.vm.BlockOutput, ​​tx: Transaction, ​​index: Uint​) -> None:
1017
    <snip>
860
    tx_state = TransactionState(parent=block_env.state)
1041
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
1042
        index + Uint(1)
1043
    )
1044
1045
    trie_set(
1046
        block_output.transactions_trie,
1047
        rlp.encode(index),
1048
        encode_transaction(tx),
1049
    )
1050
868
    intrinsic = validate_transaction(tx)
1051
    tx_chain_id = chain_id(tx)
1052
    if tx_chain_id is not None and tx_chain_id != block_env.chain_id:
1053
        raise WrongChainIdError(
1054
            expected=block_env.chain_id,
1055
            actual=tx_chain_id,
1056
        )
1057
870
    (
871
        sender,
872
        effective_gas_price,
873
        blob_versioned_hashes,
874
        tx_blob_gas_used,
875
    ) = check_transaction(
876
        block_env=block_env,
877
        block_output=block_output,
878
        tx=tx,
879
        tx_state=tx_state,
1058
    tx_env = check_transaction(block_env, block_output, tx, index)
1059
1060
    update_sender_state(block_env, tx_env, tx)
1061
1062
    tx_output = process_top_level(block_env, tx_env)
1063
1064
    settlement = settle_transaction_gas(
1065
        tx_env.gas_limit,
1066
        tx_env.calldata_floor,
1067
        tx_output.gas_left,
1068
        tx_output.state_gas_left,
1069
        tx_output.refund_counter,
1070
        tx_output.state_gas_used,
1071
    )
1072
882
    sender_account = get_account(tx_state, sender)
1073
    disburse_gas_fees(block_env, tx_env, settlement, tx_env.origin)
1074
884
    if isinstance(tx, BlobTransaction):
885
        blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx)
886
    else:
887
        blob_gas_fee = Uint(0)
1075
    block_output.block_gas_used += settlement.execution_gas_used
1076
    block_output.block_state_gas_used += settlement.state_gas_used
1077
    block_output.blob_gas_used += calculate_total_blob_gas(tx)
1078
889
    effective_gas_fee = tx.gas * effective_gas_price
890
891
    gas = tx.gas - intrinsic.regular
892
    increment_nonce(tx_state, sender)
893
894
    sender_balance_after_gas_fee = (
895
        Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
896
    )
897
    set_account_balance(tx_state, sender, U256(sender_balance_after_gas_fee))
898
899
    access_list_addresses = set()
900
    access_list_storage_keys = set()
901
    access_list_addresses.add(block_env.coinbase)
902
    if has_access_list(tx):
903
        for access in tx.access_list:
904
            access_list_addresses.add(access.account)
905
            for slot in access.slots:
906
                access_list_storage_keys.add((access.account, slot))
907
908
    authorizations: Tuple[Authorization, ...] = ()
909
    if isinstance(tx, SetCodeTransaction):
910
        authorizations = tx.authorizations
911
912
    tx_env = vm.TransactionEnvironment(
913
        origin=sender,
914
        gas_price=effective_gas_price,
915
        gas=gas,
916
        access_list_addresses=access_list_addresses,
917
        access_list_storage_keys=access_list_storage_keys,
918
        state=tx_state,
919
        blob_versioned_hashes=blob_versioned_hashes,
920
        authorizations=authorizations,
921
        index_in_block=index,
922
        tx_hash=get_transaction_hash(encode_transaction(tx)),
923
    )
924
925
    message = prepare_message(block_env, tx_env, tx)
926
927
    tx_output = process_message_call(message)
928
929
    # For EIP-7623 we first calculate the execution_gas_used, which includes
930
    # the execution gas refund.
931
    tx_gas_used_before_refund = tx.gas - tx_output.gas_left
932
    tx_gas_refund = min(
933
        tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter)
934
    )
935
    tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund
936
937
    # Transactions with less execution_gas_used than the floor pay at the
938
    # floor cost.
939
    tx_gas_used_after_refund = max(
940
        tx_gas_used_after_refund, intrinsic.calldata_floor
941
    )
942
943
    tx_gas_left = tx.gas - tx_gas_used_after_refund
944
    gas_refund_amount = tx_gas_left * effective_gas_price
945
946
    # For non-1559 transactions effective_gas_price == tx.gas_price
947
    priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas
948
    transaction_fee = tx_gas_used_after_refund * priority_fee_per_gas
949
950
    # refund gas
951
    create_ether(tx_state, sender, U256(gas_refund_amount))
952
953
    # transfer miner fees
954
    create_ether(tx_state, block_env.coinbase, U256(transaction_fee))
955
956
    for address in tx_output.accounts_to_delete:
957
        destroy_account(tx_state, address)
958
959
    block_output.block_gas_used += tx_gas_used_after_refund
960
    block_output.blob_gas_used += tx_blob_gas_used
961
1079
    block_output.cumulative_gas_used += settlement.gas_used
1080
    receipt = make_receipt(
963
        tx, tx_output.error, block_output.block_gas_used, tx_output.logs
1081
        tx, tx_output.error, block_output.cumulative_gas_used, tx_output.logs
1082
    )
1083
1084
    receipt_key = rlp.encode(Uint(index))
1085
    block_output.receipt_keys += (receipt_key,)
1086
1087
    trie_set(
1088
        block_output.receipts_trie,
1089
        receipt_key,
1090
        receipt,
1091
    )
1092
1093
    block_output.block_logs += tx_output.logs
1094
977
    incorporate_tx_into_block(tx_state)
1095
    for address in tx_output.accounts_to_delete:
1096
        clear_account_preserving_balance(tx_env.state, address)
1097
1098
    incorporate_tx_into_block(
1099
        tx_env.state, block_env.block_access_list_builder
1100
    )

process_withdrawals

Increase the balance of the withdrawing account.

def process_withdrawals(​block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo5.vm.BlockOutputethereum.forks.amsterdam.vm.BlockOutput, ​​withdrawals: Tuple[Withdrawal, ...]​) -> None:
1108
    <snip>
1111
    wd_state = TransactionState(parent=block_env.state)
1112
1113
    for i, wd in enumerate(withdrawals):
1114
        trie_set(
1115
            block_output.withdrawals_trie,
1116
            rlp.encode(Uint(i)),
1117
            rlp.encode(wd),
1118
        )
1119
997
        create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9))
1120
        create_ether(wd_state, wd.address, U256(wd.amount) * GWEI_TO_WEI)
1121
999
    incorporate_tx_into_block(wd_state)
1122
    incorporate_tx_into_block(wd_state, block_env.block_access_list_builder)

check_gas_limit

Validates the gas limit for a block.

The bounds of the gas limit, max_adjustment_delta, is set as the quotient of the parent block's gas limit and the LIMIT_ADJUSTMENT_FACTOR. Therefore, if the gas limit that is passed through as a parameter is greater than or equal to the sum of the parent's gas and the adjustment delta then the limit for gas is too high and fails this function's check. Similarly, if the limit is less than or equal to the difference of the parent's gas and the adjustment delta or the predefined LIMIT_MINIMUM then this function's check fails because the gas limit doesn't allow for a sufficient or reasonable amount of gas to be used on a block.

Parameters

gas_limit : Gas limit to validate.

parent_gas_limit : Gas limit of the parent block.

Returns

check : bool True if gas limit constraints are satisfied, False otherwise.

def check_gas_limit(​gas_limit: Uint, ​​parent_gas_limit: Uint​) -> bool:
1126
    <snip>
1154
    max_adjustment_delta = parent_gas_limit // GasCosts.LIMIT_ADJUSTMENT_FACTOR
1155
    if gas_limit >= parent_gas_limit + max_adjustment_delta:
1156
        return False
1157
    if gas_limit <= parent_gas_limit - max_adjustment_delta:
1158
        return False
1159
    if gas_limit < GasCosts.LIMIT_MINIMUM:
1160
        return False
1161
1162
    return True