package validator import ( "context" emptypb "github.com/golang/protobuf/ptypes/empty" "github.com/pkg/errors" types "github.com/prysmaticlabs/eth2-types" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state" iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface" statev1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1" v1 "github.com/prysmaticlabs/prysm/proto/eth/v1" "go.opencensus.io/trace" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // GetAttesterDuties requests the beacon node to provide a set of attestation duties, // which should be performed by validators, for a particular epoch. func (vs *Server) GetAttesterDuties(ctx context.Context, req *v1.AttesterDutiesRequest) (*v1.AttesterDutiesResponse, error) { ctx, span := trace.StartSpan(ctx, "validatorv1.GetAttesterDuties") defer span.End() if vs.SyncChecker.Syncing() { return nil, status.Error(codes.Unavailable, "Syncing to latest head, not ready to respond") } cs := vs.TimeFetcher.CurrentSlot() currentEpoch := helpers.SlotToEpoch(cs) if req.Epoch > currentEpoch+1 { return nil, status.Errorf(codes.InvalidArgument, "Request epoch %d can not be greater than next epoch %d", req.Epoch, currentEpoch+1) } s, err := vs.HeadFetcher.HeadState(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err) } // Advance state with empty transitions up to the requested epoch start slot. // In case 1 epoch ahead was requested, we take the start slot of the current epoch. // Taking the start slot of the next epoch would result in an error inside state.ProcessSlots. var epochStartSlot types.Slot if req.Epoch == currentEpoch+1 { epochStartSlot, err = helpers.StartSlot(req.Epoch.Sub(1)) if err != nil { return nil, status.Errorf(codes.Internal, "Could not obtain epoch's start slot: %v", err) } } else { epochStartSlot, err = helpers.StartSlot(req.Epoch) if err != nil { return nil, status.Errorf(codes.Internal, "Could not obtain epoch's start slot: %v", err) } } if s.Slot() < epochStartSlot { s, err = state.ProcessSlots(ctx, s, epochStartSlot) if err != nil { return nil, status.Errorf(codes.Internal, "Could not process slots up to %d: %v", epochStartSlot, err) } } committeeAssignments, _, err := helpers.CommitteeAssignments(s, req.Epoch) if err != nil { return nil, status.Errorf(codes.Internal, "Could not compute committee assignments: %v", err) } activeValidatorCount, err := helpers.ActiveValidatorCount(s, req.Epoch) if err != nil { return nil, status.Errorf(codes.Internal, "Could not get active validator count: %v", err) } committeesAtSlot := helpers.SlotCommitteeCount(activeValidatorCount) duties := make([]*v1.AttesterDuty, len(req.Index)) for i, index := range req.Index { val, err := s.ValidatorAtIndexReadOnly(index) if _, ok := err.(*statev1.ValidatorIndexOutOfRangeError); ok { return nil, status.Errorf(codes.InvalidArgument, "Invalid index: %v", err) } else if err != nil { return nil, status.Errorf(codes.Internal, "Could not get validator: %v", err) } pubkey := val.PublicKey() committee := committeeAssignments[index] var valIndexInCommittee types.CommitteeIndex // valIndexInCommittee will be 0 in case we don't get a match. This is a potential false positive, // however it's an impossible condition because every validator must be assigned to a committee. for cIndex, vIndex := range committee.Committee { if vIndex == index { valIndexInCommittee = types.CommitteeIndex(uint64(cIndex)) break } } duties[i] = &v1.AttesterDuty{ Pubkey: pubkey[:], ValidatorIndex: index, CommitteeIndex: committee.CommitteeIndex, CommitteeLength: uint64(len(committee.Committee)), CommitteesAtSlot: committeesAtSlot, ValidatorCommitteeIndex: valIndexInCommittee, Slot: committee.AttesterSlot, } } root, err := dependentRoot(s, req.Epoch) if err != nil { return nil, status.Errorf(codes.Internal, "Could not get dependent root: %v", err) } return &v1.AttesterDutiesResponse{ DependentRoot: root, Data: duties, }, nil } // GetProposerDuties requests beacon node to provide all validators that are scheduled to propose a block in the given epoch. func (vs *Server) GetProposerDuties(ctx context.Context, req *v1.ProposerDutiesRequest) (*v1.ProposerDutiesResponse, error) { return nil, errors.New("Unimplemented") } // ProduceBlock requests the beacon node to produce a valid unsigned beacon block, which can then be signed by a proposer and submitted. func (vs *Server) ProduceBlock(ctx context.Context, req *v1.ProduceBlockRequest) (*v1.ProduceBlockResponse, error) { return nil, errors.New("Unimplemented") } // ProduceAttestationData requests that the beacon node produces attestation data for // the requested committee index and slot based on the nodes current head. func (vs *Server) ProduceAttestationData(ctx context.Context, req *v1.ProduceAttestationDataRequest) (*v1.ProduceAttestationDataResponse, error) { return nil, errors.New("Unimplemented") } // GetAggregateAttestation aggregates all attestations matching the given attestation data root and slot, returning the aggregated result. func (vs *Server) GetAggregateAttestation(ctx context.Context, req *v1.AggregateAttestationRequest) (*v1.AggregateAttestationResponse, error) { return nil, errors.New("Unimplemented") } // SubmitAggregateAndProofs verifies given aggregate and proofs and publishes them on appropriate gossipsub topic. func (vs *Server) SubmitAggregateAndProofs(ctx context.Context, req *v1.SubmitAggregateAndProofsRequest) (*emptypb.Empty, error) { return nil, errors.New("Unimplemented") } // SubmitBeaconCommitteeSubscription searches using discv5 for peers related to the provided subnet information // and replaces current peers with those ones if necessary. func (vs *Server) SubmitBeaconCommitteeSubscription(ctx context.Context, req *v1.SubmitBeaconCommitteeSubscriptionsRequest) (*emptypb.Empty, error) { return nil, errors.New("Unimplemented") } // dependentRoot is get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1) // or the genesis block root in the case of underflow. func dependentRoot(s iface.BeaconState, epoch types.Epoch) ([]byte, error) { var dependentRootSlot types.Slot if epoch <= 1 { dependentRootSlot = 0 } else { prevEpochStartSlot, err := helpers.StartSlot(epoch.Sub(1)) if err != nil { return nil, status.Errorf(codes.Internal, "Could not obtain epoch's start slot: %v", err) } dependentRootSlot = prevEpochStartSlot.Sub(1) } root, err := helpers.BlockRootAtSlot(s, dependentRootSlot) if err != nil { return nil, errors.Wrap(err, "could not get block root") } return root, nil }