bubble up validate transaction error message
This commit is contained in:
parent
8700d8b1e1
commit
10aabd8ca1
|
@ -11,6 +11,7 @@
|
|||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/strutils,
|
||||
../../common/common,
|
||||
../../db/accounts_cache,
|
||||
../../transaction/call_evm,
|
||||
|
@ -19,7 +20,6 @@ import
|
|||
../../vm_types,
|
||||
../../evm/async/operations,
|
||||
../validate,
|
||||
chronicles,
|
||||
chronos,
|
||||
stew/results
|
||||
|
||||
|
@ -38,18 +38,18 @@ proc commitOrRollbackDependingOnGasUsed(
|
|||
vmState: BaseVMState, accTx: SavePoint,
|
||||
header: BlockHeader, tx: Transaction,
|
||||
gasBurned: GasInt, priorityFee: GasInt):
|
||||
Result[GasInt, void] {.raises: [].} =
|
||||
Result[GasInt, string] {.raises: [].} =
|
||||
# Make sure that the tx does not exceed the maximum cumulative limit as
|
||||
# set in the block header. Again, the eip-1559 reference does not mention
|
||||
# an early stop. It would rather detect differing values for the block
|
||||
# header `gasUsed` and the `vmState.cumulativeGasUsed` at a later stage.
|
||||
if header.gasLimit < vmState.cumulativeGasUsed + gasBurned:
|
||||
vmState.stateDB.rollback(accTx)
|
||||
debug "invalid tx: block header gasLimit reached",
|
||||
maxLimit = header.gasLimit,
|
||||
gasUsed = vmState.cumulativeGasUsed,
|
||||
addition = gasBurned
|
||||
return err()
|
||||
try:
|
||||
vmState.stateDB.rollback(accTx)
|
||||
return err("invalid tx: block header gasLimit reached. gasLimit=$1, gasUsed=$2, addition=$3" % [
|
||||
$header.gasLimit, $vmState.cumulativeGasUsed, $gasBurned])
|
||||
except ValueError as ex:
|
||||
return err(ex.msg)
|
||||
else:
|
||||
# Accept transaction and collect mining fee.
|
||||
vmState.stateDB.commit(accTx)
|
||||
|
@ -66,15 +66,12 @@ proc asyncProcessTransactionImpl(
|
|||
tx: Transaction; ## Transaction to validate
|
||||
sender: EthAddress; ## tx.getSender or tx.ecRecover
|
||||
header: BlockHeader; ## Header for the block containing the current tx
|
||||
fork: EVMFork): Future[Result[GasInt,void]]
|
||||
fork: EVMFork): Future[Result[GasInt, string]]
|
||||
# wildcard exception, wrapped below
|
||||
{.async, gcsafe.} =
|
||||
## Modelled after `https://eips.ethereum.org/EIPS/eip-1559#specification`_
|
||||
## which provides a backward compatible framwork for EIP1559.
|
||||
|
||||
#trace "Sender", sender
|
||||
#trace "txHash", rlpHash = ty.rlpHash
|
||||
|
||||
let
|
||||
roDB = vmState.readOnlyStateDB
|
||||
baseFee256 = header.eip1559BaseFee(fork)
|
||||
|
@ -83,7 +80,7 @@ proc asyncProcessTransactionImpl(
|
|||
priorityFee = min(tx.maxPriorityFee, tx.maxFee - baseFee)
|
||||
|
||||
# Return failure unless explicitely set `ok()`
|
||||
var res: Result[GasInt,void] = err()
|
||||
var res: Result[GasInt, string] = err("")
|
||||
|
||||
await ifNecessaryGetAccounts(vmState, @[sender, vmState.coinbase()])
|
||||
if tx.to.isSome:
|
||||
|
@ -91,10 +88,9 @@ proc asyncProcessTransactionImpl(
|
|||
|
||||
# buy gas, then the gas goes into gasMeter
|
||||
if vmState.gasPool < tx.gasLimit:
|
||||
debug "gas limit reached",
|
||||
gasLimit = vmState.gasPool,
|
||||
gasNeeded = tx.gasLimit
|
||||
return
|
||||
return err("gas limit reached. gasLimit=$1, gasNeeded=$2" % [
|
||||
$vmState.gasPool, $tx.gasLimit])
|
||||
|
||||
vmState.gasPool -= tx.gasLimit
|
||||
|
||||
# Actually, the eip-1559 reference does not mention an early exit.
|
||||
|
@ -103,7 +99,8 @@ proc asyncProcessTransactionImpl(
|
|||
# before leaving is crucial for some unit tests that us a direct/deep call
|
||||
# of the `processTransaction()` function. So there is no `return err()`
|
||||
# statement, here.
|
||||
if roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, fork):
|
||||
let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, fork)
|
||||
if txRes.isOk:
|
||||
|
||||
# Execute the transaction.
|
||||
let
|
||||
|
@ -111,6 +108,8 @@ proc asyncProcessTransactionImpl(
|
|||
gasBurned = tx.txCallEvm(sender, vmState, fork)
|
||||
|
||||
res = commitOrRollbackDependingOnGasUsed(vmState, accTx, header, tx, gasBurned, priorityFee)
|
||||
else:
|
||||
res = err(txRes.error)
|
||||
|
||||
if vmState.generateWitness:
|
||||
vmState.stateDB.collectWitnessData()
|
||||
|
@ -129,7 +128,7 @@ proc asyncProcessTransaction*(
|
|||
tx: Transaction; ## Transaction to validate
|
||||
sender: EthAddress; ## tx.getSender or tx.ecRecover
|
||||
header: BlockHeader; ## Header for the block containing the current tx
|
||||
fork: EVMFork): Future[Result[GasInt,void]]
|
||||
fork: EVMFork): Future[Result[GasInt,string]]
|
||||
{.async, gcsafe.} =
|
||||
## Process the transaction, write the results to accounts db. The function
|
||||
## returns the amount of gas burned if executed.
|
||||
|
@ -140,7 +139,7 @@ proc asyncProcessTransaction*(
|
|||
vmState: BaseVMState; ## Parent accounts environment for transaction
|
||||
tx: Transaction; ## Transaction to validate
|
||||
sender: EthAddress; ## tx.getSender or tx.ecRecover
|
||||
header: BlockHeader): Future[Result[GasInt,void]]
|
||||
header: BlockHeader): Future[Result[GasInt,string]]
|
||||
{.async, gcsafe.} =
|
||||
## Variant of `asyncProcessTransaction()` with `*fork* derived
|
||||
## from the `vmState` argument.
|
||||
|
@ -152,7 +151,7 @@ proc processTransaction*(
|
|||
tx: Transaction; ## Transaction to validate
|
||||
sender: EthAddress; ## tx.getSender or tx.ecRecover
|
||||
header: BlockHeader; ## Header for the block containing the current tx
|
||||
fork: EVMFork): Result[GasInt,void]
|
||||
fork: EVMFork): Result[GasInt,string]
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
return waitFor(vmState.asyncProcessTransaction(tx, sender, header, fork))
|
||||
|
||||
|
@ -160,7 +159,7 @@ 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,void]
|
||||
header: BlockHeader): Result[GasInt,string]
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
return waitFor(vmState.asyncProcessTransaction(tx, sender, header))
|
||||
|
||||
|
|
|
@ -234,7 +234,7 @@ proc classifyValidatePacked*(xp: TxPoolRef;
|
|||
xp.chain.limits.trgLimit
|
||||
tx = item.tx.eip1559TxNormalization(xp.chain.baseFee.GasInt, fork)
|
||||
|
||||
roDB.validateTransaction(tx, item.sender, gasLimit, baseFee, fork)
|
||||
roDB.validateTransaction(tx, item.sender, gasLimit, baseFee, fork).isOk
|
||||
|
||||
proc classifyPacked*(xp: TxPoolRef; gasBurned, moreBurned: GasInt): bool =
|
||||
## Classifier for *packing* (i.e. adding up `gasUsed` values after executing
|
||||
|
|
|
@ -9,21 +9,16 @@
|
|||
# according to those terms.
|
||||
|
||||
import
|
||||
std/[sequtils, sets, times],
|
||||
std/[sequtils, sets, times, strutils],
|
||||
../common/common,
|
||||
../db/accounts_cache,
|
||||
".."/[errors, transaction, vm_state, vm_types],
|
||||
"."/[dao, eip4844, gaslimit, withdrawals],
|
||||
./pow/[difficulty, header],
|
||||
./pow,
|
||||
chronicles,
|
||||
nimcrypto/utils,
|
||||
stew/[objects, results]
|
||||
|
||||
# chronicles stuff
|
||||
when loggingEnabled or enabledLogLevel > NONE:
|
||||
import
|
||||
nimcrypto/utils
|
||||
|
||||
from stew/byteutils
|
||||
import nil
|
||||
|
||||
|
@ -51,31 +46,28 @@ func isGenesis(header: BlockHeader): bool =
|
|||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc validateSeal(pow: PowRef; header: BlockHeader): Result[void,string] =
|
||||
let (expMixDigest, miningValue) = try:
|
||||
pow.getPowDigest(header)
|
||||
try:
|
||||
let (expMixDigest, miningValue) = pow.getPowDigest(header)
|
||||
|
||||
if expMixDigest != header.mixDigest:
|
||||
let
|
||||
miningHash = header.getPowSpecs.miningHash
|
||||
(size, cachedHash) = try: pow.getPowCacheLookup(header.blockNumber)
|
||||
except KeyError: return err("Unknown block")
|
||||
except CatchableError as e: return err(e.msg)
|
||||
return err("mixHash mismatch. actual=$1, expected=$2," &
|
||||
" blockNumber=$3, miningHash=$4, nonce=$5, difficulty=$6," &
|
||||
" size=$7, cachedHash=$8" % [
|
||||
$header.mixDigest, $expMixDigest, $header.blockNumber,
|
||||
$miningHash, header.nonce.toHex, $header.difficulty,
|
||||
$size, $cachedHash])
|
||||
|
||||
let value = UInt256.fromBytesBE(miningValue.data)
|
||||
if value > UInt256.high div header.difficulty:
|
||||
return err("mining difficulty error")
|
||||
|
||||
except CatchableError as err:
|
||||
return err("test")
|
||||
|
||||
if expMixDigest != header.mixDigest:
|
||||
let
|
||||
miningHash = header.getPowSpecs.miningHash
|
||||
(size, cachedHash) = try: pow.getPowCacheLookup(header.blockNumber)
|
||||
except KeyError: return err("Unknown block")
|
||||
except CatchableError as e: return err(e.msg)
|
||||
debug "mixHash mismatch",
|
||||
actual = header.mixDigest,
|
||||
expected = expMixDigest,
|
||||
blockNumber = header.blockNumber,
|
||||
miningHash = miningHash,
|
||||
nonce = header.nonce.toHex,
|
||||
difficulty = header.difficulty,
|
||||
size = size,
|
||||
cachedHash = cachedHash
|
||||
return err("mixHash mismatch")
|
||||
|
||||
let value = UInt256.fromBytesBE(miningValue.data)
|
||||
if value > UInt256.high div header.difficulty:
|
||||
return err("mining difficulty error")
|
||||
return err(err.msg)
|
||||
|
||||
ok()
|
||||
|
||||
|
@ -249,22 +241,19 @@ proc validateTransaction*(
|
|||
sender: EthAddress; ## tx.getSender or tx.ecRecover
|
||||
maxLimit: GasInt; ## gasLimit from block header
|
||||
baseFee: UInt256; ## baseFee from block header
|
||||
fork: EVMFork): bool =
|
||||
fork: EVMFork): Result[void, string] =
|
||||
let
|
||||
balance = roDB.getBalance(sender)
|
||||
nonce = roDB.getNonce(sender)
|
||||
|
||||
if tx.txType == TxEip2930 and fork < FkBerlin:
|
||||
debug "invalid tx: Eip2930 Tx type detected before Berlin"
|
||||
return false
|
||||
return err("invalid tx: Eip2930 Tx type detected before Berlin")
|
||||
|
||||
if tx.txType == TxEip1559 and fork < FkLondon:
|
||||
debug "invalid tx: Eip1559 Tx type detected before London"
|
||||
return false
|
||||
return err("invalid tx: Eip1559 Tx type detected before London")
|
||||
|
||||
if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE:
|
||||
debug "invalid tx: initcode size exceeds maximum"
|
||||
return false
|
||||
return err("invalid tx: initcode size exceeds maximum")
|
||||
|
||||
# Note that the following check bears some plausibility but is _not_
|
||||
# covered by the eip-1559 reference (sort of) pseudo code, for details
|
||||
|
@ -281,81 +270,66 @@ proc validateTransaction*(
|
|||
#
|
||||
# The parallel lowGasLimit.json test never triggers the case checked below
|
||||
# as the paricular transaction is omitted (the txs list is just set empty.)
|
||||
if maxLimit < tx.gasLimit:
|
||||
debug "invalid tx: block header gasLimit exceeded",
|
||||
maxLimit,
|
||||
gasLimit = tx.gasLimit
|
||||
return false
|
||||
try:
|
||||
if maxLimit < tx.gasLimit:
|
||||
return err("invalid tx: block header gasLimit exceeded. maxLimit=$1, gasLimit=$2" % [
|
||||
$maxLimit, $tx.gasLimit])
|
||||
|
||||
# ensure that the user was willing to at least pay the base fee
|
||||
if tx.maxFee < baseFee.truncate(int64):
|
||||
debug "invalid tx: maxFee is smaller than baseFee",
|
||||
maxFee = tx.maxFee,
|
||||
baseFee
|
||||
return false
|
||||
# ensure that the user was willing to at least pay the base fee
|
||||
if tx.maxFee < baseFee.truncate(int64):
|
||||
return err("invalid tx: maxFee is smaller than baseFee. maxFee=$1, baseFee=$2" % [
|
||||
$tx.maxFee, $baseFee])
|
||||
|
||||
# The total must be the larger of the two
|
||||
if tx.maxFee < tx.maxPriorityFee:
|
||||
debug "invalid tx: maxFee is smaller than maPriorityFee",
|
||||
maxFee = tx.maxFee,
|
||||
maxPriorityFee = tx.maxPriorityFee
|
||||
return false
|
||||
# The total must be the larger of the two
|
||||
if tx.maxFee < tx.maxPriorityFee:
|
||||
return err("invalid tx: maxFee is smaller than maPriorityFee. maxFee=$1, maxPriorityFee=$2" % [
|
||||
$tx.maxFee, $tx.maxPriorityFee])
|
||||
|
||||
# the signer must be able to fully afford the transaction
|
||||
let gasCost = if tx.txType >= TxEip1559:
|
||||
tx.gasLimit.u256 * tx.maxFee.u256
|
||||
else:
|
||||
tx.gasLimit.u256 * tx.gasPrice.u256
|
||||
# the signer must be able to fully afford the transaction
|
||||
let gasCost = if tx.txType >= TxEip1559:
|
||||
tx.gasLimit.u256 * tx.maxFee.u256
|
||||
else:
|
||||
tx.gasLimit.u256 * tx.gasPrice.u256
|
||||
|
||||
if balance < gasCost:
|
||||
debug "invalid tx: not enough cash for gas",
|
||||
available = balance,
|
||||
require = gasCost
|
||||
return false
|
||||
if balance < gasCost:
|
||||
return err("invalid tx: not enough cash for gas. avail=$1, require=$2" % [
|
||||
$balance, $gasCost])
|
||||
|
||||
if balance - gasCost < tx.value:
|
||||
debug "invalid tx: not enough cash to send",
|
||||
available=balance,
|
||||
availableMinusGas=balance-gasCost,
|
||||
require=tx.value
|
||||
return false
|
||||
if balance - gasCost < tx.value:
|
||||
return err("invalid tx: not enough cash to send. avail=$1, availMinusGas=$2, require=$3" % [
|
||||
$balance, $(balance-gasCost), $tx.value])
|
||||
|
||||
if tx.gasLimit < tx.intrinsicGas(fork):
|
||||
debug "invalid tx: not enough gas to perform calculation",
|
||||
available=tx.gasLimit,
|
||||
require=tx.intrinsicGas(fork)
|
||||
return false
|
||||
if tx.gasLimit < tx.intrinsicGas(fork):
|
||||
return err("invalid tx: not enough gas to perform calculation. avail=$1, require=$2" % [
|
||||
$tx.gasLimit, $tx.intrinsicGas(fork)])
|
||||
|
||||
if tx.nonce != nonce:
|
||||
debug "invalid tx: account nonce mismatch",
|
||||
txNonce=tx.nonce,
|
||||
accountNonce=nonce
|
||||
return false
|
||||
if tx.nonce != nonce:
|
||||
return err("invalid tx: account nonce mismatch. txNonce=$1, accNonce=$2" % [
|
||||
$tx.nonce, $nonce])
|
||||
|
||||
if tx.nonce == high(uint64):
|
||||
debug "invalid tx: nonce at maximum"
|
||||
return false
|
||||
if tx.nonce == high(uint64):
|
||||
return err("invalid tx: nonce at maximum")
|
||||
|
||||
# EIP-3607 Reject transactions from senders with deployed code
|
||||
# The EIP spec claims this attack never happened before
|
||||
# Clients might choose to disable this rule for RPC calls like
|
||||
# `eth_call` and `eth_estimateGas`
|
||||
# EOA = Externally Owned Account
|
||||
let codeHash = roDB.getCodeHash(sender)
|
||||
if codeHash != EMPTY_SHA3:
|
||||
debug "invalid tx: sender is not an EOA",
|
||||
sender=sender.toHex,
|
||||
codeHash=codeHash.data.toHex
|
||||
return false
|
||||
# EIP-3607 Reject transactions from senders with deployed code
|
||||
# The EIP spec claims this attack never happened before
|
||||
# Clients might choose to disable this rule for RPC calls like
|
||||
# `eth_call` and `eth_estimateGas`
|
||||
# EOA = Externally Owned Account
|
||||
let codeHash = roDB.getCodeHash(sender)
|
||||
if codeHash != EMPTY_SHA3:
|
||||
return err("invalid tx: sender is not an EOA. sender=$1, codeHash=$2" % [
|
||||
sender.toHex, codeHash.data.toHex])
|
||||
except CatchableError as ex:
|
||||
return err(ex.msg)
|
||||
|
||||
true
|
||||
ok()
|
||||
|
||||
proc validateTransaction*(
|
||||
vmState: BaseVMState; ## Parent accounts environment for transaction
|
||||
tx: Transaction; ## tx to validate
|
||||
sender: EthAddress; ## tx.getSender or tx.ecRecover
|
||||
header: BlockHeader; ## Header for the block containing the current tx
|
||||
fork: EVMFork): bool =
|
||||
fork: EVMFork): Result[void, string] =
|
||||
## Variant of `validateTransaction()`
|
||||
let
|
||||
roDB = vmState.readOnlyStateDB
|
||||
|
|
|
@ -228,7 +228,7 @@ proc exec(ctx: var TransContext,
|
|||
if rc.isErr:
|
||||
rejected.add RejectedTx(
|
||||
index: txIndex,
|
||||
error: "processTransaction failed"
|
||||
error: rc.error
|
||||
)
|
||||
continue
|
||||
|
||||
|
|
Loading…
Reference in New Issue