Skip to content
This repository was archived by the owner on May 23, 2023. It is now read-only.

EVMJIT support #406

Closed
wants to merge 11 commits into from
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
186 changes: 186 additions & 0 deletions ethereum/jitvm.py
Original file line number Diff line number Diff line change
@@ -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
5 changes: 4 additions & 1 deletion ethereum/processblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion ethereum/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion fixtures
Submodule fixtures updated 652 files
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ search = version = "{current_version}"
[aliases]
test = pytest

[flake8]
max-line-length = 100