RPC: Gather EVM-calling functions to one place; rpcSetupComputation

Start gathering the functions that call the EVM into one place,
`transaction/call_evm.nim`.

This is first of a series of changes to gather all ways the EVM is called to
one place.  Duplicate, slightly different setup functions have accumulated over
time, each with some knowledge of EVM internals.  When they are brought
together, these methods will be changed to use a single entry point to the EVM,
allowing the entry point to be refactored, EVMC to be completed, and async
concurrency to be implemented on top.  This also simplifies the callers.

First, a helper function used by RPC and GraphQL to make EVM calls without
permanently modifying the account state.  `setupComputation` ->
`rpcSetupComputation`.

Signed-off-by: Jamie Lokier <jamie@shareable.org>
This commit is contained in:
Jamie Lokier 2021-04-30 10:33:14 +01:00
parent 24ce283498
commit 7c3b7ab7a8
No known key found for this signature in database
GPG Key ID: CBC25C68435C30A2
3 changed files with 53 additions and 39 deletions

View File

@ -15,7 +15,8 @@ import
graphql/common/types, graphql/httpserver, graphql/common/types, graphql/httpserver,
../db/[db_chain, state_db], ../errors, ../utils, ../db/[db_chain, state_db], ../errors, ../utils,
../transaction, ../rpc/rpc_utils, ../vm_state, ../config, ../transaction, ../rpc/rpc_utils, ../vm_state, ../config,
../vm_computation, ../vm_state_transactions ../vm_computation, ../vm_state_transactions,
../transaction/call_evm
from eth/p2p import EthereumNode from eth/p2p import EthereumNode
export httpserver export httpserver
@ -716,9 +717,9 @@ proc blockAccount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPrag
let address = hexToByteArray[20](params[0].val.stringVal) let address = hexToByteArray[20](params[0].val.stringVal)
ctx.accountNode(h.header, address) ctx.accountNode(h.header, address)
proc toCallData(n: Node): (CallData, bool) = proc toCallData(n: Node): (RpcCallData, bool) =
# phew, probably need to use macro here :) # phew, probably need to use macro here :)
var cd: CallData var cd: RpcCallData
var gasLimit = false var gasLimit = false
if n[0][1].kind != nkEmpty: if n[0][1].kind != nkEmpty:
cd.source = hextoByteArray[20](n[0][1].stringVal) cd.source = hextoByteArray[20](n[0][1].stringVal)
@ -744,14 +745,14 @@ proc toCallData(n: Node): (CallData, bool) =
(cd, gasLimit) (cd, gasLimit)
proc makeCall(ctx: GraphqlContextRef, callData: CallData, header: BlockHeader, chainDB: BaseChainDB): RespResult = proc makeCall(ctx: GraphqlContextRef, callData: RpcCallData, header: BlockHeader, chainDB: BaseChainDB): RespResult =
# TODO: handle revert # TODO: handle revert
var var
# we use current header stateRoot, unlike block validation # we use current header stateRoot, unlike block validation
# which use previous block stateRoot # which use previous block stateRoot
vmState = newBaseVMState(header.stateRoot, header, chainDB) vmState = newBaseVMState(header.stateRoot, header, chainDB)
fork = toFork(chainDB.config, header.blockNumber) fork = toFork(chainDB.config, header.blockNumber)
comp = setupComputation(vmState, callData, fork) comp = rpcSetupComputation(vmState, callData, fork)
let gas = comp.gasMeter.gasRemaining let gas = comp.gasMeter.gasRemaining
comp.execComputation() comp.execComputation()

View File

@ -11,7 +11,8 @@ import hexstrings, eth/[common, rlp, keys, trie/db], stew/byteutils, nimcrypto,
../db/[db_chain, accounts_cache], strutils, algorithm, options, times, json, ../db/[db_chain, accounts_cache], strutils, algorithm, options, times, json,
../constants, stint, hexstrings, rpc_types, ../config, ../constants, stint, hexstrings, rpc_types, ../config,
../vm_state_transactions, ../vm_state, ../vm_types, ../vm_types2, ../vm_state_transactions, ../vm_state, ../vm_types, ../vm_types2,
../vm_computation, ../p2p/executor, ../utils, ../transaction ../vm_computation, ../p2p/executor, ../utils, ../transaction,
../transaction/call_evm
type type
UnsignedTx* = object UnsignedTx* = object
@ -23,15 +24,6 @@ type
payload* : Blob payload* : Blob
contractCreation* {.rlpIgnore.}: bool contractCreation* {.rlpIgnore.}: bool
CallData* = object
source*: EthAddress
to*: EthAddress
gas*: GasInt
gasPrice*: GasInt
value*: UInt256
data*: seq[byte]
contractCreation*: bool
proc read(rlp: var Rlp, t: var UnsignedTx, _: type EthAddress): EthAddress {.inline.} = proc read(rlp: var Rlp, t: var UnsignedTx, _: type EthAddress): EthAddress {.inline.} =
if rlp.blobLen != 0: if rlp.blobLen != 0:
result = rlp.read(EthAddress) result = rlp.read(EthAddress)
@ -161,7 +153,7 @@ proc signTransaction*(tx: UnsignedTx, chain: BaseChainDB, privateKey: PrivateKey
S: Uint256.fromBytesBE(sig[32..63]) S: Uint256.fromBytesBE(sig[32..63])
) )
proc callData*(call: EthCall, callMode: bool = true, chain: BaseChainDB): CallData = proc callData*(call: EthCall, callMode: bool = true, chain: BaseChainDB): RpcCallData =
if call.source.isSome: if call.source.isSome:
result.source = toAddress(call.source.get) result.source = toAddress(call.source.get)
@ -188,40 +180,20 @@ proc callData*(call: EthCall, callMode: bool = true, chain: BaseChainDB): CallDa
if call.data.isSome: if call.data.isSome:
result.data = hexToSeqByte(call.data.get.string) result.data = hexToSeqByte(call.data.get.string)
proc setupComputation*(vmState: BaseVMState, call: CallData, fork: Fork) : Computation = proc doCall*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB): HexDataStr =
vmState.setupTxContext(
origin = call.source,
gasPrice = call.gasPrice,
forkOverride = some(fork)
)
let msg = Message(
kind: evmcCall,
depth: 0,
gas: call.gas,
sender: call.source,
contractAddress: call.to,
codeAddress: call.to,
value: call.value,
data: call.data
)
result = newComputation(vmState, msg)
proc doCall*(call: CallData, header: BlockHeader, chain: BaseChainDB): HexDataStr =
var var
# we use current header stateRoot, unlike block validation # we use current header stateRoot, unlike block validation
# which use previous block stateRoot # which use previous block stateRoot
vmState = newBaseVMState(header.stateRoot, header, chain) vmState = newBaseVMState(header.stateRoot, header, chain)
fork = toFork(chain.config, header.blockNumber) fork = toFork(chain.config, header.blockNumber)
comp = setupComputation(vmState, call, fork) comp = rpcSetupComputation(vmState, call, fork)
comp.execComputation() comp.execComputation()
result = hexDataStr(comp.output) result = hexDataStr(comp.output)
# TODO: handle revert and error # TODO: handle revert and error
# TODO: handle contract ABI # TODO: handle contract ABI
proc estimateGas*(call: CallData, header: BlockHeader, chain: BaseChainDB, haveGasLimit: bool): GasInt = proc estimateGas*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB, haveGasLimit: bool): GasInt =
var var
# we use current header stateRoot, unlike block validation # we use current header stateRoot, unlike block validation
# which use previous block stateRoot # which use previous block stateRoot

View File

@ -0,0 +1,41 @@
# 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
eth/common/eth_types, stint, options,
".."/[vm_types, vm_types2, vm_state, vm_computation]
type
RpcCallData* = object
source*: EthAddress
to*: EthAddress
gas*: GasInt
gasPrice*: GasInt
value*: UInt256
data*: seq[byte]
contractCreation*: bool
proc rpcSetupComputation*(vmState: BaseVMState, call: RpcCallData, fork: Fork): Computation =
vmState.setupTxContext(
origin = call.source,
gasPrice = call.gasPrice,
forkOverride = some(fork)
)
let msg = Message(
kind: evmcCall,
depth: 0,
gas: call.gas,
sender: call.source,
contractAddress: call.to,
codeAddress: call.to,
value: call.value,
data: call.data
)
return newComputation(vmState, msg)