diff --git a/plugin/evm/client.go b/plugin/evm/client.go index f87507da56..36a0ee675f 100644 --- a/plugin/evm/client.go +++ b/plugin/evm/client.go @@ -10,6 +10,7 @@ import ( "golang.org/x/exp/slog" "github.com/ava-labs/avalanchego/api" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/rpc" ) @@ -24,7 +25,7 @@ type Client interface { LockProfile(ctx context.Context, options ...rpc.Option) error SetLogLevel(ctx context.Context, level slog.Level, options ...rpc.Option) error GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) - GetCurrentValidators(ctx context.Context, options ...rpc.Option) ([]CurrentValidator, error) + GetCurrentValidators(ctx context.Context, nodeIDs []ids.NodeID, options ...rpc.Option) ([]CurrentValidator, error) } // Client implementation for interacting with EVM [chain] @@ -77,8 +78,10 @@ func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*Confi } // GetCurrentValidators returns the current validators -func (c *client) GetCurrentValidators(ctx context.Context, options ...rpc.Option) ([]CurrentValidator, error) { +func (c *client) GetCurrentValidators(ctx context.Context, nodeIDs []ids.NodeID, options ...rpc.Option) ([]CurrentValidator, error) { res := &GetCurrentValidatorsResponse{} - err := c.validatorsRequester.SendRequest(ctx, "validators.getCurrentValidators", struct{}{}, res, options...) + err := c.validatorsRequester.SendRequest(ctx, "validators.getCurrentValidators", &GetCurrentValidatorsRequest{ + NodeIDs: nodeIDs, + }, res, options...) return res.Validators, err } diff --git a/plugin/evm/service.go b/plugin/evm/service.go index dcddd54eec..0ad10a63b6 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -4,61 +4,93 @@ package evm import ( + "fmt" "net/http" "time" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/set" ) type ValidatorsAPI struct { vm *VM } +type GetCurrentValidatorsRequest struct { + NodeIDs []ids.NodeID `json:"nodeIDs"` +} + type GetCurrentValidatorsResponse struct { Validators []CurrentValidator `json:"validators"` } type CurrentValidator struct { - ValidationID ids.ID `json:"validationID"` - NodeID ids.NodeID `json:"nodeID"` - Weight uint64 `json:"weight"` - StartTimestamp uint64 `json:"startTimestamp"` - IsActive bool `json:"isActive"` - IsL1Validator bool `json:"isL1Validator"` - IsConnected bool `json:"isConnected"` - Uptime time.Duration `json:"uptime"` + ValidationID ids.ID `json:"validationID"` + NodeID ids.NodeID `json:"nodeID"` + Weight uint64 `json:"weight"` + StartTimestamp uint64 `json:"startTimestamp"` + IsActive bool `json:"isActive"` + IsL1Validator bool `json:"isL1Validator"` + IsConnected bool `json:"isConnected"` + UptimePercentage float32 `json:"uptimePercentage"` + UptimeSeconds uint64 `json:"uptimeSeconds"` } -func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, _ *struct{}, reply *GetCurrentValidatorsResponse) error { +func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, req *GetCurrentValidatorsRequest, reply *GetCurrentValidatorsResponse) error { api.vm.ctx.Lock.RLock() defer api.vm.ctx.Lock.RUnlock() - vIDs := api.vm.validatorsManager.GetValidationIDs() + var vIDs set.Set[ids.ID] + if len(req.NodeIDs) > 0 { + vIDs = set.NewSet[ids.ID](len(req.NodeIDs)) + for _, nodeID := range req.NodeIDs { + vID, err := api.vm.validatorsManager.GetValidationID(nodeID) + if err != nil { + return fmt.Errorf("couldn't find validator with node ID %s", nodeID) + } + vIDs.Add(vID) + } + } else { + vIDs = api.vm.validatorsManager.GetValidationIDs() + } reply.Validators = make([]CurrentValidator, 0, vIDs.Len()) for _, vID := range vIDs.List() { validator, err := api.vm.validatorsManager.GetValidator(vID) if err != nil { - return err + return fmt.Errorf("couldn't find validator with validation ID %s", vID) } isConnected := api.vm.validatorsManager.IsConnected(validator.NodeID) - uptime, _, err := api.vm.validatorsManager.CalculateUptime(validator.NodeID) + upDuration, lastUpdated, err := api.vm.validatorsManager.CalculateUptime(validator.NodeID) if err != nil { return err } + var uptimeFloat float64 + startTime := time.Unix(int64(validator.StartTimestamp), 0) + bestPossibleUpDuration := lastUpdated.Sub(startTime) + if bestPossibleUpDuration == 0 { + uptimeFloat = 1 + } else { + uptimeFloat = float64(upDuration) / float64(bestPossibleUpDuration) + } + + // Transform this to a percentage (0-100) to make it consistent + // with currentValidators in PlatformVM API + uptimePercentage := float32(uptimeFloat * 100) reply.Validators = append(reply.Validators, CurrentValidator{ - ValidationID: validator.ValidationID, - NodeID: validator.NodeID, - StartTimestamp: validator.StartTimestamp, - Weight: validator.Weight, - IsActive: validator.IsActive, - IsL1Validator: validator.IsL1Validator, - IsConnected: isConnected, - Uptime: time.Duration(uptime.Seconds()), + ValidationID: validator.ValidationID, + NodeID: validator.NodeID, + StartTimestamp: validator.StartTimestamp, + Weight: validator.Weight, + IsActive: validator.IsActive, + IsL1Validator: validator.IsL1Validator, + IsConnected: isConnected, + UptimePercentage: uptimePercentage, + UptimeSeconds: uint64(upDuration.Seconds()), }) } return nil diff --git a/plugin/evm/validators/state/interfaces/state.go b/plugin/evm/validators/state/interfaces/state.go index 13cb68ba10..6ed743d09c 100644 --- a/plugin/evm/validators/state/interfaces/state.go +++ b/plugin/evm/validators/state/interfaces/state.go @@ -16,6 +16,8 @@ type StateReader interface { GetValidationIDs() set.Set[ids.ID] // GetNodeIDs returns the validator node IDs in the state GetNodeIDs() set.Set[ids.NodeID] + // GetValidationID returns the validation ID for the given node ID + GetValidationID(nodeID ids.NodeID) (ids.ID, error) } type State interface { diff --git a/plugin/evm/validators/state/state.go b/plugin/evm/validators/state/state.go index 175a67a5bf..c3150c10ac 100644 --- a/plugin/evm/validators/state/state.go +++ b/plugin/evm/validators/state/state.go @@ -239,6 +239,15 @@ func (s *state) GetNodeIDs() set.Set[ids.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{}, database.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]