gecko/snow/engine/common/bootstrapper.go

171 lines
5.6 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package common
import (
stdmath "math"
"time"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/math"
)
const (
// MaxContainersPerMultiPut is the maximum number of containers that can be sent in a MultiPut
MaxContainersPerMultiPut = 2000
// StatusUpdateFrequency ... bootstrapper logs "processed X blocks/vertices" every [statusUpdateFrequency] blocks/vertices
StatusUpdateFrequency = 2500
// MaxOutstandingRequests is the maximum number of GetAncestors sent but not responsded to/failed
MaxOutstandingRequests = 8
)
var (
// 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
// 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
RequestID uint32
}
// Initialize implements the Engine interface.
func (b *Bootstrapper) Initialize(config Config) {
b.Config = config
for _, vdr := range b.Beacons.List() {
vdrID := vdr.ID()
b.pendingAcceptedFrontier.Add(vdrID)
b.pendingAccepted.Add(vdrID)
}
b.acceptedVotes = make(map[[32]byte]uint64)
}
// Startup implements the Engine interface.
func (b *Bootstrapper) Startup() error {
if b.pendingAcceptedFrontier.Len() == 0 {
b.Context.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
b.AcceptedFrontier(validatorID, requestID, ids.Set{})
return nil
}
// AcceptedFrontier implements the Engine interface.
func (b *Bootstrapper) AcceptedFrontier(validatorID ids.ShortID, requestID uint32, containerIDs ids.Set) error {
if !b.pendingAcceptedFrontier.Contains(validatorID) {
b.Context.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.Context.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 vdr, ok := b.Beacons.Get(validatorID); ok {
weight = vdr.Weight()
}
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.Config.Alpha {
accepted.Add(ids.NewID(key))
}
}
if size := accepted.Len(); size == 0 && b.Config.Beacons.Len() > 0 {
b.Context.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.Context.Log.Info("Bootstrapping started syncing with %d vertices in the accepted frontier", size)
}
return b.Bootstrapable.ForceAccepted(accepted)
}