-
Notifications
You must be signed in to change notification settings - Fork 669
/
bootstrapper.go
224 lines (185 loc) · 6.74 KB
/
bootstrapper.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
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package common
import (
"time"
stdmath "math"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/math"
)
const (
// MaxContainersPerMultiPut is the maximum number of containers that can be
// sent in a MultiPut message
MaxContainersPerMultiPut = 2000
// StatusUpdateFrequency is how many containers should be processed between
// logs
StatusUpdateFrequency = 2500
// MaxOutstandingRequests is the maximum number of GetAncestors sent but not
// responded to/failed
MaxOutstandingRequests = 8
// MaxTimeFetchingAncestors is the maximum amount of time to spend fetching
// vertices during a call to GetAncestors
MaxTimeFetchingAncestors = 50 * time.Millisecond
)
// Bootstrapper implements the Engine interface.
type Bootstrapper struct {
Config
RequestID uint32
// IDs of validators we have requested the accepted frontier from but haven't
// received a reply from
pendingAcceptedFrontier ids.ShortSet
acceptedFrontier ids.Set
pendingAccepted ids.ShortSet
acceptedVotes map[[32]byte]uint64
// current weight
started bool
weight uint64
}
// Initialize implements the Engine interface.
func (b *Bootstrapper) Initialize(config Config) error {
b.Config = config
beacons, err := b.Beacons.Sample(config.SampleK)
if err != nil {
return err
}
for _, vdr := range beacons {
vdrID := vdr.ID()
b.pendingAcceptedFrontier.Add(vdrID)
}
for _, vdr := range b.Beacons.List() {
vdrID := vdr.ID()
b.pendingAccepted.Add(vdrID)
}
b.acceptedVotes = make(map[[32]byte]uint64)
if b.Config.StartupAlpha > 0 {
return nil
}
return b.Startup()
}
// Startup implements the Engine interface.
func (b *Bootstrapper) Startup() error {
b.started = true
if b.pendingAcceptedFrontier.Len() == 0 {
b.Ctx.Log.Info("Bootstrapping skipped due to no provided bootstraps")
return b.Bootstrapable.ForceAccepted(ids.Set{})
}
// Ask each of the bootstrap validators to send their accepted frontier
vdrs := ids.ShortSet{}
vdrs.Union(b.pendingAcceptedFrontier)
b.RequestID++
b.Sender.GetAcceptedFrontier(vdrs, b.RequestID)
return nil
}
// GetAcceptedFrontier implements the Engine interface.
func (b *Bootstrapper) GetAcceptedFrontier(validatorID ids.ShortID, requestID uint32) error {
b.Sender.AcceptedFrontier(validatorID, requestID, b.Bootstrapable.CurrentAcceptedFrontier())
return nil
}
// GetAcceptedFrontierFailed implements the Engine interface.
func (b *Bootstrapper) GetAcceptedFrontierFailed(validatorID ids.ShortID, requestID uint32) error {
// If we can't get a response from [validatorID], act as though they said their accepted frontier is empty
return b.AcceptedFrontier(validatorID, requestID, ids.Set{})
}
// AcceptedFrontier implements the Engine interface.
func (b *Bootstrapper) AcceptedFrontier(validatorID ids.ShortID, requestID uint32, containerIDs ids.Set) error {
if !b.pendingAcceptedFrontier.Contains(validatorID) {
b.Ctx.Log.Debug("Received an AcceptedFrontier message from %s unexpectedly", validatorID)
return nil
}
// Mark that we received a response from [validatorID]
b.pendingAcceptedFrontier.Remove(validatorID)
// Union the reported accepted frontier from [validatorID] with the accepted frontier we got from others
b.acceptedFrontier.Union(containerIDs)
// We've received the accepted frontier from every bootstrap validator
// Ask each bootstrap validator to filter the list of containers that we were
// told are on the accepted frontier such that the list only contains containers
// they think are accepted
if b.pendingAcceptedFrontier.Len() == 0 {
vdrs := ids.ShortSet{}
vdrs.Union(b.pendingAccepted)
b.RequestID++
b.Sender.GetAccepted(vdrs, b.RequestID, b.acceptedFrontier)
}
return nil
}
// GetAccepted implements the Engine interface.
func (b *Bootstrapper) GetAccepted(validatorID ids.ShortID, requestID uint32, containerIDs ids.Set) error {
b.Sender.Accepted(validatorID, requestID, b.Bootstrapable.FilterAccepted(containerIDs))
return nil
}
// GetAcceptedFailed implements the Engine interface.
func (b *Bootstrapper) GetAcceptedFailed(validatorID ids.ShortID, requestID uint32) error {
// If we can't get a response from [validatorID], act as though they said
// that they think none of the containers we sent them in GetAccepted are accepted
return b.Accepted(validatorID, requestID, ids.Set{})
}
// Accepted implements the Engine interface.
func (b *Bootstrapper) Accepted(validatorID ids.ShortID, requestID uint32, containerIDs ids.Set) error {
if !b.pendingAccepted.Contains(validatorID) {
b.Ctx.Log.Debug("Received an Accepted message from %s unexpectedly", validatorID)
return nil
}
// Mark that we received a response from [validatorID]
b.pendingAccepted.Remove(validatorID)
weight := uint64(0)
if w, ok := b.Beacons.GetWeight(validatorID); ok {
weight = w
}
for _, containerID := range containerIDs.List() {
key := containerID.Key()
previousWeight := b.acceptedVotes[key]
newWeight, err := math.Add64(weight, previousWeight)
if err != nil {
newWeight = stdmath.MaxUint64
}
b.acceptedVotes[key] = newWeight
}
if b.pendingAccepted.Len() != 0 {
return nil
}
// We've received the filtered accepted frontier from every bootstrap validator
// Accept all containers that have a sufficient weight behind them
accepted := ids.Set{}
for key, weight := range b.acceptedVotes {
if weight >= b.Alpha {
accepted.Add(ids.NewID(key))
}
}
if size := accepted.Len(); size == 0 && b.Beacons.Len() > 0 {
b.Ctx.Log.Info("Bootstrapping finished with no accepted frontier. This is likely a result of failing to be able to connect to the specified bootstraps, or no transactions have been issued on this chain yet")
} else {
b.Ctx.Log.Info("Bootstrapping started syncing with %d vertices in the accepted frontier", size)
}
return b.Bootstrapable.ForceAccepted(accepted)
}
// Connected implements the Engine interface.
func (b *Bootstrapper) Connected(validatorID ids.ShortID) error {
if b.started {
return nil
}
weight, ok := b.Beacons.GetWeight(validatorID)
if !ok {
return nil
}
weight, err := math.Add64(weight, b.weight)
if err != nil {
return err
}
b.weight = weight
if b.weight < b.StartupAlpha {
return nil
}
return b.Startup()
}
// Disconnected implements the Engine interface.
func (b *Bootstrapper) Disconnected(validatorID ids.ShortID) error {
if weight, ok := b.Beacons.GetWeight(validatorID); ok {
// TODO: Account for weight changes in a more robust manner.
// Sub64 should rarely error since only validators that have added their
// weight can become disconnected. Because it is possible that there are
// changes to the validators set, we utilize that Sub64 returns 0 on
// error.
b.weight, _ = math.Sub64(b.weight, weight)
}
return nil
}