-
Notifications
You must be signed in to change notification settings - Fork 2
/
one-node-staked-vote.go
331 lines (288 loc) · 8.89 KB
/
one-node-staked-vote.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
319
320
321
322
323
324
325
326
327
328
329
330
331
package quorum
import (
"bytes"
"encoding/json"
"math/big"
"github.com/PositionExchange/posichain/crypto/bls"
"github.com/PositionExchange/posichain/internal/utils"
bls_core "github.com/PositionExchange/bls/ffi/go/bls"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/PositionExchange/posichain/consensus/votepower"
bls_cosi "github.com/PositionExchange/posichain/crypto/bls"
"github.com/PositionExchange/posichain/numeric"
"github.com/PositionExchange/posichain/shard"
)
var (
twoThird = numeric.NewDec(2).Quo(numeric.NewDec(3))
)
// TallyResult is the result of when we calculate voting power,
// recall that it happens to us at epoch change
type TallyResult struct {
ourPercent numeric.Dec
theirPercent numeric.Dec
}
type tallyAndQuorum struct {
tally numeric.Dec
quorumAchieved bool
}
// VoteTally is the vote tally for each phase
type VoteTally struct {
Prepare *tallyAndQuorum
Commit *tallyAndQuorum
ViewChange *tallyAndQuorum
}
type stakedVoteWeight struct {
SignatureReader
roster votepower.Roster
voteTally VoteTally
lastPower map[Phase]numeric.Dec
}
// Policy ..
func (v *stakedVoteWeight) Policy() Policy {
return SuperMajorityStake
}
// AddNewVote ..
func (v *stakedVoteWeight) AddNewVote(
p Phase, pubKeys []*bls_cosi.PublicKeyWrapper,
sig *bls_core.Sign, headerHash common.Hash,
height, viewID uint64) (*votepower.Ballot, error) {
pubKeysBytes := make([]bls.SerializedPublicKey, len(pubKeys))
signerAddr := common.Address{}
for i, pubKey := range pubKeys {
voter, ok := v.roster.Voters[pubKey.Bytes]
if !ok {
return nil, errors.Errorf("Signer not in committee: %x", pubKey.Bytes)
}
if i == 0 {
signerAddr = voter.EarningAccount
} else {
// Aggregated signature should not contain signatures from keys belonging to different accounts,
// to avoid malicious node catching other people's signatures and merge with their own to cause problems.
// Harmony nodes are excluded from this rule.
if bytes.Compare(signerAddr.Bytes(), voter.EarningAccount[:]) != 0 && !voter.IsHarmonyNode {
return nil, errors.Errorf("Multiple signer accounts used in multi-sig: %x, %x", signerAddr.Bytes(), voter.EarningAccount)
}
}
pubKeysBytes[i] = pubKey.Bytes
}
ballet, err := v.submitVote(p, pubKeysBytes, sig, headerHash, height, viewID)
if err != nil {
return ballet, err
}
// Accumulate total voting power
additionalVotePower := numeric.NewDec(0)
for _, pubKeyBytes := range pubKeysBytes {
votingPower := v.roster.Voters[pubKeyBytes].OverallPercent
utils.Logger().Debug().
Str("signer", pubKeyBytes.Hex()).
Str("votingPower", votingPower.String()).
Msg("Signer vote counted")
additionalVotePower = additionalVotePower.Add(votingPower)
}
tallyQuorum := func() *tallyAndQuorum {
switch p {
case Prepare:
return v.voteTally.Prepare
case Commit:
return v.voteTally.Commit
case ViewChange:
return v.voteTally.ViewChange
default:
// Should not happen
return nil
}
}()
tallyQuorum.tally = tallyQuorum.tally.Add(additionalVotePower)
t := v.QuorumThreshold()
msg := "[AddNewVote] New Vote Added!"
if !tallyQuorum.quorumAchieved {
tallyQuorum.quorumAchieved = tallyQuorum.tally.GT(t)
if tallyQuorum.quorumAchieved {
msg = "[AddNewVote] Quorum Achieved!"
}
}
utils.Logger().Info().
Str("phase", p.String()).
Int64("signer-count", v.SignersCount(p)).
Str("new-power-added", additionalVotePower.String()).
Str("total-power-of-signers", tallyQuorum.tally.String()).
Msg(msg)
return ballet, nil
}
// IsQuorumAchieved ..
func (v *stakedVoteWeight) IsQuorumAchieved(p Phase) bool {
switch p {
case Prepare:
return v.voteTally.Prepare.quorumAchieved
case Commit:
return v.voteTally.Commit.quorumAchieved
case ViewChange:
return v.voteTally.ViewChange.quorumAchieved
default:
// Should not happen
return false
}
}
// IsQuorumAchivedByMask ..
func (v *stakedVoteWeight) IsQuorumAchievedByMask(mask *bls_cosi.Mask) bool {
threshold := v.QuorumThreshold()
if mask == nil {
return false
}
currentTotalPower := v.computeTotalPowerByMask(mask)
if currentTotalPower == nil {
return false
}
return (*currentTotalPower).GT(threshold)
}
func (v *stakedVoteWeight) currentTotalPower(p Phase) (*numeric.Dec, error) {
switch p {
case Prepare:
return &v.voteTally.Prepare.tally, nil
case Commit:
return &v.voteTally.Commit.tally, nil
case ViewChange:
return &v.voteTally.ViewChange.tally, nil
default:
// Should not happen
return nil, errors.New("wrong phase is provided")
}
}
// ComputeTotalPowerByMask computes the total power indicated by bitmap mask
func (v *stakedVoteWeight) computeTotalPowerByMask(mask *bls_cosi.Mask) *numeric.Dec {
currentTotal := numeric.ZeroDec()
for key, i := range mask.PublicsIndex {
if enabled, err := mask.IndexEnabled(i); err == nil && enabled {
if voter, ok := v.roster.Voters[key]; ok {
currentTotal = currentTotal.Add(
voter.OverallPercent,
)
}
}
}
return ¤tTotal
}
// QuorumThreshold ..
func (v *stakedVoteWeight) QuorumThreshold() numeric.Dec {
return twoThird
}
// IsAllSigsCollected ..
func (v *stakedVoteWeight) IsAllSigsCollected() bool {
return v.voteTally.Commit.tally.Equal(numeric.NewDec(1))
}
func (v *stakedVoteWeight) SetVoters(
subCommittee *shard.Committee, epoch *big.Int,
) (*TallyResult, error) {
v.ResetPrepareAndCommitVotes()
v.ResetViewChangeVotes()
roster, err := votepower.Compute(subCommittee, epoch)
if err != nil {
return nil, err
}
// Hold onto this calculation
v.roster = *roster
utils.Logger().Debug().
Uint64("curEpoch", epoch.Uint64()).
Uint32("shard-id", subCommittee.ShardID).
Str("committee", roster.String()).
Msg("[SetVoters] Successfully updated voters")
return &TallyResult{
roster.OurVotingPowerTotalPercentage,
roster.TheirVotingPowerTotalPercentage,
}, nil
}
func (v *stakedVoteWeight) String() string {
s, _ := json.Marshal(v)
return string(s)
}
// HACK later remove - unify votepower in UI (aka MarshalJSON)
func (v *stakedVoteWeight) SetRawStake(key bls.SerializedPublicKey, d numeric.Dec) {
if voter, ok := v.roster.Voters[key]; ok {
voter.RawStake = d
}
}
// TODO remove this large method, use roster's own Marshal, mix it
// specific logic here
func (v *stakedVoteWeight) MarshalJSON() ([]byte, error) {
voterCount := len(v.roster.Voters)
type u struct {
IsHarmony bool `json:"is-harmony-slot"`
EarningAccount string `json:"earning-account"`
Identity string `json:"bls-public-key"`
RawPercent string `json:"voting-power-unnormalized"`
VotingPower string `json:"voting-power-%"`
EffectiveStake string `json:"effective-stake,omitempty"`
RawStake string `json:"raw-stake,omitempty"`
}
type t struct {
Policy string `json:"policy"`
Count int `json:"count"`
Externals int `json:"external-validator-slot-count"`
Participants []u `json:"committee-members"`
HmyVotingPower string `json:"hmy-voting-power"`
StakedVotingPower string `json:"staked-voting-power"`
TotalRawStake string `json:"total-raw-stake"`
TotalEffectiveStake string `json:"total-effective-stake"`
}
parts := make([]u, voterCount)
i, externalCount := 0, 0
totalRaw := numeric.ZeroDec()
for _, slot := range v.roster.OrderedSlots {
identity := slot
voter := v.roster.Voters[slot]
member := u{
voter.IsHarmonyNode,
voter.EarningAccount.Hex(),
identity.Hex(),
voter.GroupPercent.String(),
voter.OverallPercent.String(),
"",
"",
}
if !voter.IsHarmonyNode {
externalCount++
member.EffectiveStake = voter.EffectiveStake.String()
member.RawStake = voter.RawStake.String()
totalRaw = totalRaw.Add(voter.RawStake)
}
parts[i] = member
i++
}
return json.Marshal(t{
v.Policy().String(),
voterCount,
externalCount,
parts,
v.roster.OurVotingPowerTotalPercentage.String(),
v.roster.TheirVotingPowerTotalPercentage.String(),
totalRaw.String(),
v.roster.TotalEffectiveStake.String(),
})
}
func newVoteTally() VoteTally {
return VoteTally{
Prepare: &tallyAndQuorum{numeric.NewDec(0), false},
Commit: &tallyAndQuorum{numeric.NewDec(0), false},
ViewChange: &tallyAndQuorum{numeric.NewDec(0), false},
}
}
func (v *stakedVoteWeight) ResetPrepareAndCommitVotes() {
v.lastPower[Prepare] = v.voteTally.Prepare.tally
v.lastPower[Commit] = v.voteTally.Commit.tally
v.reset([]Phase{Prepare, Commit})
v.voteTally.Prepare = &tallyAndQuorum{numeric.NewDec(0), false}
v.voteTally.Commit = &tallyAndQuorum{numeric.NewDec(0), false}
}
func (v *stakedVoteWeight) ResetViewChangeVotes() {
v.lastPower[ViewChange] = v.voteTally.ViewChange.tally
v.reset([]Phase{ViewChange})
v.voteTally.ViewChange = &tallyAndQuorum{numeric.NewDec(0), false}
}
func (v *stakedVoteWeight) CurrentTotalPower(p Phase) (*numeric.Dec, error) {
if power, ok := v.lastPower[p]; ok {
return &power, nil
} else {
return nil, errors.New("stakedVoteWeight not cache this phase")
}
}