github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/mempool/v1/mempool.go (about) 1 // Deprecated: Priority mempool will be removed in the next major release. 2 package v1 3 4 import ( 5 "fmt" 6 "runtime" 7 "sort" 8 "sync" 9 "sync/atomic" 10 "time" 11 12 "github.com/creachadair/taskgroup" 13 14 abci "github.com/badrootd/nibiru-cometbft/abci/types" 15 "github.com/badrootd/nibiru-cometbft/config" 16 "github.com/badrootd/nibiru-cometbft/libs/clist" 17 "github.com/badrootd/nibiru-cometbft/libs/log" 18 "github.com/badrootd/nibiru-cometbft/mempool" 19 "github.com/badrootd/nibiru-cometbft/proxy" 20 "github.com/badrootd/nibiru-cometbft/types" 21 ) 22 23 var _ mempool.Mempool = (*TxMempool)(nil) 24 25 // TxMempoolOption sets an optional parameter on the TxMempool. 26 type TxMempoolOption func(*TxMempool) 27 28 // TxMempool implemements the Mempool interface and allows the application to 29 // set priority values on transactions in the CheckTx response. When selecting 30 // transactions to include in a block, higher-priority transactions are chosen 31 // first. When evicting transactions from the mempool for size constraints, 32 // lower-priority transactions are evicted sooner. 33 // 34 // Within the mempool, transactions are ordered by time of arrival, and are 35 // gossiped to the rest of the network based on that order (gossip order does 36 // not take priority into account). 37 type TxMempool struct { 38 // Immutable fields 39 logger log.Logger 40 config *config.MempoolConfig 41 proxyAppConn proxy.AppConnMempool 42 metrics *mempool.Metrics 43 cache mempool.TxCache // seen transactions 44 45 // Atomically-updated fields 46 txsBytes int64 // atomic: the total size of all transactions in the mempool, in bytes 47 48 // Synchronized fields, protected by mtx. 49 mtx *sync.RWMutex 50 notifiedTxsAvailable bool 51 txsAvailable chan struct{} // one value sent per height when mempool is not empty 52 preCheck mempool.PreCheckFunc 53 postCheck mempool.PostCheckFunc 54 height int64 // the latest height passed to Update 55 56 txs *clist.CList // valid transactions (passed CheckTx) 57 txByKey map[types.TxKey]*clist.CElement 58 txBySender map[string]*clist.CElement // for sender != "" 59 } 60 61 // NewTxMempool constructs a new, empty priority mempool at the specified 62 // initial height and using the given config and options. 63 func NewTxMempool( 64 logger log.Logger, 65 cfg *config.MempoolConfig, 66 proxyAppConn proxy.AppConnMempool, 67 height int64, 68 options ...TxMempoolOption, 69 ) *TxMempool { 70 71 txmp := &TxMempool{ 72 logger: logger, 73 config: cfg, 74 proxyAppConn: proxyAppConn, 75 metrics: mempool.NopMetrics(), 76 cache: mempool.NopTxCache{}, 77 txs: clist.New(), 78 mtx: new(sync.RWMutex), 79 height: height, 80 txByKey: make(map[types.TxKey]*clist.CElement), 81 txBySender: make(map[string]*clist.CElement), 82 } 83 if cfg.CacheSize > 0 { 84 txmp.cache = mempool.NewLRUTxCache(cfg.CacheSize) 85 } 86 87 for _, opt := range options { 88 opt(txmp) 89 } 90 91 return txmp 92 } 93 94 // WithPreCheck sets a filter for the mempool to reject a transaction if f(tx) 95 // returns an error. This is executed before CheckTx. It only applies to the 96 // first created block. After that, Update() overwrites the existing value. 97 func WithPreCheck(f mempool.PreCheckFunc) TxMempoolOption { 98 return func(txmp *TxMempool) { txmp.preCheck = f } 99 } 100 101 // WithPostCheck sets a filter for the mempool to reject a transaction if 102 // f(tx, resp) returns an error. This is executed after CheckTx. It only applies 103 // to the first created block. After that, Update overwrites the existing value. 104 func WithPostCheck(f mempool.PostCheckFunc) TxMempoolOption { 105 return func(txmp *TxMempool) { txmp.postCheck = f } 106 } 107 108 // WithMetrics sets the mempool's metrics collector. 109 func WithMetrics(metrics *mempool.Metrics) TxMempoolOption { 110 return func(txmp *TxMempool) { txmp.metrics = metrics } 111 } 112 113 // Lock obtains a write-lock on the mempool. A caller must be sure to explicitly 114 // release the lock when finished. 115 func (txmp *TxMempool) Lock() { txmp.mtx.Lock() } 116 117 // Unlock releases a write-lock on the mempool. 118 func (txmp *TxMempool) Unlock() { txmp.mtx.Unlock() } 119 120 // Size returns the number of valid transactions in the mempool. It is 121 // thread-safe. 122 func (txmp *TxMempool) Size() int { return txmp.txs.Len() } 123 124 // SizeBytes return the total sum in bytes of all the valid transactions in the 125 // mempool. It is thread-safe. 126 func (txmp *TxMempool) SizeBytes() int64 { return atomic.LoadInt64(&txmp.txsBytes) } 127 128 // FlushAppConn executes FlushSync on the mempool's proxyAppConn. 129 // 130 // The caller must hold an exclusive mempool lock (by calling txmp.Lock) before 131 // calling FlushAppConn. 132 func (txmp *TxMempool) FlushAppConn() error { 133 // N.B.: We have to issue the call outside the lock so that its callback can 134 // fire. It's safe to do this, the flush will block until complete. 135 // 136 // We could just not require the caller to hold the lock at all, but the 137 // semantics of the Mempool interface require the caller to hold it, and we 138 // can't change that without disrupting existing use. 139 txmp.mtx.Unlock() 140 defer txmp.mtx.Lock() 141 142 return txmp.proxyAppConn.FlushSync() 143 } 144 145 // EnableTxsAvailable enables the mempool to trigger events when transactions 146 // are available on a block by block basis. 147 func (txmp *TxMempool) EnableTxsAvailable() { 148 txmp.mtx.Lock() 149 defer txmp.mtx.Unlock() 150 151 txmp.txsAvailable = make(chan struct{}, 1) 152 } 153 154 // TxsAvailable returns a channel which fires once for every height, and only 155 // when transactions are available in the mempool. It is thread-safe. 156 func (txmp *TxMempool) TxsAvailable() <-chan struct{} { return txmp.txsAvailable } 157 158 // CheckTx adds the given transaction to the mempool if it fits and passes the 159 // application's ABCI CheckTx method. 160 // 161 // CheckTx reports an error without adding tx if: 162 // 163 // - The size of tx exceeds the configured maximum transaction size. 164 // - The pre-check hook is defined and reports an error for tx. 165 // - The transaction already exists in the cache. 166 // - The proxy connection to the application fails. 167 // 168 // If tx passes all of the above conditions, it is passed (asynchronously) to 169 // the application's ABCI CheckTx method and this CheckTx method returns nil. 170 // If cb != nil, it is called when the ABCI request completes to report the 171 // application response. 172 // 173 // If the application accepts the transaction and the mempool is full, the 174 // mempool evicts one or more of the lowest-priority transaction whose priority 175 // is (strictly) lower than the priority of tx and whose size together exceeds 176 // the size of tx, and adds tx instead. If no such transactions exist, tx is 177 // discarded. 178 func (txmp *TxMempool) CheckTx(tx types.Tx, cb func(*abci.Response), txInfo mempool.TxInfo) error { 179 180 // During the initial phase of CheckTx, we do not need to modify any state. 181 // A transaction will not actually be added to the mempool until it survives 182 // a call to the ABCI CheckTx method and size constraint checks. 183 height, err := func() (int64, error) { 184 txmp.mtx.RLock() 185 defer txmp.mtx.RUnlock() 186 187 // Reject transactions in excess of the configured maximum transaction size. 188 if len(tx) > txmp.config.MaxTxBytes { 189 return 0, mempool.ErrTxTooLarge{Max: txmp.config.MaxTxBytes, Actual: len(tx)} 190 } 191 192 // If a precheck hook is defined, call it before invoking the application. 193 if txmp.preCheck != nil { 194 if err := txmp.preCheck(tx); err != nil { 195 return 0, mempool.ErrPreCheck{Reason: err} 196 } 197 } 198 199 // Early exit if the proxy connection has an error. 200 if err := txmp.proxyAppConn.Error(); err != nil { 201 return 0, err 202 } 203 204 txKey := tx.Key() 205 206 // Check for the transaction in the cache. 207 if !txmp.cache.Push(tx) { 208 // If the cached transaction is also in the pool, record its sender. 209 if elt, ok := txmp.txByKey[txKey]; ok { 210 w := elt.Value.(*WrappedTx) 211 w.SetPeer(txInfo.SenderID) 212 } 213 return 0, mempool.ErrTxInCache 214 } 215 return txmp.height, nil 216 }() 217 if err != nil { 218 return err 219 } 220 221 // Invoke an ABCI CheckTx for this transaction. 222 rsp, err := txmp.proxyAppConn.CheckTxSync(abci.RequestCheckTx{Tx: tx}) 223 if err != nil { 224 txmp.cache.Remove(tx) 225 return err 226 } 227 wtx := &WrappedTx{ 228 tx: tx, 229 hash: tx.Key(), 230 timestamp: time.Now().UTC(), 231 height: height, 232 } 233 wtx.SetPeer(txInfo.SenderID) 234 txmp.addNewTransaction(wtx, rsp) 235 if cb != nil { 236 cb(&abci.Response{Value: &abci.Response_CheckTx{CheckTx: rsp}}) 237 } 238 return nil 239 } 240 241 // RemoveTxByKey removes the transaction with the specified key from the 242 // mempool. It reports an error if no such transaction exists. This operation 243 // does not remove the transaction from the cache. 244 func (txmp *TxMempool) RemoveTxByKey(txKey types.TxKey) error { 245 txmp.mtx.Lock() 246 defer txmp.mtx.Unlock() 247 return txmp.removeTxByKey(txKey) 248 } 249 250 // removeTxByKey removes the specified transaction key from the mempool. 251 // The caller must hold txmp.mtx excluxively. 252 func (txmp *TxMempool) removeTxByKey(key types.TxKey) error { 253 if elt, ok := txmp.txByKey[key]; ok { 254 w := elt.Value.(*WrappedTx) 255 delete(txmp.txByKey, key) 256 delete(txmp.txBySender, w.sender) 257 txmp.txs.Remove(elt) 258 elt.DetachPrev() 259 elt.DetachNext() 260 atomic.AddInt64(&txmp.txsBytes, -w.Size()) 261 return nil 262 } 263 return fmt.Errorf("transaction %x not found", key) 264 } 265 266 // removeTxByElement removes the specified transaction element from the mempool. 267 // The caller must hold txmp.mtx exclusively. 268 func (txmp *TxMempool) removeTxByElement(elt *clist.CElement) { 269 w := elt.Value.(*WrappedTx) 270 delete(txmp.txByKey, w.tx.Key()) 271 delete(txmp.txBySender, w.sender) 272 txmp.txs.Remove(elt) 273 elt.DetachPrev() 274 elt.DetachNext() 275 atomic.AddInt64(&txmp.txsBytes, -w.Size()) 276 } 277 278 // Flush purges the contents of the mempool and the cache, leaving both empty. 279 // The current height is not modified by this operation. 280 func (txmp *TxMempool) Flush() { 281 txmp.mtx.Lock() 282 defer txmp.mtx.Unlock() 283 284 // Remove all the transactions in the list explicitly, so that the sizes 285 // and indexes get updated properly. 286 cur := txmp.txs.Front() 287 for cur != nil { 288 next := cur.Next() 289 txmp.removeTxByElement(cur) 290 cur = next 291 } 292 txmp.cache.Reset() 293 } 294 295 // allEntriesSorted returns a slice of all the transactions currently in the 296 // mempool, sorted in nonincreasing order by priority with ties broken by 297 // increasing order of arrival time. 298 func (txmp *TxMempool) allEntriesSorted() []*WrappedTx { 299 txmp.mtx.RLock() 300 defer txmp.mtx.RUnlock() 301 302 all := make([]*WrappedTx, 0, len(txmp.txByKey)) 303 for _, tx := range txmp.txByKey { 304 all = append(all, tx.Value.(*WrappedTx)) 305 } 306 sort.Slice(all, func(i, j int) bool { 307 if all[i].priority == all[j].priority { 308 return all[i].timestamp.Before(all[j].timestamp) 309 } 310 return all[i].priority > all[j].priority // N.B. higher priorities first 311 }) 312 return all 313 } 314 315 // ReapMaxBytesMaxGas returns a slice of valid transactions that fit within the 316 // size and gas constraints. The results are ordered by nonincreasing priority, 317 // with ties broken by increasing order of arrival. Reaping transactions does 318 // not remove them from the mempool. 319 // 320 // If maxBytes < 0, no limit is set on the total size in bytes. 321 // If maxGas < 0, no limit is set on the total gas cost. 322 // 323 // If the mempool is empty or has no transactions fitting within the given 324 // constraints, the result will also be empty. 325 func (txmp *TxMempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs { 326 var totalGas, totalBytes int64 327 328 var keep []types.Tx //nolint:prealloc 329 for _, w := range txmp.allEntriesSorted() { 330 // N.B. When computing byte size, we need to include the overhead for 331 // encoding as protobuf to send to the application. 332 totalGas += w.gasWanted 333 totalBytes += types.ComputeProtoSizeForTxs([]types.Tx{w.tx}) 334 if (maxGas >= 0 && totalGas > maxGas) || (maxBytes >= 0 && totalBytes > maxBytes) { 335 break 336 } 337 keep = append(keep, w.tx) 338 } 339 return keep 340 } 341 342 // TxsWaitChan returns a channel that is closed when there is at least one 343 // transaction available to be gossiped. 344 func (txmp *TxMempool) TxsWaitChan() <-chan struct{} { return txmp.txs.WaitChan() } 345 346 // TxsFront returns the frontmost element of the pending transaction list. 347 // It will be nil if the mempool is empty. 348 func (txmp *TxMempool) TxsFront() *clist.CElement { return txmp.txs.Front() } 349 350 // ReapMaxTxs returns up to max transactions from the mempool. The results are 351 // ordered by nonincreasing priority with ties broken by increasing order of 352 // arrival. Reaping transactions does not remove them from the mempool. 353 // 354 // If max < 0, all transactions in the mempool are reaped. 355 // 356 // The result may have fewer than max elements (possibly zero) if the mempool 357 // does not have that many transactions available. 358 func (txmp *TxMempool) ReapMaxTxs(max int) types.Txs { 359 var keep []types.Tx //nolint:prealloc 360 361 for _, w := range txmp.allEntriesSorted() { 362 if max >= 0 && len(keep) >= max { 363 break 364 } 365 keep = append(keep, w.tx) 366 } 367 return keep 368 } 369 370 // Update removes all the given transactions from the mempool and the cache, 371 // and updates the current block height. The blockTxs and deliverTxResponses 372 // must have the same length with each response corresponding to the tx at the 373 // same offset. 374 // 375 // If the configuration enables recheck, Update sends each remaining 376 // transaction after removing blockTxs to the ABCI CheckTx method. Any 377 // transactions marked as invalid during recheck are also removed. 378 // 379 // The caller must hold an exclusive mempool lock (by calling txmp.Lock) before 380 // calling Update. 381 func (txmp *TxMempool) Update( 382 blockHeight int64, 383 blockTxs types.Txs, 384 deliverTxResponses []*abci.ResponseDeliverTx, 385 newPreFn mempool.PreCheckFunc, 386 newPostFn mempool.PostCheckFunc, 387 ) error { 388 // Safety check: Transactions and responses must match in number. 389 if len(blockTxs) != len(deliverTxResponses) { 390 panic(fmt.Sprintf("mempool: got %d transactions but %d DeliverTx responses", 391 len(blockTxs), len(deliverTxResponses))) 392 } 393 394 txmp.height = blockHeight 395 txmp.notifiedTxsAvailable = false 396 397 if newPreFn != nil { 398 txmp.preCheck = newPreFn 399 } 400 if newPostFn != nil { 401 txmp.postCheck = newPostFn 402 } 403 404 for i, tx := range blockTxs { 405 // Add successful committed transactions to the cache (if they are not 406 // already present). Transactions that failed to commit are removed from 407 // the cache unless the operator has explicitly requested we keep them. 408 if deliverTxResponses[i].Code == abci.CodeTypeOK { 409 _ = txmp.cache.Push(tx) 410 } else if !txmp.config.KeepInvalidTxsInCache { 411 txmp.cache.Remove(tx) 412 } 413 414 // Regardless of success, remove the transaction from the mempool. 415 _ = txmp.removeTxByKey(tx.Key()) 416 } 417 418 txmp.purgeExpiredTxs(blockHeight) 419 420 // If there any uncommitted transactions left in the mempool, we either 421 // initiate re-CheckTx per remaining transaction or notify that remaining 422 // transactions are left. 423 size := txmp.Size() 424 txmp.metrics.Size.Set(float64(size)) 425 if size > 0 { 426 if txmp.config.Recheck { 427 txmp.recheckTransactions() 428 } else { 429 txmp.notifyTxsAvailable() 430 } 431 } 432 return nil 433 } 434 435 // addNewTransaction handles the ABCI CheckTx response for the first time a 436 // transaction is added to the mempool. A recheck after a block is committed 437 // goes to handleRecheckResult. 438 // 439 // If either the application rejected the transaction or a post-check hook is 440 // defined and rejects the transaction, it is discarded. 441 // 442 // Otherwise, if the mempool is full, check for lower-priority transactions 443 // that can be evicted to make room for the new one. If no such transactions 444 // exist, this transaction is logged and dropped; otherwise the selected 445 // transactions are evicted. 446 // 447 // Finally, the new transaction is added and size stats updated. 448 func (txmp *TxMempool) addNewTransaction(wtx *WrappedTx, checkTxRes *abci.ResponseCheckTx) { 449 txmp.mtx.Lock() 450 defer txmp.mtx.Unlock() 451 452 var err error 453 if txmp.postCheck != nil { 454 err = txmp.postCheck(wtx.tx, checkTxRes) 455 } 456 457 if err != nil || checkTxRes.Code != abci.CodeTypeOK { 458 txmp.logger.Debug( 459 "rejected bad transaction", 460 "priority", wtx.Priority(), 461 "tx", fmt.Sprintf("%X", wtx.tx.Hash()), 462 "peer_id", wtx.peers, 463 "code", checkTxRes.Code, 464 "post_check_err", err, 465 ) 466 467 txmp.metrics.FailedTxs.Add(1) 468 469 // Remove the invalid transaction from the cache, unless the operator has 470 // instructed us to keep invalid transactions. 471 if !txmp.config.KeepInvalidTxsInCache { 472 txmp.cache.Remove(wtx.tx) 473 } 474 475 // If there was a post-check error, record its text in the result for 476 // debugging purposes. 477 if err != nil { 478 checkTxRes.MempoolError = err.Error() 479 } 480 return 481 } 482 483 priority := checkTxRes.Priority 484 sender := checkTxRes.Sender 485 486 // Disallow multiple concurrent transactions from the same sender assigned 487 // by the ABCI application. As a special case, an empty sender is not 488 // restricted. 489 if sender != "" { 490 elt, ok := txmp.txBySender[sender] 491 if ok { 492 w := elt.Value.(*WrappedTx) 493 txmp.logger.Debug( 494 "rejected valid incoming transaction; tx already exists for sender", 495 "tx", fmt.Sprintf("%X", w.tx.Hash()), 496 "sender", sender, 497 ) 498 checkTxRes.MempoolError = 499 fmt.Sprintf("rejected valid incoming transaction; tx already exists for sender %q (%X)", 500 sender, w.tx.Hash()) 501 txmp.metrics.RejectedTxs.Add(1) 502 return 503 } 504 } 505 506 // At this point the application has ruled the transaction valid, but the 507 // mempool might be full. If so, find the lowest-priority items with lower 508 // priority than the application assigned to this new one, and evict as many 509 // of them as necessary to make room for tx. If no such items exist, we 510 // discard tx. 511 512 if err := txmp.canAddTx(wtx); err != nil { 513 var victims []*clist.CElement // eligible transactions for eviction 514 var victimBytes int64 // total size of victims 515 for cur := txmp.txs.Front(); cur != nil; cur = cur.Next() { 516 cw := cur.Value.(*WrappedTx) 517 if cw.priority < priority { 518 victims = append(victims, cur) 519 victimBytes += cw.Size() 520 } 521 } 522 523 // If there are no suitable eviction candidates, or the total size of 524 // those candidates is not enough to make room for the new transaction, 525 // drop the new one. 526 if len(victims) == 0 || victimBytes < wtx.Size() { 527 txmp.cache.Remove(wtx.tx) 528 txmp.logger.Error( 529 "rejected valid incoming transaction; mempool is full", 530 "tx", fmt.Sprintf("%X", wtx.tx.Hash()), 531 "err", err.Error(), 532 ) 533 checkTxRes.MempoolError = 534 fmt.Sprintf("rejected valid incoming transaction; mempool is full (%X)", 535 wtx.tx.Hash()) 536 txmp.metrics.RejectedTxs.Add(1) 537 return 538 } 539 540 txmp.logger.Debug("evicting lower-priority transactions", 541 "new_tx", fmt.Sprintf("%X", wtx.tx.Hash()), 542 "new_priority", priority, 543 ) 544 545 // Sort lowest priority items first so they will be evicted first. Break 546 // ties in favor of newer items (to maintain FIFO semantics in a group). 547 sort.Slice(victims, func(i, j int) bool { 548 iw := victims[i].Value.(*WrappedTx) 549 jw := victims[j].Value.(*WrappedTx) 550 if iw.Priority() == jw.Priority() { 551 return iw.timestamp.After(jw.timestamp) 552 } 553 return iw.Priority() < jw.Priority() 554 }) 555 556 // Evict as many of the victims as necessary to make room. 557 var evictedBytes int64 558 for _, vic := range victims { 559 w := vic.Value.(*WrappedTx) 560 561 txmp.logger.Debug( 562 "evicted valid existing transaction; mempool full", 563 "old_tx", fmt.Sprintf("%X", w.tx.Hash()), 564 "old_priority", w.priority, 565 ) 566 txmp.removeTxByElement(vic) 567 txmp.cache.Remove(w.tx) 568 txmp.metrics.EvictedTxs.Add(1) 569 570 // We may not need to evict all the eligible transactions. Bail out 571 // early if we have made enough room. 572 evictedBytes += w.Size() 573 if evictedBytes >= wtx.Size() { 574 break 575 } 576 } 577 } 578 579 wtx.SetGasWanted(checkTxRes.GasWanted) 580 wtx.SetPriority(priority) 581 wtx.SetSender(sender) 582 txmp.insertTx(wtx) 583 584 txmp.metrics.TxSizeBytes.Observe(float64(wtx.Size())) 585 txmp.metrics.Size.Set(float64(txmp.Size())) 586 txmp.logger.Debug( 587 "inserted new valid transaction", 588 "priority", wtx.Priority(), 589 "tx", fmt.Sprintf("%X", wtx.tx.Hash()), 590 "height", txmp.height, 591 "num_txs", txmp.Size(), 592 ) 593 txmp.notifyTxsAvailable() 594 } 595 596 func (txmp *TxMempool) insertTx(wtx *WrappedTx) { 597 elt := txmp.txs.PushBack(wtx) 598 txmp.txByKey[wtx.tx.Key()] = elt 599 if s := wtx.Sender(); s != "" { 600 txmp.txBySender[s] = elt 601 } 602 603 atomic.AddInt64(&txmp.txsBytes, wtx.Size()) 604 } 605 606 // handleRecheckResult handles the responses from ABCI CheckTx calls issued 607 // during the recheck phase of a block Update. It removes any transactions 608 // invalidated by the application. 609 // 610 // This method is NOT executed for the initial CheckTx on a new transaction; 611 // that case is handled by addNewTransaction instead. 612 func (txmp *TxMempool) handleRecheckResult(tx types.Tx, checkTxRes *abci.ResponseCheckTx) { 613 txmp.metrics.RecheckTimes.Add(1) 614 txmp.mtx.Lock() 615 defer txmp.mtx.Unlock() 616 617 // Find the transaction reported by the ABCI callback. It is possible the 618 // transaction was evicted during the recheck, in which case the transaction 619 // will be gone. 620 elt, ok := txmp.txByKey[tx.Key()] 621 if !ok { 622 return 623 } 624 wtx := elt.Value.(*WrappedTx) 625 626 // If a postcheck hook is defined, call it before checking the result. 627 var err error 628 if txmp.postCheck != nil { 629 err = txmp.postCheck(tx, checkTxRes) 630 } 631 632 if checkTxRes.Code == abci.CodeTypeOK && err == nil { 633 wtx.SetPriority(checkTxRes.Priority) 634 return // N.B. Size of mempool did not change 635 } 636 637 txmp.logger.Debug( 638 "existing transaction no longer valid; failed re-CheckTx callback", 639 "priority", wtx.Priority(), 640 "tx", fmt.Sprintf("%X", wtx.tx.Hash()), 641 "err", err, 642 "code", checkTxRes.Code, 643 ) 644 txmp.removeTxByElement(elt) 645 txmp.metrics.FailedTxs.Add(1) 646 if !txmp.config.KeepInvalidTxsInCache { 647 txmp.cache.Remove(wtx.tx) 648 } 649 txmp.metrics.Size.Set(float64(txmp.Size())) 650 } 651 652 // recheckTransactions initiates re-CheckTx ABCI calls for all the transactions 653 // currently in the mempool. It reports the number of recheck calls that were 654 // successfully initiated. 655 // 656 // Precondition: The mempool is not empty. 657 // The caller must hold txmp.mtx exclusively. 658 func (txmp *TxMempool) recheckTransactions() { 659 if txmp.Size() == 0 { 660 panic("mempool: cannot run recheck on an empty mempool") 661 } 662 txmp.logger.Debug( 663 "executing re-CheckTx for all remaining transactions", 664 "num_txs", txmp.Size(), 665 "height", txmp.height, 666 ) 667 668 // Collect transactions currently in the mempool requiring recheck. 669 wtxs := make([]*WrappedTx, 0, txmp.txs.Len()) 670 for e := txmp.txs.Front(); e != nil; e = e.Next() { 671 wtxs = append(wtxs, e.Value.(*WrappedTx)) 672 } 673 674 // Issue CheckTx calls for each remaining transaction, and when all the 675 // rechecks are complete signal watchers that transactions may be available. 676 go func() { 677 g, start := taskgroup.New(nil).Limit(2 * runtime.NumCPU()) 678 679 for _, wtx := range wtxs { 680 wtx := wtx 681 start(func() error { 682 // The response for this CheckTx is handled by the default recheckTxCallback. 683 rsp, err := txmp.proxyAppConn.CheckTxSync(abci.RequestCheckTx{ 684 Tx: wtx.tx, 685 Type: abci.CheckTxType_Recheck, 686 }) 687 if err != nil { 688 txmp.logger.Error("failed to execute CheckTx during recheck", 689 "err", err, "hash", fmt.Sprintf("%x", wtx.tx.Hash())) 690 } else { 691 txmp.handleRecheckResult(wtx.tx, rsp) 692 } 693 return nil 694 }) 695 } 696 _ = txmp.proxyAppConn.FlushAsync() 697 698 // When recheck is complete, trigger a notification for more transactions. 699 _ = g.Wait() 700 txmp.mtx.Lock() 701 defer txmp.mtx.Unlock() 702 txmp.notifyTxsAvailable() 703 }() 704 } 705 706 // canAddTx returns an error if we cannot insert the provided *WrappedTx into 707 // the mempool due to mempool configured constraints. Otherwise, nil is 708 // returned and the transaction can be inserted into the mempool. 709 func (txmp *TxMempool) canAddTx(wtx *WrappedTx) error { 710 numTxs := txmp.Size() 711 txBytes := txmp.SizeBytes() 712 713 if numTxs >= txmp.config.Size || wtx.Size()+txBytes > txmp.config.MaxTxsBytes { 714 return mempool.ErrMempoolIsFull{ 715 NumTxs: numTxs, 716 MaxTxs: txmp.config.Size, 717 TxsBytes: txBytes, 718 MaxTxsBytes: txmp.config.MaxTxsBytes, 719 } 720 } 721 722 return nil 723 } 724 725 // purgeExpiredTxs removes all transactions from the mempool that have exceeded 726 // their respective height or time-based limits as of the given blockHeight. 727 // Transactions removed by this operation are not removed from the cache. 728 // 729 // The caller must hold txmp.mtx exclusively. 730 func (txmp *TxMempool) purgeExpiredTxs(blockHeight int64) { 731 if txmp.config.TTLNumBlocks == 0 && txmp.config.TTLDuration == 0 { //nolint:staticcheck // SA1019 Priority mempool deprecated but still supported in this release. 732 return // nothing to do 733 } 734 735 now := time.Now() 736 cur := txmp.txs.Front() 737 for cur != nil { 738 // N.B. Grab the next element first, since if we remove cur its successor 739 // will be invalidated. 740 next := cur.Next() 741 742 w := cur.Value.(*WrappedTx) 743 if txmp.config.TTLNumBlocks > 0 && (blockHeight-w.height) > txmp.config.TTLNumBlocks { //nolint:staticcheck // SA1019 Priority mempool deprecated but still supported in this release. 744 txmp.removeTxByElement(cur) 745 txmp.cache.Remove(w.tx) 746 txmp.metrics.EvictedTxs.Add(1) 747 } else if txmp.config.TTLDuration > 0 && now.Sub(w.timestamp) > txmp.config.TTLDuration { //nolint:staticcheck // SA1019 Priority mempool deprecated but still supported in this release. 748 txmp.removeTxByElement(cur) 749 txmp.cache.Remove(w.tx) 750 txmp.metrics.EvictedTxs.Add(1) 751 } 752 cur = next 753 } 754 } 755 756 func (txmp *TxMempool) notifyTxsAvailable() { 757 if txmp.Size() == 0 { 758 return // nothing to do 759 } 760 761 if txmp.txsAvailable != nil && !txmp.notifiedTxsAvailable { 762 // channel cap is 1, so this will send once 763 txmp.notifiedTxsAvailable = true 764 765 select { 766 case txmp.txsAvailable <- struct{}{}: 767 default: 768 } 769 } 770 }