diff --git a/nimbus/transaction/evmc_host_glue.nim b/nimbus/transaction/evmc_host_glue.nim new file mode 100644 index 000000000..72c84531b --- /dev/null +++ b/nimbus/transaction/evmc_host_glue.nim @@ -0,0 +1,119 @@ +# Nimbus - Binary compatibility on the host side of the EVMC API interface +# +# Copyright (c) 2019-2021 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +#{.push raises: [Defect].} + +when not declaredInScope(included_from_host_services): + {.error: "Do not import this file directly, import host_services instead".} + +import evmc/evmc + +template toHost(p: evmc_host_context): TransactionHost = + cast[TransactionHost](p) + +proc accountExists(p: evmc_host_context, address: var evmc_address): c99bool {.cdecl.} = + toHost(p).accountExists(address.fromEvmc) + +proc getStorage(p: evmc_host_context, address: var evmc_address, + key: var evmc_bytes32): evmc_bytes32 {.cdecl.} = + toHost(p).getStorage(address.fromEvmc, key.fromEvmc).toEvmc + +proc setStorage(p: evmc_host_context, address: var evmc_address, + key, value: var evmc_bytes32): evmc_storage_status {.cdecl.} = + toHost(p).setStorage(address.fromEvmc, key.fromEvmc, value.fromEvmc) + +proc getBalance(p: evmc_host_context, + address: var evmc_address): evmc_uint256be {.cdecl.} = + toHost(p).getBalance(address.fromEvmc).toEvmc + +proc getCodeSize(p: evmc_host_context, + address: var evmc_address): csize_t {.cdecl.} = + toHost(p).getCodeSize(address.fromEvmc) + +proc getCodeHash(p: evmc_host_context, + address: var evmc_address): evmc_bytes32 {.cdecl.} = + toHost(p).getCodeHash(address.fromEvmc).toEvmc + +proc copyCode(p: evmc_host_context, address: var evmc_address, code_offset: csize_t, + buffer_data: ptr byte, buffer_size: csize_t): csize_t {.cdecl.} = + toHost(p).copyCode(address.fromEvmc, code_offset, buffer_data, buffer_size) + +proc selfDestruct(p: evmc_host_context, address, + beneficiary: var evmc_address) {.cdecl.} = + toHost(p).selfDestruct(address.fromEvmc, beneficiary.fromEvmc) + +proc call(p: evmc_host_context, msg: var evmc_message): evmc_result {.cdecl.} = + toHost(p).call(msg) + +proc getTxContext(p: evmc_host_context): evmc_tx_context {.cdecl.} = + toHost(p).getTxContext() + +proc getBlockHash(p: evmc_host_context, number: int64): evmc_bytes32 {.cdecl.} = + # TODO: `HostBlockNumber` is 256-bit unsigned. It should be changed to match + # EVMC which is more sensible. + toHost(p).getBlockHash(number.uint64.u256).toEvmc + +proc emitLog(p: evmc_host_context, address: var evmc_address, + data: ptr byte, data_size: csize_t, + topics: ptr evmc_bytes32, topics_count: csize_t) {.cdecl.} = + toHost(p).emitLog(address.fromEvmc, data, data_size, + cast[ptr HostTopic](topics), topics_count) + +proc evmcGetHostInterface(): ref evmc_host_interface = + var theHostInterface {.global, threadvar.}: ref evmc_host_interface + if theHostInterface.isNil: + theHostInterface = (ref evmc_host_interface)( + account_exists: accountExists, + get_storage: getStorage, + set_storage: setStorage, + get_balance: getBalance, + get_code_size: getCodeSize, + get_code_hash: getCodeHash, + copy_code: copyCode, + selfdestruct: selfDestruct, + call: call, + get_tx_context: getTxContext, + get_block_hash: getBlockHash, + emit_log: emitLog, + ) + return theHostInterface + +# The built-in Nimbus EVM, via imported C function. +proc evmc_create_nimbus_evm(): ptr evmc_vm {.cdecl, importc.} + +proc evmcExecComputation*(host: TransactionHost, code: seq[byte]): EvmcResult = + let vm = evmc_create_nimbus_evm() + if vm.isNil: + echo "Warning: No EVM" + # Nim defaults are fine for all other fields in the result object. + return EvmcResult(status_code: EVMC_INTERNAL_ERROR) + + let hostInterface = evmcGetHostInterface() + let hostContext = cast[evmc_host_context](host) + + # Without `{.gcsafe.}:` here, the call via `vm.execute` results in a Nim + # compile-time error in a far away function. Starting here, a cascade of + # warnings takes place: "Warning: '...' is not GC-safe as it performs an + # indirect call here [GCUnsafe2]", then a list of "Warning: '...' is not + # GC-safe as it calls '...'" at each function up the call stack, to a high + # level function `persistBlocks` where it terminates compilation as an error + # instead of a warning. + # + # It is tempting to annotate all EVMC API functions with `{.cdecl, gcsafe.}`, + # overriding the function signatures from the Nim EVMC module. Perhaps we + # will do that, though it's conceptually dubious, as the two sides of the + # EVMC ABI live in different GC worlds (when loaded as a shared library with + # its own Nim runtime), very similar to calling between threads. + # + # TODO: But wait: Why does the Nim EVMC test program compile fine without + # any `gcsafe`, even with `--threads:on`? + {.gcsafe.}: + vm.execute(vm, hostInterface[].addr, hostContext, + evmc_revision(host.vmState.fork), host.msg, + if code.len > 0: code[0].unsafeAddr else: nil, + code.len.csize_t) diff --git a/nimbus/transaction/host_services.nim b/nimbus/transaction/host_services.nim index 432046c2b..727db2375 100644 --- a/nimbus/transaction/host_services.nim +++ b/nimbus/transaction/host_services.nim @@ -64,6 +64,16 @@ proc setupTxContext(host: TransactionHost) = host.txContext.block_difficulty = vmState.blockHeader.difficulty.toEvmc host.txContext.chain_id = vmState.chaindb.config.chainId.uint.u256.toEvmc +const use_evmc_glue = defined(evmc_enabled) + +# When using the EVMC binary interface, each of the functions below is wrapped +# in another function that converts types to be compatible with the binary +# interface, and the functions below are not called directly. The conversions +# mostly just cast between byte-compatible types, so to avoid a redundant call +# layer, make the functions below `{.inline.}` when wrapped in this way. +when use_evmc_glue: + {.push inline.} + proc accountExists(host: TransactionHost, address: HostAddress): bool = if host.vmState.fork >= FkSpurious: not host.vmState.readOnlyStateDB.isDeadAccount(address) @@ -207,6 +217,11 @@ proc emitLog(host: TransactionHost, address: HostAddress, log.address = address host.logEntries.add(log) -export - accountExists, getStorage, storage, getBalance, getCodeSize, getCodeHash, - copyCode, selfDestruct, getTxContext, call, getBlockHash, emitLog +when use_evmc_glue: + {.pop: inline.} + const included_from_host_services = true + include ./evmc_host_glue +else: + export + accountExists, getStorage, storage, getBalance, getCodeSize, getCodeHash, + copyCode, selfDestruct, getTxContext, call, getBlockHash, emitLog