Skip to content

Commit 8a97922

Browse files
committed
WIP
1 parent 7b94eb1 commit 8a97922

File tree

10 files changed

+350
-0
lines changed

10 files changed

+350
-0
lines changed

docs/guides/implementing_vm_forks.rst

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
Implementing VM forks
2+
=====================
3+
4+
The Ethereum protocol follows specified rules which continue to be improved through so called
5+
`Ethereum Improvement Proposals (EIPs) <https://eips.ethereum.org/>`_. Every now and then the
6+
community agrees on a few EIPs to become part of the next protocol upgrade. These upgrades happen
7+
through so called `Hardforks <https://en.wikipedia.org/wiki/Fork_(blockchain)>`_ which define:
8+
9+
1. A name for the set of rule changes (e.g. the Istanbul hardfork)
10+
2. A block number from which on blocks are processed according to these new rules (e.g. ``9069000``)
11+
12+
Every client that wants to support the official Ethereum protocol needs to implement these changes
13+
to remain functional.
14+
15+
16+
This guide covers how to implement new hardforks in Py-EVM. The specifics and impact of each rule
17+
change many vary a lot between different hardforks and it is out of the scope of this guide to
18+
cover these in depth. This is mainly a reference guide for developers to ensure the process of
19+
implementing hardforks in Py-EVM is as smooth and safe as possible.
20+
21+
22+
Creating the fork module
23+
------------------------
24+
25+
Every fork is encapsulated in its own module under ``eth.vm.forks.<fork-name>``. To create the
26+
scaffolding for a new fork run ``python scripts/forking/create_fork.py`` and follow the assistent.
27+
28+
.. code:: sh
29+
30+
$ python scripts/forking/create_fork.py
31+
Specify the name of the fork (e.g Muir Glacier):
32+
-->ancient tavira
33+
Specify the fork base (e.g Istanbul):
34+
-->istanbul
35+
Check your inputs:
36+
New fork:
37+
Writing(pascal_case='AncientTavira', lower_dash_case='ancient-tavira', lower_snake_case='ancient_tavira', upper_snake_case='ANCIENT_TAVIRA')
38+
Base fork:
39+
Writing(pascal_case='Istanbul', lower_dash_case='istanbul', lower_snake_case='istanbul', upper_snake_case='ISTANBUL')
40+
Proceed (y/n)?
41+
-->y
42+
Your fork is ready!
43+
44+
45+
Configuring new opcodes
46+
-----------------------
47+
48+
Configuring new precompiles
49+
---------------------------
50+
51+
Activating the fork
52+
-------------------
53+
54+
Ethereum is a protocol that powers different networks. Most notably, the ethereum mainnet but there
55+
are also other networks such as testnetworks (e.g. Görli) or xDai. If and when a specific network
56+
will activate a concrete fork remains to be configured on a per network basis.
57+
58+
At the time of writing, Py-EVM has supports the following three networks:
59+
60+
- Mainnet
61+
- Ropsten
62+
- Goerli
63+
64+
For each network that wants to activate the fork, we have to create a new constant in
65+
``eth/chains/<network>/constants.py`` that describes the block number at which the fork becomes
66+
active as seen in the following example:
67+
68+
.. literalinclude:: ../../eth/chains/mainnet/constants.py
69+
:language: python
70+
:start-after: BYZANTIUM_MAINNET_BLOCK
71+
:end-before: # Istanbul Block
72+
73+
Then,
74+
75+
76+
Wiring up the tests
77+
-------------------

docs/guides/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ This section aims to provide hands-on guides to demonstrate how to use Py-EVM. I
1212
architecture
1313
understanding_the_mining_process
1414
creating_opcodes
15+
implementing_vm_forks

scripts/forking/create_fork.py

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import glob
2+
from typing import NamedTuple
3+
import pathlib
4+
import shutil
5+
6+
SCRIPT_BASE_PATH = pathlib.Path(__file__).parent
7+
SCRIPT_TEMPLATE_PATH = SCRIPT_BASE_PATH / 'template' / 'whitelabel'
8+
ETH_BASE_PATH = SCRIPT_BASE_PATH.parent.parent / 'eth'
9+
FORKS_BASE_PATH = ETH_BASE_PATH / 'vm' / 'forks'
10+
11+
INPUT_PROMPT = '-->'
12+
YES = 'y'
13+
14+
# Given a fork name of Muir Glacier we need to derive:
15+
# pascal case: MuirGlacier
16+
# lower_dash_case: muir-glacier
17+
# lower_snake_case: muir_glacier
18+
# upper_snake_case: MUIR_GLACIER
19+
20+
21+
class Writing(NamedTuple):
22+
pascal_case: str
23+
lower_dash_case: str
24+
lower_snake_case: str
25+
upper_snake_case: str
26+
27+
28+
WHITELABEL_FORK = Writing(
29+
pascal_case="Istanbul",
30+
lower_dash_case="istanbul",
31+
lower_snake_case="istanbul",
32+
upper_snake_case="ISTANBUL",
33+
)
34+
35+
WHITELABEL_PARENT = Writing(
36+
pascal_case="Petersburg",
37+
lower_dash_case="petersburg",
38+
lower_snake_case="petersburg",
39+
upper_snake_case="PETERSBURG",
40+
)
41+
42+
43+
def bootstrap() -> None:
44+
print("Specify the name of the fork (e.g Muir Glacier):")
45+
fork_name = input(INPUT_PROMPT)
46+
47+
if not all(x.isalpha() or x.isspace() for x in fork_name):
48+
print(f"Can't use {fork_name} as fork name, must be alphabetical")
49+
return
50+
51+
print("Specify the fork base (e.g Istanbul):")
52+
fork_base = input(INPUT_PROMPT)
53+
54+
fork_base_path = FORKS_BASE_PATH / fork_base
55+
if not fork_base_path.exists():
56+
print(f"No fork exists at {fork_base_path}")
57+
return
58+
59+
writing_new_fork = create_writing(fork_name)
60+
writing_parent_fork = create_writing(fork_base)
61+
62+
print("Check your inputs:")
63+
print("New fork:")
64+
print(writing_new_fork)
65+
66+
print("Base fork:")
67+
print(writing_parent_fork)
68+
69+
print("Proceed (y/n)?")
70+
proceed = input(INPUT_PROMPT)
71+
72+
if proceed.lower() == YES:
73+
create_fork(writing_new_fork, writing_parent_fork)
74+
print("Your fork is ready!")
75+
76+
77+
def create_writing(fork_name: str):
78+
# Remove extra spaces
79+
normalized = " ".join(fork_name.split())
80+
81+
snake_case = normalized.replace(' ', '_')
82+
dash_case = normalized.replace(' ', '-')
83+
pascal_case = normalized.title().replace(' ', '')
84+
85+
return Writing(
86+
pascal_case=pascal_case,
87+
lower_dash_case=dash_case.lower(),
88+
lower_snake_case=snake_case.lower(),
89+
upper_snake_case=snake_case.upper(),
90+
)
91+
92+
93+
def create_fork(writing_new_fork: Writing, writing_parent_fork: Writing) -> None:
94+
fork_path = FORKS_BASE_PATH / writing_new_fork.lower_snake_case
95+
shutil.copytree(SCRIPT_TEMPLATE_PATH, fork_path)
96+
replace_in(fork_path, WHITELABEL_FORK.pascal_case, writing_new_fork.pascal_case)
97+
replace_in(fork_path, WHITELABEL_FORK.lower_snake_case, writing_new_fork.lower_snake_case)
98+
replace_in(fork_path, WHITELABEL_FORK.lower_dash_case, writing_new_fork.lower_dash_case)
99+
replace_in(fork_path, WHITELABEL_FORK.upper_snake_case, writing_new_fork.upper_snake_case)
100+
101+
replace_in(fork_path, WHITELABEL_PARENT.pascal_case, writing_parent_fork.pascal_case)
102+
replace_in(fork_path, WHITELABEL_PARENT.lower_snake_case, writing_parent_fork.lower_snake_case)
103+
replace_in(fork_path, WHITELABEL_PARENT.lower_dash_case, writing_parent_fork.lower_dash_case)
104+
replace_in(fork_path, WHITELABEL_PARENT.upper_snake_case, writing_parent_fork.upper_snake_case)
105+
106+
107+
def replace_in(base_path: pathlib.Path, find_text: str, replace_txt: str) -> None:
108+
for filepath in glob.iglob(f'{base_path}/**/*.py', recursive=True):
109+
with open(filepath) as file:
110+
s = file.read()
111+
s = s.replace(find_text, replace_txt)
112+
with open(filepath, "w") as file:
113+
file.write(s)
114+
115+
116+
if __name__ == '__main__':
117+
bootstrap()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import (
2+
Type,
3+
)
4+
5+
from eth.rlp.blocks import BaseBlock
6+
from eth.vm.forks.constantinople import (
7+
ConstantinopleVM,
8+
)
9+
from eth.vm.state import BaseState
10+
11+
from .blocks import IstanbulBlock
12+
from .headers import (
13+
compute_istanbul_difficulty,
14+
configure_istanbul_header,
15+
create_istanbul_header_from_parent,
16+
)
17+
from .state import IstanbulState
18+
19+
20+
class IstanbulVM(ConstantinopleVM):
21+
# fork name
22+
fork = 'istanbul'
23+
24+
# classes
25+
block_class: Type[BaseBlock] = IstanbulBlock
26+
_state_class: Type[BaseState] = IstanbulState
27+
28+
# Methods
29+
create_header_from_parent = staticmethod(create_istanbul_header_from_parent) # type: ignore
30+
compute_difficulty = staticmethod(compute_istanbul_difficulty) # type: ignore
31+
configure_header = configure_istanbul_header
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from rlp.sedes import (
2+
CountableList,
3+
)
4+
from eth.rlp.headers import (
5+
BlockHeader,
6+
)
7+
from eth.vm.forks.petersburg.blocks import (
8+
PetersburgBlock,
9+
)
10+
11+
from .transactions import (
12+
IstanbulTransaction,
13+
)
14+
15+
16+
class IstanbulBlock(PetersburgBlock):
17+
transaction_class = IstanbulTransaction
18+
fields = [
19+
('header', BlockHeader),
20+
('transactions', CountableList(transaction_class)),
21+
('uncles', CountableList(BlockHeader))
22+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from eth.vm.forks.petersburg.computation import (
2+
PETERSBURG_PRECOMPILES
3+
)
4+
from eth.vm.forks.petersburg.computation import (
5+
PetersburgComputation,
6+
)
7+
8+
from .opcodes import ISTANBUL_OPCODES
9+
10+
ISTANBUL_PRECOMPILES = PETERSBURG_PRECOMPILES
11+
12+
13+
class IstanbulComputation(PetersburgComputation):
14+
"""
15+
A class for all execution computations in the ``Istanbul`` fork.
16+
Inherits from :class:`~eth.vm.forks.petersburg.PetersburgComputation`
17+
"""
18+
# Override
19+
opcodes = ISTANBUL_OPCODES
20+
_precompiles = ISTANBUL_PRECOMPILES
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from eth.vm.forks.petersburg.headers import (
2+
configure_header,
3+
create_header_from_parent,
4+
compute_petersburg_difficulty,
5+
)
6+
7+
8+
compute_istanbul_difficulty = compute_petersburg_difficulty
9+
10+
create_istanbul_header_from_parent = create_header_from_parent(
11+
compute_istanbul_difficulty
12+
)
13+
configure_istanbul_header = configure_header(compute_istanbul_difficulty)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import copy
2+
3+
from eth_utils.toolz import merge
4+
5+
6+
from eth.vm.forks.petersburg.opcodes import (
7+
PETERSBURG_OPCODES,
8+
)
9+
10+
11+
UPDATED_OPCODES = {
12+
# New opcodes
13+
}
14+
15+
ISTANBUL_OPCODES = merge(
16+
copy.deepcopy(PETERSBURG_OPCODES),
17+
UPDATED_OPCODES,
18+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from eth.vm.forks.petersburg.state import (
2+
PetersburgState
3+
)
4+
5+
from .computation import IstanbulComputation
6+
7+
8+
class IstanbulState(PetersburgState):
9+
computation_class = IstanbulComputation
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from eth_keys.datatypes import PrivateKey
2+
from eth_typing import Address
3+
4+
from eth.vm.forks.petersburg.transactions import (
5+
PetersburgTransaction,
6+
PetersburgUnsignedTransaction,
7+
)
8+
9+
from eth._utils.transactions import (
10+
create_transaction_signature,
11+
)
12+
13+
14+
class IstanbulTransaction(PetersburgTransaction):
15+
@classmethod
16+
def create_unsigned_transaction(cls,
17+
*,
18+
nonce: int,
19+
gas_price: int,
20+
gas: int,
21+
to: Address,
22+
value: int,
23+
data: bytes) -> 'IstanbulUnsignedTransaction':
24+
return IstanbulUnsignedTransaction(nonce, gas_price, gas, to, value, data)
25+
26+
27+
class IstanbulUnsignedTransaction(PetersburgUnsignedTransaction):
28+
def as_signed_transaction(self,
29+
private_key: PrivateKey,
30+
chain_id: int=None) -> IstanbulTransaction:
31+
v, r, s = create_transaction_signature(self, private_key, chain_id=chain_id)
32+
return IstanbulTransaction(
33+
nonce=self.nonce,
34+
gas_price=self.gas_price,
35+
gas=self.gas,
36+
to=self.to,
37+
value=self.value,
38+
data=self.data,
39+
v=v,
40+
r=r,
41+
s=s,
42+
)

0 commit comments

Comments
 (0)