diff --git a/nimbus/transaction/evmc_host_glue.nim b/nimbus/transaction/evmc_host_glue.nim index 64652f1f5..150f0b91c 100644 --- a/nimbus/transaction/evmc_host_glue.nim +++ b/nimbus/transaction/evmc_host_glue.nim @@ -21,15 +21,15 @@ proc accountExists(p: evmc_host_context, address: var evmc_address): c99bool {.c 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 + toHost(p).getStorage(address.fromEvmc, key.flip256.fromEvmc).toEvmc.flip256 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) + toHost(p).setStorage(address.fromEvmc, key.flip256.fromEvmc, value.flip256.fromEvmc) proc getBalance(p: evmc_host_context, address: var evmc_address): evmc_uint256be {.cdecl.} = - toHost(p).getBalance(address.fromEvmc).toEvmc + toHost(p).getBalance(address.fromEvmc).toEvmc.flip256 proc getCodeSize(p: evmc_host_context, address: var evmc_address): csize_t {.cdecl.} = @@ -48,9 +48,14 @@ proc selfDestruct(p: evmc_host_context, address, toHost(p).selfDestruct(address.fromEvmc, beneficiary.fromEvmc) proc call(p: evmc_host_context, msg: var evmc_message): evmc_result {.cdecl.} = + # This would contain `flip256`, but `call` is special. The C stack usage + # must be kept small for deeply nested EVM calls. To ensure small stack, + # `flip256` must be handled at `host_call_nested`, not here. toHost(p).call(msg) proc getTxContext(p: evmc_host_context): evmc_tx_context {.cdecl.} = + # This would contain `flip256`, but due to this result being cached in + # `getTxContext`, it's better to do `flip256` when filling the cache. toHost(p).getTxContext() proc getBlockHash(p: evmc_host_context, number: int64): evmc_bytes32 {.cdecl.} = @@ -70,7 +75,7 @@ proc accessAccount(p: evmc_host_context, proc accessStorage(p: evmc_host_context, address: var evmc_address, key: var evmc_bytes32): evmc_access_status {.cdecl.} = - toHost(p).accessStorage(address.fromEvmc, key.fromEvmc) + toHost(p).accessStorage(address.fromEvmc, key.flip256.fromEvmc) let hostInterface = evmc_host_interface( account_exists: accountExists, diff --git a/nimbus/transaction/host_call_nested.nim b/nimbus/transaction/host_call_nested.nim index 377752319..7f877b760 100644 --- a/nimbus/transaction/host_call_nested.nim +++ b/nimbus/transaction/host_call_nested.nim @@ -111,6 +111,13 @@ proc beforeExecEvmcNested(host: TransactionHost, msg: EvmcMessage): Computation # This function must be declared with `{.noinline.}` to make sure it doesn't # contribute to the stack frame of `callEvmcNested` below. {.noinline.} = + # `call` is special. Most host functions do `flip256` in `evmc_host_glue` + # and `show` in `host_services`, but `call` needs to minimise C stack used + # by nested EVM calls. Just `flip256` in glue's `call` adds a lot of + # stack: +65% in tests, enough to blow our 750kiB test stack target and + # crash. Easily avoided by doing `flip256` and `show` out-of-line here. + var msg = msg # Make a local copy that's ok to modify. + msg.value = flip256(msg.value) host.showCallEntry(msg) let c = if msg.kind == EVMC_CREATE or msg.kind == EVMC_CREATE2: beforeExecCreateEvmcNested(host, msg) @@ -138,9 +145,12 @@ proc afterExecEvmcNested(host: TransactionHost, child: Computation, host.showCallReturn(result, kind.isCreate) template callEvmcNested*(host: TransactionHost, msg: EvmcMessage): EvmcResult = - # This function must be declared `template` to ensure it is inlined at Nim - # level to its caller across `import`. C level `{.inline.}` won't do this. - # Note that template parameters `host` and `msg` are multiple-evaluated. + # `call` is special. The C stack usage must be kept small for deeply nested + # EVM calls. To ensure small stack, this function must use `template` to + # inline at Nim level (same for `host.call(msg)`). `{.inline.}` is not good + # enough. Due to object return it ends up using a lot more stack. (Note + # that template parameters `host` and `msg` are multiple-evaluated here; + # simple expressions must be used when calling.) let child = beforeExecEvmcNested(host, msg) child.execCallOrCreate() afterExecEvmcNested(host, child, msg.kind) diff --git a/nimbus/transaction/host_services.nim b/nimbus/transaction/host_services.nim index bb4b2e843..55be06c35 100644 --- a/nimbus/transaction/host_services.nim +++ b/nimbus/transaction/host_services.nim @@ -65,6 +65,15 @@ proc setupTxContext(host: TransactionHost) = host.txContext.chain_id = vmState.chaindb.config.chainId.uint.u256.toEvmc host.txContext.block_base_fee = vmState.blockHeader.baseFee.toEvmc + # Most host functions do `flip256` in `evmc_host_glue`, but due to this + # result being cached, it's better to do `flip256` when filling the cache. + host.txContext.tx_gas_price = flip256(host.txContext.tx_gas_price) + host.txContext.block_difficulty = flip256(host.txContext.block_difficulty) + host.txContext.chain_id = flip256(host.txContext.chain_id) + host.txContext.block_base_fee = flip256(host.txContext.block_base_fee) + + host.cachedTxContext = true + const use_evmc_glue = defined(evmc_enabled) # When using the EVMC binary interface, each of the functions below is wrapped @@ -243,7 +252,6 @@ template call(host: TransactionHost, msg: EvmcMessage): EvmcResult = proc getTxContext(host: TransactionHost): EvmcTxContext {.show.} = if not host.cachedTxContext: host.setupTxContext() - host.cachedTxContext = true return host.txContext proc getBlockHash(host: TransactionHost, number: HostBlockNumber): HostHash {.show.} = diff --git a/nimbus/vm/evmc_helpers.nim b/nimbus/vm/evmc_helpers.nim index 904b082d2..7952e27a4 100644 --- a/nimbus/vm/evmc_helpers.nim +++ b/nimbus/vm/evmc_helpers.nim @@ -1,7 +1,7 @@ import eth/common, stint, evmc/evmc, ../utils const - evmc_native* {.booldefine.} = true + evmc_native* {.booldefine.} = false func toEvmc*(a: EthAddress): evmc_address {.inline.} = cast[evmc_address](a)