nimbus-eth1/nimbus/vm2/computation.nim

263 lines
8.4 KiB
Nim
Raw Normal View History

# Nimbus
# Copyright (c) 2018 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
../constants, ../forks,
../db/accounts_cache,
../utils,
./code_stream,
./interpreter/[gas_meter, gas_costs, op_codes],
./memory,
./message,
./stack,
./state,
./transaction_tracer,
./types,
chronicles,
eth/[common, keys],
options,
sets
logScope:
topics = "vm computation"
when defined(chronicles_log_level):
import stew/byteutils
# ------------------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------------------
proc generateContractAddress(c: Computation, salt: ContractSalt): EthAddress =
if c.msg.kind == evmcCreate:
let creationNonce = c.vmState.readOnlyStateDb().getNonce(c.msg.sender)
result = generateAddress(c.msg.sender, creationNonce)
else:
result = generateSafeAddress(c.msg.sender, salt, c.msg.data)
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
template getCoinbase*(c: Computation): EthAddress =
c.vmState.coinbase
template getTimestamp*(c: Computation): int64 =
c.vmState.timestamp.toUnix
template getBlockNumber*(c: Computation): Uint256 =
c.vmState.blockNumber.blockNumberToVmWord
template getDifficulty*(c: Computation): DifficultyInt =
c.vmState.difficulty
template getGasLimit*(c: Computation): GasInt =
c.vmState.gasLimit
template getBaseFee*(c: Computation): Uint256 =
c.vmState.baseFee
template getChainId*(c: Computation): uint =
c.vmState.chaindb.config.chainId.uint
template getOrigin*(c: Computation): EthAddress =
c.vmState.txOrigin
template getGasPrice*(c: Computation): GasInt =
c.vmState.txGasPrice
template getBlockHash*(c: Computation, blockNumber: Uint256): Hash256 =
c.vmState.getAncestorHash(blockNumber.vmWordToBlockNumber)
template accountExists*(c: Computation, address: EthAddress): bool =
if c.fork >= FkSpurious:
not c.vmState.readOnlyStateDB.isDeadAccount(address)
else:
c.vmState.readOnlyStateDB.accountExists(address)
template getStorage*(c: Computation, slot: Uint256): Uint256 =
c.vmState.readOnlyStateDB.getStorage(c.msg.contractAddress, slot)
template getBalance*(c: Computation, address: EthAddress): Uint256 =
c.vmState.readOnlyStateDB.getBalance(address)
template getCodeSize*(c: Computation, address: EthAddress): uint =
uint(c.vmState.readOnlyStateDB.getCodeSize(address))
template getCodeHash*(c: Computation, address: EthAddress): Hash256 =
let
db = c.vmState.readOnlyStateDB
if not db.accountExists(address) or db.isEmptyAccount(address):
default(Hash256)
else:
db.getCodeHash(address)
template selfDestruct*(c: Computation, address: EthAddress) =
c.execSelfDestruct(address)
template getCode*(c: Computation, address: EthAddress): seq[byte] =
c.vmState.readOnlyStateDB.getCode(address)
proc newComputation*(vmState: BaseVMState, message: Message,
salt: ContractSalt = ZERO_CONTRACTSALT): Computation =
new result
result.vmState = vmState
result.msg = message
result.memory = Memory()
result.stack = newStack()
result.returnStack = @[]
result.gasMeter.init(message.gas)
result.touchedAccounts = initHashSet[EthAddress]()
result.selfDestructs = initHashSet[EthAddress]()
if result.msg.isCreate():
result.msg.contractAddress = result.generateContractAddress(salt)
result.code = newCodeStream(message.data)
message.data = @[]
else:
result.code = newCodeStream(
vmState.readOnlyStateDb.getCode(message.codeAddress))
template gasCosts*(c: Computation): untyped =
c.vmState.gasCosts
template fork*(c: Computation): untyped =
c.vmState.fork
proc isOriginComputation*(c: Computation): bool =
# Is this computation the computation initiated by a transaction
c.msg.sender == c.vmState.txOrigin
template isSuccess*(c: Computation): bool =
c.error.isNil
template isError*(c: Computation): bool =
not c.isSuccess
func shouldBurnGas*(c: Computation): bool =
c.isError and c.error.burnsGas
proc isSelfDestructed*(c: Computation, address: EthAddress): bool =
result = address in c.selfDestructs
proc snapshot*(c: Computation) =
c.savePoint = c.vmState.stateDB.beginSavePoint()
proc commit*(c: Computation) =
c.vmState.stateDB.commit(c.savePoint)
proc dispose*(c: Computation) {.inline.} =
c.vmState.stateDB.safeDispose(c.savePoint)
c.savePoint = nil
proc rollback*(c: Computation) =
c.vmState.stateDB.rollback(c.savePoint)
proc setError*(c: Computation, msg: string, burnsGas = false) {.inline.} =
c.error = Error(info: msg, burnsGas: burnsGas)
proc writeContract*(c: Computation, fork: Fork): bool {.gcsafe.} =
result = true
let contractCode = c.output
if contractCode.len == 0: return
if fork >= FkSpurious and contractCode.len > EIP170_MAX_CODE_SIZE:
debug "Contract code size exceeds EIP170",
max = EIP170_MAX_CODE_SIZE, actual = contractCode.len
return false
2021-06-30 00:54:39 +00:00
if fork >= FkLondon and contractCode[0] == 0xEF.byte:
debug "Contract code can't start with 0xEF byte"
return false
Bugfix: Incorrect processing of self-destructed, new contract Fixes #868 "Gas usage consensus error at Mainnet block 6001128", and equivalent on other networks. Mainnet sync is able to continue past 6001128 after this. Here's a trace: ``` TRC 2021-09-29 15:13:21.532+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=6000961 toBlock=6001152 ... DBG 2021-09-29 15:14:35.925+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 gasUsed=7999726 cumulativeGasUsed=7989726 TRC 2021-09-29 15:14:35.925+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<PEER:IP> ``` Similar output is seen at many blocks in the range 6001128..6001204. The bug is when handling a combination of `CREATE` or `CREATE2`, along with `SELFDESTRUCT` applied to the new contract address. Init code for a contract can't return non-empty code and do `SELFDESTRUCT` at the same time, because `SELFDESTRUCT` returns empty data. But it is possible to return non-empty code in a newly created, self-destructed account if the init code calls `DELEGATECALL` or `CALLCODE` to other code which uses `SELFDESTRUCT`. In this case we must still charge gas and write the code. This shows on Mainnet blocks 6001128..6001204, where the gas difference matters. The code must be written because the new code can be called later in the transaction too, before self-destruction wipes the account at the end. There are actually three semantic changes here for a self-destructed, new contract: - Gas is charged. - The code is written to the account. - It can fail due to insufficient gas. This patch almost exactly reverts a15805e4 "fix applyCreateMessage" from 2019-02-28. I wonder what that fixed. Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-10-19 11:52:01 +00:00
# Charge gas and write the code even if the code address is self-destructed.
# Non-empty code in a newly created, self-destructed account is possible if
# the init code calls `DELEGATECALL` or `CALLCODE` to other code which uses
# `SELFDESTRUCT`. This shows on Mainnet blocks 6001128..6001204, where the
# gas difference matters. The new code can be called later in the
# transaction too, before self-destruction wipes the account at the end.
let gasParams = GasParams(kind: Create, cr_memLength: contractCode.len)
let codeCost = c.gasCosts[Create].c_handler(0.u256, gasParams).gasCost
if c.gasMeter.gasRemaining >= codeCost:
c.gasMeter.consumeGas(codeCost, reason = "Write contract code for CREATE")
c.vmState.mutateStateDb:
Bugfix: Incorrect processing of self-destructed, new contract Fixes #868 "Gas usage consensus error at Mainnet block 6001128", and equivalent on other networks. Mainnet sync is able to continue past 6001128 after this. Here's a trace: ``` TRC 2021-09-29 15:13:21.532+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=6000961 toBlock=6001152 ... DBG 2021-09-29 15:14:35.925+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 gasUsed=7999726 cumulativeGasUsed=7989726 TRC 2021-09-29 15:14:35.925+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<PEER:IP> ``` Similar output is seen at many blocks in the range 6001128..6001204. The bug is when handling a combination of `CREATE` or `CREATE2`, along with `SELFDESTRUCT` applied to the new contract address. Init code for a contract can't return non-empty code and do `SELFDESTRUCT` at the same time, because `SELFDESTRUCT` returns empty data. But it is possible to return non-empty code in a newly created, self-destructed account if the init code calls `DELEGATECALL` or `CALLCODE` to other code which uses `SELFDESTRUCT`. In this case we must still charge gas and write the code. This shows on Mainnet blocks 6001128..6001204, where the gas difference matters. The code must be written because the new code can be called later in the transaction too, before self-destruction wipes the account at the end. There are actually three semantic changes here for a self-destructed, new contract: - Gas is charged. - The code is written to the account. - It can fail due to insufficient gas. This patch almost exactly reverts a15805e4 "fix applyCreateMessage" from 2019-02-28. I wonder what that fixed. Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-10-19 11:52:01 +00:00
db.setCode(c.msg.contractAddress, contractCode)
result = true
else:
if fork < FkHomestead or FkByzantium <= fork:
c.output = @[]
result = false
template chainTo*(c, toChild: Computation, after: untyped) =
c.child = toChild
c.continuation = proc() =
after
proc merge*(c, child: Computation) =
c.logEntries.add child.logEntries
c.gasMeter.refundGas(child.gasMeter.gasRefunded)
c.selfDestructs.incl child.selfDestructs
c.touchedAccounts.incl child.touchedAccounts
proc execSelfDestruct*(c: Computation, beneficiary: EthAddress) =
c.vmState.mutateStateDB:
let
localBalance = c.getBalance(c.msg.contractAddress)
beneficiaryBalance = c.getBalance(beneficiary)
# Transfer to beneficiary
db.setBalance(beneficiary, localBalance + beneficiaryBalance)
# Zero the balance of the address being deleted.
# This must come after sending to beneficiary in case the
# contract named itself as the beneficiary.
db.setBalance(c.msg.contractAddress, 0.u256)
trace "SELFDESTRUCT",
contractAddress = c.msg.contractAddress.toHex,
localBalance = localBalance.toString,
beneficiary = beneficiary.toHex
c.touchedAccounts.incl beneficiary
# Register the account to be deleted
c.selfDestructs.incl(c.msg.contractAddress)
proc addLogEntry*(c: Computation, log: Log) {.inline.} =
c.logEntries.add(log)
proc getGasRefund*(c: Computation): GasInt =
if c.isSuccess:
result = c.gasMeter.gasRefunded
proc refundSelfDestruct*(c: Computation) =
let cost = gasFees[c.fork][RefundSelfDestruct]
c.gasMeter.refundGas(cost * c.selfDestructs.len)
proc tracingEnabled*(c: Computation): bool {.inline.} =
TracerFlags.EnableTracing in c.vmState.tracer.flags
proc traceOpCodeStarted*(c: Computation, op: Op): int {.inline.} =
c.vmState.tracer.traceOpCodeStarted(c, op)
proc traceOpCodeEnded*(c: Computation, op: Op, lastIndex: int) {.inline.} =
c.vmState.tracer.traceOpCodeEnded(c, op, lastIndex)
proc traceError*(c: Computation) {.inline.} =
c.vmState.tracer.traceError(c)
proc prepareTracer*(c: Computation) {.inline.} =
c.vmState.tracer.prepare(c.msg.depth)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------