diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index 44de3d4c2..580629f11 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -192,7 +192,8 @@ proc commit*(c: Computation) = c.vmState.accountDb.commit(c.savePoint) proc dispose*(c: Computation) {.inline.} = - c.vmState.accountDb.dispose(c.savePoint) + c.vmState.accountDb.safeDispose(c.savePoint) + c.savePoint = nil proc rollback*(c: Computation) = c.vmState.accountDb.rollback(c.savePoint) @@ -228,18 +229,14 @@ proc initAddress(x: int): EthAddress {.compileTime.} = result[19] = x.byte const ripemdAddr = initAddress(3) proc executeOpcodes*(c: Computation) {.gcsafe.} -proc execCall*(c: Computation) = +proc beforeExecCall(c: Computation) = c.snapshot() - defer: - c.dispose() - if c.msg.kind == evmcCall: c.vmState.mutateStateDb: db.subBalance(c.msg.sender, c.msg.value) db.addBalance(c.msg.contractAddress, c.msg.value) - executeOpcodes(c) - +proc afterExecCall(c: Computation) = ## Collect all of the accounts that *may* need to be deleted based on EIP161 ## https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md ## also see: https://github.com/ethereum/EIPs/issues/716 @@ -255,7 +252,7 @@ proc execCall*(c: Computation) = else: c.rollback() -proc execCreate*(c: Computation) = +proc beforeExecCreate(c: Computation): bool = c.vmState.mutateStateDB: db.incNonce(c.msg.sender) @@ -266,13 +263,11 @@ proc execCreate*(c: Computation) = db.accessList(c.msg.contractAddress) c.snapshot() - defer: - c.dispose() if c.vmState.readOnlyStateDb().hasCodeOrNonce(c.msg.contractAddress): c.setError("Address collision when creating contract address={c.msg.contractAddress.toHex}", true) c.rollback() - return + return true c.vmState.mutateStateDb: db.subBalance(c.msg.sender, c.msg.value) @@ -282,8 +277,9 @@ proc execCreate*(c: Computation) = # EIP161 nonce incrementation db.incNonce(c.msg.contractAddress) - executeOpcodes(c) + return false +proc afterExecCreate(c: Computation) = if c.isSuccess: let fork = c.fork let contractFailed = not c.writeContract(fork) @@ -295,6 +291,55 @@ proc execCreate*(c: Computation) = else: c.rollback() +proc beforeExec(c: Computation): bool = + if not c.msg.isCreate: + c.beforeExecCall() + false + else: + c.beforeExecCreate() + +proc afterExec(c: Computation) = + if not c.msg.isCreate: + c.afterExecCall() + else: + c.afterExecCreate() + +template chainTo*(c, toChild: Computation, after: untyped) = + c.child = toChild + c.continuation = proc() = + after + +proc execCallOrCreateAux(c: var Computation) {.noinline.} = + # Perform recursion with minimum-size stack per level. The exception + # handling is very subtle. Each call to `snapshot` must have a corresponding + # `dispose` on exception. To minimise this proc's stackframe, `defer` is + # moved to the outermost proc only. `{.noinline.}` is also used to make + # extra sure they stay separate. `c` is a `var` parameter at every level of + # recursion, so the outermost proc sees every change to `c`, which is why `c` + # is updated instead of using `let`. On exception, the outermost `defer` + # walks the `c.parent` chain to call `dispose` on each `c`. + if c.beforeExec(): + return + c.executeOpcodes() + while not c.continuation.isNil: + # Parent and child refs are updated and cleared so as to avoid circular + # refs (like a double-linked list) or dangling refs (to finished child). + (c.child, c, c.parent) = (nil.Computation, c.child, c) + execCallOrCreateAux(c) + c.dispose() + (c.parent, c) = (nil.Computation, c.parent) + (c.continuation)() + c.executeOpcodes() + c.afterExec() + +proc execCallOrCreate*(cParam: Computation) = + var c = cParam + defer: + while not c.isNil: + c.dispose() + c = c.parent + execCallOrCreateAux(c) + proc merge*(c, child: Computation) = c.logEntries.add child.logEntries c.gasMeter.refundGas(child.gasMeter.gasRefunded) diff --git a/nimbus/vm/evmc_host.nim b/nimbus/vm/evmc_host.nim index 2a72bbf8b..72b66e6f1 100644 --- a/nimbus/vm/evmc_host.nim +++ b/nimbus/vm/evmc_host.nim @@ -135,7 +135,7 @@ template createImpl(c: Computation, m: nimbus_message, res: nimbus_result) = ) let child = newComputation(c.vmState, childMsg, Uint256.fromEvmc(m.create2_salt)) - child.execCreate() + child.execCallOrCreate() if not child.shouldBurnGas: res.gas_left = child.gasMeter.gasRemaining @@ -167,7 +167,7 @@ template callImpl(c: Computation, m: nimbus_message, res: nimbus_result) = ) let child = newComputation(c.vmState, childMsg) - child.execCall() + child.execCallOrCreate() if not child.shouldBurnGas: res.gas_left = child.gasMeter.gasRemaining diff --git a/nimbus/vm/interpreter.nim b/nimbus/vm/interpreter.nim index f12dfb1d7..543949c91 100644 --- a/nimbus/vm/interpreter.nim +++ b/nimbus/vm/interpreter.nim @@ -38,8 +38,8 @@ export vmc.addLogEntry, vmc.commit, vmc.dispose, - vmc.execCall, - vmc.execCreate, + vmc.execCallOrCreate, + vmc.chainTo, vmc.execSelfDestruct, vmc.executeOpcodes, vmc.fork, diff --git a/nimbus/vm/interpreter/opcodes_impl.nim b/nimbus/vm/interpreter/opcodes_impl.nim index 5efe0e246..e8a4b15d3 100644 --- a/nimbus/vm/interpreter/opcodes_impl.nim +++ b/nimbus/vm/interpreter/opcodes_impl.nim @@ -678,15 +678,15 @@ template genCreate(callName: untyped, opCode: Op): untyped = ) var child = newComputation(c.vmState, childMsg, salt) - child.execCreate() - if not child.shouldBurnGas: - c.gasMeter.returnGas(child.gasMeter.gasRemaining) + c.chainTo(child): + if not child.shouldBurnGas: + c.gasMeter.returnGas(child.gasMeter.gasRemaining) - if child.isSuccess: - c.merge(child) - c.stack.top child.msg.contractAddress - else: - c.returnData = child.output + if child.isSuccess: + c.merge(child) + c.stack.top child.msg.contractAddress + else: + c.returnData = child.output genCreate(create, Create) genCreate(create2, Create2) @@ -870,20 +870,19 @@ template genCall(callName: untyped, opCode: Op): untyped = flags: flags) var child = newComputation(c.vmState, msg) - child.execCall() + c.chainTo(child): + if not child.shouldBurnGas: + c.gasMeter.returnGas(child.gasMeter.gasRemaining) - if not child.shouldBurnGas: - c.gasMeter.returnGas(child.gasMeter.gasRemaining) + if child.isSuccess: + c.merge(child) + c.stack.top(1) - if child.isSuccess: - c.merge(child) - c.stack.top(1) - - c.returnData = child.output - let actualOutputSize = min(memOutLen, child.output.len) - if actualOutputSize > 0: - c.memory.write(memOutPos, - child.output.toOpenArray(0, actualOutputSize - 1)) + c.returnData = child.output + let actualOutputSize = min(memOutLen, child.output.len) + if actualOutputSize > 0: + c.memory.write(memOutPos, + child.output.toOpenArray(0, actualOutputSize - 1)) genCall(call, Call) genCall(callCode, CallCode) diff --git a/nimbus/vm/interpreter_dispatch.nim b/nimbus/vm/interpreter_dispatch.nim index af7e3f59b..593d76dc1 100644 --- a/nimbus/vm/interpreter_dispatch.nim +++ b/nimbus/vm/interpreter_dispatch.nim @@ -272,6 +272,9 @@ proc opTableToCaseStmt(opTable: array[Op, NimNode], c: NimNode): NimNode = `opImpl`(`c`) if `c`.tracingEnabled: `c`.traceOpCodeEnded(`asOp`, `c`.opIndex) + when `asOp` in {Create, Create2, Call, CallCode, DelegateCall, StaticCall}: + if not `c`.continuation.isNil: + return else: quote do: if `c`.tracingEnabled: @@ -279,6 +282,9 @@ proc opTableToCaseStmt(opTable: array[Op, NimNode], c: NimNode): NimNode = `opImpl`(`c`) if `c`.tracingEnabled: `c`.traceOpCodeEnded(`asOp`, `c`.opIndex) + when `asOp` in {Create, Create2, Call, CallCode, DelegateCall, StaticCall}: + if not `c`.continuation.isNil: + return when `asOp` in {Return, Revert, SelfDestruct}: break @@ -380,7 +386,9 @@ proc executeOpcodes(c: Computation) = let fork = c.fork block: - if c.execPrecompiles(fork): + if not c.continuation.isNil: + c.continuation = nil + elif c.execPrecompiles(fork): break try: @@ -388,6 +396,6 @@ proc executeOpcodes(c: Computation) = except CatchableError as e: c.setError(&"Opcode Dispatch Error msg={e.msg}, depth={c.msg.depth}", true) - if c.isError(): + if c.isError() and c.continuation.isNil: if c.tracingEnabled: c.traceError() debug "executeOpcodes error", msg=c.error.info diff --git a/nimbus/vm/state_transactions.nim b/nimbus/vm/state_transactions.nim index fa17ac4ad..f6e471849 100644 --- a/nimbus/vm/state_transactions.nim +++ b/nimbus/vm/state_transactions.nim @@ -36,12 +36,11 @@ proc setupComputation*(vmState: BaseVMState, tx: Transaction, sender: EthAddress doAssert result.isOriginComputation proc execComputation*(c: Computation) = - if c.msg.isCreate: - c.execCreate() - else: + if not c.msg.isCreate: c.vmState.mutateStateDB: db.incNonce(c.msg.sender) - c.execCall() + + c.execCallOrCreate() if c.isSuccess: c.refundSelfDestruct() diff --git a/nimbus/vm/types.nim b/nimbus/vm/types.nim index e5776c812..a1aa9faf6 100644 --- a/nimbus/vm/types.nim +++ b/nimbus/vm/types.nim @@ -85,6 +85,8 @@ type savePoint*: SavePoint instr*: Op opIndex*: int + parent*, child*: Computation + continuation*: proc() {.gcsafe.} Error* = ref object info*: string diff --git a/nimbus/vm_computation.nim b/nimbus/vm_computation.nim index be222385b..06ed1fe16 100644 --- a/nimbus/vm_computation.nim +++ b/nimbus/vm_computation.nim @@ -19,8 +19,8 @@ export vmc.addLogEntry, vmc.commit, vmc.dispose, - vmc.execCall, - vmc.execCreate, + vmc.execCallOrCreate, + vmc.chainTo, vmc.execSelfDestruct, vmc.executeOpcodes, vmc.fork,