ethereum.forks.bpo3.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
    if len(recent_blocks) == 0:
181
        return []
182
183
    recent_block_hashes = []
184
185
    for block in recent_blocks:
186
        prev_block_hash = block.header.parent_hash
187
        recent_block_hashes.append(prev_block_hash)
188
189
    # We are computing the hash only for the most recent block and not for
190
    # the rest of the blocks as they have successors which have the hash of
191
    # the current block as parent hash.
192
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
193
    recent_block_hashes.append(most_recent_block_hash)
194
195
    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:
199
    <snip>
221
    if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
222
        raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
223
224
    validate_header(chain, block.header)
225
    if block.ommers != ():
226
        raise InvalidBlock
227
228
    block_state = BlockState(pre_state=chain.state)
229
230
    block_env = vm.BlockEnvironment(
231
        chain_id=chain.chain_id,
232
        state=block_state,
233
        block_gas_limit=block.header.gas_limit,
234
        block_hashes=get_last_256_block_hashes(chain),
235
        coinbase=block.header.coinbase,
236
        number=block.header.number,
237
        base_fee_per_gas=block.header.base_fee_per_gas,
238
        time=block.header.timestamp,
239
        prev_randao=block.header.prev_randao,
240
        excess_blob_gas=block.header.excess_blob_gas,
241
        parent_beacon_block_root=block.header.parent_beacon_block_root,
242
    )
243
244
    block_output = apply_body(
245
        block_env=block_env,
246
        transactions=block.transactions,
247
        withdrawals=block.withdrawals,
248
    )
249
    block_diff = extract_block_diff(block_state)
250
    block_state_root = chain.state.compute_state_root(block_diff)
251
    transactions_root = root(block_output.transactions_trie)
252
    receipt_root = root(block_output.receipts_trie)
253
    block_logs_bloom = logs_bloom(block_output.block_logs)
254
    withdrawals_root = root(block_output.withdrawals_trie)
255
    requests_hash = compute_requests_hash(block_output.requests)
256
257
    if block_output.block_gas_used != block.header.gas_used:
258
        raise InvalidBlock(
259
            f"{block_output.block_gas_used} != {block.header.gas_used}"
260
        )
261
    if transactions_root != block.header.transactions_root:
262
        raise InvalidBlock
263
    if block_state_root != block.header.state_root:
264
        raise InvalidBlock
265
    if receipt_root != block.header.receipt_root:
266
        raise InvalidBlock
267
    if block_logs_bloom != block.header.bloom:
268
        raise InvalidBlock
269
    if withdrawals_root != block.header.withdrawals_root:
270
        raise InvalidBlock
271
    if block_output.blob_gas_used != block.header.blob_gas_used:
272
        raise InvalidBlock
273
    if requests_hash != block.header.requests_hash:
274
        raise InvalidBlock
275
276
    apply_changes_to_state(chain.state, block_diff)
277
    chain.blocks.append(block)
278
    if len(chain.blocks) > 255:
279
        # Real clients have to store more blocks to deal with reorgs, but the
280
        # protocol only requires the last 255
281
        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:
290
    <snip>
310
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
311
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
312
        raise InvalidBlock
313
314
    if parent_gas_used == parent_gas_target:
315
        expected_base_fee_per_gas = parent_base_fee_per_gas
316
    elif parent_gas_used > parent_gas_target:
317
        gas_used_delta = parent_gas_used - parent_gas_target
318
319
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
320
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
321
322
        base_fee_per_gas_delta = max(
323
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
324
            Uint(1),
325
        )
326
327
        expected_base_fee_per_gas = (
328
            parent_base_fee_per_gas + base_fee_per_gas_delta
329
        )
330
    else:
331
        gas_used_delta = parent_gas_target - parent_gas_used
332
333
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
334
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
335
336
        base_fee_per_gas_delta = (
337
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
338
        )
339
340
        expected_base_fee_per_gas = (
341
            parent_base_fee_per_gas - base_fee_per_gas_delta
342
        )
343
344
    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:
348
    <snip>
366
    if header.number < Uint(1):
367
        raise InvalidBlock
368
369
    parent_header = chain.blocks[-1].header
370
371
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
372
    if header.excess_blob_gas != excess_blob_gas:
373
        raise InvalidBlock
374
375
    if header.gas_used > header.gas_limit:
376
        raise InvalidBlock
377
378
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
379
        header.gas_limit,
380
        parent_header.gas_limit,
381
        parent_header.gas_used,
382
        parent_header.base_fee_per_gas,
383
    )
384
    if expected_base_fee_per_gas != header.base_fee_per_gas:
385
        raise InvalidBlock
386
    if header.timestamp <= parent_header.timestamp:
387
        raise InvalidBlock
388
    if header.number != parent_header.number + Uint(1):
389
        raise InvalidBlock
390
    if len(header.extra_data) > 32:
391
        raise InvalidBlock
392
    if header.difficulty != 0:
393
        raise InvalidBlock
394
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
395
        raise InvalidBlock
396
    if header.ommers_hash != EMPTY_OMMER_HASH:
397
        raise InvalidBlock
398
399
    block_parent_hash = keccak256(rlp.encode(parent_header))
400
    if header.parent_hash != block_parent_hash:
401
        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.bpo3.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo3.vm.BlockOutput, ​​tx: Transaction, ​​tx_state: TransactionState​) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]:
410
    <snip>
468
    gas_available = block_env.block_gas_limit - block_output.block_gas_used
469
    blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used
470
471
    if tx.gas > gas_available:
472
        raise GasUsedExceedsLimitError("gas used exceeds limit")
473
474
    tx_blob_gas_used = calculate_total_blob_gas(tx)
475
    if tx_blob_gas_used > blob_gas_available:
476
        raise BlobGasLimitExceededError("blob gas limit exceeded")
477
478
    tx_chain_id = chain_id(tx)
479
    if tx_chain_id is not None and tx_chain_id != block_env.chain_id:
480
        raise WrongChainIdError(
481
            expected=block_env.chain_id,
482
            actual=tx_chain_id,
483
        )
484
485
    sender_address = recover_sender(tx)
486
    sender_account = get_account(tx_state, sender_address)
487
488
    if isinstance(tx, FeeMarketCapableTransaction):
489
        if tx.max_fee_per_gas < block_env.base_fee_per_gas:
490
            raise InsufficientMaxFeePerGasError(
491
                tx.max_fee_per_gas, block_env.base_fee_per_gas
492
            )
493
494
        priority_fee_per_gas = min(
495
            tx.max_priority_fee_per_gas,
496
            tx.max_fee_per_gas - block_env.base_fee_per_gas,
497
        )
498
        effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas
499
        max_gas_fee = tx.gas * tx.max_fee_per_gas
500
    else:
501
        if tx.gas_price < block_env.base_fee_per_gas:
502
            raise InvalidBlock
503
        effective_gas_price = tx.gas_price
504
        max_gas_fee = tx.gas * tx.gas_price
505
506
    if isinstance(tx, BlobTransaction):
507
        blob_count = len(tx.blob_versioned_hashes)
508
        if blob_count == 0:
509
            raise NoBlobDataError("no blob data in transaction")
510
        if blob_count > BLOB_COUNT_LIMIT:
511
            raise BlobCountExceededError(
512
                f"Tx has {blob_count} blobs. Max allowed: {BLOB_COUNT_LIMIT}"
513
            )
514
        for blob_versioned_hash in tx.blob_versioned_hashes:
515
            if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG:
516
                raise InvalidBlobVersionedHashError(
517
                    "invalid blob versioned hash"
518
                )
519
520
        blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas)
521
        if Uint(tx.max_fee_per_blob_gas) < blob_gas_price:
522
            raise InsufficientMaxFeePerBlobGasError(
523
                "insufficient max fee per blob gas"
524
            )
525
526
        max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint(
527
            tx.max_fee_per_blob_gas
528
        )
529
        blob_versioned_hashes = tx.blob_versioned_hashes
530
    else:
531
        blob_versioned_hashes = ()
532
533
    if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
534
        if not isinstance(tx.to, Address):
535
            raise TransactionTypeContractCreationError(tx)
536
537
    if isinstance(tx, SetCodeTransaction):
538
        if not any(tx.authorizations):
539
            raise EmptyAuthorizationListError("empty authorization list")
540
541
    if sender_account.nonce > Uint(tx.nonce):
542
        raise NonceMismatchError("nonce too low")
543
    elif sender_account.nonce < Uint(tx.nonce):
544
        raise NonceMismatchError("nonce too high")
545
    if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value):
546
        raise InsufficientBalanceError("insufficient sender balance")
547
    sender_code = get_code(tx_state, sender_account.code_hash)
548
    if sender_account.code_hash != EMPTY_CODE_HASH and not is_valid_delegation(
549
        sender_code
550
    ):
551
        raise InvalidSenderError("not EOA")
552
553
    return (
554
        sender_address,
555
        effective_gas_price,
556
        blob_versioned_hashes,
557
        tx_blob_gas_used,
558
    )

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

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

process_withdrawals

Increase the balance of the withdrawing account.

def process_withdrawals(​block_env: ethereum.forks.bpo3.vm.BlockEnvironment, ​​block_output: ethereum.forks.bpo3.vm.BlockOutput, ​​withdrawals: Tuple[Withdrawal, ...]​) -> None:
984
    <snip>
987
    wd_state = TransactionState(parent=block_env.state)
988
989
    for i, wd in enumerate(withdrawals):
990
        trie_set(
991
            block_output.withdrawals_trie,
992
            rlp.encode(Uint(i)),
993
            rlp.encode(wd),
994
        )
995
996
        create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9))
997
998
    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:
1002
    <snip>
1030
    max_adjustment_delta = parent_gas_limit // GasCosts.LIMIT_ADJUSTMENT_FACTOR
1031
    if gas_limit >= parent_gas_limit + max_adjustment_delta:
1032
        return False
1033
    if gas_limit <= parent_gas_limit - max_adjustment_delta:
1034
        return False
1035
    if gas_limit < GasCosts.LIMIT_MINIMUM:
1036
        return False
1037
1038
    return True