diff --git a/core/txpool/legacypool/avl.go b/core/txpool/legacypool/avl.go new file mode 100755 index 0000000000..aa24f5bdec --- /dev/null +++ b/core/txpool/legacypool/avl.go @@ -0,0 +1,284 @@ +package legacypool + +import ( + "errors" + "math/big" +) + +var ( + ErrEmptyTree = errors.New("empty tree") +) + +// AVLTree structure. Public methods are Add, Remove, Update, Search, Flatten. +type AVLTree struct { + root *AVLNode +} + +func (t *AVLTree) Add(key uint64, value *big.Int) { + t.root = t.root.add(key, value) +} + +func (t *AVLTree) Remove(key uint64) { + t.root = t.root.remove(key) +} + +func (t *AVLTree) Update(oldKey uint64, newKey uint64, newValue *big.Int) { + t.root = t.root.remove(oldKey) + t.root = t.root.add(newKey, newValue) +} + +func (t *AVLTree) Search(key uint64) (node *AVLNode, sum *big.Int) { + return t.root.search(key) +} + +func (t *AVLTree) Smallest() (uint64, error) { + if t.root == nil { + return 0, ErrEmptyTree + } + return t.root.findSmallest().key, nil // might get error if root is nil +} + +func (t *AVLTree) Largest() (uint64, error) { + if t.root == nil { + return 0, ErrEmptyTree + } + return t.root.findLargest().key, nil // might get error if root is nil +} + +func (t *AVLTree) Flatten() []*AVLNode { + nodes := make([]*AVLNode, 0) + if t.root == nil { + return nodes + } + t.root.displayNodesInOrder(&nodes) + return nodes +} + +// AVLNode structure +type AVLNode struct { + key uint64 // nonce + value *big.Int // cost + sum *big.Int // Sum of costs of the subtree + + // height counts nodes (not edges) + height int + left *AVLNode + right *AVLNode +} + +// Adds a new node +func (n *AVLNode) add(key uint64, value *big.Int) *AVLNode { + if n == nil { + newValue := new(big.Int) + newSum := new(big.Int) + newValue.Add(newValue, value) + newSum.Add(newSum, value) + return &AVLNode{key, newValue, newSum, 1, nil, nil} + } + + if key < n.key { + n.left = n.left.add(key, value) + + } else if key > n.key { + n.right = n.right.add(key, value) + } else { + // if same key exists update value + new := new(big.Int) + n.value = new.Add(value, new) + } + + new := new(big.Int) + n.sum = new.Add(n.value, new) + if n.left != nil { + n.sum = n.sum.Add(n.sum, n.left.sum) + } + if n.right != nil { + n.sum = n.sum.Add(n.sum, n.right.sum) + } + return n.rebalanceTree() +} + +// Removes a node +func (n *AVLNode) remove(key uint64) *AVLNode { + if n == nil { + return nil + } + if key < n.key { + n.left = n.left.remove(key) + } else if key > n.key { + n.right = n.right.remove(key) + } else { + if n.left != nil && n.right != nil { + // node to delete found with both children; + // replace values with smallest node of the right sub-tree + rightMinNode := n.right.findSmallest() + n.key = rightMinNode.key + n.value = rightMinNode.value + // delete smallest node that we replaced + n.right = n.right.remove(rightMinNode.key) + } else if n.left != nil { + // node only has left child + n = n.left + } else if n.right != nil { + // node only has right child + n = n.right + } else { + // node has no children + n = nil + return n + } + } + new := new(big.Int) + n.sum = new.Add(n.value, new) + if n.left != nil { + n.sum = n.sum.Add(n.sum, n.left.sum) + } + if n.right != nil { + n.sum = n.sum.Add(n.sum, n.right.sum) + } + return n.rebalanceTree() +} + +// Searches for a node +func (n *AVLNode) search(key uint64) (*AVLNode, *big.Int) { + if n == nil { + return nil, big.NewInt(0) + } + if key < n.key { + return n.left.search(key) + } else if key > n.key { + node, sum := n.right.search(key) + if n.left != nil { + sum = sum.Add(sum, n.left.sum) + } + sum = sum.Add(sum, n.value) + return node, sum + + } else { + new := new(big.Int) + new.Add(new, n.value) + if n.left != nil { + return n, new.Add(new, n.left.sum) + } else { + return n, new + } + } +} + +func (n *AVLNode) displayNodesInOrder(nodes *[]*AVLNode) { + if n.left != nil { + n.left.displayNodesInOrder(nodes) + } + (*nodes) = append((*nodes), n) + if n.right != nil { + n.right.displayNodesInOrder(nodes) + } +} + +func (n *AVLNode) getHeight() int { + if n == nil { + return 0 + } + return n.height +} + +func (n *AVLNode) recalculateHeight() { + n.height = 1 + max(n.left.getHeight(), n.right.getHeight()) +} + +// Checks if node is balanced and rebalance +func (n *AVLNode) rebalanceTree() *AVLNode { + if n == nil { + return n + } + n.recalculateHeight() + + // check balance factor and rotateLeft if right-heavy and rotateRight if left-heavy + balanceFactor := n.left.getHeight() - n.right.getHeight() + if balanceFactor == -2 { + // check if child is left-heavy and rotateRight first + if n.right.left.getHeight() > n.right.right.getHeight() { + n.right = n.right.rotateRight() + } + return n.rotateLeft() + } else if balanceFactor == 2 { + // check if child is right-heavy and rotateLeft first + if n.left.right.getHeight() > n.left.left.getHeight() { + n.left = n.left.rotateLeft() + } + return n.rotateRight() + } + return n +} + +// Rotate nodes left to balance node +func (n *AVLNode) rotateLeft() *AVLNode { + newRoot := n.right + new := new(big.Int) + temp := new.Add(new, n.sum) + + if n.right != nil { + n.sum = n.sum.Sub(n.sum, n.right.sum) + } + + n.right = newRoot.left + + if n.right != nil { + n.sum = n.sum.Add(n.sum, n.right.sum) + } + + newRoot.left = n + newRoot.sum = temp + n.recalculateHeight() + newRoot.recalculateHeight() + return newRoot +} + +// Rotate nodes right to balance node +func (n *AVLNode) rotateRight() *AVLNode { + newRoot := n.left + new := new(big.Int) + temp := new.Add(new, n.sum) + + if n.left != nil { + n.sum = n.sum.Sub(n.sum, n.left.sum) + } + + n.left = newRoot.right + + if n.left != nil { + n.sum = n.sum.Add(n.sum, n.left.sum) + } + + newRoot.right = n + newRoot.sum = temp + n.recalculateHeight() + newRoot.recalculateHeight() + return newRoot +} + +// Finds the smallest child (based on the key) for the current node +func (n *AVLNode) findSmallest() *AVLNode { + if n.left != nil { + return n.left.findSmallest() + } else { + return n + } +} + +// Finds the largest child (based on the key) for the current node +func (n *AVLNode) findLargest() *AVLNode { + if n.right != nil { + return n.right.findLargest() + } else { + return n + } +} + +// Returns max number - TODO: std lib seemed to only have a method for floats! +func max(a int, b int) int { + if a > b { + return a + } + return b +} diff --git a/core/txpool/legacypool/avl_test.go b/core/txpool/legacypool/avl_test.go new file mode 100755 index 0000000000..f177adf911 --- /dev/null +++ b/core/txpool/legacypool/avl_test.go @@ -0,0 +1,95 @@ +package legacypool + +import ( + "math/big" + "math/rand" + "sort" + "testing" +) + +const ( + opAdd = iota + opRemove + opSearch +) + +const maxKey = 100 +const nops = 100000 + +func TestTree(t *testing.T) { + for j := 0; j < 100; j++ { + //t.Logf("------------------Test %d--------------------", j) + rand.Seed(int64(j)) + tree := &AVLTree{} + m := make(map[uint64]*big.Int) + + for i := 0; i < nops; i++ { + op := rand.Intn(3) + k := uint64(rand.Intn(maxKey)) + + switch op { + case opAdd: + v := int64(rand.Int()) + // t.Logf("Insert %d: %d", k, v) + tree.Add(uint64(k), big.NewInt(v)) + m[k] = big.NewInt(v) + case opRemove: + tree.Remove(k) + // t.Logf("Remove %d", k) + delete(m, k) + + case opSearch: + tv := big.NewInt(0) + // t.Logf("Search %d", k) + node, sum := tree.Search(k) + tok := node != nil + if tok { + tv = node.value + } + + mv := m[k] + if mv == nil { + mv = big.NewInt(0) + } + if tv.Cmp(mv) != 0 { + t.Fatalf("Incorrect value for key %d, want: %d, got: %d", k, mv, tv) + } + + var msum = big.NewInt(0) + + for key, value := range m { + if key <= k { + msum.Add(msum, value) + } + } + + if sum.Cmp(msum) != 0 { + t.Fatalf("Incorrect sum for key %d, want: %d, got: %d", k, msum, sum) + } + } + } + + nodes := tree.Flatten() + keys := make([]uint64, 0) + for key := range m { + keys = append(keys, uint64(key)) + } + sort.Slice(keys, func(i, j2 int) bool { + return keys[i] < keys[j2] + }) + + if len(keys) != len(nodes) { + t.Fatalf("Incorrect number of nodes, want: %d, got: %d", len(keys), len(nodes)) + } + + for i := 0; i < len(keys); i++ { + if keys[i] != nodes[i].key { + t.Fatalf("Incorrect key, want: %d, got: %d", keys[i], nodes[i].key) + } + } + } + // tree := &AVLTree{} + // tree.Add(4, big.NewInt(7)) + // tree.Add(8, big.NewInt(8)) + // tree.Search(8) +} diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 00e326c4b8..0968c1813c 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -613,7 +613,7 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { }, ExistingExpenditure: func(addr common.Address) *big.Int { if list := pool.pending[addr]; list != nil { - return list.totalcost + return list.GetCost(tx.Nonce()) } return new(big.Int) }, @@ -1437,7 +1437,7 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T queuedNofundsMeter.Mark(int64(len(drops))) // Gather all executable transactions and promote them - readies := list.Ready(pool.pendingNonces.get(addr)) + readies := list.Ready(pool.pendingNonces.get(addr), pool.currentState.GetBalance(addr)) for _, tx := range readies { hash := tx.Hash() if pool.promoteTx(addr, hash, tx) { diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index a8f3dd7d86..92901d1c9d 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -198,8 +198,8 @@ func validatePoolInternals(pool *LegacyPool) error { if nonce := pool.pendingNonces.get(addr); nonce != last+1 { return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1) } - if txs.totalcost.Cmp(common.Big0) < 0 { - return fmt.Errorf("totalcost went negative: %v", txs.totalcost) + if txs.txs.tree.root.sum.Cmp(common.Big0) < 0 { + return fmt.Errorf("totalcost went negative: %v", txs.txs.tree.root.sum) } } return nil diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index 384fa7b61b..b86f106304 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -20,7 +20,6 @@ import ( "container/heap" "math" "math/big" - "sort" "sync" "sync/atomic" "time" @@ -29,238 +28,162 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// nonceHeap is a heap.Interface implementation over 64bit unsigned integers for -// retrieving sorted transactions from the possibly gapped future queue. -type nonceHeap []uint64 - -func (h nonceHeap) Len() int { return len(h) } -func (h nonceHeap) Less(i, j int) bool { return h[i] < h[j] } -func (h nonceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } - -func (h *nonceHeap) Push(x interface{}) { - *h = append(*h, x.(uint64)) -} - -func (h *nonceHeap) Pop() interface{} { - old := *h - n := len(old) - x := old[n-1] - old[n-1] = 0 - *h = old[0 : n-1] - return x -} - // sortedMap is a nonce->transaction hash map with a heap based index to allow // iterating over the contents in a nonce-incrementing way. type sortedMap struct { - items map[uint64]*types.Transaction // Hash map storing the transaction data - index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode) - cache types.Transactions // Cache of the transactions already sorted - cacheMu sync.Mutex // Mutex covering the cache + items map[uint64]*types.Transaction // Hash map storing the transaction data + tree *AVLTree // AVL tree of nonces of all the stored transactions (non-strict mode) } -// newSortedMap creates a new nonce-sorted transaction map. func newSortedMap() *sortedMap { return &sortedMap{ items: make(map[uint64]*types.Transaction), - index: new(nonceHeap), + tree: new(AVLTree), } } -// Get retrieves the current transactions associated with the given nonce. func (m *sortedMap) Get(nonce uint64) *types.Transaction { return m.items[nonce] } -// Put inserts a new transaction into the map, also updating the map's nonce -// index. If a transaction already exists with the same nonce, it's overwritten. +func (m *sortedMap) GetCost(nonce uint64) *big.Int { + _, cost := m.tree.Search(nonce) + return cost +} + func (m *sortedMap) Put(tx *types.Transaction) { nonce := tx.Nonce() - if m.items[nonce] == nil { - heap.Push(m.index, nonce) - } - m.cacheMu.Lock() - m.items[nonce], m.cache = tx, nil - m.cacheMu.Unlock() + m.items[nonce] = tx + m.tree.Add(nonce, tx.Cost()) } -// Forward removes all transactions from the map with a nonce lower than the -// provided threshold. Every removed transaction is returned for any post-removal -// maintenance. func (m *sortedMap) Forward(threshold uint64) types.Transactions { - var removed types.Transactions - - // Pop off heap items until the threshold is reached - for m.index.Len() > 0 && (*m.index)[0] < threshold { - nonce := heap.Pop(m.index).(uint64) - removed = append(removed, m.items[nonce]) + var remove types.Transactions + for { + nonce, err := m.tree.Smallest() + if nonce >= threshold || err != nil { + break + } + tx := m.items[nonce] + remove = append(remove, tx) + m.tree.Remove(nonce) delete(m.items, nonce) } - // If we had a cached order, shift the front - m.cacheMu.Lock() - if m.cache != nil { - m.cache = m.cache[len(removed):] - } - m.cacheMu.Unlock() - return removed + return remove } -// Filter iterates over the list of transactions and removes all of them for which -// the specified function evaluates to true. -// Filter, as opposed to 'filter', re-initialises the heap after the operation is done. -// If you want to do several consecutive filterings, it's therefore better to first -// do a .filter(func1) followed by .Filter(func2) or reheap() func (m *sortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions { - removed := m.filter(filter) - // If transactions were removed, the heap and cache are ruined - if len(removed) > 0 { - m.reheap() - } - return removed -} - -func (m *sortedMap) reheap() { - *m.index = make([]uint64, 0, len(m.items)) - for nonce := range m.items { - *m.index = append(*m.index, nonce) - } - heap.Init(m.index) - m.cacheMu.Lock() - m.cache = nil - m.cacheMu.Unlock() -} - -// filter is identical to Filter, but **does not** regenerate the heap. This method -// should only be used if followed immediately by a call to Filter or reheap() -func (m *sortedMap) filter(filter func(*types.Transaction) bool) types.Transactions { - var removed types.Transactions - - // Collect all the transactions to filter out + var remove types.Transactions for nonce, tx := range m.items { if filter(tx) { - removed = append(removed, tx) + remove = append(remove, tx) delete(m.items, nonce) + m.tree.Remove(nonce) } } - if len(removed) > 0 { - m.cacheMu.Lock() - m.cache = nil - m.cacheMu.Unlock() - } - return removed + return remove } -// Cap places a hard limit on the number of items, returning all transactions -// exceeding that limit. func (m *sortedMap) Cap(threshold int) types.Transactions { // Short circuit if the number of items is under the limit - if len(m.items) <= threshold { + size := len(m.items) + if size <= threshold { return nil } - // Otherwise gather and drop the highest nonce'd transactions - var drops types.Transactions - - sort.Sort(*m.index) - for size := len(m.items); size > threshold; size-- { - drops = append(drops, m.items[(*m.index)[size-1]]) - delete(m.items, (*m.index)[size-1]) + var remove types.Transactions + for size > threshold { + nonce, err := m.tree.Largest() + if err != nil { + break + } + remove = append(remove, m.items[nonce]) + delete(m.items, nonce) + m.tree.Remove(nonce) + size-- } - *m.index = (*m.index)[:threshold] - heap.Init(m.index) - - // If we had a cache, shift the back - m.cacheMu.Lock() - if m.cache != nil { - m.cache = m.cache[:len(m.cache)-len(drops)] - } - m.cacheMu.Unlock() - return drops + return remove } -// Remove deletes a transaction from the maintained map, returning whether the -// transaction was found. func (m *sortedMap) Remove(nonce uint64) bool { // Short circuit if no transaction is present _, ok := m.items[nonce] if !ok { return false } - // Otherwise delete the transaction and fix the heap index - for i := 0; i < m.index.Len(); i++ { - if (*m.index)[i] == nonce { - heap.Remove(m.index, i) - break - } - } delete(m.items, nonce) - m.cacheMu.Lock() - m.cache = nil - m.cacheMu.Unlock() - + m.tree.Remove(nonce) return true } -// Ready retrieves a sequentially increasing list of transactions starting at the -// provided nonce that is ready for processing. The returned transactions will be -// removed from the list. -// -// Note, all transactions with nonces lower than start will also be returned to -// prevent getting into and invalid state. This is not something that should ever -// happen but better to be self correcting than failing! -func (m *sortedMap) Ready(start uint64) types.Transactions { - // Short circuit if no transactions are available - if m.index.Len() == 0 || (*m.index)[0] > start { +// Given the provided start nonce, Ready returns +// transactions that are continous, the varible start is the virtual nonce. +func (m *sortedMap) Ready(start uint64, threshold *big.Int) types.Transactions { + size := len(m.items) + if size == 0 { return nil } - // Otherwise start accumulating incremental transactions - var ready types.Transactions - for next := (*m.index)[0]; m.index.Len() > 0 && (*m.index)[0] == next; next++ { - ready = append(ready, m.items[next]) - delete(m.items, next) - heap.Pop(m.index) + smallest, err := m.tree.Smallest() + if smallest > start || err != nil { + return nil + } + + var ready types.Transactions + tx := m.items[smallest] + total := new(big.Int).Set(tx.Cost()) + for next := smallest; size > 0 && smallest == next && total.Cmp(threshold) <= 0; next++ { + ready = append(ready, m.items[next]) + m.tree.Remove(smallest) + delete(m.items, smallest) + size-- + + smallest, err = m.tree.Smallest() + if err != nil { + break + } + + tx = m.items[smallest] + total = total.Add(total, tx.Cost()) } - m.cacheMu.Lock() - m.cache = nil - m.cacheMu.Unlock() return ready } -// Len returns the length of the transaction map. +func (m *sortedMap) PopExceeds(threshold *big.Int) types.Transactions { + var invalid types.Transactions + size := len(m.items) + for total := m.tree.root.sum; size > 0 && total.Cmp(threshold) > 0; { + largest, err := m.tree.Largest() + if err != nil { + break + } + tx := m.items[largest] + invalid = append(invalid, tx) + m.tree.Remove(largest) + delete(m.items, largest) + size-- + } + return invalid +} + func (m *sortedMap) Len() int { return len(m.items) } -func (m *sortedMap) flatten() types.Transactions { - m.cacheMu.Lock() - defer m.cacheMu.Unlock() - // If the sorting was not cached yet, create and cache it - if m.cache == nil { - m.cache = make(types.Transactions, 0, len(m.items)) - for _, tx := range m.items { - m.cache = append(m.cache, tx) - } - sort.Sort(types.TxByNonce(m.cache)) - } - return m.cache -} - -// Flatten creates a nonce-sorted slice of transactions based on the loosely -// sorted internal representation. The result of the sorting is cached in case -// it's requested again before any modifications are made to the contents. func (m *sortedMap) Flatten() types.Transactions { - cache := m.flatten() - // Copy the cache to prevent accidental modification - txs := make(types.Transactions, len(cache)) - copy(txs, cache) - return txs + nodes := m.tree.Flatten() + cache := make(types.Transactions, 0, len(m.items)) + for _, node := range nodes { + cache = append(cache, m.items[node.key]) + } + return cache } -// LastElement returns the last element of a flattened list, thus, the -// transaction with the highest nonce func (m *sortedMap) LastElement() *types.Transaction { - cache := m.flatten() - return cache[len(cache)-1] + last, err := m.tree.Largest() + if err != nil { + return nil + } + return m.items[last] } // list is a "list" of transactions belonging to an account, sorted by account @@ -271,19 +194,17 @@ type list struct { strict bool // Whether nonces are strictly continuous or not txs *sortedMap // Heap indexed sorted hash map of the transactions - costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance) - gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit) - totalcost *big.Int // Total cost of all transactions in the list + costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance) + gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit) } // newList create a new transaction list for maintaining nonce-indexable fast, -// gapped, sortable transaction lists. +// gapped, sortable transaction Lists. func newList(strict bool) *list { return &list{ - strict: strict, - txs: newSortedMap(), - costcap: new(big.Int), - totalcost: new(big.Int), + strict: strict, + txs: newSortedMap(), + costcap: new(big.Int), } } @@ -293,10 +214,14 @@ func (l *list) Contains(nonce uint64) bool { return l.txs.Get(nonce) != nil } +func (l *list) GetCost(nonce uint64) *big.Int { + return l.txs.GetCost(nonce) +} + // Add tries to insert a new transaction into the list, returning whether the // transaction was accepted, and if yes, any previous transaction it replaced. // -// If the new transaction is accepted into the list, the lists' cost and gas +// If the new transaction is accepted into the list, the Lists' cost and gas // thresholds are also potentially updated. func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { // If there's an older better transaction, abort @@ -321,12 +246,9 @@ func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transa if tx.GasFeeCapIntCmp(thresholdFeeCap) < 0 || tx.GasTipCapIntCmp(thresholdTip) < 0 { return false, nil } - // Old is being replaced, subtract old cost - l.subTotalCost([]*types.Transaction{old}) } - // Add new tx cost to totalcost - l.totalcost.Add(l.totalcost, tx.Cost()) // Otherwise overwrite the old transaction with the current one + // and update the total cost, gas cap and cost cap l.txs.Put(tx) if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 { l.costcap = cost @@ -342,7 +264,6 @@ func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transa // maintenance. func (l *list) Forward(threshold uint64) types.Transactions { txs := l.txs.Forward(threshold) - l.subTotalCost(txs) return txs } @@ -380,12 +301,10 @@ func (l *list) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions, lowest = nonce } } - invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest }) + // TODO: we can use LastElement() here, may be more efficient + invalids = l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest }) } // Reset total cost - l.subTotalCost(removed) - l.subTotalCost(invalids) - l.txs.reheap() return removed, invalids } @@ -393,7 +312,6 @@ func (l *list) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions, // exceeding that limit. func (l *list) Cap(threshold int) types.Transactions { txs := l.txs.Cap(threshold) - l.subTotalCost(txs) return txs } @@ -406,11 +324,10 @@ func (l *list) Remove(tx *types.Transaction) (bool, types.Transactions) { if removed := l.txs.Remove(nonce); !removed { return false, nil } - l.subTotalCost([]*types.Transaction{tx}) // In strict mode, filter out non-executable transactions if l.strict { + // TODO: we can use LastElement() here, may be more efficient txs := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > nonce }) - l.subTotalCost(txs) return true, txs } return true, nil @@ -419,13 +336,17 @@ func (l *list) Remove(tx *types.Transaction) (bool, types.Transactions) { // Ready retrieves a sequentially increasing list of transactions starting at the // provided nonce that is ready for processing. The returned transactions will be // removed from the list. -// +// The start is the virtual nonce of the account, not the first nonce of it. // Note, all transactions with nonces lower than start will also be returned to // prevent getting into and invalid state. This is not something that should ever // happen but better to be self correcting than failing! -func (l *list) Ready(start uint64) types.Transactions { - txs := l.txs.Ready(start) - l.subTotalCost(txs) +func (l *list) Ready(start uint64, threshold *big.Int) types.Transactions { + txs := l.txs.Ready(start, threshold) + return txs +} + +func (l *list) PopExceeds(threshold *big.Int) types.Transactions { + txs := l.txs.PopExceeds(threshold) return txs } @@ -452,14 +373,6 @@ func (l *list) LastElement() *types.Transaction { return l.txs.LastElement() } -// subTotalCost subtracts the cost of the given transactions from the -// total cost of all transactions. -func (l *list) subTotalCost(txs []*types.Transaction) { - for _, tx := range txs { - l.totalcost.Sub(l.totalcost, tx.Cost()) - } -} - // priceHeap is a heap.Interface implementation over transactions for retrieving // price-sorted transactions to discard when the pool fills up. If baseFee is set // then the heap is sorted based on the effective tip based on the given base fee. @@ -649,7 +562,7 @@ func (l *pricedList) Reheap() { start := time.Now() l.stales.Store(0) l.urgent.list = make([]*types.Transaction, 0, l.all.RemoteCount()) - l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { + l.all.Range(func(_ common.Hash, tx *types.Transaction, _ bool) bool { l.urgent.list = append(l.urgent.list, tx) return true }, false, true) // Only iterate remotes