nimbus-eth1/nimbus/transaction/call_common.nim
Jordan Hrycaj df1217b7ca
Silence compiler gossip after nim upgrade cont3 (#1466)
* Removed some Windows specific unit test annoyances

details:
+ Short put()/get() cycles on persistent database have a race condition
  with vendor rocksdb. On a specific (and slow) qemu/win7 a 50ms `sleep()`
  in between will mostly do the job (i.e. unless heavy CPU load.) This
  issue was not observed on github/ci.
+ Removed annoyances when qemu/Win7 keeps the rocksdb database files
  locked even after closing the db. The problem is solved by strictly
  using fresh names for each test. No assumption made to be able to
  properly clean up. This issue was not observed on github/ci.

* Silence some compiler gossip -- part 7, misc/non(sync or graphql)

details:
  Adding some missing exception annotation
2023-02-14 20:27:17 +00:00

295 lines
11 KiB
Nim

# Nimbus - Common entry point to the EVM from all different callers
#
# 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.
{.push raises: [].}
import
eth/common/eth_types, stint, options, stew/ranges/ptr_arith,
chronos,
".."/[vm_types, vm_state, vm_computation, vm_state_transactions],
".."/[vm_internals, vm_precompiles, vm_gas_costs],
".."/[db/accounts_cache],
../common/evmforks,
./host_types
when defined(evmc_enabled):
import ../utils/utils
import ./host_services
type
# Standard call parameters.
CallParams* = object
vmState*: BaseVMState # Chain, database, state, block, fork.
forkOverride*: Option[EVMFork] # Default fork is usually correct.
origin*: Option[HostAddress] # Default origin is `sender`.
gasPrice*: GasInt # Gas price for this call.
gasLimit*: GasInt # Maximum gas available for this call.
sender*: HostAddress # Sender account.
to*: HostAddress # Recipient (ignored when `isCreate`).
isCreate*: bool # True if this is a contract creation.
value*: HostValue # Value sent from sender to recipient.
input*: seq[byte] # Input data.
accessList*: AccessList # EIP-2930 (Berlin) tx access list.
noIntrinsic*: bool # Don't charge intrinsic gas.
noAccessList*: bool # Don't initialise EIP-2929 access list.
noGasCharge*: bool # Don't charge sender account for gas.
noRefund*: bool # Don't apply gas refund/burn rule.
# Standard call result. (Some fields are beyond what EVMC can return,
# and must only be used from tests because they will not always be set).
CallResult* = object
isError*: bool # True if the call failed.
gasUsed*: GasInt # Gas used by the call.
contractAddress*: EthAddress # Created account (when `isCreate`).
output*: seq[byte] # Output data.
logEntries*: seq[Log] # Output logs.
stack*: Stack # EVM stack on return (for test only).
memory*: Memory # EVM memory on return (for test only).
proc hostToComputationMessage*(msg: EvmcMessage): Message =
Message(
kind: CallKind(msg.kind.ord),
depth: msg.depth,
gas: msg.gas,
sender: msg.sender.fromEvmc,
contractAddress: msg.recipient.fromEvmc,
codeAddress: msg.code_address.fromEvmc,
value: msg.value.fromEvmc,
# When input size is zero, input data pointer may be null.
data: if msg.input_size <= 0: @[]
else: @(makeOpenArray(msg.input_data, msg.input_size.int)),
flags: if msg.isStatic: emvcStatic else: emvcNoFlags
)
func intrinsicGas*(call: CallParams, fork: EVMFork): GasInt {.inline.} =
# Compute the baseline gas cost for this transaction. This is the amount
# of gas needed to send this transaction (but that is not actually used
# for computation).
var gas = gasFees[fork][GasTransaction]
# EIP-2 (Homestead) extra intrinsic gas for contract creations.
if call.isCreate:
gas += gasFees[fork][GasTXCreate]
if fork >= FkShanghai:
gas += (gasFees[fork][GasInitcodeWord] * call.input.len.wordCount)
# Input data cost, reduced in EIP-2028 (Istanbul).
let gasZero = gasFees[fork][GasTXDataZero]
let gasNonZero = gasFees[fork][GasTXDataNonZero]
for b in call.input:
gas += (if b == 0: gasZero else: gasNonZero)
# EIP-2930 (Berlin) intrinsic gas for transaction access list.
if fork >= FkBerlin:
for account in call.accessList:
gas += ACCESS_LIST_ADDRESS_COST
gas += account.storageKeys.len * ACCESS_LIST_STORAGE_KEY_COST
return gas
proc initialAccessListEIP2929(call: CallParams) =
# EIP2929 initial access list.
let vmState = call.vmState
if vmState.fork < FkBerlin:
return
vmState.mutateStateDB:
db.accessList(call.sender)
# For contract creations the EVM will add the contract address to the
# access list itself, after calculating the new contract address.
if not call.isCreate:
db.accessList(call.to)
# EIP3651 adds coinbase to the list of addresses that should start warm.
if vmState.fork >= FkShanghai:
db.accessList(vmState.coinbase)
# TODO: Check this only adds the correct subset of precompiles.
for c in activePrecompiles():
db.accessList(c)
# EIP2930 optional access list.
for account in call.accessList:
db.accessList(account.address)
for key in account.storageKeys:
db.accessList(account.address, UInt256.fromBytesBE(key))
proc setupHost(call: CallParams): TransactionHost =
let vmState = call.vmState
vmState.setupTxContext(
origin = call.origin.get(call.sender),
gasPrice = call.gasPrice,
forkOverride = call.forkOverride
)
var intrinsicGas: GasInt = 0
if not call.noIntrinsic:
intrinsicGas = intrinsicGas(call, vmState.fork)
let host = TransactionHost(
vmState: vmState,
msg: EvmcMessage(
kind: if call.isCreate: EVMC_CREATE else: EVMC_CALL,
# Default: flags: {},
# Default: depth: 0,
gas: call.gasLimit - intrinsicGas,
recipient: call.to.toEvmc,
code_address: call.to.toEvmc,
sender: call.sender.toEvmc,
value: call.value.toEvmc,
)
# All other defaults in `TransactionHost` are fine.
)
# Generate new contract address, prepare code, and update message `recipient`
# with the contract address. This differs from the previous Nimbus EVM API.
# Guarded under `evmc_enabled` for now so it doesn't break vm2.
when defined(evmc_enabled):
var code: seq[byte]
if call.isCreate:
let sender = call.sender
let contractAddress =
generateAddress(sender, call.vmState.readOnlyStateDB.getNonce(sender))
host.msg.recipient = contractAddress.toEvmc
host.msg.input_size = 0
host.msg.input_data = nil
code = call.input
else:
# TODO: Share the underlying data, but only after checking this does not
# cause problems with the database.
code = host.vmState.readOnlyStateDB.getCode(host.msg.code_address.fromEvmc)
if call.input.len > 0:
host.msg.input_size = call.input.len.csize_t
# Must copy the data so the `host.msg.input_data` pointer
# remains valid after the end of `call` lifetime.
host.input = call.input
host.msg.input_data = host.input[0].addr
let cMsg = hostToComputationMessage(host.msg)
host.computation = newComputation(vmState, cMsg, code)
shallowCopy(host.code, code)
else:
if call.input.len > 0:
host.msg.input_size = call.input.len.csize_t
# Must copy the data so the `host.msg.input_data` pointer
# remains valid after the end of `call` lifetime.
host.input = call.input
host.msg.input_data = host.input[0].addr
let cMsg = hostToComputationMessage(host.msg)
host.computation = newComputation(vmState, cMsg)
return host
when defined(evmc_enabled):
proc doExecEvmc(host: TransactionHost, call: CallParams)
{.gcsafe, raises: [CatchableError].} =
var callResult = evmcExecComputation(host)
let c = host.computation
if callResult.status_code == EVMC_SUCCESS:
c.error = nil
elif callResult.status_code == EVMC_REVERT:
c.setError("EVMC_REVERT", false)
else:
c.setError($callResult.status_code, true)
c.gasMeter.gasRemaining = callResult.gas_left
c.msg.contractAddress = callResult.create_address.fromEvmc
c.output = if callResult.output_size <= 0: @[]
else: @(makeOpenArray(callResult.output_data,
callResult.output_size.int))
if not callResult.release.isNil:
{.gcsafe.}:
try:
callResult.release(callResult)
except Exception as e:
{.warning: "Kludge(BareExcept): `evmc_release_fn` in vendor package needs to be updated"}
raiseAssert "Ooops evmcExecComputation(): name=" &
$e.name & " msg=" & e.msg
# FIXME-awkwardFactoring: the factoring out of the pre and
# post parts feels awkward to me, but for now I'd really like
# not to have too much duplicated code between sync and async.
# --Adam
proc prepareToRunComputation(host: TransactionHost, call: CallParams) =
# Must come after `setupHost` for correct fork.
if not call.noAccessList:
initialAccessListEIP2929(call)
# Charge for gas.
if not call.noGasCharge:
host.vmState.mutateStateDB:
db.subBalance(call.sender, call.gasLimit.u256 * call.gasPrice.u256)
proc calculateAndPossiblyRefundGas(host: TransactionHost, call: CallParams): GasInt =
let c = host.computation
# EIP-3529: Reduction in refunds
let MaxRefundQuotient = if host.vmState.fork >= FkLondon:
5.GasInt
else:
2.GasInt
# Calculated gas used, taking into account refund rules.
if call.noRefund:
result = c.gasMeter.gasRemaining
elif not c.shouldBurnGas:
let maxRefund = (call.gasLimit - c.gasMeter.gasRemaining) div MaxRefundQuotient
let refund = min(c.getGasRefund(), maxRefund)
c.gasMeter.returnGas(refund)
result = c.gasMeter.gasRemaining
# Refund for unused gas.
if result > 0 and not call.noGasCharge:
host.vmState.mutateStateDB:
db.addBalance(call.sender, result.u256 * call.gasPrice.u256)
proc finishRunningComputation(host: TransactionHost, call: CallParams): CallResult =
let c = host.computation
let gasRemaining = calculateAndPossiblyRefundGas(host, call)
# evm gas used without intrinsic gas
host.vmState.tracerGasUsed(host.msg.gas - gasRemaining)
result.isError = c.isError
result.gasUsed = call.gasLimit - gasRemaining
shallowCopy(result.output, c.output)
result.contractAddress = if call.isCreate: c.msg.contractAddress
else: default(HostAddress)
shallowCopy(result.logEntries, c.logEntries)
result.stack = c.stack
result.memory = c.memory
proc runComputation*(call: CallParams): CallResult
{.gcsafe, raises: [CatchableError].} =
let host = setupHost(call)
prepareToRunComputation(host, call)
when defined(evmc_enabled):
doExecEvmc(host, call)
else:
execComputation(host.computation)
finishRunningComputation(host, call)
# FIXME-duplicatedForAsync
proc asyncRunComputation*(call: CallParams): Future[CallResult] {.async.} =
let host = setupHost(call)
prepareToRunComputation(host, call)
# FIXME-asyncAndEvmc: I'm not sure what to do with EVMC at the moment.
# when defined(evmc_enabled):
# doExecEvmc(host, call)
# else:
await asyncExecComputation(host.computation)
return finishRunningComputation(host, call)