2021-04-08 14:52:10 +00:00
# Nimbus
2024-02-20 03:07:38 +00:00
# Copyright (c) 2018-2024 Status Research & Development GmbH
2021-04-08 14:52:10 +00:00
# Licensed under either of
2021-04-20 15:39:32 +00:00
# * 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.
2021-04-08 14:52:10 +00:00
2021-04-22 11:02:13 +00:00
const
2021-04-26 15:16:38 +00:00
# help with low memory when compiling selectVM() function
2021-04-23 07:50:48 +00:00
lowmem {. intdefine . } : int = 0
lowMemoryCompileTime {. used . } = lowmem > 0
2021-04-08 14:52:10 +00:00
import
2023-03-20 11:51:09 +00:00
std / [ macros , strformat ] ,
2023-01-31 12:38:08 +00:00
pkg / [ chronicles , chronos , stew / byteutils ] ,
2023-12-12 19:12:56 +00:00
" .. " / [ constants , db / ledger ] ,
2022-12-02 04:35:41 +00:00
" . " / [ code_stream , computation ] ,
" . " / [ message , precompiles , state , types ] ,
2023-04-12 12:39:11 +00:00
. / async / operations ,
2023-01-31 12:38:08 +00:00
. / interpreter / [ op_dispatcher , gas_costs ]
2021-04-08 14:52:10 +00:00
2023-02-14 14:37:21 +00:00
{. push raises : [ ] . }
2021-04-08 14:52:10 +00:00
logScope :
topics = " vm opcode "
2021-04-22 11:02:13 +00:00
# ------------------------------------------------------------------------------
2021-04-26 15:16:38 +00:00
# Private functions
2021-04-22 11:02:13 +00:00
# ------------------------------------------------------------------------------
2023-09-25 10:20:14 +00:00
const
supportedOS = defined ( windows ) or defined ( linux ) or defined ( macosx )
optimizationCondition = not lowMemoryCompileTime and defined ( release ) and supportedOS
when optimizationCondition :
# this is a top level pragma since nim 1.6.16
{. optimization : speed . }
2023-02-14 14:37:21 +00:00
proc selectVM ( c : Computation , fork : EVMFork , shouldPrepareTracer : bool )
{. gcsafe , raises : [ CatchableError ] . } =
2021-04-22 11:02:13 +00:00
## Op code execution handler main loop.
2021-04-20 12:07:01 +00:00
var desc : Vm2Ctx
2021-04-22 11:02:13 +00:00
desc . cpt = c
2021-04-20 12:07:01 +00:00
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
# It's important not to re-prepare the tracer after
# an async operation, only after a call/create.
#
# That is, tracingEnabled is checked in many places, and
# indicates something like, "Do we want tracing to be
# enabled?", whereas shouldPrepareTracer is more like,
# "Are we at a spot right now where we want to re-initialize
# the tracer?"
if c . tracingEnabled and shouldPrepareTracer :
2021-04-20 11:14:43 +00:00
c . prepareTracer ( )
while true :
c . instr = c . code . next ( )
2021-04-22 11:02:13 +00:00
# Note Mamy's observation in opTableToCaseStmt() from original VM
# regarding computed goto
#
# ackn:
# #{.computedGoto.}
# # computed goto causing stack overflow, it consumes a lot of space
# # we could use manual jump table instead
# # TODO lots of macro magic here to unravel, with chronicles...
# # `c`.logger.log($`c`.stack & "\n\n", fgGreen)
2021-04-23 07:50:48 +00:00
when not lowMemoryCompileTime :
when defined ( release ) :
#
# FIXME: OS case list below needs to be adjusted
#
when defined ( windows ) :
when defined ( cpu64 ) :
{. warning : " *** Win64/VM2 handler switch => computedGoto " . }
2023-09-25 10:20:14 +00:00
{. computedGoto . }
2021-04-23 07:50:48 +00:00
else :
# computedGoto not compiling on github/ci (out of memory) -- jordan
{. warning : " *** Win32/VM2 handler switch => optimisation disabled " . }
2023-09-25 10:20:14 +00:00
# {.computedGoto.}
2021-04-23 07:50:48 +00:00
elif defined ( linux ) :
when defined ( cpu64 ) :
{. warning : " *** Linux64/VM2 handler switch => computedGoto " . }
2023-09-25 10:20:14 +00:00
{. computedGoto . }
2021-04-23 07:50:48 +00:00
else :
{. warning : " *** Linux32/VM2 handler switch => computedGoto " . }
2023-09-25 10:20:14 +00:00
{. computedGoto . }
2021-04-23 07:50:48 +00:00
elif defined ( macosx ) :
when defined ( cpu64 ) :
{. warning : " *** MacOs64/VM2 handler switch => computedGoto " . }
2023-09-25 10:20:14 +00:00
{. computedGoto . }
2021-04-23 07:50:48 +00:00
else :
{. warning : " *** MacOs32/VM2 handler switch => computedGoto " . }
2023-09-25 10:20:14 +00:00
{. computedGoto . }
2021-04-22 11:02:13 +00:00
else :
2021-04-23 07:50:48 +00:00
{. warning : " *** Unsupported OS => no handler switch optimisation " . }
2021-04-22 11:02:13 +00:00
2021-04-26 14:40:58 +00:00
genOptimisedDispatcher ( fork , c . instr , desc )
2021-04-22 11:02:13 +00:00
2021-04-23 07:50:48 +00:00
else :
{. warning : " *** low memory compiler mode => program will be slow " . }
2021-04-22 11:02:13 +00:00
2021-04-23 07:50:48 +00:00
genLowMemDispatcher ( fork , c . instr , desc )
2021-04-22 11:02:13 +00:00
2021-04-26 15:16:38 +00:00
proc beforeExecCall ( c : Computation ) =
c . snapshot ( )
2023-08-28 12:10:31 +00:00
if c . msg . kind = = EVMC_CALL :
2022-04-08 04:54:11 +00:00
c . vmState . mutateStateDB :
2021-04-26 15:16:38 +00:00
db . subBalance ( c . msg . sender , c . msg . value )
db . addBalance ( c . msg . contractAddress , c . msg . value )
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
2022-04-08 04:54:11 +00:00
if c . isError or c . fork > = FkByzantium :
2023-10-11 19:09:11 +00:00
if c . msg . contractAddress = = RIPEMD_ADDR :
2021-04-26 15:16:38 +00:00
# Special case to account for geth+parity bug
2023-03-20 11:51:09 +00:00
c . vmState . stateDB . ripemdSpecial ( )
2021-04-26 15:16:38 +00:00
if c . isSuccess :
c . commit ( )
else :
c . rollback ( )
2023-02-14 14:37:21 +00:00
proc beforeExecCreate ( c : Computation ) : bool
{. gcsafe , raises : [ ValueError ] . } =
2021-04-26 15:16:38 +00:00
c . vmState . mutateStateDB :
2022-02-10 08:02:39 +00:00
let nonce = db . getNonce ( c . msg . sender )
if nonce + 1 < nonce :
c . setError ( & " Nonce overflow when sender={c.msg.sender.toHex} wants to create contract " , false )
return true
db . setNonce ( c . msg . sender , nonce + 1 )
2021-04-26 15:16:38 +00:00
# We add this to the access list _before_ taking a snapshot.
# Even if the creation fails, the access-list change should not be rolled
# back EIP2929
if c . fork > = FkBerlin :
db . accessList ( c . msg . contractAddress )
c . snapshot ( )
2022-04-08 04:54:11 +00:00
if c . vmState . readOnlyStateDB ( ) . hasCodeOrNonce ( c . msg . contractAddress ) :
2023-01-31 01:32:17 +00:00
# let blurb = c.msg.contractAddress.toHex -- notused
2021-04-26 15:16:38 +00:00
c . setError ( " Address collision when creating contract address={blurb} " , true )
c . rollback ( )
return true
2022-04-08 04:54:11 +00:00
c . vmState . mutateStateDB :
2021-04-26 15:16:38 +00:00
db . subBalance ( c . msg . sender , c . msg . value )
db . addBalance ( c . msg . contractAddress , c . msg . value )
db . clearStorage ( c . msg . contractAddress )
if c . fork > = FkSpurious :
# EIP161 nonce incrementation
db . incNonce ( c . msg . contractAddress )
return false
2023-02-14 14:37:21 +00:00
proc afterExecCreate ( c : Computation )
{. gcsafe , raises : [ CatchableError ] . } =
2021-04-26 15:16:38 +00:00
if c . isSuccess :
EVM: `writeContract` fixes, never return contract code as `RETURNDATA`
This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and
equivalent on other networks.
This combines a change on the EVM-caller side with an EVM-side change from
@jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller
EVM ignore any data except from `REVERT`.
Either change works by itself. The reason for both is to ensure we definitely
comply with ambiguous EVMC expectations from either side of that boundary, and
it makes the internal API clearer.
As well as fixing a specific consensus issue, there are some other EVM logic
changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the
EVM, and changed behaviour with quirks before EIP-2 (Homestead).
The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on
other networks. Here's a trace at batch 5080897..5081088:
```
TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088
...
DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411
DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615
TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT>
```
Although it says "Contract code size" and "gasUsed", this bug is more general
than either contract size or gas. It's due to incorrect behaviour of EVM
instructions `RETURNDATA` and `RETURNDATASIZE`.
Sometimes when `writeContract` decides to reject writing the contract for any
of several reasons (for example just insufficient gas), the unwritten contract
code was being used as the "return data", and given to the caller. If the
caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported
the contract code that didn't get written.
EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`:
> "`CREATE` and `CREATE2` are considered to return the empty buffer in the
> success case and the failure data in the failure case".
The language is ambiguous. In fact "failure case" means when the contract uses
`REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170
limit, EIP-3541, etc.
To be thorough, and to ensure we always do the right thing with real EVMC when
that's finalised, this patch fixes the `RETURNDATA` issue in two places, either
of which make Goerli block 5080941 pass.
`writeContract` has been refactored to be caller, and so has where it's called.
It sets an error in the usual way if contract writing is rejected -- that's
anticipating EVMC, where we'll use different error codes later.
Overall four behaviour changes:
1. On the callee side, it doesn't set `c.outputData` except for `REVERT`.
2. On the caller side, it doesn't read `child.outputData` except for `REVERT`.
3. There was a bug in processing before Homestead fork (EIP-2). We did not
match the spec or other implementations; now we do. When there's
insufficient gas, before Homestead it's treated as success but with an empty
contract.
https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304
https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586
4. The Byzantium check has been removed, as it's unnecessary.
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
# This can change `c.isSuccess`.
c . writeContract ( )
# Contract code should never be returned to the caller. Only data from
# `REVERT` is returned after a create. Clearing in this branch covers the
# right cases, particularly important with EVMC where it must be cleared.
if c . output . len > 0 :
c . output = @ [ ]
2021-04-26 15:16:38 +00:00
if c . isSuccess :
c . commit ( )
else :
c . rollback ( )
2023-08-02 10:17:40 +00:00
const
MsgKindToOp : array [ CallKind , Op ] = [
2024-02-20 03:07:38 +00:00
Call ,
DelegateCall ,
CallCode ,
Create ,
2024-03-28 06:47:02 +00:00
Create2 ,
EofCreate
2023-08-02 10:17:40 +00:00
]
func msgToOp ( msg : Message ) : Op =
2023-08-28 12:10:31 +00:00
if EVMC_STATIC in msg . flags :
2024-02-20 03:07:38 +00:00
return StaticCall
2023-08-02 10:17:40 +00:00
MsgKindToOp [ msg . kind ]
2023-02-14 14:37:21 +00:00
proc beforeExec ( c : Computation ) : bool
{. gcsafe , raises : [ ValueError ] . } =
2023-08-02 10:17:40 +00:00
if c . msg . depth > 0 :
2023-08-25 09:07:20 +00:00
c . vmState . captureEnter ( c ,
msgToOp ( c . msg ) ,
2023-08-02 10:17:40 +00:00
c . msg . sender , c . msg . contractAddress ,
c . msg . data , c . msg . gas ,
c . msg . value )
2021-04-26 15:16:38 +00:00
if not c . msg . isCreate :
c . beforeExecCall ( )
false
else :
c . beforeExecCreate ( )
2023-02-14 14:37:21 +00:00
proc afterExec ( c : Computation )
{. gcsafe , raises : [ CatchableError ] . } =
2023-08-02 10:17:40 +00:00
2021-04-26 15:16:38 +00:00
if not c . msg . isCreate :
c . afterExecCall ( )
else :
c . afterExecCreate ( )
2023-08-02 10:17:40 +00:00
if c . msg . depth > 0 :
let gasUsed = c . msg . gas - c . gasMeter . gasRemaining
2023-08-28 12:10:31 +00:00
c . vmState . captureExit ( c , c . output , gasUsed , c . errorOpt )
2023-08-02 10:17:40 +00:00
2021-04-26 15:16:38 +00:00
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
2023-02-14 14:37:21 +00:00
proc executeOpcodes * ( c : Computation , shouldPrepareTracer : bool = true )
2023-08-28 12:10:31 +00:00
{. gcsafe , raises : [ ] . } =
2021-04-26 15:16:38 +00:00
let fork = c . fork
block :
2022-09-26 04:56:54 +00:00
if c . continuation . isNil and c . execPrecompiles ( fork ) :
2021-04-26 15:16:38 +00:00
break
try :
2023-04-12 12:39:11 +00:00
let cont = c . continuation
if not cont . isNil :
c . continuation = nil
cont ( )
let nextCont = c . continuation
if nextCont . isNil :
# FIXME-Adam: I hate how convoluted this is. See also the comment in
# op_dispatcher.nim. The idea here is that we need to call
# traceOpCodeEnded at the end of the opcode (and only if there
# hasn't been an exception thrown); otherwise we run into problems
# if an exception (e.g. out of gas) is thrown during a continuation.
# So this code says, "If we've just run a continuation, but there's
# no *subsequent* continuation, then the opcode is done."
if c . tracingEnabled and not ( cont . isNil ) and nextCont . isNil :
c . traceOpCodeEnded ( c . instr , c . opIndex )
case c . instr
of Return , Revert , SelfDestruct : # FIXME-Adam: HACK, fix this in a clean way; I think the idea is that these are the ones from the "always break" case in op_dispatcher
discard
else :
c . selectVM ( fork , shouldPrepareTracer )
else :
# Return up to the caller, which will run the async operation or child
# and then call this proc again.
discard
2021-04-26 15:16:38 +00:00
except CatchableError as e :
2023-02-14 14:37:21 +00:00
let
msg = e . msg
2023-08-23 09:29:33 +00:00
depth = $ ( c . msg . depth + 1 ) # plus one to match tracer depth, and avoid confusion
c . setError ( " Opcode Dispatch Error: " & msg & " , depth= " & depth , true )
2021-04-26 15:16:38 +00:00
if c . isError ( ) and c . continuation . isNil :
if c . tracingEnabled : c . traceError ( )
2022-09-26 04:56:54 +00:00
when vm_use_recursion :
# Recursion with tiny stack frame per level.
2023-02-14 14:37:21 +00:00
proc execCallOrCreate * ( c : Computation )
{. gcsafe , raises : [ CatchableError ] . } =
2022-09-26 04:56:54 +00:00
defer : c . dispose ( )
if c . beforeExec ( ) :
return
c . executeOpcodes ( )
while not c . continuation . isNil :
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
# If there's a continuation, then it's because there's either
# a child (i.e. call or create) or a pendingAsyncOperation.
if not c . pendingAsyncOperation . isNil :
let p = c . pendingAsyncOperation
c . pendingAsyncOperation = nil
doAssert ( p . finished ( ) , " In synchronous mode, every async operation should be an already-resolved Future. " )
c . executeOpcodes ( false )
2022-09-26 04:56:54 +00:00
else :
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
when evmc_enabled :
c . res = c . host . call ( c . child [ ] )
else :
execCallOrCreate ( c . child )
c . child = nil
c . executeOpcodes ( )
2022-09-26 04:56:54 +00:00
c . afterExec ( )
2021-04-26 15:16:38 +00:00
2022-09-26 04:56:54 +00:00
else :
2023-02-14 14:37:21 +00:00
proc execCallOrCreate * ( cParam : Computation )
{. gcsafe , raises : [ CatchableError ] . } =
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
var ( c , before , shouldPrepareTracer ) = ( cParam , true , true )
2022-09-26 04:56:54 +00:00
defer :
while not c . isNil :
c . dispose ( )
c = c . parent
2021-04-26 15:16:38 +00:00
2022-09-26 04:56:54 +00:00
# No actual recursion, but simulate recursion including before/after/dispose.
2021-04-26 15:16:38 +00:00
while true :
2022-09-26 04:56:54 +00:00
while true :
if before and c . beforeExec ( ) :
break
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
c . executeOpcodes ( shouldPrepareTracer )
2022-09-26 04:56:54 +00:00
if c . continuation . isNil :
c . afterExec ( )
break
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
if not c . pendingAsyncOperation . isNil :
before = false
shouldPrepareTracer = false
let p = c . pendingAsyncOperation
c . pendingAsyncOperation = nil
doAssert ( p . finished ( ) , " In synchronous mode, every async operation should be an already-resolved Future. " )
else :
( before , shouldPrepareTracer , c . child , c , c . parent ) = ( true , true , nil . Computation , c . child , c )
2022-09-26 04:56:54 +00:00
if c . parent . isNil :
2021-04-26 15:16:38 +00:00
break
2022-09-26 04:56:54 +00:00
c . dispose ( )
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
( before , shouldPrepareTracer , c . parent , c ) = ( false , true , nil . Computation , c . parent )
# FIXME-duplicatedForAsync
#
# In the long run I'd like to make some clever macro/template to
# eliminate the duplication between the synchronous and
# asynchronous versions. But for now let's stick with this for
2022-12-02 04:35:41 +00:00
# simplicity.
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
#
# Also, I've based this on the recursive one (above), which I think
# is okay because the "async" pragma is going to rewrite this whole
# thing to use callbacks anyway. But maybe I'm wrong? It isn't hard
# to write the async version of the iterative one, but this one is
# a bit shorter and feels cleaner, so if it works just as well I'd
# rather use this one. --Adam
proc asyncExecCallOrCreate * ( c : Computation ) : Future [ void ] {. async . } =
defer : c . dispose ( )
2023-04-12 12:39:11 +00:00
await ifNecessaryGetCode ( c . vmState , c . msg . contractAddress )
2023-08-02 10:17:40 +00:00
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
if c . beforeExec ( ) :
return
c . executeOpcodes ( )
while not c . continuation . isNil :
# If there's a continuation, then it's because there's either
# a child (i.e. call or create) or a pendingAsyncOperation.
if not c . pendingAsyncOperation . isNil :
let p = c . pendingAsyncOperation
c . pendingAsyncOperation = nil
await p
c . executeOpcodes ( false )
else :
when evmc_enabled :
# FIXME-asyncAndEvmc
# Note that this is NOT async. I'm not sure how/whether I
# can do EVMC asynchronously.
c . res = c . host . call ( c . child [ ] )
else :
await asyncExecCallOrCreate ( c . child )
c . child = nil
c . executeOpcodes ( )
c . afterExec ( )
2021-04-26 15:16:38 +00:00
2021-04-22 11:02:13 +00:00
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------