-
Notifications
You must be signed in to change notification settings - Fork 691
[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
Closed
Changes from 1 commit
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
8070f2d
WIP add support for EIP-1559
ftruzzi c0e67ff
add initial EIP-1559 transaction validation
ftruzzi 0b15249
WIP tx execution
ftruzzi bb0c3f7
fixes
ftruzzi 51d353e
rename gas_target to gas_limit
ftruzzi 765636f
fixes
ftruzzi 9da46a6
add london VM in existing tests
ftruzzi cf48c45
fixes
ftruzzi 48ad8fb
update block according to latest EIP updates
ftruzzi 3b9eb9a
remove unused LondonNormalizedTransaction class
ftruzzi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,3 +28,6 @@ | |
from .berlin import ( # noqa: F401 | ||
BerlinVM, | ||
) | ||
from .london import ( # noqa: F401 | ||
LondonVM | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
from eth_utils.exceptions import ValidationError | ||
from eth.vm.forks.london.constants import ( | ||
BASE_FEE_MAX_CHANGE_DENOMINATOR, | ||
ELASTICITY_MULTIPLIER | ||
) | ||
from typing import Type | ||
|
||
from eth.abc import BlockAPI, BlockHeaderAPI | ||
from eth.rlp.blocks import BaseBlock | ||
from eth._utils.db import get_parent_header | ||
from eth.vm.forks.berlin import BerlinVM | ||
from eth.vm.state import BaseState | ||
|
||
from .blocks import LondonBlock | ||
from .state import LondonState | ||
|
||
|
||
class LondonVM(BerlinVM): | ||
# fork name | ||
fork = 'london' | ||
|
||
# classes | ||
block_class: Type[BaseBlock] = LondonBlock | ||
_state_class: Type[BaseState] = LondonState | ||
|
||
# Methods | ||
# create_header_from_parent = staticmethod(create_berlin_header_from_parent) # type: ignore | ||
# compute_difficulty = staticmethod(compute_berlin_difficulty) # type: ignore | ||
# configure_header = configure_berlin_header | ||
|
||
# @staticmethod | ||
# def make_receipt( | ||
# base_header: BlockHeaderAPI, | ||
# transaction: SignedTransactionAPI, | ||
# computation: ComputationAPI, | ||
# state: StateAPI) -> ReceiptAPI: | ||
|
||
# gas_used = base_header.gas_used + finalize_gas_used(transaction, computation) | ||
|
||
# if computation.is_error: | ||
# status_code = EIP658_TRANSACTION_STATUS_CODE_FAILURE | ||
# else: | ||
# status_code = EIP658_TRANSACTION_STATUS_CODE_SUCCESS | ||
|
||
# return transaction.make_receipt(status_code, gas_used, computation.get_log_entries()) | ||
|
||
@staticmethod | ||
def calculate_expected_base_fee_per_gas(parent_header: BlockHeaderAPI) -> int: | ||
parent_base_fee_per_gas = parent_header.base_fee_per_gas | ||
parent_gas_target = parent_header.gas_target | ||
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_target | ||
|
||
max_usable_gas = header.gas_target * ELASTICITY_MULTIPLIER | ||
if header.gas_used > max_usable_gas: | ||
raise ValidationError( | ||
f"Block used too much gas: {header.gas_used} " | ||
f"(max: {max_usable_gas})" | ||
) | ||
|
||
if header.gas_target > parent_gas_target + (parent_gas_target // 1024): | ||
raise ValidationError( | ||
f"Gas target increased too much (from {parent_gas_target} " | ||
f"to {header.gas_target})" | ||
) | ||
|
||
if header.gas_target < parent_gas_target - (parent_gas_target // 1024): | ||
raise ValidationError( | ||
f"Gas target decreased too much (from {parent_gas_target} " | ||
f"to {header.gas_target})" | ||
) | ||
|
||
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})" | ||
) | ||
|
||
# TODO continue validation | ||
|
||
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
import time | ||
|
||
from typing import ( | ||
Dict, | ||
Type, | ||
) | ||
|
||
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_target', 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_target', big_endian_int), | ||
('gas_used', big_endian_int), | ||
('timestamp', big_endian_int), | ||
('extra_data', binary), | ||
('mix_hash', binary), | ||
('nonce', Binary(8, allow_empty=True)), | ||
('base_fee_per_gas', big_endian_int), | ||
] | ||
|
||
def __init__(self, # type: ignore # noqa: F811 | ||
difficulty: int, | ||
block_number: BlockNumber, | ||
gas_target: 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_target=gas_target, | ||
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'<BlockHeader #{self.block_number} {encode_hex(self.hash)[2:10]}>' | ||
|
||
_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: 'LondonBlockHeader', | ||
gas_target: int, | ||
difficulty: int, | ||
timestamp: int, | ||
coinbase: Address = ZERO_ADDRESS, | ||
base_fee_per_gas: int = 0, | ||
nonce: bytes = None, | ||
extra_data: bytes = None, | ||
transaction_root: bytes = None, | ||
receipt_root: bytes = None) -> 'LondonBlockHeader': | ||
""" | ||
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_target': gas_target, | ||
'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) | ||
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)) | ||
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
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 | ||
ELASTICITY_MULTIPLIER = 2 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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-checkgas_price
which is no longer a thing in London transactions so they need to be duplicated almost entirely. We could keepgas_price
as an (internal, not exposed) alias for eithermax_fee_per_gas
ormax_priority_fee_per_gas
so that we could extend and call basic validation methods viasuper()
, but it sounds confusing :(There was a problem hiding this comment.
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.