diff --git a/ethereum/jitvm.py b/ethereum/jitvm.py new file mode 100644 index 000000000..98a020d52 --- /dev/null +++ b/ethereum/jitvm.py @@ -0,0 +1,186 @@ +from evmjit import EVMJIT +from ethereum import opcodes +from ethereum.utils import sha3_256, decode_int +from ethereum.vm import CallData, Message + +from binascii import hexlify + +jit = EVMJIT() + + +class JitEnv(object): + def __init__(self, ext, msg): + self.ext = ext + self.msg = msg + + def get_balance(self, addr): + addr = addr.encode('hex') + if addr not in self.pre: + return 0 + return int(self.pre[addr]['balance'], 16) + + def query(self, key, arg): + print("query(key: {}, arg: {})".format(key, arg)) + if key == EVMJIT.SLOAD: + v = self.ext.get_storage_data(self.msg.to, arg) + print("SLOAD({}): {}".format(arg, v)) + return v + if key == EVMJIT.ADDRESS: + return self.msg.to + if key == EVMJIT.CALLER: + return self.msg.sender + if key == EVMJIT.ORIGIN: + return self.ext.tx_origin + if key == EVMJIT.GAS_PRICE: + return self.ext.tx_gasprice + if key == EVMJIT.COINBASE: + return self.ext.block_coinbase + if key == EVMJIT.NUMBER: + n = self.ext.block_number + print("NUMBER: {}".format(n)) + return n + if key == EVMJIT.TIMESTAMP: + n = self.ext.block_timestamp + print("TIMESTAMP: {}".format(n)) + return n + if key == EVMJIT.GAS_LIMIT: + return self.ext.block_gas_limit + if key == EVMJIT.DIFFICULTY: + return self.ext.block_difficulty + if key == EVMJIT.BLOCKHASH: + block_hash = self.ext.block_hash(arg) + if not block_hash: + # Do not return empty bytes, but 0 hash. + block_hash = b'\x00' * 32 + print("BLOCKHASH({}): {}".format(arg, hexlify(block_hash))) + return block_hash + if key == EVMJIT.CODE_BY_ADDRESS: + addr = arg[:] + code = self.ext.get_code(addr) + print("EXTCODE({}): {}".format(hexlify(arg), hexlify(code))) + return code + if key == EVMJIT.BALANCE: + addr = arg[:] # Copy + b = self.ext.get_balance(addr) + print("BALANCE({}): {}".format(hexlify(addr), b)) + return b + assert False, "Implement ME!" + + def update(self, key, arg1, arg2): + if key == EVMJIT.SSTORE: + print("SSTORE({}, {})".format(arg1, arg2)) + if arg2 == 0 and self.ext.get_storage_data(self.msg.to, arg1): + print("refund") + self.ext.add_refund(opcodes.GSTORAGEREFUND) + + self.ext.set_storage_data(self.msg.to, arg1, arg2) + elif key == EVMJIT.SELFDESTRUCT: + print("SELFDESTRUCT({})".format(hexlify(arg1))) + # Copy the argument to bytes because some API freaks out otherwise. + addr = arg1[:] + assert len(addr) == 20 + # TODO: This logic should go to VMExt + xfer = self.ext.get_balance(self.msg.to) + self.ext.set_balance(addr, self.ext.get_balance(addr) + xfer) + self.ext.set_balance(self.msg.to, 0) + self.ext.add_suicide(self.msg.to) + elif key == EVMJIT.LOG: + print("LOG {}".format(map(hexlify, arg2))) + # Make a copy of data because pyethereum expects bytes type + # not buffer protocol. + data = bytes(arg1) + # Convert topics to ints. + topics = [decode_int(bytes(t).lstrip('\0')) for t in arg2] + self.ext.log(self.msg.to, topics, data) + else: + assert False, "Unknown EVM-C update key" + + def call(self, kind, gas, address, value, input): + # First convert bytes to a list of int to allow CallData to pack it + # again to bytes. WTF???????? + call_data = CallData(map(ord, input)) + # Convert to bytes becase rlp.encode_hex requires str or unicode. WTF? + address = bytes(address) + msg = Message(self.msg.to, address, value, gas, call_data, + self.msg.depth + 1, code_address=address) + if kind == EVMJIT.CREATE: + if self.msg.depth >= 1024: + return EVMJIT.FAILURE, b'', 0 + if value and self.ext.get_balance(self.msg.to) < value: + print("CREATE: no balance") + return EVMJIT.FAILURE, b'', 0 + # TODO: msg.address is invalid + print("CREATEing...") + o, gas_left, addr = self.ext.create(msg) + if addr: + res_code = EVMJIT.SUCCESS + else: + res_code = EVMJIT.FAILURE + addr = b'\0' * 20 # EVMJIT expects 0 address + print("CREATE(gas: {}, value: {}, code: {}): {}".format( + gas, value, hexlify(input), hexlify(addr))) + # FIXME: Change out args order to match ext.create() + return EVMJIT.SUCCESS, addr, gas - gas_left + + if kind == EVMJIT.DELEGATECALL: + assert value == 0 # Guaranteed by the pyevmjit. + + cost = msg.gas + if value and kind != EVMJIT.DELEGATECALL: + cost += 9000 + msg.gas += 2300 + + name = 'CALL' + if kind == EVMJIT.CALL and not self.ext.account_exists(address): + cost += 25000 + elif kind == EVMJIT.CALLCODE: + name = 'CALLCODE' + msg.to = self.msg.to + msg.code_address = address + elif kind == EVMJIT.DELEGATECALL: + name = 'DELEGATECALL' + msg.sender = self.msg.sender + msg.to = self.msg.to + msg.code_address = address + msg.value = self.msg.value + msg.transfers_value = False + + if self.msg.depth >= 1024 or \ + (value and self.ext.get_balance(self.msg.to) < value): + cost -= msg.gas + print("{}: no gas".format(name)) + return EVMJIT.FAILURE, b'', cost + + print("{}({}, value: {}, gas: {})".format( + name, hexlify(address), value, gas)) + result, gas_left, out = self.ext.msg(msg) + print(out) + cost -= gas_left + assert cost >= 0 + res_code = EVMJIT.SUCCESS if result else EVMJIT.FAILURE + # The output must be bytes, not list of ints. WTF? + out = b''.join(map(chr, out)) + print(out) + + print(" -> {}({}, cost: {}): {}".format( + name, hexlify(address), cost, hexlify(out))) + return res_code, out, cost + + +def vm_execute(ext, msg, code): + # FIXME: This is needed for ext.get_code() to work. WTF?????????? + # ext._block.commit_state() + # pprint(msg.__dict__) + # EVMJIT requires secure hash of the code to be used as the code + # identifier. + # TODO: Can we avoid recalculating it? + code_hash = sha3_256(code) + mode = (EVMJIT.HOMESTEAD if ext.post_homestead_hardfork() + else EVMJIT.FRONTIER) + data = msg.data.extract_all() + env = JitEnv(ext, msg) + print("Execute{}: {}".format(mode, hexlify(code))) + result = jit.execute(env, mode, code_hash, code, msg.gas, data, msg.value) + # Convert to list of ints + output = map(ord, result.output) + return result.code == EVMJIT.SUCCESS, result.gas_left, output diff --git a/ethereum/processblock.py b/ethereum/processblock.py index 2f5d0bc1b..555a2998b 100644 --- a/ethereum/processblock.py +++ b/ethereum/processblock.py @@ -12,10 +12,13 @@ from ethereum.utils import safe_ord, normalize_address, mk_contract_address, \ mk_metropolis_contract_address, big_endian_to_int from ethereum import transactions +from ethereum import jitvm import ethereum.config as config sys.setrecursionlimit(100000) +vm_execute = jitvm.vm_execute + from ethereum.slogging import get_logger log_tx = get_logger('eth.pb.tx') log_msg = get_logger('eth.pb.msg') @@ -280,7 +283,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 = vm_execute(ext, msg, code) # gas = int(gas) # assert utils.is_numeric(gas) if trace_msg: diff --git a/ethereum/testutils.py b/ethereum/testutils.py index e0627f860..64a17eddc 100644 --- a/ethereum/testutils.py +++ b/ethereum/testutils.py @@ -16,6 +16,9 @@ import time from ethereum import ethash from ethereum import ethash_utils +from ethereum import jitvm + +vm_execute = jitvm.vm_execute db = EphemDB() db_env = Env(db) @@ -219,7 +222,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 = vm_execute(ext, msg, code) if profiler: profiler.disable() pb.apply_msg = orig_apply_msg @@ -396,6 +399,8 @@ def blkhash(n): 'out', 'gas', 'logs', 'postStateRoot']: _shouldbe = params1.get(k, None) _reallyis = params2.get(k, None) + if k == 'out' and _shouldbe.startswith(u'#'): + _reallyis = u'#{}'.format(len(output)) if _shouldbe != _reallyis: print(('Mismatch {key}: shouldbe {shouldbe_key} != reallyis {reallyis_key}.\n' 'post: {shouldbe_post} != {reallyis_post}').format( diff --git a/fixtures b/fixtures index 3ccc0b369..7d69336e7 160000 --- a/fixtures +++ b/fixtures @@ -1 +1 @@ -Subproject commit 3ccc0b3696b358383a71cc40f1df6baa010b1b50 +Subproject commit 7d69336e76346055ca9e6a1624914fdb6e447396 diff --git a/setup.cfg b/setup.cfg index db259b158..5ac50db7d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,3 +9,5 @@ search = version = "{current_version}" [aliases] test = pytest +[flake8] +max-line-length = 100