/
block_cache.go
169 lines (140 loc) · 4.63 KB
/
block_cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package execution
import (
"errors"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/types"
"github.com/prysmaticlabs/prysm/v5/config/params"
"k8s.io/client-go/tools/cache"
)
var (
// ErrNotAHeaderInfo will be returned when a cache object is not a pointer to
// a headerInfo struct.
ErrNotAHeaderInfo = errors.New("object is not a header info")
// maxCacheSize is 2x of the follow distance for additional cache padding.
// Requests should be only accessing blocks within recent blocks within the
// Eth1FollowDistance.
maxCacheSize = 2 * params.BeaconConfig().Eth1FollowDistance
// Metrics
headerCacheMiss = promauto.NewCounter(prometheus.CounterOpts{
Name: "powchain_header_cache_miss",
Help: "The number of header requests that aren't present in the cache.",
})
headerCacheHit = promauto.NewCounter(prometheus.CounterOpts{
Name: "powchain_header_cache_hit",
Help: "The number of header requests that are present in the cache.",
})
headerCacheSize = promauto.NewGauge(prometheus.GaugeOpts{
Name: "powchain_header_cache_size",
Help: "The number of headers in the header cache",
})
)
// hashKeyFn takes the hex string representation as the key for a headerInfo.
func hashKeyFn(obj interface{}) (string, error) {
hInfo, ok := obj.(*types.HeaderInfo)
if !ok {
return "", ErrNotAHeaderInfo
}
return hInfo.Hash.Hex(), nil
}
// heightKeyFn takes the string representation of the block header number as the key
// for a headerInfo.
func heightKeyFn(obj interface{}) (string, error) {
hInfo, ok := obj.(*types.HeaderInfo)
if !ok {
return "", ErrNotAHeaderInfo
}
return hInfo.Number.String(), nil
}
// headerCache struct with two queues for looking up by hash or by block height.
type headerCache struct {
hashCache *cache.FIFO
heightCache *cache.FIFO
lock sync.RWMutex
}
// newHeaderCache creates a new block cache for storing/accessing headerInfo from
// memory.
func newHeaderCache() *headerCache {
return &headerCache{
hashCache: cache.NewFIFO(hashKeyFn),
heightCache: cache.NewFIFO(heightKeyFn),
}
}
// HeaderInfoByHash fetches headerInfo by its header hash. Returns true with a
// reference to the header info, if exists. Otherwise returns false, nil.
func (c *headerCache) HeaderInfoByHash(hash common.Hash) (bool, *types.HeaderInfo, error) {
c.lock.RLock()
defer c.lock.RUnlock()
obj, exists, err := c.hashCache.GetByKey(hash.Hex())
if err != nil {
return false, nil, err
}
if exists {
headerCacheHit.Inc()
} else {
headerCacheMiss.Inc()
return false, nil, nil
}
hInfo, ok := obj.(*types.HeaderInfo)
if !ok {
return false, nil, ErrNotAHeaderInfo
}
return true, hInfo.Copy(), nil
}
// HeaderInfoByHeight fetches headerInfo by its header number. Returns true with a
// reference to the header info, if exists. Otherwise returns false, nil.
func (c *headerCache) HeaderInfoByHeight(height *big.Int) (bool, *types.HeaderInfo, error) {
c.lock.RLock()
defer c.lock.RUnlock()
obj, exists, err := c.heightCache.GetByKey(height.String())
if err != nil {
return false, nil, err
}
if exists {
headerCacheHit.Inc()
} else {
headerCacheMiss.Inc()
return false, nil, nil
}
hInfo, ok := obj.(*types.HeaderInfo)
if !ok {
return false, nil, ErrNotAHeaderInfo
}
return exists, hInfo.Copy(), nil
}
// AddHeader adds a headerInfo object to the cache. This method also trims the
// least recently added header info if the cache size has reached the max cache
// size limit. This method should be called in sequential header number order if
// the desired behavior is that the blocks with the highest header number should
// be present in the cache.
func (c *headerCache) AddHeader(hdr *types.HeaderInfo) error {
c.lock.Lock()
defer c.lock.Unlock()
hInfo := hdr.Copy()
if err := c.hashCache.AddIfNotPresent(hInfo); err != nil {
return err
}
if err := c.heightCache.AddIfNotPresent(hInfo); err != nil {
return err
}
trim(c.hashCache, maxCacheSize)
trim(c.heightCache, maxCacheSize)
headerCacheSize.Set(float64(len(c.hashCache.ListKeys())))
return nil
}
// trim the FIFO queue to the maxSize.
func trim(queue *cache.FIFO, maxSize uint64) {
for s := uint64(len(queue.ListKeys())); s > maxSize; s-- {
// #nosec G104 popProcessNoopFunc never returns an error
if _, err := queue.Pop(popProcessNoopFunc); err != nil { // This never returns an error, but we'll handle anyway for sanity.
panic(err)
}
}
}
// popProcessNoopFunc is a no-op function that never returns an error.
func popProcessNoopFunc(_ interface{}) error {
return nil
}