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  }