-
Notifications
You must be signed in to change notification settings - Fork 210
/
fixedoracle.go
211 lines (172 loc) · 5.47 KB
/
fixedoracle.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
// Package eligibility defines fixed size oracle used for node testing
package eligibility
import (
"encoding/binary"
"hash/fnv"
"sync"
"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/log"
)
// FixedRolacle is an eligibility simulator with pre-determined honest and faulty participants.
type FixedRolacle struct {
honest map[string]struct{}
faulty map[string]struct{}
emaps map[uint32]map[string]struct{}
mutex sync.Mutex
mapRW sync.RWMutex
}
// New initializes the oracle with no participants.
func New() *FixedRolacle {
rolacle := &FixedRolacle{}
rolacle.honest = make(map[string]struct{})
rolacle.faulty = make(map[string]struct{})
rolacle.emaps = make(map[uint32]map[string]struct{})
return rolacle
}
// IsIdentityActiveOnConsensusView is use to satisfy the API, currently always returns true.
func (fo *FixedRolacle) IsIdentityActiveOnConsensusView(edID string, layer types.LayerID) (bool, error) {
return true, nil
}
// Export creates a map with the eligible participants for id and committee size.
func (fo *FixedRolacle) Export(id uint32, committeeSize int) map[string]struct{} {
fo.mapRW.RLock()
total := len(fo.honest) + len(fo.faulty)
fo.mapRW.RUnlock()
// normalize committee size
size := committeeSize
if committeeSize > total {
log.Warning("committee size bigger than the number of clients. Expected %v<=%v", committeeSize, total)
size = total
}
fo.mapRW.Lock()
// generate if not exist for the requested K
if _, exist := fo.emaps[id]; !exist {
fo.emaps[id] = fo.generateEligibility(size)
}
m := fo.emaps[id]
fo.mapRW.Unlock()
return m
}
func (fo *FixedRolacle) update(m map[string]struct{}, client string) {
fo.mutex.Lock()
if _, exist := m[client]; exist {
fo.mutex.Unlock()
return
}
m[client] = struct{}{}
fo.mutex.Unlock()
}
// Register adds a participant to the eligibility map. can be honest or faulty.
func (fo *FixedRolacle) Register(isHonest bool, client string) {
if isHonest {
fo.update(fo.honest, client)
} else {
fo.update(fo.faulty, client)
}
}
// Unregister removes a participant from the eligibility map. can be honest or faulty.
// TODO: just remove from both instead of specifying.
func (fo *FixedRolacle) Unregister(isHonest bool, client string) {
fo.mutex.Lock()
if isHonest {
delete(fo.honest, client)
} else {
delete(fo.faulty, client)
}
fo.mutex.Unlock()
}
func cloneMap(m map[string]struct{}) map[string]struct{} {
c := make(map[string]struct{}, len(m))
for k, v := range m {
c[k] = v
}
return c
}
func pickUnique(pickCount int, orig map[string]struct{}, dest map[string]struct{}) {
i := 0
for k := range orig { // randomly pass on clients
if i == pickCount { // pick exactly size
break
}
dest[k] = struct{}{}
delete(orig, k) // unique pick
i++
}
}
func (fo *FixedRolacle) generateEligibility(expCom int) map[string]struct{} {
emap := make(map[string]struct{}, expCom)
if expCom == 0 {
return emap
}
expHonest := expCom/2 + 1
if expHonest > len(fo.honest) {
log.Warning("Not enough registered honest. Expected %v<=%v", expHonest, len(fo.honest))
expHonest = len(fo.honest)
}
hon := cloneMap(fo.honest)
pickUnique(expHonest, hon, emap)
expFaulty := expCom - expHonest
if expFaulty > len(fo.faulty) {
if len(fo.faulty) > 0 { // not enough
log.Debug("Not enough registered dishonest to pick from. Expected %v<=%v. Picking %v instead", expFaulty, len(fo.faulty), len(fo.faulty))
} else { // no faulty at all - acceptable
log.Debug("No registered dishonest to pick from. Picking honest instead")
}
expFaulty = len(fo.faulty)
}
if expFaulty > 0 { // pick faulty if you need
fau := cloneMap(fo.faulty)
pickUnique(expFaulty, fau, emap)
}
rem := expCom - expHonest - expFaulty
if rem > 0 { // need to pickUnique the remaining from honest
pickUnique(rem, hon, emap)
}
return emap
}
func hashLayerAndRound(instanceID types.LayerID, round int32) uint32 {
kInBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(kInBytes, uint32(round))
h := fnv.New32()
_, err := h.Write(instanceID.ToBytes())
_, err2 := h.Write(kInBytes)
if err != nil || err2 != nil {
log.Error("Errors trying to create a hash %v, %v", err, err2)
}
return h.Sum32()
}
// Eligible returns whether the specific NodeID is eligible for layer in roudn and committee size.
func (fo *FixedRolacle) Eligible(layer types.LayerID, round int32, committeeSize int, id types.NodeID, sig []byte) (bool, error) {
fo.mapRW.RLock()
total := len(fo.honest) + len(fo.faulty) // safe since len >= 0
fo.mapRW.RUnlock()
// normalize committee size
size := committeeSize
if committeeSize > total {
log.Warning("committee size bigger than the number of clients. Expected %v<=%v", committeeSize, total)
size = total
}
instID := hashLayerAndRound(layer, round)
fo.mapRW.Lock()
// generate if not exist for the requested K
if _, exist := fo.emaps[instID]; !exist {
fo.emaps[instID] = fo.generateEligibility(size)
}
fo.mapRW.Unlock()
// get eligibility result
_, exist := fo.emaps[instID][id.Key]
return exist, nil
}
// Proof generates a proof for the round. used to satisfy interface.
func (fo *FixedRolacle) Proof(layer types.LayerID, round int32) ([]byte, error) {
kInBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(kInBytes, uint32(round))
hash := fnv.New32()
_, err := hash.Write(kInBytes)
if err != nil {
log.Error("Error writing hash err: %v", err)
}
hashBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(hashBytes, uint32(hash.Sum32()))
return hashBytes, nil
}