Files
zeekay 060d77569e evmgpu: codec rip — message.Manager + validators state hand-rolled binary
Mirrors luxfi/evm Wave 1C cascade. 18 files. Same migration patterns:
plugin/evm/message/codec.go local Manager interface, sync/* swap,
customrawdb/* + validators/state/* hand-rolled binary, vm.go +
network.go + plugin/evm/network_handler.go call-site fixups, plus
core/parallel/gpu_{ecrecover,evm}.go GPU dispatch wiring.

Builds clean: go build ./...
2026-06-05 15:32:02 -07:00

367 lines
10 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package state
import (
"fmt"
"time"
luxdb "github.com/luxfi/database"
"github.com/luxfi/evmgpu/plugin/evm/validators/state/interfaces"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
"github.com/luxfi/validators/uptime"
)
var _ uptime.State = (*state)(nil)
type dbUpdateStatus bool
var (
ErrAlreadyExists = fmt.Errorf("validator already exists")
ErrImmutableField = fmt.Errorf("immutable field cannot be updated")
)
const (
updatedStatus dbUpdateStatus = true
deletedStatus dbUpdateStatus = false
)
type validatorData struct {
UpDuration time.Duration `serialize:"true"`
LastUpdated uint64 `serialize:"true"`
NodeID ids.NodeID `serialize:"true"`
Weight uint64 `serialize:"true"`
StartTime uint64 `serialize:"true"`
IsActive bool `serialize:"true"`
IsL1Validator bool `serialize:"true"`
validationID ids.ID // database key
}
type state struct {
data map[ids.ID]*validatorData // vID -> validatorData
index map[ids.NodeID]ids.ID // nodeID -> vID
// updatedData tracks the updates since WriteValidator was last called
updatedData map[ids.ID]dbUpdateStatus // vID -> updated status
db luxdb.Database
listeners []interfaces.StateCallbackListener
}
// NewState creates a new State, it also loads the data from the disk
func NewState(db luxdb.Database) (interfaces.State, error) {
s := &state{
index: make(map[ids.NodeID]ids.ID),
data: make(map[ids.ID]*validatorData),
updatedData: make(map[ids.ID]dbUpdateStatus),
db: db,
}
if err := s.loadFromDisk(); err != nil {
return nil, fmt.Errorf("failed to load data from disk: %w", err)
}
return s, nil
}
// GetUptime returns the uptime of the validator with the given nodeID
func (s *state) GetUptime(
nodeID ids.NodeID,
chainID ids.ID,
) (time.Duration, time.Duration, error) {
// Note: For chain-specific validators, we currently ignore the chainID
// as this state tracks validators per chain already
data, err := s.getData(nodeID)
if err != nil {
return 0, 0, err
}
// Return uptime and time since last update
lastUpdated := data.getLastUpdated()
timeSinceLastUpdate := time.Since(lastUpdated)
return data.UpDuration, timeSinceLastUpdate, nil
}
// SetUptime sets the uptime of the validator with the given nodeID
func (s *state) SetUptime(
nodeID ids.NodeID,
chainID ids.ID,
upDuration time.Duration,
lastUpdated time.Time,
) error {
// Note: For chain-specific validators, we currently ignore the chainID
// as this state tracks validators per chain already
data, err := s.getData(nodeID)
if err != nil {
return err
}
data.UpDuration = upDuration
data.setLastUpdated(lastUpdated)
s.updatedData[data.validationID] = updatedStatus
return nil
}
// GetStartTime returns the start time of the validator with the given nodeID
func (s *state) GetStartTime(nodeID ids.NodeID, chainID ids.ID) (time.Time, error) {
// Note: For chain-specific validators, we currently ignore the chainID
// as this state tracks validators per chain already
data, err := s.getData(nodeID)
if err != nil {
return time.Time{}, err
}
return data.getStartTime(), nil
}
// AddValidator adds a new validator to the state
// the new validator is marked as updated and will be written to the disk when WriteState is called
func (s *state) AddValidator(vdr interfaces.Validator) error {
data := &validatorData{
NodeID: vdr.NodeID,
validationID: vdr.ValidationID,
IsActive: vdr.IsActive,
StartTime: vdr.StartTimestamp,
UpDuration: 0,
LastUpdated: vdr.StartTimestamp,
IsL1Validator: vdr.IsL1Validator,
Weight: vdr.Weight,
}
if err := s.addData(vdr.ValidationID, data); err != nil {
return err
}
s.updatedData[vdr.ValidationID] = updatedStatus
for _, listener := range s.listeners {
listener.OnValidatorAdded(vdr.ValidationID, vdr.NodeID, vdr.StartTimestamp, vdr.IsActive)
}
return nil
}
// UpdateValidator updates the validator in the state
// returns an error if the validator does not exist or if the immutable fields are modified
func (s *state) UpdateValidator(vdr interfaces.Validator) error {
data, exists := s.data[vdr.ValidationID]
if !exists {
return luxdb.ErrNotFound
}
// check immutable fields
if !data.constantsAreUnmodified(vdr) {
return ErrImmutableField
}
// check if mutable fields have changed
updated := false
if data.IsActive != vdr.IsActive {
data.IsActive = vdr.IsActive
updated = true
for _, listener := range s.listeners {
listener.OnValidatorStatusUpdated(data.validationID, data.NodeID, data.IsActive)
}
}
if data.Weight != vdr.Weight {
data.Weight = vdr.Weight
updated = true
}
if updated {
s.updatedData[vdr.ValidationID] = updatedStatus
}
return nil
}
// DeleteValidator marks the validator as deleted
// marked validator will be deleted from disk when WriteState is called
func (s *state) DeleteValidator(vID ids.ID) error {
data, exists := s.data[vID]
if !exists {
return luxdb.ErrNotFound
}
delete(s.data, data.validationID)
delete(s.index, data.NodeID)
// mark as deleted for WriteValidator
s.updatedData[data.validationID] = deletedStatus
for _, listener := range s.listeners {
listener.OnValidatorRemoved(vID, data.NodeID)
}
return nil
}
// WriteState writes the updated state to the disk
func (s *state) WriteState() error {
// TODO: consider adding batch size
batch := s.db.NewBatch()
for vID, updateStatus := range s.updatedData {
switch updateStatus {
case updatedStatus:
data := s.data[vID]
if err := batch.Put(vID[:], marshalValidatorData(data)); err != nil {
return err
}
case deletedStatus:
if err := batch.Delete(vID[:]); err != nil {
return err
}
}
}
if err := batch.Write(); err != nil {
return err
}
// we've successfully flushed the updates, clear the updated marker.
clear(s.updatedData)
return nil
}
// SetStatus sets the active status of the validator with the given vID
func (s *state) SetStatus(vID ids.ID, isActive bool) error {
data, exists := s.data[vID]
if !exists {
return luxdb.ErrNotFound
}
data.IsActive = isActive
s.updatedData[vID] = updatedStatus
for _, listener := range s.listeners {
listener.OnValidatorStatusUpdated(vID, data.NodeID, isActive)
}
return nil
}
// GetValidationIDs returns the validation IDs in the state
func (s *state) GetValidationIDs() set.Set[ids.ID] {
ids := set.NewSet[ids.ID](len(s.data))
for vID := range s.data {
ids.Add(vID)
}
return ids
}
// GetNodeIDs returns the node IDs of validators in the state
func (s *state) GetNodeIDs() set.Set[ids.NodeID] {
ids := set.NewSet[ids.NodeID](len(s.index))
for nodeID := range s.index {
ids.Add(nodeID)
}
return ids
}
// GetValidationID returns the validation ID for the given nodeID
func (s *state) GetValidationID(nodeID ids.NodeID) (ids.ID, error) {
vID, exists := s.index[nodeID]
if !exists {
return ids.ID{}, luxdb.ErrNotFound
}
return vID, nil
}
// GetValidator returns the validator data for the given validationID
func (s *state) GetValidator(vID ids.ID) (interfaces.Validator, error) {
data, ok := s.data[vID]
if !ok {
return interfaces.Validator{}, luxdb.ErrNotFound
}
return interfaces.Validator{
ValidationID: data.validationID,
NodeID: data.NodeID,
StartTimestamp: data.StartTime,
IsActive: data.IsActive,
Weight: data.Weight,
IsL1Validator: data.IsL1Validator,
}, nil
}
// RegisterListener registers a listener to the state
// OnValidatorAdded is called for all current validators on the provided listener before this function returns
func (s *state) RegisterListener(listener interfaces.StateCallbackListener) {
s.listeners = append(s.listeners, listener)
// notify the listener of the current state
for vID, data := range s.data {
listener.OnValidatorAdded(vID, data.NodeID, data.StartTime, data.IsActive)
}
}
// parseValidatorData parses the data from the bytes into given validatorData.
// An empty byte slice is treated as a fresh record and leaves data zeroed.
func parseValidatorData(bytes []byte, data *validatorData) error {
if len(bytes) == 0 {
return nil
}
return unmarshalValidatorData(bytes, data)
}
// Load the state from the disk
func (s *state) loadFromDisk() error {
it := s.db.NewIterator()
defer it.Release()
for it.Next() {
vIDBytes := it.Key()
vID, err := ids.ToID(vIDBytes)
if err != nil {
return fmt.Errorf("failed to parse validator ID: %w", err)
}
vdr := &validatorData{
validationID: vID,
}
if err := parseValidatorData(it.Value(), vdr); err != nil {
return fmt.Errorf("failed to parse validator data: %w", err)
}
if err := s.addData(vID, vdr); err != nil {
return err
}
}
return it.Error()
}
// addData adds the data to the state
// returns an error if the data already exists
func (s *state) addData(vID ids.ID, data *validatorData) error {
if _, exists := s.data[vID]; exists {
return fmt.Errorf("%w, vID: %s", ErrAlreadyExists, vID)
}
if _, exists := s.index[data.NodeID]; exists {
return fmt.Errorf("%w, nodeID: %s", ErrAlreadyExists, data.NodeID)
}
s.data[vID] = data
s.index[data.NodeID] = vID
return nil
}
// getData returns the data for the validator with the given nodeID
// returns luxdb.ErrNotFound if the data does not exist
func (s *state) getData(nodeID ids.NodeID) (*validatorData, error) {
vID, exists := s.index[nodeID]
if !exists {
return nil, luxdb.ErrNotFound
}
data, exists := s.data[vID]
if !exists {
return nil, luxdb.ErrNotFound
}
return data, nil
}
func (v *validatorData) setLastUpdated(t time.Time) {
v.LastUpdated = uint64(t.Unix())
}
func (v *validatorData) getLastUpdated() time.Time {
return time.Unix(int64(v.LastUpdated), 0)
}
func (v *validatorData) getStartTime() time.Time {
return time.Unix(int64(v.StartTime), 0)
}
// constantsAreUnmodified returns true if the constants of this validator have
// not been modified compared to the updated validator.
func (v *validatorData) constantsAreUnmodified(u interfaces.Validator) bool {
return v.validationID == u.ValidationID &&
v.NodeID == u.NodeID &&
v.IsL1Validator == u.IsL1Validator &&
v.StartTime == u.StartTimestamp
}