mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-03-02 21:00:49 +00:00
Remove the intermediate bySender table usage. This will lower the memory and CPU usage. Also add more comments about how algorithm works.
122 lines
4.3 KiB
Nim
122 lines
4.3 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2018-2024 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.
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
std/[tables, heapqueue],
|
|
eth/common/base,
|
|
eth/common/addresses,
|
|
eth/common/hashes,
|
|
stew/sorted_set,
|
|
../../db/ledger,
|
|
./tx_item
|
|
|
|
type
|
|
SenderNonceList* = SortedSet[AccountNonce, TxItemRef]
|
|
|
|
TxSenderNonceRef* = ref object
|
|
## Sub-list ordered by `AccountNonce` values containing transaction
|
|
## item lists.
|
|
list*: SenderNonceList
|
|
|
|
TxSenderTab* = Table[Address, TxSenderNonceRef]
|
|
|
|
TxIdTab* = Table[Hash32, TxItemRef]
|
|
|
|
func init*(_ : type TxSenderNonceRef): TxSenderNonceRef =
|
|
TxSenderNonceRef(list: SenderNonceList.init())
|
|
|
|
template insertOrReplace*(sn: TxSenderNonceRef, item: TxItemRef) =
|
|
sn.list.findOrInsert(item.nonce).
|
|
expect("insert txitem ok").data = item
|
|
|
|
func len*(sn: TxSenderNonceRef): auto =
|
|
sn.list.len
|
|
|
|
iterator byPriceAndNonce*(senderTab: TxSenderTab,
|
|
idTab: var TxIdTab,
|
|
ledger: LedgerRef,
|
|
baseFee: GasInt): TxItemRef =
|
|
|
|
## This algorithm and comment is taken from ethereumjs but modified.
|
|
##
|
|
## Returns eligible txs to be packed sorted by price in such a way that the
|
|
## nonce orderings within a single account are maintained.
|
|
##
|
|
## Note, this is not as trivial as it seems from the first look as there are three
|
|
## different criteria that need to be taken into account (price, nonce, account
|
|
## match), which cannot be done with any plain sorting method, as certain items
|
|
## cannot be compared without context.
|
|
##
|
|
## This method first sorts the list of transactions into individual
|
|
## sender accounts and sorts them by nonce.
|
|
## -- This is done by senderTab internal algorithm.
|
|
##
|
|
## After the account nonce ordering is satisfied, the results are merged back
|
|
## together by price, always comparing only the head transaction from each account.
|
|
## This is done via a heap to keep it fast.
|
|
##
|
|
## @param baseFee Provide a baseFee to exclude txs with a lower gasPrice
|
|
##
|
|
|
|
template getHeadAndPushTo(sn, byPrice, nonce) =
|
|
let rc = sn.list.ge(nonce)
|
|
if rc.isOk:
|
|
let item = rc.get.data
|
|
item.calculatePrice(baseFee)
|
|
byPrice.push(item)
|
|
|
|
# HeapQueue needs `<` to be overloaded for custom object
|
|
# and in this case, we want to pop highest price first.
|
|
# That's why we use '>' instead of '<' in the implementation.
|
|
func `<`(a, b: TxItemRef): bool {.used.} = a.price > b.price
|
|
var byPrice = initHeapQueue[TxItemRef]()
|
|
|
|
# Fill byPrice with `head item` from each account.
|
|
# The `head item` is the lowest allowed nonce.
|
|
for address, sn in senderTab:
|
|
let nonce = ledger.getNonce(address)
|
|
|
|
# Remove item with nonce lower than current account's nonce.
|
|
# Happen when proposed block rejected.
|
|
# removeNewBlockTxs will also remove this kind of txs,
|
|
# but in a less explicit way. And probably less thoroughly.
|
|
# EMV will reject the transaction too, but we filter it here
|
|
# for efficiency.
|
|
var rc = sn.list.lt(nonce)
|
|
while rc.isOk:
|
|
let item = rc.get.data
|
|
idTab.del(item.id)
|
|
discard sn.list.delete(item.nonce)
|
|
rc = sn.list.lt(nonce)
|
|
|
|
# Check if the account nonce matches the lowest known tx nonce.
|
|
sn.getHeadAndPushTo(byPrice, nonce)
|
|
|
|
while byPrice.len > 0:
|
|
# Retrieve the next best transaction by price.
|
|
let best = byPrice.pop()
|
|
|
|
# Push in its place the next transaction from the same account.
|
|
let sn = senderTab.getOrDefault(best.sender)
|
|
if sn.isNil.not:
|
|
# This algorithm will automatically reject
|
|
# transaction with nonce gap(best.nonce + 1)
|
|
# EVM will reject this kind transaction too, but
|
|
# why do expensive EVM call when we can do it cheaply here.
|
|
# We don't remove transactions with gap like we do with transactions
|
|
# of lower nonce? because they might be packed by future blocks
|
|
# when the gap is filled. Worst case is they will expired and get purged by
|
|
# `removeExpiredTxs`
|
|
sn.getHeadAndPushTo(byPrice, best.nonce + 1)
|
|
|
|
yield best
|