Files
evmgpu/plugin/evm/validators/manager.go

198 lines
6.4 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/database"
validators "github.com/luxfi/evmgpu/plugin/evm/validators/state"
stateinterfaces "github.com/luxfi/evmgpu/plugin/evm/validators/state/interfaces"
"github.com/luxfi/evmgpu/plugin/evm/validators/uptime"
uptimeinterfaces "github.com/luxfi/evmgpu/plugin/evm/validators/uptime/interfaces"
"github.com/luxfi/ids"
"github.com/luxfi/runtime"
"github.com/luxfi/timer/mockable"
luxvalidators "github.com/luxfi/validators"
log "github.com/luxfi/log"
)
const (
SyncFrequency = 1 * time.Minute
)
type manager struct {
runtimeCtx runtime.VMContext
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(
runtimeCtx runtime.VMContext,
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{
runtimeCtx: runtimeCtx,
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
if m.runtimeCtx == nil {
log.Debug("runtime context not available, skipping sync")
return nil
}
validatorState := m.runtimeCtx.GetValidatorState()
if validatorState == nil {
// ValidatorState not available in context - this is normal for chains
// that don't have access to P-Chain validator information
log.Debug("validator state not available, skipping sync")
return nil
}
chainID := m.runtimeCtx.GetChainID()
currentHeight, err := validatorState.GetCurrentHeight(ctx)
if err != nil {
return fmt.Errorf("failed to get current height: %w", err)
}
currentValidatorSet, err := validatorState.GetValidatorSet(ctx, currentHeight, chainID)
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, vdrOutput := range currentValidatorSet {
// Create a simple validator output - use a unique ID based on NodeID
// NodeID is 20 bytes (ShortID), but ids.ID is 32 bytes, so pad with zeros
var validationID ids.ID
copy(validationID[:], nodeID[:])
convertedValidatorSet[validationID] = vdrOutput
}
// 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
}