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
)

HISTORY_STORAGE_ADDRESS

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

MAX_BLOCK_SIZE

138
MAX_BLOCK_SIZE = 10_485_760

SAFETY_MARGIN

139
SAFETY_MARGIN = 2_097_152

MAX_RLP_BLOCK_SIZE

140
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN

BLOB_COUNT_LIMIT

141
BLOB_COUNT_LIMIT = 6

ChainContext

Chain context needed for block execution.

144
@final
145
@slotted_freezable
146
@dataclass
class ChainContext:

chain_id

Identify the chain for transaction signature recovery.

152
    chain_id: U64

block_hashes

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

155
    block_hashes: List[Hash32]

parent_header

Parent header used for header validation and system contracts.

158
    parent_header: Header | PreviousHeader

BlockChain

History and current state of the block chain.

162
@final
163
@dataclass
class BlockChain:

blocks

169
    blocks: List[Block]

state

170
    state: State

chain_id

171
    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:
175
    <snip>
194
    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]:
198
    <snip>
218
    recent_blocks = chain.blocks[-255:]
219
    # TODO: This function has not been tested rigorously
220
    if len(recent_blocks) == 0:
221
        return []
222
223
    recent_block_hashes = []
224
225
    for block in recent_blocks:
226
        prev_block_hash = block.header.parent_hash
227
        recent_block_hashes.append(prev_block_hash)
228
229
    # We are computing the hash only for the most recent block and not for
230
    # the rest of the blocks as they have successors which have the hash of
231
    # the current block as parent hash.
232
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
233
    recent_block_hashes.append(most_recent_block_hash)
234
235
    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:
239
    <snip>
261
    chain_context = ChainContext(
262
        chain_id=chain.chain_id,
263
        block_hashes=get_last_256_block_hashes(chain),
264
        parent_header=chain.blocks[-1].header,
265
    )
266
267
    block_diff = execute_block(block, chain.state, chain_context)
268
269
    apply_changes_to_state(chain.state, block_diff)
270
    chain.blocks.append(block)
271
    if len(chain.blocks) > 255:
272
        # Real clients have to store more blocks to deal with reorgs, but the
273
        # protocol only requires the last 255
274
        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:
282
    <snip>
302
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
303
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
304
305
    parent_header = chain_context.parent_header
306
    validate_header(parent_header, block.header)
307
308
    if block.ommers != ():
309
        raise InvalidBlock
310
311
    block_state = BlockState(pre_state=pre_state)
312
313
    block_env = vm.BlockEnvironment(
314
        chain_id=chain_context.chain_id,
315
        state=block_state,
316
        block_gas_limit=block.header.gas_limit,
317
        block_hashes=chain_context.block_hashes,
318
        coinbase=block.header.coinbase,
319
        number=block.header.number,
320
        base_fee_per_gas=block.header.base_fee_per_gas,
321
        time=block.header.timestamp,
322
        prev_randao=block.header.prev_randao,
323
        excess_blob_gas=block.header.excess_blob_gas,
324
        parent_beacon_block_root=block.header.parent_beacon_block_root,
325
        block_access_list_builder=BlockAccessListBuilder(),
326
        slot_number=block.header.slot_number,
327
    )
328
329
    block_output = apply_body(
330
        block_env=block_env,
331
        transactions=block.transactions,
332
        withdrawals=block.withdrawals,
333
    )
334
    block_diff = extract_block_diff(block_state)
335
    block_state_root, _ = pre_state.compute_state_root_and_trie_changes(
336
        block_diff.account_changes, block_diff.storage_changes
337
    )
338
    transactions_root = root(block_output.transactions_trie)
339
    receipt_root = root(block_output.receipts_trie)
340
    block_logs_bloom = logs_bloom(block_output.block_logs)
341
    withdrawals_root = root(block_output.withdrawals_trie)
342
    requests_hash = compute_requests_hash(block_output.requests)
343
    computed_block_access_list_hash = hash_block_access_list(
344
        block_output.block_access_list
345
    )
346
347
    block_gas_used = max(
348
        block_output.block_gas_used,
349
        block_output.block_state_gas_used,
350
    )
351
    if block_gas_used != block.header.gas_used:
352
        raise InvalidBlock(f"{block_gas_used} != {block.header.gas_used}")
353
    if transactions_root != block.header.transactions_root:
354
        raise InvalidBlock
355
    if block_state_root != block.header.state_root:
356
        raise InvalidBlock
357
    if receipt_root != block.header.receipt_root:
358
        raise InvalidBlock
359
    if block_logs_bloom != block.header.bloom:
360
        raise InvalidBlock
361
    if withdrawals_root != block.header.withdrawals_root:
362
        raise InvalidBlock
363
    if block_output.blob_gas_used != block.header.blob_gas_used:
364
        raise InvalidBlock
365
    if requests_hash != block.header.requests_hash:
366
        raise InvalidBlock
367
    if computed_block_access_list_hash != block.header.block_access_list_hash:
368
        raise InvalidBlock("Invalid block access list hash")
369
370
    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:
379
    <snip>
399
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
400
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
401
        raise InvalidBlock
402
403
    if parent_gas_used == parent_gas_target:
404
        expected_base_fee_per_gas = parent_base_fee_per_gas
405
    elif parent_gas_used > parent_gas_target:
406
        gas_used_delta = parent_gas_used - parent_gas_target
407
408
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
409
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
410
411
        base_fee_per_gas_delta = max(
412
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
413
            Uint(1),
414
        )
415
416
        expected_base_fee_per_gas = (
417
            parent_base_fee_per_gas + base_fee_per_gas_delta
418
        )
419
    else:
420
        gas_used_delta = parent_gas_target - parent_gas_used
421
422
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
423
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
424
425
        base_fee_per_gas_delta = (
426
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
427
        )
428
429
        expected_base_fee_per_gas = (
430
            parent_base_fee_per_gas - base_fee_per_gas_delta
431
        )
432
433
    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:
439
    <snip>
457
    if header.number < Uint(1):
458
        raise InvalidBlock
459
460
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
461
    if header.excess_blob_gas != excess_blob_gas:
462
        raise InvalidBlock
463
464
    if header.gas_used > header.gas_limit:
465
        raise InvalidBlock
466
467
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
468
        header.gas_limit,
469
        parent_header.gas_limit,
470
        parent_header.gas_used,
471
        parent_header.base_fee_per_gas,
472
    )
473
    if expected_base_fee_per_gas != header.base_fee_per_gas:
474
        raise InvalidBlock
475
    if header.timestamp <= parent_header.timestamp:
476
        raise InvalidBlock
477
    if header.number != parent_header.number + Uint(1):
478
        raise InvalidBlock
479
    if len(header.extra_data) > 32:
480
        raise InvalidBlock
481
    if header.difficulty != 0:
482
        raise InvalidBlock
483
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
484
        raise InvalidBlock
485
    if header.ommers_hash != EMPTY_OMMER_HASH:
486
        raise InvalidBlock
487
488
    block_parent_hash = keccak256(rlp.encode(parent_header))
489
    if header.parent_hash != block_parent_hash:
490
        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]:
499
    <snip>
559
    regular_gas_available = (
560
        block_env.block_gas_limit - block_output.block_gas_used
561
    )
562
    state_gas_available = (
563
        block_env.block_gas_limit - block_output.block_state_gas_used
564
    )
565
    blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used
566
567
    # EIP-8037 per-dimension inclusion check.
568
    if min(TX_MAX_GAS_LIMIT, tx.gas) > regular_gas_available:
569
        raise GasUsedExceedsLimitError("regular gas used exceeds limit")
570
571
    if tx.gas > state_gas_available:
572
        raise GasUsedExceedsLimitError("state gas used exceeds limit")
573
574
    tx_blob_gas_used = calculate_total_blob_gas(tx)
575
    if tx_blob_gas_used > blob_gas_available:
576
        raise BlobGasLimitExceededError("blob gas limit exceeded")
577
578
    sender_address = recover_sender(block_env.chain_id, tx)
579
    sender_account = get_account(tx_state, sender_address)
580
581
    if isinstance(tx, FeeMarketCapableTransaction):
582
        if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
583
            raise PriorityFeeGreaterThanMaxFeeError(
584
                "priority fee greater than max fee"
585
            )
586
        if tx.max_fee_per_gas < block_env.base_fee_per_gas:
587
            raise InsufficientMaxFeePerGasError(
588
                tx.max_fee_per_gas, block_env.base_fee_per_gas
589
            )
590
591
        priority_fee_per_gas = min(
592
            tx.max_priority_fee_per_gas,
593
            tx.max_fee_per_gas - block_env.base_fee_per_gas,
594
        )
595
        effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas
596
        max_gas_fee = tx.gas * tx.max_fee_per_gas
597
    else:
598
        if tx.gas_price < block_env.base_fee_per_gas:
599
            raise InvalidBlock
600
        effective_gas_price = tx.gas_price
601
        max_gas_fee = tx.gas * tx.gas_price
602
603
    if isinstance(tx, BlobTransaction):
604
        blob_count = len(tx.blob_versioned_hashes)
605
        if blob_count == 0:
606
            raise NoBlobDataError("no blob data in transaction")
607
        if blob_count > BLOB_COUNT_LIMIT:
608
            raise BlobCountExceededError(
609
                f"Tx has {blob_count} blobs. Max allowed: {BLOB_COUNT_LIMIT}"
610
            )
611
        for blob_versioned_hash in tx.blob_versioned_hashes:
612
            if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG:
613
                raise InvalidBlobVersionedHashError(
614
                    "invalid blob versioned hash"
615
                )
616
617
        blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas)
618
        if Uint(tx.max_fee_per_blob_gas) < blob_gas_price:
619
            raise InsufficientMaxFeePerBlobGasError(
620
                "insufficient max fee per blob gas"
621
            )
622
623
        max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint(
624
            tx.max_fee_per_blob_gas
625
        )
626
        blob_versioned_hashes = tx.blob_versioned_hashes
627
    else:
628
        blob_versioned_hashes = ()
629
630
    if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
631
        if not isinstance(tx.to, Address):
632
            raise TransactionTypeContractCreationError(tx)
633
634
    if isinstance(tx, SetCodeTransaction):
635
        if not any(tx.authorizations):
636
            raise EmptyAuthorizationListError("empty authorization list")
637
638
    if sender_account.nonce > Uint(tx.nonce):
639
        raise NonceMismatchError("nonce too low")
640
    elif sender_account.nonce < Uint(tx.nonce):
641
        raise NonceMismatchError("nonce too high")
642
643
    if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value):
644
        raise InsufficientBalanceError("insufficient sender balance")
645
    sender_code = get_code(tx_state, sender_account.code_hash)
646
    if sender_account.code_hash != EMPTY_CODE_HASH and not is_valid_delegation(
647
        sender_code
648
    ):
649
        raise InvalidSenderError("not EOA")
650
651
    return (
652
        sender_address,
653
        effective_gas_price,
654
        blob_versioned_hashes,
655
        tx_blob_gas_used,
656
    )

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

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