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

108
BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8)

ELASTICITY_MULTIPLIER

109
ELASTICITY_MULTIPLIER = Uint(2)

EMPTY_OMMER_HASH

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

SYSTEM_ADDRESS

111
SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe")

BEACON_ROOTS_ADDRESS

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

SYSTEM_TRANSACTION_GAS

115
SYSTEM_TRANSACTION_GAS = Uint(30000000)

MAX_BLOB_GAS_PER_BLOCK

116
MAX_BLOB_GAS_PER_BLOCK: Final[U64] = (
117
    GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB
118
)

VERSIONED_HASH_VERSION_KZG

119
VERSIONED_HASH_VERSION_KZG = b"\x01"

GWEI_TO_WEI

120
GWEI_TO_WEI = U256(10**9)

WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS

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

CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS

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

HISTORY_STORAGE_ADDRESS

128
HISTORY_STORAGE_ADDRESS = hex_to_address(
129
    "0x0000F90827F1C53a10cb7A02335B175320002935"
130
)

MAX_BLOCK_SIZE

131
MAX_BLOCK_SIZE = 10_485_760

SAFETY_MARGIN

132
SAFETY_MARGIN = 2_097_152

MAX_RLP_BLOCK_SIZE

133
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN

BLOB_COUNT_LIMIT

134
BLOB_COUNT_LIMIT = 6

ChainContext

Chain context needed for block execution.

137
@slotted_freezable
138
@dataclass
class ChainContext:

chain_id

Identify the chain for transaction signature recovery.

144
    chain_id: U64

block_hashes

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

147
    block_hashes: List[Hash32]

parent_header

Parent header used for header validation and system contracts.

150
    parent_header: Header | PreviousHeader

BlockChain

History and current state of the block chain.

154
@dataclass
class BlockChain:

blocks

160
    blocks: List[Block]

state

161
    state: State

chain_id

162
    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:
166
    <snip>
185
    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]:
189
    <snip>
209
    recent_blocks = chain.blocks[-255:]
210
    # TODO: This function has not been tested rigorously
211
    if len(recent_blocks) == 0:
212
        return []
213
214
    recent_block_hashes = []
215
216
    for block in recent_blocks:
217
        prev_block_hash = block.header.parent_hash
218
        recent_block_hashes.append(prev_block_hash)
219
220
    # We are computing the hash only for the most recent block and not for
221
    # the rest of the blocks as they have successors which have the hash of
222
    # the current block as parent hash.
223
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
224
    recent_block_hashes.append(most_recent_block_hash)
225
226
    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:
230
    <snip>
252
    chain_context = ChainContext(
253
        chain_id=chain.chain_id,
254
        block_hashes=get_last_256_block_hashes(chain),
255
        parent_header=chain.blocks[-1].header,
256
    )
257
258
    block_diff = execute_block(block, chain.state, chain_context)
259
260
    apply_changes_to_state(chain.state, block_diff)
261
    chain.blocks.append(block)
262
    if len(chain.blocks) > 255:
263
        # Real clients have to store more blocks to deal with reorgs, but the
264
        # protocol only requires the last 255
265
        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:
273
    <snip>
293
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
294
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
295
296
    parent_header = chain_context.parent_header
297
    validate_header(parent_header, block.header)
298
299
    if block.ommers != ():
300
        raise InvalidBlock
301
302
    block_state = BlockState(pre_state=pre_state)
303
304
    block_env = vm.BlockEnvironment(
305
        chain_id=chain_context.chain_id,
306
        state=block_state,
307
        block_gas_limit=block.header.gas_limit,
308
        block_hashes=chain_context.block_hashes,
309
        coinbase=block.header.coinbase,
310
        number=block.header.number,
311
        base_fee_per_gas=block.header.base_fee_per_gas,
312
        time=block.header.timestamp,
313
        prev_randao=block.header.prev_randao,
314
        excess_blob_gas=block.header.excess_blob_gas,
315
        parent_beacon_block_root=block.header.parent_beacon_block_root,
316
        block_access_list_builder=BlockAccessListBuilder(),
317
        slot_number=block.header.slot_number,
318
    )
319
320
    block_output = apply_body(
321
        block_env=block_env,
322
        transactions=block.transactions,
323
        withdrawals=block.withdrawals,
324
    )
325
    block_diff = extract_block_diff(block_state)
326
    block_state_root, _ = pre_state.compute_state_root_and_trie_changes(
327
        block_diff.account_changes, block_diff.storage_changes
328
    )
329
    transactions_root = root(block_output.transactions_trie)
330
    receipt_root = root(block_output.receipts_trie)
331
    block_logs_bloom = logs_bloom(block_output.block_logs)
332
    withdrawals_root = root(block_output.withdrawals_trie)
333
    requests_hash = compute_requests_hash(block_output.requests)
334
    computed_block_access_list_hash = hash_block_access_list(
335
        block_output.block_access_list
336
    )
337
338
    if block_output.block_gas_used != block.header.gas_used:
339
        raise InvalidBlock(
340
            f"{block_output.block_gas_used} != {block.header.gas_used}"
341
        )
342
    if transactions_root != block.header.transactions_root:
343
        raise InvalidBlock
344
    if block_state_root != block.header.state_root:
345
        raise InvalidBlock
346
    if receipt_root != block.header.receipt_root:
347
        raise InvalidBlock
348
    if block_logs_bloom != block.header.bloom:
349
        raise InvalidBlock
350
    if withdrawals_root != block.header.withdrawals_root:
351
        raise InvalidBlock
352
    if block_output.blob_gas_used != block.header.blob_gas_used:
353
        raise InvalidBlock
354
    if requests_hash != block.header.requests_hash:
355
        raise InvalidBlock
356
    if computed_block_access_list_hash != block.header.block_access_list_hash:
357
        raise InvalidBlock("Invalid block access list hash")
358
359
    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:
368
    <snip>
388
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
389
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
390
        raise InvalidBlock
391
392
    if parent_gas_used == parent_gas_target:
393
        expected_base_fee_per_gas = parent_base_fee_per_gas
394
    elif parent_gas_used > parent_gas_target:
395
        gas_used_delta = parent_gas_used - parent_gas_target
396
397
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
398
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
399
400
        base_fee_per_gas_delta = max(
401
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
402
            Uint(1),
403
        )
404
405
        expected_base_fee_per_gas = (
406
            parent_base_fee_per_gas + base_fee_per_gas_delta
407
        )
408
    else:
409
        gas_used_delta = parent_gas_target - parent_gas_used
410
411
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
412
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
413
414
        base_fee_per_gas_delta = (
415
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
416
        )
417
418
        expected_base_fee_per_gas = (
419
            parent_base_fee_per_gas - base_fee_per_gas_delta
420
        )
421
422
    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:
428
    <snip>
446
    if header.number < Uint(1):
447
        raise InvalidBlock
448
449
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
450
    if header.excess_blob_gas != excess_blob_gas:
451
        raise InvalidBlock
452
453
    if header.gas_used > header.gas_limit:
454
        raise InvalidBlock
455
456
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
457
        header.gas_limit,
458
        parent_header.gas_limit,
459
        parent_header.gas_used,
460
        parent_header.base_fee_per_gas,
461
    )
462
    if expected_base_fee_per_gas != header.base_fee_per_gas:
463
        raise InvalidBlock
464
    if header.timestamp <= parent_header.timestamp:
465
        raise InvalidBlock
466
    if header.number != parent_header.number + Uint(1):
467
        raise InvalidBlock
468
    if len(header.extra_data) > 32:
469
        raise InvalidBlock
470
    if header.difficulty != 0:
471
        raise InvalidBlock
472
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
473
        raise InvalidBlock
474
    if header.ommers_hash != EMPTY_OMMER_HASH:
475
        raise InvalidBlock
476
477
    block_parent_hash = keccak256(rlp.encode(parent_header))
478
    if header.parent_hash != block_parent_hash:
479
        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.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.amsterdam.vm.BlockOutput, ​​tx: Transaction, ​​tx_state: TransactionState) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]:
488
    <snip>
548
    gas_available = block_env.block_gas_limit - block_output.block_gas_used
549
    blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used
550
551
    if tx.gas > gas_available:
552
        raise GasUsedExceedsLimitError("gas used exceeds limit")
553
554
    tx_blob_gas_used = calculate_total_blob_gas(tx)
555
    if tx_blob_gas_used > blob_gas_available:
556
        raise BlobGasLimitExceededError("blob gas limit exceeded")
557
558
    sender_address = recover_sender(block_env.chain_id, tx)
559
    sender_account = get_account(tx_state, sender_address)
560
561
    if isinstance(
562
        tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction)
563
    ):
564
        if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
565
            raise PriorityFeeGreaterThanMaxFeeError(
566
                "priority fee greater than max fee"
567
            )
568
        if tx.max_fee_per_gas < block_env.base_fee_per_gas:
569
            raise InsufficientMaxFeePerGasError(
570
                tx.max_fee_per_gas, block_env.base_fee_per_gas
571
            )
572
573
        priority_fee_per_gas = min(
574
            tx.max_priority_fee_per_gas,
575
            tx.max_fee_per_gas - block_env.base_fee_per_gas,
576
        )
577
        effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas
578
        max_gas_fee = tx.gas * tx.max_fee_per_gas
579
    else:
580
        if tx.gas_price < block_env.base_fee_per_gas:
581
            raise InvalidBlock
582
        effective_gas_price = tx.gas_price
583
        max_gas_fee = tx.gas * tx.gas_price
584
585
    if isinstance(tx, BlobTransaction):
586
        blob_count = len(tx.blob_versioned_hashes)
587
        if blob_count == 0:
588
            raise NoBlobDataError("no blob data in transaction")
589
        if blob_count > BLOB_COUNT_LIMIT:
590
            raise BlobCountExceededError(
591
                f"Tx has {blob_count} blobs. Max allowed: {BLOB_COUNT_LIMIT}"
592
            )
593
        for blob_versioned_hash in tx.blob_versioned_hashes:
594
            if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG:
595
                raise InvalidBlobVersionedHashError(
596
                    "invalid blob versioned hash"
597
                )
598
599
        blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas)
600
        if Uint(tx.max_fee_per_blob_gas) < blob_gas_price:
601
            raise InsufficientMaxFeePerBlobGasError(
602
                "insufficient max fee per blob gas"
603
            )
604
605
        max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint(
606
            tx.max_fee_per_blob_gas
607
        )
608
        blob_versioned_hashes = tx.blob_versioned_hashes
609
    else:
610
        blob_versioned_hashes = ()
611
612
    if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
613
        if not isinstance(tx.to, Address):
614
            raise TransactionTypeContractCreationError(tx)
615
616
    if isinstance(tx, SetCodeTransaction):
617
        if not any(tx.authorizations):
618
            raise EmptyAuthorizationListError("empty authorization list")
619
620
    if sender_account.nonce > Uint(tx.nonce):
621
        raise NonceMismatchError("nonce too low")
622
    elif sender_account.nonce < Uint(tx.nonce):
623
        raise NonceMismatchError("nonce too high")
624
625
    if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value):
626
        raise InsufficientBalanceError("insufficient sender balance")
627
    sender_code = get_code(tx_state, sender_account.code_hash)
628
    if sender_account.code_hash != EMPTY_CODE_HASH and not is_valid_delegation(
629
        sender_code
630
    ):
631
        raise InvalidSenderError("not EOA")
632
633
    return (
634
        sender_address,
635
        effective_gas_price,
636
        blob_versioned_hashes,
637
        tx_blob_gas_used,
638
    )

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:
647
    <snip>
668
    receipt = Receipt(
669
        succeeded=error is None,
670
        cumulative_gas_used=cumulative_gas_used,
671
        bloom=logs_bloom(logs),
672
        logs=logs,
673
    )
674
675
    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:
683
    <snip>
702
    # Pre-check that the system contract has code. We use a throwaway
703
    # TransactionState here that is *never* propagated back to BlockState
704
    # (no incorporate_tx_into_block call); the same get_account / get_code
705
    # lookups are performed and properly tracked by
706
    # process_unchecked_system_transaction below, which this function
707
    # always calls. Reading via a TransactionState (rather than directly
708
    # against pre_state) lets us see system contracts deployed earlier in
709
    # the same block — see EIP-7002 and EIP-7251 for this edge case.
710
    untracked_state = TransactionState(parent=block_env.state)
711
    system_contract_code = get_code(
712
        untracked_state,
713
        get_account(untracked_state, target_address).code_hash,
714
    )
715
716
    if len(system_contract_code) == 0:
717
        raise InvalidBlock(
718
            f"System contract address {target_address.hex()} does not "
719
            "contain code"
720
        )
721
722
    system_tx_output = process_unchecked_system_transaction(
723
        block_env,
724
        target_address,
725
        data,
726
    )
727
728
    if system_tx_output.error:
729
        raise InvalidBlock(
730
            f"System contract ({target_address.hex()}) call failed: "
731
            f"{system_tx_output.error}"
732
        )
733
734
    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:
742
    <snip>
761
    system_tx_state = TransactionState(parent=block_env.state)
762
    system_contract_code = get_code(
763
        system_tx_state,
764
        get_account(system_tx_state, target_address).code_hash,
765
    )
766
767
    tx_env = vm.TransactionEnvironment(
768
        origin=SYSTEM_ADDRESS,
769
        gas_price=block_env.base_fee_per_gas,
770
        gas=SYSTEM_TRANSACTION_GAS,
771
        access_list_addresses=set(),
772
        access_list_storage_keys=set(),
773
        state=system_tx_state,
774
        blob_versioned_hashes=(),
775
        authorizations=(),
776
        index_in_block=None,
777
        tx_hash=None,
778
    )
779
780
    system_tx_message = Message(
781
        block_env=block_env,
782
        tx_env=tx_env,
783
        caller=SYSTEM_ADDRESS,
784
        target=target_address,
785
        gas=SYSTEM_TRANSACTION_GAS,
786
        value=U256(0),
787
        data=data,
788
        code=system_contract_code,
789
        depth=Uint(0),
790
        current_target=target_address,
791
        code_address=target_address,
792
        should_transfer_value=False,
793
        is_static=False,
794
        accessed_addresses=set(),
795
        accessed_storage_keys=set(),
796
        disable_precompiles=False,
797
        parent_evm=None,
798
    )
799
800
    system_tx_output = process_message_call(system_tx_message)
801
802
    incorporate_tx_into_block(
803
        system_tx_state, block_env.block_access_list_builder
804
    )
805
806
    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:
814
    <snip>
839
    block_output = vm.BlockOutput()
840
841
    process_unchecked_system_transaction(
842
        block_env=block_env,
843
        target_address=BEACON_ROOTS_ADDRESS,
844
        data=block_env.parent_beacon_block_root,
845
    )
846
847
    process_unchecked_system_transaction(
848
        block_env=block_env,
849
        target_address=HISTORY_STORAGE_ADDRESS,
850
        data=block_env.block_hashes[-1],  # The parent hash
851
    )
852
853
    for i, tx in enumerate(map(decode_transaction, transactions)):
854
        process_transaction(block_env, block_output, tx, Uint(i))
855
856
    # EIP-7928: Post-execution operations use index N+1
857
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
858
        ulen(transactions) + Uint(1)
859
    )
860
861
    process_withdrawals(block_env, block_output, withdrawals)
862
863
    process_general_purpose_requests(
864
        block_env=block_env,
865
        block_output=block_output,
866
    )
867
868
    block_output.block_access_list = build_block_access_list(
869
        block_env.block_access_list_builder, block_env.state
870
    )
871
872
    # Validate block access list gas limit constraint (EIP-7928)
873
    validate_block_access_list_gas_limit(
874
        block_access_list=block_output.block_access_list,
875
        block_gas_limit=block_env.block_gas_limit,
876
    )
877
878
    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:
885
    <snip>
896
    # Requests are to be in ascending order of request type
897
    deposit_requests = parse_deposit_requests(block_output)
898
    requests_from_execution = block_output.requests
899
    if len(deposit_requests) > 0:
900
        requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests)
901
902
    system_withdrawal_tx_output = process_checked_system_transaction(
903
        block_env=block_env,
904
        target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
905
        data=b"",
906
    )
907
908
    if len(system_withdrawal_tx_output.return_data) > 0:
909
        requests_from_execution.append(
910
            WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data
911
        )
912
913
    system_consolidation_tx_output = process_checked_system_transaction(
914
        block_env=block_env,
915
        target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
916
        data=b"",
917
    )
918
919
    if len(system_consolidation_tx_output.return_data) > 0:
920
        requests_from_execution.append(
921
            CONSOLIDATION_REQUEST_TYPE
922
            + system_consolidation_tx_output.return_data
923
        )

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:
932
    <snip>
956
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
957
        index + Uint(1)
958
    )
959
    tx_state = TransactionState(parent=block_env.state)
960
961
    trie_set(
962
        block_output.transactions_trie,
963
        rlp.encode(index),
964
        encode_transaction(tx),
965
    )
966
967
    intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx)
968
969
    (
970
        sender,
971
        effective_gas_price,
972
        blob_versioned_hashes,
973
        tx_blob_gas_used,
974
    ) = check_transaction(
975
        block_env=block_env,
976
        block_output=block_output,
977
        tx=tx,
978
        tx_state=tx_state,
979
    )
980
981
    sender_account = get_account(tx_state, sender)
982
983
    if isinstance(tx, BlobTransaction):
984
        blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx)
985
    else:
986
        blob_gas_fee = Uint(0)
987
988
    effective_gas_fee = tx.gas * effective_gas_price
989
990
    gas = tx.gas - intrinsic_gas
991
992
    increment_nonce(tx_state, sender)
993
994
    sender_balance_after_gas_fee = (
995
        Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
996
    )
997
    set_account_balance(tx_state, sender, U256(sender_balance_after_gas_fee))
998
999
    access_list_addresses = set()
1000
    access_list_storage_keys = set()
1001
    access_list_addresses.add(block_env.coinbase)
1002
    if has_access_list(tx):
1003
        for access in tx.access_list:
1004
            access_list_addresses.add(access.account)
1005
            for slot in access.slots:
1006
                access_list_storage_keys.add((access.account, slot))
1007
1008
    authorizations: Tuple[Authorization, ...] = ()
1009
    if isinstance(tx, SetCodeTransaction):
1010
        authorizations = tx.authorizations
1011
1012
    tx_env = vm.TransactionEnvironment(
1013
        origin=sender,
1014
        gas_price=effective_gas_price,
1015
        gas=gas,
1016
        access_list_addresses=access_list_addresses,
1017
        access_list_storage_keys=access_list_storage_keys,
1018
        state=tx_state,
1019
        blob_versioned_hashes=blob_versioned_hashes,
1020
        authorizations=authorizations,
1021
        index_in_block=index,
1022
        tx_hash=get_transaction_hash(encode_transaction(tx)),
1023
    )
1024
1025
    message = prepare_message(
1026
        block_env,
1027
        tx_env,
1028
        tx,
1029
    )
1030
1031
    tx_output = process_message_call(message)
1032
1033
    # For EIP-7623 we first calculate the execution_gas_used, which includes
1034
    # the execution gas refund.
1035
    tx_gas_used_before_refund = tx.gas - tx_output.gas_left
1036
    tx_gas_refund = min(
1037
        tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter)
1038
    )
1039
    tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund
1040
1041
    # Transactions with less execution_gas_used than the floor pay at the
1042
    # floor cost.
1043
    tx_gas_used = max(tx_gas_used_after_refund, calldata_floor_gas_cost)
1044
    block_gas_used_in_tx = max(
1045
        tx_gas_used_before_refund, calldata_floor_gas_cost
1046
    )
1047
1048
    tx_gas_left = tx.gas - tx_gas_used
1049
    gas_refund_amount = tx_gas_left * effective_gas_price
1050
1051
    # For non-1559 transactions effective_gas_price == tx.gas_price
1052
    priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas
1053
    transaction_fee = tx_gas_used * priority_fee_per_gas
1054
1055
    # refund gas
1056
    create_ether(tx_state, sender, U256(gas_refund_amount))
1057
1058
    # transfer miner fees
1059
    create_ether(tx_state, block_env.coinbase, U256(transaction_fee))
1060
1061
    # EIP-7708: Emit burn logs for balances held by accounts marked for
1062
    # deletion AFTER miner fee transfer.
1063
    finalization_logs: List[Log] = []
1064
    for address in sorted(tx_output.accounts_to_delete):
1065
        balance = get_account(tx_state, address).balance
1066
        if balance > U256(0):
1067
            padded_address = left_pad_zero_bytes(address, 32)
1068
            finalization_logs.append(
1069
                Log(
1070
                    address=vm.SYSTEM_ADDRESS,
1071
                    topics=(
1072
                        vm.BURN_TOPIC,
1073
                        Hash32(padded_address),
1074
                    ),
1075
                    data=balance.to_be_bytes32(),
1076
                )
1077
            )
1078
1079
    all_logs = tx_output.logs + tuple(finalization_logs)
1080
1081
    block_output.cumulative_gas_used += tx_gas_used
1082
    block_output.block_gas_used += block_gas_used_in_tx
1083
    block_output.blob_gas_used += tx_blob_gas_used
1084
1085
    receipt = make_receipt(
1086
        tx,
1087
        tx_output.error,
1088
        block_output.cumulative_gas_used,
1089
        all_logs,
1090
    )
1091
1092
    receipt_key = rlp.encode(Uint(index))
1093
    block_output.receipt_keys += (receipt_key,)
1094
1095
    trie_set(
1096
        block_output.receipts_trie,
1097
        receipt_key,
1098
        receipt,
1099
    )
1100
1101
    block_output.block_logs += all_logs
1102
1103
    for address in tx_output.accounts_to_delete:
1104
        destroy_account(tx_state, address)
1105
1106
    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:
1114
    <snip>
1117
    wd_state = TransactionState(parent=block_env.state)
1118
1119
    for i, wd in enumerate(withdrawals):
1120
        trie_set(
1121
            block_output.withdrawals_trie,
1122
            rlp.encode(Uint(i)),
1123
            rlp.encode(wd),
1124
        )
1125
1126
        create_ether(wd_state, wd.address, wd.amount * GWEI_TO_WEI)
1127
1128
    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:
1132
    <snip>
1160
    max_adjustment_delta = parent_gas_limit // GasCosts.LIMIT_ADJUSTMENT_FACTOR
1161
    if gas_limit >= parent_gas_limit + max_adjustment_delta:
1162
        return False
1163
    if gas_limit <= parent_gas_limit - max_adjustment_delta:
1164
        return False
1165
    if gas_limit < GasCosts.LIMIT_MINIMUM:
1166
        return False
1167
1168
    return True