diff --git a/ethereum/pow/chain.py b/ethereum/pow/chain.py index 6afb2eb3a..8e06d8a4f 100644 --- a/ethereum/pow/chain.py +++ b/ethereum/pow/chain.py @@ -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): diff --git a/ethereum/vm.py b/ethereum/vm.py index 200049720..15de1f886 100644 --- a/ethereum/vm.py +++ b/ethereum/vm.py @@ -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 @@ -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 @@ -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: @@ -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: @@ -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, []) diff --git a/tools/evm.py b/tools/evm.py new file mode 100644 index 000000000..cb4bc2d64 --- /dev/null +++ b/tools/evm.py @@ -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()