From b7b31a0f9f79813485d4cbd53bee7cfb0e007a43 Mon Sep 17 00:00:00 2001 From: Alberto Benegiamo Date: Mon, 23 Jan 2023 16:01:03 +0100 Subject: [PATCH] Optimize P-chain GetValidators when NodeIDs are provided (#2263) --- vms/platformvm/service.go | 123 +++++++++++++------ vms/platformvm/txs/executor/state_changes.go | 16 ++- 2 files changed, 96 insertions(+), 43 deletions(-) diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 5eb3754e164b..326616fc25db 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -685,24 +685,48 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato // Create set of nodeIDs nodeIDs := set.Set[ids.NodeID]{} nodeIDs.Add(args.NodeIDs...) - includeAllNodes := nodeIDs.Len() == 0 - currentStakerIterator, err := s.vm.state.GetCurrentStakerIterator() - if err != nil { - return err - } - defer currentStakerIterator.Release() - - // TODO: do not iterate over all stakers when nodeIDs given. Use currentValidators.ValidatorSet for iteration - for currentStakerIterator.Next() { // Iterates in order of increasing stop time - currentStaker := currentStakerIterator.Value() - if args.SubnetID != currentStaker.SubnetID { - continue + numNodeIDs := nodeIDs.Len() + targetStakers := make([]*state.Staker, 0, numNodeIDs) + if numNodeIDs == 0 { // Include all nodes + currentStakerIterator, err := s.vm.state.GetCurrentStakerIterator() + if err != nil { + return err } - if !includeAllNodes && !nodeIDs.Contains(currentStaker.NodeID) { - continue + for currentStakerIterator.Next() { + staker := currentStakerIterator.Value() + if args.SubnetID != staker.SubnetID { + continue + } + targetStakers = append(targetStakers, staker) } + currentStakerIterator.Release() + } else { + for nodeID := range nodeIDs { + staker, err := s.vm.state.GetCurrentValidator(args.SubnetID, nodeID) + switch err { + case nil: + case database.ErrNotFound: + // nothing to do, continue + continue + default: + return err + } + targetStakers = append(targetStakers, staker) + + delegatorsIt, err := s.vm.state.GetCurrentDelegatorIterator(args.SubnetID, nodeID) + if err != nil { + return err + } + for delegatorsIt.Next() { + staker := delegatorsIt.Value() + targetStakers = append(targetStakers, staker) + } + delegatorsIt.Release() + } + } + for _, currentStaker := range targetStakers { tx, _, err := s.vm.state.GetTx(currentStaker.TxID) if err != nil { return err @@ -717,11 +741,11 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato StakeAmount: &weight, NodeID: nodeID, } - potentialReward := json.Uint64(currentStaker.PotentialReward) - switch staker := tx.Unsigned.(type) { + + switch stakerTx := tx.Unsigned.(type) { case txs.ValidatorTx: - shares := staker.Shares() + shares := stakerTx.Shares() delegationFee := json.Float32(100 * float32(shares) / float32(reward.PercentDenominator)) uptime, err := s.getAPIUptime(currentStaker) @@ -734,14 +758,14 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato validationRewardOwner *platformapi.Owner delegationRewardOwner *platformapi.Owner ) - validationOwner, ok := staker.ValidationRewardsOwner().(*secp256k1fx.OutputOwners) + validationOwner, ok := stakerTx.ValidationRewardsOwner().(*secp256k1fx.OutputOwners) if ok { validationRewardOwner, err = s.getAPIOwner(validationOwner) if err != nil { return err } } - delegationOwner, ok := staker.DelegationRewardsOwner().(*secp256k1fx.OutputOwners) + delegationOwner, ok := stakerTx.DelegationRewardsOwner().(*secp256k1fx.OutputOwners) if ok { delegationRewardOwner, err = s.getAPIOwner(delegationOwner) if err != nil { @@ -760,7 +784,7 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato DelegationFee: delegationFee, } - if staker, ok := staker.(*txs.AddPermissionlessValidatorTx); ok { + if staker, ok := stakerTx.(*txs.AddPermissionlessValidatorTx); ok { if signer, ok := staker.Signer.(*signer.ProofOfPossession); ok { vdr.Signer = signer } @@ -770,7 +794,7 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato case txs.DelegatorTx: var rewardOwner *platformapi.Owner - owner, ok := staker.RewardsOwner().(*secp256k1fx.OutputOwners) + owner, ok := stakerTx.RewardsOwner().(*secp256k1fx.OutputOwners) if ok { rewardOwner, err = s.getAPIOwner(owner) if err != nil { @@ -841,23 +865,48 @@ func (s *Service) GetPendingValidators(_ *http.Request, args *GetPendingValidato // Create set of nodeIDs nodeIDs := set.Set[ids.NodeID]{} nodeIDs.Add(args.NodeIDs...) - includeAllNodes := nodeIDs.Len() == 0 - - pendingStakerIterator, err := s.vm.state.GetPendingStakerIterator() - if err != nil { - return err - } - defer pendingStakerIterator.Release() - for pendingStakerIterator.Next() { // Iterates in order of increasing start time - pendingStaker := pendingStakerIterator.Value() - if args.SubnetID != pendingStaker.SubnetID { - continue + numNodeIDs := nodeIDs.Len() + targetStakers := make([]*state.Staker, 0, numNodeIDs) + if numNodeIDs == 0 { // Include all nodes + pendingStakerIterator, err := s.vm.state.GetPendingStakerIterator() + if err != nil { + return err } - if !includeAllNodes && !nodeIDs.Contains(pendingStaker.NodeID) { - continue + for pendingStakerIterator.Next() { // Iterates in order of increasing stop time + staker := pendingStakerIterator.Value() + if args.SubnetID != staker.SubnetID { + continue + } + targetStakers = append(targetStakers, staker) } + pendingStakerIterator.Release() + } else { + for nodeID := range nodeIDs { + staker, err := s.vm.state.GetPendingValidator(args.SubnetID, nodeID) + switch err { + case nil: + case database.ErrNotFound: + // nothing to do, continue + continue + default: + return err + } + targetStakers = append(targetStakers, staker) + + delegatorsIt, err := s.vm.state.GetPendingDelegatorIterator(args.SubnetID, nodeID) + if err != nil { + return err + } + for delegatorsIt.Next() { + staker := delegatorsIt.Value() + targetStakers = append(targetStakers, staker) + } + delegatorsIt.Release() + } + } + for _, pendingStaker := range targetStakers { tx, _, err := s.vm.state.GetTx(pendingStaker.TxID) if err != nil { return err @@ -873,9 +922,9 @@ func (s *Service) GetPendingValidators(_ *http.Request, args *GetPendingValidato StakeAmount: &weight, } - switch staker := tx.Unsigned.(type) { + switch stakerTx := tx.Unsigned.(type) { case txs.ValidatorTx: - shares := staker.Shares() + shares := stakerTx.Shares() delegationFee := json.Float32(100 * float32(shares) / float32(reward.PercentDenominator)) connected := s.vm.uptimeManager.IsConnected(nodeID, args.SubnetID) @@ -885,7 +934,7 @@ func (s *Service) GetPendingValidators(_ *http.Request, args *GetPendingValidato Connected: connected, } - if staker, ok := staker.(*txs.AddPermissionlessValidatorTx); ok { + if staker, ok := stakerTx.(*txs.AddPermissionlessValidatorTx); ok { if signer, ok := staker.Signer.(*signer.ProofOfPossession); ok { vdr.Signer = signer } diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index d45f19388ea7..f87f37d0c809 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -119,6 +119,16 @@ func AdvanceTimeTo( // Add to the staker set any pending stakers whose start time is at or // before the new timestamp + + // Note: we process pending stakers ready to be promoted to current ones and + // then we process current stakers to be demoted out of stakers set. It is + // guaranteed that no promoted stakers would be demoted immediately. A + // failure of this invariant would cause a staker to be added to + // StateChanges and be persisted among current stakers even if it already + // expired. The following invariants ensure this does not happens: + // Invariant: minimum stake duration is > 0, so staker.StartTime != staker.EndTime. + // Invariant: [newChainTime] does not skip stakers set change times. + for pendingStakerIterator.Next() { stakerToRemove := pendingStakerIterator.Value() if stakerToRemove.StartTime.After(newChainTime) { @@ -130,12 +140,6 @@ func AdvanceTimeTo( stakerToAdd.Priority = txs.PendingToCurrentPriorities[stakerToRemove.Priority] if stakerToRemove.Priority == txs.SubnetPermissionedValidatorPendingPriority { - // Invariant: [txTimestamp] <= [nextStakerChangeTime]. - // Invariant: minimum stake duration is > 0. - // - // Both of the above invariants ensure the staker we are adding here - // should never be attempted to be removed in the following loop. - changes.currentValidatorsToAdd = append(changes.currentValidatorsToAdd, &stakerToAdd) changes.pendingValidatorsToRemove = append(changes.pendingValidatorsToRemove, stakerToRemove) continue