mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-28 13:05:18 +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.
283 lines
9.0 KiB
Nim
283 lines
9.0 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 Tasklets: Packer, VM execute and compact txs
|
|
## =============================================================
|
|
##
|
|
|
|
import
|
|
std/[sets, tables],
|
|
../../../db/[accounts_cache, db_chain],
|
|
../../../forks,
|
|
../../../p2p/[dao, executor, validate],
|
|
../../../transaction/call_evm,
|
|
../../../vm_state,
|
|
../../../vm_types,
|
|
../tx_chain,
|
|
../tx_desc,
|
|
../tx_item,
|
|
../tx_tabs,
|
|
../tx_tabs/tx_status,
|
|
./tx_bucket,
|
|
./tx_classify,
|
|
chronicles,
|
|
eth/[common, keys, rlp, trie, trie/db],
|
|
stew/[sorted_set]
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
type
|
|
TxPackerError* = object of CatchableError
|
|
## Catch and relay exception error
|
|
|
|
TxPackerStateRef = ref object
|
|
xp: TxPoolRef
|
|
tr: HexaryTrie
|
|
cleanState: bool
|
|
balance: UInt256
|
|
|
|
const
|
|
receiptsExtensionSize = ##\
|
|
## Number of slots to extend the `receipts[]` at the same time.
|
|
20
|
|
|
|
logScope:
|
|
topics = "tx-pool packer"
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
template safeExecutor(info: string; code: untyped) =
|
|
try:
|
|
code
|
|
except CatchableError as e:
|
|
raise (ref CatchableError)(msg: e.msg)
|
|
except Defect as e:
|
|
raise (ref Defect)(msg: e.msg)
|
|
except:
|
|
let e = getCurrentException()
|
|
raise newException(TxPackerError, info & "(): " & $e.name & " -- " & e.msg)
|
|
|
|
proc eip1559TxNormalization(tx: Transaction): Transaction =
|
|
result = tx
|
|
if tx.txType < TxEip1559:
|
|
result.maxPriorityFee = tx.gasPrice
|
|
result.maxFee = tx.gasPrice
|
|
|
|
proc persist(pst: TxPackerStateRef)
|
|
{.gcsafe,raises: [Defect,RlpError].} =
|
|
## Smart wrapper
|
|
if not pst.cleanState:
|
|
pst.xp.chain.vmState.stateDB.persist(clearCache = false)
|
|
pst.cleanState = true
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc runTx(pst: TxPackerStateRef; item: TxItemRef): GasInt
|
|
{.gcsafe,raises: [Defect,CatchableError].} =
|
|
## Execute item transaction and update `vmState` book keeping. Returns the
|
|
## `gasUsed` after executing the transaction.
|
|
var
|
|
tx = item.tx.eip1559TxNormalization
|
|
let
|
|
fork = pst.xp.chain.nextFork
|
|
baseFee = pst.xp.chain.head.baseFee
|
|
if FkLondon <= fork:
|
|
tx.gasPrice = min(tx.maxPriorityFee + baseFee.truncate(int64), tx.maxFee)
|
|
|
|
safeExecutor "tx_packer.runTx":
|
|
# Execute transaction, may return a wildcard `Exception`
|
|
result = tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork)
|
|
|
|
pst.cleanState = false
|
|
doAssert 0 <= result
|
|
|
|
|
|
proc runTxCommit(pst: TxPackerStateRef; item: TxItemRef; gasBurned: GasInt)
|
|
{.gcsafe,raises: [Defect,CatchableError].} =
|
|
## Book keeping after executing argument `item` transaction in the VM. The
|
|
## function returns the next number of items `nItems+1`.
|
|
let
|
|
xp = pst.xp
|
|
vmState = xp.chain.vmState
|
|
inx = xp.txDB.byStatus.eq(txItemPacked).nItems
|
|
gasTip = item.tx.effectiveGasTip(xp.chain.head.baseFee)
|
|
|
|
# The gas tip cannot get negative as all items in the `staged` bucket
|
|
# are vetted for profitability before entering that bucket.
|
|
assert 0 <= gasTip
|
|
let reward = gasBurned.u256 * gasTip.uint64.u256
|
|
vmState.stateDB.addBalance(xp.chain.miner, reward)
|
|
|
|
# Update account database
|
|
vmState.mutateStateDB:
|
|
for deletedAccount in vmState.selfDestructs:
|
|
db.deleteAccount deletedAccount
|
|
|
|
if FkSpurious <= xp.chain.nextFork:
|
|
vmState.touchedAccounts.incl(xp.chain.miner)
|
|
# EIP158/161 state clearing
|
|
for account in vmState.touchedAccounts:
|
|
if db.accountExists(account) and db.isEmptyAccount(account):
|
|
debug "state clearing", account
|
|
db.deleteAccount account
|
|
|
|
if vmState.generateWitness:
|
|
vmState.stateDB.collectWitnessData()
|
|
|
|
# Save accounts via persist() is not needed unless the fork is smaller
|
|
# than `FkByzantium` in which case, the `rootHash()` function is called
|
|
# by `makeReceipt()`. As the `rootHash()` function asserts unconditionally
|
|
# that the account cache has been saved, the `persist()` call is
|
|
# obligatory here.
|
|
if xp.chain.nextFork < FkByzantium:
|
|
pst.persist
|
|
|
|
# Update receipts sequence
|
|
if vmState.receipts.len <= inx:
|
|
vmState.receipts.setLen(inx + receiptsExtensionSize)
|
|
|
|
vmState.cumulativeGasUsed += gasBurned
|
|
vmState.receipts[inx] = vmState.makeReceipt(item.tx.txType)
|
|
|
|
# Update txRoot
|
|
pst.tr.put(rlp.encode(inx), rlp.encode(item.tx))
|
|
|
|
# Add the item to the `packed` bucket. This implicitely increases the
|
|
# receipts index `inx` at the next visit of this function.
|
|
discard xp.txDB.reassign(item,txItemPacked)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions: packer packerVmExec() helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc vmExecInit(xp: TxPoolRef): TxPackerStateRef
|
|
{.gcsafe,raises: [Defect,CatchableError].} =
|
|
|
|
# Flush `packed` bucket
|
|
xp.bucketFlushPacked
|
|
|
|
xp.chain.maxMode = (packItemsMaxGasLimit in xp.pFlags)
|
|
|
|
if xp.chain.config.daoForkSupport and
|
|
xp.chain.config.daoForkBlock == xp.chain.head.blockNumber + 1:
|
|
xp.chain.vmState.mutateStateDB:
|
|
db.applyDAOHardFork()
|
|
|
|
TxPackerStateRef( # return value
|
|
xp: xp,
|
|
tr: newMemoryDB().initHexaryTrie,
|
|
balance: xp.chain.vmState.readOnlyStateDB.getBalance(xp.chain.miner))
|
|
|
|
|
|
proc vmExecGrabItem(pst: TxPackerStateRef; item: TxItemRef): Result[bool,void]
|
|
{.gcsafe,raises: [Defect,CatchableError].} =
|
|
## Greedily collect & compact items as long as the accumulated `gasLimit`
|
|
## values are below the maximum block size.
|
|
let
|
|
xp = pst.xp
|
|
vmState = xp.chain.vmState
|
|
|
|
# Validate transaction relative to the current vmState
|
|
if not xp.classifyValidatePacked(vmState, item):
|
|
return ok(false) # continue with next account
|
|
|
|
let
|
|
accTx = vmState.stateDB.beginSavepoint
|
|
gasUsed = pst.runTx(item) # this is the crucial part, running the tx
|
|
|
|
# Find out what to do next: accepting this tx or trying the next account
|
|
if not xp.classifyPacked(vmState.cumulativeGasUsed, gasUsed):
|
|
vmState.stateDB.rollback(accTx)
|
|
if xp.classifyPackedNext(vmState.cumulativeGasUsed, gasUsed):
|
|
return ok(false) # continue with next account
|
|
return err() # otherwise stop collecting
|
|
|
|
# Commit account state DB
|
|
vmState.stateDB.commit(accTx)
|
|
|
|
vmState.stateDB.persist(clearCache = false)
|
|
let midRoot = vmState.stateDB.rootHash
|
|
|
|
# Finish book-keeping and move item to `packed` bucket
|
|
pst.runTxCommit(item, gasUsed)
|
|
|
|
ok(true) # fetch the very next item
|
|
|
|
|
|
proc vmExecCommit(pst: TxPackerStateRef)
|
|
{.gcsafe,raises: [Defect,CatchableError].} =
|
|
let
|
|
xp = pst.xp
|
|
vmState = xp.chain.vmState
|
|
|
|
if not vmState.chainDB.config.poaEngine:
|
|
let
|
|
number = xp.chain.head.blockNumber + 1
|
|
uncles: seq[BlockHeader] = @[] # no uncles yet
|
|
vmState.calculateReward(xp.chain.miner, number + 1, uncles)
|
|
|
|
# Reward beneficiary
|
|
vmState.mutateStateDB:
|
|
if vmState.generateWitness:
|
|
db.collectWitnessData()
|
|
# Finish up, then vmState.stateDB.rootHash may be accessed
|
|
db.persist(ClearCache in vmState.flags)
|
|
|
|
# Update flexi-array, set proper length
|
|
let nItems = xp.txDB.byStatus.eq(txItemPacked).nItems
|
|
vmState.receipts.setLen(nItems)
|
|
|
|
xp.chain.receipts = vmState.receipts
|
|
xp.chain.txRoot = pst.tr.rootHash
|
|
xp.chain.stateRoot = vmState.stateDB.rootHash
|
|
|
|
proc balanceDelta: Uint256 =
|
|
let postBalance = vmState.readOnlyStateDB.getBalance(xp.chain.miner)
|
|
if pst.balance < postBalance:
|
|
return postBalance - pst.balance
|
|
|
|
xp.chain.profit = balanceDelta()
|
|
xp.chain.reward = balanceDelta()
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc packerVmExec*(xp: TxPoolRef) {.gcsafe,raises: [Defect,CatchableError].} =
|
|
## Rebuild `packed` bucket by selection items from the `staged` bucket
|
|
## after executing them in the VM.
|
|
let dbTx = xp.chain.db.db.beginTransaction
|
|
defer: dbTx.dispose()
|
|
|
|
var pst = xp.vmExecInit
|
|
|
|
block loop:
|
|
for (_,nonceList) in pst.xp.txDB.decAccount(txItemStaged):
|
|
|
|
block account:
|
|
for item in nonceList.incNonce:
|
|
|
|
let rc = pst.vmExecGrabItem(item)
|
|
if rc.isErr:
|
|
break loop # stop
|
|
if not rc.value:
|
|
break account # continue with next account
|
|
|
|
pst.vmExecCommit
|
|
# Block chain will roll back automatically
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|