nimbus-eth1/nimbus/vm2/computation.nim

365 lines
12 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
when defined(evmc_enabled):
import
evmc/evmc,
evmc_helpers,
evmc_api,
stew/ranges/ptr_arith
export
evmc,
evmc_helpers,
evmc_api,
ptr_arith
const
evmc_enabled* = defined(evmc_enabled)
# ------------------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------------------
proc generateContractAddress(c: Computation, salt: ContractSalt): EthAddress =
if c.msg.kind == evmcCreate:
2022-04-08 04:54:11 +00:00
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 =
when evmc_enabled:
c.host.getTxContext().block_coinbase
else:
c.vmState.coinbase
template getTimestamp*(c: Computation): int64 =
when evmc_enabled:
c.host.getTxContext().block_timestamp
else:
c.vmState.timestamp.toUnix
2022-04-08 04:54:11 +00:00
template getBlockNumber*(c: Computation): UInt256 =
when evmc_enabled:
c.host.getTxContext().block_number.u256
else:
c.vmState.blockNumber.blockNumberToVmWord
template getDifficulty*(c: Computation): DifficultyInt =
when evmc_enabled:
UInt256.fromEvmc c.host.getTxContext().block_prev_randao
else:
c.vmState.difficulty
template getGasLimit*(c: Computation): GasInt =
when evmc_enabled:
c.host.getTxContext().block_gas_limit.GasInt
else:
c.vmState.gasLimit
2022-04-08 04:54:11 +00:00
template getBaseFee*(c: Computation): UInt256 =
when evmc_enabled:
UInt256.fromEvmc c.host.getTxContext().block_base_fee
else:
c.vmState.baseFee
template getChainId*(c: Computation): uint =
when evmc_enabled:
UInt256.fromEvmc(c.host.getTxContext().chain_id).truncate(uint)
else:
c.vmState.chainDB.config.chainId.uint
template getOrigin*(c: Computation): EthAddress =
when evmc_enabled:
c.host.getTxContext().tx_origin
else:
c.vmState.txOrigin
template getGasPrice*(c: Computation): GasInt =
when evmc_enabled:
UInt256.fromEvmc(c.host.getTxContext().tx_gas_price).truncate(GasInt)
else:
c.vmState.txGasPrice
2022-04-08 04:54:11 +00:00
template getBlockHash*(c: Computation, blockNumber: UInt256): Hash256 =
when evmc_enabled:
c.host.getBlockHash(blockNumber)
else:
c.vmState.getAncestorHash(blockNumber.vmWordToBlockNumber)
template accountExists*(c: Computation, address: EthAddress): bool =
when evmc_enabled:
c.host.accountExists(address)
else:
if c.fork >= FkSpurious:
not c.vmState.readOnlyStateDB.isDeadAccount(address)
else:
c.vmState.readOnlyStateDB.accountExists(address)
2022-04-08 04:54:11 +00:00
template getStorage*(c: Computation, slot: UInt256): UInt256 =
when evmc_enabled:
c.host.getStorage(c.msg.contractAddress, slot)
else:
c.vmState.readOnlyStateDB.getStorage(c.msg.contractAddress, slot)
2022-04-08 04:54:11 +00:00
template getBalance*(c: Computation, address: EthAddress): UInt256 =
when evmc_enabled:
c.host.getBalance(address)
else:
c.vmState.readOnlyStateDB.getBalance(address)
template getCodeSize*(c: Computation, address: EthAddress): uint =
when evmc_enabled:
c.host.getCodeSize(address)
else:
uint(c.vmState.readOnlyStateDB.getCodeSize(address))
template getCodeHash*(c: Computation, address: EthAddress): Hash256 =
when evmc_enabled:
c.host.getCodeHash(address)
else:
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) =
when evmc_enabled:
c.host.selfDestruct(c.msg.contractAddress, address)
else:
c.execSelfDestruct(address)
template getCode*(c: Computation, address: EthAddress): seq[byte] =
when evmc_enabled:
c.host.copyCode(address)
else:
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(
2022-04-08 04:54:11 +00:00
vmState.readOnlyStateDB.getCode(message.codeAddress))
proc newComputation*(vmState: BaseVMState, message: Message, code: seq[byte]): 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]()
result.code = newCodeStream(code)
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) =
2022-04-08 04:54:11 +00:00
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)
EVM: `writeContract` fixes, never return contract code as `RETURNDATA` This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and equivalent on other networks. This combines a change on the EVM-caller side with an EVM-side change from @jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller EVM ignore any data except from `REVERT`. Either change works by itself. The reason for both is to ensure we definitely comply with ambiguous EVMC expectations from either side of that boundary, and it makes the internal API clearer. As well as fixing a specific consensus issue, there are some other EVM logic changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the EVM, and changed behaviour with quirks before EIP-2 (Homestead). The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on other networks. Here's a trace at batch 5080897..5081088: ``` TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088 ... DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411 DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615 TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT> ``` Although it says "Contract code size" and "gasUsed", this bug is more general than either contract size or gas. It's due to incorrect behaviour of EVM instructions `RETURNDATA` and `RETURNDATASIZE`. Sometimes when `writeContract` decides to reject writing the contract for any of several reasons (for example just insufficient gas), the unwritten contract code was being used as the "return data", and given to the caller. If the caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported the contract code that didn't get written. EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`: > "`CREATE` and `CREATE2` are considered to return the empty buffer in the > success case and the failure data in the failure case". The language is ambiguous. In fact "failure case" means when the contract uses `REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170 limit, EIP-3541, etc. To be thorough, and to ensure we always do the right thing with real EVMC when that's finalised, this patch fixes the `RETURNDATA` issue in two places, either of which make Goerli block 5080941 pass. `writeContract` has been refactored to be caller, and so has where it's called. It sets an error in the usual way if contract writing is rejected -- that's anticipating EVMC, where we'll use different error codes later. Overall four behaviour changes: 1. On the callee side, it doesn't set `c.outputData` except for `REVERT`. 2. On the caller side, it doesn't read `child.outputData` except for `REVERT`. 3. There was a bug in processing before Homestead fork (EIP-2). We did not match the spec or other implementations; now we do. When there's insufficient gas, before Homestead it's treated as success but with an empty contract. https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304 https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586 4. The Byzantium check has been removed, as it's unnecessary. Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
proc writeContract*(c: Computation) =
template withExtra(tracer: untyped, args: varargs[untyped]) =
tracer args, newContract=($c.msg.contractAddress),
Redesign of BaseVMState descriptor (#923) * Redesign of BaseVMState descriptor why: BaseVMState provides an environment for executing transactions. The current descriptor also provides data that cannot generally be known within the execution environment, e.g. the total gasUsed which is available not before after all transactions have finished. Also, the BaseVMState constructor has been replaced by a constructor that does not need pre-initialised input of the account database. also: Previous constructor and some fields are provided with a deprecated annotation (producing a lot of noise.) * Replace legacy directives in production sources * Replace legacy directives in unit test sources * fix CI (missing premix update) * Remove legacy directives * chase CI problem * rebased * Re-introduce 'AccountsCache' constructor optimisation for 'BaseVmState' re-initialisation why: Constructing a new 'AccountsCache' descriptor can be avoided sometimes when the current state root is properly positioned already. Such a feature existed already as the update function 'initStateDB()' for the 'BaseChanDB' where the accounts cache was linked into this desctiptor. The function 'initStateDB()' was removed and re-implemented into the 'BaseVmState' constructor without optimisation. The old version was of restricted use as a wrong accounts cache state would unconditionally throw an exception rather than conceptually ask for a remedy. The optimised 'BaseVmState' re-initialisation has been implemented for the 'persistBlocks()' function. also: moved some test helpers to 'test/replay' folder * Remove unused & undocumented fields from Chain descriptor why: Reduces attack surface in general & improves reading the code.
2022-01-18 16:19:32 +00:00
blockNumber=c.vmState.blockNumber,
parentHash=($c.vmState.parent.blockHash)
EVM: `writeContract` fixes, never return contract code as `RETURNDATA` This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and equivalent on other networks. This combines a change on the EVM-caller side with an EVM-side change from @jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller EVM ignore any data except from `REVERT`. Either change works by itself. The reason for both is to ensure we definitely comply with ambiguous EVMC expectations from either side of that boundary, and it makes the internal API clearer. As well as fixing a specific consensus issue, there are some other EVM logic changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the EVM, and changed behaviour with quirks before EIP-2 (Homestead). The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on other networks. Here's a trace at batch 5080897..5081088: ``` TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088 ... DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411 DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615 TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT> ``` Although it says "Contract code size" and "gasUsed", this bug is more general than either contract size or gas. It's due to incorrect behaviour of EVM instructions `RETURNDATA` and `RETURNDATASIZE`. Sometimes when `writeContract` decides to reject writing the contract for any of several reasons (for example just insufficient gas), the unwritten contract code was being used as the "return data", and given to the caller. If the caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported the contract code that didn't get written. EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`: > "`CREATE` and `CREATE2` are considered to return the empty buffer in the > success case and the failure data in the failure case". The language is ambiguous. In fact "failure case" means when the contract uses `REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170 limit, EIP-3541, etc. To be thorough, and to ensure we always do the right thing with real EVMC when that's finalised, this patch fixes the `RETURNDATA` issue in two places, either of which make Goerli block 5080941 pass. `writeContract` has been refactored to be caller, and so has where it's called. It sets an error in the usual way if contract writing is rejected -- that's anticipating EVMC, where we'll use different error codes later. Overall four behaviour changes: 1. On the callee side, it doesn't set `c.outputData` except for `REVERT`. 2. On the caller side, it doesn't read `child.outputData` except for `REVERT`. 3. There was a bug in processing before Homestead fork (EIP-2). We did not match the spec or other implementations; now we do. When there's insufficient gas, before Homestead it's treated as success but with an empty contract. https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304 https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586 4. The Byzantium check has been removed, as it's unnecessary. Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
# In each check below, they are guarded by `len > 0`. This includes writing
# out the code, because the account already has zero-length code to handle
# nested calls (this is specified). May as well simplify other branches.
let (len, fork) = (c.output.len, c.fork)
if len == 0:
return
# EIP-3541 constraint (https://eips.ethereum.org/EIPS/eip-3541).
if fork >= FkLondon and c.output[0] == 0xEF.byte:
withExtra trace, "New contract code starts with 0xEF byte, not allowed by EIP-3541"
# TODO: Return `EVMC_CONTRACT_VALIDATION_FAILURE` (like Silkworm).
c.setError("EVMC_CONTRACT_VALIDATION_FAILURE", true)
return
# EIP-170 constraint (https://eips.ethereum.org/EIPS/eip-3541).
if fork >= FkSpurious and len > EIP170_MAX_CODE_SIZE:
withExtra trace, "New contract code exceeds EIP-170 limit",
codeSize=len, maxSize=EIP170_MAX_CODE_SIZE
# TODO: Return `EVMC_OUT_OF_GAS` (like Silkworm).
c.setError("EVMC_OUT_OF_GAS", true)
return
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.
EVM: `writeContract` fixes, never return contract code as `RETURNDATA` This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and equivalent on other networks. This combines a change on the EVM-caller side with an EVM-side change from @jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller EVM ignore any data except from `REVERT`. Either change works by itself. The reason for both is to ensure we definitely comply with ambiguous EVMC expectations from either side of that boundary, and it makes the internal API clearer. As well as fixing a specific consensus issue, there are some other EVM logic changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the EVM, and changed behaviour with quirks before EIP-2 (Homestead). The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on other networks. Here's a trace at batch 5080897..5081088: ``` TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088 ... DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411 DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615 TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT> ``` Although it says "Contract code size" and "gasUsed", this bug is more general than either contract size or gas. It's due to incorrect behaviour of EVM instructions `RETURNDATA` and `RETURNDATASIZE`. Sometimes when `writeContract` decides to reject writing the contract for any of several reasons (for example just insufficient gas), the unwritten contract code was being used as the "return data", and given to the caller. If the caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported the contract code that didn't get written. EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`: > "`CREATE` and `CREATE2` are considered to return the empty buffer in the > success case and the failure data in the failure case". The language is ambiguous. In fact "failure case" means when the contract uses `REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170 limit, EIP-3541, etc. To be thorough, and to ensure we always do the right thing with real EVMC when that's finalised, this patch fixes the `RETURNDATA` issue in two places, either of which make Goerli block 5080941 pass. `writeContract` has been refactored to be caller, and so has where it's called. It sets an error in the usual way if contract writing is rejected -- that's anticipating EVMC, where we'll use different error codes later. Overall four behaviour changes: 1. On the callee side, it doesn't set `c.outputData` except for `REVERT`. 2. On the caller side, it doesn't read `child.outputData` except for `REVERT`. 3. There was a bug in processing before Homestead fork (EIP-2). We did not match the spec or other implementations; now we do. When there's insufficient gas, before Homestead it's treated as success but with an empty contract. https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304 https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586 4. The Byzantium check has been removed, as it's unnecessary. Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
let gasParams = GasParams(kind: Create, cr_memLength: len)
let codeCost = c.gasCosts[Create].c_handler(0.u256, gasParams).gasCost
EVM: `writeContract` fixes, never return contract code as `RETURNDATA` This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and equivalent on other networks. This combines a change on the EVM-caller side with an EVM-side change from @jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller EVM ignore any data except from `REVERT`. Either change works by itself. The reason for both is to ensure we definitely comply with ambiguous EVMC expectations from either side of that boundary, and it makes the internal API clearer. As well as fixing a specific consensus issue, there are some other EVM logic changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the EVM, and changed behaviour with quirks before EIP-2 (Homestead). The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on other networks. Here's a trace at batch 5080897..5081088: ``` TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088 ... DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411 DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615 TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT> ``` Although it says "Contract code size" and "gasUsed", this bug is more general than either contract size or gas. It's due to incorrect behaviour of EVM instructions `RETURNDATA` and `RETURNDATASIZE`. Sometimes when `writeContract` decides to reject writing the contract for any of several reasons (for example just insufficient gas), the unwritten contract code was being used as the "return data", and given to the caller. If the caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported the contract code that didn't get written. EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`: > "`CREATE` and `CREATE2` are considered to return the empty buffer in the > success case and the failure data in the failure case". The language is ambiguous. In fact "failure case" means when the contract uses `REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170 limit, EIP-3541, etc. To be thorough, and to ensure we always do the right thing with real EVMC when that's finalised, this patch fixes the `RETURNDATA` issue in two places, either of which make Goerli block 5080941 pass. `writeContract` has been refactored to be caller, and so has where it's called. It sets an error in the usual way if contract writing is rejected -- that's anticipating EVMC, where we'll use different error codes later. Overall four behaviour changes: 1. On the callee side, it doesn't set `c.outputData` except for `REVERT`. 2. On the caller side, it doesn't read `child.outputData` except for `REVERT`. 3. There was a bug in processing before Homestead fork (EIP-2). We did not match the spec or other implementations; now we do. When there's insufficient gas, before Homestead it's treated as success but with an empty contract. https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304 https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586 4. The Byzantium check has been removed, as it's unnecessary. Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
if codeCost <= c.gasMeter.gasRemaining:
c.gasMeter.consumeGas(codeCost, reason = "Write new contract code")
2022-04-08 04:54:11 +00:00
c.vmState.mutateStateDB:
EVM: `writeContract` fixes, never return contract code as `RETURNDATA` This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and equivalent on other networks. This combines a change on the EVM-caller side with an EVM-side change from @jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller EVM ignore any data except from `REVERT`. Either change works by itself. The reason for both is to ensure we definitely comply with ambiguous EVMC expectations from either side of that boundary, and it makes the internal API clearer. As well as fixing a specific consensus issue, there are some other EVM logic changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the EVM, and changed behaviour with quirks before EIP-2 (Homestead). The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on other networks. Here's a trace at batch 5080897..5081088: ``` TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088 ... DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411 DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615 TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT> ``` Although it says "Contract code size" and "gasUsed", this bug is more general than either contract size or gas. It's due to incorrect behaviour of EVM instructions `RETURNDATA` and `RETURNDATASIZE`. Sometimes when `writeContract` decides to reject writing the contract for any of several reasons (for example just insufficient gas), the unwritten contract code was being used as the "return data", and given to the caller. If the caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported the contract code that didn't get written. EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`: > "`CREATE` and `CREATE2` are considered to return the empty buffer in the > success case and the failure data in the failure case". The language is ambiguous. In fact "failure case" means when the contract uses `REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170 limit, EIP-3541, etc. To be thorough, and to ensure we always do the right thing with real EVMC when that's finalised, this patch fixes the `RETURNDATA` issue in two places, either of which make Goerli block 5080941 pass. `writeContract` has been refactored to be caller, and so has where it's called. It sets an error in the usual way if contract writing is rejected -- that's anticipating EVMC, where we'll use different error codes later. Overall four behaviour changes: 1. On the callee side, it doesn't set `c.outputData` except for `REVERT`. 2. On the caller side, it doesn't read `child.outputData` except for `REVERT`. 3. There was a bug in processing before Homestead fork (EIP-2). We did not match the spec or other implementations; now we do. When there's insufficient gas, before Homestead it's treated as success but with an empty contract. https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304 https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586 4. The Byzantium check has been removed, as it's unnecessary. Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
db.setCode(c.msg.contractAddress, c.output)
withExtra trace, "Writing new contract code"
return
if fork >= FkHomestead:
# EIP-2 (https://eips.ethereum.org/EIPS/eip-2).
# TODO: Return `EVMC_OUT_OF_GAS` (like Silkworm).
c.setError("EVMC_OUT_OF_GAS", true)
else:
EVM: `writeContract` fixes, never return contract code as `RETURNDATA` This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and equivalent on other networks. This combines a change on the EVM-caller side with an EVM-side change from @jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller EVM ignore any data except from `REVERT`. Either change works by itself. The reason for both is to ensure we definitely comply with ambiguous EVMC expectations from either side of that boundary, and it makes the internal API clearer. As well as fixing a specific consensus issue, there are some other EVM logic changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the EVM, and changed behaviour with quirks before EIP-2 (Homestead). The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on other networks. Here's a trace at batch 5080897..5081088: ``` TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088 ... DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411 DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615 TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT> ``` Although it says "Contract code size" and "gasUsed", this bug is more general than either contract size or gas. It's due to incorrect behaviour of EVM instructions `RETURNDATA` and `RETURNDATASIZE`. Sometimes when `writeContract` decides to reject writing the contract for any of several reasons (for example just insufficient gas), the unwritten contract code was being used as the "return data", and given to the caller. If the caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported the contract code that didn't get written. EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`: > "`CREATE` and `CREATE2` are considered to return the empty buffer in the > success case and the failure data in the failure case". The language is ambiguous. In fact "failure case" means when the contract uses `REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170 limit, EIP-3541, etc. To be thorough, and to ensure we always do the right thing with real EVMC when that's finalised, this patch fixes the `RETURNDATA` issue in two places, either of which make Goerli block 5080941 pass. `writeContract` has been refactored to be caller, and so has where it's called. It sets an error in the usual way if contract writing is rejected -- that's anticipating EVMC, where we'll use different error codes later. Overall four behaviour changes: 1. On the callee side, it doesn't set `c.outputData` except for `REVERT`. 2. On the caller side, it doesn't read `child.outputData` except for `REVERT`. 3. There was a bug in processing before Homestead fork (EIP-2). We did not match the spec or other implementations; now we do. When there's insufficient gas, before Homestead it's treated as success but with an empty contract. https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304 https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586 4. The Byzantium check has been removed, as it's unnecessary. Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
# Before EIP-2, when out of gas for code storage, the account ends up with
# zero-length code and no error. No gas is charged. Code cited in EIP-2:
# https://github.com/ethereum/pyethereum/blob/d117c8f3fd93/ethereum/processblock.py#L304
# https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586
# The account already has zero-length code to handle nested calls.
withExtra trace, "New contract given empty code by pre-Homestead rules"
template chainTo*(c: Computation, toChild: typeof(c.child), after: untyped) =
c.child = toChild
c.continuation = proc() =
c.continuation = nil
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
# ------------------------------------------------------------------------------