ethereum.prague.fork

Ethereum Specification ^^^^^^^^^^^^^^^^^^^^^^

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

Introduction

Entry point for the Ethereum specification.

BASE_FEE_MAX_CHANGE_DENOMINATOR

90
BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8)

ELASTICITY_MULTIPLIER

91
ELASTICITY_MULTIPLIER = Uint(2)

GAS_LIMIT_ADJUSTMENT_FACTOR

92
GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024)

GAS_LIMIT_MINIMUM

93
GAS_LIMIT_MINIMUM = Uint(5000)

EMPTY_OMMER_HASH

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

SYSTEM_ADDRESS

95
SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe")

BEACON_ROOTS_ADDRESS

96
BEACON_ROOTS_ADDRESS = hex_to_address(
97
    "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02"
98
)

SYSTEM_TRANSACTION_GAS

99
SYSTEM_TRANSACTION_GAS = Uint(30000000)

MAX_BLOB_GAS_PER_BLOCK

100
MAX_BLOB_GAS_PER_BLOCK = U64(1179648)

VERSIONED_HASH_VERSION_KZG

101
VERSIONED_HASH_VERSION_KZG = b"\x01"

WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS

103
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
104
    "0x00000961Ef480Eb55e80D19ad83579A64c007002"
105
)

CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS

106
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address(
107
    "0x0000BBdDc7CE488642fb579F8B00f3a590007251"
108
)

HISTORY_STORAGE_ADDRESS

109
HISTORY_STORAGE_ADDRESS = hex_to_address(
110
    "0x0000F90827F1C53a10cb7A02335B175320002935"
111
)

HISTORY_SERVE_WINDOW

112
HISTORY_SERVE_WINDOW = 8192

BlockChain

History and current state of the block chain.

115
@dataclass
class BlockChain:

blocks

121
    blocks: List[Block]

state

122
    state: State

chain_id

123
    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:
127
    """
128
    Transforms the state from the previous hard fork (`old`) into the block
129
    chain object for this hard fork and returns it.
130
131
    When forks need to implement an irregular state transition, this function
132
    is used to handle the irregularity. See the :ref:`DAO Fork <dao-fork>` for
133
    an example.
134
135
    Parameters
136
    ----------
137
    old :
138
        Previous block chain object.
139
140
    Returns
141
    -------
142
    new : `BlockChain`
143
        Upgraded block chain object for this hard fork.
144
    """
145
    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]:
149
    """
150
    Obtain the list of hashes of the previous 256 blocks in order of
151
    increasing block number.
152
153
    This function will return less hashes for the first 256 blocks.
154
155
    The ``BLOCKHASH`` opcode needs to access the latest hashes on the chain,
156
    therefore this function retrieves them.
157
158
    Parameters
159
    ----------
160
    chain :
161
        History and current state.
162
163
    Returns
164
    -------
165
    recent_block_hashes : `List[Hash32]`
166
        Hashes of the recent 256 blocks in order of increasing block number.
167
    """
168
    recent_blocks = chain.blocks[-255:]
169
    # TODO: This function has not been tested rigorously
170
    if len(recent_blocks) == 0:
171
        return []
172
173
    recent_block_hashes = []
174
175
    for block in recent_blocks:
176
        prev_block_hash = block.header.parent_hash
177
        recent_block_hashes.append(prev_block_hash)
178
179
    # We are computing the hash only for the most recent block and not for
180
    # the rest of the blocks as they have successors which have the hash of
181
    # the current block as parent hash.
182
    most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header))
183
    recent_block_hashes.append(most_recent_block_hash)
184
185
    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:
189
    """
190
    Attempts to apply a block to an existing block chain.
191
192
    All parts of the block's contents need to be verified before being added
193
    to the chain. Blocks are verified by ensuring that the contents of the
194
    block make logical sense with the contents of the parent block. The
195
    information in the block's header must also match the corresponding
196
    information in the block.
197
198
    To implement Ethereum, in theory clients are only required to store the
199
    most recent 255 blocks of the chain since as far as execution is
200
    concerned, only those blocks are accessed. Practically, however, clients
201
    should store more blocks to handle reorgs.
202
203
    Parameters
204
    ----------
205
    chain :
206
        History and current state.
207
    block :
208
        Block to apply to `chain`.
209
    """
210
    validate_header(chain, block.header)
211
    if block.ommers != ():
212
        raise InvalidBlock
213
214
    block_env = vm.BlockEnvironment(
215
        chain_id=chain.chain_id,
216
        state=chain.state,
217
        block_gas_limit=block.header.gas_limit,
218
        block_hashes=get_last_256_block_hashes(chain),
219
        coinbase=block.header.coinbase,
220
        number=block.header.number,
221
        base_fee_per_gas=block.header.base_fee_per_gas,
222
        time=block.header.timestamp,
223
        prev_randao=block.header.prev_randao,
224
        excess_blob_gas=block.header.excess_blob_gas,
225
        parent_beacon_block_root=block.header.parent_beacon_block_root,
226
    )
227
228
    block_output = apply_body(
229
        block_env=block_env,
230
        transactions=block.transactions,
231
        withdrawals=block.withdrawals,
232
    )
233
    block_state_root = state_root(block_env.state)
234
    transactions_root = root(block_output.transactions_trie)
235
    receipt_root = root(block_output.receipts_trie)
236
    block_logs_bloom = logs_bloom(block_output.block_logs)
237
    withdrawals_root = root(block_output.withdrawals_trie)
238
    requests_hash = compute_requests_hash(block_output.requests)
239
240
    if block_output.block_gas_used != block.header.gas_used:
241
        raise InvalidBlock(
242
            f"{block_output.block_gas_used} != {block.header.gas_used}"
243
        )
244
    if transactions_root != block.header.transactions_root:
245
        raise InvalidBlock
246
    if block_state_root != block.header.state_root:
247
        raise InvalidBlock
248
    if receipt_root != block.header.receipt_root:
249
        raise InvalidBlock
250
    if block_logs_bloom != block.header.bloom:
251
        raise InvalidBlock
252
    if withdrawals_root != block.header.withdrawals_root:
253
        raise InvalidBlock
254
    if block_output.blob_gas_used != block.header.blob_gas_used:
255
        raise InvalidBlock
256
    if requests_hash != block.header.requests_hash:
257
        raise InvalidBlock
258
259
    chain.blocks.append(block)
260
    if len(chain.blocks) > 255:
261
        # Real clients have to store more blocks to deal with reorgs, but the
262
        # protocol only requires the last 255
263
        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:
272
    """
273
    Calculates the base fee per gas for the block.
274
275
    Parameters
276
    ----------
277
    block_gas_limit :
278
        Gas limit of the block for which the base fee is being calculated.
279
    parent_gas_limit :
280
        Gas limit of the parent block.
281
    parent_gas_used :
282
        Gas used in the parent block.
283
    parent_base_fee_per_gas :
284
        Base fee per gas of the parent block.
285
286
    Returns
287
    -------
288
    base_fee_per_gas : `Uint`
289
        Base fee per gas for the block.
290
    """
291
    parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
292
    if not check_gas_limit(block_gas_limit, parent_gas_limit):
293
        raise InvalidBlock
294
295
    if parent_gas_used == parent_gas_target:
296
        expected_base_fee_per_gas = parent_base_fee_per_gas
297
    elif parent_gas_used > parent_gas_target:
298
        gas_used_delta = parent_gas_used - parent_gas_target
299
300
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
301
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
302
303
        base_fee_per_gas_delta = max(
304
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR,
305
            Uint(1),
306
        )
307
308
        expected_base_fee_per_gas = (
309
            parent_base_fee_per_gas + base_fee_per_gas_delta
310
        )
311
    else:
312
        gas_used_delta = parent_gas_target - parent_gas_used
313
314
        parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta
315
        target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target
316
317
        base_fee_per_gas_delta = (
318
            target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR
319
        )
320
321
        expected_base_fee_per_gas = (
322
            parent_base_fee_per_gas - base_fee_per_gas_delta
323
        )
324
325
    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:
329
    """
330
    Verifies a block header.
331
332
    In order to consider a block's header valid, the logic for the
333
    quantities in the header should match the logic for the block itself.
334
    For example the header timestamp should be greater than the block's parent
335
    timestamp because the block was created *after* the parent block.
336
    Additionally, the block's number should be directly following the parent
337
    block's number since it is the next block in the sequence.
338
339
    Parameters
340
    ----------
341
    chain :
342
        History and current state.
343
    header :
344
        Header to check for correctness.
345
    """
346
    if header.number < Uint(1):
347
        raise InvalidBlock
348
349
    parent_header = chain.blocks[-1].header
350
351
    excess_blob_gas = calculate_excess_blob_gas(parent_header)
352
    if header.excess_blob_gas != excess_blob_gas:
353
        raise InvalidBlock
354
355
    if header.gas_used > header.gas_limit:
356
        raise InvalidBlock
357
358
    expected_base_fee_per_gas = calculate_base_fee_per_gas(
359
        header.gas_limit,
360
        parent_header.gas_limit,
361
        parent_header.gas_used,
362
        parent_header.base_fee_per_gas,
363
    )
364
    if expected_base_fee_per_gas != header.base_fee_per_gas:
365
        raise InvalidBlock
366
    if header.timestamp <= parent_header.timestamp:
367
        raise InvalidBlock
368
    if header.number != parent_header.number + Uint(1):
369
        raise InvalidBlock
370
    if len(header.extra_data) > 32:
371
        raise InvalidBlock
372
    if header.difficulty != 0:
373
        raise InvalidBlock
374
    if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00":
375
        raise InvalidBlock
376
    if header.ommers_hash != EMPTY_OMMER_HASH:
377
        raise InvalidBlock
378
379
    block_parent_hash = keccak256(rlp.encode(parent_header))
380
    if header.parent_hash != block_parent_hash:
381
        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.

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. 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.prague.vm.BlockEnvironment, ​​block_output: ethereum.prague.vm.BlockOutput, ​​tx: Transaction) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]:
389
    """
390
    Check if the transaction is includable in the block.
391
392
    Parameters
393
    ----------
394
    block_env :
395
        The block scoped environment.
396
    block_output :
397
        The block output for the current block.
398
    tx :
399
        The transaction.
400
401
    Returns
402
    -------
403
    sender_address :
404
        The sender of the transaction.
405
    effective_gas_price :
406
        The price to charge for gas when the transaction is executed.
407
    blob_versioned_hashes :
408
        The blob versioned hashes of the transaction.
409
    tx_blob_gas_used:
410
        The blob gas used by the transaction.
411
412
    Raises
413
    ------
414
    InvalidBlock :
415
        If the transaction is not includable.
416
    GasUsedExceedsLimitError :
417
        If the gas used by the transaction exceeds the block's gas limit.
418
    NonceMismatchError :
419
        If the nonce of the transaction is not equal to the sender's nonce.
420
    InsufficientBalanceError :
421
        If the sender's balance is not enough to pay for the transaction.
422
    InvalidSenderError :
423
        If the transaction is from an address that does not exist anymore.
424
    PriorityFeeGreaterThanMaxFeeError :
425
        If the priority fee is greater than the maximum fee per gas.
426
    InsufficientMaxFeePerGasError :
427
        If the maximum fee per gas is insufficient for the transaction.
428
    InsufficientMaxFeePerBlobGasError :
429
        If the maximum fee per blob gas is insufficient for the transaction.
430
    BlobGasLimitExceededError :
431
        If the blob gas used by the transaction exceeds the block's blob gas
432
        limit.
433
    InvalidBlobVersionedHashError :
434
        If the transaction contains a blob versioned hash with an invalid
435
        version.
436
    NoBlobDataError :
437
        If the transaction is a type 3 but has no blobs.
438
    TransactionTypeContractCreationError:
439
        If the transaction type is not allowed to create contracts.
440
    EmptyAuthorizationListError :
441
        If the transaction is a SetCodeTransaction and the authorization list
442
        is empty.
443
    """
444
    gas_available = block_env.block_gas_limit - block_output.block_gas_used
445
    blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used
446
447
    if tx.gas > gas_available:
448
        raise GasUsedExceedsLimitError("gas used exceeds limit")
449
450
    tx_blob_gas_used = calculate_total_blob_gas(tx)
451
    if tx_blob_gas_used > blob_gas_available:
452
        raise BlobGasLimitExceededError("blob gas limit exceeded")
453
454
    sender_address = recover_sender(block_env.chain_id, tx)
455
    sender_account = get_account(block_env.state, sender_address)
456
457
    if isinstance(
458
        tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction)
459
    ):
460
        if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
461
            raise PriorityFeeGreaterThanMaxFeeError(
462
                "priority fee greater than max fee"
463
            )
464
        if tx.max_fee_per_gas < block_env.base_fee_per_gas:
465
            raise InsufficientMaxFeePerGasError(
466
                tx.max_fee_per_gas, block_env.base_fee_per_gas
467
            )
468
469
        priority_fee_per_gas = min(
470
            tx.max_priority_fee_per_gas,
471
            tx.max_fee_per_gas - block_env.base_fee_per_gas,
472
        )
473
        effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas
474
        max_gas_fee = tx.gas * tx.max_fee_per_gas
475
    else:
476
        if tx.gas_price < block_env.base_fee_per_gas:
477
            raise InvalidBlock
478
        effective_gas_price = tx.gas_price
479
        max_gas_fee = tx.gas * tx.gas_price
480
481
    if isinstance(tx, BlobTransaction):
482
        if len(tx.blob_versioned_hashes) == 0:
483
            raise NoBlobDataError("no blob data in transaction")
484
        for blob_versioned_hash in tx.blob_versioned_hashes:
485
            if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG:
486
                raise InvalidBlobVersionedHashError(
487
                    "invalid blob versioned hash"
488
                )
489
490
        blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas)
491
        if Uint(tx.max_fee_per_blob_gas) < blob_gas_price:
492
            raise InsufficientMaxFeePerBlobGasError(
493
                "insufficient max fee per blob gas"
494
            )
495
496
        max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint(
497
            tx.max_fee_per_blob_gas
498
        )
499
        blob_versioned_hashes = tx.blob_versioned_hashes
500
    else:
501
        blob_versioned_hashes = ()
502
503
    if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
504
        if not isinstance(tx.to, Address):
505
            raise TransactionTypeContractCreationError(tx)
506
507
    if isinstance(tx, SetCodeTransaction):
508
        if not any(tx.authorizations):
509
            raise EmptyAuthorizationListError("empty authorization list")
510
511
    if sender_account.nonce > Uint(tx.nonce):
512
        raise NonceMismatchError("nonce too low")
513
    elif sender_account.nonce < Uint(tx.nonce):
514
        raise NonceMismatchError("nonce too high")
515
516
    if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value):
517
        raise InsufficientBalanceError("insufficient sender balance")
518
    if sender_account.code and not is_valid_delegation(sender_account.code):
519
        raise InvalidSenderError("not EOA")
520
521
    return (
522
        sender_address,
523
        effective_gas_price,
524
        blob_versioned_hashes,
525
        tx_blob_gas_used,
526
    )

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:
535
    """
536
    Make the receipt for a transaction that was executed.
537
538
    Parameters
539
    ----------
540
    tx :
541
        The executed transaction.
542
    error :
543
        Error in the top level frame of the transaction, if any.
544
    cumulative_gas_used :
545
        The total gas used so far in the block after the transaction was
546
        executed.
547
    logs :
548
        The logs produced by the transaction.
549
550
    Returns
551
    -------
552
    receipt :
553
        The receipt for the transaction.
554
    """
555
    receipt = Receipt(
556
        succeeded=error is None,
557
        cumulative_gas_used=cumulative_gas_used,
558
        bloom=logs_bloom(logs),
559
        logs=logs,
560
    )
561
562
    return encode_receipt(tx, receipt)

process_system_transaction

Process a system transaction with the given code.

Prefer calling process_checked_system_transaction or process_unchecked_system_transaction depending on whether missing code or an execution error should cause the block to be rejected.

Parameters

block_env : The block scoped environment. target_address : Address of the contract to call. system_contract_code : Code 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_system_transaction(block_env: ethereum.prague.vm.BlockEnvironment, ​​target_address: Address, ​​system_contract_code: Bytes, ​​data: Bytes) -> MessageCallOutput:
571
    """
572
    Process a system transaction with the given code.
573
574
    Prefer calling `process_checked_system_transaction` or
575
    `process_unchecked_system_transaction` depending on whether missing code or
576
    an execution error should cause the block to be rejected.
577
578
    Parameters
579
    ----------
580
    block_env :
581
        The block scoped environment.
582
    target_address :
583
        Address of the contract to call.
584
    system_contract_code :
585
        Code of the contract to call.
586
    data :
587
        Data to pass to the contract.
588
589
    Returns
590
    -------
591
    system_tx_output : `MessageCallOutput`
592
        Output of processing the system transaction.
593
    """
594
    tx_env = vm.TransactionEnvironment(
595
        origin=SYSTEM_ADDRESS,
596
        gas_price=block_env.base_fee_per_gas,
597
        gas=SYSTEM_TRANSACTION_GAS,
598
        access_list_addresses=set(),
599
        access_list_storage_keys=set(),
600
        transient_storage=TransientStorage(),
601
        blob_versioned_hashes=(),
602
        authorizations=(),
603
        index_in_block=None,
604
        tx_hash=None,
605
        traces=[],
606
    )
607
608
    system_tx_message = Message(
609
        block_env=block_env,
610
        tx_env=tx_env,
611
        caller=SYSTEM_ADDRESS,
612
        target=target_address,
613
        gas=SYSTEM_TRANSACTION_GAS,
614
        value=U256(0),
615
        data=data,
616
        code=system_contract_code,
617
        depth=Uint(0),
618
        current_target=target_address,
619
        code_address=target_address,
620
        should_transfer_value=False,
621
        is_static=False,
622
        accessed_addresses=set(),
623
        accessed_storage_keys=set(),
624
        disable_precompiles=False,
625
        parent_evm=None,
626
    )
627
628
    system_tx_output = process_message_call(system_tx_message)
629
630
    return system_tx_output

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.prague.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
638
    """
639
    Process a system transaction and raise an error if the contract does not
640
    contain code or if the transaction fails.
641
642
    Parameters
643
    ----------
644
    block_env :
645
        The block scoped environment.
646
    target_address :
647
        Address of the contract to call.
648
    data :
649
        Data to pass to the contract.
650
651
    Returns
652
    -------
653
    system_tx_output : `MessageCallOutput`
654
        Output of processing the system transaction.
655
    """
656
    system_contract_code = get_account(block_env.state, target_address).code
657
658
    if len(system_contract_code) == 0:
659
        raise InvalidBlock(
660
            f"System contract address {target_address.hex()} does not "
661
            "contain code"
662
        )
663
664
    system_tx_output = process_system_transaction(
665
        block_env,
666
        target_address,
667
        system_contract_code,
668
        data,
669
    )
670
671
    if system_tx_output.error:
672
        raise InvalidBlock(
673
            f"System contract ({target_address.hex()}) call failed: "
674
            f"{system_tx_output.error}"
675
        )
676
677
    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.prague.vm.BlockEnvironment, ​​target_address: Address, ​​data: Bytes) -> MessageCallOutput:
685
    """
686
    Process a system transaction without checking if the contract contains code
687
    or if the transaction fails.
688
689
    Parameters
690
    ----------
691
    block_env :
692
        The block scoped environment.
693
    target_address :
694
        Address of the contract to call.
695
    data :
696
        Data to pass to the contract.
697
698
    Returns
699
    -------
700
    system_tx_output : `MessageCallOutput`
701
        Output of processing the system transaction.
702
    """
703
    system_contract_code = get_account(block_env.state, target_address).code
704
    return process_system_transaction(
705
        block_env,
706
        target_address,
707
        system_contract_code,
708
        data,
709
    )

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.prague.vm.BlockEnvironment, ​​transactions: Tuple[LegacyTransaction | Bytes, ...], ​​withdrawals: Tuple[Withdrawal, ...]) -> ethereum.prague.vm.BlockOutput:
717
    """
718
    Executes a block.
719
720
    Many of the contents of a block are stored in data structures called
721
    tries. There is a transactions trie which is similar to a ledger of the
722
    transactions stored in the current block. There is also a receipts trie
723
    which stores the results of executing a transaction, like the post state
724
    and gas used. This function creates and executes the block that is to be
725
    added to the chain.
726
727
    Parameters
728
    ----------
729
    block_env :
730
        The block scoped environment.
731
    transactions :
732
        Transactions included in the block.
733
    withdrawals :
734
        Withdrawals to be processed in the current block.
735
736
    Returns
737
    -------
738
    block_output :
739
        The block output for the current block.
740
    """
741
    block_output = vm.BlockOutput()
742
743
    process_unchecked_system_transaction(
744
        block_env=block_env,
745
        target_address=BEACON_ROOTS_ADDRESS,
746
        data=block_env.parent_beacon_block_root,
747
    )
748
749
    process_unchecked_system_transaction(
750
        block_env=block_env,
751
        target_address=HISTORY_STORAGE_ADDRESS,
752
        data=block_env.block_hashes[-1],  # The parent hash
753
    )
754
755
    for i, tx in enumerate(map(decode_transaction, transactions)):
756
        process_transaction(block_env, block_output, tx, Uint(i))
757
758
    process_withdrawals(block_env, block_output, withdrawals)
759
760
    process_general_purpose_requests(
761
        block_env=block_env,
762
        block_output=block_output,
763
    )
764
765
    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.prague.vm.BlockEnvironment, ​​block_output: ethereum.prague.vm.BlockOutput) -> None:
772
    """
773
    Process all the requests in the block.
774
775
    Parameters
776
    ----------
777
    block_env :
778
        The execution environment for the Block.
779
    block_output :
780
        The block output for the current block.
781
    """
782
    # Requests are to be in ascending order of request type
783
    deposit_requests = parse_deposit_requests(block_output)
784
    requests_from_execution = block_output.requests
785
    if len(deposit_requests) > 0:
786
        requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests)
787
788
    system_withdrawal_tx_output = process_checked_system_transaction(
789
        block_env=block_env,
790
        target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
791
        data=b"",
792
    )
793
794
    if len(system_withdrawal_tx_output.return_data) > 0:
795
        requests_from_execution.append(
796
            WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data
797
        )
798
799
    system_consolidation_tx_output = process_checked_system_transaction(
800
        block_env=block_env,
801
        target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
802
        data=b"",
803
    )
804
805
    if len(system_consolidation_tx_output.return_data) > 0:
806
        requests_from_execution.append(
807
            CONSOLIDATION_REQUEST_TYPE
808
            + system_consolidation_tx_output.return_data
809
        )

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 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.prague.vm.BlockEnvironment, ​​block_output: ethereum.prague.vm.BlockOutput, ​​tx: Transaction, ​​index: Uint) -> None:
818
    """
819
    Execute a transaction against the provided environment.
820
821
    This function processes the actions needed to execute a transaction.
822
    It decrements the sender's account after calculating the gas fee and
823
    refunds them the proper amount after execution. Calling contracts,
824
    deploying code, and incrementing nonces are all examples of actions that
825
    happen within this function or from a call made within this function.
826
827
    Accounts that are marked for deletion are processed and destroyed after
828
    execution.
829
830
    Parameters
831
    ----------
832
    block_env :
833
        Environment for the Ethereum Virtual Machine.
834
    block_output :
835
        The block output for the current block.
836
    tx :
837
        Transaction to execute.
838
    index:
839
        Index of the transaction in the block.
840
    """
841
    trie_set(
842
        block_output.transactions_trie,
843
        rlp.encode(index),
844
        encode_transaction(tx),
845
    )
846
847
    intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx)
848
849
    (
850
        sender,
851
        effective_gas_price,
852
        blob_versioned_hashes,
853
        tx_blob_gas_used,
854
    ) = check_transaction(
855
        block_env=block_env,
856
        block_output=block_output,
857
        tx=tx,
858
    )
859
860
    sender_account = get_account(block_env.state, sender)
861
862
    if isinstance(tx, BlobTransaction):
863
        blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx)
864
    else:
865
        blob_gas_fee = Uint(0)
866
867
    effective_gas_fee = tx.gas * effective_gas_price
868
869
    gas = tx.gas - intrinsic_gas
870
    increment_nonce(block_env.state, sender)
871
872
    sender_balance_after_gas_fee = (
873
        Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
874
    )
875
    set_account_balance(
876
        block_env.state, sender, U256(sender_balance_after_gas_fee)
877
    )
878
879
    access_list_addresses = set()
880
    access_list_storage_keys = set()
881
    access_list_addresses.add(block_env.coinbase)
882
    if isinstance(
883
        tx,
884
        (
885
            AccessListTransaction,
886
            FeeMarketTransaction,
887
            BlobTransaction,
888
            SetCodeTransaction,
889
        ),
890
    ):
891
        for access in tx.access_list:
892
            access_list_addresses.add(access.account)
893
            for slot in access.slots:
894
                access_list_storage_keys.add((access.account, slot))
895
896
    authorizations: Tuple[Authorization, ...] = ()
897
    if isinstance(tx, SetCodeTransaction):
898
        authorizations = tx.authorizations
899
900
    tx_env = vm.TransactionEnvironment(
901
        origin=sender,
902
        gas_price=effective_gas_price,
903
        gas=gas,
904
        access_list_addresses=access_list_addresses,
905
        access_list_storage_keys=access_list_storage_keys,
906
        transient_storage=TransientStorage(),
907
        blob_versioned_hashes=blob_versioned_hashes,
908
        authorizations=authorizations,
909
        index_in_block=index,
910
        tx_hash=get_transaction_hash(encode_transaction(tx)),
911
        traces=[],
912
    )
913
914
    message = prepare_message(block_env, tx_env, tx)
915
916
    tx_output = process_message_call(message)
917
918
    # For EIP-7623 we first calculate the execution_gas_used, which includes
919
    # the execution gas refund.
920
    tx_gas_used_before_refund = tx.gas - tx_output.gas_left
921
    tx_gas_refund = min(
922
        tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter)
923
    )
924
    tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund
925
926
    # Transactions with less execution_gas_used than the floor pay at the
927
    # floor cost.
928
    tx_gas_used_after_refund = max(
929
        tx_gas_used_after_refund, calldata_floor_gas_cost
930
    )
931
932
    tx_gas_left = tx.gas - tx_gas_used_after_refund
933
    gas_refund_amount = tx_gas_left * effective_gas_price
934
935
    # For non-1559 transactions effective_gas_price == tx.gas_price
936
    priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas
937
    transaction_fee = tx_gas_used_after_refund * priority_fee_per_gas
938
939
    # refund gas
940
    sender_balance_after_refund = get_account(
941
        block_env.state, sender
942
    ).balance + U256(gas_refund_amount)
943
    set_account_balance(block_env.state, sender, sender_balance_after_refund)
944
945
    # transfer miner fees
946
    coinbase_balance_after_mining_fee = get_account(
947
        block_env.state, block_env.coinbase
948
    ).balance + U256(transaction_fee)
949
    if coinbase_balance_after_mining_fee != 0:
950
        set_account_balance(
951
            block_env.state,
952
            block_env.coinbase,
953
            coinbase_balance_after_mining_fee,
954
        )
955
    elif account_exists_and_is_empty(block_env.state, block_env.coinbase):
956
        destroy_account(block_env.state, block_env.coinbase)
957
958
    for address in tx_output.accounts_to_delete:
959
        destroy_account(block_env.state, address)
960
961
    block_output.block_gas_used += tx_gas_used_after_refund
962
    block_output.blob_gas_used += tx_blob_gas_used
963
964
    receipt = make_receipt(
965
        tx, tx_output.error, block_output.block_gas_used, tx_output.logs
966
    )
967
968
    receipt_key = rlp.encode(Uint(index))
969
    block_output.receipt_keys += (receipt_key,)
970
971
    trie_set(
972
        block_output.receipts_trie,
973
        receipt_key,
974
        receipt,
975
    )
976
977
    block_output.block_logs += tx_output.logs

process_withdrawals

Increase the balance of the withdrawing account.

def process_withdrawals(block_env: ethereum.prague.vm.BlockEnvironment, ​​block_output: ethereum.prague.vm.BlockOutput, ​​withdrawals: Tuple[Withdrawal, ...]) -> None:
985
    """
986
    Increase the balance of the withdrawing account.
987
    """
988
989
    def increase_recipient_balance(recipient: Account) -> None:
990
        recipient.balance += wd.amount * U256(10**9)
991
992
    for i, wd in enumerate(withdrawals):
993
        trie_set(
994
            block_output.withdrawals_trie,
995
            rlp.encode(Uint(i)),
996
            rlp.encode(wd),
997
        )
998
999
        modify_state(block_env.state, wd.address, increase_recipient_balance)
1000
1001
        if account_exists_and_is_empty(block_env.state, wd.address):
1002
            destroy_account(block_env.state, wd.address)

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 GAS_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 GAS_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:
1006
    """
1007
    Validates the gas limit for a block.
1008
1009
    The bounds of the gas limit, ``max_adjustment_delta``, is set as the
1010
    quotient of the parent block's gas limit and the
1011
    ``GAS_LIMIT_ADJUSTMENT_FACTOR``. Therefore, if the gas limit that is
1012
    passed through as a parameter is greater than or equal to the *sum* of
1013
    the parent's gas and the adjustment delta then the limit for gas is too
1014
    high and fails this function's check. Similarly, if the limit is less
1015
    than or equal to the *difference* of the parent's gas and the adjustment
1016
    delta *or* the predefined ``GAS_LIMIT_MINIMUM`` then this function's
1017
    check fails because the gas limit doesn't allow for a sufficient or
1018
    reasonable amount of gas to be used on a block.
1019
1020
    Parameters
1021
    ----------
1022
    gas_limit :
1023
        Gas limit to validate.
1024
1025
    parent_gas_limit :
1026
        Gas limit of the parent block.
1027
1028
    Returns
1029
    -------
1030
    check : `bool`
1031
        True if gas limit constraints are satisfied, False otherwise.
1032
    """
1033
    max_adjustment_delta = parent_gas_limit // GAS_LIMIT_ADJUSTMENT_FACTOR
1034
    if gas_limit >= parent_gas_limit + max_adjustment_delta:
1035
        return False
1036
    if gas_limit <= parent_gas_limit - max_adjustment_delta:
1037
        return False
1038
    if gas_limit < GAS_LIMIT_MINIMUM:
1039
        return False
1040
1041
    return True