2021-04-08 14:52:10 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2018 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
2021-04-20 15:39:32 +00:00
|
|
|
# * 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.
|
2021-04-08 14:52:10 +00:00
|
|
|
|
2021-04-22 11:02:13 +00:00
|
|
|
const
|
2021-04-26 15:16:38 +00:00
|
|
|
# help with low memory when compiling selectVM() function
|
2021-04-23 07:50:48 +00:00
|
|
|
lowmem {.intdefine.}: int = 0
|
|
|
|
lowMemoryCompileTime {.used.} = lowmem > 0
|
|
|
|
|
2021-04-08 14:52:10 +00:00
|
|
|
import
|
2021-04-26 15:16:38 +00:00
|
|
|
../constants,
|
|
|
|
../db/accounts_cache,
|
2021-04-20 15:39:32 +00:00
|
|
|
./code_stream,
|
2021-04-26 16:00:46 +00:00
|
|
|
./computation,
|
2021-04-26 14:40:58 +00:00
|
|
|
./interpreter/op_dispatcher,
|
2021-04-26 15:16:38 +00:00
|
|
|
./message,
|
|
|
|
./precompiles,
|
|
|
|
./state,
|
2021-04-22 16:05:58 +00:00
|
|
|
./types,
|
2021-04-26 15:16:38 +00:00
|
|
|
chronicles,
|
|
|
|
eth/[common, keys],
|
|
|
|
macros,
|
|
|
|
options,
|
|
|
|
sets,
|
|
|
|
stew/byteutils,
|
|
|
|
strformat
|
2021-04-08 14:52:10 +00:00
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "vm opcode"
|
|
|
|
|
2021-04-26 15:16:38 +00:00
|
|
|
const
|
|
|
|
ripemdAddr = block:
|
|
|
|
proc initAddress(x: int): EthAddress {.compileTime.} =
|
|
|
|
result[19] = x.byte
|
|
|
|
initAddress(3)
|
|
|
|
|
2021-04-22 11:02:13 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
2021-04-26 15:16:38 +00:00
|
|
|
# Private functions
|
2021-04-22 11:02:13 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2021-04-26 15:16:38 +00:00
|
|
|
proc selectVM(c: Computation, fork: Fork) {.gcsafe.} =
|
2021-04-22 11:02:13 +00:00
|
|
|
## Op code execution handler main loop.
|
2021-04-20 12:07:01 +00:00
|
|
|
var desc: Vm2Ctx
|
2021-04-22 11:02:13 +00:00
|
|
|
desc.cpt = c
|
2021-04-20 12:07:01 +00:00
|
|
|
|
2021-04-20 11:14:43 +00:00
|
|
|
if c.tracingEnabled:
|
|
|
|
c.prepareTracer()
|
|
|
|
|
|
|
|
while true:
|
|
|
|
c.instr = c.code.next()
|
|
|
|
|
2021-04-22 11:02:13 +00:00
|
|
|
# Note Mamy's observation in opTableToCaseStmt() from original VM
|
|
|
|
# regarding computed goto
|
|
|
|
#
|
|
|
|
# ackn:
|
|
|
|
# #{.computedGoto.}
|
|
|
|
# # computed goto causing stack overflow, it consumes a lot of space
|
|
|
|
# # we could use manual jump table instead
|
|
|
|
# # TODO lots of macro magic here to unravel, with chronicles...
|
|
|
|
# # `c`.logger.log($`c`.stack & "\n\n", fgGreen)
|
2021-04-23 07:50:48 +00:00
|
|
|
when not lowMemoryCompileTime:
|
|
|
|
when defined(release):
|
|
|
|
#
|
|
|
|
# FIXME: OS case list below needs to be adjusted
|
|
|
|
#
|
|
|
|
when defined(windows):
|
|
|
|
when defined(cpu64):
|
|
|
|
{.warning: "*** Win64/VM2 handler switch => computedGoto".}
|
|
|
|
{.computedGoto, optimization: speed.}
|
|
|
|
else:
|
|
|
|
# computedGoto not compiling on github/ci (out of memory) -- jordan
|
|
|
|
{.warning: "*** Win32/VM2 handler switch => optimisation disabled".}
|
|
|
|
# {.computedGoto, optimization: speed.}
|
|
|
|
|
|
|
|
elif defined(linux):
|
|
|
|
when defined(cpu64):
|
|
|
|
{.warning: "*** Linux64/VM2 handler switch => computedGoto".}
|
|
|
|
{.computedGoto, optimization: speed.}
|
|
|
|
else:
|
|
|
|
{.warning: "*** Linux32/VM2 handler switch => computedGoto".}
|
|
|
|
{.computedGoto, optimization: speed.}
|
|
|
|
|
|
|
|
elif defined(macosx):
|
|
|
|
when defined(cpu64):
|
|
|
|
{.warning: "*** MacOs64/VM2 handler switch => computedGoto".}
|
|
|
|
{.computedGoto, optimization: speed.}
|
|
|
|
else:
|
|
|
|
{.warning: "*** MacOs32/VM2 handler switch => computedGoto".}
|
|
|
|
{.computedGoto, optimization: speed.}
|
2021-04-22 11:02:13 +00:00
|
|
|
|
|
|
|
else:
|
2021-04-23 07:50:48 +00:00
|
|
|
{.warning: "*** Unsupported OS => no handler switch optimisation".}
|
2021-04-22 11:02:13 +00:00
|
|
|
|
2021-04-26 14:40:58 +00:00
|
|
|
genOptimisedDispatcher(fork, c.instr, desc)
|
2021-04-22 11:02:13 +00:00
|
|
|
|
2021-04-23 07:50:48 +00:00
|
|
|
else:
|
|
|
|
{.warning: "*** low memory compiler mode => program will be slow".}
|
2021-04-22 11:02:13 +00:00
|
|
|
|
2021-04-23 07:50:48 +00:00
|
|
|
genLowMemDispatcher(fork, c.instr, desc)
|
2021-04-22 11:02:13 +00:00
|
|
|
|
2021-04-26 15:16:38 +00:00
|
|
|
|
|
|
|
proc beforeExecCall(c: Computation) =
|
|
|
|
c.snapshot()
|
|
|
|
if c.msg.kind == evmcCall:
|
|
|
|
c.vmState.mutateStateDb:
|
|
|
|
db.subBalance(c.msg.sender, c.msg.value)
|
|
|
|
db.addBalance(c.msg.contractAddress, c.msg.value)
|
|
|
|
|
|
|
|
proc afterExecCall(c: Computation) =
|
|
|
|
## Collect all of the accounts that *may* need to be deleted based on EIP161
|
|
|
|
## https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
|
|
|
|
## also see: https://github.com/ethereum/EIPs/issues/716
|
|
|
|
|
|
|
|
if c.isError or c.fork >= FKByzantium:
|
|
|
|
if c.msg.contractAddress == ripemdAddr:
|
|
|
|
# Special case to account for geth+parity bug
|
|
|
|
c.vmState.touchedAccounts.incl c.msg.contractAddress
|
|
|
|
|
|
|
|
if c.isSuccess:
|
|
|
|
c.commit()
|
|
|
|
c.touchedAccounts.incl c.msg.contractAddress
|
|
|
|
else:
|
|
|
|
c.rollback()
|
|
|
|
|
|
|
|
|
|
|
|
proc beforeExecCreate(c: Computation): bool =
|
|
|
|
c.vmState.mutateStateDB:
|
2022-02-10 08:02:39 +00:00
|
|
|
let nonce = db.getNonce(c.msg.sender)
|
|
|
|
if nonce+1 < nonce:
|
|
|
|
c.setError(&"Nonce overflow when sender={c.msg.sender.toHex} wants to create contract", false)
|
|
|
|
return true
|
|
|
|
db.setNonce(c.msg.sender, nonce+1)
|
2021-04-26 15:16:38 +00:00
|
|
|
|
|
|
|
# We add this to the access list _before_ taking a snapshot.
|
|
|
|
# Even if the creation fails, the access-list change should not be rolled
|
|
|
|
# back EIP2929
|
|
|
|
if c.fork >= FkBerlin:
|
|
|
|
db.accessList(c.msg.contractAddress)
|
|
|
|
|
|
|
|
c.snapshot()
|
|
|
|
|
|
|
|
if c.vmState.readOnlyStateDb().hasCodeOrNonce(c.msg.contractAddress):
|
|
|
|
var blurb =c.msg.contractAddress.toHex
|
|
|
|
c.setError("Address collision when creating contract address={blurb}", true)
|
|
|
|
c.rollback()
|
|
|
|
return true
|
|
|
|
|
|
|
|
c.vmState.mutateStateDb:
|
|
|
|
db.subBalance(c.msg.sender, c.msg.value)
|
|
|
|
db.addBalance(c.msg.contractAddress, c.msg.value)
|
|
|
|
db.clearStorage(c.msg.contractAddress)
|
|
|
|
if c.fork >= FkSpurious:
|
|
|
|
# EIP161 nonce incrementation
|
|
|
|
db.incNonce(c.msg.contractAddress)
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
proc afterExecCreate(c: Computation) =
|
|
|
|
if c.isSuccess:
|
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
|
|
|
# This can change `c.isSuccess`.
|
|
|
|
c.writeContract()
|
|
|
|
# Contract code should never be returned to the caller. Only data from
|
|
|
|
# `REVERT` is returned after a create. Clearing in this branch covers the
|
|
|
|
# right cases, particularly important with EVMC where it must be cleared.
|
|
|
|
if c.output.len > 0:
|
|
|
|
c.output = @[]
|
2021-04-26 15:16:38 +00:00
|
|
|
|
|
|
|
if c.isSuccess:
|
|
|
|
c.commit()
|
|
|
|
else:
|
|
|
|
c.rollback()
|
|
|
|
|
|
|
|
|
|
|
|
proc beforeExec(c: Computation): bool =
|
|
|
|
if not c.msg.isCreate:
|
|
|
|
c.beforeExecCall()
|
|
|
|
false
|
|
|
|
else:
|
|
|
|
c.beforeExecCreate()
|
|
|
|
|
|
|
|
proc afterExec(c: Computation) =
|
|
|
|
if not c.msg.isCreate:
|
|
|
|
c.afterExecCall()
|
|
|
|
else:
|
|
|
|
c.afterExecCreate()
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc executeOpcodes*(c: Computation) =
|
|
|
|
let fork = c.fork
|
|
|
|
|
|
|
|
block:
|
|
|
|
if not c.continuation.isNil:
|
|
|
|
c.continuation = nil
|
|
|
|
elif c.execPrecompiles(fork):
|
|
|
|
break
|
|
|
|
|
|
|
|
try:
|
|
|
|
c.selectVM(fork)
|
|
|
|
except CatchableError as e:
|
|
|
|
c.setError(
|
|
|
|
&"Opcode Dispatch Error msg={e.msg}, depth={c.msg.depth}", true)
|
|
|
|
|
|
|
|
if c.isError() and c.continuation.isNil:
|
|
|
|
if c.tracingEnabled: c.traceError()
|
Tracing: Remove some trace messages that occur a lot during sync
Disable some trace messages which appeared a lot in the output and probably
aren't so useful any more, when block processing is functioning well at high
speed.
Turning on the trace level globally is useful to get a feel for what's
happening, but only if each category is kept to a reasonable amount.
As well as overwhelming the output so that it's hard to see general activity,
some of these messages happen so much they severely slow down processing. Ones
called every time an EVM opcode uses some gas are particularly extreme.
These messages have all been chosen as things which are probably not useful any
more (the relevant functionality has been debugged and is tested plenty).
These have been commented out rather than removed. It may be that turning
trace topics on/off, or other selection, is a better longer term solution, but
that will require better command line options and good defaults for sure.
(I think higher levels `tracev` and `tracevv` levels (extra verbose) would be
more useful for this sort of deep tracing on request.)
For now, enabling `--log-level:TRACE` on the command line is quite useful as
long as we keep each category reasonable, and this patch tries to keep that
balance.
- Don't show "has transactions" on virtually every block imported.
- Don't show "Sender" and "txHash" lines on every transaction processed.
- Don't show "GAS CONSUMPTION" on every opcode executed", this is way too much.
- Don't show "GAS RETURNED" and "GAS REFUND" on each contract call.
- Don't show "op: Stop" on every Stop opcode, which means every transaction.
- Don't show "Insufficient funds" whenever a contract can't call another.
- Don't show "ECRecover", "SHA256 precompile", "RIPEMD160", "Identity"
or even "Call precompile" every time a precompile is called. These are
very well tested now.
- Don't show "executeOpcodes error" whenever a contract returns an error.
(This is changed to `trace` too, it's a normal event that is well tested.)
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-07-22 13:35:41 +00:00
|
|
|
#trace "executeOpcodes error", msg=c.error.info
|
2021-04-26 15:16:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
proc execCallOrCreate*(cParam: Computation) =
|
|
|
|
var (c, before) = (cParam, true)
|
|
|
|
defer:
|
|
|
|
while not c.isNil:
|
|
|
|
c.dispose()
|
|
|
|
c = c.parent
|
|
|
|
|
|
|
|
# No actual recursion, but simulate recursion including before/after/dispose.
|
|
|
|
while true:
|
|
|
|
while true:
|
|
|
|
if before and c.beforeExec():
|
|
|
|
break
|
|
|
|
c.executeOpcodes()
|
|
|
|
if c.continuation.isNil:
|
|
|
|
c.afterExec()
|
|
|
|
break
|
|
|
|
(before, c.child, c, c.parent) = (true, nil.Computation, c.child, c)
|
|
|
|
if c.parent.isNil:
|
|
|
|
break
|
|
|
|
c.dispose()
|
|
|
|
(before, c.parent, c) = (false, nil.Computation, c.parent)
|
|
|
|
(c.continuation)()
|
|
|
|
|
2021-04-22 11:02:13 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|