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 }