RPC and GraphQL: Change `estimateGas` to go through unified EVM runner
Simplify `estimateGas` to use `runComputation`; drop other code. The RPC/GraphQL `estimateGas` operation is quite different from the `call` operation. It is much more like ordinary transaction execution than `call`, though there are still enough differences that tx validation cannot be used. Signed-off-by: Jamie Lokier <jamie@shareable.org>
This commit is contained in:
parent
b16aa2f1f7
commit
deffa20b07
|
@ -25,7 +25,8 @@ type
|
||||||
contractCreation*: bool
|
contractCreation*: bool
|
||||||
|
|
||||||
proc rpcRunComputation(vmState: BaseVMState, rpc: RpcCallData,
|
proc rpcRunComputation(vmState: BaseVMState, rpc: RpcCallData,
|
||||||
gasLimit: GasInt, forkOverride = none(Fork)): CallResult =
|
gasLimit: GasInt, forkOverride = none(Fork),
|
||||||
|
forEstimateGas: bool = false): CallResult =
|
||||||
return runComputation(CallParams(
|
return runComputation(CallParams(
|
||||||
vmState: vmState,
|
vmState: vmState,
|
||||||
forkOverride: forkOverride,
|
forkOverride: forkOverride,
|
||||||
|
@ -39,10 +40,10 @@ proc rpcRunComputation(vmState: BaseVMState, rpc: RpcCallData,
|
||||||
# This matches historical behaviour. It might be that not all these steps
|
# This matches historical behaviour. It might be that not all these steps
|
||||||
# should be disabled for RPC/GraphQL `call`. But until we investigate what
|
# should be disabled for RPC/GraphQL `call`. But until we investigate what
|
||||||
# RPC/GraphQL clients are expecting, keep the same behaviour.
|
# RPC/GraphQL clients are expecting, keep the same behaviour.
|
||||||
noIntrinsic: true, # Don't charge intrinsic gas.
|
noIntrinsic: not forEstimateGas, # Don't charge intrinsic gas.
|
||||||
noAccessList: true, # Don't initialise EIP-2929 access list.
|
noAccessList: not forEstimateGas, # Don't initialise EIP-2929 access list.
|
||||||
noGasCharge: true, # Don't charge sender account for gas.
|
noGasCharge: not forEstimateGas, # Don't charge sender account for gas.
|
||||||
noRefund: true # Don't apply gas refund/burn rule.
|
noRefund: not forEstimateGas # Don't apply gas refund/burn rule.
|
||||||
))
|
))
|
||||||
|
|
||||||
proc rpcDoCall*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB): HexDataStr =
|
proc rpcDoCall*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB): HexDataStr =
|
||||||
|
@ -69,31 +70,19 @@ func rpcIntrinsicGas(call: RpcCallData, fork: Fork): GasInt =
|
||||||
return intrinsicGas
|
return intrinsicGas
|
||||||
|
|
||||||
func rpcValidateCall(call: RpcCallData, vmState: BaseVMState, gasLimit: GasInt,
|
func rpcValidateCall(call: RpcCallData, vmState: BaseVMState, gasLimit: GasInt,
|
||||||
fork: Fork, intrinsicGas: var GasInt, gasCost: var UInt256): bool =
|
fork: Fork): bool =
|
||||||
# This behaviour matches `validateTransaction`, used by `processTransaction`.
|
# This behaviour matches `validateTransaction`, used by `processTransaction`.
|
||||||
if vmState.cumulativeGasUsed + gasLimit > vmState.blockHeader.gasLimit:
|
if vmState.cumulativeGasUsed + gasLimit > vmState.blockHeader.gasLimit:
|
||||||
return false
|
return false
|
||||||
let balance = vmState.readOnlyStateDB.getBalance(call.source)
|
let balance = vmState.readOnlyStateDB.getBalance(call.source)
|
||||||
gasCost = gasLimit.u256 * call.gasPrice.u256
|
let gasCost = gasLimit.u256 * call.gasPrice.u256
|
||||||
if gasCost > balance or call.value > balance - gasCost:
|
if gasCost > balance or call.value > balance - gasCost:
|
||||||
return false
|
return false
|
||||||
intrinsicGas = rpcIntrinsicGas(call, fork)
|
let intrinsicGas = rpcIntrinsicGas(call, fork)
|
||||||
if intrinsicGas > gasLimit:
|
if intrinsicGas > gasLimit:
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc rpcInitialAccessListEIP2929(call: RpcCallData, vmState: BaseVMState, fork: Fork) =
|
|
||||||
# EIP2929 initial access list.
|
|
||||||
if fork >= FkBerlin:
|
|
||||||
vmState.mutateStateDB:
|
|
||||||
db.accessList(call.source)
|
|
||||||
# For contract creations the EVM will add the contract address to the
|
|
||||||
# access list itself, after calculating the new contract address.
|
|
||||||
if not call.contractCreation:
|
|
||||||
db.accessList(call.to)
|
|
||||||
for c in activePrecompiles():
|
|
||||||
db.accessList(c)
|
|
||||||
|
|
||||||
proc rpcEstimateGas*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB, haveGasLimit: bool): GasInt =
|
proc rpcEstimateGas*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB, haveGasLimit: bool): GasInt =
|
||||||
# TODO: handle revert and error
|
# TODO: handle revert and error
|
||||||
var
|
var
|
||||||
|
@ -102,8 +91,6 @@ proc rpcEstimateGas*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB,
|
||||||
vmState = newBaseVMState(header.stateRoot, header, chain)
|
vmState = newBaseVMState(header.stateRoot, header, chain)
|
||||||
fork = toFork(chain.config, header.blockNumber)
|
fork = toFork(chain.config, header.blockNumber)
|
||||||
gasLimit = if haveGasLimit: call.gas else: header.gasLimit - vmState.cumulativeGasUsed
|
gasLimit = if haveGasLimit: call.gas else: header.gasLimit - vmState.cumulativeGasUsed
|
||||||
intrinsicGas: GasInt
|
|
||||||
gasCost: UInt256
|
|
||||||
|
|
||||||
# Nimbus `estimateGas` has historically checked against remaining gas in the
|
# Nimbus `estimateGas` has historically checked against remaining gas in the
|
||||||
# current block, balance in the sender account (even if the sender is default
|
# current block, balance in the sender account (even if the sender is default
|
||||||
|
@ -116,27 +103,15 @@ proc rpcEstimateGas*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB,
|
||||||
# are other differences in rpc_utils.nim `callData` too. Are the different
|
# are other differences in rpc_utils.nim `callData` too. Are the different
|
||||||
# behaviours intended, and is 0 the correct return value to mean "not enough
|
# behaviours intended, and is 0 the correct return value to mean "not enough
|
||||||
# gas to start"? Probably not.
|
# gas to start"? Probably not.
|
||||||
if not rpcValidateCall(call, vmState, gasLimit, fork, intrinsicGas, gasCost):
|
if not rpcValidateCall(call, vmState, gasLimit, fork):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
# Use a db transaction to save and restore the state of the database.
|
||||||
var dbTx = chain.db.beginTransaction()
|
var dbTx = chain.db.beginTransaction()
|
||||||
defer: dbTx.dispose()
|
defer: dbTx.dispose()
|
||||||
|
|
||||||
# TODO: EIP2929 setup also historically differs from `rpcDoCall` and `rpcMakeCall`.
|
let callResult = rpcRunComputation(vmState, call, gasLimit, some(fork), true)
|
||||||
rpcInitialAccessListEIP2929(call, vmState, fork)
|
return callResult.gasUsed
|
||||||
|
|
||||||
# TODO: Deduction of `intrinsicGas` also differs from `rpcDoCall` and `rpcMakeCall`.
|
|
||||||
var c = rpcSetupComputation(vmState, call, gasLimit - intrinsicGas, some(fork))
|
|
||||||
vmState.mutateStateDB:
|
|
||||||
db.subBalance(call.source, gasCost)
|
|
||||||
|
|
||||||
execComputation(c)
|
|
||||||
|
|
||||||
if c.shouldBurnGas:
|
|
||||||
return gasLimit
|
|
||||||
let maxRefund = (gasLimit - c.gasMeter.gasRemaining) div 2
|
|
||||||
let refund = min(c.getGasRefund(), maxRefund)
|
|
||||||
return gasLimit - c.gasMeter.gasRemaining - refund
|
|
||||||
|
|
||||||
proc txSetupComputation(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): Computation =
|
proc txSetupComputation(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): Computation =
|
||||||
var gas = tx.gasLimit - tx.intrinsicGas(fork)
|
var gas = tx.gasLimit - tx.intrinsicGas(fork)
|
||||||
|
|
Loading…
Reference in New Issue