2022-01-18 14:40:02 +00:00
|
|
|
# Nimbus
|
2024-05-15 06:07:59 +03:00
|
|
|
# Copyright (c) 2018-2024 Status Research & Development GmbH
|
2022-01-18 14:40:02 +00:00
|
|
|
# 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.
|
|
|
|
|
2024-05-30 14:54:03 +02:00
|
|
|
{.push raises: [].}
|
|
|
|
|
2022-01-18 14:40:02 +00:00
|
|
|
import
|
2024-12-26 17:07:25 +07:00
|
|
|
std/[tables, heapqueue],
|
|
|
|
eth/common/base,
|
|
|
|
eth/common/addresses,
|
|
|
|
eth/common/hashes,
|
|
|
|
stew/sorted_set,
|
|
|
|
../../db/ledger,
|
|
|
|
./tx_item
|
2022-01-18 14:40:02 +00:00
|
|
|
|
|
|
|
type
|
2024-12-26 17:07:25 +07:00
|
|
|
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 =
|
2024-12-29 13:02:42 +07:00
|
|
|
|
|
|
|
## 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.
|
2024-12-26 17:07:25 +07:00
|
|
|
# Happen when proposed block rejected.
|
2024-12-29 13:02:42 +07:00
|
|
|
# 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.
|
2024-12-26 17:07:25 +07:00
|
|
|
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)
|
|
|
|
|
2024-12-29 13:02:42 +07:00
|
|
|
# Check if the account nonce matches the lowest known tx nonce.
|
|
|
|
sn.getHeadAndPushTo(byPrice, nonce)
|
2024-12-26 17:07:25 +07:00
|
|
|
|
|
|
|
while byPrice.len > 0:
|
2024-12-29 13:02:42 +07:00
|
|
|
# Retrieve the next best transaction by price.
|
2024-12-26 17:07:25 +07:00
|
|
|
let best = byPrice.pop()
|
|
|
|
|
2024-12-29 13:02:42 +07:00
|
|
|
# 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)
|
2024-12-26 17:07:25 +07:00
|
|
|
|
|
|
|
yield best
|