|
1 | 1 | package renderer |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "container/list" |
4 | 5 | "context" |
5 | 6 | "encoding/hex" |
6 | 7 | "encoding/json" |
@@ -29,6 +30,83 @@ var zeroAddress = common.Address{} |
29 | 30 | // Maximum number of blocks to store and display |
30 | 31 | const maxBlocks = 1000 |
31 | 32 |
|
| 33 | +// Maximum number of signer cache entries (LRU eviction when exceeded) |
| 34 | +const maxSignerCacheSize = 1000 |
| 35 | + |
| 36 | +// signerCacheEntry represents an entry in the LRU cache |
| 37 | +type signerCacheEntry struct { |
| 38 | + key string |
| 39 | + value string |
| 40 | +} |
| 41 | + |
| 42 | +// signerLRUCache implements a simple LRU cache for signer addresses |
| 43 | +type signerLRUCache struct { |
| 44 | + capacity int |
| 45 | + cache map[string]*list.Element |
| 46 | + lruList *list.List |
| 47 | + mu sync.RWMutex |
| 48 | +} |
| 49 | + |
| 50 | +// newSignerLRUCache creates a new LRU cache with the given capacity |
| 51 | +func newSignerLRUCache(capacity int) *signerLRUCache { |
| 52 | + return &signerLRUCache{ |
| 53 | + capacity: capacity, |
| 54 | + cache: make(map[string]*list.Element), |
| 55 | + lruList: list.New(), |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +// get retrieves a value from the cache and marks it as recently used |
| 60 | +func (c *signerLRUCache) get(key string) (string, bool) { |
| 61 | + c.mu.RLock() |
| 62 | + elem, exists := c.cache[key] |
| 63 | + c.mu.RUnlock() |
| 64 | + |
| 65 | + if !exists { |
| 66 | + return "", false |
| 67 | + } |
| 68 | + |
| 69 | + c.mu.Lock() |
| 70 | + c.lruList.MoveToFront(elem) |
| 71 | + c.mu.Unlock() |
| 72 | + |
| 73 | + return elem.Value.(*signerCacheEntry).value, true |
| 74 | +} |
| 75 | + |
| 76 | +// put adds or updates a value in the cache |
| 77 | +func (c *signerLRUCache) put(key, value string) { |
| 78 | + c.mu.Lock() |
| 79 | + defer c.mu.Unlock() |
| 80 | + |
| 81 | + // Check if key already exists |
| 82 | + if elem, exists := c.cache[key]; exists { |
| 83 | + // Update existing entry and move to front |
| 84 | + c.lruList.MoveToFront(elem) |
| 85 | + elem.Value.(*signerCacheEntry).value = value |
| 86 | + return |
| 87 | + } |
| 88 | + |
| 89 | + // Add new entry |
| 90 | + entry := &signerCacheEntry{key: key, value: value} |
| 91 | + elem := c.lruList.PushFront(entry) |
| 92 | + c.cache[key] = elem |
| 93 | + |
| 94 | + // Evict least recently used if over capacity |
| 95 | + if c.lruList.Len() > c.capacity { |
| 96 | + c.evictOldest() |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +// evictOldest removes the least recently used entry (must be called with lock held) |
| 101 | +func (c *signerLRUCache) evictOldest() { |
| 102 | + elem := c.lruList.Back() |
| 103 | + if elem != nil { |
| 104 | + c.lruList.Remove(elem) |
| 105 | + entry := elem.Value.(*signerCacheEntry) |
| 106 | + delete(c.cache, entry.key) |
| 107 | + } |
| 108 | +} |
| 109 | + |
32 | 110 | // Comparison functions for different data types |
33 | 111 | func compareNumbers(a, b interface{}) int { |
34 | 112 | aNum := a.(*big.Int) |
@@ -87,10 +165,9 @@ type TviewRenderer struct { |
87 | 165 | // Map to store blocks by hash for parent lookup |
88 | 166 | blocksByHash map[string]rpctypes.PolyBlock |
89 | 167 | // Column definitions for sorting |
90 | | - columns []ColumnDef |
91 | | - // Signer cache to avoid expensive Ecrecover operations |
92 | | - signerCache map[string]string // block hash -> signer address |
93 | | - signerCacheMu sync.RWMutex |
| 168 | + columns []ColumnDef |
| 169 | + // Signer LRU cache to avoid expensive Ecrecover operations |
| 170 | + signerCache *signerLRUCache |
94 | 171 | // View state management |
95 | 172 | viewState ViewState |
96 | 173 | // Mutex for thread-safe access to blocks and viewState |
@@ -157,12 +234,12 @@ func NewTviewRenderer(indexer *indexer.Indexer) *TviewRenderer { |
157 | 234 | columns := createColumnDefinitions() |
158 | 235 |
|
159 | 236 | renderer := &TviewRenderer{ |
160 | | - BaseRenderer: NewBaseRenderer(indexer), |
161 | | - app: app, |
162 | | - blocks: make([]rpctypes.PolyBlock, 0), |
163 | | - blocksByHash: make(map[string]rpctypes.PolyBlock), |
164 | | - columns: columns, |
165 | | - signerCache: make(map[string]string), |
| 237 | + BaseRenderer: NewBaseRenderer(indexer), |
| 238 | + app: app, |
| 239 | + blocks: make([]rpctypes.PolyBlock, 0), |
| 240 | + blocksByHash: make(map[string]rpctypes.PolyBlock), |
| 241 | + columns: columns, |
| 242 | + signerCache: newSignerLRUCache(maxSignerCacheSize), |
166 | 243 | viewState: ViewState{ |
167 | 244 | followMode: true, // Start in follow mode |
168 | 245 | sortColumn: "number", // Default sort by block number |
@@ -570,45 +647,34 @@ func (t *TviewRenderer) throttledDraw() { |
570 | 647 | } |
571 | 648 | } |
572 | 649 |
|
573 | | -// getCachedSigner gets the signer for a block, using cache to avoid expensive Ecrecover calls |
| 650 | +// getCachedSigner gets the signer for a block, using LRU cache to avoid expensive Ecrecover calls |
574 | 651 | func (t *TviewRenderer) getCachedSigner(block rpctypes.PolyBlock) string { |
575 | 652 | blockHash := block.Hash().Hex() |
576 | | - |
| 653 | + |
577 | 654 | // Check cache first |
578 | | - t.signerCacheMu.RLock() |
579 | | - if cached, exists := t.signerCache[blockHash]; exists { |
580 | | - t.signerCacheMu.RUnlock() |
| 655 | + if cached, exists := t.signerCache.get(blockHash); exists { |
581 | 656 | return cached |
582 | 657 | } |
583 | | - t.signerCacheMu.RUnlock() |
584 | | - |
| 658 | + |
585 | 659 | // If miner is non-zero, use the miner |
586 | 660 | zeroAddr := common.Address{} |
587 | 661 | if block.Miner() != zeroAddr { |
588 | 662 | result := truncateHash(block.Miner().Hex(), 6, 4) |
589 | | - // Cache the result |
590 | | - t.signerCacheMu.Lock() |
591 | | - t.signerCache[blockHash] = result |
592 | | - t.signerCacheMu.Unlock() |
| 663 | + t.signerCache.put(blockHash, result) |
593 | 664 | return result |
594 | 665 | } |
595 | | - |
596 | | - // If miner is zero, try to extract signer from extra data |
| 666 | + |
| 667 | + // If miner is zero, try to extract signer from extra data (EXPENSIVE - Ecrecover) |
597 | 668 | if signer, err := polymetrics.Ecrecover(&block); err == nil { |
598 | 669 | signerAddr := common.HexToAddress("0x" + hex.EncodeToString(signer)) |
599 | 670 | result := truncateHash(signerAddr.Hex(), 6, 4) |
600 | | - // Cache the result |
601 | | - t.signerCacheMu.Lock() |
602 | | - t.signerCache[blockHash] = result |
603 | | - t.signerCacheMu.Unlock() |
| 671 | + t.signerCache.put(blockHash, result) |
604 | 672 | return result |
605 | 673 | } |
606 | | - |
| 674 | + |
607 | 675 | // If can't extract signer, cache N/A result |
608 | 676 | result := "N/A" |
609 | | - t.signerCacheMu.Lock() |
610 | | - t.signerCache[blockHash] = result |
611 | | - t.signerCacheMu.Unlock() |
| 677 | + t.signerCache.put(blockHash, result) |
612 | 678 | return result |
613 | 679 | } |
614 | 680 |
|
|
0 commit comments