2021-04-26 14:40:58 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2018 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.
|
|
|
|
|
|
|
|
const
|
|
|
|
# debugging flag, dump macro info when asked for
|
|
|
|
noisy {.intdefine.}: int = 0
|
|
|
|
# isNoisy {.used.} = noisy > 0
|
|
|
|
isChatty {.used.} = noisy > 1
|
|
|
|
|
|
|
|
import
|
|
|
|
../code_stream,
|
2021-04-26 16:00:46 +00:00
|
|
|
../computation,
|
2022-12-02 04:35:41 +00:00
|
|
|
../../common/evmforks,
|
2021-04-26 14:40:58 +00:00
|
|
|
./gas_costs,
|
|
|
|
./gas_meter,
|
|
|
|
./op_codes,
|
|
|
|
./op_handlers,
|
|
|
|
./op_handlers/oph_defs,
|
|
|
|
chronicles,
|
|
|
|
macros
|
|
|
|
|
|
|
|
export
|
2022-12-02 04:35:41 +00:00
|
|
|
EVMFork, Op,
|
2021-04-26 14:40:58 +00:00
|
|
|
oph_defs,
|
|
|
|
gas_meter
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Helpers
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
template handleStopDirective(k: var Vm2Ctx) =
|
Tracing: Remove some trace messages that occur a lot during sync
Disable some trace messages which appeared a lot in the output and probably
aren't so useful any more, when block processing is functioning well at high
speed.
Turning on the trace level globally is useful to get a feel for what's
happening, but only if each category is kept to a reasonable amount.
As well as overwhelming the output so that it's hard to see general activity,
some of these messages happen so much they severely slow down processing. Ones
called every time an EVM opcode uses some gas are particularly extreme.
These messages have all been chosen as things which are probably not useful any
more (the relevant functionality has been debugged and is tested plenty).
These have been commented out rather than removed. It may be that turning
trace topics on/off, or other selection, is a better longer term solution, but
that will require better command line options and good defaults for sure.
(I think higher levels `tracev` and `tracevv` levels (extra verbose) would be
more useful for this sort of deep tracing on request.)
For now, enabling `--log-level:TRACE` on the command line is quite useful as
long as we keep each category reasonable, and this patch tries to keep that
balance.
- Don't show "has transactions" on virtually every block imported.
- Don't show "Sender" and "txHash" lines on every transaction processed.
- Don't show "GAS CONSUMPTION" on every opcode executed", this is way too much.
- Don't show "GAS RETURNED" and "GAS REFUND" on each contract call.
- Don't show "op: Stop" on every Stop opcode, which means every transaction.
- Don't show "Insufficient funds" whenever a contract can't call another.
- Don't show "ECRecover", "SHA256 precompile", "RIPEMD160", "Identity"
or even "Call precompile" every time a precompile is called. These are
very well tested now.
- Don't show "executeOpcodes error" whenever a contract returns an error.
(This is changed to `trace` too, it's a normal event that is well tested.)
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-07-22 13:35:41 +00:00
|
|
|
#trace "op: Stop"
|
2021-04-26 14:40:58 +00:00
|
|
|
if not k.cpt.code.atEnd() and k.cpt.tracingEnabled:
|
|
|
|
# we only trace `REAL STOP` and ignore `FAKE STOP`
|
|
|
|
k.cpt.opIndex = k.cpt.traceOpCodeStarted(Stop)
|
|
|
|
k.cpt.traceOpCodeEnded(Stop, k.cpt.opIndex)
|
|
|
|
|
|
|
|
|
2022-12-02 04:35:41 +00:00
|
|
|
template handleFixedGasCostsDirective(fork: EVMFork; op: Op; k: var Vm2Ctx) =
|
2021-04-26 14:40:58 +00:00
|
|
|
if k.cpt.tracingEnabled:
|
|
|
|
k.cpt.opIndex = k.cpt.traceOpCodeStarted(op)
|
|
|
|
|
|
|
|
k.cpt.gasMeter.consumeGas(k.cpt.gasCosts[op].cost, reason = $op)
|
|
|
|
vmOpHandlers[fork][op].run(k)
|
|
|
|
|
|
|
|
if k.cpt.tracingEnabled:
|
|
|
|
k.cpt.traceOpCodeEnded(op, k.cpt.opIndex)
|
|
|
|
|
|
|
|
|
2022-12-02 04:35:41 +00:00
|
|
|
template handleOtherDirective(fork: EVMFork; op: Op; k: var Vm2Ctx) =
|
2021-04-26 14:40:58 +00:00
|
|
|
if k.cpt.tracingEnabled:
|
|
|
|
k.cpt.opIndex = k.cpt.traceOpCodeStarted(op)
|
|
|
|
|
|
|
|
vmOpHandlers[fork][op].run(k)
|
|
|
|
|
|
|
|
if k.cpt.tracingEnabled:
|
|
|
|
k.cpt.traceOpCodeEnded(op, k.cpt.opIndex)
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private, big nasty doubly nested case matrix generator
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
# reminiscent of Mamy's opTableToCaseStmt() from original VM
|
|
|
|
proc toCaseStmt(forkArg, opArg, k: NimNode): NimNode =
|
|
|
|
|
|
|
|
# Outer case/switch => Op
|
|
|
|
let branchOnOp = quote do: `opArg`
|
|
|
|
result = nnkCaseStmt.newTree(branchOnOp)
|
|
|
|
for op in Op:
|
2021-06-01 09:35:01 +00:00
|
|
|
let asOp = quote do: Op(`op`)
|
2021-04-26 14:40:58 +00:00
|
|
|
|
|
|
|
# Inner case/switch => Fork
|
|
|
|
let branchOnFork = quote do: `forkArg`
|
|
|
|
var forkCaseSubExpr = nnkCaseStmt.newTree(branchOnFork)
|
2022-12-02 04:35:41 +00:00
|
|
|
for fork in EVMFork:
|
|
|
|
let asFork = quote do: EVMFork(`fork`)
|
2021-04-26 14:40:58 +00:00
|
|
|
|
|
|
|
let branchStmt = block:
|
|
|
|
if op == Stop:
|
|
|
|
quote do:
|
|
|
|
handleStopDirective(`k`)
|
|
|
|
elif BaseGasCosts[op].kind == GckFixed:
|
|
|
|
quote do:
|
|
|
|
handleFixedGasCostsDirective(`asFork`,`asOp`,`k`)
|
|
|
|
else:
|
|
|
|
quote do:
|
|
|
|
handleOtherDirective(`asFork`,`asOp`,`k`)
|
|
|
|
|
2021-06-01 09:35:01 +00:00
|
|
|
forkCaseSubExpr.add nnkOfBranch.newTree(asFork, branchStmt)
|
2021-04-26 14:40:58 +00:00
|
|
|
|
|
|
|
# Wrap innner case/switch into outer case/switch
|
|
|
|
let branchStmt = block:
|
|
|
|
case op
|
Added basic async capabilities for vm2. (#1260)
* Added basic async capabilities for vm2.
This is a whole new Git branch, not the same one as last time
(https://github.com/status-im/nimbus-eth1/pull/1250) - there wasn't
much worth salvaging. Main differences:
I didn't do the "each opcode has to specify an async handler" junk
that I put in last time. Instead, in oph_memory.nim you can see
sloadOp calling asyncChainTo and passing in an async operation.
That async operation is then run by the execCallOrCreate (or
asyncExecCallOrCreate) code in interpreter_dispatch.nim.
In the test code, the (previously existing) macro called "assembler"
now allows you to add a section called "initialStorage", specifying
fake data to be used by the EVM computation run by that test. (In
the long run we'll obviously want to write tests that for-real use
the JSON-RPC API to asynchronously fetch data; for now, this was
just an expedient way to write a basic unit test that exercises the
async-EVM code pathway.)
There's also a new macro called "concurrentAssemblers" that allows
you to write a test that runs multiple assemblers concurrently (and
then waits for them all to finish). There's one example test using
this, in test_op_memory_lazy.nim, though you can't actually see it
doing so unless you uncomment some echo statements in
async_operations.nim (in which case you can see the two concurrently
running EVM computations each printing out what they're doing, and
you'll see that they interleave).
A question: is it possible to make EVMC work asynchronously? (For
now, this code compiles and "make test" passes even if ENABLE_EVMC
is turned on, but it doesn't actually work asynchronously, it just
falls back on doing the usual synchronous EVMC thing. See
FIXME-asyncAndEvmc.)
* Moved the AsyncOperationFactory to the BaseVMState object.
* Made the AsyncOperationFactory into a table of fn pointers.
Also ditched the plain-data Vm2AsyncOperation type; it wasn't
really serving much purpose. Instead, the pendingAsyncOperation
field directly contains the Future.
* Removed the hasStorage idea.
It's not the right solution to the "how do we know whether we
still need to fetch the storage value or not?" problem. I
haven't implemented the right solution yet, but at least
we're better off not putting in a wrong one.
* Added/modified/removed some comments.
(Based on feedback on the PR.)
* Removed the waitFor from execCallOrCreate.
There was some back-and-forth in the PR regarding whether nested
waitFor calls are acceptable:
https://github.com/status-im/nimbus-eth1/pull/1260#discussion_r998587449
The eventual decision was to just change the waitFor to a doAssert
(since we probably won't want this extra functionality when running
synchronously anyway) to make sure that the Future is already
finished.
2022-11-01 15:35:46 +00:00
|
|
|
of Create, Create2, Call, CallCode, DelegateCall, StaticCall, Sload:
|
2021-04-26 14:40:58 +00:00
|
|
|
quote do:
|
|
|
|
`forkCaseSubExpr`
|
|
|
|
if not `k`.cpt.continuation.isNil:
|
|
|
|
break
|
|
|
|
of Stop, Return, Revert, SelfDestruct:
|
|
|
|
quote do:
|
|
|
|
`forkCaseSubExpr`
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
quote do:
|
|
|
|
`forkCaseSubExpr`
|
|
|
|
|
2021-06-01 09:35:01 +00:00
|
|
|
result.add nnkOfBranch.newTree(asOp, branchStmt)
|
2021-04-26 14:40:58 +00:00
|
|
|
|
|
|
|
when isChatty:
|
|
|
|
echo ">>> ", result.repr
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public macros/functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2022-12-02 04:35:41 +00:00
|
|
|
macro genOptimisedDispatcher*(fork: EVMFork; op: Op; k: Vm2Ctx): untyped =
|
2021-04-26 14:40:58 +00:00
|
|
|
result = fork.toCaseStmt(op, k)
|
|
|
|
|
|
|
|
|
2022-12-02 04:35:41 +00:00
|
|
|
template genLowMemDispatcher*(fork: EVMFork; op: Op; k: Vm2Ctx) =
|
2021-04-26 14:40:58 +00:00
|
|
|
if op == Stop:
|
|
|
|
handleStopDirective(k)
|
|
|
|
break
|
|
|
|
|
|
|
|
if BaseGasCosts[op].kind == GckFixed:
|
|
|
|
handleFixedGasCostsDirective(fork, op, k)
|
|
|
|
else:
|
|
|
|
handleOtherDirective(fork, op, k)
|
|
|
|
|
|
|
|
case c.instr
|
Added basic async capabilities for vm2. (#1260)
* Added basic async capabilities for vm2.
This is a whole new Git branch, not the same one as last time
(https://github.com/status-im/nimbus-eth1/pull/1250) - there wasn't
much worth salvaging. Main differences:
I didn't do the "each opcode has to specify an async handler" junk
that I put in last time. Instead, in oph_memory.nim you can see
sloadOp calling asyncChainTo and passing in an async operation.
That async operation is then run by the execCallOrCreate (or
asyncExecCallOrCreate) code in interpreter_dispatch.nim.
In the test code, the (previously existing) macro called "assembler"
now allows you to add a section called "initialStorage", specifying
fake data to be used by the EVM computation run by that test. (In
the long run we'll obviously want to write tests that for-real use
the JSON-RPC API to asynchronously fetch data; for now, this was
just an expedient way to write a basic unit test that exercises the
async-EVM code pathway.)
There's also a new macro called "concurrentAssemblers" that allows
you to write a test that runs multiple assemblers concurrently (and
then waits for them all to finish). There's one example test using
this, in test_op_memory_lazy.nim, though you can't actually see it
doing so unless you uncomment some echo statements in
async_operations.nim (in which case you can see the two concurrently
running EVM computations each printing out what they're doing, and
you'll see that they interleave).
A question: is it possible to make EVMC work asynchronously? (For
now, this code compiles and "make test" passes even if ENABLE_EVMC
is turned on, but it doesn't actually work asynchronously, it just
falls back on doing the usual synchronous EVMC thing. See
FIXME-asyncAndEvmc.)
* Moved the AsyncOperationFactory to the BaseVMState object.
* Made the AsyncOperationFactory into a table of fn pointers.
Also ditched the plain-data Vm2AsyncOperation type; it wasn't
really serving much purpose. Instead, the pendingAsyncOperation
field directly contains the Future.
* Removed the hasStorage idea.
It's not the right solution to the "how do we know whether we
still need to fetch the storage value or not?" problem. I
haven't implemented the right solution yet, but at least
we're better off not putting in a wrong one.
* Added/modified/removed some comments.
(Based on feedback on the PR.)
* Removed the waitFor from execCallOrCreate.
There was some back-and-forth in the PR regarding whether nested
waitFor calls are acceptable:
https://github.com/status-im/nimbus-eth1/pull/1260#discussion_r998587449
The eventual decision was to just change the waitFor to a doAssert
(since we probably won't want this extra functionality when running
synchronously anyway) to make sure that the Future is already
finished.
2022-11-01 15:35:46 +00:00
|
|
|
of Create, Create2, Call, CallCode, DelegateCall, StaticCall, Sload:
|
2021-04-26 14:40:58 +00:00
|
|
|
if not k.cpt.continuation.isNil:
|
|
|
|
break
|
|
|
|
of Return, Revert, SelfDestruct:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
discard
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Debugging ...
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
when isMainModule and isChatty:
|
|
|
|
|
|
|
|
import ../types
|
|
|
|
|
2022-12-02 04:35:41 +00:00
|
|
|
proc optimised(c: Computation, fork: EVMFork) {.compileTime.} =
|
2021-04-26 14:40:58 +00:00
|
|
|
var desc: Vm2Ctx
|
|
|
|
while true:
|
|
|
|
genOptimisedDispatcher(fork, desc.cpt.instr, desc)
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|