nimbus-eth1/nimbus/utils/tx_pool/tx_tabs/tx_status.nim

323 lines
9.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 Table: `status` > `nonce`
## ==========================================
##
import
../tx_info,
../tx_item,
eth/[common],
stew/[results, keyed_queue, keyed_queue/kq_debug, sorted_set]
{.push raises: [Defect].}
type
TxStatusNonceRef* = ref object ##\
## Sub-list ordered by `AccountNonce` or `TxItemRef` insertion order.
nonceList: SortedSet[AccountNonce,TxItemRef]
TxStatusSenderRef* = ref object ##\
## Per address table. This table is provided as a keyed queue so deletion\
## while traversing is supported and predictable.
size: int ## Total number of items
gasLimits: GasInt ## Accumulated gas limits
addrList: KeyedQueue[EthAddress,TxStatusNonceRef]
TxStatusTab* = object ##\
## Per status table
size: int ## Total number of items
statusList: array[TxItemStatus,TxStatusSenderRef]
TxStatusInx = object ##\
## Internal access data
addrData: TxStatusSenderRef
nonceData: TxStatusNonceRef
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
proc `$`(rq: TxStatusNonceRef): string =
## Needed by `rq.verify()` for printing error messages
$rq.nonceList.len
proc nActive(sq: TxStatusTab): int =
## Number of non-nil items
for status in TxItemStatus:
if not sq.statusList[status].isNil:
result.inc
proc mkInxImpl(sq: var TxStatusTab; item: TxItemRef): Result[TxStatusInx,void]
{.gcsafe,raises: [Defect,KeyError].} =
## Fails if item exists, already
var inx: TxStatusInx
# array of buckets (aka status) => senders
inx.addrData = sq.statusList[item.status]
if inx.addrData.isNil:
new inx.addrData
inx.addrData.addrList.init
sq.statusList[item.status] = inx.addrData
# sender address sub-list => nonces
if inx.addrData.addrList.hasKey(item.sender):
inx.nonceData = inx.addrData.addrList[item.sender]
else:
new inx.nonceData
inx.nonceData.nonceList.init
inx.addrData.addrList[item.sender] = inx.nonceData
# nonce sublist
let rc = inx.nonceData.nonceList.insert(item.tx.nonce)
if rc.isErr:
return err()
rc.value.data = item
return ok(inx)
proc getInxImpl(sq: var TxStatusTab; item: TxItemRef): Result[TxStatusInx,void]
{.gcsafe,raises: [Defect,KeyError].} =
var inx: TxStatusInx
# array of buckets (aka status) => senders
inx.addrData = sq.statusList[item.status]
if inx.addrData.isNil:
return err()
# sender address sub-list => nonces
if not inx.addrData.addrList.hasKey(item.sender):
return err()
inx.nonceData = inx.addrData.addrList[item.sender]
ok(inx)
# ------------------------------------------------------------------------------
# Public all-queue helpers
# ------------------------------------------------------------------------------
proc init*(sq: var TxStatusTab; size = 10) =
## Optional constructor
sq.size = 0
sq.statusList.reset
proc insert*(sq: var TxStatusTab; item: TxItemRef): bool
{.gcsafe,raises: [Defect,KeyError].} =
## Add transaction `item` to the list. The function has no effect if the
## transaction exists, already (apart from returning `false`.)
let rc = sq.mkInxImpl(item)
if rc.isOK:
let inx = rc.value
sq.size.inc
inx.addrData.size.inc
inx.addrData.gasLimits += item.tx.gasLimit
return true
proc delete*(sq: var TxStatusTab; item: TxItemRef): bool
{.gcsafe,raises: [Defect,KeyError].} =
let rc = sq.getInxImpl(item)
if rc.isOK:
let inx = rc.value
sq.size.dec
inx.addrData.size.dec
inx.addrData.gasLimits -= item.tx.gasLimit
discard inx.nonceData.nonceList.delete(item.tx.nonce)
if inx.nonceData.nonceList.len == 0:
discard inx.addrData.addrList.delete(item.sender)
if inx.addrData.addrList.len == 0:
sq.statusList[item.status] = nil
return true
proc verify*(sq: var TxStatusTab): Result[void,TxInfo]
{.gcsafe,raises: [Defect,CatchableError].} =
## walk `TxItemStatus` > `EthAddress` > `AccountNonce`
var totalCount = 0
for status in TxItemStatus:
let addrData = sq.statusList[status]
if not addrData.isNil:
block:
let rc = addrData.addrList.verify
if rc.isErr:
return err(txInfoVfyStatusSenderList)
var
addrCount = 0
gasLimits = 0.GasInt
for p in addrData.addrList.nextPairs:
let (addrKey, nonceData) = (p.key, p.data)
block:
let rc = nonceData.nonceList.verify
if rc.isErr:
return err(txInfoVfyStatusNonceList)
var rcNonce = nonceData.nonceList.ge(AccountNonce.low)
while rcNonce.isOK:
let (nonceKey, item) = (rcNonce.value.key, rcNonce.value.data)
rcNonce = nonceData.nonceList.gt(nonceKey)
gasLimits += item.tx.gasLimit
addrCount.inc
if addrCount != addrData.size:
return err(txInfoVfyStatusTotal)
if gasLimits != addrData.gasLimits:
return err(txInfoVfyStatusGasLimits)
totalCount += addrCount
# end while
if totalCount != sq.size:
return err(txInfoVfyStatusTotal)
ok()
# ------------------------------------------------------------------------------
# Public array ops -- `TxItemStatus` (level 0)
# ------------------------------------------------------------------------------
proc len*(sq: var TxStatusTab): int =
sq.nActive
proc nItems*(sq: var TxStatusTab): int =
## Getter, total number of items in the list
sq.size
proc eq*(sq: var TxStatusTab; status: TxItemStatus):
SortedSetResult[TxItemStatus,TxStatusSenderRef] =
let addrData = sq.statusList[status]
if addrData.isNil:
return err(rbNotFound)
toSortedSetResult(key = status, data = addrData)
# ------------------------------------------------------------------------------
# Public array ops -- `EthAddress` (level 1)
# ------------------------------------------------------------------------------
proc nItems*(addrData: TxStatusSenderRef): int =
## Getter, total number of items in the sub-list
addrData.size
proc nItems*(rc: SortedSetResult[TxItemStatus,TxStatusSenderRef]): int =
if rc.isOK:
return rc.value.data.nItems
0
proc gasLimits*(addrData: TxStatusSenderRef): GasInt =
## Getter, accumulated `gasLimit` values
addrData.gasLimits
proc gasLimits*(rc: SortedSetResult[TxItemStatus,TxStatusSenderRef]): GasInt =
if rc.isOK:
return rc.value.data.gasLimits
0
proc eq*(addrData: TxStatusSenderRef; sender: EthAddress):
SortedSetResult[EthAddress,TxStatusNonceRef]
{.gcsafe,raises: [Defect,KeyError].} =
if addrData.addrList.haskey(sender):
return toSortedSetResult(key = sender, data = addrData.addrList[sender])
err(rbNotFound)
proc eq*(rc: SortedSetResult[TxItemStatus,TxStatusSenderRef];
sender: EthAddress): SortedSetResult[EthAddress,TxStatusNonceRef]
{.gcsafe,raises: [Defect,KeyError].} =
if rc.isOK:
return rc.value.data.eq(sender)
err(rc.error)
# ------------------------------------------------------------------------------
# Public array ops -- `AccountNonce` (level 2)
# ------------------------------------------------------------------------------
proc len*(nonceData: TxStatusNonceRef): int =
## Getter, same as `nItems` (for last level list)
nonceData.nonceList.len
proc nItems*(nonceData: TxStatusNonceRef): int =
## Getter, total number of items in the sub-list
nonceData.nonceList.len
proc nItems*(rc: SortedSetResult[EthAddress,TxStatusNonceRef]): int =
if rc.isOK:
return rc.value.data.nItems
0
proc eq*(nonceData: TxStatusNonceRef; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
nonceData.nonceList.eq(nonce)
proc eq*(rc: SortedSetResult[EthAddress,TxStatusNonceRef]; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
if rc.isOK:
return rc.value.data.eq(nonce)
err(rc.error)
proc ge*(nonceData: TxStatusNonceRef; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
nonceData.nonceList.ge(nonce)
proc ge*(rc: SortedSetResult[EthAddress,TxStatusNonceRef]; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
if rc.isOK:
return rc.value.data.ge(nonce)
err(rc.error)
proc gt*(nonceData: TxStatusNonceRef; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
nonceData.nonceList.gt(nonce)
proc gt*(rc: SortedSetResult[EthAddress,TxStatusNonceRef]; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
if rc.isOK:
return rc.value.data.gt(nonce)
err(rc.error)
proc le*(nonceData: TxStatusNonceRef; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
nonceData.nonceList.le(nonce)
proc le*(rc: SortedSetResult[EthAddress,TxStatusNonceRef]; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
if rc.isOK:
return rc.value.data.le(nonce)
err(rc.error)
proc lt*(nonceData: TxStatusNonceRef; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
nonceData.nonceList.lt(nonce)
proc lt*(rc: SortedSetResult[EthAddress,TxStatusNonceRef]; nonce: AccountNonce):
SortedSetResult[AccountNonce,TxItemRef] =
if rc.isOK:
return rc.value.data.lt(nonce)
err(rc.error)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------