Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.
Closed
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
40 changes: 28 additions & 12 deletions eth/vm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,15 @@
validate_length_lte,
validate_gas_limit,
)
from eth.vm.computation import BaseComputation
from eth.vm.message import (
Message,
)
from eth.vm.state import BaseState
from eth.vm.computation import BaseComputation
from eth.vm.tracing import (
BaseTracer,
NoopTracer,
)


class BaseVM(Configurable, ABC):
Expand Down Expand Up @@ -111,8 +115,9 @@ def logger(self) -> logging.Logger:
@abstractmethod
def apply_transaction(self,
header: BlockHeader,
transaction: BaseTransaction
) -> Tuple[BlockHeader, Receipt, BaseComputation]:
transaction: BaseTransaction,
*,
tracer: BaseTracer=None) -> Tuple[BlockHeader, Receipt, BaseComputation]:
raise NotImplementedError("VM classes must implement this method")

@abstractmethod
Expand Down Expand Up @@ -397,17 +402,22 @@ def logger(self) -> logging.Logger:
#
def apply_transaction(self,
header: BlockHeader,
transaction: BaseTransaction
) -> Tuple[BlockHeader, Receipt, BaseComputation]:
transaction: BaseTransaction,
*,
tracer: BaseTracer=None) -> Tuple[BlockHeader, Receipt, BaseComputation]:
"""
Apply the transaction to the current block. This is a wrapper around
:func:`~eth.vm.state.State.apply_transaction` with some extra orchestration logic.

:param header: header of the block before application
:param transaction: to apply
"""
if tracer is None:
tracer = NoopTracer()

self.validate_transaction_against_header(header, transaction)
state_root, computation = self.state.apply_transaction(transaction)
with self.state.trace(tracer):
state_root, computation = self.state.apply_transaction(transaction)
receipt = self.make_receipt(header, transaction, computation, self.state)
self.validate_receipt(receipt)

Expand All @@ -429,14 +439,18 @@ def execute_bytecode(self,
data: bytes,
code: bytes,
code_address: Address=None,
) -> BaseComputation:
*,
tracer: BaseTracer=None) -> BaseComputation:
"""
Execute raw bytecode in the context of the current state of
the virtual machine.
"""
if origin is None:
origin = sender

if tracer is None:
tracer = NoopTracer()

# Construct a message
message = Message(
gas=gas,
Expand All @@ -455,11 +469,13 @@ def execute_bytecode(self,
)

# Execute it in the VM
return self.state.get_computation(message, transaction_context).apply_computation(
self.state,
message,
transaction_context,
)
with self.state.trace(tracer):
return self.state.get_computation(message, transaction_context).apply_computation(
self.state,
message,
transaction_context,
tracer,
)

def apply_all_transactions(
self,
Expand Down
64 changes: 54 additions & 10 deletions eth/vm/computation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from abc import (
ABC,
abstractmethod
abstractmethod,
)
import itertools
import logging
Expand All @@ -10,6 +10,7 @@
cast,
Dict,
List,
Optional,
Tuple,
Union,
)
Expand Down Expand Up @@ -73,6 +74,9 @@
from eth.vm.transaction_context import (
BaseTransactionContext
)
from eth.vm.tracing import (
BaseTracer,
)


def memory_gas_cost(size_in_bytes: int) -> int:
Expand Down Expand Up @@ -125,7 +129,8 @@ class BaseComputation(Configurable, ABC):
def __init__(self,
state: BaseState,
message: Message,
transaction_context: BaseTransactionContext) -> None:
transaction_context: BaseTransactionContext,
tracer: BaseTracer) -> None:

self.state = state
self.msg = message
Expand All @@ -142,6 +147,8 @@ def __init__(self,
code = message.code
self.code = CodeStream(code)

self.tracer = tracer

#
# Convenience
#
Expand All @@ -152,9 +159,31 @@ def is_origin_computation(self) -> bool:
"""
return self.msg.sender == self.transaction_context.origin

#
# Program Counter
#
def get_pc(self) -> int:
return max(0, self.code.pc - 1)

#
# Error handling
#
@property
def error(self) -> Optional[VMError]:
if self.is_error:
return self._error
else:
return None

@error.setter
def error(self, value: VMError) -> None:
if not isinstance(value, VMError):
raise TypeError(
"Computation.error can only be set to a VMError subclass. Got: "
"{0}".format(value)
)
self._error = value

@property
def is_success(self) -> bool:
"""
Expand All @@ -175,15 +204,15 @@ def raise_if_error(self) -> None:

:raise VMError:
"""
if self._error is not None:
raise self._error
if self.is_error:
raise self.error

@property
def should_burn_gas(self) -> bool:
"""
Return ``True`` if the remaining gas should be burned.
"""
return self.is_error and self._error.burns_gas
return self.is_error and self.error.burns_gas

@property
def should_return_gas(self) -> bool:
Expand Down Expand Up @@ -258,6 +287,9 @@ def memory_read_bytes(self, start_position: int, size: int) -> bytes:
"""
return self._memory.read_bytes(start_position, size)

def dump_memory(self) -> bytes:
return self._memory.read_bytes(0, len(self._memory))

#
# Gas Consumption
#
Expand Down Expand Up @@ -341,6 +373,9 @@ def stack_dup(self, position: int) -> None:
"""
return self._stack.dup(position)

def dump_stack(self) -> Tuple[int, ...]:
return tuple(self._stack) # type: ignore

#
# Computation result
#
Expand Down Expand Up @@ -402,12 +437,14 @@ def generate_child_computation(self, child_msg: Message) -> 'BaseComputation':
self.state,
child_msg,
self.transaction_context,
self.tracer,
).apply_create_message()
else:
child_computation = self.__class__(
self.state,
child_msg,
self.transaction_context,
self.tracer,
).apply_message()
return child_computation

Expand Down Expand Up @@ -511,7 +548,7 @@ def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None:
"y" if self.msg.is_static else "n",
exc_value,
)
self._error = exc_value
self.error = exc_value
if self.should_burn_gas:
self.consume_gas(
self._gas_meter.gas_remaining,
Expand Down Expand Up @@ -559,30 +596,37 @@ def apply_create_message(self) -> 'BaseComputation':
def apply_computation(cls,
state: BaseState,
message: Message,
transaction_context: BaseTransactionContext) -> 'BaseComputation':
transaction_context: BaseTransactionContext,
tracer: BaseTracer) -> 'BaseComputation':
"""
Perform the computation that would be triggered by the VM message.
"""
with cls(state, message, transaction_context) as computation:
with cls(state, message, transaction_context, tracer) 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(opcode)
pc = computation.get_pc()

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

try:
opcode_fn(computation=computation)
with computation.tracer.capture(computation, opcode_fn):
opcode_fn(computation=computation)
except Halt:
break

# Allow the tracer to record final values.
computation.tracer.finalize(computation)

return computation

#
Expand Down
1 change: 1 addition & 0 deletions eth/vm/forks/frontier/computation.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def apply_message(self) -> BaseComputation:
self.state,
self.msg,
self.transaction_context,
self.tracer,
)

if computation.is_error:
Expand Down
24 changes: 17 additions & 7 deletions eth/vm/forks/frontier/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
BaseState,
BaseTransactionExecutor,
)
from eth.vm.tracing import (
BaseTracer,
NoopTracer,
)


from .computation import FrontierComputation
Expand Down Expand Up @@ -106,8 +110,12 @@ def build_evm_message(self, transaction: BaseOrSpoofTransaction) -> Message:

def build_computation(self,
message: Message,
transaction: BaseOrSpoofTransaction) -> BaseComputation:
transaction: BaseOrSpoofTransaction,
tracer: BaseTracer=None) -> BaseComputation:
"""Apply the message to the VM."""
if tracer is None:
tracer = NoopTracer()

transaction_context = self.vm_state.get_transaction_context(transaction)
if message.is_create:
is_collision = self.vm_state.account_db.account_has_code_or_nonce(
Expand All @@ -128,14 +136,16 @@ def build_computation(self,
encode_hex(message.storage_address),
)
else:
with self.vm_state.trace(tracer):
computation = self.vm_state.get_computation(
message,
transaction_context,
).apply_create_message()
else:
with self.vm_state.trace(tracer):
computation = self.vm_state.get_computation(
message,
transaction_context,
).apply_create_message()
else:
computation = self.vm_state.get_computation(
message,
transaction_context).apply_message()
transaction_context).apply_message()

return computation

Expand Down
25 changes: 19 additions & 6 deletions eth/vm/stack.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import logging
from typing import ( # noqa: F401
Generator,
Iterable,
List,
Tuple,
Union
)

from eth_utils import (
int_to_big_endian,
Expand All @@ -13,12 +20,6 @@
validate_stack_item,
)

from typing import ( # noqa: F401
Generator,
List,
Tuple,
Union
)
from eth_typing import Hash32 # noqa: F401


Expand All @@ -35,6 +36,18 @@ def __init__(self) -> None:
def __len__(self) -> int:
return len(self.values)

def __iter__(self) -> Iterable[int]:
for item in self.values:
if isinstance(item, int):
yield item
elif isinstance(item, bytes):
yield big_endian_to_int(item)
else:
raise TypeError(
"Invariant: stack should only contain `int` and `bytes` "
"types. Got: {!r}".format(item)
)

def push(self, value: Union[int, bytes]) -> None:
"""
Push an item onto the stack.
Expand Down
Loading