Skip to content
This repository was archived by the owner on May 23, 2023. 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
2 changes: 1 addition & 1 deletion ethereum/pow/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
log = get_logger('eth.chain')
config_string = ':info' #,eth.chain:debug'
#config_string = ':info,eth.vm.log:trace,eth.vm.op:trace,eth.vm.stack:trace,eth.vm.exit:trace,eth.pb.msg:trace,eth.pb.tx:debug'
configure_logging(config_string=config_string)
#configure_logging(config_string=config_string)


class Chain(object):
Expand Down
104 changes: 65 additions & 39 deletions ethereum/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,26 @@ class Compustate():
def __init__(self, **kwargs):
self.memory = bytearray()
self.stack = []
self.steps = 0
self.pc = 0
self.gas = 0

self.prev_memory = bytearray()
self.prev_stack = []
self.prev_pc = 0
self.prev_gas = 0
self.prev_prev_op = None
self.last_returned = bytearray()

for kw in kwargs:
setattr(self, kw, kwargs[kw])

def reset_prev(self):
self.prev_memory = copy.copy(self.memory)
self.prev_stack = copy.copy(self.stack)
self.prev_pc = self.pc
self.prev_gas = self.gas


# Preprocesses code, and determines which locations are in the middle
# of pushdata and thus invalid
Expand Down Expand Up @@ -182,6 +196,49 @@ def revert(gas, data, **kargs):
return 0, gas, data


def vm_trace(ext, msg, compustate, opcode, pushcache, tracer=log_vm_op):
"""
This diverges from normal logging, as we use the logging namespace
only to decide which features get logged in 'eth.vm.op'
i.e. tracing can not be activated by activating a sub
like 'eth.vm.op.stack'
"""

op, in_args, out_args, fee = opcodes.opcodes[opcode]

trace_data = {}
trace_data['stack'] = list(map(to_string, list(compustate.prev_stack)))
if compustate.prev_prev_op in ('MLOAD', 'MSTORE', 'MSTORE8', 'SHA3', 'CALL',
'CALLCODE', 'CREATE', 'CALLDATACOPY', 'CODECOPY',
'EXTCODECOPY'):
if len(compustate.prev_memory) < 4096:
trace_data['memory'] = \
''.join([encode_hex(ascii_chr(x)) for x
in compustate.prev_memory])
else:
trace_data['sha3memory'] = \
encode_hex(utils.sha3(b''.join([ascii_chr(x) for
x in compustate.prev_memory])))
if compustate.prev_prev_op in ('SSTORE',) or compustate.steps == 0:
trace_data['storage'] = ext.log_storage(msg.to)
trace_data['gas'] = to_string(compustate.prev_gas)
trace_data['gas_cost'] = to_string(compustate.prev_gas - compustate.gas)
trace_data['fee'] = fee
trace_data['inst'] = opcode
trace_data['pc'] = to_string(compustate.prev_pc)
if compustate.steps == 0:
trace_data['depth'] = msg.depth
trace_data['address'] = msg.to
trace_data['steps'] = compustate.steps
trace_data['depth'] = msg.depth
if op[:4] == 'PUSH':
print repr(pushcache)
trace_data['pushvalue'] = pushcache[compustate.prev_pc]
tracer.trace('vm', op=op, **trace_data)
compustate.steps += 1
compustate.prev_prev_op = op


# Main function
def vm_execute(ext, msg, code):
# precompute trace flag
Expand All @@ -199,8 +256,6 @@ def vm_execute(ext, msg, code):

# For tracing purposes
op = None
steps = 0
_prevop = None # for trace only

while compustate.pc < codelen:

Expand Down Expand Up @@ -229,46 +284,11 @@ def vm_execute(ext, msg, code):
pre_height=to_string(len(compustate.stack)))

# Apply operation
if trace_vm:
compustate.reset_prev()
compustate.gas -= fee
compustate.pc += 1

# Tracing
if trace_vm:
"""
This diverges from normal logging, as we use the logging namespace
only to decide which features get logged in 'eth.vm.op'
i.e. tracing can not be activated by activating a sub
like 'eth.vm.op.stack'
"""
trace_data = {}
trace_data['stack'] = list(map(to_string, list(compustate.stack)))
if _prevop in ('MLOAD', 'MSTORE', 'MSTORE8', 'SHA3', 'CALL',
'CALLCODE', 'CREATE', 'CALLDATACOPY', 'CODECOPY',
'EXTCODECOPY'):
if len(compustate.memory) < 4096:
trace_data['memory'] = \
''.join([encode_hex(ascii_chr(x)) for x
in compustate.memory])
else:
trace_data['sha3memory'] = \
encode_hex(utils.sha3(b''.join([ascii_chr(x) for
x in compustate.memory])))
if _prevop in ('SSTORE',) or steps == 0:
trace_data['storage'] = ext.log_storage(msg.to)
trace_data['gas'] = to_string(compustate.gas + fee)
trace_data['inst'] = opcode
trace_data['pc'] = to_string(compustate.pc - 1)
if steps == 0:
trace_data['depth'] = msg.depth
trace_data['address'] = msg.to
trace_data['steps'] = steps
trace_data['depth'] = msg.depth
if op[:4] == 'PUSH':
trace_data['pushvalue'] = pushcache[compustate.pc - 1]
log_vm_op.trace('vm', op=op, **trace_data)
steps += 1
_prevop = op

# Valid operations
# Pushes first because they are very frequent
if 0x60 <= opcode <= 0x7f:
Expand Down Expand Up @@ -677,6 +697,12 @@ def vm_execute(ext, msg, code):
log_msg.debug('SUICIDING', addr=utils.checksum_encode(msg.to), to=utils.checksum_encode(to), xferring=xfer)
return peaceful_exit('SUICIDED', compustate.gas, [])

if trace_vm:
vm_trace(ext, msg, compustate, opcode, pushcache)

if trace_vm:
compustate.reset_prev()
vm_trace(ext, msg, compustate, 0, None)
return peaceful_exit('CODE OUT OF RANGE', compustate.gas, [])


Expand Down
118 changes: 118 additions & 0 deletions tools/evm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import click
import copy
import json

from ethereum import slogging
slogging.PRINT_FORMAT = '%(message)s'

from ethereum import vm
from ethereum.block import Block
from ethereum.transactions import Transaction
from ethereum.config import Env, default_config
from ethereum.db import EphemDB
from ethereum.genesis_helpers import initialize_genesis_keys, state_from_genesis_declaration, mk_genesis_data
from ethereum.messages import VMExt, _apply_msg
from ethereum.utils import bytearray_to_bytestr, normalize_address, encode_int256, encode_bin, scan_bin, zpad, encode_hex, decode_hex, big_endian_to_int, int_to_big_endian

slogging.configure(':info,eth.vm.op:trace')


def scan_int(v):
if v[:2] in ('0x', b'0x'):
v = v[2:]
if len(v) % 2 != 0:
v = '0' + v
return big_endian_to_int(decode_hex(v))

def encode_int(v):
s = encode_hex(int_to_big_endian(int(v)))
if s[:1] == '0': # remove leading zero
s = s[1:]
if not s:
s = '0'
return '0x' + s

vm.log_vm_op.memory = '0x'
vm.log_vm_op.storage = ''
def format_message(msg, kwargs, highlight, level):
if 'memory' in kwargs:
vm.log_vm_op.memory = '0x' + kwargs['memory']
if 'storage' in kwargs:
s = []
storage = kwargs['storage']['storage']
for k in sorted(storage.keys()):
v = '0x' + encode_hex(zpad(int_to_big_endian(scan_int(storage[k])),32))
k = '0x' + encode_hex(zpad(int_to_big_endian(scan_int(k)),32))
s.append(k + ': ' + v)
s = ','.join(s)
vm.log_vm_op.storage = s

return '{"pc":%s,"op":%s,"gas":"%s","gasCost":"%s","memory":"%s","stack":[%s],"storage":{%s},"depth":%s}' % (
kwargs['pc'],
kwargs['inst'],
encode_int(int(kwargs['gas'])),
encode_int(kwargs['gas_cost']),
vm.log_vm_op.memory,
','.join(['"%s"' % encode_int(v) for v in kwargs['stack']]),
vm.log_vm_op.storage,
kwargs['depth']+1
)
vm.log_vm_op.format_message = format_message


konfig = copy.copy(default_config)
konfig['HOMESTEAD_FORK_BLKNUM'] = 0
konfig['DAO_FORK_BLKNUM'] = 0
konfig['ANTI_DOS_FORK_BLKNUM'] = 0
konfig['SPURIOUS_DRAGON_FORK_BLKNUM'] = 0
konfig['METROPOLIS_FORK_BLKNUM'] = 999999999


class EVMRunner(object):
def __init__(self, genesis):
env = Env(EphemDB(), konfig)
if not genesis:
genesis = mk_genesis_data(env)

if 'config' in genesis:
if 'homesteadBlock' in genesis['config']:
env.config['HOMESTEAD_FORK_BLKNUM'] = int(genesis['config']['homesteadBlock'])
env.config['DAO_FORK_BLKNUM'] = int(genesis['config']['homesteadBlock'])
env.config['ANTI_DOS_FORK_BLKNUM'] = int(genesis['config']['homesteadBlock'])
env.config['SPURIOUS_DRAGON_FORK_BLKNUM'] = int(genesis['config']['homesteadBlock'])

self.state = state_from_genesis_declaration(genesis, env)
initialize_genesis_keys(self.state, Block(self.state.prev_headers[0], [], []))

def run(self, sender=None, to=None, code=None, gas=None):
sender = normalize_address(sender) if sender else normalize_address(zpad('sender', 20))
to = normalize_address(to) if to else normalize_address(zpad('receiver', 20))
code = scan_bin(code) if code else ''
gas = scan_int(gas) if gas else 10000000000000

msg = vm.Message(sender, to, gas=gas)
ext = VMExt(self.state, Transaction(0, 0, 21000, b'', 0, b''))

result, gas_remained, data = _apply_msg(ext, msg, code)
return bytearray_to_bytestr(data) if result else None


@click.command()
@click.option('-g', '--genesis', type=click.File(), help='Genesis json file to use.')
@click.option('-c', '--code', type=str, help='Code to be run on evm.')
@click.option('-s', '--sender', type=str, help='Sender of the transaction.')
@click.option('-r', '--receiver', type=str, help='Receiver of the transaction.')
@click.option('--gas', type=str, help='Gas limit for the run.')
def main(genesis, code, sender, receiver, gas):
if genesis:
genesis = json.load(genesis)
EVMRunner(genesis).run(
sender=sender,
to=receiver,
code=code,
gas=gas
)


if __name__ == '__main__':
main()