github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/rpc/prysm/v1alpha1/validator/assignments.go (about)

     1  package validator
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	types "github.com/prysmaticlabs/eth2-types"
     8  	"github.com/prysmaticlabs/prysm/beacon-chain/cache"
     9  	"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
    10  	statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
    11  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    12  	"github.com/prysmaticlabs/prysm/beacon-chain/core/state"
    13  	ethpbv1 "github.com/prysmaticlabs/prysm/proto/eth/v1"
    14  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    15  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    16  	"github.com/prysmaticlabs/prysm/shared/params"
    17  	"github.com/prysmaticlabs/prysm/shared/rand"
    18  	"github.com/prysmaticlabs/prysm/shared/slotutil"
    19  	"github.com/prysmaticlabs/prysm/shared/timeutils"
    20  	"google.golang.org/grpc/codes"
    21  	"google.golang.org/grpc/status"
    22  )
    23  
    24  // GetDuties returns the duties assigned to a list of validators specified
    25  // in the request object.
    26  func (vs *Server) GetDuties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) {
    27  	if vs.SyncChecker.Syncing() {
    28  		return nil, status.Error(codes.Unavailable, "Syncing to latest head, not ready to respond")
    29  	}
    30  	return vs.duties(ctx, req)
    31  }
    32  
    33  // StreamDuties returns the duties assigned to a list of validators specified
    34  // in the request object via a server-side stream. The stream sends out new assignments in case
    35  // a chain re-org occurred.
    36  func (vs *Server) StreamDuties(req *ethpb.DutiesRequest, stream ethpb.BeaconNodeValidator_StreamDutiesServer) error {
    37  	if vs.SyncChecker.Syncing() {
    38  		return status.Error(codes.Unavailable, "Syncing to latest head, not ready to respond")
    39  	}
    40  
    41  	// If we are post-genesis time, then set the current epoch to
    42  	// the number epochs since the genesis time, otherwise 0 by default.
    43  	genesisTime := vs.TimeFetcher.GenesisTime()
    44  	if genesisTime.IsZero() {
    45  		return status.Error(codes.Unavailable, "genesis time is not set")
    46  	}
    47  	var currentEpoch types.Epoch
    48  	if genesisTime.Before(timeutils.Now()) {
    49  		currentEpoch = slotutil.EpochsSinceGenesis(vs.TimeFetcher.GenesisTime())
    50  	}
    51  	req.Epoch = currentEpoch
    52  	res, err := vs.duties(stream.Context(), req)
    53  	if err != nil {
    54  		return status.Errorf(codes.Internal, "Could not compute validator duties: %v", err)
    55  	}
    56  	if err := stream.Send(res); err != nil {
    57  		return status.Errorf(codes.Internal, "Could not send response over stream: %v", err)
    58  	}
    59  
    60  	// We start a for loop which ticks on every epoch or a chain reorg.
    61  	stateChannel := make(chan *feed.Event, 1)
    62  	stateSub := vs.StateNotifier.StateFeed().Subscribe(stateChannel)
    63  	defer stateSub.Unsubscribe()
    64  
    65  	secondsPerEpoch := params.BeaconConfig().SecondsPerSlot * uint64(params.BeaconConfig().SlotsPerEpoch)
    66  	epochTicker := slotutil.NewSlotTicker(vs.TimeFetcher.GenesisTime(), secondsPerEpoch)
    67  	for {
    68  		select {
    69  		// Ticks every epoch to submit assignments to connected validator clients.
    70  		case slot := <-epochTicker.C():
    71  			req.Epoch = types.Epoch(slot)
    72  			res, err := vs.duties(stream.Context(), req)
    73  			if err != nil {
    74  				return status.Errorf(codes.Internal, "Could not compute validator duties: %v", err)
    75  			}
    76  			if err := stream.Send(res); err != nil {
    77  				return status.Errorf(codes.Internal, "Could not send response over stream: %v", err)
    78  			}
    79  		case ev := <-stateChannel:
    80  			// If a reorg occurred, we recompute duties for the connected validator clients
    81  			// and send another response over the server stream right away.
    82  			currentEpoch = slotutil.EpochsSinceGenesis(vs.TimeFetcher.GenesisTime())
    83  			if ev.Type == statefeed.Reorg {
    84  				data, ok := ev.Data.(*ethpbv1.EventChainReorg)
    85  				if !ok {
    86  					return status.Errorf(codes.Internal, "Received incorrect data type over reorg feed: %v", data)
    87  				}
    88  				req.Epoch = currentEpoch
    89  				res, err := vs.duties(stream.Context(), req)
    90  				if err != nil {
    91  					return status.Errorf(codes.Internal, "Could not compute validator duties: %v", err)
    92  				}
    93  				if err := stream.Send(res); err != nil {
    94  					return status.Errorf(codes.Internal, "Could not send response over stream: %v", err)
    95  				}
    96  			}
    97  		case <-stream.Context().Done():
    98  			return status.Error(codes.Canceled, "Stream context canceled")
    99  		case <-vs.Ctx.Done():
   100  			return status.Error(codes.Canceled, "RPC context canceled")
   101  		}
   102  	}
   103  }
   104  
   105  // Compute the validator duties from the head state's corresponding epoch
   106  // for validators public key / indices requested.
   107  func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) {
   108  	currentEpoch := helpers.SlotToEpoch(vs.TimeFetcher.CurrentSlot())
   109  	if req.Epoch > currentEpoch+1 {
   110  		return nil, status.Errorf(codes.Unavailable, "Request epoch %d can not be greater than next epoch %d", req.Epoch, currentEpoch+1)
   111  	}
   112  
   113  	s, err := vs.HeadFetcher.HeadState(ctx)
   114  	if err != nil {
   115  		return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err)
   116  	}
   117  
   118  	// Advance state with empty transitions up to the requested epoch start slot.
   119  	epochStartSlot, err := helpers.StartSlot(req.Epoch)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	if s.Slot() < epochStartSlot {
   124  		s, err = state.ProcessSlots(ctx, s, epochStartSlot)
   125  		if err != nil {
   126  			return nil, status.Errorf(codes.Internal, "Could not process slots up to %d: %v", epochStartSlot, err)
   127  		}
   128  	}
   129  	committeeAssignments, proposerIndexToSlots, err := helpers.CommitteeAssignments(s, req.Epoch)
   130  	if err != nil {
   131  		return nil, status.Errorf(codes.Internal, "Could not compute committee assignments: %v", err)
   132  	}
   133  	// Query the next epoch assignments for committee subnet subscriptions.
   134  	nextCommitteeAssignments, _, err := helpers.CommitteeAssignments(s, req.Epoch+1)
   135  	if err != nil {
   136  		return nil, status.Errorf(codes.Internal, "Could not compute next committee assignments: %v", err)
   137  	}
   138  
   139  	validatorAssignments := make([]*ethpb.DutiesResponse_Duty, 0, len(req.PublicKeys))
   140  	nextValidatorAssignments := make([]*ethpb.DutiesResponse_Duty, 0, len(req.PublicKeys))
   141  	for _, pubKey := range req.PublicKeys {
   142  		if ctx.Err() != nil {
   143  			return nil, status.Errorf(codes.Aborted, "Could not continue fetching assignments: %v", ctx.Err())
   144  		}
   145  		assignment := &ethpb.DutiesResponse_Duty{
   146  			PublicKey: pubKey,
   147  		}
   148  		nextAssignment := &ethpb.DutiesResponse_Duty{
   149  			PublicKey: pubKey,
   150  		}
   151  		idx, ok := s.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
   152  		if ok {
   153  			s := assignmentStatus(s, idx)
   154  
   155  			assignment.ValidatorIndex = idx
   156  			assignment.Status = s
   157  			assignment.ProposerSlots = proposerIndexToSlots[idx]
   158  
   159  			// The next epoch has no lookup for proposer indexes.
   160  			nextAssignment.ValidatorIndex = idx
   161  			nextAssignment.Status = s
   162  
   163  			ca, ok := committeeAssignments[idx]
   164  			if ok {
   165  				assignment.Committee = ca.Committee
   166  				assignment.AttesterSlot = ca.AttesterSlot
   167  				assignment.CommitteeIndex = ca.CommitteeIndex
   168  			}
   169  			// Save the next epoch assignments.
   170  			ca, ok = nextCommitteeAssignments[idx]
   171  			if ok {
   172  				nextAssignment.Committee = ca.Committee
   173  				nextAssignment.AttesterSlot = ca.AttesterSlot
   174  				nextAssignment.CommitteeIndex = ca.CommitteeIndex
   175  			}
   176  		} else {
   177  			// If the validator isn't in the beacon state, try finding their deposit to determine their status.
   178  			vStatus, _ := vs.validatorStatus(ctx, s, pubKey)
   179  			assignment.Status = vStatus.Status
   180  		}
   181  		validatorAssignments = append(validatorAssignments, assignment)
   182  		nextValidatorAssignments = append(nextValidatorAssignments, nextAssignment)
   183  		// Assign relevant validator to subnet.
   184  		assignValidatorToSubnet(pubKey, assignment.Status)
   185  		assignValidatorToSubnet(pubKey, nextAssignment.Status)
   186  	}
   187  
   188  	return &ethpb.DutiesResponse{
   189  		Duties:             validatorAssignments,
   190  		CurrentEpochDuties: validatorAssignments,
   191  		NextEpochDuties:    nextValidatorAssignments,
   192  	}, nil
   193  }
   194  
   195  // assignValidatorToSubnet checks the status and pubkey of a particular validator
   196  // to discern whether persistent subnets need to be registered for them.
   197  func assignValidatorToSubnet(pubkey []byte, status ethpb.ValidatorStatus) {
   198  	if status != ethpb.ValidatorStatus_ACTIVE && status != ethpb.ValidatorStatus_EXITING {
   199  		return
   200  	}
   201  
   202  	_, ok, expTime := cache.SubnetIDs.GetPersistentSubnets(pubkey)
   203  	if ok && expTime.After(timeutils.Now()) {
   204  		return
   205  	}
   206  	epochDuration := time.Duration(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
   207  	var assignedIdxs []uint64
   208  	randGen := rand.NewGenerator()
   209  	for i := uint64(0); i < params.BeaconConfig().RandomSubnetsPerValidator; i++ {
   210  		assignedIdx := randGen.Intn(int(params.BeaconNetworkConfig().AttestationSubnetCount))
   211  		assignedIdxs = append(assignedIdxs, uint64(assignedIdx))
   212  	}
   213  
   214  	assignedDuration := uint64(randGen.Intn(int(params.BeaconConfig().EpochsPerRandomSubnetSubscription)))
   215  	assignedDuration += params.BeaconConfig().EpochsPerRandomSubnetSubscription
   216  
   217  	totalDuration := epochDuration * time.Duration(assignedDuration)
   218  	cache.SubnetIDs.AddPersistentCommittee(pubkey, assignedIdxs, totalDuration*time.Second)
   219  }