2021-04-30 09:33:14 +00:00
|
|
|
# Nimbus - Various ways of calling the EVM
|
|
|
|
#
|
|
|
|
# Copyright (c) 2018-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.
|
|
|
|
|
|
|
|
import
|
2022-05-09 14:04:48 +00:00
|
|
|
std/[options, times],
|
|
|
|
chronicles,
|
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
|
|
|
chronos,
|
2022-09-03 18:15:35 +00:00
|
|
|
eth/[common/eth_types_rlp, trie/db],
|
2022-05-09 14:04:48 +00:00
|
|
|
stint,
|
2021-10-26 15:18:08 +00:00
|
|
|
".."/[vm_types, vm_state, vm_gas_costs, forks, constants],
|
2022-05-09 14:04:48 +00:00
|
|
|
".."/[db/db_chain, db/accounts_cache, chain_config],
|
2021-05-17 11:39:52 +00:00
|
|
|
./call_common
|
2021-04-30 09:33:14 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
RpcCallData* = object
|
2021-10-26 15:18:08 +00:00
|
|
|
source* : Option[EthAddress]
|
|
|
|
to* : Option[EthAddress]
|
|
|
|
gasLimit* : Option[GasInt]
|
|
|
|
gasPrice* : Option[GasInt]
|
|
|
|
maxFee* : Option[GasInt]
|
|
|
|
maxPriorityFee*: Option[GasInt]
|
|
|
|
value* : Option[UInt256]
|
|
|
|
data* : seq[byte]
|
|
|
|
accessList* : AccessList
|
|
|
|
|
|
|
|
proc toCallParams(vmState: BaseVMState, cd: RpcCallData,
|
2022-04-08 04:54:11 +00:00
|
|
|
globalGasCap: GasInt, baseFee: Option[UInt256],
|
2021-10-26 15:18:08 +00:00
|
|
|
forkOverride = none(Fork)): CallParams =
|
|
|
|
|
|
|
|
# Reject invalid combinations of pre- and post-1559 fee styles
|
|
|
|
if cd.gasPrice.isSome and (cd.maxFee.isSome or cd.maxPriorityFee.isSome):
|
|
|
|
raise newException(ValueError, "both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
|
|
|
|
|
|
|
# Set default gas & gas price if none were set
|
|
|
|
var gasLimit = globalGasCap
|
|
|
|
if gasLimit == 0:
|
|
|
|
gasLimit = GasInt(high(uint64) div 2)
|
|
|
|
|
|
|
|
if cd.gasLimit.isSome:
|
|
|
|
gasLimit = cd.gasLimit.get()
|
|
|
|
|
|
|
|
if globalGasCap != 0 and globalGasCap < gasLimit:
|
|
|
|
warn "Caller gas above allowance, capping", requested = gasLimit, cap = globalGasCap
|
|
|
|
gasLimit = globalGasCap
|
|
|
|
|
|
|
|
var gasPrice = cd.gasPrice.get(0.GasInt)
|
|
|
|
if baseFee.isSome:
|
|
|
|
# A basefee is provided, necessitating EIP-1559-type execution
|
|
|
|
let maxPriorityFee = cd.maxPriorityFee.get(0.GasInt)
|
|
|
|
let maxFee = cd.maxFee.get(0.GasInt)
|
|
|
|
|
|
|
|
# Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
|
|
|
|
if maxPriorityFee > 0 or maxFee > 0:
|
|
|
|
let baseFee = baseFee.get().truncate(GasInt)
|
|
|
|
let priorityFee = min(maxPriorityFee, maxFee - baseFee)
|
|
|
|
gasPrice = priorityFee + baseFee
|
|
|
|
|
|
|
|
CallParams(
|
2021-05-17 10:03:39 +00:00
|
|
|
vmState: vmState,
|
|
|
|
forkOverride: forkOverride,
|
2021-10-26 15:18:08 +00:00
|
|
|
sender: cd.source.get(ZERO_ADDRESS),
|
|
|
|
to: cd.to.get(ZERO_ADDRESS),
|
|
|
|
isCreate: cd.to.isNone,
|
2021-05-17 10:03:39 +00:00
|
|
|
gasLimit: gasLimit,
|
2021-10-26 15:18:08 +00:00
|
|
|
gasPrice: gasPrice,
|
|
|
|
value: cd.value.get(0.u256),
|
|
|
|
input: cd.data,
|
|
|
|
accessList: cd.accessList
|
|
|
|
)
|
|
|
|
|
|
|
|
proc rpcCallEvm*(call: RpcCallData, header: BlockHeader, chainDB: BaseChainDB): CallResult =
|
|
|
|
const globalGasCap = 0 # TODO: globalGasCap should configurable by user
|
2022-01-18 16:19:32 +00:00
|
|
|
let topHeader = BlockHeader(
|
|
|
|
parentHash: header.blockHash,
|
|
|
|
timestamp: getTime().utc.toTime,
|
|
|
|
gasLimit: 0.GasInt, ## ???
|
2022-04-08 04:54:11 +00:00
|
|
|
fee: UInt256.none()) ## ???
|
2022-01-18 16:19:32 +00:00
|
|
|
let vmState = BaseVMState.new(topHeader, chainDB)
|
2021-10-26 15:18:08 +00:00
|
|
|
let params = toCallParams(vmState, call, globalGasCap, header.fee)
|
|
|
|
|
|
|
|
var dbTx = chainDB.db.beginTransaction()
|
|
|
|
defer: dbTx.dispose() # always dispose state changes
|
|
|
|
|
|
|
|
runComputation(params)
|
|
|
|
|
|
|
|
proc rpcEstimateGas*(cd: RpcCallData, header: BlockHeader, chainDB: BaseChainDB, gasCap: GasInt): GasInt =
|
|
|
|
# Binary search the gas requirement, as it may be higher than the amount used
|
2022-01-18 16:19:32 +00:00
|
|
|
let topHeader = BlockHeader(
|
|
|
|
parentHash: header.blockHash,
|
|
|
|
timestamp: getTime().utc.toTime,
|
|
|
|
gasLimit: 0.GasInt, ## ???
|
2022-04-08 04:54:11 +00:00
|
|
|
fee: UInt256.none()) ## ???
|
2022-01-18 16:19:32 +00:00
|
|
|
let vmState = BaseVMState.new(topHeader, chainDB)
|
2021-10-26 15:18:08 +00:00
|
|
|
let fork = chainDB.config.toFork(header.blockNumber)
|
|
|
|
let txGas = gasFees[fork][GasTransaction] # txGas always 21000, use constants?
|
|
|
|
var params = toCallParams(vmState, cd, gasCap, header.fee)
|
|
|
|
|
2021-05-03 08:08:11 +00:00
|
|
|
var
|
2021-10-26 15:18:08 +00:00
|
|
|
lo : GasInt = txGas - 1
|
|
|
|
hi : GasInt = cd.gasLimit.get(0.GasInt)
|
|
|
|
cap: GasInt
|
|
|
|
|
|
|
|
var dbTx = chainDB.db.beginTransaction()
|
|
|
|
defer: dbTx.dispose() # always dispose state changes
|
|
|
|
|
|
|
|
# Determine the highest gas limit can be used during the estimation.
|
|
|
|
if hi < txGas:
|
|
|
|
# block's gasLimit act as the gas ceiling
|
|
|
|
hi = header.gasLimit
|
|
|
|
|
|
|
|
# Normalize the max fee per gas the call is willing to spend.
|
|
|
|
var feeCap = cd.gasPrice.get(0.GasInt)
|
|
|
|
if cd.gasPrice.isSome and (cd.maxFee.isSome or cd.maxPriorityFee.isSome):
|
|
|
|
raise newException(ValueError, "both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
|
|
|
elif cd.maxFee.isSome:
|
|
|
|
feeCap = cd.maxFee.get
|
|
|
|
|
|
|
|
# Recap the highest gas limit with account's available balance.
|
|
|
|
if feeCap > 0:
|
|
|
|
if cd.source.isNone:
|
|
|
|
raise newException(ValueError, "`from` can't be null")
|
|
|
|
|
|
|
|
let balance = vmState.readOnlyStateDB.getBalance(cd.source.get)
|
|
|
|
var available = balance
|
|
|
|
if cd.value.isSome:
|
|
|
|
let value = cd.value.get
|
|
|
|
if value > available:
|
|
|
|
raise newException(ValueError, "insufficient funds for transfer")
|
|
|
|
available -= value
|
|
|
|
|
|
|
|
let allowance = available div feeCap.u256
|
|
|
|
# If the allowance is larger than maximum GasInt, skip checking
|
|
|
|
if allowance < high(GasInt).u256 and hi > allowance.truncate(GasInt):
|
|
|
|
let transfer = cd.value.get(0.u256)
|
|
|
|
warn "Gas estimation capped by limited funds", original=hi, balance,
|
|
|
|
sent=transfer, maxFeePerGas=feeCap, fundable=allowance
|
|
|
|
hi = allowance.truncate(GasInt)
|
|
|
|
|
|
|
|
# Recap the highest gas allowance with specified gasCap.
|
|
|
|
if gasCap != 0 and hi > gasCap:
|
|
|
|
warn "Caller gas above allowance, capping", requested=hi, cap=gasCap
|
|
|
|
hi = gasCap
|
|
|
|
|
|
|
|
cap = hi
|
|
|
|
let intrinsicGas = intrinsicGas(params, fork)
|
|
|
|
|
|
|
|
# Create a helper to check if a gas allowance results in an executable transaction
|
|
|
|
proc executable(gasLimit: GasInt): bool =
|
|
|
|
if intrinsicGas > gasLimit:
|
|
|
|
# Special case, raise gas limit
|
|
|
|
return true
|
|
|
|
|
|
|
|
params.gasLimit = gasLimit
|
|
|
|
# TODO: bail out on consensus error similar to validateTransaction
|
|
|
|
runComputation(params).isError
|
|
|
|
|
|
|
|
# Execute the binary search and hone in on an executable gas limit
|
|
|
|
while lo+1 < hi:
|
|
|
|
let mid = (hi + lo) div 2
|
|
|
|
let failed = executable(mid)
|
|
|
|
if failed:
|
|
|
|
lo = mid
|
|
|
|
else:
|
|
|
|
hi = mid
|
|
|
|
|
|
|
|
# Reject the transaction as invalid if it still fails at the highest allowance
|
|
|
|
if hi == cap:
|
|
|
|
let failed = executable(hi)
|
|
|
|
if failed:
|
|
|
|
# TODO: provide more descriptive EVM error beside out of gas
|
|
|
|
# e.g. revert and other EVM errors
|
|
|
|
raise newException(ValueError, "gas required exceeds allowance " & $cap)
|
|
|
|
|
|
|
|
hi
|
2021-05-03 16:50:47 +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
|
|
|
proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): CallParams =
|
|
|
|
# Is there a nice idiom for this kind of thing? Should I
|
|
|
|
# just be writing this as a bunch of assignment statements?
|
|
|
|
result = CallParams(
|
2021-05-17 09:36:34 +00:00
|
|
|
vmState: vmState,
|
|
|
|
forkOverride: some(fork),
|
|
|
|
gasPrice: tx.gasPrice,
|
2021-05-17 13:01:41 +00:00
|
|
|
gasLimit: tx.gasLimit,
|
2021-05-17 09:36:34 +00:00
|
|
|
sender: sender,
|
2021-06-27 04:19:43 +00:00
|
|
|
to: tx.destination,
|
|
|
|
isCreate: tx.contractCreation,
|
2021-05-17 09:36:34 +00:00
|
|
|
value: tx.value,
|
|
|
|
input: tx.payload
|
2021-05-17 13:01:41 +00:00
|
|
|
)
|
2021-06-27 04:19:43 +00:00
|
|
|
if tx.txType > TxLegacy:
|
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
|
|
|
shallowCopy(result.accessList, tx.accessList)
|
2021-05-03 19:35:29 +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
|
|
|
proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): CallParams =
|
|
|
|
result = CallParams(
|
2021-05-17 16:27:20 +00:00
|
|
|
vmState: vmState,
|
2021-10-14 06:08:40 +00:00
|
|
|
forkOverride: some(fork),
|
|
|
|
gasPrice: tx.gasPrice,
|
|
|
|
gasLimit: tx.gasLimit,
|
|
|
|
sender: sender,
|
|
|
|
to: tx.destination,
|
|
|
|
isCreate: tx.contractCreation,
|
|
|
|
value: tx.value,
|
|
|
|
input: tx.payload,
|
2021-05-04 14:02:16 +00:00
|
|
|
|
2021-10-14 06:08:40 +00:00
|
|
|
noIntrinsic: true, # Don't charge intrinsic gas.
|
|
|
|
noRefund: true, # Don't apply gas refund/burn rule.
|
|
|
|
)
|
|
|
|
if tx.txType > TxLegacy:
|
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
|
|
|
shallowCopy(result.accessList, tx.accessList)
|
|
|
|
|
|
|
|
proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): GasInt =
|
|
|
|
let call = callParamsForTx(tx, sender, vmState, fork)
|
|
|
|
return runComputation(call).gasUsed
|
|
|
|
|
|
|
|
proc testCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): CallResult =
|
|
|
|
let call = callParamsForTest(tx, sender, vmState, fork)
|
2021-10-14 06:08:40 +00:00
|
|
|
runComputation(call)
|
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
|
|
|
|
|
|
|
# FIXME-duplicatedForAsync
|
|
|
|
proc asyncTestCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): Future[CallResult] {.async.} =
|
|
|
|
let call = callParamsForTest(tx, sender, vmState, fork)
|
|
|
|
return await asyncRunComputation(call)
|