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

112
BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8)

ELASTICITY_MULTIPLIER

113
ELASTICITY_MULTIPLIER = Uint(2)

EMPTY_OMMER_HASH

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

SYSTEM_ADDRESS

115
SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe")

BEACON_ROOTS_ADDRESS

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

SYSTEM_TRANSACTION_GAS

119
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.

120
SYSTEM_MAX_SSTORES_PER_CALL = Uint(16)

MAX_BLOB_GAS_PER_BLOCK

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

VERSIONED_HASH_VERSION_KZG

128
VERSIONED_HASH_VERSION_KZG = b"\x01"

GWEI_TO_WEI

129
GWEI_TO_WEI = U256(10**9)

WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS

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

CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS

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

HISTORY_STORAGE_ADDRESS

137
HISTORY_STORAGE_ADDRESS = hex_to_address(
138
    "0x0000F90827F1C53a10cb7A02335B175320002935"
139
)

MAX_BLOCK_SIZE

140
MAX_BLOCK_SIZE = 10_485_760

SAFETY_MARGIN

141
SAFETY_MARGIN = 2_097_152

MAX_RLP_BLOCK_SIZE

142
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN

BLOB_COUNT_LIMIT

143
BLOB_COUNT_LIMIT = 6

ChainContext

Chain context needed for block execution.

146
@final
147
@slotted_freezable
148
@dataclass
class ChainContext:

chain_id

Identify the chain for transaction signature recovery.

154
    chain_id: U64

block_hashes

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

157
    block_hashes: List[Hash32]

parent_header

Parent header used for header validation and system contracts.

160
    parent_header: Header | PreviousHeader

BlockChain

History and current state of the block chain.

164
@final
165
@dataclass
class BlockChain:

blocks

171
    blocks: List[Block]

state

172
    state: State

chain_id

173
    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:
177
    <snip>
196
    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]:
200
    <snip>
220
    recent_blocks = chain.blocks[-255:]
221
    # TODO: This function has not been tested rigorously
222
    if len(recent_blocks) == 0:
223
        return []
224
225
    recent_block_hashes = []
226
227
    for block in recent_blocks:
228
        prev_block_hash = block.header.parent_hash
229
        recent_block_hashes.append(prev_block_hash)
230
231
    # We are computing the hash only for the most recent block and not for
232
    # the rest of the blocks as they have successors which have the hash of
233
    # the current block as parent hash.
234
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
235
    recent_block_hashes.append(most_recent_block_hash)
236
237
    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:
241
    <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(
263
    chain_context = ChainContext(
264
        chain_id=chain.chain_id,
238
        state=block_state,
239
        block_gas_limit=block.header.gas_limit,
265
        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,
266
        parent_header=chain.blocks[-1].header,
267
    )
268
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
269
    block_diff = execute_block(block, chain.state, chain_context)
270
271
    apply_changes_to_state(chain.state, block_diff)
272
    chain.blocks.append(block)
273
    if len(chain.blocks) > 255:
274
        # Real clients have to store more blocks to deal with reorgs, but the
275
        # protocol only requires the last 255
276
        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:
284
    <snip>
304
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
305
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
306
307
    parent_header = chain_context.parent_header
308
    validate_header(parent_header, block.header)
309
310
    if block.ommers != ():
311
        raise InvalidBlock
312
313
    block_state = BlockState(pre_state=pre_state)
314
315
    block_env = vm.BlockEnvironment(
316
        chain_id=chain_context.chain_id,
317
        state=block_state,
318
        block_gas_limit=block.header.gas_limit,
319
        block_hashes=chain_context.block_hashes,
320
        coinbase=block.header.coinbase,
321
        number=block.header.number,
322
        base_fee_per_gas=block.header.base_fee_per_gas,
323
        time=block.header.timestamp,
324
        prev_randao=block.header.prev_randao,
325
        excess_blob_gas=block.header.excess_blob_gas,
326
        parent_beacon_block_root=block.header.parent_beacon_block_root,
327
        block_access_list_builder=BlockAccessListBuilder(),
328
        slot_number=block.header.slot_number,
329
    )
330
331
    block_output = apply_body(
332
        block_env=block_env,
333
        transactions=block.transactions,
334
        withdrawals=block.withdrawals,
335
    )
336
    block_diff = extract_block_diff(block_state)
337
    block_state_root, _ = pre_state.compute_state_root_and_trie_changes(
338
        block_diff.account_changes, block_diff.storage_changes
339
    )
340
    transactions_root = root(block_output.transactions_trie)
341
    receipt_root = root(block_output.receipts_trie)
342
    block_logs_bloom = logs_bloom(block_output.block_logs)
343
    withdrawals_root = root(block_output.withdrawals_trie)
344
    requests_hash = compute_requests_hash(block_output.requests)
345
    computed_block_access_list_hash = hash_block_access_list(
346
        block_output.block_access_list
347
    )
348
349
    block_gas_used = max(
350
        block_output.block_gas_used,
351
        block_output.block_state_gas_used,
352
    )
353
    if block_gas_used != block.header.gas_used:
354
        raise InvalidBlock(f"{block_gas_used} != {block.header.gas_used}")
355
    if transactions_root != block.header.transactions_root:
356
        raise InvalidBlock
357
    if block_state_root != block.header.state_root:
358
        raise InvalidBlock
359
    if receipt_root != block.header.receipt_root:
360
        raise InvalidBlock
361
    if block_logs_bloom != block.header.bloom:
362
        raise InvalidBlock
363
    if withdrawals_root != block.header.withdrawals_root:
364
        raise InvalidBlock
365
    if block_output.blob_gas_used != block.header.blob_gas_used:
366
        raise InvalidBlock
367
    if requests_hash != block.header.requests_hash:
368
        raise InvalidBlock
369
    if computed_block_access_list_hash != block.header.block_access_list_hash:
370
        raise InvalidBlock("Invalid block access list hash")
371
372
    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:
381
    <snip>
401
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
402
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
403
        raise InvalidBlock
404
405
    if parent_gas_used == parent_gas_target:
406
        expected_base_fee_per_gas = parent_base_fee_per_gas
407
    elif parent_gas_used > parent_gas_target:
408
        gas_used_delta = parent_gas_used - parent_gas_target
409
410
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
411
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
412
413
        base_fee_per_gas_delta = max(
414
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
415
            Uint(1),
416
        )
417
418
        expected_base_fee_per_gas = (
419
            parent_base_fee_per_gas + base_fee_per_gas_delta
420
        )
421
    else:
422
        gas_used_delta = parent_gas_target - parent_gas_used
423
424
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
425
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
426
427
        base_fee_per_gas_delta = (
428
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
429
        )
430
431
        expected_base_fee_per_gas = (
432
            parent_base_fee_per_gas - base_fee_per_gas_delta
433
        )
434
435
    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:
441
    <snip>
459
    if header.number < Uint(1):
460
        raise InvalidBlock
376
377
    parent_header = chain.blocks[-1].header
461
462
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
463
    if header.excess_blob_gas != excess_blob_gas:
464
        raise InvalidBlock
465
466
    if header.gas_used > header.gas_limit:
467
        raise InvalidBlock
468
469
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
470
        header.gas_limit,
471
        parent_header.gas_limit,
472
        parent_header.gas_used,
473
        parent_header.base_fee_per_gas,
474
    )
475
    if expected_base_fee_per_gas != header.base_fee_per_gas:
476
        raise InvalidBlock
477
    if header.timestamp <= parent_header.timestamp:
478
        raise InvalidBlock
479
    if header.number != parent_header.number + Uint(1):
480
        raise InvalidBlock
481
    if len(header.extra_data) > 32:
482
        raise InvalidBlock
483
    if header.difficulty != 0:
484
        raise InvalidBlock
485
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
486
        raise InvalidBlock
487
    if header.ommers_hash != EMPTY_OMMER_HASH:
488
        raise InvalidBlock
489
490
    block_parent_hash = keccak256(rlp.encode(parent_header))
491
    if header.parent_hash != block_parent_hash:
492
        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. 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, ​​tx_state: TransactionState) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]:
501
    <snip>
478
    gas_available = block_env.block_gas_limit - block_output.block_gas_used
561
    regular_gas_available = (
562
        block_env.block_gas_limit - block_output.block_gas_used
563
    )
564
    state_gas_available = (
565
        block_env.block_gas_limit - block_output.block_state_gas_used
566
    )
567
    blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used
568
481
    if tx.gas > gas_available:
482
        raise GasUsedExceedsLimitError("gas used exceeds limit")
569
    # EIP-8037 per-dimension inclusion check.
570
    if min(TX_MAX_GAS_LIMIT, tx.gas) > regular_gas_available:
571
        raise GasUsedExceedsLimitError("regular gas used exceeds limit")
572
573
    if tx.gas > state_gas_available:
574
        raise GasUsedExceedsLimitError("state gas used exceeds limit")
575
576
    tx_blob_gas_used = calculate_total_blob_gas(tx)
577
    if tx_blob_gas_used > blob_gas_available:
578
        raise BlobGasLimitExceededError("blob gas limit exceeded")
579
580
    tx_chain_id = chain_id(tx)
581
    if tx_chain_id is not None and tx_chain_id != block_env.chain_id:
582
        raise WrongChainIdError(
583
            expected=block_env.chain_id,
584
            actual=tx_chain_id,
585
        )
586
587
    sender_address = recover_sender(tx)
588
    sender_account = get_account(tx_state, sender_address)
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 (
661
        sender_address,
662
        effective_gas_price,
663
        blob_versioned_hashes,
664
        tx_blob_gas_used,
665
    )

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:
674
    <snip>
695
    receipt = Receipt(
696
        succeeded=error is None,
697
        cumulative_gas_used=cumulative_gas_used,
698
        bloom=logs_bloom(logs),
699
        logs=logs,
700
    )
701
702
    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:
710
    <snip>
729
    # Pre-check that the system contract has code. We use a throwaway
730
    # TransactionState here that is *never* propagated back to BlockState
731
    # (no incorporate_tx_into_block call); the same get_account / get_code
732
    # lookups are performed and properly tracked by
733
    # process_unchecked_system_transaction below, which this function
734
    # always calls. Reading via a TransactionState (rather than directly
735
    # against pre_state) lets us see system contracts deployed earlier in
736
    # the same block — see EIP-7002 and EIP-7251 for this edge case.
737
    untracked_state = TransactionState(parent=block_env.state)
738
    system_contract_code = get_code(
739
        untracked_state,
740
        get_account(untracked_state, target_address).code_hash,
741
    )
742
743
    if len(system_contract_code) == 0:
744
        raise InvalidBlock(
745
            f"System contract address {target_address.hex()} does not "
746
            "contain code"
747
        )
748
749
    system_tx_output = process_unchecked_system_transaction(
750
        block_env,
751
        target_address,
752
        data,
753
    )
754
755
    if system_tx_output.error:
756
        raise InvalidBlock(
757
            f"System contract ({target_address.hex()}) call failed: "
758
            f"{system_tx_output.error}"
759
        )
760
761
    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:
769
    <snip>
788
    system_tx_state = TransactionState(parent=block_env.state)
789
    system_contract_code = get_code(
790
        system_tx_state,
791
        get_account(system_tx_state, target_address).code_hash,
792
    )
793
794
    tx_env = vm.TransactionEnvironment(
795
        origin=SYSTEM_ADDRESS,
796
        gas_price=block_env.base_fee_per_gas,
797
        gas=SYSTEM_TRANSACTION_GAS,
798
        state_gas_reservoir=(
799
            StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL
800
        ),
801
        access_list_addresses=set(),
802
        access_list_storage_keys=set(),
803
        state=system_tx_state,
804
        blob_versioned_hashes=(),
805
        authorizations=(),
806
        index_in_block=None,
807
        tx_hash=None,
808
        intrinsic_regular_gas=Uint(0),
809
        intrinsic_state_gas=Uint(0),
810
    )
811
812
    system_tx_message = Message(
813
        block_env=block_env,
814
        tx_env=tx_env,
815
        caller=SYSTEM_ADDRESS,
816
        target=target_address,
817
        gas=SYSTEM_TRANSACTION_GAS,
818
        state_gas_reservoir=(
819
            StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL
820
        ),
821
        value=U256(0),
822
        data=data,
823
        code=system_contract_code,
824
        depth=Uint(0),
825
        current_target=target_address,
826
        code_address=target_address,
827
        should_transfer_value=False,
828
        is_static=False,
829
        accessed_addresses=set(),
830
        accessed_storage_keys=set(),
831
        disable_precompiles=False,
832
        parent_evm=None,
833
    )
834
835
    system_tx_output = process_message_call(system_tx_message)
836
737
    incorporate_tx_into_block(system_tx_state)
837
    incorporate_tx_into_block(
838
        system_tx_state, block_env.block_access_list_builder
839
    )
840
841
    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:
849
    <snip>
874
    block_output = vm.BlockOutput()
875
876
    process_unchecked_system_transaction(
877
        block_env=block_env,
878
        target_address=BEACON_ROOTS_ADDRESS,
879
        data=block_env.parent_beacon_block_root,
880
    )
881
882
    process_unchecked_system_transaction(
883
        block_env=block_env,
884
        target_address=HISTORY_STORAGE_ADDRESS,
885
        data=block_env.block_hashes[-1],  # The parent hash
886
    )
887
888
    for i, tx in enumerate(map(decode_transaction, transactions)):
889
        process_transaction(block_env, block_output, tx, Uint(i))
890
891
    # EIP-7928: Post-execution operations use index N+1
892
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
893
        ulen(transactions) + Uint(1)
894
    )
895
896
    process_withdrawals(block_env, block_output, withdrawals)
897
898
    process_general_purpose_requests(
899
        block_env=block_env,
900
        block_output=block_output,
901
    )
902
903
    block_output.block_access_list = build_block_access_list(
904
        block_env.block_access_list_builder, block_env.state
905
    )
906
907
    # Validate block access list gas limit constraint (EIP-7928)
908
    validate_block_access_list_gas_limit(
909
        block_access_list=block_output.block_access_list,
910
        block_gas_limit=block_env.block_gas_limit,
911
    )
912
913
    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:
920
    <snip>
931
    # Requests are to be in ascending order of request type
932
    deposit_requests = parse_deposit_requests(block_output)
933
    requests_from_execution = block_output.requests
934
    if len(deposit_requests) > 0:
935
        requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests)
936
937
    system_withdrawal_tx_output = process_checked_system_transaction(
938
        block_env=block_env,
939
        target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
940
        data=b"",
941
    )
942
943
    if len(system_withdrawal_tx_output.return_data) > 0:
944
        requests_from_execution.append(
945
            WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data
946
        )
947
948
    system_consolidation_tx_output = process_checked_system_transaction(
949
        block_env=block_env,
950
        target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
951
        data=b"",
952
    )
953
954
    if len(system_consolidation_tx_output.return_data) > 0:
955
        requests_from_execution.append(
956
            CONSOLIDATION_REQUEST_TYPE
957
            + system_consolidation_tx_output.return_data
958
        )

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:
967
    <snip>
991
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
992
        index + Uint(1)
993
    )
994
    tx_state = TransactionState(parent=block_env.state)
995
996
    trie_set(
997
        block_output.transactions_trie,
998
        rlp.encode(index),
999
        encode_transaction(tx),
1000
    )
1001
1002
    intrinsic = validate_transaction(tx)
1003
1004
    intrinsic_gas = intrinsic.regular + intrinsic.state
1005
1006
    (
1007
        sender,
1008
        effective_gas_price,
1009
        blob_versioned_hashes,
1010
        tx_blob_gas_used,
1011
    ) = check_transaction(
1012
        block_env=block_env,
1013
        block_output=block_output,
1014
        tx=tx,
1015
        tx_state=tx_state,
1016
    )
1017
1018
    sender_account = get_account(tx_state, sender)
1019
1020
    if isinstance(tx, BlobTransaction):
1021
        blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx)
1022
    else:
1023
        blob_gas_fee = Uint(0)
1024
1025
    effective_gas_fee = tx.gas * effective_gas_price
1026
905
    gas = tx.gas - intrinsic.regular
1027
    # Split execution gas into gas_left (capped by remaining regular gas
1028
    # budget) and state_gas_reservoir.
1029
    execution_gas = tx.gas - intrinsic_gas
1030
    regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.regular
1031
    gas = min(regular_gas_budget, execution_gas)
1032
    state_gas_reservoir = Uint(execution_gas - gas)
1033
1034
    increment_nonce(tx_state, sender)
1035
1036
    sender_balance_after_gas_fee = (
1037
        Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
1038
    )
1039
    set_account_balance(tx_state, sender, U256(sender_balance_after_gas_fee))
1040
1041
    access_list_addresses = set()
1042
    access_list_storage_keys = set()
1043
    access_list_addresses.add(block_env.coinbase)
1044
    if has_access_list(tx):
1045
        for access in tx.access_list:
1046
            access_list_addresses.add(access.account)
1047
            for slot in access.slots:
1048
                access_list_storage_keys.add((access.account, slot))
1049
1050
    authorizations: Tuple[Authorization, ...] = ()
1051
    if isinstance(tx, SetCodeTransaction):
1052
        authorizations = tx.authorizations
1053
1054
    tx_env = vm.TransactionEnvironment(
1055
        origin=sender,
1056
        gas_price=effective_gas_price,
1057
        gas=gas,
1058
        state_gas_reservoir=state_gas_reservoir,
1059
        access_list_addresses=access_list_addresses,
1060
        access_list_storage_keys=access_list_storage_keys,
1061
        state=tx_state,
1062
        blob_versioned_hashes=blob_versioned_hashes,
1063
        authorizations=authorizations,
1064
        index_in_block=index,
1065
        tx_hash=get_transaction_hash(encode_transaction(tx)),
1066
        intrinsic_regular_gas=intrinsic.regular,
1067
        intrinsic_state_gas=intrinsic.state,
1068
    )
1069
939
    message = prepare_message(block_env, tx_env, tx)
1070
    message = prepare_message(
1071
        block_env,
1072
        tx_env,
1073
        tx,
1074
    )
1075
1076
    tx_output = process_message_call(message)
1077
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
1078
    if isinstance(tx.to, Bytes0) and (
1079
        tx_output.error is not None or tx_output.created_target_alive
1080
    ):
1081
        new_account_refund = StateGasCosts.NEW_ACCOUNT
1082
        tx_output.state_gas_left += new_account_refund
1083
        tx_output.state_refund += new_account_refund
1084
1085
    tx_gas_used_before_refund = (
1086
        tx.gas - tx_output.gas_left - tx_output.state_gas_left
1087
    )
1088
    tx_gas_refund = min(
1089
        tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter)
1090
    )
1091
    tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund
1092
1093
    # Transactions with less execution_gas_used than the floor pay at the
1094
    # floor cost.
953
    tx_gas_used_after_refund = max(
954
        tx_gas_used_after_refund, intrinsic.calldata_floor
955
    )
1095
    tx_gas_used = max(tx_gas_used_after_refund, intrinsic.calldata_floor)
1096
957
    tx_gas_left = tx.gas - tx_gas_used_after_refund
1097
    tx_gas_left = tx.gas - tx_gas_used
1098
    gas_refund_amount = tx_gas_left * effective_gas_price
1099
1100
    # For non-1559 transactions effective_gas_price == tx.gas_price
1101
    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
1102
    transaction_fee = tx_gas_used * priority_fee_per_gas
1103
1104
    # refund gas
1105
    create_ether(tx_state, sender, U256(gas_refund_amount))
1106
1107
    # transfer miner fees
1108
    create_ether(tx_state, block_env.coinbase, U256(transaction_fee))
1109
970
    for address in tx_output.accounts_to_delete:
971
        destroy_account(tx_state, address)
1110
    # EIP-7708: Emit burn logs for balances held by accounts marked for
1111
    # deletion AFTER miner fee transfer.
1112
    finalization_logs: List[Log] = []
1113
    for address in sorted(tx_output.accounts_to_delete):
1114
        balance = get_account(tx_state, address).balance
1115
        if balance > U256(0):
1116
            padded_address = left_pad_zero_bytes(address, 32)
1117
            finalization_logs.append(
1118
                Log(
1119
                    address=vm.SYSTEM_ADDRESS,
1120
                    topics=(
1121
                        vm.BURN_TOPIC,
1122
                        Hash32(padded_address),
1123
                    ),
1124
                    data=balance.to_be_bytes32(),
1125
                )
1126
            )
1127
973
    block_output.block_gas_used += tx_gas_used_after_refund
1128
    all_logs = tx_output.logs + tuple(finalization_logs)
1129
1130
    tx_state_gas = (
1131
        int(tx_env.intrinsic_state_gas)
1132
        + tx_output.state_gas_used
1133
        - int(tx_output.state_refund)
1134
    )
1135
    tx_regular_gas = tx_gas_used_before_refund - Uint(max(0, tx_state_gas))
1136
    block_output.block_gas_used += tx_regular_gas
1137
    block_output.block_state_gas_used += Uint(max(0, tx_state_gas))
1138
    block_output.blob_gas_used += tx_blob_gas_used
1139
1140
    block_output.cumulative_gas_used += tx_gas_used
1141
    receipt = make_receipt(
977
        tx, tx_output.error, block_output.block_gas_used, tx_output.logs
1142
        tx,
1143
        tx_output.error,
1144
        block_output.cumulative_gas_used,
1145
        all_logs,
1146
    )
1147
1148
    receipt_key = rlp.encode(Uint(index))
1149
    block_output.receipt_keys += (receipt_key,)
1150
1151
    trie_set(
1152
        block_output.receipts_trie,
1153
        receipt_key,
1154
        receipt,
1155
    )
1156
989
    block_output.block_logs += tx_output.logs
1157
    block_output.block_logs += all_logs
1158
991
    incorporate_tx_into_block(tx_state)
1159
    for address in tx_output.accounts_to_delete:
1160
        destroy_account(tx_state, address)
1161
1162
    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:
1170
    <snip>
1173
    wd_state = TransactionState(parent=block_env.state)
1174
1175
    for i, wd in enumerate(withdrawals):
1176
        trie_set(
1177
            block_output.withdrawals_trie,
1178
            rlp.encode(Uint(i)),
1179
            rlp.encode(wd),
1180
        )
1181
1011
        create_ether(wd_state, wd.address, wd.amount * U256(10**9))
1182
        create_ether(wd_state, wd.address, wd.amount * GWEI_TO_WEI)
1183
1013
    incorporate_tx_into_block(wd_state)
1184
    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:
1188
    <snip>
1216
    max_adjustment_delta = parent_gas_limit // GasCosts.LIMIT_ADJUSTMENT_FACTOR
1217
    if gas_limit >= parent_gas_limit + max_adjustment_delta:
1218
        return False
1219
    if gas_limit <= parent_gas_limit - max_adjustment_delta:
1220
        return False
1221
    if gas_limit < GasCosts.LIMIT_MINIMUM:
1222
        return False
1223
1224
    return True