mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-30 05:55:30 +00:00
103656dbb5
details: For documentation, see comments in the file tx_pool.nim. For prettified manual pages run 'make docs' in the nimbus directory and point your web browser to the newly created 'docs' directory.
265 lines
8.7 KiB
Nim
265 lines
8.7 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.fork)
|
|
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
|
|
|
|
roDB.validateTransaction(item.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
|
|
# ------------------------------------------------------------------------------
|