-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathpowertable.go
270 lines (244 loc) · 8.26 KB
/
powertable.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
package gpbft
import (
"bytes"
"errors"
"fmt"
"maps"
"slices"
"sort"
"github.com/filecoin-project/go-state-types/big"
)
var _ sort.Interface = (*PowerTable)(nil)
var _ sort.Interface = (PowerEntries)(nil)
// PowerEntry represents a single entry in the PowerTable, including ActorID and its StoragePower and PubKey.
type PowerEntry struct {
ID ActorID
Power StoragePower
PubKey PubKey `cborgen:"maxlen=48"`
}
type PowerEntries []PowerEntry
// PowerTable maps ActorID to a unique index in the range [0, len(powerTable.Entries)).
// Entries is the reverse mapping to a PowerEntry.
type PowerTable struct {
Entries PowerEntries // Slice to maintain the order. Meant to be maintained in order in order by (Power descending, ID ascending)
ScaledPower []int64
Lookup map[ActorID]int // Maps ActorID to the index of the associated entry in Entries
Total StoragePower
ScaledTotal int64
}
func (e PowerEntries) PublicKeys() []PubKey {
keys := make([]PubKey, len(e))
for i, e := range e {
keys[i] = e.PubKey
}
return keys
}
func (p *PowerEntry) Equal(o *PowerEntry) bool {
return p.ID == o.ID && p.Power.Equals(o.Power) && bytes.Equal(p.PubKey, o.PubKey)
}
func (p PowerEntries) Equal(o PowerEntries) bool {
if len(p) != len(o) {
return false
}
for i := range p {
if !p[i].Equal(&o[i]) {
return false
}
}
return true
}
// Len returns the number of entries in this PowerTable.
func (p PowerEntries) Len() int {
return len(p)
}
// Less determines if the entry at index i should be sorted before the entry at index j.
// Entries are sorted descending order of their power, where entries with equal power are
// sorted by ascending order of their ID.
// This ordering is guaranteed to be stable, since a valid PowerTable cannot contain entries with duplicate IDs; see Validate.
func (p PowerEntries) Less(i, j int) bool {
one, other := p[i], p[j]
switch cmp := big.Cmp(one.Power, other.Power); {
case cmp > 0:
return true
case cmp == 0:
return one.ID < other.ID
default:
return false
}
}
// Swap swaps the entry at index i with the entry at index j.
// This function must not be called directly since it is used as part of sort.Interface.
func (p PowerEntries) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
func (p PowerEntries) Scaled() (scaled []int64, total int64, err error) {
totalUnscaled := big.Zero()
for i := range p {
pwr := p[i].Power
if pwr.Sign() <= 0 {
return nil, 0, fmt.Errorf("invalid non-positive power %s for participant %d", pwr, p[i].ID)
}
totalUnscaled = big.Add(totalUnscaled, pwr)
}
scaled = make([]int64, len(p))
for i := range p {
p, err := scalePower(p[i].Power, totalUnscaled)
if err != nil {
// We just summed the total power, this operation can't fail.
panic(err)
}
scaled[i] = p
total += p
}
return scaled, total, nil
}
// NewPowerTable creates a new PowerTable from a slice of PowerEntry .
// It is more efficient than Add, as it only needs to sort the entries once.
func NewPowerTable() *PowerTable {
return &PowerTable{
Lookup: make(map[ActorID]int),
Total: NewStoragePower(0),
}
}
// Add inserts one or more entries to this PowerTable.
//
// Each inserted entry must meet the following criteria:
// * It must not already be present int the PowerTable.
// * It must have StoragePower larger than zero.
// * It must have a non-zero length public key.
func (p *PowerTable) Add(entries ...PowerEntry) error {
for _, entry := range entries {
switch {
case len(entry.PubKey) == 0:
return fmt.Errorf("unspecified public key for actor ID: %d", entry.ID)
case p.Has(entry.ID):
return fmt.Errorf("power entry already exists for actor ID: %d", entry.ID)
case entry.Power.Sign() <= 0:
return fmt.Errorf("zero power for actor ID: %d", entry.ID)
default:
p.Total = big.Add(p.Total, entry.Power)
p.Entries = append(p.Entries, entry)
p.ScaledPower = append(p.ScaledPower, 0)
p.Lookup[entry.ID] = len(p.Entries) - 1
}
}
sort.Sort(p)
return p.rescale()
}
func (p *PowerTable) rescale() error {
p.ScaledTotal = 0
for i := range p.Entries {
scaled, err := scalePower(p.Entries[i].Power, p.Total)
if err != nil {
return err
}
p.ScaledPower[i] = scaled
p.ScaledTotal += scaled
}
return nil
}
// Get retrieves the scaled power, unscaled StoragePower and PubKey for the given id, if present in
// the table. Otherwise, returns 0/nil.
func (p *PowerTable) Get(id ActorID) (int64, PubKey) {
if index, ok := p.Lookup[id]; ok {
key := p.Entries[index].PubKey
scaledPower := p.ScaledPower[index]
return scaledPower, key
}
return 0, nil
}
// Has check whether this PowerTable contains an entry for the given id.
func (p *PowerTable) Has(id ActorID) bool {
_, found := p.Lookup[id]
return found
}
// Copy creates a deep copy of this PowerTable.
func (p *PowerTable) Copy() *PowerTable {
replica := NewPowerTable()
replica.Entries = slices.Clone(p.Entries)
replica.ScaledPower = slices.Clone(p.ScaledPower)
replica.Lookup = maps.Clone(p.Lookup)
replica.ScaledTotal = p.ScaledTotal
replica.Total = big.Add(replica.Total, p.Total)
return replica
}
// Len returns the number of entries in this PowerTable.
func (p *PowerTable) Len() int {
return p.Entries.Len()
}
// Less determines if the entry at index i should be sorted before the entry at index j.
// Entries are sorted descending order of their power, where entries with equal power are
// sorted by ascending order of their ID.
// This ordering is guaranteed to be stable, since a valid PowerTable cannot contain entries with duplicate IDs; see Validate.
func (p *PowerTable) Less(i, j int) bool {
return p.Entries.Less(i, j)
}
// Swap swaps the entry at index i with the entry at index j.
// This function must not be called directly since it is used as part of sort.Interface.
func (p *PowerTable) Swap(i, j int) {
p.Entries.Swap(i, j)
p.ScaledPower[i], p.ScaledPower[j] = p.ScaledPower[j], p.ScaledPower[i]
p.Lookup[p.Entries[i].ID], p.Lookup[p.Entries[j].ID] = i, j
}
// Validate checks the validity of this PowerTable.
// Such table must meet the following criteria:
// * Its entries must be in order as defined by Less.
// * It must not contain any entries with duplicate ID.
// * All entries must have power larger than zero
// * All entries must have non-zero public key.
// * All entries must match their scaled powers.
// * PowerTable.Total must correspond to the total aggregated power of entries.
// * PowerTable.ScaledTotal must correspond to the total aggregated scaled power.
// * PowerTable.Lookup must contain the expected mapping of entry actor ID to index.
func (p *PowerTable) Validate() error {
if len(p.Entries) != len(p.Lookup) {
return errors.New("inconsistent entries and lookup map")
}
if len(p.Entries) != len(p.ScaledPower) {
return errors.New("inconsistent entries and scaled power")
}
total := NewStoragePower(0)
totalScaled := 0 // int instead of int64 to detect overflow
var previous *PowerEntry
for index, entry := range p.Entries {
if lookupIndex, found := p.Lookup[entry.ID]; !found || index != lookupIndex {
return fmt.Errorf("lookup index does not match entries for actor ID: %d", entry.ID)
}
if len(entry.PubKey) == 0 {
return fmt.Errorf("unspecified public key for actor ID: %d", entry.ID)
}
if entry.Power.Sign() <= 0 {
return fmt.Errorf("zero power for entry with actor ID: %d", entry.ID)
}
if previous != nil && !p.Less(index-1, index) {
return fmt.Errorf("entry not in order at index: %d", index)
}
scaledPower, err := scalePower(entry.Power, p.Total)
if err != nil {
return fmt.Errorf("failed to scale power at index %d: %w", index, err)
}
if scaledPower != p.ScaledPower[index] {
return fmt.Errorf("incorrect scaled power at index: %d", index)
}
total = big.Add(total, entry.Power)
totalScaled += int(scaledPower)
previous = &entry
}
if !total.Equals(p.Total) {
return errors.New("total power does not match entries")
}
if int(p.ScaledTotal) != totalScaled {
return errors.New("scaled total power does not match entries")
}
return nil
}
func scalePower(power, total StoragePower) (int64, error) {
const maxPower = 0xffff
if total.LessThan(power) {
return 0, fmt.Errorf("total power %d is less than the power of a single participant %d", total, power)
}
scaled := big.NewInt(maxPower)
scaled = big.Mul(scaled, power)
scaled = big.Div(scaled, total)
return scaled.Int64(), nil
}