Files
evm/plugin/evm/validators/manager.go
T
zeekay 07ccb13bed Fix warp message SourceChainID conversions and formatting
- Fixed aggregator test to use byte slice conversion
- Auto-formatted all Go files with gofmt and goimports
- Fixed import ordering
2025-09-21 21:25:23 +00:00

189 lines
6.0 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package validators
import (
"context"
"fmt"
"sync"
"time"
"github.com/luxfi/consensus"
luxvalidators "github.com/luxfi/consensus/validators"
"github.com/luxfi/database"
validators "github.com/luxfi/evm/plugin/evm/validators/state"
stateinterfaces "github.com/luxfi/evm/plugin/evm/validators/state/interfaces"
"github.com/luxfi/evm/plugin/evm/validators/uptime"
uptimeinterfaces "github.com/luxfi/evm/plugin/evm/validators/uptime/interfaces"
"github.com/luxfi/ids"
"github.com/luxfi/node/utils/timer/mockable"
"github.com/luxfi/log"
)
const (
SyncFrequency = 1 * time.Minute
)
type manager struct {
chainCtx context.Context
stateinterfaces.State
uptimeinterfaces.PausableManager
}
// NewManager returns a new validator manager
// that manages the validator state and the uptime manager.
// Manager is not thread safe and should be used with the VM locked.
func NewManager(
ctx context.Context,
db database.Database,
clock *mockable.Clock,
) (*manager, error) {
validatorState, err := validators.NewState(db)
if err != nil {
return nil, fmt.Errorf("failed to initialize validator state: %w", err)
}
// Initialize uptime manager
uptimeManager := uptime.NewPausableManager(uptime.NewManager(validatorState, clock))
validatorState.RegisterListener(uptimeManager)
return &manager{
chainCtx: ctx,
State: validatorState,
PausableManager: uptimeManager,
}, nil
}
// Initialize initializes the validator manager
// by syncing the validator state with the current validator set
// and starting the uptime tracking.
func (m *manager) Initialize(ctx context.Context) error {
// sync validators first
if err := m.sync(ctx); err != nil {
return fmt.Errorf("failed to update validators: %w", err)
}
_ = m.GetNodeIDs().List() // vdrIDs - will use when StartTracking is implemented
// Then start tracking with updated validators
// StartTracking initializes the uptime tracking with the known validators
// and update their uptime to account for the time we were being offline.
// TODO: Implement StartTracking with new uptime interface
// if err := m.StartTracking(vdrIDs); err != nil {
// return fmt.Errorf("failed to start tracking uptime: %w", err)
// }
return nil
}
// Shutdown stops the uptime tracking and writes the validator state to the database.
func (m *manager) Shutdown() error {
// vdrIDs := m.GetNodeIDs().List()
// TODO: Implement StopTracking with new uptime interface
// if err := m.StopTracking(vdrIDs); err != nil {
// return fmt.Errorf("failed to stop tracking uptime: %w", err)
// }
if err := m.WriteState(); err != nil {
return fmt.Errorf("failed to write validator: %w", err)
}
return nil
}
// DispatchSync starts the sync process
// DispatchSync holds the given lock while performing the sync.
func (m *manager) DispatchSync(ctx context.Context, lock sync.Locker) {
ticker := time.NewTicker(SyncFrequency)
defer ticker.Stop()
for {
select {
case <-ticker.C:
lock.Lock()
if err := m.sync(ctx); err != nil {
log.Error("failed to sync validators", "error", err)
}
lock.Unlock()
case <-ctx.Done():
return
}
}
}
// sync synchronizes the validator state with the current validator set
// and writes the state to the database.
// sync is not safe to call concurrently and should be called with the VM locked.
func (m *manager) sync(ctx context.Context) error {
now := time.Now()
log.Debug("performing validator sync")
// get current validator set
validatorState := consensus.GetValidatorState(m.chainCtx)
subnetID := consensus.GetSubnetID(m.chainCtx)
currentHeight, err := validatorState.GetCurrentHeight()
if err != nil {
return fmt.Errorf("failed to get current height: %w", err)
}
currentValidatorSet, err := validatorState.GetValidatorSet(currentHeight, subnetID)
if err != nil {
return fmt.Errorf("failed to get current validator set: %w", err)
}
// Convert validator set format for loadValidators
convertedValidatorSet := make(map[ids.ID]*luxvalidators.GetValidatorOutput)
for nodeID, weight := range currentValidatorSet {
// Create a simple validator output - use a unique ID based on NodeID
validationID := ids.ID(nodeID.Bytes())
convertedValidatorSet[validationID] = &luxvalidators.GetValidatorOutput{
NodeID: nodeID,
Weight: weight,
}
}
// load the current validator set into the validator state
if err := loadValidators(m.State, convertedValidatorSet); err != nil {
return fmt.Errorf("failed to load current validators: %w", err)
}
// write validators to the database
if err := m.WriteState(); err != nil {
return fmt.Errorf("failed to write validator state: %w", err)
}
// TODO: add metrics
log.Debug("validator sync complete", "duration", time.Since(now))
return nil
}
// loadValidators loads the [validators] into the validator state [validatorState]
func loadValidators(validatorState stateinterfaces.State, newValidators map[ids.ID]*luxvalidators.GetValidatorOutput) error {
currentValidationIDs := validatorState.GetValidationIDs()
// first check if we need to delete any existing validators
for vID := range currentValidationIDs {
// if the validator is not in the new set of validators
// delete the validator
if _, exists := newValidators[vID]; !exists {
validatorState.DeleteValidator(vID)
}
}
// then load the new validators
for newVID, newVdr := range newValidators {
currentVdr := stateinterfaces.Validator{
ValidationID: newVID,
NodeID: newVdr.NodeID,
Weight: newVdr.Weight,
StartTimestamp: 0, // Default value since GetValidatorOutput doesn't have StartTime
IsActive: true, // Default to active for current validators
IsL1Validator: false, // Default to false, can be updated based on your requirements
}
if currentValidationIDs.Contains(newVID) {
if err := validatorState.UpdateValidator(currentVdr); err != nil {
return err
}
} else {
if err := validatorState.AddValidator(currentVdr); err != nil {
return err
}
}
}
return nil
}