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

Implementing VMState and Refactoring Computation #244

Merged
merged 13 commits into from
Dec 29, 2017
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
135 changes: 116 additions & 19 deletions evm/vm/computation.py → evm/computation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,36 @@
GAS_MEMORY_QUADRATIC_DENOMINATOR,
)
from evm.exceptions import (
Halt,
VMError,
)
from evm.validation import (
validate_canonical_address,
validate_uint256,
validate_is_bytes,
from evm.logic.invalid import (
InvalidOpcode,
)

from evm.utils.hexadecimal import (
encode_hex,
)
from evm.utils.numeric import (
ceil32,
)

from .code_stream import (
from evm.validation import (
validate_canonical_address,
validate_is_bytes,
validate_uint256,
)
from evm.vm.code_stream import (
CodeStream,
)
from .gas_meter import (
from evm.vm.gas_meter import (
GasMeter,
)
from .memory import (
from evm.vm.memory import (
Memory,
)
from .message import (
from evm.vm.message import (
Message,
)
from .stack import (
from evm.vm.stack import (
Stack,
)

Expand All @@ -47,11 +49,11 @@ def memory_gas_cost(size_in_bytes):
return total_cost


class Computation(object):
class BaseComputation(object):
"""
The execution computation
"""
vm = None
vm_state = None
msg = None

memory = None
Expand All @@ -69,10 +71,13 @@ class Computation(object):
logs = None
accounts_to_delete = None

opcodes = None
precompiles = None

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

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

self.memory = Memory()
Expand All @@ -86,6 +91,9 @@ def __init__(self, vm, message):
code = message.code
self.code = CodeStream(code)

self.opcodes = opcodes
self.precompiles = precompiles

#
# Convenience
#
Expand Down Expand Up @@ -192,12 +200,31 @@ def output(self, value):
# Runtime operations
#
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):
if child_msg.is_create:
child_computation = self.vm.apply_create_message(child_msg)
child_computation = cls(
vm_state,
child_msg,
opcodes,
precompiles,
).apply_create_message()
else:
child_computation = self.vm.apply_message(child_msg)

self.add_child_computation(child_computation)
child_computation = cls(
vm_state,
child_msg,
opcodes,
precompiles,
).apply_message()
return child_computation

def add_child_computation(self, child_computation):
Expand Down Expand Up @@ -334,3 +361,73 @@ def __exit__(self, exc_type, exc_value, traceback):
self.msg.gas - self.gas_meter.gas_remaining,
self.gas_meter.gas_remaining,
)

#
# State Transition
#
def apply_message(self):
"""
Execution of an VM message.
"""
raise NotImplementedError("Must be implemented by subclasses")

def apply_create_message(self):
"""
Execution of an VM message to create a new contract.
"""
raise NotImplementedError("Must be implemented by subclasses")

@classmethod
def apply_computation(cls, vm_state, message, opcodes, precompiles):
"""
Perform the computation that would be triggered by the VM message.
"""
with cls(vm_state, message, opcodes, precompiles) as computation:
# Early exit on pre-compiles
if message.code_address in computation.precompiles:
computation.precompiles[message.code_address](computation)
return computation

for opcode in computation.code:
opcode_fn = computation.get_opcode_fn(computation.opcodes, opcode)

computation.logger.trace(
"OPCODE: 0x%x (%s) | pc: %s",
opcode,
opcode_fn.mnemonic,
max(0, computation.code.pc - 1),
)

try:
opcode_fn(computation=computation)
except Halt:
break
return computation

#
# Opcode API
#
def get_opcode_fn(self, opcodes, opcode):
try:
return opcodes[opcode]
except KeyError:
return InvalidOpcode(opcode)

#
# classmethod
#
@classmethod
def configure(cls,
name,
**overrides):
"""
Class factory method for simple inline subclassing.
"""
for key in overrides:
if not hasattr(cls, key):
raise TypeError(
"The Computation.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)
)
return type(name, (cls,), overrides)
4 changes: 2 additions & 2 deletions evm/db/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
JournalDB,
)
from evm.db.state import (
State,
AccountStateDB,
)
from evm.rlp.headers import (
BlockHeader,
Expand Down Expand Up @@ -243,4 +243,4 @@ def clear(self):
self.db.clear()

def get_state_db(self, state_root, read_only):
return State(db=self.db, root_hash=state_root, read_only=read_only)
return AccountStateDB(db=self.db, root_hash=state_root, read_only=read_only)
2 changes: 1 addition & 1 deletion evm/db/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from .hash_trie import HashTrie


class State:
class AccountStateDB:
"""
High level API around account storage.
"""
Expand Down
12 changes: 6 additions & 6 deletions evm/logic/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@
def blockhash(computation):
block_number = computation.stack.pop(type_hint=constants.UINT256)

block_hash = computation.vm.get_ancestor_hash(block_number)
block_hash = computation.vm_state.get_ancestor_hash(block_number)

computation.stack.push(block_hash)


def coinbase(computation):
computation.stack.push(computation.vm.block.header.coinbase)
computation.stack.push(computation.vm_state.coinbase)


def timestamp(computation):
computation.stack.push(computation.vm.block.header.timestamp)
computation.stack.push(computation.vm_state.timestamp)


def number(computation):
computation.stack.push(computation.vm.block.header.block_number)
computation.stack.push(computation.vm_state.block_number)


def difficulty(computation):
computation.stack.push(computation.vm.block.header.difficulty)
computation.stack.push(computation.vm_state.difficulty)


def gaslimit(computation):
computation.stack.push(computation.vm.block.header.gas_limit)
computation.stack.push(computation.vm_state.gas_limit)
8 changes: 4 additions & 4 deletions evm/logic/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __call__(self, computation):
computation.gas_meter.consume_gas(child_msg_gas_fee, reason=self.mnemonic)

# Pre-call checks
with computation.vm.state_db(read_only=True) as state_db:
with computation.vm_state.state_db(read_only=True) as state_db:
sender_balance = state_db.get_balance(computation.msg.storage_address)
insufficient_funds = should_transfer_value and sender_balance < value
stack_too_deep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT
Expand All @@ -83,7 +83,7 @@ def __call__(self, computation):
computation.gas_meter.return_gas(child_msg_gas)
computation.stack.push(0)
else:
with computation.vm.state_db(read_only=True) as state_db:
with computation.vm_state.state_db(read_only=True) as state_db:
if code_address:
code = state_db.get_code(code_address)
else:
Expand Down Expand Up @@ -125,7 +125,7 @@ def __call__(self, computation):

class Call(BaseCall):
def compute_msg_extra_gas(self, computation, gas, to, value):
with computation.vm.state_db(read_only=True) as state_db:
with computation.vm_state.state_db(read_only=True) as state_db:
account_exists = state_db.account_exists(to)
transfer_gas_fee = constants.GAS_CALLVALUE if value else 0
create_gas_fee = constants.GAS_NEWACCOUNT if not account_exists else 0
Expand Down Expand Up @@ -282,7 +282,7 @@ def compute_eip150_msg_gas(computation, gas, extra_gas, value, mnemonic, callsti
#
class CallEIP161(CallEIP150):
def compute_msg_extra_gas(self, computation, gas, to, value):
with computation.vm.state_db(read_only=True) as state_db:
with computation.vm_state.state_db(read_only=True) as state_db:
account_is_dead = (
not state_db.account_exists(to) or
state_db.account_is_empty(to)
Expand Down
6 changes: 3 additions & 3 deletions evm/logic/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

def balance(computation):
addr = force_bytes_to_address(computation.stack.pop(type_hint=constants.BYTES))
with computation.vm.state_db(read_only=True) as state_db:
with computation.vm_state.state_db(read_only=True) as state_db:
balance = state_db.get_balance(addr)
computation.stack.push(balance)

Expand Down Expand Up @@ -111,7 +111,7 @@ def gasprice(computation):

def extcodesize(computation):
account = force_bytes_to_address(computation.stack.pop(type_hint=constants.BYTES))
with computation.vm.state_db(read_only=True) as state_db:
with computation.vm_state.state_db(read_only=True) as state_db:
code_size = len(state_db.get_code(account))

computation.stack.push(code_size)
Expand All @@ -135,7 +135,7 @@ def extcodecopy(computation):
reason='EXTCODECOPY: word gas cost',
)

with computation.vm.state_db(read_only=True) as state_db:
with computation.vm_state.state_db(read_only=True) as state_db:
code = state_db.get_code(account)
code_bytes = code[code_start_position:code_start_position + size]
padded_code_bytes = pad_right(code_bytes, size, b'\x00')
Expand Down
6 changes: 3 additions & 3 deletions evm/logic/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
def sstore(computation):
slot, value = computation.stack.pop(num_items=2, type_hint=constants.UINT256)

with computation.vm.state_db(read_only=True) as state_db:
with computation.vm_state.state_db(read_only=True) as state_db:
current_value = state_db.get_storage(
address=computation.msg.storage_address,
slot=slot,
Expand Down Expand Up @@ -43,7 +43,7 @@ def sstore(computation):
if gas_refund:
computation.gas_meter.refund_gas(gas_refund)

with computation.vm.state_db() as state_db:
with computation.vm_state.state_db() as state_db:
state_db.set_storage(
address=computation.msg.storage_address,
slot=slot,
Expand All @@ -54,7 +54,7 @@ def sstore(computation):
def sload(computation):
slot = computation.stack.pop(type_hint=constants.UINT256)

with computation.vm.state_db(read_only=True) as state_db:
with computation.vm_state.state_db(read_only=True) as state_db:
value = state_db.get_storage(
address=computation.msg.storage_address,
slot=slot,
Expand Down
Loading