From deffa20b073cd59a01d7922297cc9882313c7fc2 Mon Sep 17 00:00:00 2001 From: Jamie Lokier Date: Sat, 29 May 2021 05:22:53 +0100 Subject: [PATCH] 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 --- nimbus/transaction/call_evm.nim | 51 +++++++++------------------------ 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/nimbus/transaction/call_evm.nim b/nimbus/transaction/call_evm.nim index 9b991c4b4..0cd283dbf 100644 --- a/nimbus/transaction/call_evm.nim +++ b/nimbus/transaction/call_evm.nim @@ -25,7 +25,8 @@ type contractCreation*: bool proc rpcRunComputation(vmState: BaseVMState, rpc: RpcCallData, - gasLimit: GasInt, forkOverride = none(Fork)): CallResult = + gasLimit: GasInt, forkOverride = none(Fork), + forEstimateGas: bool = false): CallResult = return runComputation(CallParams( vmState: vmState, forkOverride: forkOverride, @@ -39,10 +40,10 @@ proc rpcRunComputation(vmState: BaseVMState, rpc: RpcCallData, # This matches historical behaviour. It might be that not all these steps # should be disabled for RPC/GraphQL `call`. But until we investigate what # RPC/GraphQL clients are expecting, keep the same behaviour. - noIntrinsic: true, # Don't charge intrinsic gas. - noAccessList: true, # Don't initialise EIP-2929 access list. - noGasCharge: true, # Don't charge sender account for gas. - noRefund: true # Don't apply gas refund/burn rule. + noIntrinsic: not forEstimateGas, # Don't charge intrinsic gas. + noAccessList: not forEstimateGas, # Don't initialise EIP-2929 access list. + noGasCharge: not forEstimateGas, # Don't charge sender account for gas. + noRefund: not forEstimateGas # Don't apply gas refund/burn rule. )) proc rpcDoCall*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB): HexDataStr = @@ -69,31 +70,19 @@ func rpcIntrinsicGas(call: RpcCallData, fork: Fork): GasInt = return intrinsicGas 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`. if vmState.cumulativeGasUsed + gasLimit > vmState.blockHeader.gasLimit: return false 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: return false - intrinsicGas = rpcIntrinsicGas(call, fork) + let intrinsicGas = rpcIntrinsicGas(call, fork) if intrinsicGas > gasLimit: return false 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 = # TODO: handle revert and error var @@ -102,8 +91,6 @@ proc rpcEstimateGas*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB, vmState = newBaseVMState(header.stateRoot, header, chain) fork = toFork(chain.config, header.blockNumber) gasLimit = if haveGasLimit: call.gas else: header.gasLimit - vmState.cumulativeGasUsed - intrinsicGas: GasInt - gasCost: UInt256 # Nimbus `estimateGas` has historically checked against remaining gas in the # 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 # behaviours intended, and is 0 the correct return value to mean "not enough # gas to start"? Probably not. - if not rpcValidateCall(call, vmState, gasLimit, fork, intrinsicGas, gasCost): + if not rpcValidateCall(call, vmState, gasLimit, fork): return 0 + # Use a db transaction to save and restore the state of the database. var dbTx = chain.db.beginTransaction() defer: dbTx.dispose() - # TODO: EIP2929 setup also historically differs from `rpcDoCall` and `rpcMakeCall`. - rpcInitialAccessListEIP2929(call, vmState, fork) - - # 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 + let callResult = rpcRunComputation(vmState, call, gasLimit, some(fork), true) + return callResult.gasUsed proc txSetupComputation(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): Computation = var gas = tx.gasLimit - tx.intrinsicGas(fork)