-
Notifications
You must be signed in to change notification settings - Fork 663
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,55 @@ | ||
Implementing VM forks | ||
===================== | ||
|
||
The Ethereum protocol follows specified rules which continue to be improved through so called | ||
`Ethereum Improvement Proposals (EIPs) <https://eips.ethereum.org/>`_. Every now and then the | ||
community agrees on a few EIPs to become part of the next protocol upgrade. These upgrades happen | ||
through so called `Hardforks <https://en.wikipedia.org/wiki/Fork_(blockchain)>`_ which define: | ||
|
||
1. A name for the set of rule changes (e.g. the Istanbul hardfork) | ||
2. A block number from which on blocks are processed according to these new rules (e.g. `9069000`) | ||
|
||
Every client that wants to support the official Ethereum protocol needs to implement these changes | ||
to remain functional. | ||
|
||
|
||
This guide covers how to implement new hardforks in Py-EVM. The specifics and impact of each rule | ||
change many vary a lot between different hardforks and it is out of the scope of this guide to | ||
cover these in depth. This is mainly a reference guide for developers to ensure the process of | ||
implementing hardforks in Py-EVM is as smooth and safe as possible. | ||
|
||
|
||
Creating the fork module | ||
------------------------ | ||
|
||
Every fork is encapsulated in its own module under ``eth.vm.forks.<fork-name>``. To create the | ||
scaffolding for the fork run ``python scripts/forking/create_fork.py`` and follow the assistent. | ||
|
||
.. code:: sh | ||
$ python scripts/forking/create_fork.py | ||
Specify the name of the fork (e.g Muir Glacier): | ||
-->ancient tavira | ||
Specify the fork base (e.g Istanbul): | ||
-->istanbul | ||
Check your inputs: | ||
New fork: | ||
Writing(pascal_case='AncientTavira', lower_dash_case='ancient-tavira', lower_snake_case='ancient_tavira', upper_snake_case='ANCIENT_TAVIRA') | ||
Base fork: | ||
Writing(pascal_case='Istanbul', lower_dash_case='istanbul', lower_snake_case='istanbul', upper_snake_case='ISTANBUL') | ||
Proceed (y/n)? | ||
-->y | ||
Your fork is ready! | ||
Configuring new opcodes | ||
----------------------- | ||
|
||
Configuring new precompiles | ||
--------------------------- | ||
|
||
Set the fork as the recent fork | ||
------------------------------- | ||
|
||
Wiring up the tests | ||
------------------- |
This file contains 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 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,117 @@ | ||
import glob | ||
from typing import NamedTuple | ||
import pathlib | ||
import shutil | ||
|
||
SCRIPT_BASE_PATH = pathlib.Path(__file__).parent | ||
SCRIPT_TEMPLATE_PATH = SCRIPT_BASE_PATH / 'template' / 'whitelabel' | ||
ETH_BASE_PATH = SCRIPT_BASE_PATH.parent.parent / 'eth' | ||
FORKS_BASE_PATH = ETH_BASE_PATH / 'vm' / 'forks' | ||
|
||
INPUT_PROMPT = '-->' | ||
YES = 'y' | ||
|
||
# Given a fork name of Muir Glacier we need to derive: | ||
# pascal case: MuirGlacier | ||
# lower_dash_case: muir-glacier | ||
# lower_snake_case: muir_glacier | ||
# upper_snake_case: MUIR_GLACIER | ||
|
||
|
||
class Writing(NamedTuple): | ||
pascal_case: str | ||
lower_dash_case: str | ||
lower_snake_case: str | ||
upper_snake_case: str | ||
|
||
|
||
WHITELABEL_FORK = Writing( | ||
pascal_case="Istanbul", | ||
lower_dash_case="istanbul", | ||
lower_snake_case="istanbul", | ||
upper_snake_case="ISTANBUL", | ||
) | ||
|
||
WHITELABEL_PARENT = Writing( | ||
pascal_case="Petersburg", | ||
lower_dash_case="petersburg", | ||
lower_snake_case="petersburg", | ||
upper_snake_case="PETERSBURG", | ||
) | ||
|
||
|
||
def bootstrap() -> None: | ||
print("Specify the name of the fork (e.g Muir Glacier):") | ||
fork_name = input(INPUT_PROMPT) | ||
|
||
if not all(x.isalpha() or x.isspace() for x in fork_name): | ||
print(f"Can't use {fork_name} as fork name, must be alphabetical") | ||
return | ||
|
||
print("Specify the fork base (e.g Istanbul):") | ||
fork_base = input(INPUT_PROMPT) | ||
|
||
fork_base_path = FORKS_BASE_PATH / fork_base | ||
if not fork_base_path.exists(): | ||
print(f"No fork exists at {fork_base_path}") | ||
return | ||
|
||
writing_new_fork = create_writing(fork_name) | ||
writing_parent_fork = create_writing(fork_base) | ||
|
||
print("Check your inputs:") | ||
print("New fork:") | ||
print(writing_new_fork) | ||
|
||
print("Base fork:") | ||
print(writing_parent_fork) | ||
|
||
print("Proceed (y/n)?") | ||
proceed = input(INPUT_PROMPT) | ||
|
||
if proceed.lower() == YES: | ||
create_fork(writing_new_fork, writing_parent_fork) | ||
print("Your fork is ready!") | ||
|
||
|
||
def create_writing(fork_name: str): | ||
# Remove extra spaces | ||
normalized = " ".join(fork_name.split()) | ||
|
||
snake_case = normalized.replace(' ', '_') | ||
dash_case = normalized.replace(' ', '-') | ||
pascal_case = normalized.title().replace(' ', '') | ||
|
||
return Writing( | ||
pascal_case=pascal_case, | ||
lower_dash_case=dash_case.lower(), | ||
lower_snake_case=snake_case.lower(), | ||
upper_snake_case=snake_case.upper(), | ||
) | ||
|
||
|
||
def create_fork(writing_new_fork: Writing, writing_parent_fork: Writing) -> None: | ||
fork_path = FORKS_BASE_PATH / writing_new_fork.lower_snake_case | ||
shutil.copytree(SCRIPT_TEMPLATE_PATH, fork_path) | ||
replace_in(fork_path, WHITELABEL_FORK.pascal_case, writing_new_fork.pascal_case) | ||
replace_in(fork_path, WHITELABEL_FORK.lower_snake_case, writing_new_fork.lower_snake_case) | ||
replace_in(fork_path, WHITELABEL_FORK.lower_dash_case, writing_new_fork.lower_dash_case) | ||
replace_in(fork_path, WHITELABEL_FORK.upper_snake_case, writing_new_fork.upper_snake_case) | ||
|
||
replace_in(fork_path, WHITELABEL_PARENT.pascal_case, writing_parent_fork.pascal_case) | ||
replace_in(fork_path, WHITELABEL_PARENT.lower_snake_case, writing_parent_fork.lower_snake_case) | ||
replace_in(fork_path, WHITELABEL_PARENT.lower_dash_case, writing_parent_fork.lower_dash_case) | ||
replace_in(fork_path, WHITELABEL_PARENT.upper_snake_case, writing_parent_fork.upper_snake_case) | ||
|
||
|
||
def replace_in(base_path: pathlib.Path, find_text: str, replace_txt: str) -> None: | ||
for filepath in glob.iglob(f'{base_path}/**/*.py', recursive=True): | ||
with open(filepath) as file: | ||
s = file.read() | ||
s = s.replace(find_text, replace_txt) | ||
with open(filepath, "w") as file: | ||
file.write(s) | ||
|
||
|
||
if __name__ == '__main__': | ||
bootstrap() |
This file contains 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,31 @@ | ||
from typing import ( | ||
Type, | ||
) | ||
|
||
from eth.rlp.blocks import BaseBlock | ||
from eth.vm.forks.constantinople import ( | ||
ConstantinopleVM, | ||
) | ||
from eth.vm.state import BaseState | ||
|
||
from .blocks import IstanbulBlock | ||
from .headers import ( | ||
compute_istanbul_difficulty, | ||
configure_istanbul_header, | ||
create_istanbul_header_from_parent, | ||
) | ||
from .state import IstanbulState | ||
|
||
|
||
class IstanbulVM(ConstantinopleVM): | ||
# fork name | ||
fork = 'istanbul' | ||
|
||
# classes | ||
block_class: Type[BaseBlock] = IstanbulBlock | ||
_state_class: Type[BaseState] = IstanbulState | ||
|
||
# Methods | ||
create_header_from_parent = staticmethod(create_istanbul_header_from_parent) # type: ignore | ||
compute_difficulty = staticmethod(compute_istanbul_difficulty) # type: ignore | ||
configure_header = configure_istanbul_header |
This file contains 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,22 @@ | ||
from rlp.sedes import ( | ||
CountableList, | ||
) | ||
from eth.rlp.headers import ( | ||
BlockHeader, | ||
) | ||
from eth.vm.forks.petersburg.blocks import ( | ||
PetersburgBlock, | ||
) | ||
|
||
from .transactions import ( | ||
IstanbulTransaction, | ||
) | ||
|
||
|
||
class IstanbulBlock(PetersburgBlock): | ||
transaction_class = IstanbulTransaction | ||
fields = [ | ||
('header', BlockHeader), | ||
('transactions', CountableList(transaction_class)), | ||
('uncles', CountableList(BlockHeader)) | ||
] |
This file contains 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,20 @@ | ||
from eth.vm.forks.petersburg.computation import ( | ||
PETERSBURG_PRECOMPILES | ||
) | ||
from eth.vm.forks.petersburg.computation import ( | ||
PetersburgComputation, | ||
) | ||
|
||
from .opcodes import ISTANBUL_OPCODES | ||
|
||
ISTANBUL_PRECOMPILES = PETERSBURG_PRECOMPILES | ||
|
||
|
||
class IstanbulComputation(PetersburgComputation): | ||
""" | ||
A class for all execution computations in the ``Istanbul`` fork. | ||
Inherits from :class:`~eth.vm.forks.petersburg.PetersburgComputation` | ||
""" | ||
# Override | ||
opcodes = ISTANBUL_OPCODES | ||
_precompiles = ISTANBUL_PRECOMPILES |
This file contains 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,13 @@ | ||
from eth.vm.forks.petersburg.headers import ( | ||
configure_header, | ||
create_header_from_parent, | ||
compute_petersburg_difficulty, | ||
) | ||
|
||
|
||
compute_istanbul_difficulty = compute_petersburg_difficulty | ||
|
||
create_istanbul_header_from_parent = create_header_from_parent( | ||
compute_istanbul_difficulty | ||
) | ||
configure_istanbul_header = configure_header(compute_istanbul_difficulty) |
This file contains 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,18 @@ | ||
import copy | ||
|
||
from eth_utils.toolz import merge | ||
|
||
|
||
from eth.vm.forks.petersburg.opcodes import ( | ||
PETERSBURG_OPCODES, | ||
) | ||
|
||
|
||
UPDATED_OPCODES = { | ||
# New opcodes | ||
} | ||
|
||
ISTANBUL_OPCODES = merge( | ||
copy.deepcopy(PETERSBURG_OPCODES), | ||
UPDATED_OPCODES, | ||
) |
This file contains 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,9 @@ | ||
from eth.vm.forks.petersburg.state import ( | ||
PetersburgState | ||
) | ||
|
||
from .computation import IstanbulComputation | ||
|
||
|
||
class IstanbulState(PetersburgState): | ||
computation_class = IstanbulComputation |
This file contains 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,42 @@ | ||
from eth_keys.datatypes import PrivateKey | ||
from eth_typing import Address | ||
|
||
from eth.vm.forks.petersburg.transactions import ( | ||
PetersburgTransaction, | ||
PetersburgUnsignedTransaction, | ||
) | ||
|
||
from eth._utils.transactions import ( | ||
create_transaction_signature, | ||
) | ||
|
||
|
||
class IstanbulTransaction(PetersburgTransaction): | ||
@classmethod | ||
def create_unsigned_transaction(cls, | ||
*, | ||
nonce: int, | ||
gas_price: int, | ||
gas: int, | ||
to: Address, | ||
value: int, | ||
data: bytes) -> 'IstanbulUnsignedTransaction': | ||
return IstanbulUnsignedTransaction(nonce, gas_price, gas, to, value, data) | ||
|
||
|
||
class IstanbulUnsignedTransaction(PetersburgUnsignedTransaction): | ||
def as_signed_transaction(self, | ||
private_key: PrivateKey, | ||
chain_id: int=None) -> IstanbulTransaction: | ||
v, r, s = create_transaction_signature(self, private_key, chain_id=chain_id) | ||
return IstanbulTransaction( | ||
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, | ||
) |