Skip to content
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

Accept genesis state and parameter overrides #123

Merged
merged 17 commits into from
Sep 20, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 14 additions & 0 deletions eth_tester/backends/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from cytoolz.dicttoolz import merge


def merge_genesis_overrides(defaults, overrides):
allowed_fields = set(defaults.keys())
override_fields = set(overrides.keys())
unexpected_fields = tuple(sorted(override_fields.difference(allowed_fields)))

if unexpected_fields:
err = "The following invalid fields were supplied to override default genesis values: {0}."
raise ValueError(err.format(unexpected_fields))

merged_params = merge(defaults, overrides)
return merged_params
11 changes: 9 additions & 2 deletions eth_tester/backends/mock/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
to_tuple,
)

from eth_tester.backends.common import merge_genesis_overrides
from eth_tester.utils.accounts import (
generate_contract_address,
)
Expand Down Expand Up @@ -236,8 +237,8 @@ def make_receipt(transaction, block, transaction_index, overrides=None):
EMPTY_UNCLE_HASH = b'\x1d\xccM\xe8\xde\xc7]z\xab\x85\xb5g\xb6\xcc\xd4\x1a\xd3\x12E\x1b\x94\x8at\x13\xf0\xa1B\xfd@\xd4\x93G' # noqa: E501


def make_genesis_block():
return {
def make_genesis_block(overrides=None):
default_genesis_block = {
"number": 0,
"hash": ZERO_32BYTES,
"parent_hash": ZERO_32BYTES,
Expand All @@ -258,6 +259,12 @@ def make_genesis_block():
"transactions": [],
"uncles": [],
}
if overrides is not None:
genesis_block = merge_genesis_overrides(defaults=default_genesis_block,
overrides=overrides)
else:
genesis_block = default_genesis_block
return genesis_block


@add_hash
Expand Down
75 changes: 55 additions & 20 deletions eth_tester/backends/pyevm/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from eth_tester.utils.formatting import (
replace_exceptions,
)
from eth_tester.backends.common import merge_genesis_overrides

from .serializers import (
serialize_block,
Expand Down Expand Up @@ -99,34 +100,40 @@
GAS_ESTIMATE_BUFFER = 1.5


def get_default_account_state():
return {
def get_default_account_state(overrides=None):
default_account_state = {
'balance': to_wei(1000000, 'ether'),
'storage': {},
'code': b'',
'nonce': 0,
}
if overrides is not None:
account_state = merge_genesis_overrides(defaults=default_account_state,
overrides=overrides)
else:
account_state = default_account_state
return account_state


@to_tuple
def get_default_account_keys():
def get_default_account_keys(quantity=None):
keys = KeyAPI()

for i in range(1, 11):
quantity = quantity or 10
for i in range(1, quantity+1):
pk_bytes = int_to_big_endian(i).rjust(32, b'\x00')
private_key = keys.PrivateKey(pk_bytes)
yield private_key


@to_dict
def generate_genesis_state(account_keys):
def generate_genesis_state_for_keys(account_keys, overrides=None):
for private_key in account_keys:
account_state = get_default_account_state()
account_state = get_default_account_state(overrides=overrides)
yield private_key.public_key.to_canonical_address(), account_state


def get_default_genesis_params():
genesis_params = {
def get_default_genesis_params(overrides=None):
default_genesis_params = {
"bloom": 0,
"coinbase": GENESIS_COINBASE,
"difficulty": GENESIS_DIFFICULTY,
Expand All @@ -142,10 +149,14 @@ def get_default_genesis_params():
"transaction_root": BLANK_ROOT_HASH,
"uncles_hash": EMPTY_RLP_LIST_HASH
}
if overrides is not None:
genesis_params = merge_genesis_overrides(default_genesis_params, overrides=overrides)
else:
genesis_params = default_genesis_params
return genesis_params


def setup_tester_chain():
def setup_tester_chain(genesis_params=None, genesis_state=None, num_accounts=None):
from eth.chains.base import MiningChain
from eth.db import get_db_backend
from eth.vm.forks.byzantium import ByzantiumVM
Expand All @@ -164,9 +175,16 @@ class MainnetTesterNoProofChain(MiningChain):
def validate_seal(cls, block):
pass

genesis_params = get_default_genesis_params()
account_keys = get_default_account_keys()
genesis_state = generate_genesis_state(account_keys)
if genesis_params is None:
genesis_params = get_default_genesis_params()

if genesis_state:
num_accounts = len(genesis_state)

account_keys = get_default_account_keys(quantity=num_accounts)

if genesis_state is None:
genesis_state = generate_genesis_state_for_keys(account_keys)

base_db = get_db_backend()

Expand Down Expand Up @@ -197,11 +215,11 @@ def _get_block_by_hash(chain, block_hash):
block = chain.get_block_by_hash(block_hash)

if block.number >= chain.get_block().number:
raise BlockNotFound("No block fuond for block hash: {0}".format(block_hash))
raise BlockNotFound("No block found for block hash: {0}".format(block_hash))

block_at_height = chain.get_canonical_block_by_number(block.number)
if block != block_at_height:
raise BlockNotFound("No block fuond for block hash: {0}".format(block_hash))
raise BlockNotFound("No block found for block hash: {0}".format(block_hash))

return block

Expand Down Expand Up @@ -278,7 +296,7 @@ class PyEVMBackend(object):
chain = None
fork_config = None

def __init__(self):
def __init__(self, genesis_parameters=None, genesis_state=None):
self.fork_config = {}

if not is_pyevm_available():
Expand All @@ -287,7 +305,27 @@ def __init__(self):
"`PyEVMBackend` requires py-evm to be installed and importable. "
"Please install the `py-evm` library."
)
self.reset_to_genesis()

self.account_keys = None # set below
accounts = len(genesis_state) if genesis_state else None
self.reset_to_genesis(genesis_parameters, genesis_state, accounts)

#
# Genesis
#

@staticmethod
def _generate_genesis_params(overrides=None):
return get_default_genesis_params(overrides=overrides)

@staticmethod
def _generate_genesis_state(overrides=None, num_accounts=None):
account_keys = get_default_account_keys(quantity=num_accounts)
return generate_genesis_state_for_keys(account_keys=account_keys, overrides=overrides)

def reset_to_genesis(self, genesis_params=None, genesis_state=None, num_accounts=None):
self.account_keys, self.chain = setup_tester_chain(genesis_params, genesis_state,
num_accounts)

#
# Private Accounts API
Expand Down Expand Up @@ -316,9 +354,6 @@ def revert_to_snapshot(self, snapshot):
self.chain.chaindb._set_as_canonical_chain_head(block.header)
self.chain = self.chain.from_genesis_header(self.chain.chaindb.db, block.header)

def reset_to_genesis(self):
self.account_keys, self.chain = setup_tester_chain()

#
# Meta
#
Expand Down
105 changes: 103 additions & 2 deletions tests/backends/test_pyevm.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from __future__ import unicode_literals

import pytest
from eth_utils import to_wei

from eth_tester import (
EthereumTester,
PyEVMBackend,
)

from eth_tester.backends.pyevm.main import (
generate_genesis_state_for_keys,
get_default_account_keys,
get_default_genesis_params
)
from eth_tester.backends.pyevm.utils import (
is_pyevm_available,
)
Expand All @@ -24,4 +29,100 @@ def eth_tester():


class TestPyEVMBackendDirect(BaseTestBackendDirect):
pass

def test_generate_custom_genesis_state(self):
state_overrides = {'balance': to_wei(900000, 'ether')}
invalid_overrides = {'gato': 'con botas'}

# Test creating a specific number of accounts
account_keys = get_default_account_keys(quantity=2)
assert len(account_keys) == 2
account_keys = get_default_account_keys(quantity=10)
assert len(account_keys) == 10

# Test the underlying state merging functionality
genesis_state = generate_genesis_state_for_keys(account_keys=account_keys, overrides=state_overrides)
assert len(genesis_state) == len(account_keys) == 10
for _public_address, account_state in genesis_state.items():
assert account_state['balance'] == state_overrides['balance']
assert account_state['code'] == b''

# Only existing default genesis state keys can be overridden
with pytest.raises(ValueError):
_invalid_genesis_state = generate_genesis_state_for_keys(account_keys=account_keys,
overrides=invalid_overrides)

# Use staticmethod state overriding
genesis_state = PyEVMBackend._generate_genesis_state(overrides=state_overrides, num_accounts=3)
assert len(genesis_state) == 3
for _public_address, account_state in genesis_state.items():
assert account_state['balance'] == state_overrides['balance']
assert account_state['code'] == b''

# Only existing default genesis state keys can be overridden
with pytest.raises(ValueError):
_invalid_genesis_state = PyEVMBackend._generate_genesis_state(overrides=invalid_overrides)

def test_override_genesis_state(self):
state_overrides = {'balance': to_wei(900000, 'ether')}
test_accounts = 3

# Initialize PyEVM backend with custom genesis state
genesis_state = PyEVMBackend._generate_genesis_state(overrides=state_overrides,
num_accounts=test_accounts)

# Test the correct number of accounts are created with the specified balance override
pyevm_backend = PyEVMBackend(genesis_state=genesis_state)
assert len(pyevm_backend.account_keys) == test_accounts
for private_key in pyevm_backend.account_keys:
account = private_key.public_key.to_canonical_address()
balance = pyevm_backend.get_balance(account=account)
assert balance == state_overrides['balance']

# Test integration with EthereumTester
tester = EthereumTester(backend=pyevm_backend)
for private_key in pyevm_backend.account_keys:
account = private_key.public_key.to_checksum_address()
balance = tester.get_balance(account=account)
assert balance == state_overrides['balance']

def test_generate_custom_genesis_parameters(self):

# Establish parameter overrides, for example a custom genesis gas limit
param_overrides = {'gas_limit': 4750000}

# Test the underlying default parameter merging functionality
genesis_params = get_default_genesis_params(overrides=param_overrides)
assert genesis_params['block_number'] == 0
assert genesis_params['gas_limit'] == param_overrides['gas_limit']

# Use the the staticmethod to generate custom genesis parameters
genesis_params = PyEVMBackend._generate_genesis_params(param_overrides)
assert genesis_params['block_number'] == 0
assert genesis_params['gas_limit'] == param_overrides['gas_limit']

# Only existing default genesis parameter keys can be overridden
invalid_overrides = {'gato': 'con botas'}
with pytest.raises(ValueError):
_invalid_genesis_params = PyEVMBackend._generate_genesis_params(overrides=invalid_overrides)

def test_override_genesis_parameters(self):

# Establish a custom gas limit
param_overrides = {'gas_limit': 4750000}
block_one_gas_limit = 4745362

# Initialize PyEVM backend with custom genesis parameters
genesis_params = PyEVMBackend._generate_genesis_params(overrides=param_overrides)
pyevm_backend = PyEVMBackend(genesis_parameters=genesis_params)
genesis_block = pyevm_backend.get_block_by_number(0)
assert genesis_block['gas_limit'] == param_overrides['gas_limit']
genesis_block = pyevm_backend.get_block_by_number(1)
assert genesis_block['gas_limit'] == block_one_gas_limit

# Integrate with EthereumTester
tester = EthereumTester(backend=pyevm_backend)
genesis_block = tester.get_block_by_number(0)
assert genesis_block['gas_limit'] == param_overrides['gas_limit']
genesis_block = tester.get_block_by_number(1)
assert genesis_block['gas_limit'] == block_one_gas_limit