-
Notifications
You must be signed in to change notification settings - Fork 1k
/
epoch_boundary_state_cache.go
154 lines (132 loc) · 3.95 KB
/
epoch_boundary_state_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
package stategen
import (
"errors"
"strconv"
"sync"
stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state"
"k8s.io/client-go/tools/cache"
)
var (
// maxCacheSize is 8. That means 8 epochs and roughly an hour
// of no finality can be endured.
maxCacheSize = uint64(8)
errNotSlotRootInfo = errors.New("not slot root info type")
errNotRootStateInfo = errors.New("not root state info type")
)
// slotRootInfo specifies the slot root info in the epoch boundary state cache.
type slotRootInfo struct {
slot uint64
root [32]byte
}
// slotKeyFn takes the string representation of the slot to be used as key
// to retrieve root.
func slotKeyFn(obj interface{}) (string, error) {
s, ok := obj.(*slotRootInfo)
if !ok {
return "", errNotSlotRootInfo
}
return slotToString(s.slot), nil
}
// rootStateInfo specifies the root state info in the epoch boundary state cache.
type rootStateInfo struct {
root [32]byte
state *stateTrie.BeaconState
}
// rootKeyFn takes the string representation of the block root to be used as key
// to retrieve epoch boundary state.
func rootKeyFn(obj interface{}) (string, error) {
s, ok := obj.(*rootStateInfo)
if !ok {
return "", errNotRootStateInfo
}
return string(s.root[:]), nil
}
// epochBoundaryState struct with two queues by looking up beacon state by slot or root.
type epochBoundaryState struct {
rootStateCache *cache.FIFO
slotRootCache *cache.FIFO
lock sync.RWMutex
}
// newBoundaryStateCache creates a new block newBoundaryStateCache for storing and accessing epoch boundary states from
// memory.
func newBoundaryStateCache() *epochBoundaryState {
return &epochBoundaryState{
rootStateCache: cache.NewFIFO(rootKeyFn),
slotRootCache: cache.NewFIFO(slotKeyFn),
}
}
// get epoch boundary state by its block root. Returns copied state in state info object if exists. Otherwise returns nil.
func (e *epochBoundaryState) getByRoot(r [32]byte) (*rootStateInfo, bool, error) {
e.lock.RLock()
defer e.lock.RUnlock()
obj, exists, err := e.rootStateCache.GetByKey(string(r[:]))
if err != nil {
return nil, false, err
}
if !exists {
return nil, false, nil
}
s, ok := obj.(*rootStateInfo)
if !ok {
return nil, false, errNotRootStateInfo
}
return &rootStateInfo{
root: r,
state: s.state.Copy(),
}, true, nil
}
// get epoch boundary state by its slot. Returns copied state in state info object if exists. Otherwise returns nil.
func (e *epochBoundaryState) getBySlot(s uint64) (*rootStateInfo, bool, error) {
e.lock.RLock()
defer e.lock.RUnlock()
obj, exists, err := e.slotRootCache.GetByKey(slotToString(s))
if err != nil {
return nil, false, err
}
if !exists {
return nil, false, nil
}
info, ok := obj.(*slotRootInfo)
if !ok {
return nil, false, errNotSlotRootInfo
}
return e.getByRoot(info.root)
}
// put adds a state to the epoch boundary state cache. This method also trims the
// least recently added state info if the cache size has reached the max cache
// size limit.
func (e *epochBoundaryState) put(r [32]byte, s *stateTrie.BeaconState) error {
e.lock.Lock()
defer e.lock.Unlock()
if err := e.slotRootCache.AddIfNotPresent(&slotRootInfo{
slot: s.Slot(),
root: r,
}); err != nil {
return err
}
if err := e.rootStateCache.AddIfNotPresent(&rootStateInfo{
root: r,
state: s.Copy(),
}); err != nil {
return err
}
trim(e.rootStateCache, maxCacheSize)
trim(e.slotRootCache, maxCacheSize)
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-- {
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
}
// Converts input uint64 to string. To be used as key for slot to get root.
func slotToString(s uint64) string {
return strconv.FormatUint(s, 10)
}