nimbus-eth1/nimbus/utils/tx_pool/tx_tasks/tx_packer.nim

277 lines
8.9 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,
../../../transaction,
../../../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 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.
let
fork = pst.xp.chain.nextFork
baseFee = pst.xp.chain.baseFee
tx = item.tx.eip1559TxNormalization(baseFee.GasInt, fork)
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.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
disableReward = vmState.chainDB.config.poaEngine or
vmState.ttdReached # EIP-3675: no reward for miner
if not disableReward:
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.packingOrderAccounts(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
# ------------------------------------------------------------------------------