nimbus-eth1/nimbus/utils/tx_pool/tx_tasks/tx_classify.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
# ------------------------------------------------------------------------------