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

111
BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8)

ELASTICITY_MULTIPLIER

112
ELASTICITY_MULTIPLIER = Uint(2)

EMPTY_OMMER_HASH

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

SYSTEM_ADDRESS

114
SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe")

BEACON_ROOTS_ADDRESS

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

SYSTEM_TRANSACTION_GAS

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

119
SYSTEM_MAX_SSTORES_PER_CALL = Uint(16)

MAX_BLOB_GAS_PER_BLOCK

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

VERSIONED_HASH_VERSION_KZG

127
VERSIONED_HASH_VERSION_KZG = b"\x01"

GWEI_TO_WEI

128
GWEI_TO_WEI = U256(10**9)

WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS

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

CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS

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

HISTORY_STORAGE_ADDRESS

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

MAX_BLOCK_SIZE

139
MAX_BLOCK_SIZE = 10_485_760

SAFETY_MARGIN

140
SAFETY_MARGIN = 2_097_152

MAX_RLP_BLOCK_SIZE

141
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN

BLOB_COUNT_LIMIT

142
BLOB_COUNT_LIMIT = 6

ChainContext

Chain context needed for block execution.

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

chain_id

Identify the chain for transaction signature recovery.

153
    chain_id: U64

block_hashes

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

156
    block_hashes: List[Hash32]

parent_header

Parent header used for header validation and system contracts.

159
    parent_header: Header | PreviousHeader

BlockChain

History and current state of the block chain.

163
@final
164
@dataclass
class BlockChain:

blocks

170
    blocks: List[Block]

state

171
    state: State

chain_id

172
    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:
176
    <snip>
195
    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]:
199
    <snip>
219
    recent_blocks = chain.blocks[-255:]
220
    # TODO: This function has not been tested rigorously
221
    if len(recent_blocks) == 0:
222
        return []
223
224
    recent_block_hashes = []
225
226
    for block in recent_blocks:
227
        prev_block_hash = block.header.parent_hash
228
        recent_block_hashes.append(prev_block_hash)
229
230
    # We are computing the hash only for the most recent block and not for
231
    # the rest of the blocks as they have successors which have the hash of
232
    # the current block as parent hash.
233
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
234
    recent_block_hashes.append(most_recent_block_hash)
235
236
    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:
240
    <snip>
225
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
226
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
227
228
    validate_header(chain, block.header)
229
    if block.ommers != ():
230
        raise InvalidBlock
231
232
    block_state = BlockState(pre_state=chain.state)
233
234
    block_env = vm.BlockEnvironment(
262
    chain_context = ChainContext(
263
        chain_id=chain.chain_id,
236
        state=block_state,
237
        block_gas_limit=block.header.gas_limit,
264
        block_hashes=get_last_256_block_hashes(chain),
239
        coinbase=block.header.coinbase,
240
        number=block.header.number,
241
        base_fee_per_gas=block.header.base_fee_per_gas,
242
        time=block.header.timestamp,
243
        prev_randao=block.header.prev_randao,
244
        excess_blob_gas=block.header.excess_blob_gas,
245
        parent_beacon_block_root=block.header.parent_beacon_block_root,
265
        parent_header=chain.blocks[-1].header,
266
    )
267
248
    block_output = apply_body(
249
        block_env=block_env,
250
        transactions=block.transactions,
251
        withdrawals=block.withdrawals,
252
    )
253
    block_diff = extract_block_diff(block_state)
254
    block_state_root, _ = chain.state.compute_state_root_and_trie_changes(
255
        block_diff.account_changes, block_diff.storage_changes
256
    )
257
    transactions_root = root(block_output.transactions_trie)
258
    receipt_root = root(block_output.receipts_trie)
259
    block_logs_bloom = logs_bloom(block_output.block_logs)
260
    withdrawals_root = root(block_output.withdrawals_trie)
261
    requests_hash = compute_requests_hash(block_output.requests)
262
263
    if block_output.block_gas_used != block.header.gas_used:
264
        raise InvalidBlock(
265
            f"{block_output.block_gas_used} != {block.header.gas_used}"
266
        )
267
    if transactions_root != block.header.transactions_root:
268
        raise InvalidBlock
269
    if block_state_root != block.header.state_root:
270
        raise InvalidBlock
271
    if receipt_root != block.header.receipt_root:
272
        raise InvalidBlock
273
    if block_logs_bloom != block.header.bloom:
274
        raise InvalidBlock
275
    if withdrawals_root != block.header.withdrawals_root:
276
        raise InvalidBlock
277
    if block_output.blob_gas_used != block.header.blob_gas_used:
278
        raise InvalidBlock
279
    if requests_hash != block.header.requests_hash:
280
        raise InvalidBlock
268
    block_diff = execute_block(block, chain.state, chain_context)
269
270
    apply_changes_to_state(chain.state, block_diff)
271
    chain.blocks.append(block)
272
    if len(chain.blocks) > 255:
273
        # Real clients have to store more blocks to deal with reorgs, but the
274
        # protocol only requires the last 255
275
        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:
283
    <snip>
303
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
304
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
305
306
    parent_header = chain_context.parent_header
307
    validate_header(parent_header, block.header)
308
309
    if block.ommers != ():
310
        raise InvalidBlock
311
312
    block_state = BlockState(pre_state=pre_state)
313
314
    block_env = vm.BlockEnvironment(
315
        chain_id=chain_context.chain_id,
316
        state=block_state,
317
        block_gas_limit=block.header.gas_limit,
318
        block_hashes=chain_context.block_hashes,
319
        coinbase=block.header.coinbase,
320
        number=block.header.number,
321
        base_fee_per_gas=block.header.base_fee_per_gas,
322
        time=block.header.timestamp,
323
        prev_randao=block.header.prev_randao,
324
        excess_blob_gas=block.header.excess_blob_gas,
325
        parent_beacon_block_root=block.header.parent_beacon_block_root,
326
        block_access_list_builder=BlockAccessListBuilder(),
327
        slot_number=block.header.slot_number,
328
    )
329
330
    block_output = apply_body(
331
        block_env=block_env,
332
        transactions=block.transactions,
333
        withdrawals=block.withdrawals,
334
    )
335
    block_diff = extract_block_diff(block_state)
336
    block_state_root, _ = pre_state.compute_state_root_and_trie_changes(
337
        block_diff.account_changes, block_diff.storage_changes
338
    )
339
    transactions_root = root(block_output.transactions_trie)
340
    receipt_root = root(block_output.receipts_trie)
341
    block_logs_bloom = logs_bloom(block_output.block_logs)
342
    withdrawals_root = root(block_output.withdrawals_trie)
343
    requests_hash = compute_requests_hash(block_output.requests)
344
    computed_block_access_list_hash = hash_block_access_list(
345
        block_output.block_access_list
346
    )
347
348
    block_gas_used = max(
349
        block_output.block_gas_used,
350
        block_output.block_state_gas_used,
351
    )
352
    if block_gas_used != block.header.gas_used:
353
        raise InvalidBlock(f"{block_gas_used} != {block.header.gas_used}")
354
    if transactions_root != block.header.transactions_root:
355
        raise InvalidBlock
356
    if block_state_root != block.header.state_root:
357
        raise InvalidBlock
358
    if receipt_root != block.header.receipt_root:
359
        raise InvalidBlock
360
    if block_logs_bloom != block.header.bloom:
361
        raise InvalidBlock
362
    if withdrawals_root != block.header.withdrawals_root:
363
        raise InvalidBlock
364
    if block_output.blob_gas_used != block.header.blob_gas_used:
365
        raise InvalidBlock
366
    if requests_hash != block.header.requests_hash:
367
        raise InvalidBlock
368
    if computed_block_access_list_hash != block.header.block_access_list_hash:
369
        raise InvalidBlock("Invalid block access list hash")
370
371
    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:
380
    <snip>
400
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
401
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
402
        raise InvalidBlock
403
404
    if parent_gas_used == parent_gas_target:
405
        expected_base_fee_per_gas = parent_base_fee_per_gas
406
    elif parent_gas_used > parent_gas_target:
407
        gas_used_delta = parent_gas_used - parent_gas_target
408
409
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
410
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
411
412
        base_fee_per_gas_delta = max(
413
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
414
            Uint(1),
415
        )
416
417
        expected_base_fee_per_gas = (
418
            parent_base_fee_per_gas + base_fee_per_gas_delta
419
        )
420
    else:
421
        gas_used_delta = parent_gas_target - parent_gas_used
422
423
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
424
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
425
426
        base_fee_per_gas_delta = (
427
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
428
        )
429
430
        expected_base_fee_per_gas = (
431
            parent_base_fee_per_gas - base_fee_per_gas_delta
432
        )
433
434
    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:
440
    <snip>
458
    if header.number < Uint(1):
459
        raise InvalidBlock
374
375
    parent_header = chain.blocks[-1].header
460
461
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
462
    if header.excess_blob_gas != excess_blob_gas:
463
        raise InvalidBlock
464
465
    if header.gas_used > header.gas_limit:
466
        raise InvalidBlock
467
468
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
469
        header.gas_limit,
470
        parent_header.gas_limit,
471
        parent_header.gas_used,
472
        parent_header.base_fee_per_gas,
473
    )
474
    if expected_base_fee_per_gas != header.base_fee_per_gas:
475
        raise InvalidBlock
476
    if header.timestamp <= parent_header.timestamp:
477
        raise InvalidBlock
478
    if header.number != parent_header.number + Uint(1):
479
        raise InvalidBlock
480
    if len(header.extra_data) > 32:
481
        raise InvalidBlock
482
    if header.difficulty != 0:
483
        raise InvalidBlock
484
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
485
        raise InvalidBlock
486
    if header.ommers_hash != EMPTY_OMMER_HASH:
487
        raise InvalidBlock
488
489
    block_parent_hash = keccak256(rlp.encode(parent_header))
490
    if header.parent_hash != block_parent_hash:
491
        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. intrinsic : The transaction's intrinsic gas cost, split into regular and state components.

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

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

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