ethereum.forks.bpo5.forkethereum.forks.amsterdam.fork

Ethereum Specification.

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

Introduction

Entry point for the Ethereum specification.

BASE_FEE_MAX_CHANGE_DENOMINATOR

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)

MAX_BLOB_GAS_PER_BLOCK

118
MAX_BLOB_GAS_PER_BLOCK: Final[U64] = (
119
    GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB
120
)

VERSIONED_HASH_VERSION_KZG

121
VERSIONED_HASH_VERSION_KZG = b"\x01"

GWEI_TO_WEI

122
GWEI_TO_WEI = U256(10**9)

WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS

124
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
125
    "0x00000961Ef480Eb55e80D19ad83579A64c007002"
126
)

CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS

127
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
128
    "0x0000BBdDc7CE488642fb579F8B00f3a590007251"
129
)

HISTORY_STORAGE_ADDRESS

130
HISTORY_STORAGE_ADDRESS = hex_to_address(
131
    "0x0000F90827F1C53a10cb7A02335B175320002935"
132
)

MAX_BLOCK_SIZE

133
MAX_BLOCK_SIZE = 10_485_760

SAFETY_MARGIN

134
SAFETY_MARGIN = 2_097_152

MAX_RLP_BLOCK_SIZE

135
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN

BLOB_COUNT_LIMIT

136
BLOB_COUNT_LIMIT = 6

ChainContext

Chain context needed for block execution.

139
@slotted_freezable
140
@dataclass
class ChainContext:

chain_id

Identify the chain for transaction signature recovery.

146
    chain_id: U64

block_hashes

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

149
    block_hashes: List[Hash32]

parent_header

Parent header used for header validation and system contracts.

152
    parent_header: Header | PreviousHeader

BlockChain

History and current state of the block chain.

156
@dataclass
class BlockChain:

blocks

162
    blocks: List[Block]

state

163
    state: State

chain_id

164
    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:
168
    <snip>
187
    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]:
191
    <snip>
211
    recent_blocks = chain.blocks[-255:]
212
    # TODO: This function has not been tested rigorously
213
    if len(recent_blocks) == 0:
214
        return []
215
216
    recent_block_hashes = []
217
218
    for block in recent_blocks:
219
        prev_block_hash = block.header.parent_hash
220
        recent_block_hashes.append(prev_block_hash)
221
222
    # We are computing the hash only for the most recent block and not for
223
    # the rest of the blocks as they have successors which have the hash of
224
    # the current block as parent hash.
225
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
226
    recent_block_hashes.append(most_recent_block_hash)
227
228
    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:
232
    <snip>
224
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
225
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
226
227
    validate_header(chain, block.header)
228
    if block.ommers != ():
229
        raise InvalidBlock
230
231
    block_state = BlockState(pre_state=chain.state)
232
233
    block_env = vm.BlockEnvironment(
254
    chain_context = ChainContext(
255
        chain_id=chain.chain_id,
235
        state=block_state,
236
        block_gas_limit=block.header.gas_limit,
256
        block_hashes=get_last_256_block_hashes(chain),
238
        coinbase=block.header.coinbase,
239
        number=block.header.number,
240
        base_fee_per_gas=block.header.base_fee_per_gas,
241
        time=block.header.timestamp,
242
        prev_randao=block.header.prev_randao,
243
        excess_blob_gas=block.header.excess_blob_gas,
244
        parent_beacon_block_root=block.header.parent_beacon_block_root,
257
        parent_header=chain.blocks[-1].header,
258
    )
259
247
    block_output = apply_body(
248
        block_env=block_env,
249
        transactions=block.transactions,
250
        withdrawals=block.withdrawals,
251
    )
252
    block_diff = extract_block_diff(block_state)
253
    block_state_root, _ = chain.state.compute_state_root_and_trie_changes(
254
        block_diff.account_changes, block_diff.storage_changes
255
    )
256
    transactions_root = root(block_output.transactions_trie)
257
    receipt_root = root(block_output.receipts_trie)
258
    block_logs_bloom = logs_bloom(block_output.block_logs)
259
    withdrawals_root = root(block_output.withdrawals_trie)
260
    requests_hash = compute_requests_hash(block_output.requests)
261
262
    if block_output.block_gas_used != block.header.gas_used:
263
        raise InvalidBlock(
264
            f"{block_output.block_gas_used} != {block.header.gas_used}"
265
        )
266
    if transactions_root != block.header.transactions_root:
267
        raise InvalidBlock
268
    if block_state_root != block.header.state_root:
269
        raise InvalidBlock
270
    if receipt_root != block.header.receipt_root:
271
        raise InvalidBlock
272
    if block_logs_bloom != block.header.bloom:
273
        raise InvalidBlock
274
    if withdrawals_root != block.header.withdrawals_root:
275
        raise InvalidBlock
276
    if block_output.blob_gas_used != block.header.blob_gas_used:
277
        raise InvalidBlock
278
    if requests_hash != block.header.requests_hash:
279
        raise InvalidBlock
260
    block_diff = execute_block(block, chain.state, chain_context)
261
262
    apply_changes_to_state(chain.state, block_diff)
263
    chain.blocks.append(block)
264
    if len(chain.blocks) > 255:
265
        # Real clients have to store more blocks to deal with reorgs, but the
266
        # protocol only requires the last 255
267
        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:
275
    <snip>
295
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
296
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
297
298
    parent_header = chain_context.parent_header
299
    validate_header(parent_header, block.header)
300
301
    if block.ommers != ():
302
        raise InvalidBlock
303
304
    block_state = BlockState(pre_state=pre_state)
305
306
    block_env = vm.BlockEnvironment(
307
        chain_id=chain_context.chain_id,
308
        state=block_state,
309
        block_gas_limit=block.header.gas_limit,
310
        block_hashes=chain_context.block_hashes,
311
        coinbase=block.header.coinbase,
312
        number=block.header.number,
313
        base_fee_per_gas=block.header.base_fee_per_gas,
314
        time=block.header.timestamp,
315
        prev_randao=block.header.prev_randao,
316
        excess_blob_gas=block.header.excess_blob_gas,
317
        parent_beacon_block_root=block.header.parent_beacon_block_root,
318
        block_access_list_builder=BlockAccessListBuilder(),
319
        slot_number=block.header.slot_number,
320
    )
321
322
    block_output = apply_body(
323
        block_env=block_env,
324
        transactions=block.transactions,
325
        withdrawals=block.withdrawals,
326
    )
327
    block_diff = extract_block_diff(block_state)
328
    block_state_root, _ = pre_state.compute_state_root_and_trie_changes(
329
        block_diff.account_changes, block_diff.storage_changes
330
    )
331
    transactions_root = root(block_output.transactions_trie)
332
    receipt_root = root(block_output.receipts_trie)
333
    block_logs_bloom = logs_bloom(block_output.block_logs)
334
    withdrawals_root = root(block_output.withdrawals_trie)
335
    requests_hash = compute_requests_hash(block_output.requests)
336
    computed_block_access_list_hash = hash_block_access_list(
337
        block_output.block_access_list
338
    )
339
340
    if block_output.block_gas_used != block.header.gas_used:
341
        raise InvalidBlock(
342
            f"{block_output.block_gas_used} != {block.header.gas_used}"
343
        )
344
    if transactions_root != block.header.transactions_root:
345
        raise InvalidBlock
346
    if block_state_root != block.header.state_root:
347
        raise InvalidBlock
348
    if receipt_root != block.header.receipt_root:
349
        raise InvalidBlock
350
    if block_logs_bloom != block.header.bloom:
351
        raise InvalidBlock
352
    if withdrawals_root != block.header.withdrawals_root:
353
        raise InvalidBlock
354
    if block_output.blob_gas_used != block.header.blob_gas_used:
355
        raise InvalidBlock
356
    if requests_hash != block.header.requests_hash:
357
        raise InvalidBlock
358
    if computed_block_access_list_hash != block.header.block_access_list_hash:
359
        raise InvalidBlock("Invalid block access list hash")
360
361
    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:
370
    <snip>
390
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
391
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
392
        raise InvalidBlock
393
394
    if parent_gas_used == parent_gas_target:
395
        expected_base_fee_per_gas = parent_base_fee_per_gas
396
    elif parent_gas_used > parent_gas_target:
397
        gas_used_delta = parent_gas_used - parent_gas_target
398
399
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
400
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
401
402
        base_fee_per_gas_delta = max(
403
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
404
            Uint(1),
405
        )
406
407
        expected_base_fee_per_gas = (
408
            parent_base_fee_per_gas + base_fee_per_gas_delta
409
        )
410
    else:
411
        gas_used_delta = parent_gas_target - parent_gas_used
412
413
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
414
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
415
416
        base_fee_per_gas_delta = (
417
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
418
        )
419
420
        expected_base_fee_per_gas = (
421
            parent_base_fee_per_gas - base_fee_per_gas_delta
422
        )
423
424
    return Uint(expected_base_fee_per_gas)

validate_header

Verifies a block header.Verify a block header against its parent.

In order to consider a block's header valid, the logic for the quantities in the header should match the logic for the block itself. For example the header timestamp should be greater than the block's parent timestamp because the block was created after the parent block. Additionally, the block's number should be directly following the parent block's number since it is the next block in the sequence.

Parameters

chain :parent_header : History and current state.Header of the parent block. header : Header to check for correctness.

def validate_header(chainparent_header: BlockChainHeader | PreviousHeader, ​​header: Header) -> None:
430
    <snip>
448
    if header.number < Uint(1):
449
        raise InvalidBlock
373
374
    parent_header = chain.blocks[-1].header
450
451
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
452
    if header.excess_blob_gas != excess_blob_gas:
453
        raise InvalidBlock
454
455
    if header.gas_used > header.gas_limit:
456
        raise InvalidBlock
457
458
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
459
        header.gas_limit,
460
        parent_header.gas_limit,
461
        parent_header.gas_used,
462
        parent_header.base_fee_per_gas,
463
    )
464
    if expected_base_fee_per_gas != header.base_fee_per_gas:
465
        raise InvalidBlock
466
    if header.timestamp <= parent_header.timestamp:
467
        raise InvalidBlock
468
    if header.number != parent_header.number + Uint(1):
469
        raise InvalidBlock
470
    if len(header.extra_data) > 32:
471
        raise InvalidBlock
472
    if header.difficulty != 0:
473
        raise InvalidBlock
474
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
475
        raise InvalidBlock
476
    if header.ommers_hash != EMPTY_OMMER_HASH:
477
        raise InvalidBlock
478
479
    block_parent_hash = keccak256(rlp.encode(parent_header))
480
    if header.parent_hash != block_parent_hash:
481
        raise InvalidBlock

check_transaction

Check if the transaction is includable in the block.

Parameters

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

Returns

sender_address : The sender of the transaction. effective_gas_price : The price to charge for gas when the transaction is executed. blob_versioned_hashes : The blob versioned hashes of the transaction. tx_blob_gas_used: The blob gas used by the transaction.

Raises

InvalidBlock : If the transaction is not includable. GasUsedExceedsLimitError : If the gas used by the transaction exceeds the block's gas limit. NonceMismatchError : If the nonce of the transaction is not equal to the sender's nonce. InsufficientBalanceError : If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. PriorityFeeGreaterThanMaxFeeError : If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : If the maximum fee per blob gas is insufficient for the transaction. BlobGasLimitExceededError : If the blob gas used by the transaction exceeds the block's blob gas limit. InvalidBlobVersionedHashError : If the transaction contains a blob versioned hash with an invalid version. NoBlobDataError : If the transaction is a type 3 but has no blobs. BlobCountExceededError : If the transaction is a type 3 and has more blobs than the limit. TransactionTypeContractCreationError: If the transaction type is not allowed to create contracts. EmptyAuthorizationListError : If the transaction is a SetCodeTransaction and the authorization list is empty.

def check_transaction(block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo5.vm.BlockOutputethereum.forks.amsterdam.vm.BlockOutput, ​​tx: Transaction, ​​tx_state: TransactionState) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]:
490
    <snip>
550
    gas_available = block_env.block_gas_limit - block_output.block_gas_used
551
    blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used
552
553
    if tx.gas > gas_available:
554
        raise GasUsedExceedsLimitError("gas used exceeds limit")
555
556
    tx_blob_gas_used = calculate_total_blob_gas(tx)
557
    if tx_blob_gas_used > blob_gas_available:
558
        raise BlobGasLimitExceededError("blob gas limit exceeded")
559
560
    sender_address = recover_sender(block_env.chain_id, tx)
561
    sender_account = get_account(tx_state, sender_address)
562
563
    if isinstance(
564
        tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction)
565
    ):
566
        if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
567
            raise PriorityFeeGreaterThanMaxFeeError(
568
                "priority fee greater than max fee"
569
            )
570
        if tx.max_fee_per_gas < block_env.base_fee_per_gas:
571
            raise InsufficientMaxFeePerGasError(
572
                tx.max_fee_per_gas, block_env.base_fee_per_gas
573
            )
574
575
        priority_fee_per_gas = min(
576
            tx.max_priority_fee_per_gas,
577
            tx.max_fee_per_gas - block_env.base_fee_per_gas,
578
        )
579
        effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas
580
        max_gas_fee = tx.gas * tx.max_fee_per_gas
581
    else:
582
        if tx.gas_price < block_env.base_fee_per_gas:
583
            raise InvalidBlock
584
        effective_gas_price = tx.gas_price
585
        max_gas_fee = tx.gas * tx.gas_price
586
587
    if isinstance(tx, BlobTransaction):
588
        blob_count = len(tx.blob_versioned_hashes)
589
        if blob_count == 0:
590
            raise NoBlobDataError("no blob data in transaction")
591
        if blob_count > BLOB_COUNT_LIMIT:
592
            raise BlobCountExceededError(
593
                f"Tx has {blob_count} blobs. Max allowed: {BLOB_COUNT_LIMIT}"
594
            )
595
        for blob_versioned_hash in tx.blob_versioned_hashes:
596
            if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG:
597
                raise InvalidBlobVersionedHashError(
598
                    "invalid blob versioned hash"
599
                )
600
601
        blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas)
602
        if Uint(tx.max_fee_per_blob_gas) < blob_gas_price:
603
            raise InsufficientMaxFeePerBlobGasError(
604
                "insufficient max fee per blob gas"
605
            )
606
607
        max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint(
608
            tx.max_fee_per_blob_gas
609
        )
610
        blob_versioned_hashes = tx.blob_versioned_hashes
611
    else:
612
        blob_versioned_hashes = ()
613
614
    if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
615
        if not isinstance(tx.to, Address):
616
            raise TransactionTypeContractCreationError(tx)
617
618
    if isinstance(tx, SetCodeTransaction):
619
        if not any(tx.authorizations):
620
            raise EmptyAuthorizationListError("empty authorization list")
621
622
    if sender_account.nonce > Uint(tx.nonce):
623
        raise NonceMismatchError("nonce too low")
624
    elif sender_account.nonce < Uint(tx.nonce):
625
        raise NonceMismatchError("nonce too high")
626
627
    if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value):
628
        raise InsufficientBalanceError("insufficient sender balance")
629
    sender_code = get_code(tx_state, sender_account.code_hash)
630
    if sender_account.code_hash != EMPTY_CODE_HASH and not is_valid_delegation(
631
        sender_code
632
    ):
633
        raise InvalidSenderError("not EOA")
634
635
    return (
636
        sender_address,
637
        effective_gas_price,
638
        blob_versioned_hashes,
639
        tx_blob_gas_used,
640
    )

make_receipt

Make the receipt for a transaction that was executed.

Parameters

tx : The executed transaction. error : Error in the top level frame of the transaction, if any. cumulative_gas_used : The total gas used so far in the block after the transaction was executed.executed. This is the gas used after refunds. logs : The logs produced by the transaction.

Returns

receipt : The receipt for the transaction.

def make_receipt(tx: Transaction, ​​error: Optional[EthereumException], ​​cumulative_gas_used: Uint, ​​logs: Tuple[Log, ...]) -> Bytes | Receipt:
649
    <snip>
670
    receipt = Receipt(
671
        succeeded=error is None,
672
        cumulative_gas_used=cumulative_gas_used,
673
        bloom=logs_bloom(logs),
674
        logs=logs,
675
    )
676
677
    return encode_receipt(tx, receipt)

process_checked_system_transaction

Process a system transaction and raise an error if the contract does not contain code or if the transaction fails.

Parameters

block_env : The block scoped environment. target_address : Address of the contract to call. data : Data to pass to the contract.

Returns

system_tx_output : MessageCallOutput Output of processing the system transaction.

def process_checked_system_transaction(block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
685
    <snip>
704
    # Pre-check that the system contract has code. We use a throwaway
705
    # TransactionState here that is *never* propagated back to BlockState
706
    # (no incorporate_tx_into_block call); the same get_account / get_code
707
    # lookups are performed and properly tracked by
708
    # process_unchecked_system_transaction below, which this function
709
    # always calls. Reading via a TransactionState (rather than directly
710
    # against pre_state) lets us see system contracts deployed earlier in
711
    # the same block — see EIP-7002 and EIP-7251 for this edge case.
712
    untracked_state = TransactionState(parent=block_env.state)
713
    system_contract_code = get_code(
714
        untracked_state,
715
        get_account(untracked_state, target_address).code_hash,
716
    )
717
718
    if len(system_contract_code) == 0:
719
        raise InvalidBlock(
720
            f"System contract address {target_address.hex()} does not "
721
            "contain code"
722
        )
723
724
    system_tx_output = process_unchecked_system_transaction(
725
        block_env,
726
        target_address,
727
        data,
728
    )
729
730
    if system_tx_output.error:
731
        raise InvalidBlock(
732
            f"System contract ({target_address.hex()}) call failed: "
733
            f"{system_tx_output.error}"
734
        )
735
736
    return system_tx_output

process_unchecked_system_transaction

Process a system transaction without checking if the contract contains code or if the transaction fails.

Parameters

block_env : The block scoped environment. target_address : Address of the contract to call. data : Data to pass to the contract.

Returns

system_tx_output : MessageCallOutput Output of processing the system transaction.

def process_unchecked_system_transaction(block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
744
    <snip>
763
    system_tx_state = TransactionState(parent=block_env.state)
764
    system_contract_code = get_code(
765
        system_tx_state,
766
        get_account(system_tx_state, target_address).code_hash,
767
    )
768
769
    tx_env = vm.TransactionEnvironment(
770
        origin=SYSTEM_ADDRESS,
771
        gas_price=block_env.base_fee_per_gas,
772
        gas=SYSTEM_TRANSACTION_GAS,
773
        access_list_addresses=set(),
774
        access_list_storage_keys=set(),
775
        state=system_tx_state,
776
        blob_versioned_hashes=(),
777
        authorizations=(),
778
        index_in_block=None,
779
        tx_hash=None,
780
    )
781
782
    system_tx_message = Message(
783
        block_env=block_env,
784
        tx_env=tx_env,
785
        caller=SYSTEM_ADDRESS,
786
        target=target_address,
787
        gas=SYSTEM_TRANSACTION_GAS,
788
        value=U256(0),
789
        data=data,
790
        code=system_contract_code,
791
        depth=Uint(0),
792
        current_target=target_address,
793
        code_address=target_address,
794
        should_transfer_value=False,
795
        is_static=False,
796
        accessed_addresses=set(),
797
        accessed_storage_keys=set(),
798
        disable_precompiles=False,
799
        parent_evm=None,
800
    )
801
802
    system_tx_output = process_message_call(system_tx_message)
803
729
    incorporate_tx_into_block(system_tx_state)
804
    incorporate_tx_into_block(
805
        system_tx_state, block_env.block_access_list_builder
806
    )
807
808
    return system_tx_output

apply_body

Executes a block.

Many of the contents of a block are stored in data structures called tries. There is a transactions trie which is similar to a ledger of the transactions stored in the current block. There is also a receipts trie which stores the results of executing a transaction, like the post state and gas used. This function creates and executes the block that is to be added to the chain.

Parameters

block_env : The block scoped environment. transactions : Transactions included in the block. withdrawals : Withdrawals to be processed in the current block.

Returns

block_output : The block output for the current block.

def apply_body(block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​transactions: Tuple[LegacyTransaction | Bytes, ...], ​​withdrawals: Tuple[Withdrawal, ...]) -> ethereum.forks.bpo5.vm.BlockOutputethereum.forks.amsterdam.vm.BlockOutput:
816
    <snip>
841
    block_output = vm.BlockOutput()
842
843
    process_unchecked_system_transaction(
844
        block_env=block_env,
845
        target_address=BEACON_ROOTS_ADDRESS,
846
        data=block_env.parent_beacon_block_root,
847
    )
848
849
    process_unchecked_system_transaction(
850
        block_env=block_env,
851
        target_address=HISTORY_STORAGE_ADDRESS,
852
        data=block_env.block_hashes[-1],  # The parent hash
853
    )
854
855
    for i, tx in enumerate(map(decode_transaction, transactions)):
856
        process_transaction(block_env, block_output, tx, Uint(i))
857
858
    # EIP-7928: Post-execution operations use index N+1
859
    block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
860
        ulen(transactions) + Uint(1)
861
    )
862
863
    process_withdrawals(block_env, block_output, withdrawals)
864
865
    process_general_purpose_requests(
866
        block_env=block_env,
867
        block_output=block_output,
868
    )
869
870
    block_output.block_access_list = build_block_access_list(
871
        block_env.block_access_list_builder, block_env.state
872
    )
873
874
    # Validate block access list gas limit constraint (EIP-7928)
875
    validate_block_access_list_gas_limit(
876
        block_access_list=block_output.block_access_list,
877
        block_gas_limit=block_env.block_gas_limit,
878
    )
879
880
    return block_output

process_general_purpose_requests

Process all the requests in the block.

Parameters

block_env : The execution environment for the Block. block_output : The block output for the current block.

def process_general_purpose_requests(block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo5.vm.BlockOutputethereum.forks.amsterdam.vm.BlockOutput) -> None:
887
    <snip>
898
    # Requests are to be in ascending order of request type
899
    deposit_requests = parse_deposit_requests(block_output)
900
    requests_from_execution = block_output.requests
901
    if len(deposit_requests) > 0:
902
        requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests)
903
904
    system_withdrawal_tx_output = process_checked_system_transaction(
905
        block_env=block_env,
906
        target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
907
        data=b"",
908
    )
909
910
    if len(system_withdrawal_tx_output.return_data) > 0:
911
        requests_from_execution.append(
912
            WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data
913
        )
914
915
    system_consolidation_tx_output = process_checked_system_transaction(
916
        block_env=block_env,
917
        target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
918
        data=b"",
919
    )
920
921
    if len(system_consolidation_tx_output.return_data) > 0:
922
        requests_from_execution.append(
923
            CONSOLIDATION_REQUEST_TYPE
924
            + system_consolidation_tx_output.return_data
925
        )

process_transaction

Execute a transaction against the provided environment.

This function processes the actions needed to execute a transaction. It decrements the sender's account balance after calculating the gas fee and refunds them the proper amount after execution. Calling contracts, deploying code, and incrementing nonces are all examples of actions that happen within this function or from a call made within this function.

Accounts that are marked for deletion are processed and destroyed after execution.

Parameters

block_env : Environment for the Ethereum Virtual Machine. block_output : The block output for the current block. tx : Transaction to execute. index: Index of the transaction in the block.

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

process_withdrawals

Increase the balance of the withdrawing account.

def process_withdrawals(block_env: ethereum.forks.bpo5.vm.BlockEnvironmentethereum.forks.amsterdam.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo5.vm.BlockOutputethereum.forks.amsterdam.vm.BlockOutput, ​​withdrawals: Tuple[Withdrawal, ...]) -> None:
1130
    <snip>
1133
    wd_state = TransactionState(parent=block_env.state)
1134
1135
    for i, wd in enumerate(withdrawals):
1136
        trie_set(
1137
            block_output.withdrawals_trie,
1138
            rlp.encode(Uint(i)),
1139
            rlp.encode(wd),
1140
        )
1141
1011
        create_ether(wd_state, wd.address, wd.amount * U256(10**9))
1142
        create_ether(wd_state, wd.address, wd.amount * GWEI_TO_WEI)
1143
1013
    incorporate_tx_into_block(wd_state)
1144
    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:
1148
    <snip>
1176
    max_adjustment_delta = parent_gas_limit // GasCosts.LIMIT_ADJUSTMENT_FACTOR
1177
    if gas_limit >= parent_gas_limit + max_adjustment_delta:
1178
        return False
1179
    if gas_limit <= parent_gas_limit - max_adjustment_delta:
1180
        return False
1181
    if gas_limit < GasCosts.LIMIT_MINIMUM:
1182
        return False
1183
1184
    return True