266 lines
8.8 KiB
Nim
266 lines
8.8 KiB
Nim
# 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.
|
|
|
|
## Transaction Pool Tasklet: Classify Transactions
|
|
## ===============================================
|
|
##
|
|
|
|
import
|
|
../../../forks,
|
|
../../../p2p/validate,
|
|
../../../transaction,
|
|
../../../vm_state,
|
|
../../../vm_types,
|
|
../tx_chain,
|
|
../tx_desc,
|
|
../tx_item,
|
|
../tx_tabs,
|
|
chronicles,
|
|
eth/[common, keys]
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
logScope:
|
|
topics = "tx-pool classify"
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private function: tx validity check helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc checkTxBasic(xp: TxPoolRef; item: TxItemRef): bool =
|
|
## Inspired by `p2p/validate.validateTransaction()`
|
|
if item.tx.txType == TxEip2930 and xp.chain.nextFork < FkBerlin:
|
|
debug "invalid tx: Eip2930 Tx type detected before Berlin"
|
|
return false
|
|
|
|
if item.tx.txType == TxEip1559 and xp.chain.nextFork < FkLondon:
|
|
debug "invalid tx: Eip1559 Tx type detected before London"
|
|
return false
|
|
|
|
if item.tx.gasLimit < item.tx.intrinsicGas(xp.chain.nextFork):
|
|
debug "invalid tx: not enough gas to perform calculation",
|
|
available = item.tx.gasLimit,
|
|
require = item.tx.intrinsicGas(xp.chain.nextFork)
|
|
return false
|
|
|
|
if item.tx.txType == TxEip1559:
|
|
# The total must be the larger of the two
|
|
if item.tx.maxFee < item.tx.maxPriorityFee:
|
|
debug "invalid tx: maxFee is smaller than maPriorityFee",
|
|
maxFee = item.tx.maxFee,
|
|
maxPriorityFee = item.tx.maxPriorityFee
|
|
return false
|
|
|
|
true
|
|
|
|
proc checkTxNonce(xp: TxPoolRef; item: TxItemRef): bool
|
|
{.gcsafe,raises: [Defect,CatchableError].} =
|
|
## Make sure that there is only one contiuous sequence of nonces (per
|
|
## sender) starting at the account nonce.
|
|
|
|
# get the next applicable nonce as registered on the account database
|
|
let accountNonce = xp.chain.getNonce(item.sender)
|
|
|
|
if item.tx.nonce < accountNonce:
|
|
debug "invalid tx: account nonce too small",
|
|
txNonce = item.tx.nonce,
|
|
accountNonce
|
|
return false
|
|
|
|
elif accountNonce < item.tx.nonce:
|
|
# for an existing account, nonces must come in increasing consecutive order
|
|
let rc = xp.txDB.bySender.eq(item.sender)
|
|
if rc.isOk:
|
|
if rc.value.data.any.eq(item.tx.nonce - 1).isErr:
|
|
debug "invalid tx: account nonces gap",
|
|
txNonce = item.tx.nonce,
|
|
accountNonce
|
|
return false
|
|
|
|
true
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private function: active tx classifier check helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc txNonceActive(xp: TxPoolRef; item: TxItemRef): bool
|
|
{.gcsafe,raises: [Defect,KeyError].} =
|
|
## Make sure that nonces appear as a contiuous sequence in `staged` bucket
|
|
## probably preceeded in `packed` bucket.
|
|
let rc = xp.txDB.bySender.eq(item.sender)
|
|
if rc.isErr:
|
|
return true
|
|
# Must not be in the `pending` bucket.
|
|
if rc.value.data.eq(txItemPending).eq(item.tx.nonce - 1).isOk:
|
|
return false
|
|
true
|
|
|
|
|
|
proc txGasCovered(xp: TxPoolRef; item: TxItemRef): bool =
|
|
## Check whether the max gas consumption is within the gas limit (aka block
|
|
## size).
|
|
let trgLimit = xp.chain.limits.trgLimit
|
|
if trgLimit < item.tx.gasLimit:
|
|
debug "invalid tx: gasLimit exceeded",
|
|
maxLimit = trgLimit,
|
|
gasLimit = item.tx.gasLimit
|
|
return false
|
|
true
|
|
|
|
proc txFeesCovered(xp: TxPoolRef; item: TxItemRef): bool =
|
|
## Ensure that the user was willing to at least pay the base fee
|
|
if item.tx.txType == TxEip1559:
|
|
if item.tx.maxFee.GasPriceEx < xp.chain.baseFee:
|
|
debug "invalid tx: maxFee is smaller than baseFee",
|
|
maxFee = item.tx.maxFee,
|
|
baseFee = xp.chain.baseFee
|
|
return false
|
|
true
|
|
|
|
proc txCostInBudget(xp: TxPoolRef; item: TxItemRef): bool
|
|
{.gcsafe,raises: [Defect,CatchableError].} =
|
|
## Check whether the worst case expense is covered by the price budget,
|
|
let
|
|
balance = xp.chain.getBalance(item.sender)
|
|
gasCost = item.tx.gasLimit.u256 * item.tx.gasPrice.u256
|
|
if balance < gasCost:
|
|
debug "invalid tx: not enough cash for gas",
|
|
available = balance,
|
|
require = gasCost
|
|
return false
|
|
let balanceOffGasCost = balance - gasCost
|
|
if balanceOffGasCost < item.tx.value:
|
|
debug "invalid tx: not enough cash to send",
|
|
available = balance,
|
|
availableMinusGas = balanceOffGasCost,
|
|
require = item.tx.value
|
|
return false
|
|
true
|
|
|
|
|
|
proc txPreLondonAcceptableGasPrice(xp: TxPoolRef; item: TxItemRef): bool =
|
|
## For legacy transactions check whether minimum gas price and tip are
|
|
## high enough. These checks are optional.
|
|
if item.tx.txType != TxEip1559:
|
|
|
|
if stageItemsPlMinPrice in xp.pFlags:
|
|
if item.tx.gasPrice.GasPriceEx < xp.pMinPlGasPrice:
|
|
return false
|
|
|
|
elif stageItems1559MinTip in xp.pFlags:
|
|
# Fall back transaction selector scheme
|
|
if item.tx.effectiveGasTip(xp.chain.baseFee) < xp.pMinTipPrice:
|
|
return false
|
|
true
|
|
|
|
proc txPostLondonAcceptableTipAndFees(xp: TxPoolRef; item: TxItemRef): bool =
|
|
## Helper for `classifyTxPacked()`
|
|
if item.tx.txType == TxEip1559:
|
|
|
|
if stageItems1559MinTip in xp.pFlags:
|
|
if item.tx.effectiveGasTip(xp.chain.baseFee) < xp.pMinTipPrice:
|
|
return false
|
|
|
|
if stageItems1559MinFee in xp.pFlags:
|
|
if item.tx.maxFee.GasPriceEx < xp.pMinFeePrice:
|
|
return false
|
|
true
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functionss
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc classifyValid*(xp: TxPoolRef; item: TxItemRef): bool
|
|
{.gcsafe,raises: [Defect,CatchableError].} =
|
|
## Check a (typically new) transaction whether it should be accepted at all
|
|
## or re-jected right away.
|
|
|
|
if not xp.checkTxNonce(item):
|
|
return false
|
|
|
|
if not xp.checkTxBasic(item):
|
|
return false
|
|
|
|
true
|
|
|
|
proc classifyActive*(xp: TxPoolRef; item: TxItemRef): bool
|
|
{.gcsafe,raises: [Defect,CatchableError].} =
|
|
## Check whether a valid transaction is ready to be held in the
|
|
## `staged` bucket in which case the function returns `true`.
|
|
|
|
if not xp.txNonceActive(item):
|
|
return false
|
|
|
|
if item.tx.effectiveGasTip(xp.chain.baseFee) <= 0.GasPriceEx:
|
|
return false
|
|
|
|
if not xp.txGasCovered(item):
|
|
return false
|
|
|
|
if not xp.txFeesCovered(item):
|
|
return false
|
|
|
|
if not xp.txCostInBudget(item):
|
|
return false
|
|
|
|
if not xp.txPreLondonAcceptableGasPrice(item):
|
|
return false
|
|
|
|
if not xp.txPostLondonAcceptableTipAndFees(item):
|
|
return false
|
|
|
|
true
|
|
|
|
|
|
proc classifyValidatePacked*(xp: TxPoolRef;
|
|
vmState: BaseVMState; item: TxItemRef): bool =
|
|
## Verify the argument `item` against the accounts database. This function
|
|
## is a wrapper around the `verifyTransaction()` call to be used in a similar
|
|
## fashion as in `processTransactionImpl()`.
|
|
let
|
|
roDB = vmState.readOnlyStateDB
|
|
baseFee = xp.chain.baseFee.uint64.u256
|
|
fork = xp.chain.nextFork
|
|
gasLimit = if packItemsMaxGasLimit in xp.pFlags:
|
|
xp.chain.limits.maxLimit
|
|
else:
|
|
xp.chain.limits.trgLimit
|
|
tx = item.tx.eip1559TxNormalization(xp.chain.baseFee.GasInt, fork)
|
|
|
|
roDB.validateTransaction(tx, item.sender, gasLimit, baseFee, fork)
|
|
|
|
proc classifyPacked*(xp: TxPoolRef; gasBurned, moreBurned: GasInt): bool =
|
|
## Classifier for *packing* (i.e. adding up `gasUsed` values after executing
|
|
## in the VM.) This function checks whether the sum of the arguments
|
|
## `gasBurned` and `moreGasBurned` is within acceptable constraints.
|
|
let totalGasUsed = gasBurned + moreBurned
|
|
if packItemsMaxGasLimit in xp.pFlags:
|
|
totalGasUsed < xp.chain.limits.maxLimit
|
|
else:
|
|
totalGasUsed < xp.chain.limits.trgLimit
|
|
|
|
proc classifyPackedNext*(xp: TxPoolRef; gasBurned, moreBurned: GasInt): bool =
|
|
## Classifier for *packing* (i.e. adding up `gasUsed` values after executing
|
|
## in the VM.) This function returns `true` if the packing level is still
|
|
## low enough to proceed trying to accumulate more items.
|
|
##
|
|
## This function is typically called as a follow up after a `false` return of
|
|
## `classifyPack()`.
|
|
if packItemsTryHarder notin xp.pFlags:
|
|
xp.classifyPacked(gasBurned, moreBurned)
|
|
elif packItemsMaxGasLimit in xp.pFlags:
|
|
gasBurned < xp.chain.limits.hwmLimit
|
|
else:
|
|
gasBurned < xp.chain.limits.lwmLimit
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functionss
|
|
# ------------------------------------------------------------------------------
|