diff --git a/eth/abc.py b/eth/abc.py index b7ebada2bb..37dba9099a 100644 --- a/eth/abc.py +++ b/eth/abc.py @@ -74,6 +74,7 @@ class MiningHeaderAPI(ABC): gas_used: int timestamp: int extra_data: bytes + base_fee_per_gas: int # EIP-1559 @property @abstractmethod @@ -326,6 +327,9 @@ class TransactionFieldsAPI(ABC): """ A class to define all common transaction fields. """ + max_fee_per_gas: int + max_priority_fee_per_gas: int + @property @abstractmethod def nonce(self) -> int: @@ -333,7 +337,7 @@ def nonce(self) -> int: @property @abstractmethod - def gas_price(self) -> int: + def gas_price(self) -> Optional[int]: ... @property @@ -1684,6 +1688,14 @@ def chain_id(self) -> int: """ ... + @property + @abstractmethod + def base_gas_fee(self) -> Optional[int]: + """ + Return the base gas fee of the block + """ + ... + class ComputationAPI(ContextManager['ComputationAPI'], StackManipulationAPI): """ @@ -2865,9 +2877,8 @@ def get_computation(self, # # Transaction context # - @classmethod @abstractmethod - def get_transaction_context_class(cls) -> Type[TransactionContextAPI]: + def get_transaction_context_class(self) -> Type[TransactionContextAPI]: """ Return the :class:`~eth.vm.transaction_context.BaseTransactionContext` class that the state class uses. @@ -2917,9 +2928,8 @@ def validate_transaction(self, transaction: SignedTransactionAPI) -> None: """ ... - @classmethod @abstractmethod - def get_transaction_context(cls, + def get_transaction_context(self, transaction: SignedTransactionAPI) -> TransactionContextAPI: """ Return the :class:`~eth.abc.TransactionContextAPI` for the given ``transaction`` diff --git a/eth/chains/mainnet/__init__.py b/eth/chains/mainnet/__init__.py index 9f64b29cd7..9cd9ac8040 100644 --- a/eth/chains/mainnet/__init__.py +++ b/eth/chains/mainnet/__init__.py @@ -12,6 +12,7 @@ from .constants import ( MAINNET_CHAIN_ID, + LONDON_MAINNET_BLOCK, BERLIN_MAINNET_BLOCK, BYZANTIUM_MAINNET_BLOCK, PETERSBURG_MAINNET_BLOCK, @@ -39,6 +40,7 @@ FrontierVM, HomesteadVM, IstanbulVM, + LondonVM, MuirGlacierVM, PetersburgVM, SpuriousDragonVM, @@ -97,6 +99,7 @@ class MainnetHomesteadVM(MainnetDAOValidatorVM): ISTANBUL_MAINNET_BLOCK, MUIR_GLACIER_MAINNET_BLOCK, BERLIN_MAINNET_BLOCK, + LONDON_MAINNET_BLOCK, ) MAINNET_VMS = ( FrontierVM, @@ -108,6 +111,7 @@ class MainnetHomesteadVM(MainnetDAOValidatorVM): IstanbulVM, MuirGlacierVM, BerlinVM, + LondonVM, ) MAINNET_VM_CONFIGURATION = tuple(zip(MAINNET_FORK_BLOCKS, MAINNET_VMS)) diff --git a/eth/chains/mainnet/constants.py b/eth/chains/mainnet/constants.py index 1e07c07d51..9cc4d2d4c8 100644 --- a/eth/chains/mainnet/constants.py +++ b/eth/chains/mainnet/constants.py @@ -57,3 +57,9 @@ # Berlin Block # BERLIN_MAINNET_BLOCK = BlockNumber(12244000) + + +# +# London Block +# +LONDON_MAINNET_BLOCK = BlockNumber(12244001) # TODO change to actual when known diff --git a/eth/db/header.py b/eth/db/header.py index 696796a43d..819a21701d 100644 --- a/eth/db/header.py +++ b/eth/db/header.py @@ -56,7 +56,13 @@ validate_block_number, validate_word, ) +from eth.vm.forks.london.blocks import ( + LondonBlockHeader +) +from rlp.exceptions import ( + ObjectDeserializationError +) class HeaderDB(HeaderDatabaseAPI): def __init__(self, db: AtomicDatabaseAPI) -> None: @@ -619,4 +625,8 @@ def _add_block_number_to_hash_lookup(db: DatabaseAPI, header: BlockHeaderAPI) -> # be looking up recent blocks. @functools.lru_cache(128) def _decode_block_header(header_rlp: bytes) -> BlockHeaderAPI: - return rlp.decode(header_rlp, sedes=BlockHeader) + try: + return rlp.decode(header_rlp, sedes=BlockHeader) + except ObjectDeserializationError: + # could be a new >=London block header + return rlp.decode(header_rlp, sedes=LondonBlockHeader) diff --git a/eth/rlp/headers.py b/eth/rlp/headers.py index d50570a1a0..4bdd2f24ff 100644 --- a/eth/rlp/headers.py +++ b/eth/rlp/headers.py @@ -1,6 +1,7 @@ import time from typing import ( Dict, + Optional, overload, ) @@ -202,3 +203,7 @@ def is_genesis(self) -> bool: # validate_header stops trying to check the current header against a parent header. # Can someone trick us into following a high difficulty header with genesis parent hash? return self.parent_hash == GENESIS_PARENT_HASH and self.block_number == 0 + + @property + def base_fee_per_gas(self) -> Optional[int]: + return 0 diff --git a/eth/tools/builder/chain/__init__.py b/eth/tools/builder/chain/__init__.py index d220befd66..3bead9ac6b 100644 --- a/eth/tools/builder/chain/__init__.py +++ b/eth/tools/builder/chain/__init__.py @@ -27,6 +27,7 @@ istanbul_at, muir_glacier_at, berlin_at, + london_at, latest_mainnet_at, ) diff --git a/eth/tools/builder/chain/builders.py b/eth/tools/builder/chain/builders.py index 34c2d6bc50..e54b1433e2 100644 --- a/eth/tools/builder/chain/builders.py +++ b/eth/tools/builder/chain/builders.py @@ -73,6 +73,7 @@ IstanbulVM, MuirGlacierVM, BerlinVM, + LondonVM, ) @@ -237,6 +238,7 @@ def dao_fork_at(dao_fork_block_number: BlockNumber, istanbul_at = fork_at(IstanbulVM) muir_glacier_at = fork_at(MuirGlacierVM) berlin_at = fork_at(BerlinVM) +london_at = fork_at(LondonVM) latest_mainnet_at = muir_glacier_at diff --git a/eth/validation.py b/eth/validation.py index 83f5cfdc41..48cfbaee44 100644 --- a/eth/validation.py +++ b/eth/validation.py @@ -166,7 +166,7 @@ def validate_uint256(value: int, title: str = "Value") -> None: ) if value > UINT_256_MAX: raise ValidationError( - f"{title} exeeds maximum UINT256 size. Got: {value}" + f"{title} exceeds maximum UINT256 size. Got: {value}" ) diff --git a/eth/vm/base.py b/eth/vm/base.py index 7fda65c556..cf9be070c3 100644 --- a/eth/vm/base.py +++ b/eth/vm/base.py @@ -184,6 +184,7 @@ def create_execution_context(cls, gas_limit=header.gas_limit, prev_hashes=prev_hashes, chain_id=chain_context.chain_id, + base_gas_fee=header.base_fee_per_gas, ) def execute_bytecode(self, @@ -590,6 +591,7 @@ def validate_header(cls, validate_length_lte( header.extra_data, cls.extra_data_max_bytes, title="BlockHeader.extra_data") + # TODO skip for EIP-1559, or override this whole function? validate_gas_limit(header.gas_limit, parent_header.gas_limit) if header.block_number != parent_header.block_number + 1: diff --git a/eth/vm/execution_context.py b/eth/vm/execution_context.py index d17284ac9d..ed4129bcb6 100644 --- a/eth/vm/execution_context.py +++ b/eth/vm/execution_context.py @@ -1,5 +1,6 @@ from typing import ( Iterable, + Optional, ) from eth_typing import ( @@ -21,6 +22,7 @@ class ExecutionContext(ExecutionContextAPI): _gas_limit = None _prev_hashes = None _chain_id = None + _base_gas_fee = 0 # TODO check if valid def __init__( self, @@ -30,7 +32,8 @@ def __init__( difficulty: int, gas_limit: int, prev_hashes: Iterable[Hash32], - chain_id: int) -> None: + chain_id: int, + base_gas_fee: Optional[int]) -> None: self._coinbase = coinbase self._timestamp = timestamp self._block_number = block_number @@ -38,6 +41,8 @@ def __init__( self._gas_limit = gas_limit self._prev_hashes = CachedIterable(prev_hashes) self._chain_id = chain_id + if base_gas_fee is not None: + self._base_gas_fee = base_gas_fee @property def coinbase(self) -> Address: @@ -66,3 +71,7 @@ def prev_hashes(self) -> Iterable[Hash32]: @property def chain_id(self) -> int: return self._chain_id + + @property + def base_gas_fee(self) -> Optional[int]: + return self._base_gas_fee \ No newline at end of file diff --git a/eth/vm/forks/__init__.py b/eth/vm/forks/__init__.py index ccce67561f..742b3c0d4d 100644 --- a/eth/vm/forks/__init__.py +++ b/eth/vm/forks/__init__.py @@ -28,3 +28,6 @@ from .berlin import ( # noqa: F401 BerlinVM, ) +from .london import ( # noqa: F401 + LondonVM +) diff --git a/eth/vm/forks/berlin/receipts.py b/eth/vm/forks/berlin/receipts.py index fc6dbd51a0..8ab3387fb7 100644 --- a/eth/vm/forks/berlin/receipts.py +++ b/eth/vm/forks/berlin/receipts.py @@ -43,6 +43,7 @@ class TypedReceipt(ReceiptAPI, ReceiptDecoderAPI): type_id: int rlp_type = Binary(min_length=1) # must have at least one byte for the type _inner: ReceiptAPI + codecs = TYPED_RECEIPT_BODY_CODECS def __init__(self, type_id: int, proxy_target: ReceiptAPI) -> None: self.type_id = type_id @@ -124,6 +125,7 @@ def __eq__(self, other: Any) -> bool: class BerlinReceiptBuilder(ReceiptBuilderAPI): legacy_sedes = Receipt + codecs = TYPED_RECEIPT_BODY_CODECS @classmethod def decode(cls, encoded: bytes) -> ReceiptAPI: @@ -131,7 +133,7 @@ def decode(cls, encoded: bytes) -> ReceiptAPI: raise ValidationError("Encoded receipt was empty, which makes it invalid") type_id = to_int(encoded[0]) - if type_id in TYPED_RECEIPT_BODY_CODECS: + if type_id in cls.codecs: return TypedReceipt.decode(encoded) else: return rlp.decode(encoded, sedes=cls.legacy_sedes) diff --git a/eth/vm/forks/berlin/transactions.py b/eth/vm/forks/berlin/transactions.py index 5554829198..e0d58c100b 100644 --- a/eth/vm/forks/berlin/transactions.py +++ b/eth/vm/forks/berlin/transactions.py @@ -1,6 +1,7 @@ from typing import ( Any, Dict, + Optional, Sequence, Tuple, Type, @@ -153,6 +154,7 @@ class AccessListTransaction(rlp.Serializable, SignedTransactionMethods, SignedTr ('s', big_endian_int), ] + def get_sender(self) -> Address: return extract_transaction_sender(self) diff --git a/eth/vm/forks/london/__init__.py b/eth/vm/forks/london/__init__.py new file mode 100644 index 0000000000..a43c6e3f17 --- /dev/null +++ b/eth/vm/forks/london/__init__.py @@ -0,0 +1,117 @@ +from typing import Type + +from eth._utils.db import get_parent_header +from eth.abc import BlockAPI, BlockHeaderAPI +from eth_utils.exceptions import ValidationError +from eth.rlp.blocks import BaseBlock +from eth.vm.forks.berlin import BerlinVM +from eth.vm.state import BaseState + +from .blocks import LondonBlock +from .constants import ( + BASE_FEE_MAX_CHANGE_DENOMINATOR, + ELASTICITY_MULTIPLIER, + INITIAL_BASE_FEE, + INITIAL_FORK_BLOCK_NUMBER, + MINIMUM_GAS_LIMIT +) +from .headers import ( + compute_london_difficulty, + create_london_header_from_parent, +) +from .state import LondonState + + +class LondonVM(BerlinVM): + # fork name + fork = 'london' + + # classes + block_class: Type[BaseBlock] = LondonBlock + _state_class: Type[BaseState] = LondonState + + # Methods + # skip header validation: validate everything in the executor as we need state access + validate_transaction_against_header = lambda *_: None # type: ignore + create_header_from_parent = staticmethod(create_london_header_from_parent) # type: ignore + compute_difficulty = staticmethod(compute_london_difficulty) # type: ignore + # configure_header = configure_berlin_header + + @staticmethod + def calculate_expected_base_fee_per_gas(parent_header: BlockHeaderAPI) -> int: + parent_base_fee_per_gas = parent_header.base_fee_per_gas + if parent_header.block_number + 1 == INITIAL_FORK_BLOCK_NUMBER: + return INITIAL_BASE_FEE + + parent_gas_target = parent_header.gas_limit + parent_gas_used = parent_header.gas_used + + if parent_gas_used == parent_gas_target: + return parent_base_fee_per_gas + + elif parent_gas_used > parent_gas_target: + gas_used_delta = parent_gas_used - parent_base_fee_per_gas + base_fee_per_gas_delta = max( + ( + parent_base_fee_per_gas * gas_used_delta // \ + parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR + ), + 1 + ) + return parent_base_fee_per_gas + base_fee_per_gas_delta + + else: + gas_used_delta = parent_gas_target - parent_gas_used + base_fee_per_gas_delta = parent_base_fee_per_gas * gas_used_delta \ + // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR + return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0) + + @classmethod + def validate_header(cls, + header: BlockHeaderAPI, + parent_header: BlockHeaderAPI) -> None: + + parent_gas_target = parent_header.gas_limit // ELASTICITY_MULTIPLIER + + # On the fork block, don't account for the ELASTICITY_MULTIPLIER + # to avoid unduly halving the gas target. + if INITIAL_FORK_BLOCK_NUMBER == header.block_number: + parent_gas_target = parent_header.gas_limit + parent_gas_limit = parent_header.gas_limit * ELASTICITY_MULTIPLIER + + if header.gas_used > header.gas_limit: + raise ValidationError( + f"Block used too much gas: {header.gas_used} " + f"(max: {header.gas_limit})" + ) + + if header.gas_limit > parent_gas_limit + (parent_gas_limit // 1024): + raise ValidationError( + f"Gas limit increased too much (from {parent_gas_limit} " + f"to {header.gas_limit})" + ) + + if header.gas_limit < parent_gas_target - (parent_gas_target // 1024): + raise ValidationError( + f"Gas limit decreased too much (from {parent_gas_target} " + f"to {header.gas_limit})" + ) + + if header.gas_limit < MINIMUM_GAS_LIMIT: + raise ValidationError( + f"Gas limit is lower than the minimum ({header.gas_limit} < {MINIMUM_GAS_LIMIT})" + ) + + expected_base_fee_per_gas = LondonVM.calculate_expected_base_fee_per_gas(parent_header) + if expected_base_fee_per_gas != header.base_fee_per_gas: + raise ValidationError( + f"Incorrect base fee per gas (got {header.base_fee_per_gas}" + f", expected {expected_base_fee_per_gas})" + ) + + def validate_block(self, block: BlockAPI) -> None: + header = block.header + parent_header = get_parent_header(block.header, self.chaindb) + LondonVM.validate_header(header, parent_header) + + # return super().validate_block(block) diff --git a/eth/vm/forks/london/blocks.py b/eth/vm/forks/london/blocks.py new file mode 100644 index 0000000000..bd3fc460db --- /dev/null +++ b/eth/vm/forks/london/blocks.py @@ -0,0 +1,213 @@ +import time + +from typing import ( + Dict, + Optional, + Type, + overload, +) +from eth_utils.exceptions import ValidationError + +import rlp + +from rlp.sedes import ( + Binary, + CountableList, + big_endian_int, + binary +) + +from eth.abc import ( + BlockHeaderAPI, + MiningHeaderAPI, + ReceiptBuilderAPI, + TransactionBuilderAPI, +) +from eth.constants import ( + ZERO_ADDRESS, + ZERO_HASH32, + EMPTY_UNCLE_HASH, + GENESIS_NONCE, + GENESIS_PARENT_HASH, + BLANK_ROOT_HASH, +) +from eth.rlp.sedes import ( + address, + hash32, + trie_root, + uint256, +) +from eth.typing import HeaderParams +from eth.vm.forks.berlin.blocks import ( + BerlinBlock, +) +from eth_hash.auto import keccak + +from eth_typing import ( + BlockNumber, +) +from eth_typing.evm import ( + Address, + Hash32 +) +from eth_utils import ( + encode_hex, +) + +from .receipts import ( + LondonReceiptBuilder, +) +from .transactions import ( + LondonTransactionBuilder, +) + + +class LondonMiningHeader(rlp.Serializable, MiningHeaderAPI): + fields = [ + ('parent_hash', hash32), + ('uncles_hash', hash32), + ('coinbase', address), + ('state_root', trie_root), + ('transaction_root', trie_root), + ('receipt_root', trie_root), + ('bloom', uint256), + ('difficulty', big_endian_int), + ('block_number', big_endian_int), + ('gas_limit', big_endian_int), + ('gas_used', big_endian_int), + ('timestamp', big_endian_int), + ('extra_data', binary), + ('base_fee_per_gas', big_endian_int), + ] + + +class LondonBlockHeader(rlp.Serializable, BlockHeaderAPI): + fields = [ + ('parent_hash', hash32), + ('uncles_hash', hash32), + ('coinbase', address), + ('state_root', trie_root), + ('transaction_root', trie_root), + ('receipt_root', trie_root), + ('bloom', uint256), + ('difficulty', big_endian_int), + ('block_number', big_endian_int), + ('gas_limit', big_endian_int), + ('gas_used', big_endian_int), + ('timestamp', big_endian_int), + ('extra_data', binary), + ('base_fee_per_gas', big_endian_int), + ('mix_hash', binary), + ('nonce', Binary(8, allow_empty=True)), + ] + def __init__(self, # type: ignore # noqa: F811 + difficulty: int, + block_number: BlockNumber, + gas_limit: int, + timestamp: int = None, + coinbase: Address = ZERO_ADDRESS, + parent_hash: Hash32 = ZERO_HASH32, + uncles_hash: Hash32 = EMPTY_UNCLE_HASH, + state_root: Hash32 = BLANK_ROOT_HASH, + transaction_root: Hash32 = BLANK_ROOT_HASH, + receipt_root: Hash32 = BLANK_ROOT_HASH, + bloom: int = 0, + gas_used: int = 0, + extra_data: bytes = b'', + mix_hash: Hash32 = ZERO_HASH32, + nonce: bytes = GENESIS_NONCE, + base_fee_per_gas: int = 0) -> None: + if timestamp is None: + timestamp = int(time.time()) + super().__init__( + parent_hash=parent_hash, + uncles_hash=uncles_hash, + coinbase=coinbase, + state_root=state_root, + transaction_root=transaction_root, + receipt_root=receipt_root, + bloom=bloom, + difficulty=difficulty, + block_number=block_number, + gas_limit=gas_limit, + gas_used=gas_used, + timestamp=timestamp, + extra_data=extra_data, + mix_hash=mix_hash, + nonce=nonce, + base_fee_per_gas=base_fee_per_gas, + ) + + def __str__(self) -> str: + return f'' + + _hash = None + + @property + def hash(self) -> Hash32: + if self._hash is None: + self._hash = keccak(rlp.encode(self)) + return self._hash + + @property + def mining_hash(self) -> Hash32: + return keccak(rlp.encode(self[:-2], LondonMiningHeader)) + + @property + def hex_hash(self) -> str: + return encode_hex(self.hash) + + @classmethod + def from_parent(cls, + parent: 'BlockHeaderAPI', + difficulty: int, + timestamp: int, + gas_limit: int, + coinbase: Address = ZERO_ADDRESS, + base_fee_per_gas: int = 0, # TODO is this correct? + nonce: bytes = None, + extra_data: bytes = None, + transaction_root: bytes = None, + receipt_root: bytes = None) -> 'BlockHeaderAPI': + """ + Initialize a new block header with the `parent` header as the block's + parent hash. + """ + header_kwargs: Dict[str, HeaderParams] = { + 'parent_hash': parent.hash, + 'coinbase': coinbase, + 'state_root': parent.state_root, + 'gas_limit': gas_limit, + 'base_fee_per_gas': base_fee_per_gas, + 'difficulty': difficulty, + 'block_number': parent.block_number + 1, + 'timestamp': timestamp, + } + if nonce is not None: + header_kwargs['nonce'] = nonce + if extra_data is not None: + header_kwargs['extra_data'] = extra_data + if transaction_root is not None: + header_kwargs['transaction_root'] = transaction_root + if receipt_root is not None: + header_kwargs['receipt_root'] = receipt_root + + header = cls(**header_kwargs) # type: ignore + return header + + @property + def is_genesis(self) -> bool: + # if removing the block_number == 0 test, consider the validation consequences. + # validate_header stops trying to check the current header against a parent header. + # Can someone trick us into following a high difficulty header with genesis parent hash? + return self.parent_hash == GENESIS_PARENT_HASH and self.block_number == 0 + + +class LondonBlock(BerlinBlock): + transaction_builder: Type[TransactionBuilderAPI] = LondonTransactionBuilder # type: ignore + receipt_builder: Type[ReceiptBuilderAPI] = LondonReceiptBuilder # type: ignore + fields = [ + ('header', LondonBlockHeader), + ('transactions', CountableList(transaction_builder)), + ('uncles', CountableList(LondonBlockHeader)) + ] diff --git a/eth/vm/forks/london/computation.py b/eth/vm/forks/london/computation.py new file mode 100644 index 0000000000..e34983772f --- /dev/null +++ b/eth/vm/forks/london/computation.py @@ -0,0 +1,11 @@ +from eth.vm.forks.berlin.computation import ( + BerlinComputation, +) + + +class LondonComputation(BerlinComputation): + """ + A class for all execution computations in the ``London`` fork. + Inherits from :class:`~eth.vm.forks.berlin.BerlinComputation` + """ + pass diff --git a/eth/vm/forks/london/constants.py b/eth/vm/forks/london/constants.py new file mode 100644 index 0000000000..54832e4e43 --- /dev/null +++ b/eth/vm/forks/london/constants.py @@ -0,0 +1,15 @@ +from eth.vm.forks.berlin.constants import ( + ACCESS_LIST_ADDRESS_COST_EIP_2930, + ACCESS_LIST_STORAGE_KEY_COST_EIP_2930, +) + +# EIP 1559 +BASE_GAS_FEE_TRANSACTION_TYPE = 2 +BASE_GAS_FEE_ADDRESS_COST = ACCESS_LIST_ADDRESS_COST_EIP_2930 +BASE_GAS_FEE_STORAGE_KEY_COST = ACCESS_LIST_STORAGE_KEY_COST_EIP_2930 + +BASE_FEE_MAX_CHANGE_DENOMINATOR = 8 +INITIAL_BASE_FEE = 1000000000 +INITIAL_FORK_BLOCK_NUMBER = -1 +MINIMUM_GAS_LIMIT = 5000 +ELASTICITY_MULTIPLIER = 2 diff --git a/eth/vm/forks/london/execution_context.py b/eth/vm/forks/london/execution_context.py new file mode 100644 index 0000000000..70e2369b0b --- /dev/null +++ b/eth/vm/forks/london/execution_context.py @@ -0,0 +1,7 @@ +from eth.abc import ExecutionContextAPI + + +class LondonExecutionContext(ExecutionContextAPI): + def __init__(self, base_gas_fee: int, **kwargs): # TODO remove kwargs + self._base_gas_fee = base_gas_fee + super().__init__(**kwargs) \ No newline at end of file diff --git a/eth/vm/forks/london/headers.py b/eth/vm/forks/london/headers.py new file mode 100644 index 0000000000..9f1e1f3a69 --- /dev/null +++ b/eth/vm/forks/london/headers.py @@ -0,0 +1,43 @@ +from eth.vm.forks.london.blocks import LondonBlockHeader +from eth.constants import GENESIS_GAS_LIMIT +from eth._utils.headers import compute_gas_limit +from eth.abc import BlockHeaderAPI +from typing import Any, Callable +from toolz.functoolz import curry +from eth.vm.forks.berlin.headers import ( + configure_header, + compute_berlin_difficulty, +) + +@curry +def create_header_from_parent(difficulty_fn: Callable[[BlockHeaderAPI, int], int], + parent_header: BlockHeaderAPI, + **header_params: Any) -> BlockHeaderAPI: + # byzantium + if 'difficulty' not in header_params: + header_params.setdefault('timestamp', parent_header.timestamp + 1) + + header_params['difficulty'] = difficulty_fn( + parent_header, + header_params['timestamp'], + ) + + # frontier + if 'gas_limit' not in header_params: + header_params['gas_limit'] = compute_gas_limit( + parent_header, + gas_limit_floor=GENESIS_GAS_LIMIT, + ) + + header = LondonBlockHeader.from_parent(parent=parent_header, **header_params) + return header + + +compute_london_difficulty = compute_berlin_difficulty + +create_london_header_from_parent = create_header_from_parent( + compute_london_difficulty +) + +# TODO update configure_header +# configure_london_header = configure_header(compute_berlin_difficulty) diff --git a/eth/vm/forks/london/receipts.py b/eth/vm/forks/london/receipts.py new file mode 100644 index 0000000000..977d0e862a --- /dev/null +++ b/eth/vm/forks/london/receipts.py @@ -0,0 +1,24 @@ +from typing import ( + Dict, + Type, +) + +from eth.rlp.receipts import ( + Receipt, + ReceiptAPI, +) +from eth.vm.forks.berlin.receipts import ( + BerlinReceiptBuilder +) +from eth.vm.forks.berlin.constants import ( + ACCESS_LIST_TRANSACTION_TYPE, +) + +from .constants import BASE_GAS_FEE_TRANSACTION_TYPE + + +class LondonReceiptBuilder(BerlinReceiptBuilder): + codecs: Dict[int, Type[Receipt]] = { + ACCESS_LIST_TRANSACTION_TYPE: Receipt, + BASE_GAS_FEE_TRANSACTION_TYPE: Receipt, + } diff --git a/eth/vm/forks/london/state.py b/eth/vm/forks/london/state.py new file mode 100644 index 0000000000..c45dacee84 --- /dev/null +++ b/eth/vm/forks/london/state.py @@ -0,0 +1,193 @@ +from eth.vm.logic.context import origin +from eth.vm.forks.frontier.constants import REFUND_SELFDESTRUCT +from typing import Type, Union + +from eth_hash.auto import keccak +from eth_utils.exceptions import ValidationError +from eth_utils import ( + encode_hex, +) + +from eth.abc import ( + AccountDatabaseAPI, + ComputationAPI, + MessageAPI, + SignedTransactionAPI, + StateAPI, + TransactionContextAPI, + TransactionExecutorAPI, +) +from eth.constants import ( + CREATE_CONTRACT_ADDRESS, + SECPK1_N, +) +from eth.db.account import ( + AccountDB +) +from eth.vm.message import ( + Message, +) +from eth.vm.forks.berlin.state import ( + BerlinState, + BerlinTransactionExecutor, +) +from eth._utils.address import ( + generate_contract_address, +) + +from .computation import LondonComputation +from .transactions import LondonLegacyTransaction, LondonTypedTransaction, LondonUnsignedLegacyTransaction, normalize_transaction +from .validation import validate_london_normalized_transaction + + +class LondonTransactionExecutor(BerlinTransactionExecutor): + def build_evm_message( + self, + transaction: SignedTransactionAPI, + ) -> MessageAPI: + transaction_context = self.vm_state.get_transaction_context(transaction) + gas_fee = transaction.gas * transaction_context.gas_price + + # Buy Gas + self.vm_state.delta_balance(transaction.sender, -1 * gas_fee) + + # Increment Nonce + self.vm_state.increment_nonce(transaction.sender) + + # Setup VM Message + message_gas = transaction.gas - transaction.intrinsic_gas + + if transaction.to == CREATE_CONTRACT_ADDRESS: + contract_address = generate_contract_address( + transaction.sender, + self.vm_state.get_nonce(transaction.sender) - 1, + ) + data = b'' + code = transaction.data + else: + contract_address = None + data = transaction.data + code = self.vm_state.get_code(transaction.to) + + self.vm_state.logger.debug2( + ( + "TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | " + "max_priority_fee_per_gas: %s | max_fee_per_gas: %s | s: %s | " + "r: %s | y_parity: %s | data-hash: %s" + ), + encode_hex(transaction.sender), + encode_hex(transaction.to), + transaction.value, + transaction.gas, + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas, + transaction.s, + transaction.r, + transaction.y_parity, + encode_hex(keccak(transaction.data)), + ) + + message = Message( + gas=message_gas, + to=transaction.to, + sender=transaction.sender, + value=transaction.value, + data=data, + code=code, + create_address=contract_address, + ) + return message + + def finalize_computation( + self, + transaction: SignedTransactionAPI, + computation: ComputationAPI + ) -> ComputationAPI: + transaction_context = self.vm_state.get_transaction_context(transaction) + + # Self Destruct Refunds + num_deletions = len(computation.get_accounts_for_deletion()) + if num_deletions: + computation.refund_gas(REFUND_SELFDESTRUCT * num_deletions) + + # Gas Refunds + gas_remaining = computation.get_gas_remaining() + gas_refunded = computation.get_gas_refund() + gas_used = transaction.gas - gas_remaining + gas_refund = min(gas_refunded, gas_used // 2) + gas_refund_amount = (gas_refund + gas_remaining) * transaction_context.gas_price + + if gas_refund_amount: + self.vm_state.logger.debug2( + 'TRANSACTION REFUND: %s -> %s', + gas_refund_amount, + encode_hex(computation.msg.sender), + ) + + self.vm_state.delta_balance(computation.msg.sender, gas_refund_amount) + + # Miner Fees + transaction_fee = \ + (transaction.gas - gas_remaining - gas_refund) * transaction.max_priority_fee_per_gas + self.vm_state.logger.debug2( + 'TRANSACTION FEE: %s -> %s', + transaction_fee, + encode_hex(self.vm_state.coinbase), + ) + self.vm_state.delta_balance(self.vm_state.coinbase, transaction_fee) + + # Process Self Destructs + for account, _ in computation.get_accounts_for_deletion(): + # TODO: need to figure out how we prevent multiple selfdestructs from + # the same account and if this is the right place to put this. + self.vm_state.logger.debug2('DELETING ACCOUNT: %s', encode_hex(account)) + + # TODO: this balance setting is likely superflous and can be + # removed since `delete_account` does this. + self.vm_state.set_balance(account, 0) + self.vm_state.delete_account(account) + + return computation + +class LondonState(BerlinState): + account_db_class: Type[AccountDatabaseAPI] = AccountDB + computation_class = LondonComputation + transaction_executor_class: Type[TransactionExecutorAPI] = LondonTransactionExecutor + + def validate_transaction( + self, + transaction: SignedTransactionAPI + ) -> None: + # homestead validation + if transaction.s > SECPK1_N // 2 or transaction.s == 0: + raise ValidationError("Invalid signature S value") + + normalized_transaction = normalize_transaction(transaction) + validate_london_normalized_transaction( + state=self, + transaction=normalized_transaction, + base_fee_per_gas=self.execution_context.base_gas_fee + ) + + def get_transaction_context(self: StateAPI, + transaction: SignedTransactionAPI) -> TransactionContextAPI: + """ + London-specific transaction context creation, + where gas_price includes the block base fee + """ + if isinstance(transaction, LondonTypedTransaction): # TODO probably do this somewhere else + priority_fee_per_gas = min( + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas - self.execution_context.base_gas_fee + ) + else: + priority_fee_per_gas = min( + transaction.gas_price, + transaction.gas_price - self.execution_context.base_gas_fee + ) + + effective_gas_price = self.execution_context.base_gas_fee + priority_fee_per_gas + return self.get_transaction_context_class()( + gas_price=effective_gas_price, + origin=transaction.sender + ) \ No newline at end of file diff --git a/eth/vm/forks/london/transactions.py b/eth/vm/forks/london/transactions.py new file mode 100644 index 0000000000..548495e622 --- /dev/null +++ b/eth/vm/forks/london/transactions.py @@ -0,0 +1,324 @@ +from cached_property import cached_property +from typing import ( + Any, + Dict, + Optional, + Sequence, + Tuple, + Type, + overload, +) +from eth_keys.datatypes import PrivateKey +from eth_utils.exceptions import ValidationError + +from eth.abc import ( + ReceiptAPI, + SignedTransactionAPI, + TransactionDecoderAPI, +) +from eth.rlp.logs import Log +from eth.rlp.receipts import Receipt +from eth.rlp.transactions import SignedTransactionMethods +from eth.rlp.sedes import address +from eth.vm.forks.berlin.constants import ( + ACCESS_LIST_ADDRESS_COST_EIP_2930, + ACCESS_LIST_STORAGE_KEY_COST_EIP_2930, + ACCESS_LIST_TRANSACTION_TYPE, +) +from eth.vm.forks.berlin.transactions import ( + AccessListPayloadDecoder, + AccountAccesses, + BerlinLegacyTransaction, + BerlinTransactionBuilder, + BerlinUnsignedLegacyTransaction, + TypedTransaction, +) +from eth.vm.forks.istanbul.transactions import ( + ISTANBUL_TX_GAS_SCHEDULE, +) +from eth.vm.spoof import ( + SpoofTransaction, +) + +from eth._utils.transactions import ( + calculate_intrinsic_gas, + create_transaction_signature, + extract_transaction_sender, + validate_transaction_signature, +) + +from .constants import ( + BASE_GAS_FEE_TRANSACTION_TYPE, +) + +import rlp +from eth_typing import ( + Address, + Hash32, +) +from eth_utils import ( + to_bytes, +) +from rlp.sedes import ( + CountableList, + big_endian_int, + binary, +) + +from .constants import BASE_GAS_FEE_TRANSACTION_TYPE + + +class LondonLegacyTransaction(BerlinLegacyTransaction): + pass + + +class LondonUnsignedLegacyTransaction(BerlinUnsignedLegacyTransaction): + def as_signed_transaction(self, + private_key: PrivateKey, + chain_id: int = None) -> LondonLegacyTransaction: + v, r, s = create_transaction_signature(self, private_key, chain_id=chain_id) + return LondonLegacyTransaction( + nonce=self.nonce, + gas_price=self.gas_price, + gas=self.gas, + to=self.to, + value=self.value, + data=self.data, + v=v, + r=r, + s=s, + ) + + + +class UnsignedBaseGasFeeTransaction(rlp.Serializable): + _type_id = BASE_GAS_FEE_TRANSACTION_TYPE + fields = [ + ('chain_id', big_endian_int), + ('nonce', big_endian_int), + ('max_priority_fee_per_gas', big_endian_int), + ('max_fee_per_gas', big_endian_int), + ('gas', big_endian_int), + ('to', address), + ('value', big_endian_int), + ('data', binary), + ('access_list', CountableList(AccountAccesses)), + ] + + @cached_property + def _type_byte(self) -> bytes: + return to_bytes(self._type_id) + + def get_message_for_signing(self) -> bytes: + payload = rlp.encode(self) + return self._type_byte + payload + + def as_signed_transaction(self, private_key: PrivateKey) -> 'TypedTransaction': + message = self.get_message_for_signing() + signature = private_key.sign_msg(message) + y_parity, r, s = signature.vrs + + signed_transaction = BaseGasFeeTransaction( + self.chain_id, + self.nonce, + self.max_priority_fee_per_gas, + self.max_fee_per_gas, + self.gas, + self.to, + self.value, + self.data, + self.access_list, + y_parity, + r, + s + ) + return LondonTypedTransaction(self._type_id, signed_transaction) + + +class BaseGasFeeTransaction(rlp.Serializable, SignedTransactionMethods, SignedTransactionAPI): + _type_id = BASE_GAS_FEE_TRANSACTION_TYPE + fields = [ + ('chain_id', big_endian_int), + ('nonce', big_endian_int), + ('max_priority_fee_per_gas', big_endian_int), + ('max_fee_per_gas', big_endian_int), + ('gas', big_endian_int), + ('to', address), + ('value', big_endian_int), + ('data', binary), + ('access_list', CountableList(AccountAccesses)), + ('y_parity', big_endian_int), + ('r', big_endian_int), + ('s', big_endian_int), + ] + + @property + def gas_price(self) -> None: + # maybe add a warning, or raise an exception instead? + return None + + def get_sender(self) -> Address: + return extract_transaction_sender(self) + + def get_message_for_signing(self) -> bytes: + unsigned = UnsignedBaseGasFeeTransaction( + self.chain_id, + self.nonce, + self.max_priority_fee_per_gas, + self.max_fee_per_gas, + self.gas, + self.to, + self.value, + self.data, + self.access_list, + ) + payload = rlp.encode(unsigned) + return self._type_byte + payload + + def check_signature_validity(self) -> None: + validate_transaction_signature(self) + + @cached_property + def _type_byte(self) -> bytes: + return to_bytes(self._type_id) + + @cached_property + def hash(self) -> Hash32: + raise NotImplementedError("Call hash() on the TypedTransaction instead") + + def get_intrinsic_gas(self) -> int: + core_gas = calculate_intrinsic_gas(ISTANBUL_TX_GAS_SCHEDULE, self) + + num_addresses = len(self.access_list) + preload_address_costs = ACCESS_LIST_ADDRESS_COST_EIP_2930 * num_addresses + + num_slots = sum(len(slots) for _, slots in self.access_list) + preload_slot_costs = ACCESS_LIST_STORAGE_KEY_COST_EIP_2930 * num_slots + + return core_gas + preload_address_costs + preload_slot_costs + + + def encode(self) -> bytes: + return rlp.encode(self) + + def make_receipt( + self, + status: bytes, + gas_used: int, + log_entries: Tuple[Tuple[bytes, Tuple[int, ...], bytes], ...]) -> ReceiptAPI: + + logs = [ + Log(address, topics, data) + for address, topics, data + in log_entries + ] + # TypedTransaction is responsible for wrapping this into a TypedReceipt + return Receipt( + state_root=status, + gas_used=gas_used, + logs=logs, + ) + + +class BaseGasFeePayloadDecoder(TransactionDecoderAPI): + @classmethod + def decode(cls, payload: bytes) -> SignedTransactionAPI: + return rlp.decode(payload, sedes=BaseGasFeeTransaction) + + +class LondonTypedTransaction(TypedTransaction): + decoders: Dict[int, Type[TransactionDecoderAPI]] = { + ACCESS_LIST_TRANSACTION_TYPE: AccessListPayloadDecoder, + BASE_GAS_FEE_TRANSACTION_TYPE: BaseGasFeePayloadDecoder, + } + + def __init__(self, type_id: int, proxy_target: SignedTransactionAPI) -> None: + super().__init__(type_id, proxy_target) + self.max_priority_fee_per_gas = self._inner.max_priority_fee_per_gas + self.max_fee_per_gas = self._inner.max_fee_per_gas + + # @property + # def max_priority_fee_per_gas(self) -> int: + # return self._inner.max_priority_fee_per_gas + + # @property + # def max_fee_per_gas(self) -> int: + # return self._inner.max_fee_per_gas + + +class LondonTransactionBuilder(BerlinTransactionBuilder): + legacy_signed = LondonLegacyTransaction + legacy_unsigned = LondonUnsignedLegacyTransaction + + @classmethod + def new_unsigned_base_gas_price_transaction( + cls, + chain_id: int, + nonce: int, + max_priority_fee_per_gas: int, + max_fee_per_gas: int, + gas: int, + to: Address, + value: int, + data: bytes, + access_list: Sequence[Tuple[Address, Sequence[int]]],) -> LondonTypedTransaction: + transaction = UnsignedBaseGasFeeTransaction( + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas, + to, + value, + data, + access_list, + ) + return transaction + + @classmethod + def new_base_gas_price_transaction( + cls, + chain_id: int, + nonce: int, + max_priority_fee_per_gas: int, + max_fee_per_gas: int, + gas: int, + to: Address, + value: int, + data: bytes, + access_list: Sequence[Tuple[Address, Sequence[int]]], + y_parity: int, + r: int, + s: int) -> LondonTypedTransaction: + transaction = BaseGasFeeTransaction( + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas, + to, + value, + data, + access_list, + y_parity, + r, + s, + ) + return LondonTypedTransaction(BASE_GAS_FEE_TRANSACTION_TYPE, transaction) + +def normalize_transaction( + transaction: SignedTransactionAPI +) -> SignedTransactionAPI: + if isinstance(transaction, (LondonLegacyTransaction, SpoofTransaction)): + transaction.max_priority_fee_per_gas = transaction.gas_price + transaction.max_fee_per_gas = transaction.gas_price + elif isinstance(transaction, LondonTypedTransaction): + if transaction.type_id == ACCESS_LIST_TRANSACTION_TYPE: + transaction.max_priority_fee_per_gas = transaction.gas_price + transaction.max_fee_per_gas = transaction.gas_price + elif transaction.type_id != BASE_GAS_FEE_TRANSACTION_TYPE: + raise ValidationError(f"Invalid transaction type_id: {transaction.type_id}") + else: + raise ValidationError(f"Invalid transaction type: {type(transaction)}") + + return transaction \ No newline at end of file diff --git a/eth/vm/forks/london/validation.py b/eth/vm/forks/london/validation.py new file mode 100644 index 0000000000..75b2de2fa5 --- /dev/null +++ b/eth/vm/forks/london/validation.py @@ -0,0 +1,44 @@ +from eth.abc import ( + SignedTransactionAPI, + StateAPI +) + +from eth_utils.exceptions import ValidationError + +def validate_london_normalized_transaction( + state: StateAPI, + transaction: SignedTransactionAPI, + base_fee_per_gas: int +) -> None: + """ + Validates a London normalized transaction. + + Raise `eth.exceptions.ValidationError` if the sender cannot + afford to send this transaction. + """ + if transaction.max_fee_per_gas < base_fee_per_gas: + raise ValidationError( + f"Sender's max fee per gas ({transaction.max_fee_per_gas}) is " + f"lower than block's base fee per gas ({base_fee_per_gas})" + ) + + sender_balance = state.get_balance(transaction.sender) + if transaction.value > sender_balance: + raise ValidationError( + f"Sender {transaction.sender!r} cannot afford txn value" + f"{transaction.value} with account balance {sender_balance}" + ) + + priority_fee_per_gas = min( + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas - base_fee_per_gas + ) + + effective_gas_price = priority_fee_per_gas + base_fee_per_gas + total_transaction_cost = transaction.value + effective_gas_price + + if sender_balance - total_transaction_cost < 0: + raise ValidationError( + f"Sender does not have enough balance to cover transaction value and gas " + f" (has {sender_balance}, needs {total_transaction_cost})" + ) diff --git a/eth/vm/state.py b/eth/vm/state.py index 3bb181250f..cf503942dd 100644 --- a/eth/vm/state.py +++ b/eth/vm/state.py @@ -237,10 +237,10 @@ def get_computation(self, # Transaction context # @classmethod - def get_transaction_context_class(cls) -> Type[TransactionContextAPI]: - if cls.transaction_context_class is None: + def get_transaction_context_class(self) -> Type[TransactionContextAPI]: + if self.transaction_context_class is None: raise AttributeError("No `transaction_context_class` has been set for this State") - return cls.transaction_context_class + return self.transaction_context_class # # Execution @@ -269,10 +269,9 @@ def get_custom_transaction_context(transaction: SignedTransactionAPI) -> Transac finally: self.get_transaction_context = original_context # type: ignore # Remove ignore if https://github.com/python/mypy/issues/708 is fixed. # noqa: E501 - @classmethod - def get_transaction_context(cls, + def get_transaction_context(self, transaction: SignedTransactionAPI) -> TransactionContextAPI: - return cls.get_transaction_context_class()( + return self.get_transaction_context_class()( gas_price=transaction.gas_price, origin=transaction.sender, ) diff --git a/tests/conftest.py b/tests/conftest.py index d955f33c21..c3e4aefc07 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,6 +33,7 @@ IstanbulVM, MuirGlacierVM, BerlinVM, + LondonVM, ) # @@ -93,6 +94,7 @@ def _file_logging(request): IstanbulVM, MuirGlacierVM, BerlinVM, + LondonVM, ]) def VM(request): return request.param diff --git a/tests/core/builder-tools/test_chain_construction.py b/tests/core/builder-tools/test_chain_construction.py index eadfb72ebf..6535d9c652 100644 --- a/tests/core/builder-tools/test_chain_construction.py +++ b/tests/core/builder-tools/test_chain_construction.py @@ -18,6 +18,7 @@ homestead_at, istanbul_at, latest_mainnet_at, + london_at, muir_glacier_at, name, petersburg_at, @@ -35,6 +36,7 @@ IstanbulVM, MuirGlacierVM, BerlinVM, + LondonVM, ) @@ -88,6 +90,7 @@ def test_chain_builder_construct_chain_vm_configuration_multiple_forks(): (istanbul_at, IstanbulVM), (muir_glacier_at, MuirGlacierVM), (berlin_at, BerlinVM), + (london_at, LondonVM), (latest_mainnet_at, MuirGlacierVM), # this will change whenever the next upgrade is locked ) ) diff --git a/tests/core/chain-object/test_contract_call.py b/tests/core/chain-object/test_contract_call.py index 94e0e5b370..78f296c81f 100644 --- a/tests/core/chain-object/test_contract_call.py +++ b/tests/core/chain-object/test_contract_call.py @@ -29,6 +29,7 @@ IstanbulVM, MuirGlacierVM, BerlinVM, + LondonVM, ) @@ -245,6 +246,16 @@ def test_get_transaction_result( 'useLotsOfGas()', OutOfGas, ), + ( + LondonVM, + 'doRevert()', + Revert, + ), + ( + LondonVM, + 'useLotsOfGas()', + OutOfGas, + ), ), ) def test_get_transaction_result_revert( diff --git a/tests/core/chain-object/test_gas_estimation.py b/tests/core/chain-object/test_gas_estimation.py index c621c349f1..1d50c977d3 100644 --- a/tests/core/chain-object/test_gas_estimation.py +++ b/tests/core/chain-object/test_gas_estimation.py @@ -15,6 +15,7 @@ IstanbulVM, MuirGlacierVM, BerlinVM, + LondonVM, ) from eth._utils.address import force_bytes_to_address diff --git a/tests/core/opcodes/test_opcodes.py b/tests/core/opcodes/test_opcodes.py index 71ffc1f336..f93a6220b9 100644 --- a/tests/core/opcodes/test_opcodes.py +++ b/tests/core/opcodes/test_opcodes.py @@ -47,6 +47,7 @@ IstanbulVM, MuirGlacierVM, BerlinVM, + LondonVM, ) from eth.vm.message import ( Message, @@ -1009,6 +1010,7 @@ def test_sstore_limit_2300(gas_supplied, success, gas_used, refund): IstanbulVM, MuirGlacierVM, BerlinVM, + LondonVM, )) @pytest.mark.parametrize( # Testcases from https://eips.ethereum.org/EIPS/eip-1344 @@ -1321,7 +1323,7 @@ def test_access_list_gas_costs(vm_class, code, expect_gas_used, access_list): # cases from https://gist.github.com/holiman/174548cad102096858583c6fbbb0649a # mentioned in EIP-2929 -@pytest.mark.parametrize('vm_class', (BerlinVM, )) +@pytest.mark.parametrize('vm_class', (BerlinVM, LondonVM, )) @pytest.mark.parametrize( 'bytecode_hex, expect_gas_used', ( diff --git a/tests/core/tester/test_generate_vm_configuration.py b/tests/core/tester/test_generate_vm_configuration.py index ea84feab7a..1c6a96cff3 100644 --- a/tests/core/tester/test_generate_vm_configuration.py +++ b/tests/core/tester/test_generate_vm_configuration.py @@ -20,6 +20,7 @@ class Forks(enum.Enum): Istanbul = 'Istanbul' MuirGlacier = 'MuirGlacier' Berlin = 'Berlin' + London = 'London' class CustomFrontierVM(FrontierVM): @@ -123,6 +124,7 @@ class CustomFrontierVM(FrontierVM): (6, 'istanbul'), (7, 'muir-glacier'), (8, 'berlin'), + (9, 'london'), ), {}, ( @@ -134,6 +136,7 @@ class CustomFrontierVM(FrontierVM): (6, Forks.Istanbul), (7, Forks.MuirGlacier), (8, Forks.Berlin), + (9, Forks.London), ), ), ), diff --git a/tests/core/transaction-utils/test_receipt_encoding.py b/tests/core/transaction-utils/test_receipt_encoding.py index cc5fa6ea37..f3cab6b9a9 100644 --- a/tests/core/transaction-utils/test_receipt_encoding.py +++ b/tests/core/transaction-utils/test_receipt_encoding.py @@ -10,6 +10,7 @@ from eth.rlp.receipts import Receipt from eth.vm.forks import ( BerlinVM, + LondonVM, ) from eth.vm.forks.berlin.receipts import ( TypedReceipt, @@ -30,7 +31,7 @@ ) -@pytest.mark.parametrize('vm_class', [BerlinVM]) +@pytest.mark.parametrize('vm_class', [BerlinVM, LondonVM]) @pytest.mark.parametrize( 'encoded, expected', ( @@ -70,7 +71,7 @@ def test_transaction_decode(vm_class, encoded, expected): assert decoded == expected -@pytest.mark.parametrize('vm_class', [BerlinVM]) +@pytest.mark.parametrize('vm_class', [BerlinVM, LondonVM]) @pytest.mark.parametrize( 'encoded, expected_failure', ( diff --git a/tests/core/transaction-utils/test_transaction_encoding.py b/tests/core/transaction-utils/test_transaction_encoding.py index 44226a7cf5..98cfef6a18 100644 --- a/tests/core/transaction-utils/test_transaction_encoding.py +++ b/tests/core/transaction-utils/test_transaction_encoding.py @@ -10,6 +10,7 @@ from eth.exceptions import UnrecognizedTransactionType from eth.vm.forks import ( BerlinVM, + LondonVM, ) RECOGNIZED_TRANSACTION_TYPES = {1} @@ -27,7 +28,7 @@ ) -@pytest.mark.parametrize('vm_class', [BerlinVM]) +@pytest.mark.parametrize('vm_class', [BerlinVM, LondonVM]) @pytest.mark.parametrize( 'encoded, expected', ( diff --git a/tests/database/test_eth1_chaindb.py b/tests/database/test_eth1_chaindb.py index 68ccaafa50..d7ab11e2e6 100644 --- a/tests/database/test_eth1_chaindb.py +++ b/tests/database/test_eth1_chaindb.py @@ -41,6 +41,7 @@ ) from eth.vm.forks import ( BerlinVM, + LondonVM, ) from eth.vm.forks.frontier.blocks import ( FrontierBlock, @@ -377,7 +378,7 @@ def mine_blocks_with_access_list_receipts( funded_address_private_key): current_vm = chain.get_vm() - if not isinstance(current_vm, BerlinVM): + if not isinstance(current_vm, (BerlinVM, LondonVM)): pytest.skip("{current_vm} does not support typed transactions") for _ in range(num_blocks):