Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add L1 support to getCurrentValidators API #3564

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Release Notes

## Pending Release

### APIs

- Added `validationID` to `platform.getL1Validator` outputs
- Added L1 validators support to `platform.getCurrentValidators`

## [v1.11.13](https://github.com/ava-labs/avalanchego/releases/tag/v1.11.13)

This version is backwards compatible to [v1.11.0](https://github.com/ava-labs/avalanchego/releases/tag/v1.11.0). It is optional, but encouraged.
Expand Down
8 changes: 0 additions & 8 deletions vms/platformvm/api/static_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,6 @@ type GenesisPermissionlessValidator struct {
Signer *signer.ProofOfPossession `json:"signer,omitempty"`
}

// PermissionedValidator is the repr. of a permissioned validator sent over APIs.
type PermissionedValidator struct {
Staker
// The owner the staking reward, if applicable, will go to
Connected *bool `json:"connected,omitempty"`
Uptime *json.Float32 `json:"uptime,omitempty"`
}

// PrimaryDelegator is the repr. of a primary network delegator sent over APIs.
type PrimaryDelegator struct {
Staker
Expand Down
201 changes: 148 additions & 53 deletions vms/platformvm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -787,48 +787,127 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato

reply.Validators = []interface{}{}

// Validator's node ID as string --> Delegators to them
vdrToDelegators := map[ids.NodeID][]platformapi.PrimaryDelegator{}

// Create set of nodeIDs
nodeIDs := set.Of(args.NodeIDs...)

s.vm.ctx.Lock.Lock()
defer s.vm.ctx.Lock.Unlock()

// if the subnetID is the primary network, return the primary validators
if args.SubnetID == constants.PrimaryNetworkID {
primaryValidators, err := s.getPrimaryOrSubnetValidators(constants.PrimaryNetworkID, nodeIDs)
if err != nil {
return err
}

reply.Validators = primaryValidators
return nil
}

// Check if subnet is L1
_, err := s.vm.state.GetSubnetToL1Conversion(args.SubnetID)
if err == database.ErrNotFound {
// Subnet is not L1, get validators for the subnet
subnetValidators, err := s.getPrimaryOrSubnetValidators(args.SubnetID, nodeIDs)
if err != nil {
return err
}
reply.Validators = subnetValidators
return nil
}
if err != nil {
return err
}
// Subnet is L1, get validators for L1
l1Validators, err := s.getL1Validators(args.SubnetID, nodeIDs)
if err != nil {
return err
}
reply.Validators = l1Validators
return nil
}

func (s *Service) getL1Validators(subnetID ids.ID, nodeIDs set.Set[ids.NodeID]) ([]interface{}, error) {
validators := []interface{}{}
baseStakers, l1Validators, _, err := s.vm.state.GetCurrentValidators(subnetID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(No action required) The docstring for GetCurrentValidators refers to the stakers returned as 'legacy stakers'. What does that imply in the context of an L1?

if err != nil {
return nil, err
}

for _, staker := range baseStakers {
nodeID := staker.NodeID
if nodeIDs.Len() != 0 && !nodeIDs.Contains(nodeID) {
continue
}
weight := avajson.Uint64(staker.Weight)
apiStaker := platformapi.Staker{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(No action required) It appears that that conversion of state.Staker to platformapi.Staker` is defined twice in this file. Maybe avoid this duplication?

Also - and not for this PR - the need to perform this conversion seems a bit strange. Presumably there is room to unify these types?

TxID: staker.TxID,
StartTime: avajson.Uint64(staker.StartTime.Unix()),
EndTime: avajson.Uint64(staker.EndTime.Unix()),
Weight: weight,
NodeID: nodeID,
}
validators = append(validators, apiStaker)
}

for _, l1Validator := range l1Validators {
nodeID := l1Validator.NodeID
if nodeIDs.Len() != 0 && !nodeIDs.Contains(nodeID) {
continue
}

apiL1Vdr, err := s.convertL1ValidatorToAPI(l1Validator)
if err != nil {
return nil, err
}

validators = append(validators, apiL1Vdr)
}

return validators, nil
}

func (s *Service) getPrimaryOrSubnetValidators(subnetID ids.ID, nodeIDs set.Set[ids.NodeID]) ([]interface{}, error) {
numNodeIDs := nodeIDs.Len()

targetStakers := make([]*state.Staker, 0, numNodeIDs)

// Validator's node ID as string --> Delegators to them
vdrToDelegators := map[ids.NodeID][]platformapi.PrimaryDelegator{}

validators := []interface{}{}

if numNodeIDs == 0 { // Include all nodes
currentStakerIterator, err := s.vm.state.GetCurrentStakerIterator()
if err != nil {
return err
return nil, err
}
// TODO: avoid iterating over delegators here.
for currentStakerIterator.Next() {
staker := currentStakerIterator.Value()
if args.SubnetID != staker.SubnetID {
if subnetID != staker.SubnetID {
continue
}
targetStakers = append(targetStakers, staker)
}
currentStakerIterator.Release()
} else {
for nodeID := range nodeIDs {
staker, err := s.vm.state.GetCurrentValidator(args.SubnetID, nodeID)
staker, err := s.vm.state.GetCurrentValidator(subnetID, nodeID)
switch err {
case nil:
case database.ErrNotFound:
// nothing to do, continue
continue
default:
return err
return nil, err
}
targetStakers = append(targetStakers, staker)

// TODO: avoid iterating over delegators when numNodeIDs > 1.
delegatorsIt, err := s.vm.state.GetCurrentDelegatorIterator(args.SubnetID, nodeID)
delegatorsIt, err := s.vm.state.GetCurrentDelegatorIterator(subnetID, nodeID)
if err != nil {
return err
return nil, err
}
for delegatorsIt.Next() {
staker := delegatorsIt.Value()
Expand All @@ -853,15 +932,15 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato

delegateeReward, err := s.vm.state.GetDelegateeReward(currentStaker.SubnetID, currentStaker.NodeID)
if err != nil {
return err
return nil, err
}
jsonDelegateeReward := avajson.Uint64(delegateeReward)

switch currentStaker.Priority {
case txs.PrimaryNetworkValidatorCurrentPriority, txs.SubnetPermissionlessValidatorCurrentPriority:
attr, err := s.loadStakerTxAttributes(currentStaker.TxID)
if err != nil {
return err
return nil, err
}

shares := attr.shares
Expand All @@ -870,16 +949,16 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
uptime *avajson.Float32
connected *bool
)
if args.SubnetID == constants.PrimaryNetworkID {
if subnetID == constants.PrimaryNetworkID {
rawUptime, err := s.vm.uptimeManager.CalculateUptimePercentFrom(currentStaker.NodeID, currentStaker.StartTime)
if err != nil {
return err
return nil, err
}
// Transform this to a percentage (0-100) to make it consistent
// with observedUptime in info.peers API
currentUptime := avajson.Float32(rawUptime * 100)
if err != nil {
return err
return nil, err
}
isConnected := s.vm.uptimeManager.IsConnected(currentStaker.NodeID)
connected = &isConnected
Expand All @@ -894,14 +973,14 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
if ok {
validationRewardOwner, err = s.getAPIOwner(validationOwner)
if err != nil {
return err
return nil, err
}
}
delegationOwner, ok := attr.delegationRewardsOwner.(*secp256k1fx.OutputOwners)
if ok {
delegationRewardOwner, err = s.getAPIOwner(delegationOwner)
if err != nil {
return err
return nil, err
}
}

Expand All @@ -917,7 +996,7 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
DelegationFee: delegationFee,
Signer: attr.proofOfPossession,
}
reply.Validators = append(reply.Validators, vdr)
validators = append(validators, vdr)

case txs.PrimaryNetworkDelegatorCurrentPriority, txs.SubnetPermissionlessDelegatorCurrentPriority:
var rewardOwner *platformapi.Owner
Expand All @@ -926,13 +1005,13 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
if numNodeIDs == 1 {
attr, err := s.loadStakerTxAttributes(currentStaker.TxID)
if err != nil {
return err
return nil, err
}
owner, ok := attr.rewardsOwner.(*secp256k1fx.OutputOwners)
if ok {
rewardOwner, err = s.getAPIOwner(owner)
if err != nil {
return err
return nil, err
}
}
}
Expand All @@ -945,17 +1024,15 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
vdrToDelegators[delegator.NodeID] = append(vdrToDelegators[delegator.NodeID], delegator)

case txs.SubnetPermissionedValidatorCurrentPriority:
reply.Validators = append(reply.Validators, platformapi.PermissionedValidator{
Staker: apiStaker,
})
validators = append(validators, apiStaker)

default:
return fmt.Errorf("unexpected staker priority %d", currentStaker.Priority)
return nil, fmt.Errorf("unexpected staker priority %d", currentStaker.Priority)
}
}

// handle delegators' information
for i, vdrIntf := range reply.Validators {
for i, vdrIntf := range validators {
vdr, ok := vdrIntf.(platformapi.PermissionlessValidator)
if !ok {
continue
Expand All @@ -979,19 +1056,19 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
// queried a specific validator, load all of its delegators
vdr.Delegators = &delegators
}
reply.Validators[i] = vdr
validators[i] = vdr
}

return nil
return validators, nil
}

type GetL1ValidatorArgs struct {
ValidationID ids.ID `json:"validationID"`
}

type GetL1ValidatorReply struct {
SubnetID ids.ID `json:"subnetID"`
NodeID ids.NodeID `json:"nodeID"`
type APIL1Validator struct {
ValidationID ids.ID `json:"validationID"`
NodeID ids.NodeID `json:"nodeID"`
// PublicKey is the compressed BLS public key of the validator
PublicKey types.JSONByteSlice `json:"publicKey"`
RemainingBalanceOwner platformapi.Owner `json:"remainingBalanceOwner"`
Expand All @@ -1003,6 +1080,11 @@ type GetL1ValidatorReply struct {
// the continuous fee, according to the last accepted state. If the
// validator is inactive, the balance will be 0.
Balance avajson.Uint64 `json:"balance"`
}

type GetL1ValidatorReply struct {
SubnetID ids.ID `json:"subnetID"`
*APIL1Validator
// Height is the height of the last accepted block
Height avajson.Uint64 `json:"height"`
}
Expand All @@ -1023,53 +1105,66 @@ func (s *Service) GetL1Validator(r *http.Request, args *GetL1ValidatorArgs, repl
return fmt.Errorf("fetching L1 validator %q failed: %w", args.ValidationID, err)
}

ctx := r.Context()
height, err := s.vm.GetCurrentHeight(ctx)
if err != nil {
return fmt.Errorf("failed getting current height: %w", err)
}
apiVdr, err := s.convertL1ValidatorToAPI(l1Validator)
if err != nil {
return fmt.Errorf("failed converting L1 validator to API: %w", err)
}

reply.SubnetID = l1Validator.SubnetID
reply.APIL1Validator = &apiVdr
reply.Height = avajson.Uint64(height)

return nil
}

func (s *Service) convertL1ValidatorToAPI(vdr state.L1Validator) (APIL1Validator, error) {
var remainingBalanceOwner message.PChainOwner
if _, err := txs.Codec.Unmarshal(l1Validator.RemainingBalanceOwner, &remainingBalanceOwner); err != nil {
return fmt.Errorf("failed unmarshalling remaining balance owner: %w", err)
if _, err := txs.Codec.Unmarshal(vdr.RemainingBalanceOwner, &remainingBalanceOwner); err != nil {
return APIL1Validator{}, fmt.Errorf("failed unmarshalling remaining balance owner: %w", err)
}
remainingBalanceAPIOwner, err := s.getAPIOwner(&secp256k1fx.OutputOwners{
Threshold: remainingBalanceOwner.Threshold,
Addrs: remainingBalanceOwner.Addresses,
})
if err != nil {
return fmt.Errorf("failed formatting remaining balance owner: %w", err)
return APIL1Validator{}, fmt.Errorf("failed formatting remaining balance owner: %w", err)
}

var deactivationOwner message.PChainOwner
if _, err := txs.Codec.Unmarshal(l1Validator.DeactivationOwner, &deactivationOwner); err != nil {
return fmt.Errorf("failed unmarshalling deactivation owner: %w", err)
if _, err := txs.Codec.Unmarshal(vdr.DeactivationOwner, &deactivationOwner); err != nil {
return APIL1Validator{}, fmt.Errorf("failed unmarshalling deactivation owner: %w", err)
}
deactivationAPIOwner, err := s.getAPIOwner(&secp256k1fx.OutputOwners{
Threshold: deactivationOwner.Threshold,
Addrs: deactivationOwner.Addresses,
})
if err != nil {
return fmt.Errorf("failed formatting deactivation owner: %w", err)
return APIL1Validator{}, fmt.Errorf("failed formatting deactivation owner: %w", err)
}

ctx := r.Context()
height, err := s.vm.GetCurrentHeight(ctx)
if err != nil {
return fmt.Errorf("failed getting current height: %w", err)
}
var apiVdr APIL1Validator

reply.SubnetID = l1Validator.SubnetID
reply.NodeID = l1Validator.NodeID
reply.PublicKey = bls.PublicKeyToCompressedBytes(
bls.PublicKeyFromValidUncompressedBytes(l1Validator.PublicKey),
apiVdr.ValidationID = vdr.ValidationID
apiVdr.NodeID = vdr.NodeID
apiVdr.PublicKey = bls.PublicKeyToCompressedBytes(
bls.PublicKeyFromValidUncompressedBytes(vdr.PublicKey),
)
reply.RemainingBalanceOwner = *remainingBalanceAPIOwner
reply.DeactivationOwner = *deactivationAPIOwner
reply.StartTime = avajson.Uint64(l1Validator.StartTime)
reply.Weight = avajson.Uint64(l1Validator.Weight)
reply.MinNonce = avajson.Uint64(l1Validator.MinNonce)
if l1Validator.EndAccumulatedFee != 0 {
apiVdr.RemainingBalanceOwner = *remainingBalanceAPIOwner
apiVdr.DeactivationOwner = *deactivationAPIOwner
apiVdr.StartTime = avajson.Uint64(vdr.StartTime)
apiVdr.Weight = avajson.Uint64(vdr.Weight)
apiVdr.MinNonce = avajson.Uint64(vdr.MinNonce)
if vdr.EndAccumulatedFee != 0 {
accruedFees := s.vm.state.GetAccruedFees()
reply.Balance = avajson.Uint64(l1Validator.EndAccumulatedFee - accruedFees)
apiVdr.Balance = avajson.Uint64(vdr.EndAccumulatedFee - accruedFees)
}
reply.Height = avajson.Uint64(height)

return nil
return apiVdr, nil
}

// GetCurrentSupplyArgs are the arguments for calling GetCurrentSupply
Expand Down
Loading
Loading