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 }