-
Notifications
You must be signed in to change notification settings - Fork 211
/
preroundtracker.go
150 lines (135 loc) · 5.16 KB
/
preroundtracker.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
package hare
import (
"context"
"encoding/binary"
"fmt"
"math"
"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/hash"
"github.com/spacemeshos/go-spacemesh/log"
)
type preroundData struct {
*Set
*types.HareProofMsg
}
// preRoundTracker tracks pre-round messages.
// The tracker can be queried to check if a value or a set is provable.
// It also provides the ability to filter set from unprovable values.
type preRoundTracker struct {
logger log.Log
malCh chan<- *types.MalfeasanceGossip
preRound map[string]*preroundData // maps PubKey->Set of already tracked Values
tracker *RefCountTracker // keeps track of seen Values
threshold int // the threshold to prove a value
bestVRF uint32 // the lowest VRF value seen in the round
coinflip bool // the value of the weak coin (based on bestVRF)
eTracker *EligibilityTracker
}
func newPreRoundTracker(logger log.Log, mch chan<- *types.MalfeasanceGossip, et *EligibilityTracker, threshold, expectedSize int) *preRoundTracker {
return &preRoundTracker{
logger: logger,
malCh: mch,
preRound: make(map[string]*preroundData, expectedSize),
tracker: NewRefCountTracker(preRound, et, expectedSize),
threshold: threshold,
bestVRF: math.MaxUint32,
eTracker: et,
}
}
// OnPreRound tracks pre-round messages.
func (pre *preRoundTracker) OnPreRound(ctx context.Context, msg *Msg) {
logger := pre.logger.WithContext(ctx)
nodeID := types.BytesToNodeID(msg.PubKey.Bytes())
// check for winning VRF
vrfHash := hash.Sum(msg.Eligibility.Proof)
vrfHashVal := binary.LittleEndian.Uint32(vrfHash[:4])
logger.With().Debug("received preround message",
nodeID,
log.Stringer("smesher", nodeID),
log.Int("num_values", len(msg.InnerMsg.Values)),
log.Uint32("vrf_value", vrfHashVal))
if vrfHashVal < pre.bestVRF {
pre.bestVRF = vrfHashVal
// store lowest-order bit as coin toss value
pre.coinflip = vrfHash[0]&byte(1) == byte(1)
pre.logger.With().Debug("got new best vrf value",
log.Stringer("smesher", nodeID),
log.String("vrf_value", fmt.Sprintf("%x", vrfHashVal)),
log.Bool("weak_coin", pre.coinflip))
}
sToTrack := NewSet(msg.InnerMsg.Values) // assume track all Values
alreadyTracked := NewDefaultEmptySet() // assume nothing tracked so far
if prev, exist := pre.preRound[string(msg.PubKey.Bytes())]; exist { // not first pre-round msg from this sender
if prev.InnerMsg.Layer == msg.Layer &&
prev.InnerMsg.Round == msg.Round &&
prev.InnerMsg.MsgHash != msg.MsgHash {
pre.logger.WithContext(ctx).With().Warning("equivocation detected in preround",
log.Stringer("smesher", nodeID),
log.Object("prev", &prev.InnerMsg),
log.Object("curr", &msg.HareMetadata),
)
pre.eTracker.Track(msg.PubKey.Bytes(), msg.Round, msg.Eligibility.Count, false)
this := &types.HareProofMsg{
InnerMsg: msg.HareMetadata,
Signature: msg.Signature,
}
if err := reportEquivocation(ctx, msg.PubKey.Bytes(), prev.HareProofMsg, this, &msg.Eligibility, pre.malCh); err != nil {
pre.logger.WithContext(ctx).With().Warning("failed to report equivocation in preround",
log.Stringer("smesher", nodeID),
log.Err(err))
return
}
}
logger.With().Debug("duplicate preround msg sender", log.Stringer("smesher", nodeID))
alreadyTracked = prev.Set // update already tracked Values
sToTrack.Subtract(alreadyTracked) // subtract the already tracked Values
} else {
pre.preRound[string(msg.PubKey.Bytes())] = &preroundData{}
}
// record Values
for _, v := range sToTrack.ToSlice() {
pre.tracker.Track(v, msg.PubKey.Bytes())
}
// update the union to include new Values
pre.preRound[string(msg.PubKey.Bytes())].Set = alreadyTracked.Union(sToTrack)
pre.preRound[string(msg.PubKey.Bytes())].HareProofMsg = &types.HareProofMsg{
InnerMsg: msg.HareMetadata,
Signature: msg.Signature,
}
}
// CanProveValue returns true if the given value is provable, false otherwise.
// a value is said to be provable if it has at least threshold pre-round votes to support it.
func (pre *preRoundTracker) CanProveValue(value types.ProposalID) bool {
// at least threshold occurrences of a given value
countStatus := pre.tracker.CountStatus(value)
if countStatus == nil {
pre.logger.Fatal("unexpected count")
}
if countStatus.numDishonest > 0 {
pre.logger.With().Warning("counting votes from malicious identities", value, log.Inline(countStatus))
}
pre.logger.With().Debug("preround tracker count",
value,
log.Inline(countStatus),
log.Int("threshold", pre.threshold))
return countStatus.Meet(pre.threshold)
}
// CanProveSet returns true if the give set is provable, false otherwise.
// a set is said to be provable if all his values are provable.
func (pre *preRoundTracker) CanProveSet(set *Set) bool {
// a set is provable iff all its Values are provable
for _, bid := range set.ToSlice() {
if !pre.CanProveValue(bid) {
return false
}
}
return true
}
// FilterSet filters out non-provable values from the given set.
func (pre *preRoundTracker) FilterSet(set *Set) {
for _, v := range set.ToSlice() {
if !pre.CanProveValue(v) { // not enough witnesses
set.Remove(v)
}
}
}