2020-03-10 12:20:34 -07:00
|
|
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
|
|
|
|
package avalanche
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/ava-labs/gecko/ids"
|
|
|
|
"github.com/ava-labs/gecko/snow/choices"
|
|
|
|
"github.com/ava-labs/gecko/snow/consensus/avalanche"
|
|
|
|
"github.com/ava-labs/gecko/snow/engine/common"
|
|
|
|
"github.com/ava-labs/gecko/snow/engine/common/queue"
|
|
|
|
"github.com/ava-labs/gecko/utils/formatting"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
)
|
|
|
|
|
|
|
|
// BootstrapConfig ...
|
|
|
|
type BootstrapConfig struct {
|
|
|
|
common.Config
|
|
|
|
|
|
|
|
// VtxBlocked tracks operations that are blocked on vertices
|
|
|
|
// TxBlocked tracks operations that are blocked on transactions
|
|
|
|
VtxBlocked, TxBlocked *queue.Jobs
|
|
|
|
|
|
|
|
State State
|
|
|
|
VM DAGVM
|
|
|
|
}
|
|
|
|
|
|
|
|
type bootstrapper struct {
|
|
|
|
BootstrapConfig
|
|
|
|
metrics
|
|
|
|
common.Bootstrapper
|
|
|
|
|
2020-05-01 11:45:21 -07:00
|
|
|
// vtxReqs prevents asking validators for the same vertex
|
|
|
|
vtxReqs common.Requests
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
pending ids.Set
|
|
|
|
finished bool
|
|
|
|
onFinished func()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize this engine.
|
|
|
|
func (b *bootstrapper) Initialize(config BootstrapConfig) {
|
|
|
|
b.BootstrapConfig = config
|
|
|
|
|
|
|
|
b.VtxBlocked.SetParser(&vtxParser{
|
|
|
|
numAccepted: b.numBootstrappedVtx,
|
|
|
|
numDropped: b.numDroppedVtx,
|
|
|
|
state: b.State,
|
|
|
|
})
|
|
|
|
|
|
|
|
b.TxBlocked.SetParser(&txParser{
|
|
|
|
numAccepted: b.numBootstrappedTx,
|
|
|
|
numDropped: b.numDroppedTx,
|
|
|
|
vm: b.VM,
|
|
|
|
})
|
|
|
|
|
|
|
|
config.Bootstrapable = b
|
|
|
|
b.Bootstrapper.Initialize(config.Config)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CurrentAcceptedFrontier ...
|
|
|
|
func (b *bootstrapper) CurrentAcceptedFrontier() ids.Set {
|
|
|
|
acceptedFrontier := ids.Set{}
|
|
|
|
acceptedFrontier.Add(b.State.Edge()...)
|
|
|
|
return acceptedFrontier
|
|
|
|
}
|
|
|
|
|
|
|
|
// FilterAccepted ...
|
|
|
|
func (b *bootstrapper) FilterAccepted(containerIDs ids.Set) ids.Set {
|
|
|
|
acceptedVtxIDs := ids.Set{}
|
|
|
|
for _, vtxID := range containerIDs.List() {
|
|
|
|
if vtx, err := b.State.GetVertex(vtxID); err == nil && vtx.Status() == choices.Accepted {
|
|
|
|
acceptedVtxIDs.Add(vtxID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return acceptedVtxIDs
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForceAccepted ...
|
|
|
|
func (b *bootstrapper) ForceAccepted(acceptedContainerIDs ids.Set) {
|
|
|
|
for _, vtxID := range acceptedContainerIDs.List() {
|
|
|
|
b.fetch(vtxID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if numPending := b.pending.Len(); numPending == 0 {
|
|
|
|
// TODO: This typically indicates bootstrapping has failed, so this
|
|
|
|
// should be handled appropriately
|
|
|
|
b.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put ...
|
|
|
|
func (b *bootstrapper) Put(vdr ids.ShortID, requestID uint32, vtxID ids.ID, vtxBytes []byte) {
|
|
|
|
b.BootstrapConfig.Context.Log.Verbo("Put called for vertexID %s", vtxID)
|
|
|
|
|
|
|
|
vtx, err := b.State.ParseVertex(vtxBytes)
|
|
|
|
if err != nil {
|
2020-05-12 11:26:38 -07:00
|
|
|
b.BootstrapConfig.Context.Log.Debug("ParseVertex failed due to %s for block:\n%s",
|
2020-03-10 12:20:34 -07:00
|
|
|
err,
|
|
|
|
formatting.DumpBytes{Bytes: vtxBytes})
|
2020-05-03 15:38:12 -07:00
|
|
|
|
|
|
|
b.GetFailed(vdr, requestID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !b.pending.Contains(vtx.ID()) {
|
2020-05-12 12:18:02 -07:00
|
|
|
b.BootstrapConfig.Context.Log.Debug("Validator %s sent an unrequested vertex:\n%s",
|
2020-05-03 15:38:12 -07:00
|
|
|
vdr,
|
|
|
|
formatting.DumpBytes{Bytes: vtxBytes})
|
|
|
|
|
2020-05-01 11:45:21 -07:00
|
|
|
b.GetFailed(vdr, requestID)
|
2020-03-10 12:20:34 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b.addVertex(vtx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetFailed ...
|
2020-05-01 11:45:21 -07:00
|
|
|
func (b *bootstrapper) GetFailed(vdr ids.ShortID, requestID uint32) {
|
|
|
|
vtxID, ok := b.vtxReqs.Remove(vdr, requestID)
|
|
|
|
if !ok {
|
2020-05-12 12:18:02 -07:00
|
|
|
b.BootstrapConfig.Context.Log.Debug("GetFailed called without sending the corresponding Get message from %s",
|
2020-05-01 11:45:21 -07:00
|
|
|
vdr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b.sendRequest(vtxID)
|
|
|
|
}
|
2020-03-10 12:20:34 -07:00
|
|
|
|
|
|
|
func (b *bootstrapper) fetch(vtxID ids.ID) {
|
|
|
|
if b.pending.Contains(vtxID) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vtx, err := b.State.GetVertex(vtxID)
|
|
|
|
if err != nil {
|
|
|
|
b.sendRequest(vtxID)
|
|
|
|
return
|
|
|
|
}
|
2020-03-24 16:08:53 -07:00
|
|
|
b.storeVertex(vtx)
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *bootstrapper) sendRequest(vtxID ids.ID) {
|
|
|
|
validators := b.BootstrapConfig.Validators.Sample(1)
|
|
|
|
if len(validators) == 0 {
|
|
|
|
b.BootstrapConfig.Context.Log.Error("Dropping request for %s as there are no validators", vtxID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
validatorID := validators[0].ID()
|
|
|
|
b.RequestID++
|
|
|
|
|
2020-05-01 11:45:21 -07:00
|
|
|
b.vtxReqs.RemoveAny(vtxID)
|
|
|
|
b.vtxReqs.Add(validatorID, b.RequestID, vtxID)
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
b.pending.Add(vtxID)
|
|
|
|
b.BootstrapConfig.Sender.Get(validatorID, b.RequestID, vtxID)
|
|
|
|
|
|
|
|
b.numPendingRequests.Set(float64(b.pending.Len()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *bootstrapper) addVertex(vtx avalanche.Vertex) {
|
2020-03-24 16:08:53 -07:00
|
|
|
b.storeVertex(vtx)
|
|
|
|
|
|
|
|
if numPending := b.pending.Len(); numPending == 0 {
|
|
|
|
b.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *bootstrapper) storeVertex(vtx avalanche.Vertex) {
|
2020-03-10 12:20:34 -07:00
|
|
|
vts := []avalanche.Vertex{vtx}
|
|
|
|
|
|
|
|
for len(vts) > 0 {
|
|
|
|
newLen := len(vts) - 1
|
|
|
|
vtx := vts[newLen]
|
|
|
|
vts = vts[:newLen]
|
|
|
|
|
|
|
|
vtxID := vtx.ID()
|
|
|
|
switch status := vtx.Status(); status {
|
|
|
|
case choices.Unknown:
|
|
|
|
b.sendRequest(vtxID)
|
|
|
|
case choices.Processing:
|
|
|
|
b.pending.Remove(vtxID)
|
|
|
|
|
|
|
|
if err := b.VtxBlocked.Push(&vertexJob{
|
|
|
|
numAccepted: b.numBootstrappedVtx,
|
|
|
|
numDropped: b.numDroppedVtx,
|
|
|
|
vtx: vtx,
|
|
|
|
}); err == nil {
|
|
|
|
b.numBlockedVtx.Inc()
|
|
|
|
}
|
|
|
|
for _, tx := range vtx.Txs() {
|
|
|
|
if err := b.TxBlocked.Push(&txJob{
|
|
|
|
numAccepted: b.numBootstrappedVtx,
|
|
|
|
numDropped: b.numDroppedVtx,
|
|
|
|
tx: tx,
|
|
|
|
}); err == nil {
|
|
|
|
b.numBlockedTx.Inc()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 14:54:24 -07:00
|
|
|
vts = append(vts, vtx.Parents()...)
|
2020-03-10 12:20:34 -07:00
|
|
|
case choices.Accepted:
|
|
|
|
b.BootstrapConfig.Context.Log.Verbo("Bootstrapping confirmed %s", vtxID)
|
|
|
|
case choices.Rejected:
|
|
|
|
b.BootstrapConfig.Context.Log.Error("Bootstrapping wants to accept %s, however it was previously rejected", vtxID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
numPending := b.pending.Len()
|
|
|
|
b.numPendingRequests.Set(float64(numPending))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *bootstrapper) finish() {
|
|
|
|
if b.finished {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b.executeAll(b.TxBlocked, b.numBlockedTx)
|
|
|
|
b.executeAll(b.VtxBlocked, b.numBlockedVtx)
|
|
|
|
|
|
|
|
// Start consensus
|
|
|
|
b.onFinished()
|
|
|
|
b.finished = true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *bootstrapper) executeAll(jobs *queue.Jobs, numBlocked prometheus.Gauge) {
|
|
|
|
for job, err := jobs.Pop(); err == nil; job, err = jobs.Pop() {
|
|
|
|
numBlocked.Dec()
|
2020-04-20 17:00:36 -07:00
|
|
|
b.BootstrapConfig.Context.Log.Debug("Executing: %s", job.ID())
|
2020-03-10 12:20:34 -07:00
|
|
|
if err := jobs.Execute(job); err != nil {
|
|
|
|
b.BootstrapConfig.Context.Log.Warn("Error executing: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|