diff --git a/ethereum/config.py b/ethereum/config.py index 7e0967ee2..a52f91938 100644 --- a/ethereum/config.py +++ b/ethereum/config.py @@ -9,7 +9,7 @@ # Genesis block difficulty GENESIS_DIFFICULTY=131072, # Genesis block gas limit - GENESIS_GAS_LIMIT=3141592, + GENESIS_GAS_LIMIT=8000000, #4712388, # Genesis block prevhash, coinbase, nonce GENESIS_PREVHASH=b'\x00' * 32, GENESIS_COINBASE=b'\x00' * 20, @@ -66,6 +66,8 @@ # Anti-DoS fork ANTI_DOS_FORK_BLKNUM=2457000, CLEARING_FORK_BLKNUM=2 ** 98, + # Trace + TRACE_TRANSACTIONS=True ) assert default_config['NEPHEW_REWARD'] == \ default_config['BLOCK_REWARD'] // 32 diff --git a/ethereum/processblock.py b/ethereum/processblock.py index d688b4e09..578c3d6e3 100644 --- a/ethereum/processblock.py +++ b/ethereum/processblock.py @@ -8,6 +8,7 @@ from ethereum import specials from ethereum import bloom from ethereum import vm as vm +from ethereum.trace import Trace from ethereum.exceptions import InvalidNonce, InsufficientStartGas, UnsignedTransaction, \ BlockGasLimitReached, InsufficientBalance, VerificationFailed from ethereum.utils import safe_ord, normalize_address, mk_contract_address, \ @@ -22,6 +23,7 @@ log_msg = get_logger('eth.pb.msg') log_state = get_logger('eth.pb.msg.state') + TT255 = 2 ** 255 TT256 = 2 ** 256 TT256M1 = 2 ** 256 - 1 @@ -167,11 +169,16 @@ def rp(what, actual, target): # MESSAGE ext = VMExt(block, tx) + tr = Trace() + if tr.enabled: + oldStorage = ext.get_storage(tx.to) if tx.to and tx.to != CREATE_CONTRACT_ADDRESS: - result, gas_remained, data = apply_msg(ext, message) + result, gas_remained, data, trc = apply_msg(ext, message) + #We receive bytesarray for data log_tx.debug('_res_', result=result, gas_remained=gas_remained, data=lazy_safe_encode(data)) else: # CREATE - result, gas_remained, data = create_contract(ext, message) + result, gas_remained, data, trc = create_contract(ext, message) + #We receive address for data assert utils.is_numeric(gas_remained) log_tx.debug('_create_', result=result, gas_remained=gas_remained, data=lazy_safe_encode(data)) @@ -184,6 +191,7 @@ def rp(what, actual, target): log_tx.debug('TX FAILED', reason='out of gas', startgas=tx.startgas, gas_remained=gas_remained) block.gas_used += tx.startgas + gas_used = tx.startgas block.delta_balance(block.coinbase, tx.gasprice * tx.startgas) output = b'' success = 0 @@ -214,6 +222,9 @@ def rp(what, actual, target): block.del_account(s) block.add_transaction_to_list(tx) block.logs = [] + if trc and tr.enabled: + tr.addTrace(tx.hash.encode('hex'), { "returnValue":output, "gas":gas_used, "structLogs":trc }) + tr.addStorage(ext.block_number, tx.hash.encode('hex'), oldStorage); return success, output @@ -230,6 +241,7 @@ def __init__(self, block, tx): self.get_nonce = block.get_nonce self.set_nonce = block.set_nonce self.set_storage_data = block.set_storage_data + self.get_storage = block.get_storage self.get_storage_data = block.get_storage_data self.log_storage = lambda x: block.account_to_dict(x)['storage'] self.add_suicide = lambda x: block.suicides.append(x) @@ -282,7 +294,7 @@ def _apply_msg(ext, msg, code): if msg.code_address in specials.specials: res, gas, dat = specials.specials[msg.code_address](ext, msg) else: - res, gas, dat = vm.vm_execute(ext, msg, code) + res, gas, dat, trc = vm.vm_execute(ext, msg, code) # gas = int(gas) # assert utils.is_numeric(gas) if trace_msg: @@ -299,8 +311,8 @@ def _apply_msg(ext, msg, code): if res == 0: log_msg.debug('REVERTING') ext._block.revert(snapshot) - - return res, gas, dat + + return res, gas, dat, trc def create_contract(ext, msg): @@ -332,12 +344,12 @@ def create_contract(ext, msg): # assert not ext.get_code(msg.to) msg.data = vm.CallData([], 0, 0) snapshot = ext._block.snapshot() - res, gas, dat = _apply_msg(ext, msg, code) + res, gas, dat, trc = _apply_msg(ext, msg, code) assert utils.is_numeric(gas) if res: if not len(dat): - return 1, gas, msg.to + return 1, gas, msg.to, trc gcost = len(dat) * opcodes.GCONTRACTBYTE if gas >= gcost: gas -= gcost @@ -346,8 +358,8 @@ def create_contract(ext, msg): log_msg.debug('CONTRACT CREATION OOG', have=gas, want=gcost, block_number=ext._block.number) if ext._block.number >= ext._block.config['HOMESTEAD_FORK_BLKNUM']: ext._block.revert(snapshot) - return 0, 0, b'' + return 0, 0, b'', trc ext._block.set_code(msg.to, b''.join(map(ascii_chr, dat))) - return 1, gas, msg.to + return 1, gas, msg.to, trc else: - return 0, gas, b'' + return 0, gas, b'', trc diff --git a/ethereum/tester.py b/ethereum/tester.py index c8485e5ad..b6238cc0f 100644 --- a/ethereum/tester.py +++ b/ethereum/tester.py @@ -8,12 +8,13 @@ import rlp from rlp.utils import ascii_chr -from ethereum import blocks, db, opcodes, processblock, transactions +from ethereum import blocks, db, opcodes, transactions, processblock from ethereum.abi import ContractTranslator from ethereum.config import Env from ethereum.slogging import LogRecorder from ethereum._solidity import get_solidity from ethereum.utils import to_string, sha3, privtoaddr, int_to_addr +from ethereum.trace import Trace TRACE_LVL_MAP = [ ':info', @@ -24,21 +25,50 @@ 'eth.vm.storage:trace,eth.vm.memory:trace' ] -GAS_LIMIT = 3141592 -GAS_PRICE = 1 +GAS_LIMIT = 8000000 +GAS_PRICE = 20000000000 # pylint: disable=invalid-name gas_limit = GAS_LIMIT gas_price = GAS_PRICE -accounts = [] -keys = [] +accounts = [ + u"ac7bebd558c734fe105d09167860b230fb0a218d".decode('hex'), + u"6e5fd1741e45c966a76a077af9132627c07b0dc1".decode('hex'), + u"ef057953c56855f16e658bf8fd0d2e300961fc1f".decode('hex'), + u"2c284ef5a0d50dda177bd8c9fdf20610f6fdac09".decode('hex'), + u"bd3c601b59f46cc59be3446ba29c66b9182a70b6".decode('hex'), + u"e6e6033428cfc58af1585c26a823916c8120ca73".decode('hex'), + u"e2c628c146a9d40c9ed4c5c3e29cd0a609f7c6f1".decode('hex'), + u"3e2ff0583a5dec1bd3ac0f7b8d26fa96b759fe92".decode('hex'), + u"2827a89f78d70c422452528634cfe522b5c668c6".decode('hex'), + u"f56ae85523c6f4773954fe0b25ba1f52e1183689".decode('hex'), + u"7703aCa0f4ee937C3073ec80c7608B6f7cE2426B".decode('hex'), + u"153ee6aD2e7e665b8a07ff37d93271d6E5FDc6d4".decode('hex'), + u"4fe1ead95d882580561b704938ecbd5cb9450ab6".decode('hex'), +] +keys = [ + u"43df74be7858da13bb08c1289fe488b9843c555538dd1638203725b18a19863e".decode('hex'), + u"0af37c53fdc5b97bf1f84d30e84f09c84f733e467a3b26c1ce6c5d448f9d7cec".decode('hex'), + u"0d5c1bd818a4086f28314415cb375a937593efab66f8f7d2903bf2a13ed35070".decode('hex'), + u"17029bda254fdf118125741e80d2d43e8ac1ffa8cca20c51683bc0735c802e5b".decode('hex'), + u"8d2fd08f91550712ec0db96bdbb849ec88a560e200c58f05954827b2593cf9e7".decode('hex'), + u"803ae2f3b0030390092910e0f1e8ec15dbc975d6422ab2274b175c74eed589fb".decode('hex'), + u"0c71a0e6e4bf22677a5750c123aaf988270e1c4025f80d82b7a18f2efe295cbf".decode('hex'), + u"9ca1d29731e6302e3e7d7f0ebaf2b4fa48d7b7fd4f5c82f59b3983b0a2160d7e".decode('hex'), + u"e2631c2443b12abdfc5e70e3b7643f2d340eb49c6102c66835d0c7286903b009".decode('hex'), + u"f7a590340d042a107ec3c9e82f81d2301ecc5b79f959f7a09d3c7fb7ab7f1024".decode('hex'), + u"9e256901c752616c231904281333e5dd11fa48fdfd7ffac3515923b1fe60a28e".decode('hex'), + u"b10ec925fccdaf155fa0b56bd55cf6ce3cc8927b304c0a563476529d925755d5".decode('hex'), + u"71c52b034f6405fbabb6ad5e91997dd2ea1f43d48502e10dd47d5a4a8a02cbdf".decode('hex'), +] + languages = {} -for account_number in range(10): - keys.append(sha3(to_string(account_number))) - accounts.append(privtoaddr(keys[-1])) +#for account_number in range(10): +# keys.append(sha3(to_string(account_number))) +# accounts.append(privtoaddr(keys[-1])) k0, k1, k2, k3, k4, k5, k6, k7, k8, k9 = keys[:10] a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = accounts[:10] @@ -157,13 +187,12 @@ def kall(self, *args, **kwargs): class state(object): - def __init__(self, num_accounts=len(keys)): self.temp_data_dir = tempfile.mkdtemp() self.db = db.EphemDB() self.env = Env(self.db) self.last_tx = None - + initial_balances = {} for i in range(num_accounts): @@ -293,7 +322,7 @@ def _send(self, sender, to, value, evmdata='', funid=None, abi=None, # pylint: (success, output) = processblock.apply_transaction(self.block, transaction) if not success: - raise TransactionFailed() + raise TransactionFailed(transaction.hash.encode('hex')) out = { 'output': output, diff --git a/ethereum/testutils.py b/ethereum/testutils.py index c0888d5a6..b28427749 100644 --- a/ethereum/testutils.py +++ b/ethereum/testutils.py @@ -220,7 +220,7 @@ def blkhash(n): time_pre = time.time() if profiler: profiler.enable() - success, gas_remained, output = vm.vm_execute(ext, msg, code) + success, gas_remained, output, trc = vm.vm_execute(ext, msg, code) if profiler: profiler.disable() pb.apply_msg = orig_apply_msg diff --git a/ethereum/trace.py b/ethereum/trace.py new file mode 100644 index 000000000..8530f6204 --- /dev/null +++ b/ethereum/trace.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +from ethereum.config import Env +from ethereum import config + +class Trace(object): + storages = {} + transactions = {} + enabled = None + def __init__(self): + self.enabled = config.default_config['TRACE_TRANSACTIONS'] + + def getTrace(self, tx_hash): + if not self.enabled: raise Exception('Trace transaction is disabled!') + if tx_hash in self.transactions: + return self.transactions[tx_hash] + else: + raise Exception('Transaction not found!') + + def addTrace(self, tx_hash, tx_trace): + if self.enabled: + if tx_hash.lower()[:2] != "0x": tx_hash = "0x"+tx_hash + if tx_hash in self.transactions: + # extend! + self.transactions[tx_hash]["structLogs"].extend(tx_trace["structLogs"]) + self.transactions[tx_hash]["returnValue"] = tx_trace["returnValue"] + self.transactions[tx_hash]["gas"] = tx_trace["gas"] + else: + self.transactions[tx_hash] = tx_trace + return True + return False + + def addStorage(self, block_num, tx_hash, storage): + if self.enabled: + str = {} + storage = storage.to_dict() + for a in storage: + str[a.encode('hex')] = storage[a].encode('hex') + if not block_num in self.storages: self.storages[block_num] = [] + tmp = [tx_hash in i for i in self.storages[block_num]] + if (True in tmp): + self.storages[block_num][tmp.index(True)] = str + else: + self.storages[block_num].append({ tx_hash:str }) + return True + return False + + def getStorage(self, block_num, tx_num, contract_address, stor_start, stor_end, limit): + # stor_start, stor_end, limit not yet implemented + if self.enabled: + if not block_num in self.storages: raise Exception('Block not found!') + if tx_num >= len(self.storages[block_num]): raise Exception('TX not found!') + return { "complete": True, "storage": self.storages[block_num][tx_num] } + raise Exception('Trace is disabled!') diff --git a/ethereum/vm.py b/ethereum/vm.py index 6f88973aa..5c2709560 100644 --- a/ethereum/vm.py +++ b/ethereum/vm.py @@ -5,12 +5,13 @@ # ###################################### import sys import copy +import json from ethereum import utils from ethereum import opcodes from ethereum.slogging import get_logger from rlp.utils import ascii_chr -from ethereum.utils import encode_hex from ethereum.utils import to_string +from ethereum.trace import Trace if sys.version_info.major == 2: from repoze.lru import lru_cache @@ -28,7 +29,6 @@ TT256M1 = 2 ** 256 - 1 TT255 = 2 ** 255 - class CallData(object): def __init__(self, parent_memory, offset=0, size=None): @@ -167,7 +167,7 @@ def peaceful_exit(cause, gas, data, **kargs): def vm_execute(ext, msg, code): # precompute trace flag # if we trace vm, we're in slow mode anyway - trace_vm = log_vm_op.is_active('trace') + trace_vm = log_vm_op.is_active('trace') or Trace.enabled compustate = Compustate(gas=msg.gas) stk = compustate.stack @@ -177,36 +177,61 @@ def vm_execute(ext, msg, code): codelen = len(processed_code) op = None + ret = [] steps = 0 _prevop = None # for trace only - - while 1: + traceData = [] + if trace_vm: + from ethereum.utils import encode_int256, int_to_bytes, bytearray_to_bytestr + def long_to_evm(data): + return encode_int256(data).encode('hex')[-64:] + def int_byteslen(data): + return len(int_to_bytes(data)) + def split_mem(data): + text = bytemem_to_hex(data) + return [text[i*64:(i+1)*64] for i in range(0,len(text)//64)] + def bytemem_to_hex(data): + return bytearray_to_bytestr(data).encode('hex') + err = "" + data = None + gas = None + trace_extend = None + while err == "": # stack size limit error if compustate.pc >= codelen: - return peaceful_exit('CODE OUT OF RANGE', compustate.gas, []) + err = "CODE OUT OF RANGE" + gas = compustate.gas + data = [] + peaceful_exit('CODE OUT OF RANGE', compustate.gas, []) + break op, in_args, out_args, fee, opcode, pushval = \ processed_code[compustate.pc] - # out of gas error if fee > compustate.gas: - return vm_exception('OUT OF GAS') + err = "OUT OF GAS" + vm_exception('OUT OF GAS') + break + #return vm_exception('OUT OF GAS') # empty stack error if in_args > len(compustate.stack): - return vm_exception('INSUFFICIENT STACK', + err = "INSUFFICIENT STACK" + vm_exception('INSUFFICIENT STACK', op=op, needed=to_string(in_args), available=to_string(len(compustate.stack))) + break if len(compustate.stack) - in_args + out_args > 1024: - return vm_exception('STACK SIZE LIMIT EXCEEDED', + err = "STACK SIZE LIMIT EXCEEDED" + vm_exception('STACK SIZE LIMIT EXCEEDED', op=op, pre_height=to_string(len(compustate.stack))) + break # Apply operation compustate.gas -= fee compustate.pc += 1 - if trace_vm: """ This diverges from normal logging, as we use the logging namespace @@ -214,43 +239,35 @@ def vm_execute(ext, msg, code): 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) < 1024: - trace_data['memory'] = \ - b''.join([encode_hex(ascii_chr(x)) for x - in compustate.memory]) - else: - trace_data['sha3memory'] = \ - encode_hex(utils.sha3(''.join([ascii_chr(x) for - x in compustate.memory]))) + trace_data = { "error": None, "gasCost":long(fee), "memory": None, "stack":[], "storage":{} } + trace_data['stack'] = list(map(long_to_evm, list(compustate.stack))) + trace_data['memory'] = split_mem(compustate.memory) if _prevop in ('SSTORE', 'SLOAD') 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['gas'] = compustate.gas + fee + trace_data['pc'] = compustate.pc - 1 + trace_data['depth'] = msg.depth+1 trace_data['op'] = op - trace_data['steps'] = steps + #trace_data['steps'] = steps if op[:4] == 'PUSH': - trace_data['pushvalue'] = pushval - log_vm_op.trace('vm', **trace_data) + trace_data['op'] = "PUSH"+str(int_byteslen(pushval)) steps += 1 _prevop = op - # Invalid operation if op == 'INVALID': - return vm_exception('INVALID OP', opcode=opcode) - + err = "INVALID OP" + vm_exception('INVALID OP', opcode=opcode) + if trace_vm: traceData.append(trace_data) + break # Valid operations if opcode < 0x10: if op == 'STOP': - return peaceful_exit('STOP', compustate.gas, []) + err = "STOP" + gas = compustate.gas + data = [] + peaceful_exit('STOP', compustate.gas, []) + if trace_vm: traceData.append(trace_data) + break elif op == 'ADD': stk.append((stk.pop() + stk.pop()) & TT256M1) elif op == 'SUB': @@ -285,7 +302,10 @@ def vm_execute(ext, msg, code): expfee = nbytes * opcodes.GEXPONENTBYTE if compustate.gas < expfee: compustate.gas = 0 - return vm_exception('OOG EXPONENT') + err = "OOG EXPONENT" + vm_exception('OOG EXPONENT') + if trace_vm: traceData.append(trace_data) + break compustate.gas -= expfee stk.append(pow(base, exponent, TT256)) elif op == 'SIGNEXTEND': @@ -332,9 +352,15 @@ def vm_execute(ext, msg, code): s0, s1 = stk.pop(), stk.pop() compustate.gas -= opcodes.GSHA3WORD * (utils.ceil32(s1) // 32) if compustate.gas < 0: - return vm_exception('OOG PAYING FOR SHA3') + err = "OOG PAYING FOR SHA3" + vm_exception('OOG PAYING FOR SHA3') + if trace_vm: traceData.append(trace_data) + break if not mem_extend(mem, compustate, op, s0, s1): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break data = b''.join(map(ascii_chr, mem[s0: s0 + s1])) stk.append(utils.big_endian_to_int(utils.sha3(data))) elif op == 'ADDRESS': @@ -343,7 +369,10 @@ def vm_execute(ext, msg, code): # EIP150: Increase the gas cost of BALANCE to 400 if ext.post_anti_dos_hardfork: if not eat_gas(compustate, opcodes.BALANCE_SUPPLEMENTAL_GAS): - return vm_exception("OUT OF GAS") + err = "OUT OF GAS" + vm_exception("OUT OF GAS") + if trace_vm: traceData.append(trace_data) + break addr = utils.coerce_addr_to_hex(stk.pop() % 2 ** 160) stk.append(ext.get_balance(addr)) elif op == 'ORIGIN': @@ -359,18 +388,30 @@ def vm_execute(ext, msg, code): elif op == 'CALLDATACOPY': mstart, dstart, size = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart, size): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break if not data_copy(compustate, size): - return vm_exception('OOG COPY DATA') + err = "OOG COPY DATA" + vm_exception('OOG COPY DATA') + if trace_vm: traceData.append(trace_data) + break msg.data.extract_copy(mem, mstart, dstart, size) elif op == 'CODESIZE': stk.append(len(processed_code)) elif op == 'CODECOPY': start, s1, size = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, start, size): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break if not data_copy(compustate, size): - return vm_exception('OOG COPY DATA') + err = "OOG COPY DATA" + vm_exception('OOG COPY DATA') + if trace_vm: traceData.append(trace_data) + break for i in range(size): if s1 + i < len(processed_code): mem[start + i] = processed_code[s1 + i][4] @@ -382,22 +423,33 @@ def vm_execute(ext, msg, code): # EIP150: Increase the gas cost of EXTCODESIZE to 700 if ext.post_anti_dos_hardfork: if not eat_gas(compustate, opcodes.EXTCODELOAD_SUPPLEMENTAL_GAS): - return vm_exception("OUT OF GAS") + err = "OUT OF GAS" + vm_exception("OUT OF GAS") + if trace_vm: traceData.append(trace_data) + break addr = utils.coerce_addr_to_hex(stk.pop() % 2 ** 160) stk.append(len(ext.get_code(addr) or b'')) elif op == 'EXTCODECOPY': # EIP150: Increase the base gas cost of EXTCODECOPY to 700 if ext.post_anti_dos_hardfork: if not eat_gas(compustate, opcodes.EXTCODELOAD_SUPPLEMENTAL_GAS): - return vm_exception("OUT OF GAS") + err = "OUT OF GAS" + vm_exception("OUT OF GAS") + if trace_vm: traceData.append(trace_data) + break addr = utils.coerce_addr_to_hex(stk.pop() % 2 ** 160) start, s2, size = stk.pop(), stk.pop(), stk.pop() extcode = ext.get_code(addr) or b'' assert utils.is_string(extcode) if not mem_extend(mem, compustate, op, start, size): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break if not data_copy(compustate, size): - return vm_exception('OOG COPY DATA') + vm_exception('OOG COPY DATA') + if trace_vm: traceData.append(trace_data) + break for i in range(size): if s2 + i < len(extcode): mem[start + i] = utils.safe_ord(extcode[s2 + i]) @@ -422,13 +474,19 @@ def vm_execute(ext, msg, code): elif op == 'MLOAD': s0 = stk.pop() if not mem_extend(mem, compustate, op, s0, 32): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break data = b''.join(map(ascii_chr, mem[s0: s0 + 32])) stk.append(utils.big_endian_to_int(data)) elif op == 'MSTORE': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, 32): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break v = s1 for i in range(31, -1, -1): mem[s0 + i] = v % 256 @@ -436,13 +494,19 @@ def vm_execute(ext, msg, code): elif op == 'MSTORE8': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, 1): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break mem[s0] = s1 % 256 elif op == 'SLOAD': # EIP150: Increase the gas cost of SLOAD to 200 if ext.post_anti_dos_hardfork: if not eat_gas(compustate, opcodes.SLOAD_SUPPLEMENTAL_GAS): - return vm_exception("OUT OF GAS") + err = "OUT OF GAS" + vm_exception("OUT OF GAS") + if trace_vm: traceData.append(trace_data) + break stk.append(ext.get_storage_data(msg.to, stk.pop())) elif op == 'SSTORE': s0, s1 = stk.pop(), stk.pop() @@ -453,7 +517,10 @@ def vm_execute(ext, msg, code): gascost = opcodes.GSTORAGEADD if s1 else opcodes.GSTORAGEMOD refund = 0 if compustate.gas < gascost: - return vm_exception('OUT OF GAS') + err = "OUT OF GAS" + vm_exception('OUT OF GAS') + if trace_vm: traceData.append(trace_data) + break compustate.gas -= gascost ext.add_refund(refund) # adds neg gascost as a refund if below zero ext.set_storage_data(msg.to, s0, s1) @@ -462,7 +529,10 @@ def vm_execute(ext, msg, code): opnew = processed_code[compustate.pc][0] if \ compustate.pc < len(processed_code) else 'STOP' if opnew != 'JUMPDEST': - return vm_exception('BAD JUMPDEST') + err = "JUMPDEST" + vm_exception('BAD JUMPDEST') + if trace_vm: traceData.append(trace_data) + break elif op == 'JUMPI': s0, s1 = stk.pop(), stk.pop() if s1: @@ -470,7 +540,10 @@ def vm_execute(ext, msg, code): opnew = processed_code[compustate.pc][0] if \ compustate.pc < len(processed_code) else 'STOP' if opnew != 'JUMPDEST': - return vm_exception('BAD JUMPDEST') + err = "BAD JUMPDEST" + vm_exception('BAD JUMPDEST') + if trace_vm: traceData.append(trace_data) + break elif op == 'PC': stk.append(compustate.pc - 1) elif op == 'MSIZE': @@ -508,7 +581,10 @@ def vm_execute(ext, msg, code): topics = [stk.pop() for x in range(depth)] compustate.gas -= msz * opcodes.GLOGBYTE if not mem_extend(mem, compustate, op, mstart, msz): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break data = b''.join(map(ascii_chr, mem[mstart: mstart + msz])) ext.log(msg.to, topics, data) log_log.trace('LOG', to=msg.to, topics=topics, data=list(map(utils.safe_ord, data))) @@ -517,7 +593,10 @@ def vm_execute(ext, msg, code): elif op == 'CREATE': value, mstart, msz = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart, msz): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break if ext.get_balance(msg.to) >= value and msg.depth < 1024: cd = CallData(mem, mstart, msz) ingas = compustate.gas @@ -536,12 +615,15 @@ def vm_execute(ext, msg, code): compustate.gas -= ingas else: stk.append(0) - elif op == 'CALL': + elif op == "CALL": gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, meminstart, meminsz) or \ not mem_extend(mem, compustate, op, memoutstart, memoutsz): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break to = utils.encode_int(to) to = ((b'\x00' * (32 - len(to))) + to)[12:] extra_gas = (not ext.account_exists(to)) * opcodes.GCALLNEWACCOUNT + \ @@ -554,11 +636,17 @@ def vm_execute(ext, msg, code): # the maximum allowed amount, call with all but one 64th of the # maximum allowed amount of gas if compustate.gas < extra_gas: - return vm_exception('OUT OF GAS', needed=extra_gas) + err = "OUT OF GAS" + vm_exception('OUT OF GAS', needed=extra_gas) + if trace_vm: traceData.append(trace_data) + break gas = min(gas, max_call_gas(compustate.gas - extra_gas)) else: if compustate.gas < gas + extra_gas: - return vm_exception('OUT OF GAS', needed=gas + extra_gas) + err = "OUT OF GAS" + vm_exception('OUT OF GAS', needed=gas + extra_gas) + if trace_vm: traceData.append(trace_data) + break submsg_gas = gas + opcodes.GSTIPEND * (value > 0) if ext.get_balance(msg.to) >= value and msg.depth < 1024: @@ -566,7 +654,11 @@ def vm_execute(ext, msg, code): cd = CallData(mem, meminstart, meminsz) call_msg = Message(msg.to, to, value, submsg_gas, cd, msg.depth + 1, code_address=to) - result, gas, data = ext.msg(call_msg) + result, gas, data, trace = ext.msg(call_msg) + if trace_vm: + trace_extend = trace + trace_data["gasCost"] = submsg_gas - gas + trace_data["gas"] -= trace_data["gasCost"] if result == 0: stk.append(0) else: @@ -587,7 +679,10 @@ def vm_execute(ext, msg, code): value = 0 if not mem_extend(mem, compustate, op, meminstart, meminsz) or \ not mem_extend(mem, compustate, op, memoutstart, memoutsz): - return vm_exception('OOG EXTENDING MEMORY') + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break extra_gas = (value > 0) * opcodes.GCALLVALUETRANSFER + \ ext.post_anti_dos_hardfork * opcodes.CALL_SUPPLEMENTAL_GAS # ^ EIP150 Increase the gas cost of CALLCODE, DELEGATECALL to 700 @@ -597,11 +692,17 @@ def vm_execute(ext, msg, code): # the maximum allowed amount, call with all but one 64th of the # maximum allowed amount of gas if compustate.gas < extra_gas: - return vm_exception('OUT OF GAS', needed=extra_gas) + err = "OUT OF GAS" + vm_exception('OUT OF GAS', needed=extra_gas) + if trace_vm: traceData.append(trace_data) + break; gas = min(gas, max_call_gas(compustate.gas - extra_gas)) else: if compustate.gas < gas + extra_gas: - return vm_exception('OUT OF GAS', needed=gas + extra_gas) + err = "OUT OF GAS" + vm_exception('OUT OF GAS', needed=gas + extra_gas) + if trace_vm: traceData.append(trace_data) + break submsg_gas = gas + opcodes.GSTIPEND * (value > 0) if ext.get_balance(msg.to) >= value and msg.depth < 1024: @@ -613,11 +714,18 @@ def vm_execute(ext, msg, code): call_msg = Message(msg.sender, msg.to, msg.value, submsg_gas, cd, msg.depth + 1, code_address=to, transfers_value=False) elif op == 'DELEGATECALL': - return vm_exception('OPCODE INACTIVE') + err = "OPCODE INACTIVE" + vm_exception('OPCODE INACTIVE') + if trace_vm: traceData.append(trace_data) + break else: call_msg = Message(msg.to, msg.to, value, submsg_gas, cd, msg.depth + 1, code_address=to) - result, gas, data = ext.msg(call_msg) + result, gas, data, trace = ext.msg(call_msg) + if trace_vm: + trace_extend = trace + trace_data["gasCost"] = submsg_gas - gas + trace_data["gas"] -= trace_data["gasCost"] if result == 0: stk.append(0) else: @@ -631,8 +739,16 @@ def vm_execute(ext, msg, code): elif op == 'RETURN': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, s1): - return vm_exception('OOG EXTENDING MEMORY') - return peaceful_exit('RETURN', compustate.gas, mem[s0: s0 + s1]) + err = "OOG EXTENDING MEMORY" + vm_exception('OOG EXTENDING MEMORY') + if trace_vm: traceData.append(trace_data) + break + err = "RETURN" + gas = compustate.gas + ret = mem[s0: s0 + s1] + peaceful_exit('RETURN', compustate.gas, ret) + if trace_vm: traceData.append(trace_data) + break elif op == 'SUICIDE': to = utils.encode_int(stk.pop()) to = ((b'\x00' * (32 - len(to))) + to)[12:] @@ -644,20 +760,46 @@ def vm_execute(ext, msg, code): # ^ EIP150(1c) If SUICIDE hits a newly created account, it # triggers an additional gas cost of 25000 (similar to CALLs) if not eat_gas(compustate, extra_gas): - return vm_exception("OUT OF GAS") + err = "OUT OF GAS" + vm_exception("OUT OF GAS") + if trace_vm: traceData.append(trace_data) + break xfer = ext.get_balance(msg.to) ext.set_balance(to, ext.get_balance(to) + xfer) ext.set_balance(msg.to, 0) ext.add_suicide(msg.to) # print('suiciding %s %s %d' % (msg.to, to, xfer)) - return 1, compustate.gas, [] + err = "SUICIDE" + if trace_vm: traceData.append(trace_data) + break + #1, compustate.gas, [] # this is slow! # for a in stk: # assert is_numeric(a), (op, stk) # assert a >= 0 and a < 2**256, (a, op, stk) + # insert sub log + if trace_vm: + traceData.append(trace_data) + if trace_extend: + for a in trace_extend: + traceData.append(a) + trace_extend = None + + if trace_vm: + if not err in [ "RETURN", "CODE OUT OF RANGE", "STOP", "SUICIDE" ]: + # insert error + traceData[-1]["error"] = err + if log_vm_op.is_active('trace'): + log_vm_op.trace('vm', **traceData) + if err in [ "RETURN", "CODE OUT OF RANGE", "STOP" ]: + return 1, gas, ret, traceData + elif err in [ "SUICIDE" ]: + return 1, compustate.gas, ret, traceData + else: + return 0, 0, ret, traceData class VmExtBase():