Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Dec 17, 2019
1 parent 7b94eb1 commit 799ae30
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 0 deletions.
55 changes: 55 additions & 0 deletions docs/guides/implementing_vm_forks.rst
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
-------------------
1 change: 1 addition & 0 deletions docs/guides/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ This section aims to provide hands-on guides to demonstrate how to use Py-EVM. I
architecture
understanding_the_mining_process
creating_opcodes
implementing_vm_forks
117 changes: 117 additions & 0 deletions scripts/forking/create_fork.py
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()
31 changes: 31 additions & 0 deletions scripts/forking/template/whitelabel/__init__.py
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
22 changes: 22 additions & 0 deletions scripts/forking/template/whitelabel/blocks.py
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))
]
20 changes: 20 additions & 0 deletions scripts/forking/template/whitelabel/computation.py
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
13 changes: 13 additions & 0 deletions scripts/forking/template/whitelabel/headers.py
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)
18 changes: 18 additions & 0 deletions scripts/forking/template/whitelabel/opcodes.py
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,
)
9 changes: 9 additions & 0 deletions scripts/forking/template/whitelabel/state.py
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
42 changes: 42 additions & 0 deletions scripts/forking/template/whitelabel/transactions.py
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,
)

0 comments on commit 799ae30

Please sign in to comment.