# 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 Tasklet: Add Transaction ## ========================================= ## import std/[tables], ../tx_desc, ../tx_gauge, ../tx_info, ../tx_item, ../tx_tabs, ./tx_classify, ./tx_recover, chronicles, eth/[common, keys], stew/[keyed_queue, sorted_set] {.push raises: [Defect].} type TxAddStats* = tuple ##\ ## Status code returned from the `addTxs()` function stagedIndicator: bool ##\ ## If `true`, this value indicates that at least one item was added to\ ## the `staged` bucket (which suggest a re-run of the packer.) topItems: seq[TxItemRef] ##\ ## For each sender where txs were added to the bucket database or waste\ ## basket, this list keeps the items with the highest nonce (handy for\ ## chasing nonce gaps after a back-move of the block chain head.) NonceList = ##\ ## Temporary sorter list SortedSet[AccountNonce,TxItemRef] AccouuntNonceTab = ##\ ## Temporary sorter table Table[EthAddress,NonceList] logScope: topics = "tx-pool add transaction" # ------------------------------------------------------------------------------ # Private helper # ------------------------------------------------------------------------------ proc getItemList(tab: var AccouuntNonceTab; key: EthAddress): var NonceList {.gcsafe,raises: [Defect,KeyError].} = if not tab.hasKey(key): tab[key] = NonceList.init tab[key] # ------------------------------------------------------------------------------ # Private functions # ------------------------------------------------------------------------------ proc supersede(xp: TxPoolRef; item: TxItemRef): Result[void,TxInfo] {.gcsafe,raises: [Defect,CatchableError].} = var current: TxItemRef block: let rc = xp.txDB.bySender.eq(item.sender).any.eq(item.tx.nonce) if rc.isErr: return err(txInfoErrUnspecified) current = rc.value.data # verify whether replacing is allowed, at all let bumpPrice = (current.tx.gasPrice * xp.priceBump.GasInt + 99) div 100 if item.tx.gasPrice < current.tx.gasPrice + bumpPrice: return err(txInfoErrReplaceUnderpriced) # make space, delete item if not xp.txDB.dispose(current, txInfoSenderNonceSuperseded): return err(txInfoErrVoidDisposal) # try again block: let rc = xp.txDB.insert(item) if rc.isErr: return err(rc.error) return ok() # ------------------------------------------------------------------------------ # Public functions # ------------------------------------------------------------------------------ proc addTx*(xp: TxPoolRef; item: TxItemRef): bool {.discardable,gcsafe,raises: [Defect,CatchableError].} = ## Add a transaction item. It is tested and stored in either of the `pending` ## or `staged` buckets, or disposed into the waste basket. The function ## returns `true` if the item was added to the `staged` bucket. var stagedItemAdded = false vetted = txInfoOk # Leave this frame with `return`, or proceeed with error block txErrorFrame: # Create tx ID and check for dups if xp.txDB.byItemID.hasKey(item.itemID): vetted = txInfoErrAlreadyKnown break txErrorFrame # Verify transaction if not xp.classifyValid(item): vetted = txInfoErrBasicValidatorFailed break txErrorFrame # Update initial state bucket item.status = if xp.classifyActive(item): txItemStaged else: txItemPending # Insert into database block: let rc = xp.txDB.insert(item) if rc.isOk: validTxMeter(1) return item.status == txItemStaged vetted = rc.error # need to replace tx with same as the new item if vetted == txInfoErrSenderNonceIndex: let rc = xp.supersede(item) if rc.isOk: validTxMeter(1) return vetted = rc.error # Error processing => store in waste basket xp.txDB.reject(item, vetted) # update gauge case vetted: of txInfoErrAlreadyKnown: knownTxMeter(1) of txInfoErrInvalidSender: invalidTxMeter(1) else: unspecifiedErrorMeter(1) # core/tx_pool.go(848): func (pool *TxPool) AddLocals(txs [].. # core/tx_pool.go(854): func (pool *TxPool) AddLocals(txs [].. # core/tx_pool.go(864): func (pool *TxPool) AddRemotes(txs [].. # core/tx_pool.go(883): func (pool *TxPool) AddRemotes(txs [].. # core/tx_pool.go(889): func (pool *TxPool) addTxs(txs []*types.Transaction, .. proc addTxs*(xp: TxPoolRef; txs: openArray[Transaction]; info = ""): TxAddStats {.discardable,gcsafe,raises: [Defect,CatchableError].} = ## Add a list of transactions. The list is sorted after nonces and txs are ## tested and stored into either of the `pending` or `staged` buckets, or ## disposed o the waste basket. The function returns the tuple ## `(staged-indicator,top-items)` as explained below. ## ## *stagedIndicator* ## If `true`, this value indicates that at least one item was added to ## the `staged` bucket (which suggest a re-run of the packer.) ## ## *topItems* ## For each sender where txs were added to the bucket database or waste ## basket, this list keeps the items with the highest nonce (handy for ## chasing nonce gaps after a back-move of the block chain head.) ## var accTab: AccouuntNonceTab for tx in txs.items: var reason: TxInfo # Create tx item wrapper, preferably recovered from waste basket let rcTx = xp.recoverItem(tx, txItemPending, info) if rcTx.isErr: reason = rcTx.error else: let item = rcTx.value rcInsert = accTab.getItemList(item.sender).insert(item.tx.nonce) if rcInsert.isErr: reason = txInfoErrSenderNonceIndex else: rcInsert.value.data = item # link that item continue # move item to waste basket xp.txDB.reject(tx, reason, txItemPending, info) # update gauge case reason: of txInfoErrAlreadyKnown: knownTxMeter(1) of txInfoErrInvalidSender: invalidTxMeter(1) else: unspecifiedErrorMeter(1) # Add sorted transaction items for itemList in accTab.mvalues: var rc = itemList.ge(AccountNonce.low) lastItem: TxItemRef # => nil while rc.isOk: let (nonce,item) = (rc.value.key,rc.value.data) if xp.addTx(item): result.stagedIndicator = true # Make sure that there is at least one item per sender, prefereably # a non-error item. if item.reject == txInfoOk or lastItem.isNil: lastItem = item rc = itemList.gt(nonce) # return the last one in the series if not lastItem.isNil: result.topItems.add lastItem # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------