-
Notifications
You must be signed in to change notification settings - Fork 210
/
oracle.go
318 lines (267 loc) · 9.54 KB
/
oracle.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
package eligibility
import (
"bytes"
"context"
"crypto/sha256"
"encoding/binary"
"errors"
"github.com/hashicorp/golang-lru"
"github.com/nullstyle/go-xdr/xdr3"
"github.com/spacemeshos/go-spacemesh/common/types"
eCfg "github.com/spacemeshos/go-spacemesh/hare/eligibility/config"
"github.com/spacemeshos/go-spacemesh/log"
"math"
"sync"
)
const vrfMsgCacheSize = 20 // numRounds per layer is <= 2. numConcurrentLayers<=10 (typically <=2) so numRounds*numConcurrentLayers <= 2*10 = 20 is a good upper bound
const activesCacheSize = 5 // we don't expect to handle more than two layers concurrently
var (
errGenesis = errors.New("no data about active nodes for genesis")
)
type valueProvider interface {
Value(layer types.LayerID) (uint32, error)
}
// a func to retrieve the active set size for the provided layer
// this func is assumed to be cpu intensive and hence we cache its results
type activeSetFunc func(epoch types.EpochID, blocks map[types.BlockID]struct{}) (map[string]struct{}, error)
type signer interface {
Sign(msg []byte) []byte
}
type goodBlocksProvider interface {
ContextuallyValidBlock(layer types.LayerID) (map[types.BlockID]struct{}, error)
}
// a function to verify the message with the signature and its public key.
type verifierFunc = func(pub, msg, sig []byte) bool
// Oracle is the hare eligibility oracle
type Oracle struct {
lock sync.Mutex
beacon valueProvider
getActiveSet activeSetFunc
vrfSigner signer
vrfVerifier verifierFunc
layersPerEpoch uint16
vrfMsgCache addGet
activesCache addGet
genesisActiveSetSize int
blocksProvider goodBlocksProvider
cfg eCfg.Config
log.Log
}
// Returns the relative layer id that w.h.p. we have agreement on its contextually valid blocks
// safe layer is defined to be the confidence param layers prior to the provided Layer
func safeLayer(layer types.LayerID, safetyParam types.LayerID) types.LayerID {
if layer <= types.GetEffectiveGenesis()+safetyParam { // assuming genesis is zero
return types.GetEffectiveGenesis()
}
return layer - safetyParam
}
func roundedSafeLayer(layer types.LayerID, safetyParam types.LayerID,
layersPerEpoch uint16, epochOffset types.LayerID) types.LayerID {
sl := safeLayer(layer, safetyParam)
if sl == types.GetEffectiveGenesis() {
return types.GetEffectiveGenesis()
}
se := types.LayerID(sl.GetEpoch()) // the safe epoch
roundedLayer := se*types.LayerID(layersPerEpoch) + epochOffset
if sl >= roundedLayer { // the safe layer is after the rounding threshold
return roundedLayer // round to threshold
}
if roundedLayer <= types.LayerID(layersPerEpoch) { // we can't go before genesis
return types.GetEffectiveGenesis() // just return genesis
}
// round to the previous epoch threshold
return roundedLayer - types.LayerID(layersPerEpoch)
}
// New returns a new eligibility oracle instance.
func New(beacon valueProvider, activeSetFunc activeSetFunc, vrfVerifier verifierFunc, vrfSigner signer,
layersPerEpoch uint16, genesisActiveSet int, goodBlocksProvider goodBlocksProvider,
cfg eCfg.Config, log log.Log) *Oracle {
vmc, e := lru.New(vrfMsgCacheSize)
if e != nil {
log.Panic("Could not create lru cache err=%v", e)
}
ac, e := lru.New(activesCacheSize)
if e != nil {
log.Panic("Could not create lru cache err=%v", e)
}
return &Oracle{
beacon: beacon,
getActiveSet: activeSetFunc,
vrfVerifier: vrfVerifier,
vrfSigner: vrfSigner,
layersPerEpoch: layersPerEpoch,
vrfMsgCache: vmc,
activesCache: ac,
genesisActiveSetSize: genesisActiveSet,
blocksProvider: goodBlocksProvider,
cfg: cfg,
Log: log,
}
}
type vrfMessage struct {
Beacon uint32
Round int32
Layer types.LayerID
}
func buildKey(l types.LayerID, r int32) [2]uint64 {
return [2]uint64{uint64(l), uint64(r)}
}
// buildVRFMessage builds the VRF message used as input for the BLS (msg=Beacon##Layer##Round)
func (o *Oracle) buildVRFMessage(ctx context.Context, layer types.LayerID, round int32) ([]byte, error) {
key := buildKey(layer, round)
o.lock.Lock()
defer o.lock.Unlock()
// check cache
if val, exist := o.vrfMsgCache.Get(key); exist {
return val.([]byte), nil
}
// get value from Beacon
v, err := o.beacon.Value(layer)
if err != nil {
o.WithContext(ctx).With().Error("could not get hare beacon value",
log.Err(err),
layer,
log.Int32("round", round))
return nil, err
}
// marshal message
var w bytes.Buffer
msg := vrfMessage{Beacon: v, Round: round, Layer: layer}
if _, err = xdr.Marshal(&w, &msg); err != nil {
o.WithContext(ctx).With().Error("could not marshal xdr", log.Err(err))
return nil, err
}
val := w.Bytes()
o.vrfMsgCache.Add(key, val) // update cache
return val, nil
}
func (o *Oracle) activeSetSize(layer types.LayerID) (uint32, error) {
actives, err := o.actives(layer)
if err != nil {
if err == errGenesis { // we are in genesis
return uint32(o.genesisActiveSetSize), nil
}
o.With().Error("activeSetSize erred while calling actives func", log.Err(err), layer)
return 0, err
}
return uint32(len(actives)), nil
}
// Eligible checks if ID is eligible on the given Layer where msg is the VRF message, sig is the role proof and assuming commSize as the expected committee size
func (o *Oracle) Eligible(ctx context.Context, layer types.LayerID, round int32, committeeSize int, id types.NodeID, sig []byte) (bool, error) {
o.WithContext(ctx).With().Debug("hare oracle checking eligibility")
defer func() {
o.WithContext(ctx).With().Debug("hare oracle eligibility check complete")
}()
msg, err := o.buildVRFMessage(ctx, layer, round)
if err != nil {
o.Error("eligibility: could not build vrf message")
return false, err
}
// validate message
if !o.vrfVerifier(id.VRFPublicKey, msg, sig) {
o.With().Info("eligibility: a node did not pass vrf signature verification",
id,
layer)
return false, nil
}
// get active set size
activeSetSize, err := o.activeSetSize(layer)
if err != nil {
return false, err
}
// require activeSetSize > 0
if activeSetSize == 0 {
o.Warning("eligibility: active set size is zero")
return false, errors.New("active set size is zero")
}
// calc hash & check threshold
sha := sha256.Sum256(sig)
shaUint32 := binary.LittleEndian.Uint32(sha[:4])
// avoid division (no floating point) & do operations on uint64 to avoid overflow
if uint64(activeSetSize)*uint64(shaUint32) > uint64(committeeSize)*uint64(math.MaxUint32) {
o.With().Info("eligibility: node did not pass vrf eligibility threshold",
id,
log.Int("committee_size", committeeSize),
log.Uint32("active_set_size", activeSetSize),
log.Int32("round", round),
layer)
return false, nil
}
// lower or equal
return true, nil
}
// Proof returns the role proof for the current Layer & Round
func (o *Oracle) Proof(ctx context.Context, layer types.LayerID, round int32) ([]byte, error) {
msg, err := o.buildVRFMessage(ctx, layer, round)
if err != nil {
o.WithContext(ctx).With().Error("proof: could not build vrf message", log.Err(err))
return nil, err
}
return o.vrfSigner.Sign(msg), nil
}
// Returns a map of all active nodes in the specified layer id
func (o *Oracle) actives(layer types.LayerID) (activeMap map[string]struct{}, err error) {
sl := roundedSafeLayer(layer, types.LayerID(o.cfg.ConfidenceParam), o.layersPerEpoch, types.LayerID(o.cfg.EpochOffset))
safeEp := sl.GetEpoch()
o.With().Info("safe layer and epoch", sl, safeEp)
// check genesis
// genesis is for 3 epochs with hare since it can only count active identities found in blocks
if safeEp < 3 {
return nil, errGenesis
}
// lock until any return
// note: no need to lock per safeEp - we do not expect many concurrent requests per safeEp (max two)
o.lock.Lock()
defer o.lock.Unlock()
// check cache
if val, exist := o.activesCache.Get(safeEp); exist {
return val.(map[string]struct{}), nil
}
// build a map of all blocks on the current layer
mp, err2 := o.blocksProvider.ContextuallyValidBlock(sl)
if err2 != nil {
return nil, err2
}
// no contextually valid blocks: for now we just fall back on an empty active set. this will go away when we
// upgrade hare eligibility to use the tortoise beacon.
if len(mp) == 0 {
o.With().Warning("no contextually valid blocks in layer, using active set of size zero",
layer,
layer.GetEpoch(),
log.FieldNamed("safe_layer_id", sl),
log.FieldNamed("safe_epoch_id", safeEp))
return
}
activeMap, err = o.getActiveSet(safeEp-1, mp)
if err != nil {
o.With().Error("could not retrieve active set size",
log.Err(err),
layer,
layer.GetEpoch(),
log.FieldNamed("safe_layer_id", sl),
log.FieldNamed("safe_epoch_id", safeEp))
return nil, err
}
// update cache
o.activesCache.Add(safeEp, activeMap)
return
}
// IsIdentityActiveOnConsensusView returns true if the provided identity is active on the consensus view derived
// from the specified layer, false otherwise.
func (o *Oracle) IsIdentityActiveOnConsensusView(ctx context.Context, edID string, layer types.LayerID) (bool, error) {
o.WithContext(ctx).With().Debug("hare oracle checking for active identity")
defer func() {
o.WithContext(ctx).With().Debug("hare oracle active identity check complete")
}()
actives, err := o.actives(layer)
if err != nil {
if err == errGenesis { // we are in genesis
return true, nil // all ids are active in genesis
}
o.With().Error("method IsIdentityActiveOnConsensusView erred while calling actives func",
layer, log.Err(err))
return false, err
}
_, exist := actives[edID]
return exist, nil
}