mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
251 lines
6.4 KiB
Go
251 lines
6.4 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/luxfi/consensus/engine/dag/state"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
// BootstrapableEngine is an engine that can be bootstrapped
|
|
type BootstrapableEngine interface {
|
|
// Start starts the engine
|
|
Start(ctx context.Context, startReqID uint32) error
|
|
|
|
// HealthCheck checks engine health
|
|
HealthCheck(ctx context.Context) (interface{}, error)
|
|
|
|
// Shutdown shuts down the engine
|
|
Shutdown(ctx context.Context) error
|
|
|
|
// IsBootstrapped returns whether bootstrapping is complete
|
|
IsBootstrapped() bool
|
|
}
|
|
|
|
// Bootstrapper bootstraps a DAG chain
|
|
type Bootstrapper struct {
|
|
mu sync.RWMutex
|
|
config Config
|
|
onFinished func(ctx context.Context, lastReqID uint32) error
|
|
requestID uint32
|
|
bootstrapped bool
|
|
pendingFetches map[ids.ID]struct{}
|
|
}
|
|
|
|
// New creates a new DAG bootstrapper
|
|
func New(config Config, onFinished func(ctx context.Context, lastReqID uint32) error) (*Bootstrapper, error) {
|
|
if config.Manager == nil {
|
|
return nil, fmt.Errorf("manager is required")
|
|
}
|
|
if config.VM == nil {
|
|
return nil, fmt.Errorf("VM is required")
|
|
}
|
|
|
|
return &Bootstrapper{
|
|
config: config,
|
|
onFinished: onFinished,
|
|
pendingFetches: make(map[ids.ID]struct{}),
|
|
}, nil
|
|
}
|
|
|
|
// Start starts the bootstrapping process
|
|
func (b *Bootstrapper) Start(ctx context.Context, startReqID uint32) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
b.requestID = startReqID
|
|
|
|
// Check if we should start
|
|
if b.config.StartupTracker != nil && !b.config.StartupTracker.ShouldStart() {
|
|
return nil
|
|
}
|
|
|
|
// If we have a stop vertex, linearize to it
|
|
if b.config.StopVertexID != ids.Empty {
|
|
if err := b.config.VM.Linearize(ctx, b.config.StopVertexID); err != nil {
|
|
return fmt.Errorf("failed to linearize: %w", err)
|
|
}
|
|
}
|
|
|
|
// Mark as bootstrapped
|
|
b.bootstrapped = true
|
|
|
|
// Call the finished callback
|
|
if b.onFinished != nil {
|
|
return b.onFinished(ctx, b.requestID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HealthCheck returns health information
|
|
func (b *Bootstrapper) HealthCheck(ctx context.Context) (interface{}, error) {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
|
|
return map[string]interface{}{
|
|
"bootstrapped": b.bootstrapped,
|
|
"pendingFetches": len(b.pendingFetches),
|
|
}, nil
|
|
}
|
|
|
|
// Shutdown shuts down the bootstrapper
|
|
func (b *Bootstrapper) Shutdown(ctx context.Context) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
// Clear pending fetches
|
|
b.pendingFetches = make(map[ids.ID]struct{})
|
|
|
|
// Clear blocked jobs
|
|
if b.config.VtxBlocked != nil {
|
|
b.config.VtxBlocked.Clear()
|
|
}
|
|
if b.config.TxBlocked != nil {
|
|
b.config.TxBlocked.Clear()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsBootstrapped returns whether bootstrapping is complete
|
|
func (b *Bootstrapper) IsBootstrapped() bool {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
return b.bootstrapped
|
|
}
|
|
|
|
// GetAncestors handles ancestor requests
|
|
func (b *Bootstrapper) GetAncestors(ctx context.Context, nodeID ids.NodeID, requestID uint32, vtxID ids.ID) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
// Delegate to the AllGetsServer if available
|
|
if b.config.AllGetsServer != nil {
|
|
return b.config.AllGetsServer.GetAncestors(ctx, nodeID, requestID, vtxID, b.config.AncestorsMaxContainersReceived)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Ancestors handles received ancestors
|
|
func (b *Bootstrapper) Ancestors(ctx context.Context, nodeID ids.NodeID, requestID uint32, containers [][]byte) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
// Parse and add each vertex
|
|
for _, container := range containers {
|
|
vtx, err := b.config.Manager.ParseVtx(ctx, container)
|
|
if err != nil {
|
|
continue // Skip invalid vertices
|
|
}
|
|
|
|
if err := b.config.Manager.AddVertex(vtx); err != nil {
|
|
continue // Skip if already exists
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Connected handles peer connection
|
|
func (b *Bootstrapper) Connected(ctx context.Context, nodeID ids.NodeID, nodeVersion string) error {
|
|
return nil
|
|
}
|
|
|
|
// Disconnected handles peer disconnection
|
|
func (b *Bootstrapper) Disconnected(ctx context.Context, nodeID ids.NodeID) error {
|
|
return nil
|
|
}
|
|
|
|
// Put handles vertex put
|
|
func (b *Bootstrapper) Put(ctx context.Context, nodeID ids.NodeID, requestID uint32, vtxBytes []byte) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
vtx, err := b.config.Manager.ParseVtx(ctx, vtxBytes)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse vertex: %w", err)
|
|
}
|
|
|
|
return b.config.Manager.AddVertex(vtx)
|
|
}
|
|
|
|
// GetFailed handles a failed get request
|
|
func (b *Bootstrapper) GetFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
// Remove from pending fetches if tracking
|
|
return nil
|
|
}
|
|
|
|
// PushQuery handles push query
|
|
func (b *Bootstrapper) PushQuery(ctx context.Context, nodeID ids.NodeID, requestID uint32, vtxBytes []byte) error {
|
|
return b.Put(ctx, nodeID, requestID, vtxBytes)
|
|
}
|
|
|
|
// PullQuery handles pull query
|
|
func (b *Bootstrapper) PullQuery(ctx context.Context, nodeID ids.NodeID, requestID uint32, vtxID ids.ID) error {
|
|
return nil
|
|
}
|
|
|
|
// Vote handles vote messages
|
|
func (b *Bootstrapper) Vote(ctx context.Context, nodeID ids.NodeID, requestID uint32, preferredID ids.ID, preferredIDAtHeight ids.ID, acceptedID ids.ID) error {
|
|
return nil
|
|
}
|
|
|
|
// QueryFailed handles query failure
|
|
func (b *Bootstrapper) QueryFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error {
|
|
return nil
|
|
}
|
|
|
|
// Trace wraps a bootstrapper with tracing
|
|
func Trace(b *Bootstrapper, tracer interface{}) *Bootstrapper {
|
|
// Tracing wrapper - to be implemented
|
|
return b
|
|
}
|
|
|
|
// Ensure Bootstrapper implements BootstrapableEngine
|
|
var _ BootstrapableEngine = (*Bootstrapper)(nil)
|
|
|
|
// fetchVertex fetches a vertex from the network
|
|
func (b *Bootstrapper) fetchVertex(ctx context.Context, vtxID ids.ID) error {
|
|
if b.config.Sender == nil {
|
|
return nil
|
|
}
|
|
|
|
// Track pending fetch
|
|
b.pendingFetches[vtxID] = struct{}{}
|
|
b.requestID++
|
|
|
|
// Request from network (would normally iterate beacons)
|
|
return nil
|
|
}
|
|
|
|
// processVertex processes a received vertex
|
|
func (b *Bootstrapper) processVertex(ctx context.Context, vtx state.Vertex) error {
|
|
// Check if we already have this vertex
|
|
if b.config.Manager.VertexIssued(vtx) {
|
|
return nil
|
|
}
|
|
|
|
// Add vertex to state
|
|
if err := b.config.Manager.AddVertex(vtx); err != nil {
|
|
return fmt.Errorf("failed to add vertex: %w", err)
|
|
}
|
|
|
|
// Fetch missing parents
|
|
for _, parentID := range vtx.ParentIDs() {
|
|
if _, err := b.config.Manager.GetVertex(parentID); err != nil {
|
|
if err := b.fetchVertex(ctx, parentID); err != nil {
|
|
return fmt.Errorf("failed to fetch parent: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|