github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/core/tx_list.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package core 13 14 import ( 15 "container/heap" 16 "math" 17 "math/big" 18 "sort" 19 20 "github.com/Sberex/go-sberex/common" 21 "github.com/Sberex/go-sberex/core/types" 22 "github.com/Sberex/go-sberex/log" 23 ) 24 25 // nonceHeap is a heap.Interface implementation over 64bit unsigned integers for 26 // retrieving sorted transactions from the possibly gapped future queue. 27 type nonceHeap []uint64 28 29 func (h nonceHeap) Len() int { return len(h) } 30 func (h nonceHeap) Less(i, j int) bool { return h[i] < h[j] } 31 func (h nonceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 32 33 func (h *nonceHeap) Push(x interface{}) { 34 *h = append(*h, x.(uint64)) 35 } 36 37 func (h *nonceHeap) Pop() interface{} { 38 old := *h 39 n := len(old) 40 x := old[n-1] 41 *h = old[0 : n-1] 42 return x 43 } 44 45 // txSortedMap is a nonce->transaction hash map with a heap based index to allow 46 // iterating over the contents in a nonce-incrementing way. 47 type txSortedMap struct { 48 items map[uint64]*types.Transaction // Hash map storing the transaction data 49 index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode) 50 cache types.Transactions // Cache of the transactions already sorted 51 } 52 53 // newTxSortedMap creates a new nonce-sorted transaction map. 54 func newTxSortedMap() *txSortedMap { 55 return &txSortedMap{ 56 items: make(map[uint64]*types.Transaction), 57 index: new(nonceHeap), 58 } 59 } 60 61 // Get retrieves the current transactions associated with the given nonce. 62 func (m *txSortedMap) Get(nonce uint64) *types.Transaction { 63 return m.items[nonce] 64 } 65 66 // Put inserts a new transaction into the map, also updating the map's nonce 67 // index. If a transaction already exists with the same nonce, it's overwritten. 68 func (m *txSortedMap) Put(tx *types.Transaction) { 69 nonce := tx.Nonce() 70 if m.items[nonce] == nil { 71 heap.Push(m.index, nonce) 72 } 73 m.items[nonce], m.cache = tx, nil 74 } 75 76 // Forward removes all transactions from the map with a nonce lower than the 77 // provided threshold. Every removed transaction is returned for any post-removal 78 // maintenance. 79 func (m *txSortedMap) Forward(threshold uint64) types.Transactions { 80 var removed types.Transactions 81 82 // Pop off heap items until the threshold is reached 83 for m.index.Len() > 0 && (*m.index)[0] < threshold { 84 nonce := heap.Pop(m.index).(uint64) 85 removed = append(removed, m.items[nonce]) 86 delete(m.items, nonce) 87 } 88 // If we had a cached order, shift the front 89 if m.cache != nil { 90 m.cache = m.cache[len(removed):] 91 } 92 return removed 93 } 94 95 // Filter iterates over the list of transactions and removes all of them for which 96 // the specified function evaluates to true. 97 func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions { 98 var removed types.Transactions 99 100 // Collect all the transactions to filter out 101 for nonce, tx := range m.items { 102 if filter(tx) { 103 removed = append(removed, tx) 104 delete(m.items, nonce) 105 } 106 } 107 // If transactions were removed, the heap and cache are ruined 108 if len(removed) > 0 { 109 *m.index = make([]uint64, 0, len(m.items)) 110 for nonce := range m.items { 111 *m.index = append(*m.index, nonce) 112 } 113 heap.Init(m.index) 114 115 m.cache = nil 116 } 117 return removed 118 } 119 120 // Cap places a hard limit on the number of items, returning all transactions 121 // exceeding that limit. 122 func (m *txSortedMap) Cap(threshold int) types.Transactions { 123 // Short circuit if the number of items is under the limit 124 if len(m.items) <= threshold { 125 return nil 126 } 127 // Otherwise gather and drop the highest nonce'd transactions 128 var drops types.Transactions 129 130 sort.Sort(*m.index) 131 for size := len(m.items); size > threshold; size-- { 132 drops = append(drops, m.items[(*m.index)[size-1]]) 133 delete(m.items, (*m.index)[size-1]) 134 } 135 *m.index = (*m.index)[:threshold] 136 heap.Init(m.index) 137 138 // If we had a cache, shift the back 139 if m.cache != nil { 140 m.cache = m.cache[:len(m.cache)-len(drops)] 141 } 142 return drops 143 } 144 145 // Remove deletes a transaction from the maintained map, returning whether the 146 // transaction was found. 147 func (m *txSortedMap) Remove(nonce uint64) bool { 148 // Short circuit if no transaction is present 149 _, ok := m.items[nonce] 150 if !ok { 151 return false 152 } 153 // Otherwise delete the transaction and fix the heap index 154 for i := 0; i < m.index.Len(); i++ { 155 if (*m.index)[i] == nonce { 156 heap.Remove(m.index, i) 157 break 158 } 159 } 160 delete(m.items, nonce) 161 m.cache = nil 162 163 return true 164 } 165 166 // Ready retrieves a sequentially increasing list of transactions starting at the 167 // provided nonce that is ready for processing. The returned transactions will be 168 // removed from the list. 169 // 170 // Note, all transactions with nonces lower than start will also be returned to 171 // prevent getting into and invalid state. This is not something that should ever 172 // happen but better to be self correcting than failing! 173 func (m *txSortedMap) Ready(start uint64) types.Transactions { 174 // Short circuit if no transactions are available 175 if m.index.Len() == 0 || (*m.index)[0] > start { 176 return nil 177 } 178 // Otherwise start accumulating incremental transactions 179 var ready types.Transactions 180 for next := (*m.index)[0]; m.index.Len() > 0 && (*m.index)[0] == next; next++ { 181 ready = append(ready, m.items[next]) 182 delete(m.items, next) 183 heap.Pop(m.index) 184 } 185 m.cache = nil 186 187 return ready 188 } 189 190 // Len returns the length of the transaction map. 191 func (m *txSortedMap) Len() int { 192 return len(m.items) 193 } 194 195 // Flatten creates a nonce-sorted slice of transactions based on the loosely 196 // sorted internal representation. The result of the sorting is cached in case 197 // it's requested again before any modifications are made to the contents. 198 func (m *txSortedMap) Flatten() types.Transactions { 199 // If the sorting was not cached yet, create and cache it 200 if m.cache == nil { 201 m.cache = make(types.Transactions, 0, len(m.items)) 202 for _, tx := range m.items { 203 m.cache = append(m.cache, tx) 204 } 205 sort.Sort(types.TxByNonce(m.cache)) 206 } 207 // Copy the cache to prevent accidental modifications 208 txs := make(types.Transactions, len(m.cache)) 209 copy(txs, m.cache) 210 return txs 211 } 212 213 // txList is a "list" of transactions belonging to an account, sorted by account 214 // nonce. The same type can be used both for storing contiguous transactions for 215 // the executable/pending queue; and for storing gapped transactions for the non- 216 // executable/future queue, with minor behavioral changes. 217 type txList struct { 218 strict bool // Whether nonces are strictly continuous or not 219 txs *txSortedMap // Heap indexed sorted hash map of the transactions 220 221 costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance) 222 gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit) 223 } 224 225 // newTxList create a new transaction list for maintaining nonce-indexable fast, 226 // gapped, sortable transaction lists. 227 func newTxList(strict bool) *txList { 228 return &txList{ 229 strict: strict, 230 txs: newTxSortedMap(), 231 costcap: new(big.Int), 232 } 233 } 234 235 // Overlaps returns whether the transaction specified has the same nonce as one 236 // already contained within the list. 237 func (l *txList) Overlaps(tx *types.Transaction) bool { 238 return l.txs.Get(tx.Nonce()) != nil 239 } 240 241 // Add tries to insert a new transaction into the list, returning whether the 242 // transaction was accepted, and if yes, any previous transaction it replaced. 243 // 244 // If the new transaction is accepted into the list, the lists' cost and gas 245 // thresholds are also potentially updated. 246 func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { 247 // If there's an older better transaction, abort 248 old := l.txs.Get(tx.Nonce()) 249 if old != nil { 250 threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100)) 251 // Have to ensure that the new gas price is higher than the old gas 252 // price as well as checking the percentage threshold to ensure that 253 // this is accurate for low (Leto-level) gas price replacements 254 if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 { 255 return false, nil 256 } 257 } 258 // Otherwise overwrite the old transaction with the current one 259 l.txs.Put(tx) 260 if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 { 261 l.costcap = cost 262 } 263 if gas := tx.Gas(); l.gascap < gas { 264 l.gascap = gas 265 } 266 return true, old 267 } 268 269 // Forward removes all transactions from the list with a nonce lower than the 270 // provided threshold. Every removed transaction is returned for any post-removal 271 // maintenance. 272 func (l *txList) Forward(threshold uint64) types.Transactions { 273 return l.txs.Forward(threshold) 274 } 275 276 // Filter removes all transactions from the list with a cost or gas limit higher 277 // than the provided thresholds. Every removed transaction is returned for any 278 // post-removal maintenance. Strict-mode invalidated transactions are also 279 // returned. 280 // 281 // This method uses the cached costcap and gascap to quickly decide if there's even 282 // a point in calculating all the costs or if the balance covers all. If the threshold 283 // is lower than the costgas cap, the caps will be reset to a new high after removing 284 // the newly invalidated transactions. 285 func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions, types.Transactions) { 286 // If all transactions are below the threshold, short circuit 287 if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit { 288 return nil, nil 289 } 290 l.costcap = new(big.Int).Set(costLimit) // Lower the caps to the thresholds 291 l.gascap = gasLimit 292 293 // Filter out all the transactions above the account's funds 294 removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost().Cmp(costLimit) > 0 || tx.Gas() > gasLimit }) 295 296 // If the list was strict, filter anything above the lowest nonce 297 var invalids types.Transactions 298 299 if l.strict && len(removed) > 0 { 300 lowest := uint64(math.MaxUint64) 301 for _, tx := range removed { 302 if nonce := tx.Nonce(); lowest > nonce { 303 lowest = nonce 304 } 305 } 306 invalids = l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest }) 307 } 308 return removed, invalids 309 } 310 311 // Cap places a hard limit on the number of items, returning all transactions 312 // exceeding that limit. 313 func (l *txList) Cap(threshold int) types.Transactions { 314 return l.txs.Cap(threshold) 315 } 316 317 // Remove deletes a transaction from the maintained list, returning whether the 318 // transaction was found, and also returning any transaction invalidated due to 319 // the deletion (strict mode only). 320 func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) { 321 // Remove the transaction from the set 322 nonce := tx.Nonce() 323 if removed := l.txs.Remove(nonce); !removed { 324 return false, nil 325 } 326 // In strict mode, filter out non-executable transactions 327 if l.strict { 328 return true, l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > nonce }) 329 } 330 return true, nil 331 } 332 333 // Ready retrieves a sequentially increasing list of transactions starting at the 334 // provided nonce that is ready for processing. The returned transactions will be 335 // removed from the list. 336 // 337 // Note, all transactions with nonces lower than start will also be returned to 338 // prevent getting into and invalid state. This is not something that should ever 339 // happen but better to be self correcting than failing! 340 func (l *txList) Ready(start uint64) types.Transactions { 341 return l.txs.Ready(start) 342 } 343 344 // Len returns the length of the transaction list. 345 func (l *txList) Len() int { 346 return l.txs.Len() 347 } 348 349 // Empty returns whether the list of transactions is empty or not. 350 func (l *txList) Empty() bool { 351 return l.Len() == 0 352 } 353 354 // Flatten creates a nonce-sorted slice of transactions based on the loosely 355 // sorted internal representation. The result of the sorting is cached in case 356 // it's requested again before any modifications are made to the contents. 357 func (l *txList) Flatten() types.Transactions { 358 return l.txs.Flatten() 359 } 360 361 // priceHeap is a heap.Interface implementation over transactions for retrieving 362 // price-sorted transactions to discard when the pool fills up. 363 type priceHeap []*types.Transaction 364 365 func (h priceHeap) Len() int { return len(h) } 366 func (h priceHeap) Less(i, j int) bool { return h[i].GasPrice().Cmp(h[j].GasPrice()) < 0 } 367 func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 368 369 func (h *priceHeap) Push(x interface{}) { 370 *h = append(*h, x.(*types.Transaction)) 371 } 372 373 func (h *priceHeap) Pop() interface{} { 374 old := *h 375 n := len(old) 376 x := old[n-1] 377 *h = old[0 : n-1] 378 return x 379 } 380 381 // txPricedList is a price-sorted heap to allow operating on transactions pool 382 // contents in a price-incrementing way. 383 type txPricedList struct { 384 all *map[common.Hash]*types.Transaction // Pointer to the map of all transactions 385 items *priceHeap // Heap of prices of all the stored transactions 386 stales int // Number of stale price points to (re-heap trigger) 387 } 388 389 // newTxPricedList creates a new price-sorted transaction heap. 390 func newTxPricedList(all *map[common.Hash]*types.Transaction) *txPricedList { 391 return &txPricedList{ 392 all: all, 393 items: new(priceHeap), 394 } 395 } 396 397 // Put inserts a new transaction into the heap. 398 func (l *txPricedList) Put(tx *types.Transaction) { 399 heap.Push(l.items, tx) 400 } 401 402 // Removed notifies the prices transaction list that an old transaction dropped 403 // from the pool. The list will just keep a counter of stale objects and update 404 // the heap if a large enough ratio of transactions go stale. 405 func (l *txPricedList) Removed() { 406 // Bump the stale counter, but exit if still too low (< 25%) 407 l.stales++ 408 if l.stales <= len(*l.items)/4 { 409 return 410 } 411 // Seems we've reached a critical number of stale transactions, reheap 412 reheap := make(priceHeap, 0, len(*l.all)) 413 414 l.stales, l.items = 0, &reheap 415 for _, tx := range *l.all { 416 *l.items = append(*l.items, tx) 417 } 418 heap.Init(l.items) 419 } 420 421 // Cap finds all the transactions below the given price threshold, drops them 422 // from the priced list and returs them for further removal from the entire pool. 423 func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions { 424 drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop 425 save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep 426 427 for len(*l.items) > 0 { 428 // Discard stale transactions if found during cleanup 429 tx := heap.Pop(l.items).(*types.Transaction) 430 if _, ok := (*l.all)[tx.Hash()]; !ok { 431 l.stales-- 432 continue 433 } 434 // Stop the discards if we've reached the threshold 435 if tx.GasPrice().Cmp(threshold) >= 0 { 436 save = append(save, tx) 437 break 438 } 439 // Non stale transaction found, discard unless local 440 if local.containsTx(tx) { 441 save = append(save, tx) 442 } else { 443 drop = append(drop, tx) 444 } 445 } 446 for _, tx := range save { 447 heap.Push(l.items, tx) 448 } 449 return drop 450 } 451 452 // Underpriced checks whether a transaction is cheaper than (or as cheap as) the 453 // lowest priced transaction currently being tracked. 454 func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) bool { 455 // Local transactions cannot be underpriced 456 if local.containsTx(tx) { 457 return false 458 } 459 // Discard stale price points if found at the heap start 460 for len(*l.items) > 0 { 461 head := []*types.Transaction(*l.items)[0] 462 if _, ok := (*l.all)[head.Hash()]; !ok { 463 l.stales-- 464 heap.Pop(l.items) 465 continue 466 } 467 break 468 } 469 // Check if the transaction is underpriced or not 470 if len(*l.items) == 0 { 471 log.Error("Pricing query for empty pool") // This cannot happen, print to catch programming errors 472 return false 473 } 474 cheapest := []*types.Transaction(*l.items)[0] 475 return cheapest.GasPrice().Cmp(tx.GasPrice()) >= 0 476 } 477 478 // Discard finds a number of most underpriced transactions, removes them from the 479 // priced list and returns them for further removal from the entire pool. 480 func (l *txPricedList) Discard(count int, local *accountSet) types.Transactions { 481 drop := make(types.Transactions, 0, count) // Remote underpriced transactions to drop 482 save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep 483 484 for len(*l.items) > 0 && count > 0 { 485 // Discard stale transactions if found during cleanup 486 tx := heap.Pop(l.items).(*types.Transaction) 487 if _, ok := (*l.all)[tx.Hash()]; !ok { 488 l.stales-- 489 continue 490 } 491 // Non stale transaction found, discard unless local 492 if local.containsTx(tx) { 493 save = append(save, tx) 494 } else { 495 drop = append(drop, tx) 496 count-- 497 } 498 } 499 for _, tx := range save { 500 heap.Push(l.items, tx) 501 } 502 return drop 503 }