ethereum.forks.amsterdam.fork

Ethereum Specification.

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

Introduction

Entry point for the Ethereum specification.

BASE_FEE_MAX_CHANGE_DENOMINATOR

110
BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8)

ELASTICITY_MULTIPLIER

111
ELASTICITY_MULTIPLIER = Uint(2)

EMPTY_OMMER_HASH

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

SYSTEM_ADDRESS

113
SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe")

BEACON_ROOTS_ADDRESS

114
BEACON_ROOTS_ADDRESS = hex_to_address(
115
    "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02"
116
)

SYSTEM_TRANSACTION_GAS

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

118
SYSTEM_MAX_SSTORES_PER_CALL = Uint(16)

MAX_BLOB_GAS_PER_BLOCK

123
MAX_BLOB_GAS_PER_BLOCK: Final[U64] = (
124
    GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB
125
)

VERSIONED_HASH_VERSION_KZG

126
VERSIONED_HASH_VERSION_KZG = b"\x01"

GWEI_TO_WEI

127
GWEI_TO_WEI = U256(10**9)

WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS

129
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
130
    "0x00000961Ef480Eb55e80D19ad83579A64c007002"
131
)

CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS

132
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
133
    "0x0000BBdDc7CE488642fb579F8B00f3a590007251"
134
)

BUILDER_DEPOSIT_CONTRACT_ADDRESS

135
BUILDER_DEPOSIT_CONTRACT_ADDRESS = hex_to_address(
136
    "0x0000BFF46984E3725691FA540A8C7589300D8282"
137
)

BUILDER_EXIT_CONTRACT_ADDRESS

138
BUILDER_EXIT_CONTRACT_ADDRESS = hex_to_address(
139
    "0x000064D678505AD48F8CCB093BC65613800E8282"
140
)

HISTORY_STORAGE_ADDRESS

141
HISTORY_STORAGE_ADDRESS = hex_to_address(
142
    "0x0000F90827F1C53a10cb7A02335B175320002935"
143
)

MAX_BLOCK_SIZE

144
MAX_BLOCK_SIZE = 10_485_760

SAFETY_MARGIN

145
SAFETY_MARGIN = 2_097_152

MAX_RLP_BLOCK_SIZE

146
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN

BLOB_COUNT_LIMIT

147
BLOB_COUNT_LIMIT = 6

ChainContext

Chain context needed for block execution.

150
@final
151
@slotted_freezable
152
@dataclass
class ChainContext:

chain_id

Identify the chain for transaction signature recovery.

158
    chain_id: U64

block_hashes

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

161
    block_hashes: List[Hash32]

parent_header

Parent header used for header validation and system contracts.

164
    parent_header: Header | PreviousHeader

BlockChain

History and current state of the block chain.

168
@final
169
@dataclass
class BlockChain:

blocks

175
    blocks: List[Block]

state

176
    state: State

chain_id

177
    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:
181
    <snip>
200
    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]:
204
    <snip>
224
    recent_blocks = chain.blocks[-255:]
225
    # TODO: This function has not been tested rigorously
226
    if len(recent_blocks) == 0:
227
        return []
228
229
    recent_block_hashes = []
230
231
    for block in recent_blocks:
232
        prev_block_hash = block.header.parent_hash
233
        recent_block_hashes.append(prev_block_hash)
234
235
    # We are computing the hash only for the most recent block and not for
236
    # the rest of the blocks as they have successors which have the hash of
237
    # the current block as parent hash.
238
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
239
    recent_block_hashes.append(most_recent_block_hash)
240
241
    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:
245
    <snip>
267
    chain_context = ChainContext(
268
        chain_id=chain.chain_id,
269
        block_hashes=get_last_256_block_hashes(chain),
270
        parent_header=chain.blocks[-1].header,
271
    )
272
273
    block_diff = execute_block(block, chain.state, chain_context)
274
275
    apply_changes_to_state(chain.state, block_diff)
276
    chain.blocks.append(block)
277
    if len(chain.blocks) > 255:
278
        # Real clients have to store more blocks to deal with reorgs, but the
279
        # protocol only requires the last 255
280
        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:
288
    <snip>
308
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
309
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
310
311
    parent_header = chain_context.parent_header
312
    validate_header(parent_header, block.header)
313
314
    if block.ommers != ():
315
        raise InvalidBlock
316
317
    block_state = BlockState(pre_state=pre_state)
318
319
    block_env = vm.BlockEnvironment(
320
        chain_id=chain_context.chain_id,
321
        state=block_state,
322
        block_gas_limit=block.header.gas_limit,
323
        block_hashes=chain_context.block_hashes,
324
        coinbase=block.header.coinbase,
325
        number=block.header.number,
326
        base_fee_per_gas=block.header.base_fee_per_gas,
327
        time=block.header.timestamp,
328
        prev_randao=block.header.prev_randao,
329
        excess_blob_gas=block.header.excess_blob_gas,
330
        parent_beacon_block_root=block.header.parent_beacon_block_root,
331
        block_access_list_builder=BlockAccessListBuilder(),
332
        slot_number=block.header.slot_number,
333
    )
334
335
    block_output = apply_body(
336
        block_env=block_env,
337
        transactions=block.transactions,
338
        withdrawals=block.withdrawals,
339
    )
340
    block_diff = extract_block_diff(block_state)
341
    block_state_root = pre_state.compute_state_root(block_diff)
342
    transactions_root = root(block_output.transactions_trie)
343
    receipt_root = root(block_output.receipts_trie)
344
    block_logs_bloom = logs_bloom(block_output.block_logs)
345
    withdrawals_root = root(block_output.withdrawals_trie)
346
    requests_hash = compute_requests_hash(block_output.requests)
347
    computed_block_access_list_hash = hash_block_access_list(
348
        block_output.block_access_list
349
    )
350
351
    block_gas_used = max(
352
        block_output.block_gas_used,
353
        block_output.block_state_gas_used,
354
    )
355
    if block_gas_used != block.header.gas_used:
356
        raise InvalidBlock(f"{block_gas_used} != {block.header.gas_used}")
357
    if transactions_root != block.header.transactions_root:
358
        raise InvalidBlock
359
    if block_state_root != block.header.state_root:
360
        raise InvalidBlock
361
    if receipt_root != block.header.receipt_root:
362
        raise InvalidBlock
363
    if block_logs_bloom != block.header.bloom:
364
        raise InvalidBlock
365
    if withdrawals_root != block.header.withdrawals_root:
366
        raise InvalidBlock
367
    if block_output.blob_gas_used != block.header.blob_gas_used:
368
        raise InvalidBlock
369
    if requests_hash != block.header.requests_hash:
370
        raise InvalidBlock
371
    if computed_block_access_list_hash != block.header.block_access_list_hash:
372
        raise InvalidBlock("Invalid block access list hash")
373
374
    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:
383
    <snip>
403
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
404
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
405
        raise InvalidBlock
406
407
    if parent_gas_used == parent_gas_target:
408
        expected_base_fee_per_gas = parent_base_fee_per_gas
409
    elif parent_gas_used > parent_gas_target:
410
        gas_used_delta = parent_gas_used - parent_gas_target
411
412
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
413
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
414
415
        base_fee_per_gas_delta = max(
416
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
417
            Uint(1),
418
        )
419
420
        expected_base_fee_per_gas = (
421
            parent_base_fee_per_gas + base_fee_per_gas_delta
422
        )
423
    else:
424
        gas_used_delta = parent_gas_target - parent_gas_used
425
426
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
427
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
428
429
        base_fee_per_gas_delta = (
430
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
431
        )
432
433
        expected_base_fee_per_gas = (
434
            parent_base_fee_per_gas - base_fee_per_gas_delta
435
        )
436
437
    return Uint(expected_base_fee_per_gas)

validate_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

parent_header : Header of the parent block. header : Header to check for correctness.

def validate_header(parent_header: Header | PreviousHeader, ​​header: Header) -> None:
443
    <snip>
461
    if header.number < Uint(1):
462
        raise InvalidBlock
463
464
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
465
    if header.excess_blob_gas != excess_blob_gas:
466
        raise InvalidBlock
467
468
    if header.gas_used > header.gas_limit:
469
        raise InvalidBlock
470
471
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
472
        header.gas_limit,
473
        parent_header.gas_limit,
474
        parent_header.gas_used,
475
        parent_header.base_fee_per_gas,
476
    )
477
    if expected_base_fee_per_gas != header.base_fee_per_gas:
478
        raise InvalidBlock
479
    if header.timestamp <= parent_header.timestamp:
480
        raise InvalidBlock
481
    if header.number != parent_header.number + Uint(1):
482
        raise InvalidBlock
483
    if len(header.extra_data) > 32:
484
        raise InvalidBlock
485
    if header.difficulty != 0:
486
        raise InvalidBlock
487
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
488
        raise InvalidBlock
489
    if header.ommers_hash != EMPTY_OMMER_HASH:
490
        raise InvalidBlock
491
492
    block_parent_hash = keccak256(rlp.encode(parent_header))
493
    if header.parent_hash != block_parent_hash:
494
        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

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

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. 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:
668
    <snip>
689
    receipt = Receipt(
690
        succeeded=error is None,
691
        cumulative_gas_used=cumulative_gas_used,
692
        bloom=logs_bloom(logs),
693
        logs=logs,
694
    )
695
696
    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.amsterdam.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
704
    <snip>
723
    # Pre-check that the system contract has code. We use a throwaway
724
    # TransactionState here that is *never* propagated back to BlockState
725
    # (no incorporate_tx_into_block call); the same get_account / get_code
726
    # lookups are performed and properly tracked by
727
    # process_unchecked_system_transaction below, which this function
728
    # always calls. Reading via a TransactionState (rather than directly
729
    # against pre_state) lets us see system contracts deployed earlier in
730
    # the same block — see EIP-7002 and EIP-7251 for this edge case.
731
    untracked_state = TransactionState(parent=block_env.state)
732
    system_contract_code = get_code(
733
        untracked_state,
734
        get_account(untracked_state, target_address).code_hash,
735
    )
736
737
    if len(system_contract_code) == 0:
738
        raise InvalidBlock(
739
            f"System contract address {target_address.hex()} does not "
740
            "contain code"
741
        )
742
743
    system_tx_output = process_unchecked_system_transaction(
744
        block_env,
745
        target_address,
746
        data,
747
    )
748
749
    if system_tx_output.error:
750
        raise InvalidBlock(
751
            f"System contract ({target_address.hex()}) call failed: "
752
            f"{system_tx_output.error}"
753
        )
754
755
    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.amsterdam.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
763
    <snip>
782
    system_tx_state = TransactionState(parent=block_env.state)
783
    system_contract_code = get_code(
784
        system_tx_state,
785
        get_account(system_tx_state, target_address).code_hash,
786
    )
787
788
    tx_env = vm.TransactionEnvironment(
789
        origin=SYSTEM_ADDRESS,
790
        recipient=target_address,
791
        value=U256(0),
792
        gas_price=block_env.base_fee_per_gas,
793
        gas=SYSTEM_TRANSACTION_GAS,
794
        state_gas_reservoir=(
795
            StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL
796
        ),
797
        access_list_addresses=set(),
798
        access_list_storage_keys=set(),
799
        state=system_tx_state,
800
        blob_versioned_hashes=(),
801
        authorizations=(),
802
        index_in_block=None,
803
        tx_hash=None,
804
    )
805
806
    system_tx_message = Message(
807
        block_env=block_env,
808
        tx_env=tx_env,
809
        caller=SYSTEM_ADDRESS,
810
        target=target_address,
811
        gas=SYSTEM_TRANSACTION_GAS,
812
        state_gas_reservoir=(
813
            StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL
814
        ),
815
        value=U256(0),
816
        data=data,
817
        code=system_contract_code,
818
        depth=Uint(0),
819
        current_target=target_address,
820
        code_address=target_address,
821
        should_transfer_value=False,
822
        is_static=False,
823
        accessed_addresses=set(),
824
        accessed_storage_keys=set(),
825
        disable_precompiles=False,
826
        parent_evm=None,
827
    )
828
829
    system_tx_output = process_message_call(system_tx_message)
830
831
    incorporate_tx_into_block(
832
        system_tx_state, block_env.block_access_list_builder
833
    )
834
835
    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.amsterdam.vm.BlockEnvironment, ​​transactions: Tuple[LegacyTransaction | Bytes, ...], ​​withdrawals: Tuple[Withdrawal, ...]) -> ethereum.forks.amsterdam.vm.BlockOutput:
843
    <snip>
868
    block_output = vm.BlockOutput()
869
870
    process_unchecked_system_transaction(
871
        block_env=block_env,
872
        target_address=BEACON_ROOTS_ADDRESS,
873
        data=block_env.parent_beacon_block_root,
874
    )
875
876
    process_unchecked_system_transaction(
877
        block_env=block_env,
878
        target_address=HISTORY_STORAGE_ADDRESS,
879
        data=block_env.block_hashes[-1],  # The parent hash
880
    )
881
882
    for i, tx in enumerate(map(decode_transaction, transactions)):
883
        process_transaction(block_env, block_output, tx, Uint(i))
884
885
    # EIP-7928: Post-execution operations use index N+1
886
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
887
        ulen(transactions) + Uint(1)
888
    )
889
890
    process_withdrawals(block_env, block_output, withdrawals)
891
892
    process_general_purpose_requests(
893
        block_env=block_env,
894
        block_output=block_output,
895
    )
896
897
    block_output.block_access_list = build_block_access_list(
898
        block_env.block_access_list_builder, block_env.state
899
    )
900
901
    # Validate block access list gas limit constraint (EIP-7928)
902
    validate_block_access_list_gas_limit(
903
        block_access_list=block_output.block_access_list,
904
        block_gas_limit=block_env.block_gas_limit,
905
    )
906
907
    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.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.amsterdam.vm.BlockOutput) -> None:
914
    <snip>
925
    # Requests are to be in ascending order of request type
926
    deposit_requests = parse_deposit_requests(block_output)
927
    requests_from_execution = block_output.requests
928
    if len(deposit_requests) > 0:
929
        requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests)
930
931
    system_withdrawal_tx_output = process_checked_system_transaction(
932
        block_env=block_env,
933
        target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
934
        data=b"",
935
    )
936
937
    if len(system_withdrawal_tx_output.return_data) > 0:
938
        requests_from_execution.append(
939
            WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data
940
        )
941
942
    system_consolidation_tx_output = process_checked_system_transaction(
943
        block_env=block_env,
944
        target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
945
        data=b"",
946
    )
947
948
    if len(system_consolidation_tx_output.return_data) > 0:
949
        requests_from_execution.append(
950
            CONSOLIDATION_REQUEST_TYPE
951
            + system_consolidation_tx_output.return_data
952
        )
953
954
    system_builder_deposit_tx_output = process_checked_system_transaction(
955
        block_env=block_env,
956
        target_address=BUILDER_DEPOSIT_CONTRACT_ADDRESS,
957
        data=b"",
958
    )
959
960
    if len(system_builder_deposit_tx_output.return_data) > 0:
961
        requests_from_execution.append(
962
            BUILDER_DEPOSIT_REQUEST_TYPE
963
            + system_builder_deposit_tx_output.return_data
964
        )
965
966
    system_builder_exit_tx_output = process_checked_system_transaction(
967
        block_env=block_env,
968
        target_address=BUILDER_EXIT_CONTRACT_ADDRESS,
969
        data=b"",
970
    )
971
972
    if len(system_builder_exit_tx_output.return_data) > 0:
973
        requests_from_execution.append(
974
            BUILDER_EXIT_REQUEST_TYPE
975
            + system_builder_exit_tx_output.return_data
976
        )

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.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.amsterdam.vm.BlockOutput, ​​tx: Transaction, ​​index: Uint) -> None:
985
    <snip>
1009
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
1010
        index + Uint(1)
1011
    )
1012
    tx_state = TransactionState(parent=block_env.state)
1013
1014
    trie_set(
1015
        block_output.transactions_trie,
1016
        rlp.encode(index),
1017
        encode_transaction(tx),
1018
    )
1019
1020
    tx_chain_id = chain_id(tx)
1021
    if tx_chain_id is not None and tx_chain_id != block_env.chain_id:
1022
        raise WrongChainIdError(
1023
            expected=block_env.chain_id,
1024
            actual=tx_chain_id,
1025
        )
1026
1027
    sender = recover_sender(tx)
1028
    intrinsic = validate_transaction(tx, sender)
1029
1030
    (
1031
        effective_gas_price,
1032
        blob_versioned_hashes,
1033
        tx_blob_gas_used,
1034
    ) = check_transaction(
1035
        block_env=block_env,
1036
        block_output=block_output,
1037
        tx=tx,
1038
        sender=sender,
1039
        tx_state=tx_state,
1040
    )
1041
1042
    sender_account = get_account(tx_state, sender)
1043
1044
    if isinstance(tx, BlobTransaction):
1045
        blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx)
1046
    else:
1047
        blob_gas_fee = Uint(0)
1048
1049
    effective_gas_fee = tx.gas * effective_gas_price
1050
1051
    # Split the EVM gas into an execution-gas grant (capped by the
1052
    # remaining execution-gas budget) and a state gas reservoir.
1053
    allocation = allocate_evm_gas(tx.gas, intrinsic)
1054
1055
    increment_nonce(tx_state, sender)
1056
1057
    sender_balance_after_gas_fee = (
1058
        Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
1059
    )
1060
    set_account_balance(tx_state, sender, U256(sender_balance_after_gas_fee))
1061
1062
    access_list_addresses = set()
1063
    access_list_storage_keys = set()
1064
    access_list_addresses.add(block_env.coinbase)
1065
    if has_access_list(tx):
1066
        for access in tx.access_list:
1067
            access_list_addresses.add(access.account)
1068
            for slot in access.slots:
1069
                access_list_storage_keys.add((access.account, slot))
1070
1071
    authorizations: Tuple[Authorization, ...] = ()
1072
    if isinstance(tx, SetCodeTransaction):
1073
        authorizations = tx.authorizations
1074
1075
    tx_env = vm.TransactionEnvironment(
1076
        origin=sender,
1077
        recipient=tx.to,
1078
        value=tx.value,
1079
        gas_price=effective_gas_price,
1080
        gas=allocation.execution_gas,
1081
        state_gas_reservoir=allocation.state_gas_reservoir,
1082
        access_list_addresses=access_list_addresses,
1083
        access_list_storage_keys=access_list_storage_keys,
1084
        state=tx_state,
1085
        blob_versioned_hashes=blob_versioned_hashes,
1086
        authorizations=authorizations,
1087
        index_in_block=index,
1088
        tx_hash=get_transaction_hash(encode_transaction(tx)),
1089
    )
1090
1091
    message = prepare_message(block_env, tx_env, tx)
1092
1093
    tx_output = process_message_call(message)
1094
1095
    settlement = settle_transaction_gas(
1096
        tx.gas,
1097
        intrinsic,
1098
        tx_output.gas_left,
1099
        tx_output.state_gas_left,
1100
        tx_output.refund_counter,
1101
        tx_output.state_gas_used,
1102
    )
1103
1104
    gas_refund_amount = settlement.gas_left * effective_gas_price
1105
1106
    # For non-1559 transactions effective_gas_price == tx.gas_price
1107
    priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas
1108
    transaction_fee = settlement.gas_used * priority_fee_per_gas
1109
1110
    # refund gas
1111
    create_ether(tx_state, sender, U256(gas_refund_amount))
1112
1113
    # transfer miner fees
1114
    create_ether(tx_state, block_env.coinbase, U256(transaction_fee))
1115
1116
    block_output.block_gas_used += settlement.execution_gas_used
1117
    block_output.block_state_gas_used += settlement.state_gas_used
1118
    block_output.blob_gas_used += tx_blob_gas_used
1119
1120
    block_output.cumulative_gas_used += settlement.gas_used
1121
    receipt = make_receipt(
1122
        tx, tx_output.error, block_output.cumulative_gas_used, tx_output.logs
1123
    )
1124
1125
    receipt_key = rlp.encode(Uint(index))
1126
    block_output.receipt_keys += (receipt_key,)
1127
1128
    trie_set(
1129
        block_output.receipts_trie,
1130
        receipt_key,
1131
        receipt,
1132
    )
1133
1134
    block_output.block_logs += tx_output.logs
1135
1136
    for address in tx_output.accounts_to_delete:
1137
        clear_account_preserving_balance(tx_state, address)
1138
1139
    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.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.amsterdam.vm.BlockOutput, ​​withdrawals: Tuple[Withdrawal, ...]) -> None:
1147
    <snip>
1150
    wd_state = TransactionState(parent=block_env.state)
1151
1152
    for i, wd in enumerate(withdrawals):
1153
        trie_set(
1154
            block_output.withdrawals_trie,
1155
            rlp.encode(Uint(i)),
1156
            rlp.encode(wd),
1157
        )
1158
1159
        create_ether(wd_state, wd.address, wd.amount * GWEI_TO_WEI)
1160
1161
    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:
1165
    <snip>
1193
    max_adjustment_delta = parent_gas_limit // GasCosts.LIMIT_ADJUSTMENT_FACTOR
1194
    if gas_limit >= parent_gas_limit + max_adjustment_delta:
1195
        return False
1196
    if gas_limit <= parent_gas_limit - max_adjustment_delta:
1197
        return False
1198
    if gas_limit < GasCosts.LIMIT_MINIMUM:
1199
        return False
1200
1201
    return True