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

Refactored VM, VMState, and Block + new apply_transaction #247

Merged
merged 22 commits into from
Jan 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c305f2b
Refactored `Block` class
hwwhww Dec 25, 2017
c3bdbb9
Implement new DB wrapper - TrackedDB, see #204
hwwhww Dec 7, 2017
21c57f2
Refactored VM and VMState
hwwhww Dec 31, 2017
79076f4
Added VMState.apply_transaction(transaction): returns AccessLogs for …
hwwhww Dec 31, 2017
390f07d
Refactored VM and VMState
hwwhww Jan 2, 2018
1ae87cd
(Unfinished) Added `VM.create_block(...)` function to demonstrate sta…
hwwhww Jan 3, 2018
53fcbe2
Bugfix: `_is_stateless` flag should be set in Frontier
hwwhww Jan 3, 2018
15982e6
Pass trie data from VMState.apply_transaction to VM.apply_transaction
hwwhww Jan 3, 2018
1e5711f
Removed unused parameter + Bugfix and refactoring
hwwhww Jan 3, 2018
40290ef
Added VMState.prev_headers class instance
hwwhww Jan 4, 2018
c2b5568
Refactored
hwwhww Jan 4, 2018
b96c3d2
Fixed create_block and added more tests
hwwhww Jan 8, 2018
c775208
Refactored reward methods: moved from base.py to Frontier
hwwhww Jan 8, 2018
85960e5
Added ReStructuredText style docstring in apply_transaction and add_t…
hwwhww Jan 8, 2018
be63d4d
Change the mechanism of generating transaction root and receipts root
hwwhww Jan 8, 2018
4e5f31d
Only assign VMState._chaindb in __init__ + Mutable VMState
hwwhww Jan 9, 2018
73599e1
Passing trie_class to make_trie_root_and_nodes, it may be HexaryTrie …
hwwhww Jan 11, 2018
876c20d
Fixed generate_block_from_parent_header_and_coinbase timestamp
hwwhww Jan 11, 2018
1c9ae57
Moved VMState.get_parent_header(block_header, db) and VMState.get_pre…
hwwhww Jan 11, 2018
c58c8c8
Refactored VMState.get_ancestor_hash(block_number) and made VMState.g…
hwwhww Jan 11, 2018
4f0b493
Changed VMState.apply_transaction back to regular instance method
hwwhww Jan 11, 2018
2b6df98
Clean up
hwwhww Jan 12, 2018
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
5 changes: 3 additions & 2 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ For example:
highest_block_num = chain.get_canonical_head().block_number

block1 = chain.get_canonical_block_by_number(1)
assert block1.number() == 1
assert block1.number == 1

blockhash = block1.hash()
blockgas = block1.get_cumulative_gas_used()
vm = chain.get_vm()
blockgas = vm.get_cumulative_gas_used(block1)

The methods available on the block are variable. They depend on what fork you're on.
The mainnet follows "Frontier" rules at the beginning, then Homestead, and so on.
Expand Down
5 changes: 3 additions & 2 deletions evm/chains/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def get_block_by_hash(self, block_hash):

def get_block_by_header(self, block_header):
vm = self.get_vm(block_header)
return vm.get_block_by_header(block_header)
return vm.get_block_by_header(block_header, self.chaindb)

#
# Chain Initialization
Expand Down Expand Up @@ -238,7 +238,8 @@ def apply_transaction(self, transaction):
Apply the transaction to the current head block of the Chain.
"""
vm = self.get_vm()
return vm.apply_transaction(transaction)
computation, _ = vm.apply_transaction(transaction)
return computation

def import_block(self, block, perform_validation=True):
"""
Expand Down
27 changes: 13 additions & 14 deletions evm/computation.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,13 @@ class BaseComputation(object):
logs = None
accounts_to_delete = None

# VM configuration
opcodes = None
precompiles = None
_precompiles = None

logger = logging.getLogger('evm.vm.computation.Computation')

def __init__(self, vm_state, message, opcodes, precompiles):
def __init__(self, vm_state, message):
self.vm_state = vm_state
self.msg = message

Expand All @@ -91,9 +92,6 @@ def __init__(self, vm_state, message, opcodes, precompiles):
code = message.code
self.code = CodeStream(code)

self.opcodes = opcodes
self.precompiles = precompiles

#
# Convenience
#
Expand Down Expand Up @@ -203,27 +201,21 @@ def apply_child_computation(self, child_msg):
child_computation = self.generate_child_computation(
self.vm_state,
child_msg,
self.opcodes,
self.precompiles,
)
self.add_child_computation(child_computation)
return child_computation

@classmethod
def generate_child_computation(cls, vm_state, child_msg, opcodes, precompiles):
def generate_child_computation(cls, vm_state, child_msg):
if child_msg.is_create:
child_computation = cls(
vm_state,
child_msg,
opcodes,
precompiles,
).apply_create_message()
else:
child_computation = cls(
vm_state,
child_msg,
opcodes,
precompiles,
).apply_message()
return child_computation

Expand Down Expand Up @@ -378,11 +370,11 @@ def apply_create_message(self):
raise NotImplementedError("Must be implemented by subclasses")

@classmethod
def apply_computation(cls, vm_state, message, opcodes, precompiles):
def apply_computation(cls, vm_state, message):
"""
Perform the computation that would be triggered by the VM message.
"""
with cls(vm_state, message, opcodes, precompiles) as computation:
with cls(vm_state, message) as computation:
# Early exit on pre-compiles
if message.code_address in computation.precompiles:
computation.precompiles[message.code_address](computation)
Expand All @@ -407,6 +399,13 @@ def apply_computation(cls, vm_state, message, opcodes, precompiles):
#
# Opcode API
#
@property
def precompiles(self):
if self._precompiles is None:
return dict()
else:
return self._precompiles

def get_opcode_fn(self, opcodes, opcode):
try:
return opcodes[opcode]
Expand Down
5 changes: 5 additions & 0 deletions evm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,8 @@


GAS_MOD_EXP_QUADRATIC_DENOMINATOR = 20

#
# BLOCKHASH opcode maximum depth
#
MAX_PREV_HEADER_DEPTH = 256
7 changes: 5 additions & 2 deletions evm/db/backends/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
class MemoryDB(BaseDB):
kv_store = None

def __init__(self):
self.kv_store = {}
def __init__(self, kv_store=None):
if kv_store is None:
self.kv_store = {}
else:
self.kv_store = kv_store

def get(self, key):
return self.kv_store[key]
Expand Down
8 changes: 5 additions & 3 deletions evm/db/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from evm.db.immutable import (
ImmutableDB,
)
from evm.db.tracked import (
TrackedDB,
)
from evm.rlp.accounts import (
Account,
)
Expand All @@ -21,7 +24,6 @@
validate_uint256,
validate_canonical_address,
)

from evm.utils.keccak import (
keccak,
)
Expand All @@ -46,9 +48,9 @@ class AccountStateDB:

def __init__(self, db, root_hash=BLANK_ROOT_HASH, read_only=False):
if read_only:
self.db = ImmutableDB(db)
self.db = TrackedDB(ImmutableDB(db))
else:
self.db = db
self.db = TrackedDB(db)
self._trie = HashTrie(HexaryTrie(self.db, root_hash))

#
Expand Down
63 changes: 63 additions & 0 deletions evm/db/tracked.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from .backends.base import BaseDB


class AccessLogs(object):
reads = None
writes = None

def __init__(self, reads=None, writes=None):
if reads is None:
self.reads = {}
else:
self.reads = reads
if writes is None:
self.writes = {}
else:
self.writes = writes


class TrackedDB(BaseDB):
"""
The StateDB would be responsible for collecting all the touched keys.

access_logs.reads: the dict of read key-value
access_logs.writes: the dict of written key-value
"""

wrapped_db = None
access_logs = None

def __init__(self, db):
self.wrapped_db = db
self.access_logs = AccessLogs()

def get(self, key):
"""
Return the value of specific key and update read dict.
"""
current_value = self.wrapped_db.get(key)
self.access_logs.reads[key] = current_value
return current_value

def set(self, key, value):
"""
Set the key-value and update writes dict.
"""
self.access_logs.writes[key] = value
return self.wrapped_db.set(key, value)

def exists(self, key):
"""
Check if the key exsits.
"""
result = self.wrapped_db.exists(key)
self.access_logs.reads[key] = self.wrapped_db.get(key) if result else None
return result

def delete(self, key):
"""
Delete the key and update writes dict.
"""
result = self.wrapped_db.delete(key)
self.access_logs.writes[key] = None
return result
25 changes: 1 addition & 24 deletions evm/rlp/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@


class BaseBlock(rlp.Serializable):
db = None

@classmethod
def configure(cls, **overrides):
class_name = cls.__name__
Expand Down Expand Up @@ -31,18 +29,12 @@ def get_transaction_class(cls):
return cls.transaction_class

@classmethod
def from_header(cls, header, db):
def from_header(cls, header, chaindb):
"""
Returns the block denoted by the given block header.
"""
raise NotImplementedError("Must be implemented by subclasses")

def get_parent_header(self):
"""
Returns the header for the parent block.
"""
raise NotImplementedError("Must be implemented by subclasses")

@property
def hash(self):
raise NotImplementedError("Must be implemented by subclasses")
Expand All @@ -55,21 +47,6 @@ def number(self):
def is_genesis(self):
return self.number == 0

def validate(self):
pass

def add_transaction(self, transaction, computation):
"""
Adds the given transaction to the current block.
"""
raise NotImplementedError("Must be implemented by subclasses")

def mine(self, *args, **kwargs):
"""
Mines the block.
"""
raise NotImplementedError("Must be implemented by subclasses")

def __repr__(self):
return '<{class_name}(#{b})>'.format(
class_name=self.__class__.__name__,
Expand Down
14 changes: 14 additions & 0 deletions evm/utils/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,17 @@ def make_block_number_to_hash_lookup_key(block_number):

def make_block_hash_to_score_lookup_key(block_hash):
return b'block-hash-to-score:%s' % block_hash


def get_parent_header(block_header, db):
"""
Returns the header for the parent block.
"""
return db.get_block_header_by_hash(block_header.parent_hash)


def get_block_header_by_hash(block_hash, db):
"""
Returns the header for the parent block.
"""
return db.get_block_header_by_hash(block_hash)
2 changes: 1 addition & 1 deletion evm/utils/fixture_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ def apply_fixture_block_to_chain(block_fixture, chain):
else:
block_class = chain.get_vm().get_block_class()

block = rlp.decode(block_fixture['rlp'], sedes=block_class, chaindb=chain.chaindb)
block = rlp.decode(block_fixture['rlp'], sedes=block_class)

mined_block = chain.import_block(block)

Expand Down
42 changes: 42 additions & 0 deletions evm/utils/headers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import time

from evm.constants import (
GENESIS_GAS_LIMIT,
GAS_LIMIT_EMA_DENOMINATOR,
GAS_LIMIT_ADJUSTMENT_FACTOR,
GAS_LIMIT_MINIMUM,
GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR,
GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR,
)
from evm.rlp.headers import (
BlockHeader,
)


def compute_gas_limit_bounds(parent):
Expand Down Expand Up @@ -65,3 +71,39 @@ def compute_gas_limit(parent_header, gas_limit_floor):
return parent_header.gas_limit + decay
else:
return gas_limit


def generate_header_from_parent_header(
compute_difficulty_fn,
parent_header,
coinbase,
timestamp=None,
extra_data=b''):
"""
Generate BlockHeader from state_root and parent_header
"""
if timestamp is None:
timestamp = max(int(time.time()), parent_header.timestamp + 1)
elif timestamp <= parent_header.timestamp:
raise ValueError(
"header.timestamp ({}) should be higher than"
"parent_header.timestamp ({})".format(
timestamp,
parent_header.timestamp,
)
)
header = BlockHeader(
difficulty=compute_difficulty_fn(parent_header, timestamp),
block_number=(parent_header.block_number + 1),
gas_limit=compute_gas_limit(
parent_header,
gas_limit_floor=GENESIS_GAS_LIMIT,
),
timestamp=timestamp,
parent_hash=parent_header.hash,
state_root=parent_header.state_root,
coinbase=coinbase,
extra_data=extra_data,
)

return header
20 changes: 20 additions & 0 deletions evm/utils/state.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from eth_utils import (
to_tuple,
)
import rlp
from trie import (
HexaryTrie,
)

from evm.db.backends.memory import MemoryDB
from evm.db.chain import BaseChainDB


@to_tuple
Expand Down Expand Up @@ -30,3 +37,16 @@ def diff_state_db(expected_state, state_db):
actual_storage_value,
expected_storage_value,
)


# Make the root of a receipt tree
def make_trie_root_and_nodes(transactions, trie_class=HexaryTrie):
chaindb = BaseChainDB(MemoryDB())
db = chaindb.db
transaction_db = trie_class(db)

for index, transaction in enumerate(transactions):
index_key = rlp.encode(index, sedes=rlp.sedes.big_endian_int)
transaction_db[index_key] = rlp.encode(transaction)

return transaction_db.root_hash, transaction_db.db.wrapped_db.kv_store
Loading