Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ clean-pyc:

lint:
flake8 evm
flake8 tests --exclude=""

test:
py.test --tb native tests
Expand Down
61 changes: 44 additions & 17 deletions evm/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from operator import itemgetter

from eth_utils import (
pad_right,
to_tuple,
)
from evm.consensus.pow import (
Expand Down Expand Up @@ -41,6 +42,7 @@
from evm.utils.hexidecimal import (
encode_hex,
)
from evm.utils.rlp import diff_rlp_object

from evm.state import State

Expand All @@ -67,26 +69,29 @@ def __init__(self, db, header):
self.header = header

@classmethod
def configure(cls, name=None, vm_configuration=None):
if vm_configuration is None:
vms_by_range = cls.vms_by_range
else:
# Organize the Chain classes by their starting blocks.
validate_vm_block_numbers(tuple(
block_number
for block_number, _
in vm_configuration
))
def configure(cls, name, vm_configuration, **overrides):
if 'vms_by_range' in overrides:
raise ValueError("Cannot override vms_by_range.")

for key in overrides:
if not hasattr(cls, key):
raise TypeError(
"The Chain.configure cannot set attributes that are not "
"already present on the base class. The attribute `{0}` was "
"not found on the base class `{1}`".format(key, cls)
)

vms_by_range = collections.OrderedDict(sorted(vm_configuration, key=itemgetter(0)))
validate_vm_block_numbers(tuple(
block_number
for block_number, _
in vm_configuration
))

if name is None:
name = cls.__name__
# Organize the Chain classes by their starting blocks.
overrides['vms_by_range'] = collections.OrderedDict(
sorted(vm_configuration, key=itemgetter(0)))

props = {
'vms_by_range': vms_by_range,
}
return type(name, (cls,), props)
return type(name, (cls,), overrides)

#
# Convenience and Helpers
Expand Down Expand Up @@ -238,6 +243,7 @@ def import_block(self, block):

parent_chain = self.get_parent_chain(block)
imported_block = parent_chain.get_vm().import_block(block)
self.ensure_blocks_are_equal(imported_block, block)
# It feels wrong to call validate_block() on self here, but we do that
# because we want to look up the recent uncles starting from the
# current canonical chain head.
Expand All @@ -249,6 +255,27 @@ def import_block(self, block):

return imported_block

def ensure_blocks_are_equal(self, block1, block2):
if block1 == block2:
return
diff = diff_rlp_object(block1, block2)
longest_field_name = max(len(field_name) for field_name, _, _ in diff)
error_message = (
"Mismatch between block and imported block on {0} fields:\n - {1}".format(
len(diff),
"\n - ".join(tuple(
"{0}:\n (actual) : {1}\n (expected): {2}".format(
pad_right(field_name, longest_field_name, ' '),
actual,
expected,
)
for field_name, actual, expected
in diff
)),
)
)
raise ValidationError(error_message)

def get_parent_chain(self, block):
try:
parent_header = self.get_block_header_by_hash(
Expand Down
2 changes: 1 addition & 1 deletion evm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
GENESIS_COINBASE = ZERO_ADDRESS
GENESIS_NONCE = b'\x00\x00\x00\x00\x00\x00\x00*' # 42 encoded as big-endian-integer
GENESIS_MIX_HASH = ZERO_HASH32
GENESIS_EXTRA_DATA = b'',
GENESIS_EXTRA_DATA = b''
GENESIS_INITIAL_ALLOC = {}


Expand Down
35 changes: 3 additions & 32 deletions evm/vm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,11 @@

import logging

from eth_utils import (
pad_right,
)

from evm.constants import (
BLOCK_REWARD,
NEPHEW_REWARD,
UNCLE_DEPTH_PENALTY_FACTOR,
)
from evm.exceptions import (
ValidationError,
)
from evm.logic.invalid import (
InvalidOpcode,
)
Expand All @@ -24,9 +17,6 @@
from evm.utils.blocks import (
get_block_header_by_hash,
)
from evm.utils.rlp import (
diff_rlp_object,
)


class VM(object):
Expand Down Expand Up @@ -95,7 +85,8 @@ def apply_transaction(self, transaction):
Apply the transaction to the vm in the current block.
"""
computation = self.execute_transaction(transaction)
# NOTE: mutation
# NOTE: mutation. Needed in order to update self.state_db, so we should be able to get rid
# of this once we fix https://github.com/pipermerriam/py-evm/issues/67
self.block = self.block.add_transaction(
transaction=transaction,
computation=computation,
Expand Down Expand Up @@ -151,27 +142,7 @@ def import_block(self, block):
for uncle in block.uncles:
self.block.add_uncle(uncle)

mined_block = self.mine_block()
if mined_block != block:
diff = diff_rlp_object(mined_block, block)
longest_field_name = max(len(field_name) for field_name, _, _ in diff)
error_message = (
"Mismatch between block and imported block on {0} fields:\n - {1}".format(
len(diff),
"\n - ".join(tuple(
"{0}:\n (actual) : {1}\n (expected): {2}".format(
pad_right(field_name, longest_field_name, ' '),
actual,
expected,
)
for field_name, actual, expected
in diff
)),
)
)
raise ValidationError(error_message)

return mined_block
return self.mine_block()

def mine_block(self, *args, **kwargs):
"""
Expand Down
3 changes: 3 additions & 0 deletions evm/vm/flavors/frontier/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ def add_uncle(self, uncle):
self.header.uncles_hash = keccak(rlp.encode(self.uncles))
return self

# TODO: Check with Piper what's the use case for having the mine() method allowing callsites
# to override header attributes, since the only place it's used we don't pass any kwarg and
# hence it just performs block-level validation.
def mine(self, **kwargs):
"""
- `uncles_hash`
Expand Down
42 changes: 42 additions & 0 deletions tests/core/chain-object/test_chain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import rlp

from eth_utils import decode_hex

from evm import constants
from evm.vm.flavors.frontier.blocks import FrontierBlock

from tests.core.fixtures import ( # noqa: F401
chain,
chain_without_block_validation,
valid_block_rlp,
)
from tests.core.helpers import new_transaction


def test_import_block_validation(chain): # noqa: F811
block = rlp.decode(valid_block_rlp, sedes=FrontierBlock, db=chain.db)
imported_block = chain.import_block(block)
assert len(imported_block.transactions) == 1
tx = imported_block.transactions[0]
assert tx.value == 10
vm = chain.get_vm()
assert vm.state_db.get_balance(
decode_hex("095e7baea6a6c7c4c2dfeb977efac326af552d87")) == tx.value
tx_gas = tx.gas_price * constants.GAS_TX
assert vm.state_db.get_balance(chain.funded_address) == (
chain.funded_address_initial_balance - tx.value - tx_gas)


def test_import_block(chain_without_block_validation): # noqa: F811
chain = chain_without_block_validation # noqa: F811
recipient = decode_hex('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c')
amount = 100
vm = chain.get_vm()
from_ = chain.funded_address
tx = new_transaction(vm, from_, recipient, amount, chain.funded_address_private_key)
computation = vm.apply_transaction(tx)
assert computation.error is None
block = chain.import_block(vm.block)
assert block.transactions == [tx]
assert chain.get_block_by_hash(block.hash) == block
assert chain.get_canonical_block_by_number(block.number) == block
9 changes: 5 additions & 4 deletions tests/core/chain-object/test_chain_retrieval_of_vm_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

def test_get_vm_class_for_block_number():
chain_class = Chain.configure(
name='TestChain',
vm_configuration=(
(constants.GENESIS_BLOCK_NUMBER, FrontierVM),
(constants.HOMESTEAD_MAINNET_BLOCK, HomesteadVM),
Expand All @@ -38,13 +39,13 @@ def test_get_vm_class_for_block_number():


def test_invalid_if_no_vm_configuration():
chain_class = Chain.configure(vm_configuration=())
chain_class = Chain.configure('TestChain', vm_configuration=())
with pytest.raises(ValueError):
chain_class(get_db_backend(), BlockHeader(1, 0, 100))


def test_vm_not_found_if_no_matching_block_number():
chain_class = Chain.configure(vm_configuration=(
chain_class = Chain.configure('TestChain', vm_configuration=(
(10, FrontierVM),
))
chain = chain_class(get_db_backend(), BlockHeader(1, 0, 100))
Expand All @@ -54,12 +55,12 @@ def test_vm_not_found_if_no_matching_block_number():

def test_configure_invalid_block_number_in_vm_configuration():
with pytest.raises(ValidationError):
Chain.configure(vm_configuration=[(-1, FrontierVM)])
Chain.configure('TestChain', vm_configuration=[(-1, FrontierVM)])


def test_configure_duplicate_block_numbers_in_vm_configuration():
with pytest.raises(ValidationError):
Chain.configure(vm_configuration=[
Chain.configure('TestChain', vm_configuration=[
(0, FrontierVM),
(0, HomesteadVM),
])
Loading