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  }