github.com/aakash4dev/cometbft@v0.38.2/mempool/clist_mempool.go (about)

     1  package mempool
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"sync"
     8  	"sync/atomic"
     9  
    10  	abcicli "github.com/aakash4dev/cometbft/abci/client"
    11  	abci "github.com/aakash4dev/cometbft/abci/types"
    12  	"github.com/aakash4dev/cometbft/config"
    13  	"github.com/aakash4dev/cometbft/libs/clist"
    14  	"github.com/aakash4dev/cometbft/libs/log"
    15  	cmtmath "github.com/aakash4dev/cometbft/libs/math"
    16  	cmtsync "github.com/aakash4dev/cometbft/libs/sync"
    17  	"github.com/aakash4dev/cometbft/proxy"
    18  	"github.com/aakash4dev/cometbft/types"
    19  )
    20  
    21  // CListMempool is an ordered in-memory pool for transactions before they are
    22  // proposed in a consensus round. Transaction validity is checked using the
    23  // CheckTx abci message before the transaction is added to the pool. The
    24  // mempool uses a concurrent list structure for storing transactions that can
    25  // be efficiently accessed by multiple concurrent readers.
    26  type CListMempool struct {
    27  	// Atomic integers
    28  	height   int64 // the last block Update()'d to
    29  	txsBytes int64 // total size of mempool, in bytes
    30  
    31  	// notify listeners (ie. consensus) when txs are available
    32  	notifiedTxsAvailable bool
    33  	txsAvailable         chan struct{} // fires once for each height, when the mempool is not empty
    34  
    35  	// Function set by the reactor to be called when a transaction is removed
    36  	// from the mempool.
    37  	removeTxOnReactorCb func(txKey types.TxKey)
    38  
    39  	config *config.MempoolConfig
    40  
    41  	// Exclusive mutex for Update method to prevent concurrent execution of
    42  	// CheckTx or ReapMaxBytesMaxGas(ReapMaxTxs) methods.
    43  	updateMtx cmtsync.RWMutex
    44  	preCheck  PreCheckFunc
    45  	postCheck PostCheckFunc
    46  
    47  	proxyAppConn proxy.AppConnMempool
    48  
    49  	// Track whether we're rechecking txs.
    50  	// These are not protected by a mutex and are expected to be mutated in
    51  	// serial (ie. by abci responses which are called in serial).
    52  	recheckCursor *clist.CElement // next expected response
    53  	recheckEnd    *clist.CElement // re-checking stops here
    54  
    55  	// Concurrent linked-list of valid txs.
    56  	// `txsMap`: txKey -> CElement is for quick access to txs.
    57  	// Transactions in both `txs` and `txsMap` must to be kept in sync.
    58  	txs    *clist.CList
    59  	txsMap sync.Map
    60  
    61  	// Keep a cache of already-seen txs.
    62  	// This reduces the pressure on the proxyApp.
    63  	cache TxCache
    64  
    65  	logger  log.Logger
    66  	metrics *Metrics
    67  }
    68  
    69  var _ Mempool = &CListMempool{}
    70  
    71  // CListMempoolOption sets an optional parameter on the mempool.
    72  type CListMempoolOption func(*CListMempool)
    73  
    74  // NewCListMempool returns a new mempool with the given configuration and
    75  // connection to an application.
    76  func NewCListMempool(
    77  	cfg *config.MempoolConfig,
    78  	proxyAppConn proxy.AppConnMempool,
    79  	height int64,
    80  	options ...CListMempoolOption,
    81  ) *CListMempool {
    82  	mp := &CListMempool{
    83  		config:        cfg,
    84  		proxyAppConn:  proxyAppConn,
    85  		txs:           clist.New(),
    86  		height:        height,
    87  		recheckCursor: nil,
    88  		recheckEnd:    nil,
    89  		logger:        log.NewNopLogger(),
    90  		metrics:       NopMetrics(),
    91  	}
    92  
    93  	if cfg.CacheSize > 0 {
    94  		mp.cache = NewLRUTxCache(cfg.CacheSize)
    95  	} else {
    96  		mp.cache = NopTxCache{}
    97  	}
    98  
    99  	proxyAppConn.SetResponseCallback(mp.globalCb)
   100  
   101  	for _, option := range options {
   102  		option(mp)
   103  	}
   104  
   105  	return mp
   106  }
   107  
   108  func (mem *CListMempool) getCElement(txKey types.TxKey) (*clist.CElement, bool) {
   109  	if e, ok := mem.txsMap.Load(txKey); ok {
   110  		return e.(*clist.CElement), true
   111  	}
   112  	return nil, false
   113  }
   114  
   115  func (mem *CListMempool) InMempool(txKey types.TxKey) bool {
   116  	_, ok := mem.getCElement(txKey)
   117  	return ok
   118  }
   119  
   120  func (mem *CListMempool) addToCache(tx types.Tx) bool {
   121  	return mem.cache.Push(tx)
   122  }
   123  
   124  func (mem *CListMempool) forceRemoveFromCache(tx types.Tx) {
   125  	mem.cache.Remove(tx)
   126  }
   127  
   128  // tryRemoveFromCache removes a transaction from the cache in case it can be
   129  // added to the mempool at a later stage (probably when the transaction becomes
   130  // valid).
   131  func (mem *CListMempool) tryRemoveFromCache(tx types.Tx) {
   132  	if !mem.config.KeepInvalidTxsInCache {
   133  		mem.forceRemoveFromCache(tx)
   134  	}
   135  }
   136  
   137  func (mem *CListMempool) removeAllTxs() {
   138  	for e := mem.txs.Front(); e != nil; e = e.Next() {
   139  		mem.txs.Remove(e)
   140  		e.DetachPrev()
   141  	}
   142  
   143  	mem.txsMap.Range(func(key, _ interface{}) bool {
   144  		mem.txsMap.Delete(key)
   145  		mem.invokeRemoveTxOnReactor(key.(types.TxKey))
   146  		return true
   147  	})
   148  }
   149  
   150  // NOTE: not thread safe - should only be called once, on startup
   151  func (mem *CListMempool) EnableTxsAvailable() {
   152  	mem.txsAvailable = make(chan struct{}, 1)
   153  }
   154  
   155  func (mem *CListMempool) SetTxRemovedCallback(cb func(txKey types.TxKey)) {
   156  	mem.removeTxOnReactorCb = cb
   157  }
   158  
   159  func (mem *CListMempool) invokeRemoveTxOnReactor(txKey types.TxKey) {
   160  	// Note that the callback is nil in the unit tests, where there are no
   161  	// reactors.
   162  	if mem.removeTxOnReactorCb != nil {
   163  		mem.removeTxOnReactorCb(txKey)
   164  	}
   165  }
   166  
   167  // SetLogger sets the Logger.
   168  func (mem *CListMempool) SetLogger(l log.Logger) {
   169  	mem.logger = l
   170  }
   171  
   172  // WithPreCheck sets a filter for the mempool to reject a tx if f(tx) returns
   173  // false. This is ran before CheckTx. Only applies to the first created block.
   174  // After that, Update overwrites the existing value.
   175  func WithPreCheck(f PreCheckFunc) CListMempoolOption {
   176  	return func(mem *CListMempool) { mem.preCheck = f }
   177  }
   178  
   179  // WithPostCheck sets a filter for the mempool to reject a tx if f(tx) returns
   180  // false. This is ran after CheckTx. Only applies to the first created block.
   181  // After that, Update overwrites the existing value.
   182  func WithPostCheck(f PostCheckFunc) CListMempoolOption {
   183  	return func(mem *CListMempool) { mem.postCheck = f }
   184  }
   185  
   186  // WithMetrics sets the metrics.
   187  func WithMetrics(metrics *Metrics) CListMempoolOption {
   188  	return func(mem *CListMempool) { mem.metrics = metrics }
   189  }
   190  
   191  // Safe for concurrent use by multiple goroutines.
   192  func (mem *CListMempool) Lock() {
   193  	mem.updateMtx.Lock()
   194  }
   195  
   196  // Safe for concurrent use by multiple goroutines.
   197  func (mem *CListMempool) Unlock() {
   198  	mem.updateMtx.Unlock()
   199  }
   200  
   201  // Safe for concurrent use by multiple goroutines.
   202  func (mem *CListMempool) Size() int {
   203  	return mem.txs.Len()
   204  }
   205  
   206  // Safe for concurrent use by multiple goroutines.
   207  func (mem *CListMempool) SizeBytes() int64 {
   208  	return atomic.LoadInt64(&mem.txsBytes)
   209  }
   210  
   211  // Lock() must be help by the caller during execution.
   212  func (mem *CListMempool) FlushAppConn() error {
   213  	return mem.proxyAppConn.Flush(context.TODO())
   214  }
   215  
   216  // XXX: Unsafe! Calling Flush may leave mempool in inconsistent state.
   217  func (mem *CListMempool) Flush() {
   218  	mem.updateMtx.RLock()
   219  	defer mem.updateMtx.RUnlock()
   220  
   221  	_ = atomic.SwapInt64(&mem.txsBytes, 0)
   222  	mem.cache.Reset()
   223  
   224  	mem.removeAllTxs()
   225  }
   226  
   227  // TxsFront returns the first transaction in the ordered list for peer
   228  // goroutines to call .NextWait() on.
   229  // FIXME: leaking implementation details!
   230  //
   231  // Safe for concurrent use by multiple goroutines.
   232  func (mem *CListMempool) TxsFront() *clist.CElement {
   233  	return mem.txs.Front()
   234  }
   235  
   236  // TxsWaitChan returns a channel to wait on transactions. It will be closed
   237  // once the mempool is not empty (ie. the internal `mem.txs` has at least one
   238  // element)
   239  //
   240  // Safe for concurrent use by multiple goroutines.
   241  func (mem *CListMempool) TxsWaitChan() <-chan struct{} {
   242  	return mem.txs.WaitChan()
   243  }
   244  
   245  // It blocks if we're waiting on Update() or Reap().
   246  // Safe for concurrent use by multiple goroutines.
   247  func (mem *CListMempool) CheckTx(tx types.Tx) (*abcicli.ReqRes, error) {
   248  	mem.updateMtx.RLock()
   249  	// use defer to unlock mutex because application (*local client*) might panic
   250  	defer mem.updateMtx.RUnlock()
   251  
   252  	txSize := len(tx)
   253  
   254  	if err := mem.isFull(txSize); err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	if txSize > mem.config.MaxTxBytes {
   259  		return nil, ErrTxTooLarge{
   260  			Max:    mem.config.MaxTxBytes,
   261  			Actual: txSize,
   262  		}
   263  	}
   264  
   265  	if mem.preCheck != nil {
   266  		if err := mem.preCheck(tx); err != nil {
   267  			return nil, ErrPreCheck{
   268  				Reason: err,
   269  			}
   270  		}
   271  	}
   272  
   273  	// NOTE: proxyAppConn may error if tx buffer is full
   274  	if err := mem.proxyAppConn.Error(); err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	if added := mem.addToCache(tx); !added {
   279  		mem.metrics.AlreadyReceivedTxs.Add(1)
   280  		// TODO: consider punishing peer for dups,
   281  		// its non-trivial since invalid txs can become valid,
   282  		// but they can spam the same tx with little cost to them atm.
   283  		return nil, ErrTxInCache
   284  	}
   285  
   286  	reqRes, err := mem.proxyAppConn.CheckTxAsync(context.TODO(), &abci.RequestCheckTx{Tx: tx})
   287  	if err != nil {
   288  		mem.logger.Error("RequestCheckTx", "err", err)
   289  		return nil, err
   290  	}
   291  
   292  	return reqRes, nil
   293  }
   294  
   295  // Global callback that will be called after every ABCI response.
   296  func (mem *CListMempool) globalCb(req *abci.Request, res *abci.Response) {
   297  	switch res.Value.(type) {
   298  	case *abci.Response_CheckTx:
   299  		switch req.GetCheckTx().GetType() {
   300  		case abci.CheckTxType_New:
   301  			if mem.recheckCursor != nil {
   302  				// this should never happen
   303  				panic("recheck cursor is not nil before resCbFirstTime")
   304  			}
   305  			mem.resCbFirstTime(req.GetCheckTx().Tx, res)
   306  
   307  		case abci.CheckTxType_Recheck:
   308  			if mem.recheckCursor == nil {
   309  				return
   310  			}
   311  			mem.metrics.RecheckTimes.Add(1)
   312  			mem.resCbRecheck(req, res)
   313  		}
   314  
   315  		// update metrics
   316  		mem.metrics.Size.Set(float64(mem.Size()))
   317  
   318  	default:
   319  		// ignore other messages
   320  	}
   321  }
   322  
   323  // Called from:
   324  //   - resCbFirstTime (lock not held) if tx is valid
   325  func (mem *CListMempool) addTx(memTx *mempoolTx) {
   326  	e := mem.txs.PushBack(memTx)
   327  	mem.txsMap.Store(memTx.tx.Key(), e)
   328  	atomic.AddInt64(&mem.txsBytes, int64(len(memTx.tx)))
   329  	mem.metrics.TxSizeBytes.Observe(float64(len(memTx.tx)))
   330  }
   331  
   332  // RemoveTxByKey removes a transaction from the mempool by its TxKey index.
   333  // Called from:
   334  //   - Update (lock held) if tx was committed
   335  //   - resCbRecheck (lock not held) if tx was invalidated
   336  func (mem *CListMempool) RemoveTxByKey(txKey types.TxKey) error {
   337  	// The transaction should be removed from the reactor, even if it cannot be
   338  	// found in the mempool.
   339  	mem.invokeRemoveTxOnReactor(txKey)
   340  	if elem, ok := mem.getCElement(txKey); ok {
   341  		mem.txs.Remove(elem)
   342  		elem.DetachPrev()
   343  		mem.txsMap.Delete(txKey)
   344  		tx := elem.Value.(*mempoolTx).tx
   345  		atomic.AddInt64(&mem.txsBytes, int64(-len(tx)))
   346  		return nil
   347  	}
   348  	return errors.New("transaction not found in mempool")
   349  }
   350  
   351  func (mem *CListMempool) isFull(txSize int) error {
   352  	var (
   353  		memSize  = mem.Size()
   354  		txsBytes = mem.SizeBytes()
   355  	)
   356  
   357  	if memSize >= mem.config.Size || int64(txSize)+txsBytes > mem.config.MaxTxsBytes {
   358  		return ErrMempoolIsFull{
   359  			NumTxs:      memSize,
   360  			MaxTxs:      mem.config.Size,
   361  			TxsBytes:    txsBytes,
   362  			MaxTxsBytes: mem.config.MaxTxsBytes,
   363  		}
   364  	}
   365  
   366  	return nil
   367  }
   368  
   369  // callback, which is called after the app checked the tx for the first time.
   370  //
   371  // The case where the app checks the tx for the second and subsequent times is
   372  // handled by the resCbRecheck callback.
   373  func (mem *CListMempool) resCbFirstTime(
   374  	tx []byte,
   375  	res *abci.Response,
   376  ) {
   377  	switch r := res.Value.(type) {
   378  	case *abci.Response_CheckTx:
   379  		var postCheckErr error
   380  		if mem.postCheck != nil {
   381  			postCheckErr = mem.postCheck(tx, r.CheckTx)
   382  		}
   383  		txKey := types.Tx(tx).Key()
   384  		if (r.CheckTx.Code == abci.CodeTypeOK) && postCheckErr == nil {
   385  			// Check mempool isn't full again to reduce the chance of exceeding the
   386  			// limits.
   387  			if err := mem.isFull(len(tx)); err != nil {
   388  				mem.forceRemoveFromCache(tx) // mempool might have space later
   389  				mem.logger.Error(err.Error())
   390  				return
   391  			}
   392  
   393  			// Check transaction not already in the mempool
   394  			if mem.InMempool(txKey) {
   395  				mem.logger.Debug(
   396  					"transaction already there, not adding it again",
   397  					"tx", types.Tx(tx).Hash(),
   398  					"res", r,
   399  					"height", mem.height,
   400  					"total", mem.Size(),
   401  				)
   402  				return
   403  			}
   404  
   405  			mem.addTx(&mempoolTx{
   406  				height:    mem.height,
   407  				gasWanted: r.CheckTx.GasWanted,
   408  				tx:        tx,
   409  			})
   410  			mem.logger.Debug(
   411  				"added valid transaction",
   412  				"tx", types.Tx(tx).Hash(),
   413  				"res", r,
   414  				"height", mem.height,
   415  				"total", mem.Size(),
   416  			)
   417  			mem.notifyTxsAvailable()
   418  		} else {
   419  			mem.tryRemoveFromCache(tx)
   420  			mem.logger.Debug(
   421  				"rejected invalid transaction",
   422  				"tx", types.Tx(tx).Hash(),
   423  				"res", r,
   424  				"err", postCheckErr,
   425  			)
   426  			mem.metrics.FailedTxs.Add(1)
   427  		}
   428  
   429  	default:
   430  		// ignore other messages
   431  	}
   432  }
   433  
   434  // callback, which is called after the app rechecked the tx.
   435  //
   436  // The case where the app checks the tx for the first time is handled by the
   437  // resCbFirstTime callback.
   438  func (mem *CListMempool) resCbRecheck(req *abci.Request, res *abci.Response) {
   439  	switch r := res.Value.(type) {
   440  	case *abci.Response_CheckTx:
   441  		tx := req.GetCheckTx().Tx
   442  		memTx := mem.recheckCursor.Value.(*mempoolTx)
   443  
   444  		// Search through the remaining list of tx to recheck for a transaction that matches
   445  		// the one we received from the ABCI application.
   446  		for {
   447  			if bytes.Equal(tx, memTx.tx) {
   448  				// We've found a tx in the recheck list that matches the tx that we
   449  				// received from the ABCI application.
   450  				// Break, and use this transaction for further checks.
   451  				break
   452  			}
   453  
   454  			mem.logger.Error(
   455  				"re-CheckTx transaction mismatch",
   456  				"got", types.Tx(tx),
   457  				"expected", memTx.tx,
   458  			)
   459  
   460  			if mem.recheckCursor == mem.recheckEnd {
   461  				// we reached the end of the recheckTx list without finding a tx
   462  				// matching the one we received from the ABCI application.
   463  				// Return without processing any tx.
   464  				mem.recheckCursor = nil
   465  				return
   466  			}
   467  
   468  			mem.recheckCursor = mem.recheckCursor.Next()
   469  			memTx = mem.recheckCursor.Value.(*mempoolTx)
   470  		}
   471  
   472  		var postCheckErr error
   473  		if mem.postCheck != nil {
   474  			postCheckErr = mem.postCheck(tx, r.CheckTx)
   475  		}
   476  
   477  		if (r.CheckTx.Code != abci.CodeTypeOK) || postCheckErr != nil {
   478  			// Tx became invalidated due to newly committed block.
   479  			mem.logger.Debug("tx is no longer valid", "tx", types.Tx(tx).Hash(), "res", r, "err", postCheckErr)
   480  			if err := mem.RemoveTxByKey(memTx.tx.Key()); err != nil {
   481  				mem.logger.Debug("Transaction could not be removed from mempool", "err", err)
   482  			}
   483  			mem.tryRemoveFromCache(tx)
   484  		}
   485  		if mem.recheckCursor == mem.recheckEnd {
   486  			mem.recheckCursor = nil
   487  		} else {
   488  			mem.recheckCursor = mem.recheckCursor.Next()
   489  		}
   490  		if mem.recheckCursor == nil {
   491  			// Done!
   492  			mem.logger.Debug("done rechecking txs")
   493  
   494  			// incase the recheck removed all txs
   495  			if mem.Size() > 0 {
   496  				mem.notifyTxsAvailable()
   497  			}
   498  		}
   499  	default:
   500  		// ignore other messages
   501  	}
   502  }
   503  
   504  // Safe for concurrent use by multiple goroutines.
   505  func (mem *CListMempool) TxsAvailable() <-chan struct{} {
   506  	return mem.txsAvailable
   507  }
   508  
   509  func (mem *CListMempool) notifyTxsAvailable() {
   510  	if mem.Size() == 0 {
   511  		panic("notified txs available but mempool is empty!")
   512  	}
   513  	if mem.txsAvailable != nil && !mem.notifiedTxsAvailable {
   514  		// channel cap is 1, so this will send once
   515  		mem.notifiedTxsAvailable = true
   516  		select {
   517  		case mem.txsAvailable <- struct{}{}:
   518  		default:
   519  		}
   520  	}
   521  }
   522  
   523  // Safe for concurrent use by multiple goroutines.
   524  func (mem *CListMempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs {
   525  	mem.updateMtx.RLock()
   526  	defer mem.updateMtx.RUnlock()
   527  
   528  	var (
   529  		totalGas    int64
   530  		runningSize int64
   531  	)
   532  
   533  	// TODO: we will get a performance boost if we have a good estimate of avg
   534  	// size per tx, and set the initial capacity based off of that.
   535  	// txs := make([]types.Tx, 0, cmtmath.MinInt(mem.txs.Len(), max/mem.avgTxSize))
   536  	txs := make([]types.Tx, 0, mem.txs.Len())
   537  	for e := mem.txs.Front(); e != nil; e = e.Next() {
   538  		memTx := e.Value.(*mempoolTx)
   539  
   540  		txs = append(txs, memTx.tx)
   541  
   542  		dataSize := types.ComputeProtoSizeForTxs([]types.Tx{memTx.tx})
   543  
   544  		// Check total size requirement
   545  		if maxBytes > -1 && runningSize+dataSize > maxBytes {
   546  			return txs[:len(txs)-1]
   547  		}
   548  
   549  		runningSize += dataSize
   550  
   551  		// Check total gas requirement.
   552  		// If maxGas is negative, skip this check.
   553  		// Since newTotalGas < masGas, which
   554  		// must be non-negative, it follows that this won't overflow.
   555  		newTotalGas := totalGas + memTx.gasWanted
   556  		if maxGas > -1 && newTotalGas > maxGas {
   557  			return txs[:len(txs)-1]
   558  		}
   559  		totalGas = newTotalGas
   560  	}
   561  	return txs
   562  }
   563  
   564  // Safe for concurrent use by multiple goroutines.
   565  func (mem *CListMempool) ReapMaxTxs(max int) types.Txs {
   566  	mem.updateMtx.RLock()
   567  	defer mem.updateMtx.RUnlock()
   568  
   569  	if max < 0 {
   570  		max = mem.txs.Len()
   571  	}
   572  
   573  	txs := make([]types.Tx, 0, cmtmath.MinInt(mem.txs.Len(), max))
   574  	for e := mem.txs.Front(); e != nil && len(txs) <= max; e = e.Next() {
   575  		memTx := e.Value.(*mempoolTx)
   576  		txs = append(txs, memTx.tx)
   577  	}
   578  	return txs
   579  }
   580  
   581  // Lock() must be help by the caller during execution.
   582  // TODO: this function always returns nil; remove the return value
   583  func (mem *CListMempool) Update(
   584  	height int64,
   585  	txs types.Txs,
   586  	txResults []*abci.ExecTxResult,
   587  	preCheck PreCheckFunc,
   588  	postCheck PostCheckFunc,
   589  ) error {
   590  	// Set height
   591  	mem.height = height
   592  	mem.notifiedTxsAvailable = false
   593  
   594  	if preCheck != nil {
   595  		mem.preCheck = preCheck
   596  	}
   597  	if postCheck != nil {
   598  		mem.postCheck = postCheck
   599  	}
   600  
   601  	for i, tx := range txs {
   602  		if txResults[i].Code == abci.CodeTypeOK {
   603  			// Add valid committed tx to the cache (if missing).
   604  			_ = mem.addToCache(tx)
   605  		} else {
   606  			mem.tryRemoveFromCache(tx)
   607  		}
   608  
   609  		// Remove committed tx from the mempool.
   610  		//
   611  		// Note an evil proposer can drop valid txs!
   612  		// Mempool before:
   613  		//   100 -> 101 -> 102
   614  		// Block, proposed by an evil proposer:
   615  		//   101 -> 102
   616  		// Mempool after:
   617  		//   100
   618  		// https://github.com/tendermint/tendermint/issues/3322.
   619  		if err := mem.RemoveTxByKey(tx.Key()); err != nil {
   620  			mem.logger.Debug("Committed transaction not in local mempool (not an error)",
   621  				"key", tx.Key(),
   622  				"error", err.Error())
   623  		}
   624  	}
   625  
   626  	// Either recheck non-committed txs to see if they became invalid
   627  	// or just notify there're some txs left.
   628  	if mem.Size() > 0 {
   629  		if mem.config.Recheck {
   630  			mem.logger.Debug("recheck txs", "numtxs", mem.Size(), "height", height)
   631  			mem.recheckTxs()
   632  			// At this point, mem.txs are being rechecked.
   633  			// mem.recheckCursor re-scans mem.txs and possibly removes some txs.
   634  			// Before mem.Reap(), we should wait for mem.recheckCursor to be nil.
   635  		} else {
   636  			mem.notifyTxsAvailable()
   637  		}
   638  	}
   639  
   640  	// Update metrics
   641  	mem.metrics.Size.Set(float64(mem.Size()))
   642  
   643  	return nil
   644  }
   645  
   646  func (mem *CListMempool) recheckTxs() {
   647  	if mem.Size() == 0 {
   648  		panic("recheckTxs is called, but the mempool is empty")
   649  	}
   650  
   651  	mem.recheckCursor = mem.txs.Front()
   652  	mem.recheckEnd = mem.txs.Back()
   653  
   654  	// Push txs to proxyAppConn
   655  	// NOTE: globalCb may be called concurrently.
   656  	for e := mem.txs.Front(); e != nil; e = e.Next() {
   657  		memTx := e.Value.(*mempoolTx)
   658  		_, err := mem.proxyAppConn.CheckTxAsync(context.TODO(), &abci.RequestCheckTx{
   659  			Tx:   memTx.tx,
   660  			Type: abci.CheckTxType_Recheck,
   661  		})
   662  		if err != nil {
   663  			mem.logger.Error("recheckTx", err, "err")
   664  			return
   665  		}
   666  	}
   667  
   668  	// In <v0.37 we would call FlushAsync at the end of recheckTx forcing the buffer to flush
   669  	// all pending messages to the app. There doesn't seem to be any need here as the buffer
   670  	// will get flushed regularly or when filled.
   671  }