github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/mempool/cat/pool.go (about) 1 package cat 2 3 import ( 4 "errors" 5 "fmt" 6 "runtime" 7 "sort" 8 "sync" 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/log" 16 "github.com/badrootd/celestia-core/mempool" 17 "github.com/badrootd/celestia-core/proxy" 18 "github.com/badrootd/celestia-core/types" 19 ) 20 21 // enforce compile-time satisfaction of the Mempool interface 22 var _ mempool.Mempool = (*TxPool)(nil) 23 24 var ( 25 ErrTxInMempool = errors.New("tx already exists in mempool") 26 ErrTxAlreadyRejected = errors.New("tx was previously rejected") 27 ) 28 29 // TxPoolOption sets an optional parameter on the TxPool. 30 type TxPoolOption func(*TxPool) 31 32 // TxPool implements the Mempool interface and allows the application to 33 // set priority values on transactions in the CheckTx response. When selecting 34 // transactions to include in a block, higher-priority transactions are chosen 35 // first. When evicting transactions from the mempool for size constraints, 36 // lower-priority transactions are evicted first. Transactions themselves are 37 // unordered (A map is used). They can be broadcast in an order different from 38 // the order to which transactions are entered. There is no guarantee when CheckTx 39 // passes that a transaction has been successfully broadcast to any of its peers. 40 // 41 // A TTL can be set to remove transactions after a period of time or a number 42 // of heights. 43 // 44 // A cache of rejectedTxs can be set in the mempool config. Transactions that 45 // are rejected because of `CheckTx` or other validity checks will be instantly 46 // rejected if they are seen again. Committed transactions are also added to 47 // this cache. This serves somewhat as replay protection but applications should 48 // implement something more comprehensive 49 type TxPool struct { 50 // Immutable fields 51 logger log.Logger 52 config *config.MempoolConfig 53 proxyAppConn proxy.AppConnMempool 54 metrics *mempool.Metrics 55 56 // these values are modified once per height 57 updateMtx sync.Mutex 58 notifiedTxsAvailable bool 59 txsAvailable chan struct{} // one value sent per height when mempool is not empty 60 preCheckFn mempool.PreCheckFunc 61 postCheckFn mempool.PostCheckFunc 62 height int64 // the latest height passed to Update 63 lastPurgeTime time.Time // the last time we attempted to purge transactions via the TTL 64 65 // Thread-safe cache of rejected transactions for quick look-up 66 rejectedTxCache *LRUTxCache 67 // Thread-safe list of transactions peers have seen that we have not yet seen 68 seenByPeersSet *SeenTxSet 69 70 // Store of wrapped transactions 71 store *store 72 73 // broadcastCh is an unbuffered channel of new transactions that need to 74 // be broadcasted to peers. Only populated if `broadcast` in the config is enabled 75 broadcastCh chan *wrappedTx 76 broadcastMtx sync.Mutex 77 txsToBeBroadcast []types.TxKey 78 } 79 80 // NewTxPool constructs a new, empty content addressable txpool at the specified 81 // initial height and using the given config and options. 82 func NewTxPool( 83 logger log.Logger, 84 cfg *config.MempoolConfig, 85 proxyAppConn proxy.AppConnMempool, 86 height int64, 87 options ...TxPoolOption, 88 ) *TxPool { 89 txmp := &TxPool{ 90 logger: logger, 91 config: cfg, 92 proxyAppConn: proxyAppConn, 93 metrics: mempool.NopMetrics(), 94 rejectedTxCache: NewLRUTxCache(cfg.CacheSize), 95 seenByPeersSet: NewSeenTxSet(), 96 height: height, 97 preCheckFn: func(_ types.Tx) error { return nil }, 98 postCheckFn: func(_ types.Tx, _ *abci.ResponseCheckTx) error { return nil }, 99 store: newStore(), 100 broadcastCh: make(chan *wrappedTx), 101 txsToBeBroadcast: make([]types.TxKey, 0), 102 } 103 104 for _, opt := range options { 105 opt(txmp) 106 } 107 108 return txmp 109 } 110 111 // WithPreCheck sets a filter for the mempool to reject a transaction if f(tx) 112 // returns an error. This is executed before CheckTx. It only applies to the 113 // first created block. After that, Update() overwrites the existing value. 114 func WithPreCheck(f mempool.PreCheckFunc) TxPoolOption { 115 return func(txmp *TxPool) { txmp.preCheckFn = f } 116 } 117 118 // WithPostCheck sets a filter for the mempool to reject a transaction if 119 // f(tx, resp) returns an error. This is executed after CheckTx. It only applies 120 // to the first created block. After that, Update overwrites the existing value. 121 func WithPostCheck(f mempool.PostCheckFunc) TxPoolOption { 122 return func(txmp *TxPool) { txmp.postCheckFn = f } 123 } 124 125 // WithMetrics sets the mempool's metrics collector. 126 func WithMetrics(metrics *mempool.Metrics) TxPoolOption { 127 return func(txmp *TxPool) { txmp.metrics = metrics } 128 } 129 130 // Lock is a noop as ABCI calls are serialized 131 func (txmp *TxPool) Lock() {} 132 133 // Unlock is a noop as ABCI calls are serialized 134 func (txmp *TxPool) Unlock() {} 135 136 // Size returns the number of valid transactions in the mempool. It is 137 // thread-safe. 138 func (txmp *TxPool) Size() int { return txmp.store.size() } 139 140 // SizeBytes returns the total sum in bytes of all the valid transactions in the 141 // mempool. It is thread-safe. 142 func (txmp *TxPool) SizeBytes() int64 { return txmp.store.totalBytes() } 143 144 // FlushAppConn executes FlushSync on the mempool's proxyAppConn. 145 // 146 // The caller must hold an exclusive mempool lock (by calling txmp.Lock) before 147 // calling FlushAppConn. 148 func (txmp *TxPool) FlushAppConn() error { 149 return txmp.proxyAppConn.FlushSync() 150 } 151 152 // EnableTxsAvailable enables the mempool to trigger events when transactions 153 // are available on a block by block basis. 154 func (txmp *TxPool) EnableTxsAvailable() { 155 txmp.txsAvailable = make(chan struct{}, 1) 156 } 157 158 // TxsAvailable returns a channel which fires once for every height, and only 159 // when transactions are available in the mempool. It is thread-safe. 160 func (txmp *TxPool) TxsAvailable() <-chan struct{} { return txmp.txsAvailable } 161 162 // Height returns the latest height that the mempool is at 163 func (txmp *TxPool) Height() int64 { 164 txmp.updateMtx.Lock() 165 defer txmp.updateMtx.Unlock() 166 return txmp.height 167 } 168 169 // Has returns true if the transaction is currently in the mempool 170 func (txmp *TxPool) Has(txKey types.TxKey) bool { 171 return txmp.store.has(txKey) 172 } 173 174 // Get retrieves a transaction based on the key. It returns a bool 175 // if the transaction exists or not 176 func (txmp *TxPool) Get(txKey types.TxKey) (types.Tx, bool) { 177 wtx := txmp.store.get(txKey) 178 if wtx != nil { 179 return wtx.tx, true 180 } 181 return types.Tx{}, false 182 } 183 184 // IsRejectedTx returns true if the transaction was recently rejected and is 185 // currently within the cache 186 func (txmp *TxPool) IsRejectedTx(txKey types.TxKey) bool { 187 return txmp.rejectedTxCache.Has(txKey) 188 } 189 190 // CheckToPurgeExpiredTxs checks if there has been adequate time since the last time 191 // the txpool looped through all transactions and if so, performs a purge of any transaction 192 // that has expired according to the TTLDuration. This is thread safe. 193 func (txmp *TxPool) CheckToPurgeExpiredTxs() { 194 txmp.updateMtx.Lock() 195 defer txmp.updateMtx.Unlock() 196 if txmp.config.TTLDuration > 0 && time.Since(txmp.lastPurgeTime) > txmp.config.TTLDuration { 197 expirationAge := time.Now().Add(-txmp.config.TTLDuration) 198 // a height of 0 means no transactions will be removed because of height 199 // (in other words, no transaction has a height less than 0) 200 numExpired := txmp.store.purgeExpiredTxs(0, expirationAge) 201 txmp.metrics.EvictedTxs.Add(float64(numExpired)) 202 txmp.lastPurgeTime = time.Now() 203 } 204 } 205 206 // CheckTx adds the given transaction to the mempool if it fits and passes the 207 // application's ABCI CheckTx method. This should be viewed as the entry method for new transactions 208 // into the network. In practice this happens via an RPC endpoint 209 func (txmp *TxPool) CheckTx(tx types.Tx, cb func(*abci.Response), txInfo mempool.TxInfo) error { 210 // Reject transactions in excess of the configured maximum transaction size. 211 if len(tx) > txmp.config.MaxTxBytes { 212 return mempool.ErrTxTooLarge{Max: txmp.config.MaxTxBytes, Actual: len(tx)} 213 } 214 215 // This is a new transaction that we haven't seen before. Verify it against the app and attempt 216 // to add it to the transaction pool. 217 key := tx.Key() 218 rsp, err := txmp.TryAddNewTx(tx, key, txInfo) 219 if err != nil { 220 return err 221 } 222 defer func() { 223 // call the callback if it is set 224 if cb != nil { 225 cb(&abci.Response{Value: &abci.Response_CheckTx{CheckTx: rsp}}) 226 } 227 }() 228 229 // push to the broadcast queue that a new transaction is ready 230 txmp.markToBeBroadcast(key) 231 return nil 232 } 233 234 // next is used by the reactor to get the next transaction to broadcast 235 // to all other peers. 236 func (txmp *TxPool) next() <-chan *wrappedTx { 237 txmp.broadcastMtx.Lock() 238 defer txmp.broadcastMtx.Unlock() 239 for len(txmp.txsToBeBroadcast) != 0 { 240 ch := make(chan *wrappedTx, 1) 241 key := txmp.txsToBeBroadcast[0] 242 txmp.txsToBeBroadcast = txmp.txsToBeBroadcast[1:] 243 wtx := txmp.store.get(key) 244 if wtx == nil { 245 continue 246 } 247 ch <- wtx 248 return ch 249 } 250 251 return txmp.broadcastCh 252 } 253 254 // markToBeBroadcast marks a transaction to be broadcasted to peers. 255 // This should never block so we use a map to create an unbounded queue 256 // of transactions that need to be gossiped. 257 func (txmp *TxPool) markToBeBroadcast(key types.TxKey) { 258 if !txmp.config.Broadcast { 259 return 260 } 261 262 wtx := txmp.store.get(key) 263 if wtx == nil { 264 return 265 } 266 267 select { 268 case txmp.broadcastCh <- wtx: 269 default: 270 txmp.broadcastMtx.Lock() 271 defer txmp.broadcastMtx.Unlock() 272 txmp.txsToBeBroadcast = append(txmp.txsToBeBroadcast, key) 273 } 274 } 275 276 // TryAddNewTx attempts to add a tx that has not already been seen before. It first marks it as seen 277 // to avoid races with the same tx. It then call `CheckTx` so that the application can validate it. 278 // If it passes `CheckTx`, the new transaction is added to the mempool as long as it has 279 // sufficient priority and space else if evicted it will return an error 280 func (txmp *TxPool) TryAddNewTx(tx types.Tx, key types.TxKey, txInfo mempool.TxInfo) (*abci.ResponseCheckTx, error) { 281 // First check any of the caches to see if we can conclude early. We may have already seen and processed 282 // the transaction if: 283 // - We are connected to nodes running v0 or v1 which simply flood the network 284 // - If a client submits a transaction to multiple nodes (via RPC) 285 // - We send multiple requests and the first peer eventually responds after the second peer has already provided the tx 286 if txmp.IsRejectedTx(key) { 287 // The peer has sent us a transaction that we have previously marked as invalid. Since `CheckTx` can 288 // be non-deterministic, we don't punish the peer but instead just ignore the tx 289 return nil, ErrTxAlreadyRejected 290 } 291 292 if txmp.Has(key) { 293 txmp.metrics.AlreadySeenTxs.Add(1) 294 // The peer has sent us a transaction that we have already seen 295 return nil, ErrTxInMempool 296 } 297 298 // reserve the key 299 if !txmp.store.reserve(key) { 300 txmp.logger.Debug("mempool already attempting to verify and add transaction", "txKey", fmt.Sprintf("%X", key)) 301 txmp.PeerHasTx(txInfo.SenderID, key) 302 return nil, ErrTxInMempool 303 } 304 defer txmp.store.release(key) 305 306 // If a precheck hook is defined, call it before invoking the application. 307 if err := txmp.preCheck(tx); err != nil { 308 txmp.metrics.FailedTxs.Add(1) 309 return nil, mempool.ErrPreCheck{Reason: err} 310 } 311 312 // Early exit if the proxy connection has an error. 313 if err := txmp.proxyAppConn.Error(); err != nil { 314 return nil, err 315 } 316 317 // Invoke an ABCI CheckTx for this transaction. 318 rsp, err := txmp.proxyAppConn.CheckTxSync(abci.RequestCheckTx{Tx: tx}) 319 if err != nil { 320 return rsp, err 321 } 322 if rsp.Code != abci.CodeTypeOK { 323 if txmp.config.KeepInvalidTxsInCache { 324 txmp.rejectedTxCache.Push(key) 325 } 326 txmp.metrics.FailedTxs.Add(1) 327 return rsp, fmt.Errorf("application rejected transaction with code %d (Log: %s)", rsp.Code, rsp.Log) 328 } 329 330 // Create wrapped tx 331 wtx := newWrappedTx( 332 tx, key, txmp.Height(), rsp.GasWanted, rsp.Priority, rsp.Sender, 333 ) 334 335 // Perform the post check 336 err = txmp.postCheck(wtx.tx, rsp) 337 if err != nil { 338 if txmp.config.KeepInvalidTxsInCache { 339 txmp.rejectedTxCache.Push(key) 340 } 341 txmp.metrics.FailedTxs.Add(1) 342 return rsp, fmt.Errorf("rejected bad transaction after post check: %w", err) 343 } 344 345 // Now we consider the transaction to be valid. Once a transaction is valid, it 346 // can only become invalid if recheckTx is enabled and RecheckTx returns a non zero code 347 if err := txmp.addNewTransaction(wtx, rsp); err != nil { 348 return nil, err 349 } 350 return rsp, nil 351 } 352 353 // RemoveTxByKey removes the transaction with the specified key from the 354 // mempool. It adds it to the rejectedTxCache so it will not be added again 355 func (txmp *TxPool) RemoveTxByKey(txKey types.TxKey) error { 356 txmp.removeTxByKey(txKey) 357 txmp.metrics.EvictedTxs.Add(1) 358 return nil 359 } 360 361 func (txmp *TxPool) removeTxByKey(txKey types.TxKey) { 362 txmp.rejectedTxCache.Push(txKey) 363 _ = txmp.store.remove(txKey) 364 txmp.seenByPeersSet.RemoveKey(txKey) 365 } 366 367 // Flush purges the contents of the mempool and the cache, leaving both empty. 368 // The current height is not modified by this operation. 369 func (txmp *TxPool) Flush() { 370 // Remove all the transactions in the list explicitly, so that the sizes 371 // and indexes get updated properly. 372 size := txmp.Size() 373 txmp.store.reset() 374 txmp.seenByPeersSet.Reset() 375 txmp.rejectedTxCache.Reset() 376 txmp.metrics.EvictedTxs.Add(float64(size)) 377 txmp.broadcastMtx.Lock() 378 defer txmp.broadcastMtx.Unlock() 379 txmp.txsToBeBroadcast = make([]types.TxKey, 0) 380 } 381 382 // PeerHasTx marks that the transaction has been seen by a peer. 383 func (txmp *TxPool) PeerHasTx(peer uint16, txKey types.TxKey) { 384 txmp.logger.Debug("peer has tx", "peer", peer, "txKey", fmt.Sprintf("%X", txKey)) 385 txmp.seenByPeersSet.Add(txKey, peer) 386 } 387 388 // allEntriesSorted returns a slice of all the transactions currently in the 389 // mempool, sorted in nonincreasing order by priority with ties broken by 390 // increasing order of arrival time. 391 func (txmp *TxPool) allEntriesSorted() []*wrappedTx { 392 txs := txmp.store.getAllTxs() 393 sort.Slice(txs, func(i, j int) bool { 394 if txs[i].priority == txs[j].priority { 395 return txs[i].timestamp.Before(txs[j].timestamp) 396 } 397 return txs[i].priority > txs[j].priority // N.B. higher priorities first 398 }) 399 return txs 400 } 401 402 // ReapMaxBytesMaxGas returns a slice of valid transactions that fit within the 403 // size and gas constraints. The results are ordered by nonincreasing priority, 404 // with ties broken by increasing order of arrival. Reaping transactions does 405 // not remove them from the mempool 406 // 407 // If maxBytes < 0, no limit is set on the total size in bytes. 408 // If maxGas < 0, no limit is set on the total gas cost. 409 // 410 // If the mempool is empty or has no transactions fitting within the given 411 // constraints, the result will also be empty. 412 func (txmp *TxPool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs { 413 var totalGas, totalBytes int64 414 415 var keep []types.Tx //nolint:prealloc 416 for _, w := range txmp.allEntriesSorted() { 417 // N.B. When computing byte size, we need to include the overhead for 418 // encoding as protobuf to send to the application. This actually overestimates it 419 // as we add the proto overhead to each transaction 420 txBytes := types.ComputeProtoSizeForTxs([]types.Tx{w.tx}) 421 if (maxGas >= 0 && totalGas+w.gasWanted > maxGas) || (maxBytes >= 0 && totalBytes+txBytes > maxBytes) { 422 continue 423 } 424 totalBytes += txBytes 425 totalGas += w.gasWanted 426 keep = append(keep, w.tx) 427 } 428 return keep 429 } 430 431 // ReapMaxTxs returns up to max transactions from the mempool. The results are 432 // ordered by nonincreasing priority with ties broken by increasing order of 433 // arrival. Reaping transactions does not remove them from the mempool. 434 // 435 // If max < 0, all transactions in the mempool are reaped. 436 // 437 // The result may have fewer than max elements (possibly zero) if the mempool 438 // does not have that many transactions available. 439 func (txmp *TxPool) ReapMaxTxs(max int) types.Txs { 440 var keep []types.Tx //nolint:prealloc 441 442 for _, w := range txmp.allEntriesSorted() { 443 if max >= 0 && len(keep) >= max { 444 break 445 } 446 keep = append(keep, w.tx) 447 } 448 return keep 449 } 450 451 // Update removes all the given transactions from the mempool and the cache, 452 // and updates the current block height. The blockTxs and deliverTxResponses 453 // must have the same length with each response corresponding to the tx at the 454 // same offset. 455 // 456 // If the configuration enables recheck, Update sends each remaining 457 // transaction after removing blockTxs to the ABCI CheckTx method. Any 458 // transactions marked as invalid during recheck are also removed. 459 // 460 // The caller must hold an exclusive mempool lock (by calling txmp.Lock) before 461 // calling Update. 462 func (txmp *TxPool) Update( 463 blockHeight int64, 464 blockTxs types.Txs, 465 deliverTxResponses []*abci.ResponseDeliverTx, 466 newPreFn mempool.PreCheckFunc, 467 newPostFn mempool.PostCheckFunc, 468 ) error { 469 // Safety check: Transactions and responses must match in number. 470 if len(blockTxs) != len(deliverTxResponses) { 471 panic(fmt.Sprintf("mempool: got %d transactions but %d DeliverTx responses", 472 len(blockTxs), len(deliverTxResponses))) 473 } 474 txmp.logger.Debug("updating mempool", "height", blockHeight, "txs", len(blockTxs)) 475 476 txmp.updateMtx.Lock() 477 txmp.height = blockHeight 478 txmp.notifiedTxsAvailable = false 479 480 if newPreFn != nil { 481 txmp.preCheckFn = newPreFn 482 } 483 if newPostFn != nil { 484 txmp.postCheckFn = newPostFn 485 } 486 txmp.lastPurgeTime = time.Now() 487 txmp.updateMtx.Unlock() 488 489 txmp.metrics.SuccessfulTxs.Add(float64(len(blockTxs))) 490 for _, tx := range blockTxs { 491 // Regardless of success, remove the transaction from the mempool. 492 txmp.removeTxByKey(tx.Key()) 493 } 494 495 txmp.purgeExpiredTxs(blockHeight) 496 497 // If there any uncommitted transactions left in the mempool, we either 498 // initiate re-CheckTx per remaining transaction or notify that remaining 499 // transactions are left. 500 size := txmp.Size() 501 txmp.metrics.Size.Set(float64(size)) 502 txmp.metrics.SizeBytes.Set(float64(txmp.SizeBytes())) 503 if size > 0 { 504 if txmp.config.Recheck { 505 txmp.recheckTransactions() 506 } else { 507 txmp.notifyTxsAvailable() 508 } 509 } 510 return nil 511 } 512 513 // addNewTransaction handles the ABCI CheckTx response for the first time a 514 // transaction is added to the mempool. A recheck after a block is committed 515 // goes to handleRecheckResult. 516 // 517 // If either the application rejected the transaction or a post-check hook is 518 // defined and rejects the transaction, it is discarded. 519 // 520 // Otherwise, if the mempool is full, check for lower-priority transactions 521 // that can be evicted to make room for the new one. If no such transactions 522 // exist, this transaction is logged and dropped; otherwise the selected 523 // transactions are evicted. 524 // 525 // Finally, the new transaction is added and size stats updated. 526 func (txmp *TxPool) addNewTransaction(wtx *wrappedTx, checkTxRes *abci.ResponseCheckTx) error { 527 // At this point the application has ruled the transaction valid, but the 528 // mempool might be full. If so, find the lowest-priority items with lower 529 // priority than the application assigned to this new one, and evict as many 530 // of them as necessary to make room for tx. If no such items exist, we 531 // discard tx. 532 if !txmp.canAddTx(wtx.size()) { 533 victims, victimBytes := txmp.store.getTxsBelowPriority(wtx.priority) 534 535 // If there are no suitable eviction candidates, or the total size of 536 // those candidates is not enough to make room for the new transaction, 537 // drop the new one. 538 if len(victims) == 0 || victimBytes < wtx.size() { 539 txmp.metrics.EvictedTxs.Add(1) 540 checkTxRes.MempoolError = fmt.Sprintf("rejected valid incoming transaction; mempool is full (%X)", 541 wtx.key) 542 return fmt.Errorf("rejected valid incoming transaction; mempool is full (%X). Size: (%d:%d)", 543 wtx.key.String(), txmp.Size(), txmp.SizeBytes()) 544 } 545 546 txmp.logger.Debug("evicting lower-priority transactions", 547 "new_tx", wtx.key.String(), 548 "new_priority", wtx.priority, 549 ) 550 551 // Sort lowest priority items first so they will be evicted first. Break 552 // ties in favor of newer items (to maintain FIFO semantics in a group). 553 sort.Slice(victims, func(i, j int) bool { 554 iw := victims[i] 555 jw := victims[j] 556 if iw.priority == jw.priority { 557 return iw.timestamp.After(jw.timestamp) 558 } 559 return iw.priority < jw.priority 560 }) 561 562 // Evict as many of the victims as necessary to make room. 563 availableBytes := txmp.availableBytes() 564 for _, tx := range victims { 565 txmp.evictTx(tx) 566 567 // We may not need to evict all the eligible transactions. Bail out 568 // early if we have made enough room. 569 availableBytes += tx.size() 570 if availableBytes >= wtx.size() { 571 break 572 } 573 } 574 } 575 576 txmp.store.set(wtx) 577 578 txmp.metrics.TxSizeBytes.Observe(float64(wtx.size())) 579 txmp.metrics.Size.Set(float64(txmp.Size())) 580 txmp.metrics.SizeBytes.Set(float64(txmp.SizeBytes())) 581 txmp.logger.Debug( 582 "inserted new valid transaction", 583 "priority", wtx.priority, 584 "tx", fmt.Sprintf("%X", wtx.key), 585 "height", wtx.height, 586 "num_txs", txmp.Size(), 587 ) 588 txmp.notifyTxsAvailable() 589 return nil 590 } 591 592 func (txmp *TxPool) evictTx(wtx *wrappedTx) { 593 txmp.store.remove(wtx.key) 594 txmp.metrics.EvictedTxs.Add(1) 595 txmp.logger.Debug( 596 "evicted valid existing transaction; mempool full", 597 "old_tx", fmt.Sprintf("%X", wtx.key), 598 "old_priority", wtx.priority, 599 ) 600 } 601 602 // handleRecheckResult handles the responses from ABCI CheckTx calls issued 603 // during the recheck phase of a block Update. It removes any transactions 604 // invalidated by the application. 605 // 606 // This method is NOT executed for the initial CheckTx on a new transaction; 607 // that case is handled by addNewTransaction instead. 608 func (txmp *TxPool) handleRecheckResult(wtx *wrappedTx, checkTxRes *abci.ResponseCheckTx) { 609 txmp.metrics.RecheckTimes.Add(1) 610 611 // If a postcheck hook is defined, call it before checking the result. 612 err := txmp.postCheck(wtx.tx, checkTxRes) 613 614 if checkTxRes.Code == abci.CodeTypeOK && err == nil { 615 // Note that we do not update the transaction with any of the values returned in 616 // recheck tx 617 return // N.B. Size of mempool did not change 618 } 619 620 txmp.logger.Debug( 621 "existing transaction no longer valid; failed re-CheckTx callback", 622 "priority", wtx.priority, 623 "tx", fmt.Sprintf("%X", wtx.key), 624 "err", err, 625 "code", checkTxRes.Code, 626 ) 627 txmp.store.remove(wtx.key) 628 if txmp.config.KeepInvalidTxsInCache { 629 txmp.rejectedTxCache.Push(wtx.key) 630 } 631 txmp.metrics.FailedTxs.Add(1) 632 txmp.metrics.Size.Set(float64(txmp.Size())) 633 txmp.metrics.SizeBytes.Set(float64(txmp.SizeBytes())) 634 } 635 636 // recheckTransactions initiates re-CheckTx ABCI calls for all the transactions 637 // currently in the mempool. It reports the number of recheck calls that were 638 // successfully initiated. 639 // 640 // Precondition: The mempool is not empty. 641 // The caller must hold txmp.mtx exclusively. 642 func (txmp *TxPool) recheckTransactions() { 643 if txmp.Size() == 0 { 644 panic("mempool: cannot run recheck on an empty mempool") 645 } 646 txmp.logger.Debug( 647 "executing re-CheckTx for all remaining transactions", 648 "num_txs", txmp.Size(), 649 "height", txmp.Height(), 650 ) 651 652 // Collect transactions currently in the mempool requiring recheck. 653 wtxs := txmp.store.getAllTxs() 654 655 // Issue CheckTx calls for each remaining transaction, and when all the 656 // rechecks are complete signal watchers that transactions may be available. 657 go func() { 658 g, start := taskgroup.New(nil).Limit(2 * runtime.NumCPU()) 659 660 for _, wtx := range wtxs { 661 wtx := wtx 662 start(func() error { 663 // The response for this CheckTx is handled by the default recheckTxCallback. 664 rsp, err := txmp.proxyAppConn.CheckTxSync(abci.RequestCheckTx{ 665 Tx: wtx.tx, 666 Type: abci.CheckTxType_Recheck, 667 }) 668 if err != nil { 669 txmp.logger.Error("failed to execute CheckTx during recheck", 670 "err", err, "key", fmt.Sprintf("%x", wtx.key)) 671 } else { 672 txmp.handleRecheckResult(wtx, rsp) 673 } 674 return nil 675 }) 676 } 677 _ = txmp.proxyAppConn.FlushAsync() 678 679 // When recheck is complete, trigger a notification for more transactions. 680 _ = g.Wait() 681 txmp.notifyTxsAvailable() 682 }() 683 } 684 685 // availableBytes returns the number of bytes available in the mempool. 686 func (txmp *TxPool) availableBytes() int64 { 687 return txmp.config.MaxTxsBytes - txmp.SizeBytes() 688 } 689 690 // canAddTx returns an error if we cannot insert the provided *wrappedTx into 691 // the mempool due to mempool configured constraints. Otherwise, nil is 692 // returned and the transaction can be inserted into the mempool. 693 func (txmp *TxPool) canAddTx(size int64) bool { 694 numTxs := txmp.Size() 695 txBytes := txmp.SizeBytes() 696 697 if numTxs > txmp.config.Size || size+txBytes > txmp.config.MaxTxsBytes { 698 return false 699 } 700 701 return true 702 } 703 704 // purgeExpiredTxs removes all transactions from the mempool that have exceeded 705 // their respective height or time-based limits as of the given blockHeight. 706 // Transactions removed by this operation are not removed from the rejectedTxCache. 707 func (txmp *TxPool) purgeExpiredTxs(blockHeight int64) { 708 if txmp.config.TTLNumBlocks == 0 && txmp.config.TTLDuration == 0 { 709 return // nothing to do 710 } 711 712 expirationHeight := blockHeight - txmp.config.TTLNumBlocks 713 if txmp.config.TTLNumBlocks == 0 { 714 expirationHeight = 0 715 } 716 717 now := time.Now() 718 expirationAge := now.Add(-txmp.config.TTLDuration) 719 if txmp.config.TTLDuration == 0 { 720 expirationAge = time.Time{} 721 } 722 723 numExpired := txmp.store.purgeExpiredTxs(expirationHeight, expirationAge) 724 txmp.metrics.EvictedTxs.Add(float64(numExpired)) 725 726 // purge old evicted and seen transactions 727 if txmp.config.TTLDuration == 0 { 728 // ensure that seenByPeersSet are eventually pruned 729 expirationAge = now.Add(-time.Hour) 730 } 731 txmp.seenByPeersSet.Prune(expirationAge) 732 } 733 734 func (txmp *TxPool) notifyTxsAvailable() { 735 if txmp.Size() == 0 { 736 return // nothing to do 737 } 738 739 if txmp.txsAvailable != nil && !txmp.notifiedTxsAvailable { 740 // channel cap is 1, so this will send once 741 txmp.notifiedTxsAvailable = true 742 743 select { 744 case txmp.txsAvailable <- struct{}{}: 745 default: 746 } 747 } 748 } 749 750 func (txmp *TxPool) preCheck(tx types.Tx) error { 751 txmp.updateMtx.Lock() 752 defer txmp.updateMtx.Unlock() 753 if txmp.preCheckFn != nil { 754 return txmp.preCheckFn(tx) 755 } 756 return nil 757 } 758 759 func (txmp *TxPool) postCheck(tx types.Tx, res *abci.ResponseCheckTx) error { 760 txmp.updateMtx.Lock() 761 defer txmp.updateMtx.Unlock() 762 if txmp.postCheckFn != nil { 763 return txmp.postCheckFn(tx, res) 764 } 765 return nil 766 }