github.com/fiagdao/tendermint@v0.32.11-0.20220824195748-2087fcc480c1/mempool/clist_mempool.go (about) 1 package mempool 2 3 import ( 4 "bytes" 5 "container/list" 6 "crypto/sha256" 7 "fmt" 8 "sync" 9 "sync/atomic" 10 11 abci "github.com/tendermint/tendermint/abci/types" 12 cfg "github.com/tendermint/tendermint/config" 13 auto "github.com/tendermint/tendermint/libs/autofile" 14 "github.com/tendermint/tendermint/libs/clist" 15 "github.com/tendermint/tendermint/libs/log" 16 tmmath "github.com/tendermint/tendermint/libs/math" 17 tmos "github.com/tendermint/tendermint/libs/os" 18 "github.com/tendermint/tendermint/p2p" 19 "github.com/tendermint/tendermint/proxy" 20 "github.com/tendermint/tendermint/types" 21 ) 22 23 //-------------------------------------------------------------------------------- 24 25 // CListMempool is an ordered in-memory pool for transactions before they are 26 // proposed in a consensus round. Transaction validity is checked using the 27 // CheckTx abci message before the transaction is added to the pool. The 28 // mempool uses a concurrent list structure for storing transactions that can 29 // be efficiently accessed by multiple concurrent readers. 30 type CListMempool struct { 31 // Atomic integers 32 height int64 // the last block Update()'d to 33 txsBytes int64 // total size of mempool, in bytes 34 35 // notify listeners (ie. consensus) when txs are available 36 notifiedTxsAvailable bool 37 txsAvailable chan struct{} // fires once for each height, when the mempool is not empty 38 39 config *cfg.MempoolConfig 40 41 // Exclusive mutex for Update method to prevent concurrent execution of 42 // CheckTx or ReapMaxBytesMaxGas(ReapMaxTxs) methods. 43 updateMtx sync.RWMutex 44 preCheck PreCheckFunc 45 postCheck PostCheckFunc 46 47 wal *auto.AutoFile // a log of mempool txs 48 txs *clist.CList // concurrent linked-list of good txs 49 proxyAppConn proxy.AppConnMempool 50 51 // Track whether we're rechecking txs. 52 // These are not protected by a mutex and are expected to be mutated in 53 // serial (ie. by abci responses which are called in serial). 54 recheckCursor *clist.CElement // next expected response 55 recheckEnd *clist.CElement // re-checking stops here 56 57 // Map for quick access to txs to record sender in CheckTx. 58 // txsMap: txKey -> CElement 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 67 metrics *Metrics 68 69 //Max amount of blocks a tx is allowed to stay on the mempool 70 maxTxLife int64 71 } 72 73 var _ Mempool = &CListMempool{} 74 75 // CListMempoolOption sets an optional parameter on the mempool. 76 type CListMempoolOption func(*CListMempool) 77 78 // NewCListMempool returns a new mempool with the given configuration and connection to an application. 79 func NewCListMempool( 80 config *cfg.MempoolConfig, 81 proxyAppConn proxy.AppConnMempool, 82 height int64, 83 options ...CListMempoolOption, 84 ) *CListMempool { 85 mempool := &CListMempool{ 86 config: config, 87 proxyAppConn: proxyAppConn, 88 txs: clist.New(), 89 height: height, 90 recheckCursor: nil, 91 recheckEnd: nil, 92 logger: log.NewNopLogger(), 93 metrics: NopMetrics(), 94 maxTxLife: int64(2), 95 } 96 if config.CacheSize > 0 { 97 mempool.cache = newMapTxCache(config.CacheSize) 98 } else { 99 mempool.cache = nopTxCache{} 100 } 101 proxyAppConn.SetResponseCallback(mempool.globalCb) 102 for _, option := range options { 103 option(mempool) 104 } 105 return mempool 106 } 107 108 // NOTE: not thread safe - should only be called once, on startup 109 func (mem *CListMempool) EnableTxsAvailable() { 110 mem.txsAvailable = make(chan struct{}, 1) 111 } 112 113 // SetLogger sets the Logger. 114 func (mem *CListMempool) SetLogger(l log.Logger) { 115 mem.logger = l 116 } 117 118 // WithPreCheck sets a filter for the mempool to reject a tx if f(tx) returns 119 // false. This is ran before CheckTx. 120 func WithPreCheck(f PreCheckFunc) CListMempoolOption { 121 return func(mem *CListMempool) { mem.preCheck = f } 122 } 123 124 // WithPostCheck sets a filter for the mempool to reject a tx if f(tx) returns 125 // false. This is ran after CheckTx. 126 func WithPostCheck(f PostCheckFunc) CListMempoolOption { 127 return func(mem *CListMempool) { mem.postCheck = f } 128 } 129 130 // WithMetrics sets the metrics. 131 func WithMetrics(metrics *Metrics) CListMempoolOption { 132 return func(mem *CListMempool) { mem.metrics = metrics } 133 } 134 135 func (mem *CListMempool) InitWAL() error { 136 var ( 137 walDir = mem.config.WalDir() 138 walFile = walDir + "/wal" 139 ) 140 141 const perm = 0700 142 if err := tmos.EnsureDir(walDir, perm); err != nil { 143 return err 144 } 145 146 af, err := auto.OpenAutoFile(walFile) 147 if err != nil { 148 return fmt.Errorf("can't open autofile %s: %w", walFile, err) 149 } 150 151 mem.wal = af 152 return nil 153 } 154 155 func (mem *CListMempool) CloseWAL() { 156 if err := mem.wal.Close(); err != nil { 157 mem.logger.Error("Error closing WAL", "err", err) 158 } 159 mem.wal = nil 160 } 161 162 // Safe for concurrent use by multiple goroutines. 163 func (mem *CListMempool) Lock() { 164 mem.updateMtx.Lock() 165 } 166 167 // Safe for concurrent use by multiple goroutines. 168 func (mem *CListMempool) Unlock() { 169 mem.updateMtx.Unlock() 170 } 171 172 // Safe for concurrent use by multiple goroutines. 173 func (mem *CListMempool) Size() int { 174 return mem.txs.Len() 175 } 176 177 // Safe for concurrent use by multiple goroutines. 178 func (mem *CListMempool) TxsBytes() int64 { 179 return atomic.LoadInt64(&mem.txsBytes) 180 } 181 182 // Lock() must be help by the caller during execution. 183 func (mem *CListMempool) FlushAppConn() error { 184 return mem.proxyAppConn.FlushSync() 185 } 186 187 // XXX: Unsafe! Calling Flush may leave mempool in inconsistent state. 188 func (mem *CListMempool) Flush() { 189 mem.updateMtx.RLock() 190 defer mem.updateMtx.RUnlock() 191 192 _ = atomic.SwapInt64(&mem.txsBytes, 0) 193 mem.cache.Reset() 194 195 for e := mem.txs.Front(); e != nil; e = e.Next() { 196 mem.txs.Remove(e) 197 e.DetachPrev() 198 } 199 200 mem.txsMap.Range(func(key, _ interface{}) bool { 201 mem.txsMap.Delete(key) 202 return true 203 }) 204 } 205 206 // TxsFront returns the first transaction in the ordered list for peer 207 // goroutines to call .NextWait() on. 208 // FIXME: leaking implementation details! 209 // 210 // Safe for concurrent use by multiple goroutines. 211 func (mem *CListMempool) TxsFront() *clist.CElement { 212 return mem.txs.Front() 213 } 214 215 // TxsWaitChan returns a channel to wait on transactions. It will be closed 216 // once the mempool is not empty (ie. the internal `mem.txs` has at least one 217 // element) 218 // 219 // Safe for concurrent use by multiple goroutines. 220 func (mem *CListMempool) TxsWaitChan() <-chan struct{} { 221 return mem.txs.WaitChan() 222 } 223 224 // It blocks if we're waiting on Update() or Reap(). 225 // cb: A callback from the CheckTx command. 226 // It gets called from another goroutine. 227 // CONTRACT: Either cb will get called, or err returned. 228 // 229 // Safe for concurrent use by multiple goroutines. 230 func (mem *CListMempool) CheckTx(tx types.Tx, cb func(*abci.Response), txInfo TxInfo) error { 231 mem.updateMtx.RLock() 232 // use defer to unlock mutex because application (*local client*) might panic 233 defer mem.updateMtx.RUnlock() 234 235 txSize := len(tx) 236 237 if err := mem.isFull(txSize); err != nil { 238 return err 239 } 240 241 // The size of the corresponding amino-encoded TxMessage 242 // can't be larger than the maxMsgSize, otherwise we can't 243 // relay it to peers. 244 if txSize > mem.config.MaxTxBytes { 245 return ErrTxTooLarge{mem.config.MaxTxBytes, txSize} 246 } 247 248 if mem.preCheck != nil { 249 if err := mem.preCheck(tx); err != nil { 250 return ErrPreCheck{err} 251 } 252 } 253 254 // CACHE 255 if !mem.cache.Push(tx) { 256 // Record a new sender for a tx we've already seen. 257 // Note it's possible a tx is still in the cache but no longer in the mempool 258 // (eg. after committing a block, txs are removed from mempool but not cache), 259 // so we only record the sender for txs still in the mempool. 260 if e, ok := mem.txsMap.Load(txKey(tx)); ok { 261 memTx := e.(*clist.CElement).Value.(*mempoolTx) 262 memTx.senders.LoadOrStore(txInfo.SenderID, true) 263 // TODO: consider punishing peer for dups, 264 // its non-trivial since invalid txs can become valid, 265 // but they can spam the same tx with little cost to them atm. 266 267 } 268 269 return ErrTxInCache 270 } 271 // END CACHE 272 273 // WAL 274 if mem.wal != nil { 275 // TODO: Notify administrators when WAL fails 276 _, err := mem.wal.Write([]byte(tx)) 277 if err != nil { 278 mem.logger.Error("Error writing to WAL", "err", err) 279 } 280 _, err = mem.wal.Write([]byte("\n")) 281 if err != nil { 282 mem.logger.Error("Error writing to WAL", "err", err) 283 } 284 } 285 // END WAL 286 287 // NOTE: proxyAppConn may error if tx buffer is full 288 if err := mem.proxyAppConn.Error(); err != nil { 289 return err 290 } 291 292 reqRes := mem.proxyAppConn.CheckTxAsync(abci.RequestCheckTx{Tx: tx}) 293 reqRes.SetCallback(mem.reqResCb(tx, txInfo.SenderID, txInfo.SenderP2PID, cb)) 294 295 return nil 296 } 297 298 // Global callback that will be called after every ABCI response. 299 // Having a single global callback avoids needing to set a callback for each request. 300 // However, processing the checkTx response requires the peerID (so we can track which txs we heard from who), 301 // and peerID is not included in the ABCI request, so we have to set request-specific callbacks that 302 // include this information. If we're not in the midst of a recheck, this function will just return, 303 // so the request specific callback can do the work. 304 // 305 // When rechecking, we don't need the peerID, so the recheck callback happens 306 // here. 307 func (mem *CListMempool) globalCb(req *abci.Request, res *abci.Response) { 308 if mem.recheckCursor == nil { 309 return 310 } 311 312 mem.metrics.RecheckTimes.Add(1) 313 mem.resCbRecheck(req, res) 314 315 // update metrics 316 mem.metrics.Size.Set(float64(mem.Size())) 317 } 318 319 // Request specific callback that should be set on individual reqRes objects 320 // to incorporate local information when processing the response. 321 // This allows us to track the peer that sent us this tx, so we can avoid sending it back to them. 322 // NOTE: alternatively, we could include this information in the ABCI request itself. 323 // 324 // External callers of CheckTx, like the RPC, can also pass an externalCb through here that is called 325 // when all other response processing is complete. 326 // 327 // Used in CheckTx to record PeerID who sent us the tx. 328 func (mem *CListMempool) reqResCb( 329 tx []byte, 330 peerID uint16, 331 peerP2PID p2p.ID, 332 externalCb func(*abci.Response), 333 ) func(res *abci.Response) { 334 return func(res *abci.Response) { 335 if mem.recheckCursor != nil { 336 // this should never happen 337 panic("recheck cursor is not nil in reqResCb") 338 } 339 340 mem.resCbFirstTime(tx, peerID, peerP2PID, res) 341 342 // update metrics 343 mem.metrics.Size.Set(float64(mem.Size())) 344 345 // passed in by the caller of CheckTx, eg. the RPC 346 if externalCb != nil { 347 externalCb(res) 348 } 349 } 350 } 351 352 // Called from: 353 // - resCbFirstTime (lock not held) if tx is valid 354 func (mem *CListMempool) addTx(memTx *mempoolTx) { 355 e := mem.txs.PushBack(memTx) 356 mem.txsMap.Store(txKey(memTx.tx), e) 357 atomic.AddInt64(&mem.txsBytes, int64(len(memTx.tx))) 358 mem.metrics.TxSizeBytes.Observe(float64(len(memTx.tx))) 359 } 360 361 // Called from: 362 // - Update (lock held) if tx was committed 363 // - resCbRecheck (lock not held) if tx was invalidated 364 func (mem *CListMempool) removeTx(tx types.Tx, elem *clist.CElement, removeFromCache bool) { 365 mem.txs.Remove(elem) 366 elem.DetachPrev() 367 mem.txsMap.Delete(txKey(tx)) 368 atomic.AddInt64(&mem.txsBytes, int64(-len(tx))) 369 370 if removeFromCache { 371 mem.cache.Remove(tx) 372 } 373 } 374 375 func (mem *CListMempool) isFull(txSize int) error { 376 var ( 377 memSize = mem.Size() 378 txsBytes = mem.TxsBytes() 379 ) 380 381 if memSize >= mem.config.Size || int64(txSize)+txsBytes > mem.config.MaxTxsBytes { 382 return ErrMempoolIsFull{ 383 memSize, mem.config.Size, 384 txsBytes, mem.config.MaxTxsBytes, 385 } 386 } 387 388 return nil 389 } 390 391 // callback, which is called after the app checked the tx for the first time. 392 // 393 // The case where the app checks the tx for the second and subsequent times is 394 // handled by the resCbRecheck callback. 395 func (mem *CListMempool) resCbFirstTime( 396 tx []byte, 397 peerID uint16, 398 peerP2PID p2p.ID, 399 res *abci.Response, 400 ) { 401 switch r := res.Value.(type) { 402 case *abci.Response_CheckTx: 403 var postCheckErr error 404 if mem.postCheck != nil { 405 postCheckErr = mem.postCheck(tx, r.CheckTx) 406 } 407 if (r.CheckTx.Code == abci.CodeTypeOK) && postCheckErr == nil { 408 // Check mempool isn't full again to reduce the chance of exceeding the 409 // limits. 410 if err := mem.isFull(len(tx)); err != nil { 411 // remove from cache (mempool might have a space later) 412 mem.cache.Remove(tx) 413 mem.logger.Error(err.Error()) 414 return 415 } 416 417 memTx := &mempoolTx{ 418 height: mem.height, 419 gasWanted: r.CheckTx.GasWanted, 420 tx: tx, 421 } 422 423 //If cache size is 0 or Tx is new to cache but exist in mempool do nothing 424 if _, found := mem.txsMap.Load(txKey(memTx.tx)); !found { 425 memTx.senders.Store(peerID, true) 426 mem.addTx(memTx) 427 mem.logger.Info("Added good transaction", 428 "tx", txID(tx), 429 "res", r, 430 "height", memTx.height, 431 "total", mem.Size(), 432 ) 433 mem.notifyTxsAvailable() 434 } 435 436 } else { 437 // ignore bad transaction 438 mem.logger.Info("Rejected bad transaction", 439 "tx", txID(tx), "peerID", peerP2PID, "res", r, "err", postCheckErr) 440 mem.metrics.FailedTxs.Add(1) 441 // remove from cache (it might be good later) 442 mem.cache.Remove(tx) 443 } 444 default: 445 // ignore other messages 446 } 447 } 448 449 // callback, which is called after the app rechecked the tx. 450 // 451 // The case where the app checks the tx for the first time is handled by the 452 // resCbFirstTime callback. 453 func (mem *CListMempool) resCbRecheck(req *abci.Request, res *abci.Response) { 454 switch r := res.Value.(type) { 455 case *abci.Response_CheckTx: 456 tx := req.GetCheckTx().Tx 457 memTx := mem.recheckCursor.Value.(*mempoolTx) 458 if !bytes.Equal(tx, memTx.tx) { 459 panic(fmt.Sprintf( 460 "Unexpected tx response from proxy during recheck\nExpected %X, got %X", 461 memTx.tx, 462 tx)) 463 } 464 var postCheckErr error 465 if mem.postCheck != nil { 466 postCheckErr = mem.postCheck(tx, r.CheckTx) 467 } 468 if (r.CheckTx.Code == abci.CodeTypeOK) && postCheckErr == nil { 469 // Check if tx is stale 470 if mem.height-memTx.height > mem.maxTxLife { 471 mem.logger.Info("Tx is stale, no longer valid", "tx", txID(tx), "res", r, "err", postCheckErr) 472 // NOTE: we don't remove tx from the cache 473 mem.removeTx(tx, mem.recheckCursor, false) 474 } 475 } else { 476 // Tx became invalidated due to newly committed block. 477 mem.logger.Info("Tx is no longer valid", "tx", txID(tx), "res", r, "err", postCheckErr) 478 // NOTE: we remove tx from the cache because it might be good later 479 mem.removeTx(tx, mem.recheckCursor, true) 480 } 481 if mem.recheckCursor == mem.recheckEnd { 482 mem.recheckCursor = nil 483 } else { 484 mem.recheckCursor = mem.recheckCursor.Next() 485 } 486 if mem.recheckCursor == nil { 487 // Done! 488 mem.logger.Info("Done rechecking txs") 489 490 // incase the recheck removed all txs 491 if mem.Size() > 0 { 492 mem.notifyTxsAvailable() 493 } 494 } 495 default: 496 // ignore other messages 497 } 498 } 499 500 // Safe for concurrent use by multiple goroutines. 501 func (mem *CListMempool) TxsAvailable() <-chan struct{} { 502 return mem.txsAvailable 503 } 504 505 func (mem *CListMempool) notifyTxsAvailable() { 506 if mem.Size() == 0 { 507 panic("notified txs available but mempool is empty!") 508 } 509 if mem.txsAvailable != nil && !mem.notifiedTxsAvailable { 510 // channel cap is 1, so this will send once 511 mem.notifiedTxsAvailable = true 512 select { 513 case mem.txsAvailable <- struct{}{}: 514 default: 515 } 516 } 517 } 518 519 // Safe for concurrent use by multiple goroutines. 520 func (mem *CListMempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs { 521 mem.updateMtx.RLock() 522 defer mem.updateMtx.RUnlock() 523 524 var ( 525 totalBytes int64 526 totalGas int64 527 ) 528 // TODO: we will get a performance boost if we have a good estimate of avg 529 // size per tx, and set the initial capacity based off of that. 530 // txs := make([]types.Tx, 0, tmmath.MinInt(mem.txs.Len(), max/mem.avgTxSize)) 531 txs := make([]types.Tx, 0, mem.txs.Len()) 532 for e := mem.txs.Front(); e != nil; e = e.Next() { 533 memTx := e.Value.(*mempoolTx) 534 // Check total size requirement 535 aminoOverhead := types.ComputeAminoOverhead(memTx.tx, 1) 536 if maxBytes > -1 && totalBytes+int64(len(memTx.tx))+aminoOverhead > maxBytes { 537 return txs 538 } 539 totalBytes += int64(len(memTx.tx)) + aminoOverhead 540 // Check total gas requirement. 541 // If maxGas is negative, skip this check. 542 // Since newTotalGas < masGas, which 543 // must be non-negative, it follows that this won't overflow. 544 newTotalGas := totalGas + memTx.gasWanted 545 if maxGas > -1 && newTotalGas > maxGas { 546 return txs 547 } 548 totalGas = newTotalGas 549 txs = append(txs, memTx.tx) 550 } 551 return txs 552 } 553 554 // Safe for concurrent use by multiple goroutines. 555 func (mem *CListMempool) ReapMaxTxs(max int) types.Txs { 556 mem.updateMtx.RLock() 557 defer mem.updateMtx.RUnlock() 558 559 if max < 0 { 560 max = mem.txs.Len() 561 } 562 563 txs := make([]types.Tx, 0, tmmath.MinInt(mem.txs.Len(), max)) 564 for e := mem.txs.Front(); e != nil && len(txs) <= max; e = e.Next() { 565 memTx := e.Value.(*mempoolTx) 566 txs = append(txs, memTx.tx) 567 } 568 return txs 569 } 570 571 // Lock() must be help by the caller during execution. 572 func (mem *CListMempool) Update( 573 height int64, 574 txs types.Txs, 575 deliverTxResponses []*abci.ResponseDeliverTx, 576 preCheck PreCheckFunc, 577 postCheck PostCheckFunc, 578 ) error { 579 // Set height 580 mem.height = height 581 mem.notifiedTxsAvailable = false 582 583 if preCheck != nil { 584 mem.preCheck = preCheck 585 } 586 if postCheck != nil { 587 mem.postCheck = postCheck 588 } 589 590 for i, tx := range txs { 591 if deliverTxResponses[i].Code == abci.CodeTypeOK { 592 // Add valid committed tx to the cache (if missing). 593 _ = mem.cache.Push(tx) 594 } else { 595 // Allow invalid transactions to be resubmitted. 596 mem.cache.Remove(tx) 597 } 598 599 // Remove committed tx from the mempool. 600 // 601 // Note an evil proposer can drop valid txs! 602 // Mempool before: 603 // 100 -> 101 -> 102 604 // Block, proposed by an evil proposer: 605 // 101 -> 102 606 // Mempool after: 607 // 100 608 // https://github.com/tendermint/tendermint/issues/3322. 609 if e, ok := mem.txsMap.Load(txKey(tx)); ok { 610 mem.removeTx(tx, e.(*clist.CElement), false) 611 } 612 } 613 614 // Either recheck non-committed txs to see if they became invalid 615 // or just notify there're some txs left. 616 if mem.Size() > 0 { 617 if mem.config.Recheck { 618 mem.logger.Info("Recheck txs", "numtxs", mem.Size(), "height", height) 619 mem.recheckTxs() 620 // At this point, mem.txs are being rechecked. 621 // mem.recheckCursor re-scans mem.txs and possibly removes some txs. 622 // Before mem.Reap(), we should wait for mem.recheckCursor to be nil. 623 } else { 624 mem.notifyTxsAvailable() 625 } 626 } 627 628 // Update metrics 629 mem.metrics.Size.Set(float64(mem.Size())) 630 631 return nil 632 } 633 634 func (mem *CListMempool) recheckTxs() { 635 if mem.Size() == 0 { 636 panic("recheckTxs is called, but the mempool is empty") 637 } 638 639 mem.recheckCursor = mem.txs.Front() 640 mem.recheckEnd = mem.txs.Back() 641 642 // Push txs to proxyAppConn 643 // NOTE: globalCb may be called concurrently. 644 for e := mem.txs.Front(); e != nil; e = e.Next() { 645 memTx := e.Value.(*mempoolTx) 646 mem.proxyAppConn.CheckTxAsync(abci.RequestCheckTx{ 647 Tx: memTx.tx, 648 Type: abci.CheckTxType_Recheck, 649 }) 650 } 651 652 mem.proxyAppConn.FlushAsync() 653 } 654 655 //-------------------------------------------------------------------------------- 656 657 // mempoolTx is a transaction that successfully ran 658 type mempoolTx struct { 659 height int64 // height that this tx had been validated in 660 gasWanted int64 // amount of gas this tx states it will require 661 tx types.Tx // 662 663 // ids of peers who've sent us this tx (as a map for quick lookups). 664 // senders: PeerID -> bool 665 senders sync.Map 666 } 667 668 // Height returns the height for this transaction 669 func (memTx *mempoolTx) Height() int64 { 670 return atomic.LoadInt64(&memTx.height) 671 } 672 673 //-------------------------------------------------------------------------------- 674 675 type txCache interface { 676 Reset() 677 Push(tx types.Tx) bool 678 Remove(tx types.Tx) 679 } 680 681 // mapTxCache maintains a LRU cache of transactions. This only stores the hash 682 // of the tx, due to memory concerns. 683 type mapTxCache struct { 684 mtx sync.Mutex 685 size int 686 cacheMap map[[sha256.Size]byte]*list.Element 687 list *list.List 688 } 689 690 var _ txCache = (*mapTxCache)(nil) 691 692 // newMapTxCache returns a new mapTxCache. 693 func newMapTxCache(cacheSize int) *mapTxCache { 694 return &mapTxCache{ 695 size: cacheSize, 696 cacheMap: make(map[[sha256.Size]byte]*list.Element, cacheSize), 697 list: list.New(), 698 } 699 } 700 701 // Reset resets the cache to an empty state. 702 func (cache *mapTxCache) Reset() { 703 cache.mtx.Lock() 704 cache.cacheMap = make(map[[sha256.Size]byte]*list.Element, cache.size) 705 cache.list.Init() 706 cache.mtx.Unlock() 707 } 708 709 // Push adds the given tx to the cache and returns true. It returns 710 // false if tx is already in the cache. 711 func (cache *mapTxCache) Push(tx types.Tx) bool { 712 cache.mtx.Lock() 713 defer cache.mtx.Unlock() 714 715 // Use the tx hash in the cache 716 txHash := txKey(tx) 717 if moved, exists := cache.cacheMap[txHash]; exists { 718 cache.list.MoveToBack(moved) 719 return false 720 } 721 722 if cache.list.Len() >= cache.size { 723 popped := cache.list.Front() 724 poppedTxHash := popped.Value.([sha256.Size]byte) 725 delete(cache.cacheMap, poppedTxHash) 726 if popped != nil { 727 cache.list.Remove(popped) 728 } 729 } 730 e := cache.list.PushBack(txHash) 731 cache.cacheMap[txHash] = e 732 return true 733 } 734 735 // Remove removes the given tx from the cache. 736 func (cache *mapTxCache) Remove(tx types.Tx) { 737 cache.mtx.Lock() 738 txHash := txKey(tx) 739 popped := cache.cacheMap[txHash] 740 delete(cache.cacheMap, txHash) 741 if popped != nil { 742 cache.list.Remove(popped) 743 } 744 745 cache.mtx.Unlock() 746 } 747 748 type nopTxCache struct{} 749 750 var _ txCache = (*nopTxCache)(nil) 751 752 func (nopTxCache) Reset() {} 753 func (nopTxCache) Push(types.Tx) bool { return true } 754 func (nopTxCache) Remove(types.Tx) {} 755 756 //-------------------------------------------------------------------------------- 757 758 // txKey is the fixed length array sha256 hash used as the key in maps. 759 func txKey(tx types.Tx) [sha256.Size]byte { 760 return sha256.Sum256(tx) 761 } 762 763 // txID is the hex encoded hash of the bytes as a types.Tx. 764 func txID(tx []byte) string { 765 return fmt.Sprintf("%X", types.Tx(tx).Hash()) 766 }