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