diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index db42c594bf..8ab6f5ea69 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -112,7 +112,7 @@ class BlockChain: chain_id: U64 -def apply_fork(old: BlockChain, block: Block) -> BlockChain: +def apply_fork(old: BlockChain) -> BlockChain: """ Transforms the state from the previous hard fork (`old`) into the block chain object for this hard fork and returns it. @@ -125,34 +125,55 @@ def apply_fork(old: BlockChain, block: Block) -> BlockChain: ---------- old : Previous block chain object. - block : - Block to apply to `old`. Returns ------- new : `BlockChain` Upgraded block chain object for this hard fork. """ - # At fork block, store the hashes for earlier blocks. - current_block = block - for i in range(HISTORY_SERVE_WINDOW - 1): - if current_block.header.number == 0: - break - - set_storage( - old.state, - HISTORY_STORAGE_ADDRESS, - ( - (current_block.header.number - 1) % HISTORY_SERVE_WINDOW - ).to_be_bytes32(), - U256.from_be_bytes(current_block.header.parent_hash), - ) - # Set the earlier block as the current block - current_block = old.blocks[-1 - i] - return old +def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]: + """ + 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. + """ + recent_blocks = chain.blocks[-255:] + # TODO: This function has not been tested rigorously + if len(recent_blocks) == 0: + return [] + + recent_block_hashes = [] + + for block in recent_blocks: + prev_block_hash = block.header.parent_hash + recent_block_hashes.append(prev_block_hash) + + # We are computing the hash only for the most recent block and not for + # the rest of the blocks as they have successors which have the hash of + # the current block as parent hash. + most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header)) + recent_block_hashes.append(most_recent_block_hash) + + return recent_block_hashes + + def state_transition(chain: BlockChain, block: Block) -> None: """ Attempts to apply a block to an existing block chain. @@ -193,6 +214,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: apply_body_output = apply_body( chain.state, + get_last_256_block_hashes(chain), block.header.coinbase, block.header.number, block.header.base_fee_per_gas, @@ -511,6 +533,7 @@ class ApplyBodyOutput: def process_system_transaction( target_address: Address, data: Bytes, + block_hashes: List[Hash32], coinbase: Address, block_number: Uint, base_fee_per_gas: Uint, @@ -530,6 +553,8 @@ def process_system_transaction( Address of the contract to call. data : Data to pass to the contract. + block_hashes : + List of hashes of the previous 256 blocks. coinbase : Address of the block's coinbase. block_number : @@ -575,6 +600,7 @@ def process_system_transaction( system_tx_env = vm.Environment( caller=SYSTEM_ADDRESS, + block_hashes=block_hashes, origin=SYSTEM_ADDRESS, coinbase=coinbase, number=block_number, @@ -607,6 +633,7 @@ def process_system_transaction( def apply_body( state: State, + block_hashes: List[Hash32], coinbase: Address, block_number: Uint, base_fee_per_gas: Uint, @@ -634,6 +661,9 @@ def apply_body( ---------- state : Current account state. + block_hashes : + List of hashes of the previous 256 blocks in the order of + increasing block number. coinbase : Address of account which receives block reward and transaction fees. block_number : @@ -687,6 +717,7 @@ def apply_body( process_system_transaction( BEACON_ROOTS_ADDRESS, parent_beacon_block_root, + block_hashes, coinbase, block_number, base_fee_per_gas, @@ -718,6 +749,7 @@ def apply_body( env = vm.Environment( caller=sender_address, + block_hashes=block_hashes, origin=sender_address, coinbase=coinbase, number=block_number, @@ -771,6 +803,7 @@ def apply_body( system_withdrawal_tx_output = process_system_transaction( WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, b"", + block_hashes, coinbase, block_number, base_fee_per_gas, diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index 687f9eee80..8b36a1f12b 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -17,6 +17,7 @@ from typing import List, Optional, Set, Tuple, Union from ethereum.base_types import U64, U256, Bytes, Bytes0, Bytes32, Uint +from ethereum.crypto.hash import Hash32 from ..blocks import Log from ..fork_types import Address, VersionedHash @@ -33,6 +34,7 @@ class Environment: """ caller: Address + block_hashes: List[Hash32] origin: Address coinbase: Address number: Uint diff --git a/src/ethereum/prague/vm/gas.py b/src/ethereum/prague/vm/gas.py index f7ec20687b..a47fc6e921 100644 --- a/src/ethereum/prague/vm/gas.py +++ b/src/ethereum/prague/vm/gas.py @@ -38,6 +38,7 @@ GAS_KECCAK256 = Uint(30) GAS_KECCAK256_WORD = Uint(6) GAS_COPY = Uint(3) +GAS_BLOCK_HASH = Uint(20) GAS_LOG = Uint(375) GAS_LOG_DATA = Uint(8) GAS_LOG_TOPIC = Uint(375) diff --git a/src/ethereum/prague/vm/instructions/block.py b/src/ethereum/prague/vm/instructions/block.py index 90b4b6e2aa..f2f1fac73f 100644 --- a/src/ethereum/prague/vm/instructions/block.py +++ b/src/ethereum/prague/vm/instructions/block.py @@ -14,9 +14,8 @@ from ethereum.base_types import U256 -from ...state import get_storage from .. import Evm -from ..gas import GAS_BASE, GAS_COLD_SLOAD, GAS_WARM_ACCESS, charge_gas +from ..gas import GAS_BASE, GAS_BLOCK_HASH, charge_gas from ..stack import pop, push @@ -37,34 +36,20 @@ def block_hash(evm: Evm) -> None: :py:class:`~ethereum.prague.vm.exceptions.OutOfGasError` If `evm.gas_left` is less than `20`. """ - from ...fork import HISTORY_SERVE_WINDOW, HISTORY_STORAGE_ADDRESS - # STACK block_number = pop(evm.stack) # GAS - key = (block_number % HISTORY_SERVE_WINDOW).to_be_bytes32() - if (HISTORY_STORAGE_ADDRESS, key) in evm.accessed_storage_keys: - charge_gas(evm, GAS_WARM_ACCESS) - else: - evm.accessed_storage_keys.add((HISTORY_STORAGE_ADDRESS, key)) - charge_gas(evm, GAS_COLD_SLOAD) + charge_gas(evm, GAS_BLOCK_HASH) # OPERATION - if ( - evm.env.number <= block_number - or evm.env.number > block_number + HISTORY_SERVE_WINDOW - ): + if evm.env.number <= block_number or evm.env.number > block_number + 256: # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), - # or if the block's age is more than HISTORY_SERVE_WINDOW. + # or if the block's age is more than 256. hash = b"\x00" else: - hash = get_storage( - evm.env.state, - HISTORY_STORAGE_ADDRESS, - key, - ).to_be_bytes32() + hash = evm.env.block_hashes[-(evm.env.number - block_number)] push(evm.stack, U256.from_be_bytes(hash))