Skip to content

Commit

Permalink
Remove exceptions from EVM (#2314)
Browse files Browse the repository at this point in the history
* Remove exception from evm memory

* Remove exception from gas meter

* Remove exception from stack

* Remove exception from precompiles

* Remove exception from gas_costs

* Remove exception from op handlers

* Remove exception from op dispatcher

* Remove exception from call_evm

* Remove exception from EVM

* Fix tools and tests

* Remove exception from EVMC

* fix evmc

* Fix evmc

* Remove remnants of async evm stuff

* Remove superflous error handling

* Proc to func

* Fix errors detected by CI

* Fix EVM op call stack usage

* REmove exception handling from getVmState

* Better error message instead of just doAssert

* Remove unused validation

* Remove superflous catchRaise

* Use results.expect instead of unsafeValue
  • Loading branch information
jangko committed Jun 7, 2024
1 parent dd80957 commit b3a5c67
Show file tree
Hide file tree
Showing 64 changed files with 1,808 additions and 1,849 deletions.
12 changes: 4 additions & 8 deletions nimbus/core/chain/persist_blocks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,8 @@ const
proc getVmState(c: ChainRef, header: BlockHeader):
Result[BaseVMState, string] =
let vmState = BaseVMState()
try:
# TODO clean up exception handling
if not vmState.init(header, c.com):
return err("Could not initialise VMState")
except CatchableError as exc:
return err("Error while initializing VMState: " & exc.msg)

if not vmState.init(header, c.com):
return err("Could not initialise VMState")
ok(vmState)

proc purgeOlderBlocksFromHistory(
Expand Down Expand Up @@ -141,7 +136,8 @@ proc persistBlocksImpl(c: ChainRef; headers: openArray[BlockHeader];
let
mkeys = vmState.stateDB.makeMultiKeys()
# Reset state to what it was before executing the block of transactions
initialState = BaseVMState.new(header, c.com)
initialState = BaseVMState.new(header, c.com).valueOr:
return err("Failed to create vm state")
witness = initialState.buildWitness(mkeys)

dbTx.rollback()
Expand Down
3 changes: 1 addition & 2 deletions nimbus/core/executor/process_block.nim
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ proc processTransactions*(
vmState: BaseVMState;
header: BlockHeader;
transactions: seq[Transaction];
): Result[void, string]
{.gcsafe, raises: [CatchableError].} =
): Result[void, string] =
vmState.receipts = newSeq[Receipt](transactions.len)
vmState.cumulativeGasUsed = 0

Expand Down
11 changes: 4 additions & 7 deletions nimbus/core/executor/process_transaction.nim
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ proc processTransactionImpl(
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader; ## Header for the block containing the current tx
fork: EVMFork;
): Result[GasInt, string]
{.raises: [CatchableError].} =
): Result[GasInt, string] =
## Modelled after `https://eips.ethereum.org/EIPS/eip-1559#specification`_
## which provides a backward compatible framwork for EIP1559.

Expand Down Expand Up @@ -127,7 +126,7 @@ proc processTransactionImpl(
# ------------------------------------------------------------------------------

proc processBeaconBlockRoot*(vmState: BaseVMState, beaconRoot: Hash256):
Result[void, string] {.raises: [CatchableError].} =
Result[void, string] =
## processBeaconBlockRoot applies the EIP-4788 system call to the
## beacon block root contract. This method is exported to be used in tests.
## If EIP-4788 is enabled, we need to invoke the beaconroot storage
Expand Down Expand Up @@ -164,17 +163,15 @@ proc processTransaction*(
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader; ## Header for the block containing the current tx
fork: EVMFork;
): Result[GasInt,string]
{.raises: [CatchableError].} =
): Result[GasInt,string] =
vmState.processTransactionImpl(tx, sender, header, fork)

proc processTransaction*(
vmState: BaseVMState; ## Parent accounts environment for transaction
tx: Transaction; ## Transaction to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader;
): Result[GasInt,string]
{.raises: [CatchableError].} =
): Result[GasInt,string] =
let fork = vmState.com.toEVMFork(header.forkDeterminationInfo)
vmState.processTransaction(tx, sender, header, fork)

Expand Down
11 changes: 4 additions & 7 deletions nimbus/core/tx_pool/tx_tasks/tx_packer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,18 @@ proc persist(pst: TxPackerStateRef)
# Private functions
# ------------------------------------------------------------------------------

proc runTx(pst: TxPackerStateRef; item: TxItemRef): GasInt
{.gcsafe,raises: [CatchableError].} =
proc runTx(pst: TxPackerStateRef; item: TxItemRef): GasInt =
## Execute item transaction and update `vmState` book keeping. Returns the
## `gasUsed` after executing the transaction.
let
fork = pst.xp.chain.nextFork
baseFee = pst.xp.chain.baseFee
tx = item.tx.eip1559TxNormalization(baseFee.GasInt)

#safeExecutor "tx_packer.runTx":
# # Execute transaction, may return a wildcard `Exception`
result = tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork)

let gasUsed = tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork)
pst.cleanState = false
doAssert 0 <= result
doAssert 0 <= gasUsed
gasUsed

proc runTxCommit(pst: TxPackerStateRef; item: TxItemRef; gasBurned: GasInt)
{.gcsafe,raises: [CatchableError].} =
Expand Down
73 changes: 23 additions & 50 deletions nimbus/evm/computation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import
"."/[code_stream, memory, message, stack, state],
"."/[types],
./interpreter/[gas_meter, gas_costs, op_codes],
./evm_errors,
../common/[common, evmforks],
../utils/utils,
stew/byteutils,
Expand Down Expand Up @@ -131,25 +132,24 @@ template getBlobBaseFee*(c: Computation): UInt256 =
else:
c.vmState.txCtx.blobBaseFee

proc getBlockHash*(c: Computation, number: UInt256): Hash256
{.gcsafe, raises: [CatchableError].} =
proc getBlockHash*(c: Computation, number: UInt256): Hash256 =
when evmc_enabled:
let
blockNumber = c.host.getTxContext().block_number.u256
ancestorDepth = blockNumber - number - 1
if ancestorDepth >= constants.MAX_PREV_HEADER_DEPTH:
return
return Hash256()
if number >= blockNumber:
return
return Hash256()
c.host.getBlockHash(number)
else:
let
blockNumber = c.vmState.blockNumber
ancestorDepth = blockNumber - number - 1
if ancestorDepth >= constants.MAX_PREV_HEADER_DEPTH:
return
return Hash256()
if number >= blockNumber:
return
return Hash256()
c.vmState.getAncestorHash(number.vmWordToBlockNumber)

template accountExists*(c: Computation, address: EthAddress): bool =
Expand Down Expand Up @@ -221,8 +221,8 @@ proc newComputation*(vmState: BaseVMState, sysCall: bool, message: Message,
new result
result.vmState = vmState
result.msg = message
result.memory = Memory()
result.stack = newStack()
result.memory = EvmMemoryRef.new()
result.stack = EvmStackRef.new()
result.returnStack = @[]
result.gasMeter.init(message.gas)
result.sysCall = sysCall
Expand All @@ -240,8 +240,8 @@ func newComputation*(vmState: BaseVMState, sysCall: bool,
new result
result.vmState = vmState
result.msg = message
result.memory = Memory()
result.stack = newStack()
result.memory = EvmMemoryRef.new()
result.stack = EvmStackRef.new()
result.returnStack = @[]
result.gasMeter.init(message.gas)
result.code = newCodeStream(code)
Expand Down Expand Up @@ -298,8 +298,7 @@ func errorOpt*(c: Computation): Option[string] =
return none(string)
some(c.error.info)

proc writeContract*(c: Computation)
{.gcsafe, raises: [CatchableError].} =
proc writeContract*(c: Computation) =
template withExtra(tracer: untyped, args: varargs[untyped]) =
tracer args, newContract=($c.msg.contractAddress),
blockNumber=c.vmState.blockNumber,
Expand Down Expand Up @@ -332,10 +331,15 @@ proc writeContract*(c: Computation)
# 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: len)
let codeCost = c.gasCosts[Create].c_handler(0.u256, gasParams).gasCost
let
gasParams = GasParams(kind: Create, cr_memLength: len)
res = c.gasCosts[Create].cr_handler(0.u256, gasParams)
codeCost = res.gasCost

if codeCost <= c.gasMeter.gasRemaining:
c.gasMeter.consumeGas(codeCost, reason = "Write new contract code")
c.gasMeter.consumeGas(codeCost,
reason = "Write new contract code").
expect("enough gas since we checked against gasRemaining")
c.vmState.mutateStateDB:
db.setCode(c.msg.contractAddress, c.output)
withExtra trace, "Writing new contract code"
Expand All @@ -354,48 +358,17 @@ proc writeContract*(c: Computation)

template chainTo*(c: Computation,
toChild: typeof(c.child),
shouldRaise: static[bool],
after: untyped) =

when shouldRaise:
{.pragma: chainToPragma, gcsafe, raises: [CatchableError].}
else:
{.pragma: chainToPragma, gcsafe, raises: [].}

c.child = toChild
c.continuation = proc() {.chainToPragma.} =
c.continuation = nil
after

# Register an async operation to be performed before the continuation is called.
template asyncChainTo*(c: Computation,
asyncOperation: Future[void],
after: untyped) =
c.pendingAsyncOperation = asyncOperation
c.continuation = proc() {.gcsafe, raises: [].} =
c.continuation = nil
after

template asyncChainToRaise*(c: Computation,
asyncOperation: Future[void],
RaisesTypes: untyped,
after: untyped) =
c.pendingAsyncOperation = asyncOperation
c.continuation = proc() {.gcsafe, raises: RaisesTypes.} =
c.continuation = proc(): EvmResultVoid {.gcsafe, raises: [].} =
c.continuation = nil
after

func merge*(c, child: Computation) =
c.gasMeter.refundGas(child.gasMeter.gasRefunded)

when evmc_enabled:
{.pragma: selfDesructPragma, gcsafe, raises: [CatchableError].}
else:
{.pragma: selfDesructPragma, gcsafe, raises: [].}

proc execSelfDestruct*(c: Computation, beneficiary: EthAddress)
{.selfDesructPragma.} =

proc execSelfDestruct*(c: Computation, beneficiary: EthAddress) =
c.vmState.mutateStateDB:
let localBalance = c.getBalance(c.msg.contractAddress)

Expand Down Expand Up @@ -468,8 +441,8 @@ func prepareTracer*(c: Computation) =
c.vmState.capturePrepare(c, c.msg.depth)

func opcodeGastCost*(
c: Computation, op: Op, gasCost: GasInt, reason: string)
{.raises: [OutOfGas, ValueError].} =
c: Computation, op: Op, gasCost: GasInt, reason: string): EvmResultVoid
{.raises: [].} =
c.vmState.captureGasCost(
c,
op,
Expand Down
73 changes: 73 additions & 0 deletions nimbus/evm/evm_errors.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Nimbus
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http:https://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http:https://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

{.push raises: [].}

import
results

export
results

type
EvmErrorCode* {.pure.} = enum
EvmBug
OutOfGas
MemoryFull
StackFull
StackInsufficient
PrcInvalidSig
PrcInvalidPoint
PrcInvalidParam
PrcValidationError
GasIntOverflow
InvalidInstruction
StaticContext
InvalidJumpDest
OutOfBounds
InvalidInitCode
EvmHeaderNotFound
EvmInvalidParam

EvmErrorObj* = object
code*: EvmErrorCode

EvmResultVoid* = Result[void, EvmErrorObj]
EvmResult*[T] = Result[T, EvmErrorObj]

template gasErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)

template memErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)

template stackErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)

template prcErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)

template opErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)

template evmErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)
Loading

0 comments on commit b3a5c67

Please sign in to comment.