github.com/cosmos/cosmos-sdk@v0.50.10/types/mempool/priority_nonce.go (about)

     1  package mempool
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"sync"
     8  
     9  	"github.com/huandu/skiplist"
    10  
    11  	sdk "github.com/cosmos/cosmos-sdk/types"
    12  )
    13  
    14  var (
    15  	_ ExtMempool = (*PriorityNonceMempool[int64])(nil)
    16  	_ Iterator   = (*PriorityNonceIterator[int64])(nil)
    17  )
    18  
    19  type (
    20  	// PriorityNonceMempoolConfig defines the configuration used to configure the
    21  	// PriorityNonceMempool.
    22  	PriorityNonceMempoolConfig[C comparable] struct {
    23  		// TxPriority defines the transaction priority and comparator.
    24  		TxPriority TxPriority[C]
    25  
    26  		// OnRead is a callback to be called when a tx is read from the mempool.
    27  		OnRead func(tx sdk.Tx)
    28  
    29  		// TxReplacement is a callback to be called when duplicated transaction nonce
    30  		// detected during mempool insert. An application can define a transaction
    31  		// replacement rule based on tx priority or certain transaction fields.
    32  		TxReplacement func(op, np C, oTx, nTx sdk.Tx) bool
    33  
    34  		// MaxTx sets the maximum number of transactions allowed in the mempool with
    35  		// the semantics:
    36  		// - if MaxTx == 0, there is no cap on the number of transactions in the mempool
    37  		// - if MaxTx > 0, the mempool will cap the number of transactions it stores,
    38  		//   and will prioritize transactions by their priority and sender-nonce
    39  		//   (sequence number) when evicting transactions.
    40  		// - if MaxTx < 0, `Insert` is a no-op.
    41  		MaxTx int
    42  
    43  		// SignerExtractor is an implementation which retrieves signer data from a sdk.Tx
    44  		SignerExtractor SignerExtractionAdapter
    45  	}
    46  
    47  	// PriorityNonceMempool is a mempool implementation that stores txs
    48  	// in a partially ordered set by 2 dimensions: priority, and sender-nonce
    49  	// (sequence number). Internally it uses one priority ordered skip list and one
    50  	// skip list per sender ordered by sender-nonce (sequence number). When there
    51  	// are multiple txs from the same sender, they are not always comparable by
    52  	// priority to other sender txs and must be partially ordered by both sender-nonce
    53  	// and priority.
    54  	PriorityNonceMempool[C comparable] struct {
    55  		mtx            sync.Mutex
    56  		priorityIndex  *skiplist.SkipList
    57  		priorityCounts map[C]int
    58  		senderIndices  map[string]*skiplist.SkipList
    59  		scores         map[txMeta[C]]txMeta[C]
    60  		cfg            PriorityNonceMempoolConfig[C]
    61  	}
    62  
    63  	// PriorityNonceIterator defines an iterator that is used for mempool iteration
    64  	// on Select().
    65  	PriorityNonceIterator[C comparable] struct {
    66  		mempool       *PriorityNonceMempool[C]
    67  		priorityNode  *skiplist.Element
    68  		senderCursors map[string]*skiplist.Element
    69  		sender        string
    70  		nextPriority  C
    71  	}
    72  
    73  	// TxPriority defines a type that is used to retrieve and compare transaction
    74  	// priorities. Priorities must be comparable.
    75  	TxPriority[C comparable] struct {
    76  		// GetTxPriority returns the priority of the transaction. A priority must be
    77  		// comparable via Compare.
    78  		GetTxPriority func(ctx context.Context, tx sdk.Tx) C
    79  
    80  		// Compare compares two transaction priorities. The result must be 0 if
    81  		// a == b, -1 if a < b, and +1 if a > b.
    82  		Compare func(a, b C) int
    83  
    84  		// MinValue defines the minimum priority value, e.g. MinInt64. This value is
    85  		// used when instantiating a new iterator and comparing weights.
    86  		MinValue C
    87  	}
    88  
    89  	// txMeta stores transaction metadata used in indices
    90  	txMeta[C comparable] struct {
    91  		// nonce is the sender's sequence number
    92  		nonce uint64
    93  		// priority is the transaction's priority
    94  		priority C
    95  		// sender is the transaction's sender
    96  		sender string
    97  		// weight is the transaction's weight, used as a tiebreaker for transactions
    98  		// with the same priority
    99  		weight C
   100  		// senderElement is a pointer to the transaction's element in the sender index
   101  		senderElement *skiplist.Element
   102  	}
   103  )
   104  
   105  // NewDefaultTxPriority returns a TxPriority comparator using ctx.Priority as
   106  // the defining transaction priority.
   107  func NewDefaultTxPriority() TxPriority[int64] {
   108  	return TxPriority[int64]{
   109  		GetTxPriority: func(goCtx context.Context, _ sdk.Tx) int64 {
   110  			return sdk.UnwrapSDKContext(goCtx).Priority()
   111  		},
   112  		Compare: func(a, b int64) int {
   113  			return skiplist.Int64.Compare(a, b)
   114  		},
   115  		MinValue: math.MinInt64,
   116  	}
   117  }
   118  
   119  func DefaultPriorityNonceMempoolConfig() PriorityNonceMempoolConfig[int64] {
   120  	return PriorityNonceMempoolConfig[int64]{
   121  		TxPriority:      NewDefaultTxPriority(),
   122  		SignerExtractor: NewDefaultSignerExtractionAdapter(),
   123  	}
   124  }
   125  
   126  // skiplistComparable is a comparator for txKeys that first compares priority,
   127  // then weight, then sender, then nonce, uniquely identifying a transaction.
   128  //
   129  // Note, skiplistComparable is used as the comparator in the priority index.
   130  func skiplistComparable[C comparable](txPriority TxPriority[C]) skiplist.Comparable {
   131  	return skiplist.LessThanFunc(func(a, b any) int {
   132  		keyA := a.(txMeta[C])
   133  		keyB := b.(txMeta[C])
   134  
   135  		res := txPriority.Compare(keyA.priority, keyB.priority)
   136  		if res != 0 {
   137  			return res
   138  		}
   139  
   140  		// Weight is used as a tiebreaker for transactions with the same priority.
   141  		// Weight is calculated in a single pass in .Select(...) and so will be 0
   142  		// on .Insert(...).
   143  		res = txPriority.Compare(keyA.weight, keyB.weight)
   144  		if res != 0 {
   145  			return res
   146  		}
   147  
   148  		// Because weight will be 0 on .Insert(...), we must also compare sender and
   149  		// nonce to resolve priority collisions. If we didn't then transactions with
   150  		// the same priority would overwrite each other in the priority index.
   151  		res = skiplist.String.Compare(keyA.sender, keyB.sender)
   152  		if res != 0 {
   153  			return res
   154  		}
   155  
   156  		return skiplist.Uint64.Compare(keyA.nonce, keyB.nonce)
   157  	})
   158  }
   159  
   160  // NewPriorityMempool returns the SDK's default mempool implementation which
   161  // returns txs in a partial order by 2 dimensions; priority, and sender-nonce.
   162  func NewPriorityMempool[C comparable](cfg PriorityNonceMempoolConfig[C]) *PriorityNonceMempool[C] {
   163  	if cfg.SignerExtractor == nil {
   164  		cfg.SignerExtractor = NewDefaultSignerExtractionAdapter()
   165  	}
   166  	mp := &PriorityNonceMempool[C]{
   167  		priorityIndex:  skiplist.New(skiplistComparable(cfg.TxPriority)),
   168  		priorityCounts: make(map[C]int),
   169  		senderIndices:  make(map[string]*skiplist.SkipList),
   170  		scores:         make(map[txMeta[C]]txMeta[C]),
   171  		cfg:            cfg,
   172  	}
   173  
   174  	return mp
   175  }
   176  
   177  // DefaultPriorityMempool returns a priorityNonceMempool with no options.
   178  func DefaultPriorityMempool() *PriorityNonceMempool[int64] {
   179  	return NewPriorityMempool(DefaultPriorityNonceMempoolConfig())
   180  }
   181  
   182  // NextSenderTx returns the next transaction for a given sender by nonce order,
   183  // i.e. the next valid transaction for the sender. If no such transaction exists,
   184  // nil will be returned.
   185  func (mp *PriorityNonceMempool[C]) NextSenderTx(sender string) sdk.Tx {
   186  	senderIndex, ok := mp.senderIndices[sender]
   187  	if !ok {
   188  		return nil
   189  	}
   190  
   191  	cursor := senderIndex.Front()
   192  	return cursor.Value.(sdk.Tx)
   193  }
   194  
   195  // Insert attempts to insert a Tx into the app-side mempool in O(log n) time,
   196  // returning an error if unsuccessful. Sender and nonce are derived from the
   197  // transaction's first signature.
   198  //
   199  // Transactions are unique by sender and nonce. Inserting a duplicate tx is an
   200  // O(log n) no-op.
   201  //
   202  // Inserting a duplicate tx with a different priority overwrites the existing tx,
   203  // changing the total order of the mempool.
   204  func (mp *PriorityNonceMempool[C]) Insert(ctx context.Context, tx sdk.Tx) error {
   205  	mp.mtx.Lock()
   206  	defer mp.mtx.Unlock()
   207  	if mp.cfg.MaxTx > 0 && mp.priorityIndex.Len() >= mp.cfg.MaxTx {
   208  		return ErrMempoolTxMaxCapacity
   209  	} else if mp.cfg.MaxTx < 0 {
   210  		return nil
   211  	}
   212  
   213  	sigs, err := mp.cfg.SignerExtractor.GetSigners(tx)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	if len(sigs) == 0 {
   218  		return fmt.Errorf("tx must have at least one signer")
   219  	}
   220  
   221  	sig := sigs[0]
   222  	sender := sig.Signer.String()
   223  	priority := mp.cfg.TxPriority.GetTxPriority(ctx, tx)
   224  	nonce := sig.Sequence
   225  	key := txMeta[C]{nonce: nonce, priority: priority, sender: sender}
   226  
   227  	senderIndex, ok := mp.senderIndices[sender]
   228  	if !ok {
   229  		senderIndex = skiplist.New(skiplist.LessThanFunc(func(a, b any) int {
   230  			return skiplist.Uint64.Compare(b.(txMeta[C]).nonce, a.(txMeta[C]).nonce)
   231  		}))
   232  
   233  		// initialize sender index if not found
   234  		mp.senderIndices[sender] = senderIndex
   235  	}
   236  
   237  	// Since mp.priorityIndex is scored by priority, then sender, then nonce, a
   238  	// changed priority will create a new key, so we must remove the old key and
   239  	// re-insert it to avoid having the same tx with different priorityIndex indexed
   240  	// twice in the mempool.
   241  	//
   242  	// This O(log n) remove operation is rare and only happens when a tx's priority
   243  	// changes.
   244  	sk := txMeta[C]{nonce: nonce, sender: sender}
   245  	if oldScore, txExists := mp.scores[sk]; txExists {
   246  		if mp.cfg.TxReplacement != nil && !mp.cfg.TxReplacement(oldScore.priority, priority, senderIndex.Get(key).Value.(sdk.Tx), tx) {
   247  			return fmt.Errorf(
   248  				"tx doesn't fit the replacement rule, oldPriority: %v, newPriority: %v, oldTx: %v, newTx: %v",
   249  				oldScore.priority,
   250  				priority,
   251  				senderIndex.Get(key).Value.(sdk.Tx),
   252  				tx,
   253  			)
   254  		}
   255  
   256  		mp.priorityIndex.Remove(txMeta[C]{
   257  			nonce:    nonce,
   258  			sender:   sender,
   259  			priority: oldScore.priority,
   260  			weight:   oldScore.weight,
   261  		})
   262  		mp.priorityCounts[oldScore.priority]--
   263  	}
   264  
   265  	mp.priorityCounts[priority]++
   266  
   267  	// Since senderIndex is scored by nonce, a changed priority will overwrite the
   268  	// existing key.
   269  	key.senderElement = senderIndex.Set(key, tx)
   270  
   271  	mp.scores[sk] = txMeta[C]{priority: priority}
   272  	mp.priorityIndex.Set(key, tx)
   273  
   274  	return nil
   275  }
   276  
   277  func (i *PriorityNonceIterator[C]) iteratePriority() Iterator {
   278  	// beginning of priority iteration
   279  	if i.priorityNode == nil {
   280  		i.priorityNode = i.mempool.priorityIndex.Front()
   281  	} else {
   282  		i.priorityNode = i.priorityNode.Next()
   283  	}
   284  
   285  	// end of priority iteration
   286  	if i.priorityNode == nil {
   287  		return nil
   288  	}
   289  
   290  	i.sender = i.priorityNode.Key().(txMeta[C]).sender
   291  
   292  	nextPriorityNode := i.priorityNode.Next()
   293  	if nextPriorityNode != nil {
   294  		i.nextPriority = nextPriorityNode.Key().(txMeta[C]).priority
   295  	} else {
   296  		i.nextPriority = i.mempool.cfg.TxPriority.MinValue
   297  	}
   298  
   299  	return i.Next()
   300  }
   301  
   302  func (i *PriorityNonceIterator[C]) Next() Iterator {
   303  	if i.priorityNode == nil {
   304  		return nil
   305  	}
   306  
   307  	cursor, ok := i.senderCursors[i.sender]
   308  	if !ok {
   309  		// beginning of sender iteration
   310  		cursor = i.mempool.senderIndices[i.sender].Front()
   311  	} else {
   312  		// middle of sender iteration
   313  		cursor = cursor.Next()
   314  	}
   315  
   316  	// end of sender iteration
   317  	if cursor == nil {
   318  		return i.iteratePriority()
   319  	}
   320  
   321  	key := cursor.Key().(txMeta[C])
   322  
   323  	// We've reached a transaction with a priority lower than the next highest
   324  	// priority in the pool.
   325  	if i.mempool.cfg.TxPriority.Compare(key.priority, i.nextPriority) < 0 {
   326  		return i.iteratePriority()
   327  	} else if i.priorityNode.Next() != nil && i.mempool.cfg.TxPriority.Compare(key.priority, i.nextPriority) == 0 {
   328  		// Weight is incorporated into the priority index key only (not sender index)
   329  		// so we must fetch it here from the scores map.
   330  		weight := i.mempool.scores[txMeta[C]{nonce: key.nonce, sender: key.sender}].weight
   331  		if i.mempool.cfg.TxPriority.Compare(weight, i.priorityNode.Next().Key().(txMeta[C]).weight) < 0 {
   332  			return i.iteratePriority()
   333  		}
   334  	}
   335  
   336  	i.senderCursors[i.sender] = cursor
   337  	return i
   338  }
   339  
   340  func (i *PriorityNonceIterator[C]) Tx() sdk.Tx {
   341  	return i.senderCursors[i.sender].Value.(sdk.Tx)
   342  }
   343  
   344  // Select returns a set of transactions from the mempool, ordered by priority
   345  // and sender-nonce in O(n) time. The passed in list of transactions are ignored.
   346  // This is a readonly operation, the mempool is not modified.
   347  //
   348  // The maxBytes parameter defines the maximum number of bytes of transactions to
   349  // return.
   350  //
   351  // NOTE: It is not safe to use this iterator while removing transactions from
   352  // the underlying mempool.
   353  func (mp *PriorityNonceMempool[C]) Select(ctx context.Context, txs [][]byte) Iterator {
   354  	mp.mtx.Lock()
   355  	defer mp.mtx.Unlock()
   356  	return mp.doSelect(ctx, txs)
   357  }
   358  
   359  func (mp *PriorityNonceMempool[C]) doSelect(_ context.Context, _ [][]byte) Iterator {
   360  	if mp.priorityIndex.Len() == 0 {
   361  		return nil
   362  	}
   363  
   364  	mp.reorderPriorityTies()
   365  
   366  	iterator := &PriorityNonceIterator[C]{
   367  		mempool:       mp,
   368  		senderCursors: make(map[string]*skiplist.Element),
   369  	}
   370  
   371  	return iterator.iteratePriority()
   372  }
   373  
   374  // SelectBy will hold the mutex during the iteration, callback returns if continue.
   375  func (mp *PriorityNonceMempool[C]) SelectBy(ctx context.Context, txs [][]byte, callback func(sdk.Tx) bool) {
   376  	mp.mtx.Lock()
   377  	defer mp.mtx.Unlock()
   378  
   379  	iter := mp.doSelect(ctx, txs)
   380  	for iter != nil && callback(iter.Tx()) {
   381  		iter = iter.Next()
   382  	}
   383  }
   384  
   385  type reorderKey[C comparable] struct {
   386  	deleteKey txMeta[C]
   387  	insertKey txMeta[C]
   388  	tx        sdk.Tx
   389  }
   390  
   391  func (mp *PriorityNonceMempool[C]) reorderPriorityTies() {
   392  	node := mp.priorityIndex.Front()
   393  
   394  	var reordering []reorderKey[C]
   395  	for node != nil {
   396  		key := node.Key().(txMeta[C])
   397  		if mp.priorityCounts[key.priority] > 1 {
   398  			newKey := key
   399  			newKey.weight = senderWeight(mp.cfg.TxPriority, key.senderElement)
   400  			reordering = append(reordering, reorderKey[C]{deleteKey: key, insertKey: newKey, tx: node.Value.(sdk.Tx)})
   401  		}
   402  
   403  		node = node.Next()
   404  	}
   405  
   406  	for _, k := range reordering {
   407  		mp.priorityIndex.Remove(k.deleteKey)
   408  		delete(mp.scores, txMeta[C]{nonce: k.deleteKey.nonce, sender: k.deleteKey.sender})
   409  		mp.priorityIndex.Set(k.insertKey, k.tx)
   410  		mp.scores[txMeta[C]{nonce: k.insertKey.nonce, sender: k.insertKey.sender}] = k.insertKey
   411  	}
   412  }
   413  
   414  // senderWeight returns the weight of a given tx (t) at senderCursor. Weight is
   415  // defined as the first (nonce-wise) same sender tx with a priority not equal to
   416  // t. It is used to resolve priority collisions, that is when 2 or more txs from
   417  // different senders have the same priority.
   418  func senderWeight[C comparable](txPriority TxPriority[C], senderCursor *skiplist.Element) C {
   419  	if senderCursor == nil {
   420  		return txPriority.MinValue
   421  	}
   422  
   423  	weight := senderCursor.Key().(txMeta[C]).priority
   424  	senderCursor = senderCursor.Next()
   425  	for senderCursor != nil {
   426  		p := senderCursor.Key().(txMeta[C]).priority
   427  		if txPriority.Compare(p, weight) != 0 {
   428  			weight = p
   429  		}
   430  
   431  		senderCursor = senderCursor.Next()
   432  	}
   433  
   434  	return weight
   435  }
   436  
   437  // CountTx returns the number of transactions in the mempool.
   438  func (mp *PriorityNonceMempool[C]) CountTx() int {
   439  	mp.mtx.Lock()
   440  	defer mp.mtx.Unlock()
   441  	return mp.priorityIndex.Len()
   442  }
   443  
   444  // Remove removes a transaction from the mempool in O(log n) time, returning an
   445  // error if unsuccessful.
   446  func (mp *PriorityNonceMempool[C]) Remove(tx sdk.Tx) error {
   447  	mp.mtx.Lock()
   448  	defer mp.mtx.Unlock()
   449  	sigs, err := mp.cfg.SignerExtractor.GetSigners(tx)
   450  	if err != nil {
   451  		return err
   452  	}
   453  	if len(sigs) == 0 {
   454  		return fmt.Errorf("attempted to remove a tx with no signatures")
   455  	}
   456  
   457  	sig := sigs[0]
   458  	sender := sig.Signer.String()
   459  	nonce := sig.Sequence
   460  
   461  	scoreKey := txMeta[C]{nonce: nonce, sender: sender}
   462  	score, ok := mp.scores[scoreKey]
   463  	if !ok {
   464  		return ErrTxNotFound
   465  	}
   466  	tk := txMeta[C]{nonce: nonce, priority: score.priority, sender: sender, weight: score.weight}
   467  
   468  	senderTxs, ok := mp.senderIndices[sender]
   469  	if !ok {
   470  		return fmt.Errorf("sender %s not found", sender)
   471  	}
   472  
   473  	mp.priorityIndex.Remove(tk)
   474  	senderTxs.Remove(tk)
   475  	delete(mp.scores, scoreKey)
   476  	mp.priorityCounts[score.priority]--
   477  
   478  	return nil
   479  }
   480  
   481  func IsEmpty[C comparable](mempool Mempool) error {
   482  	mp := mempool.(*PriorityNonceMempool[C])
   483  	if mp.priorityIndex.Len() != 0 {
   484  		return fmt.Errorf("priorityIndex not empty")
   485  	}
   486  
   487  	countKeys := make([]C, 0, len(mp.priorityCounts))
   488  	for k := range mp.priorityCounts {
   489  		countKeys = append(countKeys, k)
   490  	}
   491  
   492  	for _, k := range countKeys {
   493  		if mp.priorityCounts[k] != 0 {
   494  			return fmt.Errorf("priorityCounts not zero at %v, got %v", k, mp.priorityCounts[k])
   495  		}
   496  	}
   497  
   498  	senderKeys := make([]string, 0, len(mp.senderIndices))
   499  	for k := range mp.senderIndices {
   500  		senderKeys = append(senderKeys, k)
   501  	}
   502  
   503  	for _, k := range senderKeys {
   504  		if mp.senderIndices[k].Len() != 0 {
   505  			return fmt.Errorf("senderIndex not empty for sender %v", k)
   506  		}
   507  	}
   508  
   509  	return nil
   510  }