github.com/number571/tendermint@v0.34.11-gost/internal/mempool/v1/mempool.go (about) 1 package v1 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "sync/atomic" 8 "time" 9 10 abci "github.com/number571/tendermint/abci/types" 11 "github.com/number571/tendermint/config" 12 "github.com/number571/tendermint/internal/libs/clist" 13 tmsync "github.com/number571/tendermint/internal/libs/sync" 14 "github.com/number571/tendermint/internal/mempool" 15 "github.com/number571/tendermint/libs/log" 16 tmmath "github.com/number571/tendermint/libs/math" 17 pubmempool "github.com/number571/tendermint/pkg/mempool" 18 "github.com/number571/tendermint/proxy" 19 "github.com/number571/tendermint/types" 20 ) 21 22 var _ mempool.Mempool = (*TxMempool)(nil) 23 24 // TxMempoolOption sets an optional parameter on the TxMempool. 25 type TxMempoolOption func(*TxMempool) 26 27 // TxMempool defines a prioritized mempool data structure used by the v1 mempool 28 // reactor. It keeps a thread-safe priority queue of transactions that is used 29 // when a block proposer constructs a block and a thread-safe linked-list that 30 // is used to gossip transactions to peers in a FIFO manner. 31 type TxMempool struct { 32 logger log.Logger 33 metrics *mempool.Metrics 34 config *config.MempoolConfig 35 proxyAppConn proxy.AppConnMempool 36 37 // txsAvailable fires once for each height when the mempool is not empty 38 txsAvailable chan struct{} 39 notifiedTxsAvailable bool 40 41 // height defines the last block height process during Update() 42 height int64 43 44 // sizeBytes defines the total size of the mempool (sum of all tx bytes) 45 sizeBytes int64 46 47 // cache defines a fixed-size cache of already seen transactions as this 48 // reduces pressure on the proxyApp. 49 cache mempool.TxCache 50 51 // txStore defines the main storage of valid transactions. Indexes are built 52 // on top of this store. 53 txStore *TxStore 54 55 // gossipIndex defines the gossiping index of valid transactions via a 56 // thread-safe linked-list. We also use the gossip index as a cursor for 57 // rechecking transactions already in the mempool. 58 gossipIndex *clist.CList 59 60 // recheckCursor and recheckEnd are used as cursors based on the gossip index 61 // to recheck transactions that are already in the mempool. Iteration is not 62 // thread-safe and transaction may be mutated in serial order. 63 // 64 // XXX/TODO: It might be somewhat of a codesmell to use the gossip index for 65 // iterator and cursor management when rechecking transactions. If the gossip 66 // index changes or is removed in a future refactor, this will have to be 67 // refactored. Instead, we should consider just keeping a slice of a snapshot 68 // of the mempool's current transactions during Update and an integer cursor 69 // into that slice. This, however, requires additional O(n) space complexity. 70 recheckCursor *clist.CElement // next expected response 71 recheckEnd *clist.CElement // re-checking stops here 72 73 // priorityIndex defines the priority index of valid transactions via a 74 // thread-safe priority queue. 75 priorityIndex *TxPriorityQueue 76 77 // heightIndex defines a height-based, in ascending order, transaction index. 78 // i.e. older transactions are first. 79 heightIndex *WrappedTxList 80 81 // timestampIndex defines a timestamp-based, in ascending order, transaction 82 // index. i.e. older transactions are first. 83 timestampIndex *WrappedTxList 84 85 // A read/write lock is used to safe guard updates, insertions and deletions 86 // from the mempool. A read-lock is implicitly acquired when executing CheckTx, 87 // however, a caller must explicitly grab a write-lock via Lock when updating 88 // the mempool via Update(). 89 mtx tmsync.RWMutex 90 preCheck mempool.PreCheckFunc 91 postCheck mempool.PostCheckFunc 92 } 93 94 func NewTxMempool( 95 logger log.Logger, 96 cfg *config.MempoolConfig, 97 proxyAppConn proxy.AppConnMempool, 98 height int64, 99 options ...TxMempoolOption, 100 ) *TxMempool { 101 102 txmp := &TxMempool{ 103 logger: logger, 104 config: cfg, 105 proxyAppConn: proxyAppConn, 106 height: height, 107 cache: mempool.NopTxCache{}, 108 metrics: mempool.NopMetrics(), 109 txStore: NewTxStore(), 110 gossipIndex: clist.New(), 111 priorityIndex: NewTxPriorityQueue(), 112 heightIndex: NewWrappedTxList(func(wtx1, wtx2 *WrappedTx) bool { 113 return wtx1.height >= wtx2.height 114 }), 115 timestampIndex: NewWrappedTxList(func(wtx1, wtx2 *WrappedTx) bool { 116 return wtx1.timestamp.After(wtx2.timestamp) || wtx1.timestamp.Equal(wtx2.timestamp) 117 }), 118 } 119 120 if cfg.CacheSize > 0 { 121 txmp.cache = mempool.NewLRUTxCache(cfg.CacheSize) 122 } 123 124 proxyAppConn.SetResponseCallback(txmp.defaultTxCallback) 125 126 for _, opt := range options { 127 opt(txmp) 128 } 129 130 return txmp 131 } 132 133 // WithPreCheck sets a filter for the mempool to reject a transaction if f(tx) 134 // returns an error. This is executed before CheckTx. It only applies to the 135 // first created block. After that, Update() overwrites the existing value. 136 func WithPreCheck(f mempool.PreCheckFunc) TxMempoolOption { 137 return func(txmp *TxMempool) { txmp.preCheck = f } 138 } 139 140 // WithPostCheck sets a filter for the mempool to reject a transaction if 141 // f(tx, resp) returns an error. This is executed after CheckTx. It only applies 142 // to the first created block. After that, Update overwrites the existing value. 143 func WithPostCheck(f mempool.PostCheckFunc) TxMempoolOption { 144 return func(txmp *TxMempool) { txmp.postCheck = f } 145 } 146 147 // WithMetrics sets the mempool's metrics collector. 148 func WithMetrics(metrics *mempool.Metrics) TxMempoolOption { 149 return func(txmp *TxMempool) { txmp.metrics = metrics } 150 } 151 152 // Lock obtains a write-lock on the mempool. A caller must be sure to explicitly 153 // release the lock when finished. 154 func (txmp *TxMempool) Lock() { 155 txmp.mtx.Lock() 156 } 157 158 // Unlock releases a write-lock on the mempool. 159 func (txmp *TxMempool) Unlock() { 160 txmp.mtx.Unlock() 161 } 162 163 // Size returns the number of valid transactions in the mempool. It is 164 // thread-safe. 165 func (txmp *TxMempool) Size() int { 166 return txmp.txStore.Size() 167 } 168 169 // SizeBytes return the total sum in bytes of all the valid transactions in the 170 // mempool. It is thread-safe. 171 func (txmp *TxMempool) SizeBytes() int64 { 172 return atomic.LoadInt64(&txmp.sizeBytes) 173 } 174 175 // FlushAppConn executes FlushSync on the mempool's proxyAppConn. 176 // 177 // NOTE: The caller must obtain a write-lock via Lock() prior to execution. 178 func (txmp *TxMempool) FlushAppConn() error { 179 return txmp.proxyAppConn.FlushSync(context.Background()) 180 } 181 182 // WaitForNextTx returns a blocking channel that will be closed when the next 183 // valid transaction is available to gossip. It is thread-safe. 184 func (txmp *TxMempool) WaitForNextTx() <-chan struct{} { 185 return txmp.gossipIndex.WaitChan() 186 } 187 188 // NextGossipTx returns the next valid transaction to gossip. A caller must wait 189 // for WaitForNextTx to signal a transaction is available to gossip first. It is 190 // thread-safe. 191 func (txmp *TxMempool) NextGossipTx() *WrappedTx { 192 return txmp.gossipIndex.Front().Value.(*WrappedTx) 193 } 194 195 // EnableTxsAvailable enables the mempool to trigger events when transactions 196 // are available on a block by block basis. 197 func (txmp *TxMempool) EnableTxsAvailable() { 198 txmp.mtx.Lock() 199 defer txmp.mtx.Unlock() 200 201 txmp.txsAvailable = make(chan struct{}, 1) 202 } 203 204 // TxsAvailable returns a channel which fires once for every height, and only 205 // when transactions are available in the mempool. It is thread-safe. 206 func (txmp *TxMempool) TxsAvailable() <-chan struct{} { 207 return txmp.txsAvailable 208 } 209 210 // CheckTx executes the ABCI CheckTx method for a given transaction. It acquires 211 // a read-lock attempts to execute the application's CheckTx ABCI method via 212 // CheckTxAsync. We return an error if any of the following happen: 213 // 214 // - The CheckTxAsync execution fails. 215 // - The transaction already exists in the cache and we've already received the 216 // transaction from the peer. Otherwise, if it solely exists in the cache, we 217 // return nil. 218 // - The transaction size exceeds the maximum transaction size as defined by the 219 // configuration provided to the mempool. 220 // - The transaction fails Pre-Check (if it is defined). 221 // - The proxyAppConn fails, e.g. the buffer is full. 222 // 223 // If the mempool is full, we still execute CheckTx and attempt to find a lower 224 // priority transaction to evict. If such a transaction exists, we remove the 225 // lower priority transaction and add the new one with higher priority. 226 // 227 // NOTE: 228 // - The applications' CheckTx implementation may panic. 229 // - The caller is not to explicitly require any locks for executing CheckTx. 230 func (txmp *TxMempool) CheckTx( 231 ctx context.Context, 232 tx types.Tx, 233 cb func(*abci.Response), 234 txInfo mempool.TxInfo, 235 ) error { 236 237 txmp.mtx.RLock() 238 defer txmp.mtx.RUnlock() 239 240 txSize := len(tx) 241 if txSize > txmp.config.MaxTxBytes { 242 return pubmempool.ErrTxTooLarge{ 243 Max: txmp.config.MaxTxBytes, 244 Actual: txSize, 245 } 246 } 247 248 if txmp.preCheck != nil { 249 if err := txmp.preCheck(tx); err != nil { 250 return pubmempool.ErrPreCheck{ 251 Reason: err, 252 } 253 } 254 } 255 256 if err := txmp.proxyAppConn.Error(); err != nil { 257 return err 258 } 259 260 txHash := mempool.TxKey(tx) 261 262 // We add the transaction to the mempool's cache and if the transaction already 263 // exists, i.e. false is returned, then we check if we've seen this transaction 264 // from the same sender and error if we have. Otherwise, we return nil. 265 if !txmp.cache.Push(tx) { 266 wtx, ok := txmp.txStore.GetOrSetPeerByTxHash(txHash, txInfo.SenderID) 267 if wtx != nil && ok { 268 // We already have the transaction stored and the we've already seen this 269 // transaction from txInfo.SenderID. 270 return pubmempool.ErrTxInCache 271 } 272 273 txmp.logger.Debug("tx exists already in cache", "tx_hash", tx.Hash()) 274 return nil 275 } 276 277 if ctx == nil { 278 ctx = context.Background() 279 } 280 281 reqRes, err := txmp.proxyAppConn.CheckTxAsync(ctx, abci.RequestCheckTx{Tx: tx}) 282 if err != nil { 283 txmp.cache.Remove(tx) 284 return err 285 } 286 287 reqRes.SetCallback(func(res *abci.Response) { 288 if txmp.recheckCursor != nil { 289 panic("recheck cursor is non-nil in CheckTx callback") 290 } 291 292 wtx := &WrappedTx{ 293 tx: tx, 294 hash: txHash, 295 timestamp: time.Now().UTC(), 296 height: txmp.height, 297 } 298 txmp.initTxCallback(wtx, res, txInfo) 299 300 if cb != nil { 301 cb(res) 302 } 303 }) 304 305 return nil 306 } 307 308 // Flush flushes out the mempool. It acquires a read-lock, fetches all the 309 // transactions currently in the transaction store and removes each transaction 310 // from the store and all indexes and finally resets the cache. 311 // 312 // NOTE: 313 // - Flushing the mempool may leave the mempool in an inconsistent state. 314 func (txmp *TxMempool) Flush() { 315 txmp.mtx.RLock() 316 defer txmp.mtx.RUnlock() 317 318 txmp.heightIndex.Reset() 319 txmp.timestampIndex.Reset() 320 321 for _, wtx := range txmp.txStore.GetAllTxs() { 322 txmp.removeTx(wtx, false) 323 } 324 325 atomic.SwapInt64(&txmp.sizeBytes, 0) 326 txmp.cache.Reset() 327 } 328 329 // ReapMaxBytesMaxGas returns a list of transactions within the provided size 330 // and gas constraints. Transaction are retrieved in priority order. 331 // 332 // NOTE: 333 // - A read-lock is acquired. 334 // - Transactions returned are not actually removed from the mempool transaction 335 // store or indexes. 336 func (txmp *TxMempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs { 337 txmp.mtx.RLock() 338 defer txmp.mtx.RUnlock() 339 340 var ( 341 totalGas int64 342 totalSize int64 343 ) 344 345 // wTxs contains a list of *WrappedTx retrieved from the priority queue that 346 // need to be re-enqueued prior to returning. 347 wTxs := make([]*WrappedTx, 0, txmp.priorityIndex.NumTxs()) 348 defer func() { 349 for _, wtx := range wTxs { 350 txmp.priorityIndex.PushTx(wtx) 351 } 352 }() 353 354 txs := make([]types.Tx, 0, txmp.priorityIndex.NumTxs()) 355 for txmp.priorityIndex.NumTxs() > 0 { 356 wtx := txmp.priorityIndex.PopTx() 357 txs = append(txs, wtx.tx) 358 wTxs = append(wTxs, wtx) 359 size := types.ComputeProtoSizeForTxs([]types.Tx{wtx.tx}) 360 361 // Ensure we have capacity for the transaction with respect to the 362 // transaction size. 363 if maxBytes > -1 && totalSize+size > maxBytes { 364 return txs[:len(txs)-1] 365 } 366 367 totalSize += size 368 369 // ensure we have capacity for the transaction with respect to total gas 370 gas := totalGas + wtx.gasWanted 371 if maxGas > -1 && gas > maxGas { 372 return txs[:len(txs)-1] 373 } 374 375 totalGas = gas 376 } 377 378 return txs 379 } 380 381 // ReapMaxTxs returns a list of transactions within the provided number of 382 // transactions bound. Transaction are retrieved in priority order. 383 // 384 // NOTE: 385 // - A read-lock is acquired. 386 // - Transactions returned are not actually removed from the mempool transaction 387 // store or indexes. 388 func (txmp *TxMempool) ReapMaxTxs(max int) types.Txs { 389 txmp.mtx.RLock() 390 defer txmp.mtx.RUnlock() 391 392 numTxs := txmp.priorityIndex.NumTxs() 393 if max < 0 { 394 max = numTxs 395 } 396 397 cap := tmmath.MinInt(numTxs, max) 398 399 // wTxs contains a list of *WrappedTx retrieved from the priority queue that 400 // need to be re-enqueued prior to returning. 401 wTxs := make([]*WrappedTx, 0, cap) 402 defer func() { 403 for _, wtx := range wTxs { 404 txmp.priorityIndex.PushTx(wtx) 405 } 406 }() 407 408 txs := make([]types.Tx, 0, cap) 409 for txmp.priorityIndex.NumTxs() > 0 && len(txs) < max { 410 wtx := txmp.priorityIndex.PopTx() 411 txs = append(txs, wtx.tx) 412 wTxs = append(wTxs, wtx) 413 } 414 415 return txs 416 } 417 418 // Update iterates over all the transactions provided by the caller, i.e. the 419 // block producer, and removes them from the cache (if applicable) and removes 420 // the transactions from the main transaction store and associated indexes. 421 // Finally, if there are trainsactions remaining in the mempool, we initiate a 422 // re-CheckTx for them (if applicable), otherwise, we notify the caller more 423 // transactions are available. 424 // 425 // NOTE: 426 // - The caller must explicitly acquire a write-lock via Lock(). 427 func (txmp *TxMempool) Update( 428 blockHeight int64, 429 blockTxs types.Txs, 430 deliverTxResponses []*abci.ResponseDeliverTx, 431 newPreFn mempool.PreCheckFunc, 432 newPostFn mempool.PostCheckFunc, 433 ) error { 434 435 txmp.height = blockHeight 436 txmp.notifiedTxsAvailable = false 437 438 if newPreFn != nil { 439 txmp.preCheck = newPreFn 440 } 441 if newPostFn != nil { 442 txmp.postCheck = newPostFn 443 } 444 445 for i, tx := range blockTxs { 446 if deliverTxResponses[i].Code == abci.CodeTypeOK { 447 // add the valid committed transaction to the cache (if missing) 448 _ = txmp.cache.Push(tx) 449 } else if !txmp.config.KeepInvalidTxsInCache { 450 // allow invalid transactions to be re-submitted 451 txmp.cache.Remove(tx) 452 } 453 454 // remove the committed transaction from the transaction store and indexes 455 if wtx := txmp.txStore.GetTxByHash(mempool.TxKey(tx)); wtx != nil { 456 txmp.removeTx(wtx, false) 457 } 458 } 459 460 txmp.purgeExpiredTxs(blockHeight) 461 462 // If there any uncommitted transactions left in the mempool, we either 463 // initiate re-CheckTx per remaining transaction or notify that remaining 464 // transactions are left. 465 if txmp.Size() > 0 { 466 if txmp.config.Recheck { 467 txmp.logger.Debug( 468 "executing re-CheckTx for all remaining transactions", 469 "num_txs", txmp.Size(), 470 "height", blockHeight, 471 ) 472 txmp.updateReCheckTxs() 473 } else { 474 txmp.notifyTxsAvailable() 475 } 476 } 477 478 txmp.metrics.Size.Set(float64(txmp.Size())) 479 return nil 480 } 481 482 // initTxCallback performs the initial, i.e. the first, callback after CheckTx 483 // has been executed by the ABCI application. In other words, initTxCallback is 484 // called after executing CheckTx when we see a unique transaction for the first 485 // time. CheckTx can be called again for the same transaction at a later point 486 // in time when re-checking, however, this callback will not be called. 487 // 488 // After the ABCI application executes CheckTx, initTxCallback is called with 489 // the ABCI *Response object and TxInfo. If postCheck is defined on the mempool, 490 // we execute that first. If there is no error from postCheck (if defined) and 491 // the ABCI CheckTx response code is OK, we attempt to insert the transaction. 492 // 493 // When attempting to insert the transaction, we first check if there is 494 // sufficient capacity. If there is sufficient capacity, the transaction is 495 // inserted into the txStore and indexed across all indexes. Otherwise, if the 496 // mempool is full, we attempt to find a lower priority transaction to evict in 497 // place of the new incoming transaction. If no such transaction exists, the 498 // new incoming transaction is rejected. 499 // 500 // If the new incoming transaction fails CheckTx or postCheck fails, we reject 501 // the new incoming transaction. 502 // 503 // NOTE: 504 // - An explicit lock is NOT required. 505 func (txmp *TxMempool) initTxCallback(wtx *WrappedTx, res *abci.Response, txInfo mempool.TxInfo) { 506 checkTxRes, ok := res.Value.(*abci.Response_CheckTx) 507 if !ok { 508 return 509 } 510 511 var err error 512 if txmp.postCheck != nil { 513 err = txmp.postCheck(wtx.tx, checkTxRes.CheckTx) 514 } 515 516 if err != nil || checkTxRes.CheckTx.Code != abci.CodeTypeOK { 517 // ignore bad transactions 518 txmp.logger.Info( 519 "rejected bad transaction", 520 "priority", wtx.priority, 521 "tx", fmt.Sprintf("%X", wtx.tx.Hash()), 522 "peer_id", txInfo.SenderNodeID, 523 "code", checkTxRes.CheckTx.Code, 524 "post_check_err", err, 525 ) 526 527 txmp.metrics.FailedTxs.Add(1) 528 529 if !txmp.config.KeepInvalidTxsInCache { 530 txmp.cache.Remove(wtx.tx) 531 } 532 if err != nil { 533 checkTxRes.CheckTx.MempoolError = err.Error() 534 } 535 return 536 } 537 538 sender := checkTxRes.CheckTx.Sender 539 priority := checkTxRes.CheckTx.Priority 540 541 if len(sender) > 0 { 542 if wtx := txmp.txStore.GetTxBySender(sender); wtx != nil { 543 txmp.logger.Error( 544 "rejected incoming good transaction; tx already exists for sender", 545 "tx", fmt.Sprintf("%X", wtx.tx.Hash()), 546 "sender", sender, 547 ) 548 txmp.metrics.RejectedTxs.Add(1) 549 return 550 } 551 } 552 553 if err := txmp.canAddTx(wtx); err != nil { 554 evictTxs := txmp.priorityIndex.GetEvictableTxs( 555 priority, 556 int64(wtx.Size()), 557 txmp.SizeBytes(), 558 txmp.config.MaxTxsBytes, 559 ) 560 if len(evictTxs) == 0 { 561 // No room for the new incoming transaction so we just remove it from 562 // the cache. 563 txmp.cache.Remove(wtx.tx) 564 txmp.logger.Error( 565 "rejected incoming good transaction; mempool full", 566 "tx", fmt.Sprintf("%X", wtx.tx.Hash()), 567 "err", err.Error(), 568 ) 569 txmp.metrics.RejectedTxs.Add(1) 570 return 571 } 572 573 // evict an existing transaction(s) 574 // 575 // NOTE: 576 // - The transaction, toEvict, can be removed while a concurrent 577 // reCheckTx callback is being executed for the same transaction. 578 for _, toEvict := range evictTxs { 579 txmp.removeTx(toEvict, true) 580 txmp.logger.Debug( 581 "evicted existing good transaction; mempool full", 582 "old_tx", fmt.Sprintf("%X", toEvict.tx.Hash()), 583 "old_priority", toEvict.priority, 584 "new_tx", fmt.Sprintf("%X", wtx.tx.Hash()), 585 "new_priority", wtx.priority, 586 ) 587 txmp.metrics.EvictedTxs.Add(1) 588 } 589 } 590 591 wtx.gasWanted = checkTxRes.CheckTx.GasWanted 592 wtx.priority = priority 593 wtx.sender = sender 594 wtx.peers = map[uint16]struct{}{ 595 txInfo.SenderID: {}, 596 } 597 598 txmp.metrics.TxSizeBytes.Observe(float64(wtx.Size())) 599 txmp.metrics.Size.Set(float64(txmp.Size())) 600 601 txmp.insertTx(wtx) 602 txmp.logger.Debug( 603 "inserted good transaction", 604 "priority", wtx.priority, 605 "tx", fmt.Sprintf("%X", wtx.tx.Hash()), 606 "height", txmp.height, 607 "num_txs", txmp.Size(), 608 ) 609 txmp.notifyTxsAvailable() 610 611 } 612 613 // defaultTxCallback performs the default CheckTx application callback. This is 614 // NOT executed when a transaction is first seen/received. Instead, this callback 615 // is executed during re-checking transactions (if enabled). A caller, i.e a 616 // block proposer, acquires a mempool write-lock via Lock() and when executing 617 // Update(), if the mempool is non-empty and Recheck is enabled, then all 618 // remaining transactions will be rechecked via CheckTxAsync. The order in which 619 // they are rechecked must be the same order in which this callback is called 620 // per transaction. 621 func (txmp *TxMempool) defaultTxCallback(req *abci.Request, res *abci.Response) { 622 if txmp.recheckCursor == nil { 623 return 624 } 625 626 txmp.metrics.RecheckTimes.Add(1) 627 628 checkTxRes, ok := res.Value.(*abci.Response_CheckTx) 629 if ok { 630 tx := req.GetCheckTx().Tx 631 wtx := txmp.recheckCursor.Value.(*WrappedTx) 632 if !bytes.Equal(tx, wtx.tx) { 633 panic(fmt.Sprintf("re-CheckTx transaction mismatch; got: %X, expected: %X", wtx.tx.Hash(), mempool.TxKey(tx))) 634 } 635 636 // Only evaluate transactions that have not been removed. This can happen 637 // if an existing transaction is evicted during CheckTx and while this 638 // callback is being executed for the same evicted transaction. 639 if !txmp.txStore.IsTxRemoved(wtx.hash) { 640 var err error 641 if txmp.postCheck != nil { 642 err = txmp.postCheck(tx, checkTxRes.CheckTx) 643 } 644 645 if checkTxRes.CheckTx.Code == abci.CodeTypeOK && err == nil { 646 wtx.priority = checkTxRes.CheckTx.Priority 647 } else { 648 txmp.logger.Debug( 649 "existing transaction no longer valid; failed re-CheckTx callback", 650 "priority", wtx.priority, 651 "tx", fmt.Sprintf("%X", mempool.TxHashFromBytes(wtx.tx)), 652 "err", err, 653 "code", checkTxRes.CheckTx.Code, 654 ) 655 656 if wtx.gossipEl != txmp.recheckCursor { 657 panic("corrupted reCheckTx cursor") 658 } 659 660 txmp.removeTx(wtx, !txmp.config.KeepInvalidTxsInCache) 661 } 662 } 663 664 // move reCheckTx cursor to next element 665 if txmp.recheckCursor == txmp.recheckEnd { 666 txmp.recheckCursor = nil 667 } else { 668 txmp.recheckCursor = txmp.recheckCursor.Next() 669 } 670 671 if txmp.recheckCursor == nil { 672 txmp.logger.Debug("finished rechecking transactions") 673 674 if txmp.Size() > 0 { 675 txmp.notifyTxsAvailable() 676 } 677 } 678 679 txmp.metrics.Size.Set(float64(txmp.Size())) 680 } 681 } 682 683 // updateReCheckTxs updates the recheck cursors by using the gossipIndex. For 684 // each transaction, it executes CheckTxAsync. The global callback defined on 685 // the proxyAppConn will be executed for each transaction after CheckTx is 686 // executed. 687 // 688 // NOTE: 689 // - The caller must have a write-lock when executing updateReCheckTxs. 690 func (txmp *TxMempool) updateReCheckTxs() { 691 if txmp.Size() == 0 { 692 panic("attempted to update re-CheckTx txs when mempool is empty") 693 } 694 695 txmp.recheckCursor = txmp.gossipIndex.Front() 696 txmp.recheckEnd = txmp.gossipIndex.Back() 697 ctx := context.Background() 698 699 for e := txmp.gossipIndex.Front(); e != nil; e = e.Next() { 700 wtx := e.Value.(*WrappedTx) 701 702 // Only execute CheckTx if the transaction is not marked as removed which 703 // could happen if the transaction was evicted. 704 if !txmp.txStore.IsTxRemoved(wtx.hash) { 705 _, err := txmp.proxyAppConn.CheckTxAsync(ctx, abci.RequestCheckTx{ 706 Tx: wtx.tx, 707 Type: abci.CheckTxType_Recheck, 708 }) 709 if err != nil { 710 // no need in retrying since the tx will be rechecked after the next block 711 txmp.logger.Error("failed to execute CheckTx during rechecking", "err", err) 712 } 713 } 714 } 715 716 if _, err := txmp.proxyAppConn.FlushAsync(ctx); err != nil { 717 txmp.logger.Error("failed to flush transactions during rechecking", "err", err) 718 } 719 } 720 721 // canAddTx returns an error if we cannot insert the provided *WrappedTx into 722 // the mempool due to mempool configured constraints. Otherwise, nil is returned 723 // and the transaction can be inserted into the mempool. 724 func (txmp *TxMempool) canAddTx(wtx *WrappedTx) error { 725 var ( 726 numTxs = txmp.Size() 727 sizeBytes = txmp.SizeBytes() 728 ) 729 730 if numTxs >= txmp.config.Size || int64(wtx.Size())+sizeBytes > txmp.config.MaxTxsBytes { 731 return pubmempool.ErrMempoolIsFull{ 732 NumTxs: numTxs, 733 MaxTxs: txmp.config.Size, 734 TxsBytes: sizeBytes, 735 MaxTxsBytes: txmp.config.MaxTxsBytes, 736 } 737 } 738 739 return nil 740 } 741 742 func (txmp *TxMempool) insertTx(wtx *WrappedTx) { 743 txmp.txStore.SetTx(wtx) 744 txmp.priorityIndex.PushTx(wtx) 745 txmp.heightIndex.Insert(wtx) 746 txmp.timestampIndex.Insert(wtx) 747 748 // Insert the transaction into the gossip index and mark the reference to the 749 // linked-list element, which will be needed at a later point when the 750 // transaction is removed. 751 gossipEl := txmp.gossipIndex.PushBack(wtx) 752 wtx.gossipEl = gossipEl 753 754 atomic.AddInt64(&txmp.sizeBytes, int64(wtx.Size())) 755 } 756 757 func (txmp *TxMempool) removeTx(wtx *WrappedTx, removeFromCache bool) { 758 if txmp.txStore.IsTxRemoved(wtx.hash) { 759 return 760 } 761 762 txmp.txStore.RemoveTx(wtx) 763 txmp.priorityIndex.RemoveTx(wtx) 764 txmp.heightIndex.Remove(wtx) 765 txmp.timestampIndex.Remove(wtx) 766 767 // Remove the transaction from the gossip index and cleanup the linked-list 768 // element so it can be garbage collected. 769 txmp.gossipIndex.Remove(wtx.gossipEl) 770 wtx.gossipEl.DetachPrev() 771 772 atomic.AddInt64(&txmp.sizeBytes, int64(-wtx.Size())) 773 774 if removeFromCache { 775 txmp.cache.Remove(wtx.tx) 776 } 777 } 778 779 // purgeExpiredTxs removes all transactions that have exceeded their respective 780 // height and/or time based TTLs from their respective indexes. Every expired 781 // transaction will be removed from the mempool entirely, except for the cache. 782 // 783 // NOTE: purgeExpiredTxs must only be called during TxMempool#Update in which 784 // the caller has a write-lock on the mempool and so we can safely iterate over 785 // the height and time based indexes. 786 func (txmp *TxMempool) purgeExpiredTxs(blockHeight int64) { 787 now := time.Now() 788 expiredTxs := make(map[[mempool.TxKeySize]byte]*WrappedTx) 789 790 if txmp.config.TTLNumBlocks > 0 { 791 purgeIdx := -1 792 for i, wtx := range txmp.heightIndex.txs { 793 if (blockHeight - wtx.height) > txmp.config.TTLNumBlocks { 794 expiredTxs[mempool.TxKey(wtx.tx)] = wtx 795 purgeIdx = i 796 } else { 797 // since the index is sorted, we know no other txs can be be purged 798 break 799 } 800 } 801 802 if purgeIdx >= 0 { 803 txmp.heightIndex.txs = txmp.heightIndex.txs[purgeIdx+1:] 804 } 805 } 806 807 if txmp.config.TTLDuration > 0 { 808 purgeIdx := -1 809 for i, wtx := range txmp.timestampIndex.txs { 810 if now.Sub(wtx.timestamp) > txmp.config.TTLDuration { 811 expiredTxs[mempool.TxKey(wtx.tx)] = wtx 812 purgeIdx = i 813 } else { 814 // since the index is sorted, we know no other txs can be be purged 815 break 816 } 817 } 818 819 if purgeIdx >= 0 { 820 txmp.timestampIndex.txs = txmp.timestampIndex.txs[purgeIdx+1:] 821 } 822 } 823 824 for _, wtx := range expiredTxs { 825 txmp.removeTx(wtx, false) 826 } 827 } 828 829 func (txmp *TxMempool) notifyTxsAvailable() { 830 if txmp.Size() == 0 { 831 panic("attempt to notify txs available but mempool is empty!") 832 } 833 834 if txmp.txsAvailable != nil && !txmp.notifiedTxsAvailable { 835 // channel cap is 1, so this will send once 836 txmp.notifiedTxsAvailable = true 837 838 select { 839 case txmp.txsAvailable <- struct{}{}: 840 default: 841 } 842 } 843 }