ethereum.forks.bpo2.fork

Ethereum Specification.

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

Introduction

Entry point for the Ethereum specification.

BASE_FEE_MAX_CHANGE_DENOMINATOR

95
BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8)

ELASTICITY_MULTIPLIER

96
ELASTICITY_MULTIPLIER = Uint(2)

EMPTY_OMMER_HASH

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

SYSTEM_ADDRESS

98
SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe")

BEACON_ROOTS_ADDRESS

99
BEACON_ROOTS_ADDRESS = hex_to_address(
100
    "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02"
101
)

SYSTEM_TRANSACTION_GAS

102
SYSTEM_TRANSACTION_GAS = Uint(30000000)

MAX_BLOB_GAS_PER_BLOCK

103
MAX_BLOB_GAS_PER_BLOCK: Final[U64] = (
104
    GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB
105
)

VERSIONED_HASH_VERSION_KZG

106
VERSIONED_HASH_VERSION_KZG = b"\x01"

WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS

108
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
109
    "0x00000961Ef480Eb55e80D19ad83579A64c007002"
110
)

CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS

111
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
112
    "0x0000BBdDc7CE488642fb579F8B00f3a590007251"
113
)

HISTORY_STORAGE_ADDRESS

114
HISTORY_STORAGE_ADDRESS = hex_to_address(
115
    "0x0000F90827F1C53a10cb7A02335B175320002935"
116
)

MAX_BLOCK_SIZE

117
MAX_BLOCK_SIZE = 10_485_760

SAFETY_MARGIN

118
SAFETY_MARGIN = 2_097_152

MAX_RLP_BLOCK_SIZE

119
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN

BLOB_COUNT_LIMIT

120
BLOB_COUNT_LIMIT = 6

BlockChain

History and current state of the block chain.

123
@final
124
@dataclass
class BlockChain:

blocks

130
    blocks: List[Block]

state

131
    state: State

chain_id

132
    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:
136
    <snip>
155
    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]:
159
    <snip>
179
    recent_blocks = chain.blocks[-255:]
180
    # TODO: This function has not been tested rigorously
181
    if len(recent_blocks) == 0:
182
        return []
183
184
    recent_block_hashes = []
185
186
    for block in recent_blocks:
187
        prev_block_hash = block.header.parent_hash
188
        recent_block_hashes.append(prev_block_hash)
189
190
    # We are computing the hash only for the most recent block and not for
191
    # the rest of the blocks as they have successors which have the hash of
192
    # the current block as parent hash.
193
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
194
    recent_block_hashes.append(most_recent_block_hash)
195
196
    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:
200
    <snip>
222
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
223
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
224
225
    validate_header(chain, block.header)
226
    if block.ommers != ():
227
        raise InvalidBlock
228
229
    block_state = BlockState(pre_state=chain.state)
230
231
    block_env = vm.BlockEnvironment(
232
        chain_id=chain.chain_id,
233
        state=block_state,
234
        block_gas_limit=block.header.gas_limit,
235
        block_hashes=get_last_256_block_hashes(chain),
236
        coinbase=block.header.coinbase,
237
        number=block.header.number,
238
        base_fee_per_gas=block.header.base_fee_per_gas,
239
        time=block.header.timestamp,
240
        prev_randao=block.header.prev_randao,
241
        excess_blob_gas=block.header.excess_blob_gas,
242
        parent_beacon_block_root=block.header.parent_beacon_block_root,
243
    )
244
245
    block_output = apply_body(
246
        block_env=block_env,
247
        transactions=block.transactions,
248
        withdrawals=block.withdrawals,
249
    )
250
    block_diff = extract_block_diff(block_state)
251
    block_state_root = chain.state.compute_state_root(block_diff)
252
    transactions_root = root(block_output.transactions_trie)
253
    receipt_root = root(block_output.receipts_trie)
254
    block_logs_bloom = logs_bloom(block_output.block_logs)
255
    withdrawals_root = root(block_output.withdrawals_trie)
256
    requests_hash = compute_requests_hash(block_output.requests)
257
258
    if block_output.block_gas_used != block.header.gas_used:
259
        raise InvalidBlock(
260
            f"{block_output.block_gas_used} != {block.header.gas_used}"
261
        )
262
    if transactions_root != block.header.transactions_root:
263
        raise InvalidBlock
264
    if block_state_root != block.header.state_root:
265
        raise InvalidBlock
266
    if receipt_root != block.header.receipt_root:
267
        raise InvalidBlock
268
    if block_logs_bloom != block.header.bloom:
269
        raise InvalidBlock
270
    if withdrawals_root != block.header.withdrawals_root:
271
        raise InvalidBlock
272
    if block_output.blob_gas_used != block.header.blob_gas_used:
273
        raise InvalidBlock
274
    if requests_hash != block.header.requests_hash:
275
        raise InvalidBlock
276
277
    apply_changes_to_state(chain.state, block_diff)
278
    chain.blocks.append(block)
279
    if len(chain.blocks) > 255:
280
        # Real clients have to store more blocks to deal with reorgs, but the
281
        # protocol only requires the last 255
282
        chain.blocks = chain.blocks[-255:]

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:
291
    <snip>
311
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
312
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
313
        raise InvalidBlock
314
315
    if parent_gas_used == parent_gas_target:
316
        expected_base_fee_per_gas = parent_base_fee_per_gas
317
    elif parent_gas_used > parent_gas_target:
318
        gas_used_delta = parent_gas_used - parent_gas_target
319
320
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
321
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
322
323
        base_fee_per_gas_delta = max(
324
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
325
            Uint(1),
326
        )
327
328
        expected_base_fee_per_gas = (
329
            parent_base_fee_per_gas + base_fee_per_gas_delta
330
        )
331
    else:
332
        gas_used_delta = parent_gas_target - parent_gas_used
333
334
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
335
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
336
337
        base_fee_per_gas_delta = (
338
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
339
        )
340
341
        expected_base_fee_per_gas = (
342
            parent_base_fee_per_gas - base_fee_per_gas_delta
343
        )
344
345
    return Uint(expected_base_fee_per_gas)

validate_header

Verifies a block header.

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 : History and current state. header : Header to check for correctness.

def validate_header(chain: BlockChain, ​​header: Header) -> None:
349
    <snip>
367
    if header.number < Uint(1):
368
        raise InvalidBlock
369
370
    parent_header = chain.blocks[-1].header
371
372
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
373
    if header.excess_blob_gas != excess_blob_gas:
374
        raise InvalidBlock
375
376
    if header.gas_used > header.gas_limit:
377
        raise InvalidBlock
378
379
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
380
        header.gas_limit,
381
        parent_header.gas_limit,
382
        parent_header.gas_used,
383
        parent_header.base_fee_per_gas,
384
    )
385
    if expected_base_fee_per_gas != header.base_fee_per_gas:
386
        raise InvalidBlock
387
    if header.timestamp <= parent_header.timestamp:
388
        raise InvalidBlock
389
    if header.number != parent_header.number + Uint(1):
390
        raise InvalidBlock
391
    if len(header.extra_data) > 32:
392
        raise InvalidBlock
393
    if header.difficulty != 0:
394
        raise InvalidBlock
395
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
396
        raise InvalidBlock
397
    if header.ommers_hash != EMPTY_OMMER_HASH:
398
        raise InvalidBlock
399
400
    block_parent_hash = keccak256(rlp.encode(parent_header))
401
    if header.parent_hash != block_parent_hash:
402
        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. 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.bpo2.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo2.vm.BlockOutput, ​​tx: Transaction, ​​tx_state: TransactionState) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]:
411
    <snip>
469
    gas_available = block_env.block_gas_limit - block_output.block_gas_used
470
    blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used
471
472
    if tx.gas > gas_available:
473
        raise GasUsedExceedsLimitError("gas used exceeds limit")
474
475
    tx_blob_gas_used = calculate_total_blob_gas(tx)
476
    if tx_blob_gas_used > blob_gas_available:
477
        raise BlobGasLimitExceededError("blob gas limit exceeded")
478
479
    tx_chain_id = chain_id(tx)
480
    if tx_chain_id is not None and tx_chain_id != block_env.chain_id:
481
        raise WrongChainIdError(
482
            expected=block_env.chain_id,
483
            actual=tx_chain_id,
484
        )
485
486
    sender_address = recover_sender(tx)
487
    sender_account = get_account(tx_state, sender_address)
488
489
    if isinstance(tx, FeeMarketCapableTransaction):
490
        if tx.max_fee_per_gas < block_env.base_fee_per_gas:
491
            raise InsufficientMaxFeePerGasError(
492
                tx.max_fee_per_gas, block_env.base_fee_per_gas
493
            )
494
495
        priority_fee_per_gas = min(
496
            tx.max_priority_fee_per_gas,
497
            tx.max_fee_per_gas - block_env.base_fee_per_gas,
498
        )
499
        effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas
500
        max_gas_fee = tx.gas * tx.max_fee_per_gas
501
    else:
502
        if tx.gas_price < block_env.base_fee_per_gas:
503
            raise InvalidBlock
504
        effective_gas_price = tx.gas_price
505
        max_gas_fee = tx.gas * tx.gas_price
506
507
    if isinstance(tx, BlobTransaction):
508
        blob_count = len(tx.blob_versioned_hashes)
509
        if blob_count == 0:
510
            raise NoBlobDataError("no blob data in transaction")
511
        if blob_count > BLOB_COUNT_LIMIT:
512
            raise BlobCountExceededError(
513
                f"Tx has {blob_count} blobs. Max allowed: {BLOB_COUNT_LIMIT}"
514
            )
515
        for blob_versioned_hash in tx.blob_versioned_hashes:
516
            if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG:
517
                raise InvalidBlobVersionedHashError(
518
                    "invalid blob versioned hash"
519
                )
520
521
        blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas)
522
        if Uint(tx.max_fee_per_blob_gas) < blob_gas_price:
523
            raise InsufficientMaxFeePerBlobGasError(
524
                "insufficient max fee per blob gas"
525
            )
526
527
        max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint(
528
            tx.max_fee_per_blob_gas
529
        )
530
        blob_versioned_hashes = tx.blob_versioned_hashes
531
    else:
532
        blob_versioned_hashes = ()
533
534
    if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
535
        if not isinstance(tx.to, Address):
536
            raise TransactionTypeContractCreationError(tx)
537
538
    if isinstance(tx, SetCodeTransaction):
539
        if not any(tx.authorizations):
540
            raise EmptyAuthorizationListError("empty authorization list")
541
542
    if sender_account.nonce > Uint(tx.nonce):
543
        raise NonceMismatchError("nonce too low")
544
    elif sender_account.nonce < Uint(tx.nonce):
545
        raise NonceMismatchError("nonce too high")
546
547
    if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value):
548
        raise InsufficientBalanceError("insufficient sender balance")
549
    sender_code = get_code(tx_state, sender_account.code_hash)
550
    if sender_account.code_hash != EMPTY_CODE_HASH and not is_valid_delegation(
551
        sender_code
552
    ):
553
        raise InvalidSenderError("not EOA")
554
555
    return (
556
        sender_address,
557
        effective_gas_price,
558
        blob_versioned_hashes,
559
        tx_blob_gas_used,
560
    )

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. 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:
569
    <snip>
590
    receipt = Receipt(
591
        succeeded=error is None,
592
        cumulative_gas_used=cumulative_gas_used,
593
        bloom=logs_bloom(logs),
594
        logs=logs,
595
    )
596
597
    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.bpo2.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
605
    <snip>
624
    # Pre-check that the system contract has code. We use a throwaway
625
    # TransactionState here that is *never* propagated back to BlockState
626
    # (no incorporate_tx_into_block call); the same get_account / get_code
627
    # lookups are performed and properly tracked by
628
    # process_unchecked_system_transaction below, which this function
629
    # always calls. Reading via a TransactionState (rather than directly
630
    # against pre_state) lets us see system contracts deployed earlier in
631
    # the same block — see EIP-7002 and EIP-7251 for this edge case.
632
    untracked_state = TransactionState(parent=block_env.state)
633
    system_contract_code = get_code(
634
        untracked_state,
635
        get_account(untracked_state, target_address).code_hash,
636
    )
637
638
    if len(system_contract_code) == 0:
639
        raise InvalidBlock(
640
            f"System contract address {target_address.hex()} does not "
641
            "contain code"
642
        )
643
644
    system_tx_output = process_unchecked_system_transaction(
645
        block_env,
646
        target_address,
647
        data,
648
    )
649
650
    if system_tx_output.error:
651
        raise InvalidBlock(
652
            f"System contract ({target_address.hex()}) call failed: "
653
            f"{system_tx_output.error}"
654
        )
655
656
    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.bpo2.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
664
    <snip>
683
    system_tx_state = TransactionState(parent=block_env.state)
684
    system_contract_code = get_code(
685
        system_tx_state,
686
        get_account(system_tx_state, target_address).code_hash,
687
    )
688
689
    tx_env = vm.TransactionEnvironment(
690
        origin=SYSTEM_ADDRESS,
691
        gas_price=block_env.base_fee_per_gas,
692
        gas=SYSTEM_TRANSACTION_GAS,
693
        access_list_addresses=set(),
694
        access_list_storage_keys=set(),
695
        state=system_tx_state,
696
        blob_versioned_hashes=(),
697
        authorizations=(),
698
        index_in_block=None,
699
        tx_hash=None,
700
    )
701
702
    system_tx_message = Message(
703
        block_env=block_env,
704
        tx_env=tx_env,
705
        caller=SYSTEM_ADDRESS,
706
        target=target_address,
707
        gas=SYSTEM_TRANSACTION_GAS,
708
        value=U256(0),
709
        data=data,
710
        code=system_contract_code,
711
        depth=Uint(0),
712
        current_target=target_address,
713
        code_address=target_address,
714
        should_transfer_value=False,
715
        is_static=False,
716
        accessed_addresses=set(),
717
        accessed_storage_keys=set(),
718
        disable_precompiles=False,
719
        parent_evm=None,
720
    )
721
722
    system_tx_output = process_message_call(system_tx_message)
723
724
    incorporate_tx_into_block(system_tx_state)
725
726
    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.bpo2.vm.BlockEnvironment, ​​transactions: Tuple[LegacyTransaction | Bytes, ...], ​​withdrawals: Tuple[Withdrawal, ...]) -> ethereum.forks.bpo2.vm.BlockOutput:
734
    <snip>
759
    block_output = vm.BlockOutput()
760
761
    process_unchecked_system_transaction(
762
        block_env=block_env,
763
        target_address=BEACON_ROOTS_ADDRESS,
764
        data=block_env.parent_beacon_block_root,
765
    )
766
767
    process_unchecked_system_transaction(
768
        block_env=block_env,
769
        target_address=HISTORY_STORAGE_ADDRESS,
770
        data=block_env.block_hashes[-1],  # The parent hash
771
    )
772
773
    for i, tx in enumerate(map(decode_transaction, transactions)):
774
        process_transaction(block_env, block_output, tx, Uint(i))
775
776
    process_withdrawals(block_env, block_output, withdrawals)
777
778
    process_general_purpose_requests(
779
        block_env=block_env,
780
        block_output=block_output,
781
    )
782
783
    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.bpo2.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo2.vm.BlockOutput) -> None:
790
    <snip>
801
    # Requests are to be in ascending order of request type
802
    deposit_requests = parse_deposit_requests(block_output)
803
    requests_from_execution = block_output.requests
804
    if len(deposit_requests) > 0:
805
        requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests)
806
807
    system_withdrawal_tx_output = process_checked_system_transaction(
808
        block_env=block_env,
809
        target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
810
        data=b"",
811
    )
812
813
    if len(system_withdrawal_tx_output.return_data) > 0:
814
        requests_from_execution.append(
815
            WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data
816
        )
817
818
    system_consolidation_tx_output = process_checked_system_transaction(
819
        block_env=block_env,
820
        target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
821
        data=b"",
822
    )
823
824
    if len(system_consolidation_tx_output.return_data) > 0:
825
        requests_from_execution.append(
826
            CONSOLIDATION_REQUEST_TYPE
827
            + system_consolidation_tx_output.return_data
828
        )

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.bpo2.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo2.vm.BlockOutput, ​​tx: Transaction, ​​index: Uint) -> None:
837
    <snip>
861
    tx_state = TransactionState(parent=block_env.state)
862
863
    trie_set(
864
        block_output.transactions_trie,
865
        rlp.encode(index),
866
        encode_transaction(tx),
867
    )
868
869
    intrinsic = validate_transaction(tx)
870
871
    (
872
        sender,
873
        effective_gas_price,
874
        blob_versioned_hashes,
875
        tx_blob_gas_used,
876
    ) = check_transaction(
877
        block_env=block_env,
878
        block_output=block_output,
879
        tx=tx,
880
        tx_state=tx_state,
881
    )
882
883
    sender_account = get_account(tx_state, sender)
884
885
    if isinstance(tx, BlobTransaction):
886
        blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx)
887
    else:
888
        blob_gas_fee = Uint(0)
889
890
    effective_gas_fee = tx.gas * effective_gas_price
891
892
    gas = tx.gas - intrinsic.regular
893
    increment_nonce(tx_state, sender)
894
895
    sender_balance_after_gas_fee = (
896
        Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
897
    )
898
    set_account_balance(tx_state, sender, U256(sender_balance_after_gas_fee))
899
900
    access_list_addresses = set()
901
    access_list_storage_keys = set()
902
    access_list_addresses.add(block_env.coinbase)
903
    if has_access_list(tx):
904
        for access in tx.access_list:
905
            access_list_addresses.add(access.account)
906
            for slot in access.slots:
907
                access_list_storage_keys.add((access.account, slot))
908
909
    authorizations: Tuple[Authorization, ...] = ()
910
    if isinstance(tx, SetCodeTransaction):
911
        authorizations = tx.authorizations
912
913
    tx_env = vm.TransactionEnvironment(
914
        origin=sender,
915
        gas_price=effective_gas_price,
916
        gas=gas,
917
        access_list_addresses=access_list_addresses,
918
        access_list_storage_keys=access_list_storage_keys,
919
        state=tx_state,
920
        blob_versioned_hashes=blob_versioned_hashes,
921
        authorizations=authorizations,
922
        index_in_block=index,
923
        tx_hash=get_transaction_hash(encode_transaction(tx)),
924
    )
925
926
    message = prepare_message(block_env, tx_env, tx)
927
928
    tx_output = process_message_call(message)
929
930
    # For EIP-7623 we first calculate the execution_gas_used, which includes
931
    # the execution gas refund.
932
    tx_gas_used_before_refund = tx.gas - tx_output.gas_left
933
    tx_gas_refund = min(
934
        tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter)
935
    )
936
    tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund
937
938
    # Transactions with less execution_gas_used than the floor pay at the
939
    # floor cost.
940
    tx_gas_used_after_refund = max(
941
        tx_gas_used_after_refund, intrinsic.calldata_floor
942
    )
943
944
    tx_gas_left = tx.gas - tx_gas_used_after_refund
945
    gas_refund_amount = tx_gas_left * effective_gas_price
946
947
    # For non-1559 transactions effective_gas_price == tx.gas_price
948
    priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas
949
    transaction_fee = tx_gas_used_after_refund * priority_fee_per_gas
950
951
    # refund gas
952
    create_ether(tx_state, sender, U256(gas_refund_amount))
953
954
    # transfer miner fees
955
    create_ether(tx_state, block_env.coinbase, U256(transaction_fee))
956
957
    for address in tx_output.accounts_to_delete:
958
        destroy_account(tx_state, address)
959
960
    block_output.block_gas_used += tx_gas_used_after_refund
961
    block_output.blob_gas_used += tx_blob_gas_used
962
963
    receipt = make_receipt(
964
        tx, tx_output.error, block_output.block_gas_used, tx_output.logs
965
    )
966
967
    receipt_key = rlp.encode(Uint(index))
968
    block_output.receipt_keys += (receipt_key,)
969
970
    trie_set(
971
        block_output.receipts_trie,
972
        receipt_key,
973
        receipt,
974
    )
975
976
    block_output.block_logs += tx_output.logs
977
978
    incorporate_tx_into_block(tx_state)

process_withdrawals

Increase the balance of the withdrawing account.

def process_withdrawals(block_env: ethereum.forks.bpo2.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo2.vm.BlockOutput, ​​withdrawals: Tuple[Withdrawal, ...]) -> None:
986
    <snip>
989
    wd_state = TransactionState(parent=block_env.state)
990
991
    for i, wd in enumerate(withdrawals):
992
        trie_set(
993
            block_output.withdrawals_trie,
994
            rlp.encode(Uint(i)),
995
            rlp.encode(wd),
996
        )
997
998
        create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9))
999
1000
    incorporate_tx_into_block(wd_state)

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:
1004
    <snip>
1032
    max_adjustment_delta = parent_gas_limit // GasCosts.LIMIT_ADJUSTMENT_FACTOR
1033
    if gas_limit >= parent_gas_limit + max_adjustment_delta:
1034
        return False
1035
    if gas_limit <= parent_gas_limit - max_adjustment_delta:
1036
        return False
1037
    if gas_limit < GasCosts.LIMIT_MINIMUM:
1038
        return False
1039
1040
    return True