mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-14 14:08:19 +00:00
8428a79971
* Enable whitespace linter & fix findings * Fix new finding * fix new violation --------- Co-authored-by: Radosław Kapka <rkapka@wp.pl> Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com> Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
300 lines
12 KiB
Go
300 lines
12 KiB
Go
package beacon_api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
|
|
"github.com/prysmaticlabs/prysm/v4/config/params"
|
|
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
|
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
|
)
|
|
|
|
type dutiesProvider interface {
|
|
GetAttesterDuties(ctx context.Context, epoch primitives.Epoch, validatorIndices []primitives.ValidatorIndex) ([]*apimiddleware.AttesterDutyJson, error)
|
|
GetProposerDuties(ctx context.Context, epoch primitives.Epoch) ([]*apimiddleware.ProposerDutyJson, error)
|
|
GetSyncDuties(ctx context.Context, epoch primitives.Epoch, validatorIndices []primitives.ValidatorIndex) ([]*apimiddleware.SyncCommitteeDuty, error)
|
|
GetCommittees(ctx context.Context, epoch primitives.Epoch) ([]*apimiddleware.CommitteeJson, error)
|
|
}
|
|
|
|
type beaconApiDutiesProvider struct {
|
|
jsonRestHandler jsonRestHandler
|
|
}
|
|
|
|
type committeeIndexSlotPair struct {
|
|
committeeIndex primitives.CommitteeIndex
|
|
slot primitives.Slot
|
|
}
|
|
|
|
func (c beaconApiValidatorClient) getDuties(ctx context.Context, in *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) {
|
|
multipleValidatorStatus, err := c.multipleValidatorStatus(ctx, ðpb.MultipleValidatorStatusRequest{PublicKeys: in.PublicKeys})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get validator status")
|
|
}
|
|
|
|
// Sync committees are an Altair feature
|
|
fetchSyncDuties := in.Epoch >= params.BeaconConfig().AltairForkEpoch
|
|
|
|
currentEpochDuties, err := c.getDutiesForEpoch(ctx, in.Epoch, multipleValidatorStatus, fetchSyncDuties)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get duties for current epoch `%d`", in.Epoch)
|
|
}
|
|
|
|
nextEpochDuties, err := c.getDutiesForEpoch(ctx, in.Epoch+1, multipleValidatorStatus, fetchSyncDuties)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get duties for next epoch `%d`", in.Epoch+1)
|
|
}
|
|
|
|
return ðpb.DutiesResponse{
|
|
Duties: currentEpochDuties,
|
|
CurrentEpochDuties: currentEpochDuties,
|
|
NextEpochDuties: nextEpochDuties,
|
|
}, nil
|
|
}
|
|
|
|
func (c beaconApiValidatorClient) getDutiesForEpoch(
|
|
ctx context.Context,
|
|
epoch primitives.Epoch,
|
|
multipleValidatorStatus *ethpb.MultipleValidatorStatusResponse,
|
|
fetchSyncDuties bool,
|
|
) ([]*ethpb.DutiesResponse_Duty, error) {
|
|
attesterDuties, err := c.dutiesProvider.GetAttesterDuties(ctx, epoch, multipleValidatorStatus.Indices)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get attester duties for epoch `%d`", epoch)
|
|
}
|
|
|
|
var syncDuties []*apimiddleware.SyncCommitteeDuty
|
|
if fetchSyncDuties {
|
|
if syncDuties, err = c.dutiesProvider.GetSyncDuties(ctx, epoch, multipleValidatorStatus.Indices); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get sync duties for epoch `%d`", epoch)
|
|
}
|
|
}
|
|
|
|
var proposerDuties []*apimiddleware.ProposerDutyJson
|
|
if proposerDuties, err = c.dutiesProvider.GetProposerDuties(ctx, epoch); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get proposer duties for epoch `%d`", epoch)
|
|
}
|
|
|
|
committees, err := c.dutiesProvider.GetCommittees(ctx, epoch)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get committees for epoch `%d`", epoch)
|
|
}
|
|
|
|
// Mapping from a validator index to its attesting committee's index and slot
|
|
attesterDutiesMapping := make(map[primitives.ValidatorIndex]committeeIndexSlotPair)
|
|
for _, attesterDuty := range attesterDuties {
|
|
validatorIndex, err := strconv.ParseUint(attesterDuty.ValidatorIndex, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse attester validator index `%s`", attesterDuty.ValidatorIndex)
|
|
}
|
|
|
|
slot, err := strconv.ParseUint(attesterDuty.Slot, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse attester slot `%s`", attesterDuty.Slot)
|
|
}
|
|
|
|
committeeIndex, err := strconv.ParseUint(attesterDuty.CommitteeIndex, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse attester committee index `%s`", attesterDuty.CommitteeIndex)
|
|
}
|
|
|
|
attesterDutiesMapping[primitives.ValidatorIndex(validatorIndex)] = committeeIndexSlotPair{
|
|
slot: primitives.Slot(slot),
|
|
committeeIndex: primitives.CommitteeIndex(committeeIndex),
|
|
}
|
|
}
|
|
|
|
// Mapping from a validator index to its proposal slot
|
|
proposerDutySlots := make(map[primitives.ValidatorIndex][]primitives.Slot)
|
|
for _, proposerDuty := range proposerDuties {
|
|
validatorIndex, err := strconv.ParseUint(proposerDuty.ValidatorIndex, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse proposer validator index `%s`", proposerDuty.ValidatorIndex)
|
|
}
|
|
|
|
slot, err := strconv.ParseUint(proposerDuty.Slot, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse proposer slot `%s`", proposerDuty.Slot)
|
|
}
|
|
|
|
proposerDutySlots[primitives.ValidatorIndex(validatorIndex)] = append(proposerDutySlots[primitives.ValidatorIndex(validatorIndex)], primitives.Slot(slot))
|
|
}
|
|
|
|
// Set containing all validator indices that are part of a sync committee for this epoch
|
|
syncDutiesMapping := make(map[primitives.ValidatorIndex]bool)
|
|
for _, syncDuty := range syncDuties {
|
|
validatorIndex, err := strconv.ParseUint(syncDuty.ValidatorIndex, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse sync validator index `%s`", syncDuty.ValidatorIndex)
|
|
}
|
|
|
|
syncDutiesMapping[primitives.ValidatorIndex(validatorIndex)] = true
|
|
}
|
|
|
|
// Mapping from the {committeeIndex, slot} to each of the committee's validator indices
|
|
committeeMapping := make(map[committeeIndexSlotPair][]primitives.ValidatorIndex)
|
|
for _, committee := range committees {
|
|
committeeIndex, err := strconv.ParseUint(committee.Index, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse committee index `%s`", committee.Index)
|
|
}
|
|
|
|
slot, err := strconv.ParseUint(committee.Slot, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse slot `%s`", committee.Slot)
|
|
}
|
|
|
|
validatorIndices := make([]primitives.ValidatorIndex, len(committee.Validators))
|
|
for index, validatorIndexString := range committee.Validators {
|
|
validatorIndex, err := strconv.ParseUint(validatorIndexString, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse committee validator index `%s`", validatorIndexString)
|
|
}
|
|
validatorIndices[index] = primitives.ValidatorIndex(validatorIndex)
|
|
}
|
|
|
|
key := committeeIndexSlotPair{
|
|
committeeIndex: primitives.CommitteeIndex(committeeIndex),
|
|
slot: primitives.Slot(slot),
|
|
}
|
|
committeeMapping[key] = validatorIndices
|
|
}
|
|
|
|
duties := make([]*ethpb.DutiesResponse_Duty, len(multipleValidatorStatus.Statuses))
|
|
for index, validatorStatus := range multipleValidatorStatus.Statuses {
|
|
validatorIndex := multipleValidatorStatus.Indices[index]
|
|
pubkey := multipleValidatorStatus.PublicKeys[index]
|
|
|
|
var attesterSlot primitives.Slot
|
|
var committeeIndex primitives.CommitteeIndex
|
|
var committeeValidatorIndices []primitives.ValidatorIndex
|
|
|
|
if committeeMappingKey, ok := attesterDutiesMapping[validatorIndex]; ok {
|
|
committeeIndex = committeeMappingKey.committeeIndex
|
|
attesterSlot = committeeMappingKey.slot
|
|
|
|
if committeeValidatorIndices, ok = committeeMapping[committeeMappingKey]; !ok {
|
|
return nil, errors.Errorf("failed to find validators for committee index `%d` and slot `%d`", committeeIndex, attesterSlot)
|
|
}
|
|
}
|
|
|
|
duties[index] = ðpb.DutiesResponse_Duty{
|
|
Committee: committeeValidatorIndices,
|
|
CommitteeIndex: committeeIndex,
|
|
AttesterSlot: attesterSlot,
|
|
ProposerSlots: proposerDutySlots[validatorIndex],
|
|
PublicKey: pubkey,
|
|
Status: validatorStatus.Status,
|
|
ValidatorIndex: validatorIndex,
|
|
IsSyncCommittee: syncDutiesMapping[validatorIndex],
|
|
}
|
|
}
|
|
|
|
return duties, nil
|
|
}
|
|
|
|
// GetCommittees retrieves the committees for the given epoch
|
|
func (c beaconApiDutiesProvider) GetCommittees(ctx context.Context, epoch primitives.Epoch) ([]*apimiddleware.CommitteeJson, error) {
|
|
committeeParams := url.Values{}
|
|
committeeParams.Add("epoch", strconv.FormatUint(uint64(epoch), 10))
|
|
committeesRequest := buildURL("/eth/v1/beacon/states/head/committees", committeeParams)
|
|
|
|
var stateCommittees apimiddleware.StateCommitteesResponseJson
|
|
if _, err := c.jsonRestHandler.GetRestJsonResponse(ctx, committeesRequest, &stateCommittees); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to query committees for epoch `%d`", epoch)
|
|
}
|
|
|
|
if stateCommittees.Data == nil {
|
|
return nil, errors.New("state committees data is nil")
|
|
}
|
|
|
|
for index, committee := range stateCommittees.Data {
|
|
if committee == nil {
|
|
return nil, errors.Errorf("committee at index `%d` is nil", index)
|
|
}
|
|
}
|
|
|
|
return stateCommittees.Data, nil
|
|
}
|
|
|
|
// GetAttesterDuties retrieves the attester duties for the given epoch and validatorIndices
|
|
func (c beaconApiDutiesProvider) GetAttesterDuties(ctx context.Context, epoch primitives.Epoch, validatorIndices []primitives.ValidatorIndex) ([]*apimiddleware.AttesterDutyJson, error) {
|
|
jsonValidatorIndices := make([]string, len(validatorIndices))
|
|
for index, validatorIndex := range validatorIndices {
|
|
jsonValidatorIndices[index] = strconv.FormatUint(uint64(validatorIndex), 10)
|
|
}
|
|
|
|
validatorIndicesBytes, err := json.Marshal(jsonValidatorIndices)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal validator indices")
|
|
}
|
|
|
|
attesterDuties := &apimiddleware.AttesterDutiesResponseJson{}
|
|
if _, err := c.jsonRestHandler.PostRestJson(ctx, fmt.Sprintf("/eth/v1/validator/duties/attester/%d", epoch), nil, bytes.NewBuffer(validatorIndicesBytes), attesterDuties); err != nil {
|
|
return nil, errors.Wrap(err, "failed to send POST data to REST endpoint")
|
|
}
|
|
|
|
for index, attesterDuty := range attesterDuties.Data {
|
|
if attesterDuty == nil {
|
|
return nil, errors.Errorf("attester duty at index `%d` is nil", index)
|
|
}
|
|
}
|
|
|
|
return attesterDuties.Data, nil
|
|
}
|
|
|
|
// GetProposerDuties retrieves the proposer duties for the given epoch
|
|
func (c beaconApiDutiesProvider) GetProposerDuties(ctx context.Context, epoch primitives.Epoch) ([]*apimiddleware.ProposerDutyJson, error) {
|
|
proposerDuties := apimiddleware.ProposerDutiesResponseJson{}
|
|
if _, err := c.jsonRestHandler.GetRestJsonResponse(ctx, fmt.Sprintf("/eth/v1/validator/duties/proposer/%d", epoch), &proposerDuties); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to query proposer duties for epoch `%d`", epoch)
|
|
}
|
|
|
|
if proposerDuties.Data == nil {
|
|
return nil, errors.New("proposer duties data is nil")
|
|
}
|
|
|
|
for index, proposerDuty := range proposerDuties.Data {
|
|
if proposerDuty == nil {
|
|
return nil, errors.Errorf("proposer duty at index `%d` is nil", index)
|
|
}
|
|
}
|
|
|
|
return proposerDuties.Data, nil
|
|
}
|
|
|
|
// GetSyncDuties retrieves the sync committee duties for the given epoch and validatorIndices
|
|
func (c beaconApiDutiesProvider) GetSyncDuties(ctx context.Context, epoch primitives.Epoch, validatorIndices []primitives.ValidatorIndex) ([]*apimiddleware.SyncCommitteeDuty, error) {
|
|
jsonValidatorIndices := make([]string, len(validatorIndices))
|
|
for index, validatorIndex := range validatorIndices {
|
|
jsonValidatorIndices[index] = strconv.FormatUint(uint64(validatorIndex), 10)
|
|
}
|
|
|
|
validatorIndicesBytes, err := json.Marshal(jsonValidatorIndices)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal validator indices")
|
|
}
|
|
|
|
syncDuties := apimiddleware.SyncCommitteeDutiesResponseJson{}
|
|
if _, err := c.jsonRestHandler.PostRestJson(ctx, fmt.Sprintf("/eth/v1/validator/duties/sync/%d", epoch), nil, bytes.NewBuffer(validatorIndicesBytes), &syncDuties); err != nil {
|
|
return nil, errors.Wrap(err, "failed to send POST data to REST endpoint")
|
|
}
|
|
|
|
if syncDuties.Data == nil {
|
|
return nil, errors.New("sync duties data is nil")
|
|
}
|
|
|
|
for index, syncDuty := range syncDuties.Data {
|
|
if syncDuty == nil {
|
|
return nil, errors.Errorf("sync duty at index `%d` is nil", index)
|
|
}
|
|
}
|
|
|
|
return syncDuties.Data, nil
|
|
}
|