Skip to content

[WIP] add support for EIP-1559 #2005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions eth/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class MiningHeaderAPI(ABC):
gas_used: int
timestamp: int
extra_data: bytes
base_fee_per_gas: int # EIP-1559
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels... unfortunate, but it might be necessary. Ideally, it would be nice if these fields didn't leak into all the previous VM APIs.... but doing so might introduce even more unfortunate complexity into our type definitions which might be worse...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree and will take a deeper look -- for now I can say a similar problem applies to transactions. Base transaction.validate methods all type-check gas_price which is no longer a thing in London transactions so they need to be duplicated almost entirely. We could keep gas_price as an (internal, not exposed) alias for either max_fee_per_gas or max_priority_fee_per_gas so that we could extend and call basic validation methods via super(), but it sounds confusing :(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After dealing with typed transactions, my general take on it was: the latest VM should have the cleanest implementation. If a kludge is called for, it should be on the old VMs (like v vs y-parity in the signature, where we started to prefer y-parity as the default/native field). At first glance, this change seems to follow that approach, so I'm 👌🏻 to keep it. I'll comment if I stumble on a better option while reading the rest.


@property
@abstractmethod
Expand Down Expand Up @@ -326,14 +327,17 @@ 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:
...

@property
@abstractmethod
def gas_price(self) -> int:
def gas_price(self) -> Optional[int]:
...

@property
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this one could stay a classmethod

"""
Return the :class:`~eth.vm.transaction_context.BaseTransactionContext` class that the
state class uses.
Expand Down Expand Up @@ -2917,9 +2928,8 @@ def validate_transaction(self, transaction: SignedTransactionAPI) -> None:
"""
...

@classmethod
@abstractmethod
def get_transaction_context(cls,
def get_transaction_context(self,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, bummer that this can't be a class method anymore. I can maybe imagine doing something where the transaction context no longer gives access to the active gas price without explicit access to the execution-context (like as a parameter to a new get_gas_price(execution_context) method). Or something along those lines. I am okay with this solution though. 👍🏻

transaction: SignedTransactionAPI) -> TransactionContextAPI:
"""
Return the :class:`~eth.abc.TransactionContextAPI` for the given ``transaction``
Expand Down
4 changes: 4 additions & 0 deletions eth/chains/mainnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from .constants import (
MAINNET_CHAIN_ID,
LONDON_MAINNET_BLOCK,
BERLIN_MAINNET_BLOCK,
BYZANTIUM_MAINNET_BLOCK,
PETERSBURG_MAINNET_BLOCK,
Expand Down Expand Up @@ -39,6 +40,7 @@
FrontierVM,
HomesteadVM,
IstanbulVM,
LondonVM,
MuirGlacierVM,
PetersburgVM,
SpuriousDragonVM,
Expand Down Expand Up @@ -97,6 +99,7 @@ class MainnetHomesteadVM(MainnetDAOValidatorVM):
ISTANBUL_MAINNET_BLOCK,
MUIR_GLACIER_MAINNET_BLOCK,
BERLIN_MAINNET_BLOCK,
LONDON_MAINNET_BLOCK,
)
MAINNET_VMS = (
FrontierVM,
Expand All @@ -108,6 +111,7 @@ class MainnetHomesteadVM(MainnetDAOValidatorVM):
IstanbulVM,
MuirGlacierVM,
BerlinVM,
LondonVM,
)

MAINNET_VM_CONFIGURATION = tuple(zip(MAINNET_FORK_BLOCKS, MAINNET_VMS))
Expand Down
6 changes: 6 additions & 0 deletions eth/chains/mainnet/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,9 @@
# Berlin Block
#
BERLIN_MAINNET_BLOCK = BlockNumber(12244000)


#
# London Block
#
LONDON_MAINNET_BLOCK = BlockNumber(12244001) # TODO change to actual when known
12 changes: 11 additions & 1 deletion eth/db/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this look good, or should we find some way to check the header type first?

5 changes: 5 additions & 0 deletions eth/rlp/headers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import time
from typing import (
Dict,
Optional,
overload,
)

Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions eth/tools/builder/chain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
istanbul_at,
muir_glacier_at,
berlin_at,
london_at,
latest_mainnet_at,
)

Expand Down
2 changes: 2 additions & 0 deletions eth/tools/builder/chain/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
IstanbulVM,
MuirGlacierVM,
BerlinVM,
LondonVM,
)


Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion eth/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
)


Expand Down
2 changes: 2 additions & 0 deletions eth/vm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
11 changes: 10 additions & 1 deletion eth/vm/execution_context.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import (
Iterable,
Optional,
)

from eth_typing import (
Expand All @@ -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,
Expand All @@ -30,14 +32,17 @@ 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
self._difficulty = difficulty
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:
Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions eth/vm/forks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@
from .berlin import ( # noqa: F401
BerlinVM,
)
from .london import ( # noqa: F401
LondonVM
)
4 changes: 3 additions & 1 deletion eth/vm/forks/berlin/receipts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -124,14 +125,15 @@ def __eq__(self, other: Any) -> bool:

class BerlinReceiptBuilder(ReceiptBuilderAPI):
legacy_sedes = Receipt
codecs = TYPED_RECEIPT_BODY_CODECS

@classmethod
def decode(cls, encoded: bytes) -> ReceiptAPI:
if len(encoded) == 0:
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)
Expand Down
2 changes: 2 additions & 0 deletions eth/vm/forks/berlin/transactions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import (
Any,
Dict,
Optional,
Sequence,
Tuple,
Type,
Expand Down Expand Up @@ -153,6 +154,7 @@ class AccessListTransaction(rlp.Serializable, SignedTransactionMethods, SignedTr
('s', big_endian_int),
]


def get_sender(self) -> Address:
return extract_transaction_sender(self)

Expand Down
Loading