mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Adding avl tree to txpool.
This commit is contained in:
parent
90d5bd85bc
commit
2e981cb2ed
5 changed files with 499 additions and 207 deletions
284
core/txpool/legacypool/avl.go
Executable file
284
core/txpool/legacypool/avl.go
Executable file
|
|
@ -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
|
||||
}
|
||||
95
core/txpool/legacypool/avl_test.go
Executable file
95
core/txpool/legacypool/avl_test.go
Executable file
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
}
|
||||
*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)]
|
||||
remove = append(remove, m.items[nonce])
|
||||
delete(m.items, nonce)
|
||||
m.tree.Remove(nonce)
|
||||
size--
|
||||
}
|
||||
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
|
||||
|
|
@ -273,17 +196,15 @@ type list struct {
|
|||
|
||||
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
|
||||
}
|
||||
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue