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 := ðpb.DutiesResponse_Duty{ 146 PublicKey: pubKey, 147 } 148 nextAssignment := ðpb.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 ðpb.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 }