-
Notifications
You must be signed in to change notification settings - Fork 21.9k
core/txpool: remove "local" notion from the txpool price heap #21478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a2cce31
ab83f5c
b5f07d2
2493234
5d7850c
309c7d0
4e97b39
ad6aca3
a6a0ffc
4168919
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,6 @@ import ( | |
|
|
||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/core/types" | ||
| "github.com/ethereum/go-ethereum/log" | ||
| ) | ||
|
|
||
| // nonceHeap is a heap.Interface implementation over 64bit unsigned integers for | ||
|
|
@@ -438,24 +437,29 @@ func (h *priceHeap) Pop() interface{} { | |
| } | ||
|
|
||
| // txPricedList is a price-sorted heap to allow operating on transactions pool | ||
| // contents in a price-incrementing way. | ||
| // contents in a price-incrementing way. It's built opon the all transactions | ||
| // in txpool but only interested in the remote part. It means only remote transactions | ||
| // will be considered for tracking, sorting, eviction, etc. | ||
| type txPricedList struct { | ||
| all *txLookup // Pointer to the map of all transactions | ||
| items *priceHeap // Heap of prices of all the stored transactions | ||
| stales int // Number of stale price points to (re-heap trigger) | ||
| all *txLookup // Pointer to the map of all transactions | ||
| remotes *priceHeap // Heap of prices of all the stored **remote** transactions | ||
| stales int // Number of stale price points to (re-heap trigger) | ||
| } | ||
|
|
||
| // newTxPricedList creates a new price-sorted transaction heap. | ||
| func newTxPricedList(all *txLookup) *txPricedList { | ||
| return &txPricedList{ | ||
| all: all, | ||
| items: new(priceHeap), | ||
| all: all, | ||
| remotes: new(priceHeap), | ||
| } | ||
| } | ||
|
|
||
| // Put inserts a new transaction into the heap. | ||
| func (l *txPricedList) Put(tx *types.Transaction) { | ||
| heap.Push(l.items, tx) | ||
| func (l *txPricedList) Put(tx *types.Transaction, local bool) { | ||
| if local { | ||
| return | ||
| } | ||
| heap.Push(l.remotes, tx) | ||
| } | ||
|
|
||
| // Removed notifies the prices transaction list that an old transaction dropped | ||
|
|
@@ -464,121 +468,95 @@ func (l *txPricedList) Put(tx *types.Transaction) { | |
| func (l *txPricedList) Removed(count int) { | ||
| // Bump the stale counter, but exit if still too low (< 25%) | ||
| l.stales += count | ||
| if l.stales <= len(*l.items)/4 { | ||
| if l.stales <= len(*l.remotes)/4 { | ||
| return | ||
| } | ||
| // Seems we've reached a critical number of stale transactions, reheap | ||
| reheap := make(priceHeap, 0, l.all.Count()) | ||
|
|
||
| l.stales, l.items = 0, &reheap | ||
| l.all.Range(func(hash common.Hash, tx *types.Transaction) bool { | ||
| *l.items = append(*l.items, tx) | ||
| return true | ||
| }) | ||
| heap.Init(l.items) | ||
| l.Reheap() | ||
| } | ||
|
|
||
| // Cap finds all the transactions below the given price threshold, drops them | ||
| // from the priced list and returns them for further removal from the entire pool. | ||
| func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions { | ||
| // | ||
| // Note: only remote transactions will be considered for eviction. | ||
| func (l *txPricedList) Cap(threshold *big.Int) types.Transactions { | ||
| drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop | ||
| save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep | ||
|
|
||
| for len(*l.items) > 0 { | ||
| for len(*l.remotes) > 0 { | ||
| // Discard stale transactions if found during cleanup | ||
| tx := heap.Pop(l.items).(*types.Transaction) | ||
| if l.all.Get(tx.Hash()) == nil { | ||
| cheapest := (*l.remotes)[0] | ||
| if l.all.GetRemote(cheapest.Hash()) == nil { // Removed or migrated | ||
| heap.Pop(l.remotes) | ||
| l.stales-- | ||
| continue | ||
| } | ||
| // Stop the discards if we've reached the threshold | ||
| if tx.GasPriceIntCmp(threshold) >= 0 { | ||
| save = append(save, tx) | ||
| if cheapest.GasPriceIntCmp(threshold) >= 0 { | ||
| break | ||
| } | ||
| // Non stale transaction found, discard unless local | ||
| if local.containsTx(tx) { | ||
| save = append(save, tx) | ||
| } else { | ||
| drop = append(drop, tx) | ||
| } | ||
| } | ||
| for _, tx := range save { | ||
| heap.Push(l.items, tx) | ||
| heap.Pop(l.remotes) | ||
| drop = append(drop, cheapest) | ||
| } | ||
| return drop | ||
| } | ||
|
|
||
| // Underpriced checks whether a transaction is cheaper than (or as cheap as) the | ||
| // lowest priced transaction currently being tracked. | ||
| func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) bool { | ||
| // Local transactions cannot be underpriced | ||
| if local.containsTx(tx) { | ||
| return false | ||
| } | ||
| // lowest priced (remote) transaction currently being tracked. | ||
| func (l *txPricedList) Underpriced(tx *types.Transaction) bool { | ||
| // Discard stale price points if found at the heap start | ||
| for len(*l.items) > 0 { | ||
| head := []*types.Transaction(*l.items)[0] | ||
| if l.all.Get(head.Hash()) == nil { | ||
| for len(*l.remotes) > 0 { | ||
| head := []*types.Transaction(*l.remotes)[0] | ||
| if l.all.GetRemote(head.Hash()) == nil { // Removed or migrated | ||
| l.stales-- | ||
| heap.Pop(l.items) | ||
| heap.Pop(l.remotes) | ||
| continue | ||
| } | ||
| break | ||
| } | ||
| // Check if the transaction is underpriced or not | ||
| if len(*l.items) == 0 { | ||
| log.Error("Pricing query for empty pool") // This cannot happen, print to catch programming errors | ||
| return false | ||
| if len(*l.remotes) == 0 { | ||
| return false // There is no remote transaction at all. | ||
| } | ||
| cheapest := []*types.Transaction(*l.items)[0] | ||
| // If the remote transaction is even cheaper than the | ||
| // cheapest one tracked locally, reject it. | ||
| cheapest := []*types.Transaction(*l.remotes)[0] | ||
| return cheapest.GasPriceCmp(tx) >= 0 | ||
| } | ||
|
|
||
| // Discard finds a number of most underpriced transactions, removes them from the | ||
| // priced list and returns them for further removal from the entire pool. | ||
| func (l *txPricedList) Discard(slots int, local *accountSet) types.Transactions { | ||
| // If we have some local accountset, those will not be discarded | ||
| if !local.empty() { | ||
| // In case the list is filled to the brim with 'local' txs, we do this | ||
| // little check to avoid unpacking / repacking the heap later on, which | ||
| // is very expensive | ||
| discardable := 0 | ||
| for _, tx := range *l.items { | ||
| if !local.containsTx(tx) { | ||
| discardable++ | ||
| } | ||
| if discardable >= slots { | ||
| break | ||
| } | ||
| } | ||
| if slots > discardable { | ||
| slots = discardable | ||
| } | ||
| } | ||
| if slots == 0 { | ||
| return nil | ||
| } | ||
| drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop | ||
| save := make(types.Transactions, 0, len(*l.items)-slots) // Local underpriced transactions to keep | ||
|
|
||
| for len(*l.items) > 0 && slots > 0 { | ||
| // | ||
| // Note local transaction won't be considered for eviction. | ||
| func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool) { | ||
| drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop | ||
| for len(*l.remotes) > 0 && slots > 0 { | ||
| // Discard stale transactions if found during cleanup | ||
| tx := heap.Pop(l.items).(*types.Transaction) | ||
| if l.all.Get(tx.Hash()) == nil { | ||
| tx := heap.Pop(l.remotes).(*types.Transaction) | ||
| if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated | ||
| l.stales-- | ||
| continue | ||
| } | ||
| // Non stale transaction found, discard unless local | ||
| if local.containsTx(tx) { | ||
| save = append(save, tx) | ||
| } else { | ||
| drop = append(drop, tx) | ||
| slots -= numSlots(tx) | ||
| // Non stale transaction found, discard it | ||
| drop = append(drop, tx) | ||
| slots -= numSlots(tx) | ||
| } | ||
| // If we still can't make enough room for the new transaction | ||
| if slots > 0 && !force { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit unsure of what This looks a bit wrong to me. Essentially, if we don't manage to free up
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a special case: the txpool is full of local transactions. However we still want to make a room for the newly arrived local transaction. In this case, If we only have a part of the slot can be released, these slots are released forcibly(even the slots is 0)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes it the logic for the remote transactions. But I give some special care to the local transactions.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmmmm, re force + readds, this is interesting. Previously we've assumed that discard can always drop the needed number of transactions. IMHO this is a bug, made a bit worse by the slot rewrite:
Based on my understanding, you use |
||
| for _, tx := range drop { | ||
| heap.Push(l.remotes, tx) | ||
| } | ||
| return nil, false | ||
| } | ||
| for _, tx := range save { | ||
| heap.Push(l.items, tx) | ||
| } | ||
| return drop | ||
| return drop, true | ||
| } | ||
|
|
||
| // Reheap forcibly rebuilds the heap based on the current remote transaction set. | ||
| func (l *txPricedList) Reheap() { | ||
| reheap := make(priceHeap, 0, l.all.RemoteCount()) | ||
|
|
||
| l.stales, l.remotes = 0, &reheap | ||
| l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { | ||
| *l.remotes = append(*l.remotes, tx) | ||
| return true | ||
| }, false, true) // Only iterate remotes | ||
| heap.Init(l.remotes) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, there was a comment saying that this couldn't happen. Is that still true?
log.ErrorThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning
falseis correct in case of an empty pool because it just states that "yes, this transaction is priced correctly compared to everything else (none)". That said, I'm unsure why we'd start calling it on an empty pool.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for the empty remote set it's possible. All the transactions in the pool are locals.