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

113
BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8)

ELASTICITY_MULTIPLIER

114
ELASTICITY_MULTIPLIER = Uint(2)

EMPTY_OMMER_HASH

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

SYSTEM_ADDRESS

116
SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe")

BEACON_ROOTS_ADDRESS

117
BEACON_ROOTS_ADDRESS = hex_to_address(
118
    "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02"
119
)

SYSTEM_TRANSACTION_GAS

120
SYSTEM_TRANSACTION_GAS = Uint(30000000)

SYSTEM_MAX_SSTORES_PER_CALL

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

121
SYSTEM_MAX_SSTORES_PER_CALL = Uint(16)

MAX_BLOB_GAS_PER_BLOCK

126
MAX_BLOB_GAS_PER_BLOCK: Final[U64] = (
127
    GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB
128
)

VERSIONED_HASH_VERSION_KZG

129
VERSIONED_HASH_VERSION_KZG = b"\x01"

GWEI_TO_WEI

130
GWEI_TO_WEI = U256(10**9)

WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS

132
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
133
    "0x00000961Ef480Eb55e80D19ad83579A64c007002"
134
)

CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS

135
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
136
    "0x0000BBdDc7CE488642fb579F8B00f3a590007251"
137
)

BUILDER_DEPOSIT_CONTRACT_ADDRESS

138
BUILDER_DEPOSIT_CONTRACT_ADDRESS = hex_to_address(
139
    "0x0000884d2AA32eAa155F59A2f24eFa73D9008282"
140
)

BUILDER_EXIT_CONTRACT_ADDRESS

141
BUILDER_EXIT_CONTRACT_ADDRESS = hex_to_address(
142
    "0x000014574A74c805590AFF9499fc7A690f008282"
143
)

HISTORY_STORAGE_ADDRESS

144
HISTORY_STORAGE_ADDRESS = hex_to_address(
145
    "0x0000F90827F1C53a10cb7A02335B175320002935"
146
)

MAX_BLOCK_SIZE

147
MAX_BLOCK_SIZE = 10_485_760

SAFETY_MARGIN

148
SAFETY_MARGIN = 2_097_152

MAX_RLP_BLOCK_SIZE

149
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN

BLOB_COUNT_LIMIT

150
BLOB_COUNT_LIMIT = 6

ChainContext

Chain context needed for block execution.

153
@final
154
@slotted_freezable
155
@dataclass
class ChainContext:

chain_id

Identify the chain for transaction signature recovery.

161
    chain_id: U64

block_hashes

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

164
    block_hashes: List[Hash32]

parent_header

Parent header used for header validation and system contracts.

167
    parent_header: Header | PreviousHeader

BlockChain

History and current state of the block chain.

171
@final
172
@dataclass
class BlockChain:

blocks

178
    blocks: List[Block]

state

179
    state: State

chain_id

180
    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:
184
    <snip>
203
    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]:
207
    <snip>
227
    recent_blocks = chain.blocks[-255:]
228
    # TODO: This function has not been tested rigorously
229
    if len(recent_blocks) == 0:
230
        return []
231
232
    recent_block_hashes = []
233
234
    for block in recent_blocks:
235
        prev_block_hash = block.header.parent_hash
236
        recent_block_hashes.append(prev_block_hash)
237
238
    # We are computing the hash only for the most recent block and not for
239
    # the rest of the blocks as they have successors which have the hash of
240
    # the current block as parent hash.
241
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
242
    recent_block_hashes.append(most_recent_block_hash)
243
244
    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:
248
    <snip>
227
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
228
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
229
230
    validate_header(chain, block.header)
231
    if block.ommers != ():
232
        raise InvalidBlock
233
234
    block_state = BlockState(pre_state=chain.state)
235
236
    block_env = vm.BlockEnvironment(
270
    chain_context = ChainContext(
271
        chain_id=chain.chain_id,
238
        state=block_state,
239
        block_gas_limit=block.header.gas_limit,
272
        block_hashes=get_last_256_block_hashes(chain),
241
        coinbase=block.header.coinbase,
242
        number=block.header.number,
243
        base_fee_per_gas=block.header.base_fee_per_gas,
244
        time=block.header.timestamp,
245
        prev_randao=block.header.prev_randao,
246
        excess_blob_gas=block.header.excess_blob_gas,
247
        parent_beacon_block_root=block.header.parent_beacon_block_root,
273
        parent_header=chain.blocks[-1].header,
274
    )
275
250
    block_output = apply_body(
251
        block_env=block_env,
252
        transactions=block.transactions,
253
        withdrawals=block.withdrawals,
254
    )
255
    block_diff = extract_block_diff(block_state)
256
    block_state_root, _ = chain.state.compute_state_root_and_trie_changes(
257
        block_diff.account_changes, block_diff.storage_changes
258
    )
259
    transactions_root = root(block_output.transactions_trie)
260
    receipt_root = root(block_output.receipts_trie)
261
    block_logs_bloom = logs_bloom(block_output.block_logs)
262
    withdrawals_root = root(block_output.withdrawals_trie)
263
    requests_hash = compute_requests_hash(block_output.requests)
264
265
    if block_output.block_gas_used != block.header.gas_used:
266
        raise InvalidBlock(
267
            f"{block_output.block_gas_used} != {block.header.gas_used}"
268
        )
269
    if transactions_root != block.header.transactions_root:
270
        raise InvalidBlock
271
    if block_state_root != block.header.state_root:
272
        raise InvalidBlock
273
    if receipt_root != block.header.receipt_root:
274
        raise InvalidBlock
275
    if block_logs_bloom != block.header.bloom:
276
        raise InvalidBlock
277
    if withdrawals_root != block.header.withdrawals_root:
278
        raise InvalidBlock
279
    if block_output.blob_gas_used != block.header.blob_gas_used:
280
        raise InvalidBlock
281
    if requests_hash != block.header.requests_hash:
282
        raise InvalidBlock
276
    block_diff = execute_block(block, chain.state, chain_context)
277
278
    apply_changes_to_state(chain.state, block_diff)
279
    chain.blocks.append(block)
280
    if len(chain.blocks) > 255:
281
        # Real clients have to store more blocks to deal with reorgs, but the
282
        # protocol only requires the last 255
283
        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:
291
    <snip>
311
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
312
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
313
314
    parent_header = chain_context.parent_header
315
    validate_header(parent_header, block.header)
316
317
    if block.ommers != ():
318
        raise InvalidBlock
319
320
    block_state = BlockState(pre_state=pre_state)
321
322
    block_env = vm.BlockEnvironment(
323
        chain_id=chain_context.chain_id,
324
        state=block_state,
325
        block_gas_limit=block.header.gas_limit,
326
        block_hashes=chain_context.block_hashes,
327
        coinbase=block.header.coinbase,
328
        number=block.header.number,
329
        base_fee_per_gas=block.header.base_fee_per_gas,
330
        time=block.header.timestamp,
331
        prev_randao=block.header.prev_randao,
332
        excess_blob_gas=block.header.excess_blob_gas,
333
        parent_beacon_block_root=block.header.parent_beacon_block_root,
334
        block_access_list_builder=BlockAccessListBuilder(),
335
        slot_number=block.header.slot_number,
336
    )
337
338
    block_output = apply_body(
339
        block_env=block_env,
340
        transactions=block.transactions,
341
        withdrawals=block.withdrawals,
342
    )
343
    block_diff = extract_block_diff(block_state)
344
    block_state_root, _ = pre_state.compute_state_root_and_trie_changes(
345
        block_diff.account_changes, block_diff.storage_changes
346
    )
347
    transactions_root = root(block_output.transactions_trie)
348
    receipt_root = root(block_output.receipts_trie)
349
    block_logs_bloom = logs_bloom(block_output.block_logs)
350
    withdrawals_root = root(block_output.withdrawals_trie)
351
    requests_hash = compute_requests_hash(block_output.requests)
352
    computed_block_access_list_hash = hash_block_access_list(
353
        block_output.block_access_list
354
    )
355
356
    block_gas_used = max(
357
        block_output.block_gas_used,
358
        block_output.block_state_gas_used,
359
    )
360
    if block_gas_used != block.header.gas_used:
361
        raise InvalidBlock(f"{block_gas_used} != {block.header.gas_used}")
362
    if transactions_root != block.header.transactions_root:
363
        raise InvalidBlock
364
    if block_state_root != block.header.state_root:
365
        raise InvalidBlock
366
    if receipt_root != block.header.receipt_root:
367
        raise InvalidBlock
368
    if block_logs_bloom != block.header.bloom:
369
        raise InvalidBlock
370
    if withdrawals_root != block.header.withdrawals_root:
371
        raise InvalidBlock
372
    if block_output.blob_gas_used != block.header.blob_gas_used:
373
        raise InvalidBlock
374
    if requests_hash != block.header.requests_hash:
375
        raise InvalidBlock
376
    if computed_block_access_list_hash != block.header.block_access_list_hash:
377
        raise InvalidBlock("Invalid block access list hash")
378
379
    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:
388
    <snip>
408
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
409
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
410
        raise InvalidBlock
411
412
    if parent_gas_used == parent_gas_target:
413
        expected_base_fee_per_gas = parent_base_fee_per_gas
414
    elif parent_gas_used > parent_gas_target:
415
        gas_used_delta = parent_gas_used - parent_gas_target
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 = max(
421
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
422
            Uint(1),
423
        )
424
425
        expected_base_fee_per_gas = (
426
            parent_base_fee_per_gas + base_fee_per_gas_delta
427
        )
428
    else:
429
        gas_used_delta = parent_gas_target - parent_gas_used
430
431
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
432
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
433
434
        base_fee_per_gas_delta = (
435
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
436
        )
437
438
        expected_base_fee_per_gas = (
439
            parent_base_fee_per_gas - base_fee_per_gas_delta
440
        )
441
442
    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:
448
    <snip>
466
    if header.number < Uint(1):
467
        raise InvalidBlock
376
377
    parent_header = chain.blocks[-1].header
468
469
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
470
    if header.excess_blob_gas != excess_blob_gas:
471
        raise InvalidBlock
472
473
    if header.gas_used > header.gas_limit:
474
        raise InvalidBlock
475
476
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
477
        header.gas_limit,
478
        parent_header.gas_limit,
479
        parent_header.gas_used,
480
        parent_header.base_fee_per_gas,
481
    )
482
    if expected_base_fee_per_gas != header.base_fee_per_gas:
483
        raise InvalidBlock
484
    if header.timestamp <= parent_header.timestamp:
485
        raise InvalidBlock
486
    if header.number != parent_header.number + Uint(1):
487
        raise InvalidBlock
488
    if len(header.extra_data) > 32:
489
        raise InvalidBlock
490
    if header.difficulty != 0:
491
        raise InvalidBlock
492
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
493
        raise InvalidBlock
494
    if header.ommers_hash != EMPTY_OMMER_HASH:
495
        raise InvalidBlock
496
497
    block_parent_hash = keccak256(rlp.encode(parent_header))
498
    if header.parent_hash != block_parent_hash:
499
        raise InvalidBlock

check_transaction

Check if the transaction is includable in the block.

Parameters

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

Returns

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

Raises

InvalidBlock : If the transaction is not includable. 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. PriorityFeeGreaterThanMaxFeeError : If the priority fee is greater than the maximum fee per gas. 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, ​​sender: Address, ​​tx_state: TransactionState) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]:
509
    <snip>
478
    gas_available = block_env.block_gas_limit - block_output.block_gas_used
569
    regular_gas_available = (
570
        block_env.block_gas_limit - block_output.block_gas_used
571
    )
572
    state_gas_available = (
573
        block_env.block_gas_limit - block_output.block_state_gas_used
574
    )
575
    blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used
576
481
    if tx.gas > gas_available:
482
        raise GasUsedExceedsLimitError("gas used exceeds limit")
577
    # EIP-8037 per-dimension inclusion check.
578
    if min(TX_MAX_GAS_LIMIT, tx.gas) > regular_gas_available:
579
        raise GasUsedExceedsLimitError("regular gas used exceeds limit")
580
581
    if tx.gas > state_gas_available:
582
        raise GasUsedExceedsLimitError("state gas used exceeds limit")
583
584
    tx_blob_gas_used = calculate_total_blob_gas(tx)
585
    if tx_blob_gas_used > blob_gas_available:
586
        raise BlobGasLimitExceededError("blob gas limit exceeded")
587
488
    tx_chain_id = chain_id(tx)
489
    if tx_chain_id is not None and tx_chain_id != block_env.chain_id:
490
        raise WrongChainIdError(
491
            expected=block_env.chain_id,
492
            actual=tx_chain_id,
493
        )
494
495
    sender_address = recover_sender(tx)
496
    sender_account = get_account(tx_state, sender_address)
588
    sender_account = get_account(tx_state, sender)
589
590
    if isinstance(tx, FeeMarketCapableTransaction):
591
        if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
592
            raise PriorityFeeGreaterThanMaxFeeError(
593
                "priority fee greater than max fee"
594
            )
595
        if tx.max_fee_per_gas < block_env.base_fee_per_gas:
596
            raise InsufficientMaxFeePerGasError(
597
                tx.max_fee_per_gas, block_env.base_fee_per_gas
598
            )
599
600
        priority_fee_per_gas = min(
601
            tx.max_priority_fee_per_gas,
602
            tx.max_fee_per_gas - block_env.base_fee_per_gas,
603
        )
604
        effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas
605
        max_gas_fee = tx.gas * tx.max_fee_per_gas
606
    else:
607
        if tx.gas_price < block_env.base_fee_per_gas:
608
            raise InvalidBlock
609
        effective_gas_price = tx.gas_price
610
        max_gas_fee = tx.gas * tx.gas_price
611
612
    if isinstance(tx, BlobTransaction):
613
        blob_count = len(tx.blob_versioned_hashes)
614
        if blob_count == 0:
615
            raise NoBlobDataError("no blob data in transaction")
616
        if blob_count > BLOB_COUNT_LIMIT:
617
            raise BlobCountExceededError(
618
                f"Tx has {blob_count} blobs. Max allowed: {BLOB_COUNT_LIMIT}"
619
            )
620
        for blob_versioned_hash in tx.blob_versioned_hashes:
621
            if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG:
622
                raise InvalidBlobVersionedHashError(
623
                    "invalid blob versioned hash"
624
                )
625
626
        blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas)
627
        if Uint(tx.max_fee_per_blob_gas) < blob_gas_price:
628
            raise InsufficientMaxFeePerBlobGasError(
629
                "insufficient max fee per blob gas"
630
            )
631
632
        max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint(
633
            tx.max_fee_per_blob_gas
634
        )
635
        blob_versioned_hashes = tx.blob_versioned_hashes
636
    else:
637
        blob_versioned_hashes = ()
638
639
    if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
640
        if not isinstance(tx.to, Address):
641
            raise TransactionTypeContractCreationError(tx)
642
643
    if isinstance(tx, SetCodeTransaction):
644
        if not any(tx.authorizations):
645
            raise EmptyAuthorizationListError("empty authorization list")
646
647
    if sender_account.nonce > Uint(tx.nonce):
648
        raise NonceMismatchError("nonce too low")
649
    elif sender_account.nonce < Uint(tx.nonce):
650
        raise NonceMismatchError("nonce too high")
651
652
    if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value):
653
        raise InsufficientBalanceError("insufficient sender balance")
654
    sender_code = get_code(tx_state, sender_account.code_hash)
655
    if sender_account.code_hash != EMPTY_CODE_HASH and not is_valid_delegation(
656
        sender_code
657
    ):
658
        raise InvalidSenderError("not EOA")
659
660
    return (
569
        sender_address,
570
        effective_gas_price,
661
        effective_gas_price,
662
        blob_versioned_hashes,
663
        tx_blob_gas_used,
664
    )

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:
673
    <snip>
694
    receipt = Receipt(
695
        succeeded=error is None,
696
        cumulative_gas_used=cumulative_gas_used,
697
        bloom=logs_bloom(logs),
698
        logs=logs,
699
    )
700
701
    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 : MessageCallOutput Output of processing the system transaction.

def process_checked_system_transaction(block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
709
    <snip>
728
    # Pre-check that the system contract has code. We use a throwaway
729
    # TransactionState here that is *never* propagated back to BlockState
730
    # (no incorporate_tx_into_block call); the same get_account / get_code
731
    # lookups are performed and properly tracked by
732
    # process_unchecked_system_transaction below, which this function
733
    # always calls. Reading via a TransactionState (rather than directly
734
    # against pre_state) lets us see system contracts deployed earlier in
735
    # the same block — see EIP-7002 and EIP-7251 for this edge case.
736
    untracked_state = TransactionState(parent=block_env.state)
737
    system_contract_code = get_code(
738
        untracked_state,
739
        get_account(untracked_state, target_address).code_hash,
740
    )
741
742
    if len(system_contract_code) == 0:
743
        raise InvalidBlock(
744
            f"System contract address {target_address.hex()} does not "
745
            "contain code"
746
        )
747
748
    system_tx_output = process_unchecked_system_transaction(
749
        block_env,
750
        target_address,
751
        data,
752
    )
753
754
    if system_tx_output.error:
755
        raise InvalidBlock(
756
            f"System contract ({target_address.hex()}) call failed: "
757
            f"{system_tx_output.error}"
758
        )
759
760
    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 : MessageCallOutput Output of processing the system transaction.

def process_unchecked_system_transaction(block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
768
    <snip>
787
    system_tx_state = TransactionState(parent=block_env.state)
788
    system_contract_code = get_code(
789
        system_tx_state,
790
        get_account(system_tx_state, target_address).code_hash,
791
    )
792
793
    tx_env = vm.TransactionEnvironment(
794
        origin=SYSTEM_ADDRESS,
795
        recipient=target_address,
796
        value=U256(0),
797
        gas_price=block_env.base_fee_per_gas,
798
        gas=SYSTEM_TRANSACTION_GAS,
799
        state_gas_reservoir=(
800
            StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL
801
        ),
802
        access_list_addresses=set(),
803
        access_list_storage_keys=set(),
804
        state=system_tx_state,
805
        blob_versioned_hashes=(),
806
        authorizations=(),
807
        index_in_block=None,
808
        tx_hash=None,
809
        intrinsic_regular_gas=Uint(0),
810
        intrinsic_state_gas=Uint(0),
811
    )
812
813
    system_tx_message = Message(
814
        block_env=block_env,
815
        tx_env=tx_env,
816
        caller=SYSTEM_ADDRESS,
817
        target=target_address,
818
        gas=SYSTEM_TRANSACTION_GAS,
819
        state_gas_reservoir=(
820
            StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL
821
        ),
822
        value=U256(0),
823
        data=data,
824
        code=system_contract_code,
825
        depth=Uint(0),
826
        current_target=target_address,
827
        code_address=target_address,
828
        should_transfer_value=False,
829
        is_static=False,
830
        accessed_addresses=set(),
831
        accessed_storage_keys=set(),
832
        disable_precompiles=False,
833
        parent_evm=None,
834
    )
835
836
    system_tx_output = process_message_call(system_tx_message)
837
737
    incorporate_tx_into_block(system_tx_state)
838
    incorporate_tx_into_block(
839
        system_tx_state, block_env.block_access_list_builder
840
    )
841
842
    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:
850
    <snip>
875
    block_output = vm.BlockOutput()
876
877
    process_unchecked_system_transaction(
878
        block_env=block_env,
879
        target_address=BEACON_ROOTS_ADDRESS,
880
        data=block_env.parent_beacon_block_root,
881
    )
882
883
    process_unchecked_system_transaction(
884
        block_env=block_env,
885
        target_address=HISTORY_STORAGE_ADDRESS,
886
        data=block_env.block_hashes[-1],  # The parent hash
887
    )
888
889
    for i, tx in enumerate(map(decode_transaction, transactions)):
890
        process_transaction(block_env, block_output, tx, Uint(i))
891
892
    # EIP-7928: Post-execution operations use index N+1
893
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
894
        ulen(transactions) + Uint(1)
895
    )
896
897
    process_withdrawals(block_env, block_output, withdrawals)
898
899
    process_general_purpose_requests(
900
        block_env=block_env,
901
        block_output=block_output,
902
    )
903
904
    block_output.block_access_list = build_block_access_list(
905
        block_env.block_access_list_builder, block_env.state
906
    )
907
908
    # Validate block access list gas limit constraint (EIP-7928)
909
    validate_block_access_list_gas_limit(
910
        block_access_list=block_output.block_access_list,
911
        block_gas_limit=block_env.block_gas_limit,
912
    )
913
914
    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:
921
    <snip>
932
    # Requests are to be in ascending order of request type
933
    deposit_requests = parse_deposit_requests(block_output)
934
    requests_from_execution = block_output.requests
935
    if len(deposit_requests) > 0:
936
        requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests)
937
938
    system_withdrawal_tx_output = process_checked_system_transaction(
939
        block_env=block_env,
940
        target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
941
        data=b"",
942
    )
943
944
    if len(system_withdrawal_tx_output.return_data) > 0:
945
        requests_from_execution.append(
946
            WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data
947
        )
948
949
    system_consolidation_tx_output = process_checked_system_transaction(
950
        block_env=block_env,
951
        target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
952
        data=b"",
953
    )
954
955
    if len(system_consolidation_tx_output.return_data) > 0:
956
        requests_from_execution.append(
957
            CONSOLIDATION_REQUEST_TYPE
958
            + system_consolidation_tx_output.return_data
959
        )
960
961
    system_builder_deposit_tx_output = process_checked_system_transaction(
962
        block_env=block_env,
963
        target_address=BUILDER_DEPOSIT_CONTRACT_ADDRESS,
964
        data=b"",
965
    )
966
967
    if len(system_builder_deposit_tx_output.return_data) > 0:
968
        requests_from_execution.append(
969
            BUILDER_DEPOSIT_REQUEST_TYPE
970
            + system_builder_deposit_tx_output.return_data
971
        )
972
973
    system_builder_exit_tx_output = process_checked_system_transaction(
974
        block_env=block_env,
975
        target_address=BUILDER_EXIT_CONTRACT_ADDRESS,
976
        data=b"",
977
    )
978
979
    if len(system_builder_exit_tx_output.return_data) > 0:
980
        requests_from_execution.append(
981
            BUILDER_EXIT_REQUEST_TYPE
982
            + system_builder_exit_tx_output.return_data
983
        )

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:
992
    <snip>
1016
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
1017
        index + Uint(1)
1018
    )
1019
    tx_state = TransactionState(parent=block_env.state)
1020
1021
    trie_set(
1022
        block_output.transactions_trie,
1023
        rlp.encode(index),
1024
        encode_transaction(tx),
1025
    )
1026
882
    intrinsic = validate_transaction(tx)
1027
    tx_chain_id = chain_id(tx)
1028
    if tx_chain_id is not None and tx_chain_id != block_env.chain_id:
1029
        raise WrongChainIdError(
1030
            expected=block_env.chain_id,
1031
            actual=tx_chain_id,
1032
        )
1033
1034
    sender = recover_sender(tx)
1035
    intrinsic = validate_transaction(tx, sender)
1036
1037
    intrinsic_gas = Uint(intrinsic.regular) + Uint(intrinsic.state)
1038
1039
    (
885
        sender,
886
        effective_gas_price,
1040
        effective_gas_price,
1041
        blob_versioned_hashes,
1042
        tx_blob_gas_used,
1043
    ) = check_transaction(
1044
        block_env=block_env,
1045
        block_output=block_output,
1046
        tx=tx,
1047
        sender=sender,
1048
        tx_state=tx_state,
1049
    )
1050
1051
    sender_account = get_account(tx_state, sender)
1052
1053
    if isinstance(tx, BlobTransaction):
1054
        blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx)
1055
    else:
1056
        blob_gas_fee = Uint(0)
1057
1058
    effective_gas_fee = tx.gas * effective_gas_price
1059
905
    gas = tx.gas - intrinsic.regular
1060
    # Split execution gas into gas_left (capped by remaining regular gas
1061
    # budget) and state_gas_reservoir.
1062
    execution_gas = tx.gas - intrinsic_gas
1063
    regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.regular
1064
    gas = min(regular_gas_budget, execution_gas)
1065
    state_gas_reservoir = Uint(execution_gas - gas)
1066
1067
    increment_nonce(tx_state, sender)
1068
1069
    sender_balance_after_gas_fee = (
1070
        Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
1071
    )
1072
    set_account_balance(tx_state, sender, U256(sender_balance_after_gas_fee))
1073
1074
    access_list_addresses = set()
1075
    access_list_storage_keys = set()
1076
    access_list_addresses.add(block_env.coinbase)
1077
    if has_access_list(tx):
1078
        for access in tx.access_list:
1079
            access_list_addresses.add(access.account)
1080
            for slot in access.slots:
1081
                access_list_storage_keys.add((access.account, slot))
1082
1083
    authorizations: Tuple[Authorization, ...] = ()
1084
    if isinstance(tx, SetCodeTransaction):
1085
        authorizations = tx.authorizations
1086
1087
    tx_env = vm.TransactionEnvironment(
1088
        origin=sender,
1089
        recipient=tx.to,
1090
        value=tx.value,
1091
        gas_price=effective_gas_price,
1092
        gas=gas,
1093
        state_gas_reservoir=state_gas_reservoir,
1094
        access_list_addresses=access_list_addresses,
1095
        access_list_storage_keys=access_list_storage_keys,
1096
        state=tx_state,
1097
        blob_versioned_hashes=blob_versioned_hashes,
1098
        authorizations=authorizations,
1099
        index_in_block=index,
1100
        tx_hash=get_transaction_hash(encode_transaction(tx)),
1101
        intrinsic_regular_gas=intrinsic.regular,
1102
        intrinsic_state_gas=intrinsic.state,
1103
    )
1104
939
    message = prepare_message(block_env, tx_env, tx)
1105
    message = prepare_message(
1106
        block_env,
1107
        tx_env,
1108
        tx,
1109
    )
1110
1111
    tx_output = process_message_call(message)
1112
943
    # For EIP-7623 we first calculate the execution_gas_used, which includes
944
    # the execution gas refund.
945
    tx_gas_used_before_refund = tx.gas - tx_output.gas_left
1113
    if isinstance(tx.to, Bytes0) and (
1114
        tx_output.error is not None or tx_output.created_target_alive
1115
    ):
1116
        new_account_refund = StateGasCosts.NEW_ACCOUNT
1117
        tx_output.state_gas_left += new_account_refund
1118
        tx_output.state_refund += new_account_refund
1119
1120
    tx_gas_used_before_refund = (
1121
        tx.gas - tx_output.gas_left - tx_output.state_gas_left
1122
    )
1123
    tx_gas_refund = min(
1124
        tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter)
1125
    )
1126
    tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund
1127
1128
    # Transactions with less execution_gas_used than the floor pay at the
1129
    # floor cost.
953
    tx_gas_used_after_refund = max(
954
        tx_gas_used_after_refund, intrinsic.calldata_floor
955
    )
1130
    tx_gas_used = max(tx_gas_used_after_refund, intrinsic.calldata_floor)
1131
957
    tx_gas_left = tx.gas - tx_gas_used_after_refund
1132
    tx_gas_left = tx.gas - tx_gas_used
1133
    gas_refund_amount = tx_gas_left * effective_gas_price
1134
1135
    # For non-1559 transactions effective_gas_price == tx.gas_price
1136
    priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas
962
    transaction_fee = tx_gas_used_after_refund * priority_fee_per_gas
1137
    transaction_fee = tx_gas_used * priority_fee_per_gas
1138
1139
    # refund gas
1140
    create_ether(tx_state, sender, U256(gas_refund_amount))
1141
1142
    # transfer miner fees
1143
    create_ether(tx_state, block_env.coinbase, U256(transaction_fee))
1144
970
    for address in tx_output.accounts_to_delete:
971
        destroy_account(tx_state, address)
972
973
    block_output.block_gas_used += tx_gas_used_after_refund
1145
    tx_state_gas = (
1146
        int(tx_env.intrinsic_state_gas)
1147
        + tx_output.state_gas_used
1148
        - int(tx_output.state_refund)
1149
    )
1150
    # Defensive guard for Uint conversion: State refunds never exceed
1151
    # the state charges so the value is non-negative.
1152
    tx_regular_gas = tx_gas_used_before_refund - Uint(max(0, tx_state_gas))
1153
    block_output.block_gas_used += tx_regular_gas
1154
    block_output.block_state_gas_used += Uint(max(0, tx_state_gas))
1155
    block_output.blob_gas_used += tx_blob_gas_used
1156
1157
    block_output.cumulative_gas_used += tx_gas_used
1158
    receipt = make_receipt(
977
        tx, tx_output.error, block_output.block_gas_used, tx_output.logs
1159
        tx, tx_output.error, block_output.cumulative_gas_used, tx_output.logs
1160
    )
1161
1162
    receipt_key = rlp.encode(Uint(index))
1163
    block_output.receipt_keys += (receipt_key,)
1164
1165
    trie_set(
1166
        block_output.receipts_trie,
1167
        receipt_key,
1168
        receipt,
1169
    )
1170
1171
    block_output.block_logs += tx_output.logs
1172
991
    incorporate_tx_into_block(tx_state)
1173
    for address in tx_output.accounts_to_delete:
1174
        clear_account_preserving_balance(tx_state, address)
1175
1176
    incorporate_tx_into_block(tx_state, block_env.block_access_list_builder)

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:
1184
    <snip>
1187
    wd_state = TransactionState(parent=block_env.state)
1188
1189
    for i, wd in enumerate(withdrawals):
1190
        trie_set(
1191
            block_output.withdrawals_trie,
1192
            rlp.encode(Uint(i)),
1193
            rlp.encode(wd),
1194
        )
1195
1011
        create_ether(wd_state, wd.address, wd.amount * U256(10**9))
1196
        create_ether(wd_state, wd.address, wd.amount * GWEI_TO_WEI)
1197
1013
    incorporate_tx_into_block(wd_state)
1198
    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:
1202
    <snip>
1230
    max_adjustment_delta = parent_gas_limit // GasCosts.LIMIT_ADJUSTMENT_FACTOR
1231
    if gas_limit >= parent_gas_limit + max_adjustment_delta:
1232
        return False
1233
    if gas_limit <= parent_gas_limit - max_adjustment_delta:
1234
        return False
1235
    if gas_limit < GasCosts.LIMIT_MINIMUM:
1236
        return False
1237
1238
    return True