Optimize GetDuties VC action (#13789)

* wait groups

* errgroup

* tests

* bzl

* review
This commit is contained in:
Radosław Kapka
2024-03-22 18:50:19 +09:00
committed by GitHub
parent a6e86c6731
commit 63c2b3563a
3 changed files with 250 additions and 238 deletions

View File

@@ -66,6 +66,7 @@ go_library(
"@com_github_sirupsen_logrus//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library",
"@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//:go_default_library",
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library", "@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
"@org_golang_x_sync//errgroup:go_default_library",
], ],
) )
@@ -129,7 +130,6 @@ go_test(
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library", "//testing/assert:go_default_library",
"//testing/require:go_default_library", "//testing/require:go_default_library",
"//testing/validator-mock:go_default_library",
"//time/slots:go_default_library", "//time/slots:go_default_library",
"//validator/client/beacon-api/mock:go_default_library", "//validator/client/beacon-api/mock:go_default_library",
"//validator/client/beacon-api/test-helpers:go_default_library", "//validator/client/beacon-api/test-helpers:go_default_library",

View File

@@ -8,11 +8,14 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/server/structs" "github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/consensus-types/validator"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"golang.org/x/sync/errgroup"
) )
type dutiesProvider interface { type dutiesProvider interface {
@@ -31,37 +34,42 @@ type committeeIndexSlotPair struct {
slot primitives.Slot slot primitives.Slot
} }
type validatorForDuty struct {
pubkey []byte
index primitives.ValidatorIndex
status ethpb.ValidatorStatus
}
func (c beaconApiValidatorClient) getDuties(ctx context.Context, in *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) { func (c beaconApiValidatorClient) getDuties(ctx context.Context, in *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) {
all, err := c.multipleValidatorStatus(ctx, &ethpb.MultipleValidatorStatusRequest{PublicKeys: in.PublicKeys}) vals, err := c.getValidatorsForDuties(ctx, in.PublicKeys)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to get validator status") return nil, errors.Wrap(err, "failed to get validators for duties")
}
known := &ethpb.MultipleValidatorStatusResponse{
PublicKeys: make([][]byte, 0, len(all.PublicKeys)),
Statuses: make([]*ethpb.ValidatorStatusResponse, 0, len(all.Statuses)),
Indices: make([]primitives.ValidatorIndex, 0, len(all.Indices)),
}
for i, status := range all.Statuses {
if status.Status != ethpb.ValidatorStatus_UNKNOWN_STATUS {
known.PublicKeys = append(known.PublicKeys, all.PublicKeys[i])
known.Statuses = append(known.Statuses, all.Statuses[i])
known.Indices = append(known.Indices, all.Indices[i])
}
} }
// Sync committees are an Altair feature // Sync committees are an Altair feature
fetchSyncDuties := in.Epoch >= params.BeaconConfig().AltairForkEpoch fetchSyncDuties := in.Epoch >= params.BeaconConfig().AltairForkEpoch
currentEpochDuties, err := c.getDutiesForEpoch(ctx, in.Epoch, known, fetchSyncDuties) errCh := make(chan error, 1)
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, known, fetchSyncDuties) var currentEpochDuties []*ethpb.DutiesResponse_Duty
go func() {
currentEpochDuties, err = c.getDutiesForEpoch(ctx, in.Epoch, vals, fetchSyncDuties)
if err != nil {
errCh <- errors.Wrapf(err, "failed to get duties for current epoch `%d`", in.Epoch)
return
}
errCh <- nil
}()
nextEpochDuties, err := c.getDutiesForEpoch(ctx, in.Epoch+1, vals, fetchSyncDuties)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to get duties for next epoch `%d`", in.Epoch+1) return nil, errors.Wrapf(err, "failed to get duties for next epoch `%d`", in.Epoch+1)
} }
if err = <-errCh; err != nil {
return nil, err
}
return &ethpb.DutiesResponse{ return &ethpb.DutiesResponse{
CurrentEpochDuties: currentEpochDuties, CurrentEpochDuties: currentEpochDuties,
NextEpochDuties: nextEpochDuties, NextEpochDuties: nextEpochDuties,
@@ -71,25 +79,94 @@ func (c beaconApiValidatorClient) getDuties(ctx context.Context, in *ethpb.Dutie
func (c beaconApiValidatorClient) getDutiesForEpoch( func (c beaconApiValidatorClient) getDutiesForEpoch(
ctx context.Context, ctx context.Context,
epoch primitives.Epoch, epoch primitives.Epoch,
multipleValidatorStatus *ethpb.MultipleValidatorStatusResponse, vals []validatorForDuty,
fetchSyncDuties bool, fetchSyncDuties bool,
) ([]*ethpb.DutiesResponse_Duty, error) { ) ([]*ethpb.DutiesResponse_Duty, error) {
attesterDuties, err := c.dutiesProvider.GetAttesterDuties(ctx, epoch, multipleValidatorStatus.Indices) indices := make([]primitives.ValidatorIndex, len(vals))
if err != nil { for i, v := range vals {
return nil, errors.Wrapf(err, "failed to get attester duties for epoch `%d`", epoch) indices[i] = v.index
} }
var syncDuties []*structs.SyncCommitteeDuty // Below variables MUST NOT be used in the main function before wg.Wait().
if fetchSyncDuties { // This is because they are populated in goroutines and wg.Wait()
if syncDuties, err = c.dutiesProvider.GetSyncDuties(ctx, epoch, multipleValidatorStatus.Indices); err != nil { // will return only once all goroutines finish their execution.
return nil, errors.Wrapf(err, "failed to get sync duties for epoch `%d`", epoch)
// Mapping from a validator index to its attesting committee's index and slot
attesterDutiesMapping := make(map[primitives.ValidatorIndex]committeeIndexSlotPair)
// Set containing all validator indices that are part of a sync committee for this epoch
syncDutiesMapping := make(map[primitives.ValidatorIndex]bool)
// Mapping from a validator index to its proposal slot
proposerDutySlots := make(map[primitives.ValidatorIndex][]primitives.Slot)
// Mapping from the {committeeIndex, slot} to each of the committee's validator indices
committeeMapping := make(map[committeeIndexSlotPair][]primitives.ValidatorIndex)
var wg errgroup.Group
wg.Go(func() error {
attesterDuties, err := c.dutiesProvider.GetAttesterDuties(ctx, epoch, indices)
if err != nil {
return errors.Wrapf(err, "failed to get attester duties for epoch `%d`", epoch)
} }
for _, attesterDuty := range attesterDuties {
validatorIndex, err := strconv.ParseUint(attesterDuty.ValidatorIndex, 10, 64)
if err != nil {
return errors.Wrapf(err, "failed to parse attester validator index `%s`", attesterDuty.ValidatorIndex)
}
slot, err := strconv.ParseUint(attesterDuty.Slot, 10, 64)
if err != nil {
return errors.Wrapf(err, "failed to parse attester slot `%s`", attesterDuty.Slot)
}
committeeIndex, err := strconv.ParseUint(attesterDuty.CommitteeIndex, 10, 64)
if err != nil {
return 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),
}
}
return nil
})
if fetchSyncDuties {
wg.Go(func() error {
syncDuties, err := c.dutiesProvider.GetSyncDuties(ctx, epoch, indices)
if err != nil {
return errors.Wrapf(err, "failed to get sync duties for epoch `%d`", epoch)
}
for _, syncDuty := range syncDuties {
validatorIndex, err := strconv.ParseUint(syncDuty.ValidatorIndex, 10, 64)
if err != nil {
return errors.Wrapf(err, "failed to parse sync validator index `%s`", syncDuty.ValidatorIndex)
}
syncDutiesMapping[primitives.ValidatorIndex(validatorIndex)] = true
}
return nil
})
} }
var proposerDuties []*structs.ProposerDuty wg.Go(func() error {
if proposerDuties, err = c.dutiesProvider.GetProposerDuties(ctx, epoch); err != nil { proposerDuties, err := c.dutiesProvider.GetProposerDuties(ctx, epoch)
return nil, errors.Wrapf(err, "failed to get proposer duties for epoch `%d`", epoch) if err != nil {
} return errors.Wrapf(err, "failed to get proposer duties for epoch `%d`", epoch)
}
for _, proposerDuty := range proposerDuties {
validatorIndex, err := strconv.ParseUint(proposerDuty.ValidatorIndex, 10, 64)
if err != nil {
return errors.Wrapf(err, "failed to parse proposer validator index `%s`", proposerDuty.ValidatorIndex)
}
slot, err := strconv.ParseUint(proposerDuty.Slot, 10, 64)
if err != nil {
return errors.Wrapf(err, "failed to parse proposer slot `%s`", proposerDuty.Slot)
}
proposerDutySlots[primitives.ValidatorIndex(validatorIndex)] =
append(proposerDutySlots[primitives.ValidatorIndex(validatorIndex)], primitives.Slot(slot))
}
return nil
})
committees, err := c.dutiesProvider.GetCommittees(ctx, epoch) committees, err := c.dutiesProvider.GetCommittees(ctx, epoch)
if err != nil { if err != nil {
@@ -104,70 +181,15 @@ func (c beaconApiValidatorClient) getDutiesForEpoch(
slotCommittees[c.Slot] = n + 1 slotCommittees[c.Slot] = n + 1
} }
// 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 { for _, committee := range committees {
committeeIndex, err := strconv.ParseUint(committee.Index, 10, 64) committeeIndex, err := strconv.ParseUint(committee.Index, 10, 64)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to parse committee index `%s`", committee.Index) return nil, errors.Wrapf(err, "failed to parse committee index `%s`", committee.Index)
} }
slot, err := strconv.ParseUint(committee.Slot, 10, 64) slot, err := strconv.ParseUint(committee.Slot, 10, 64)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to parse slot `%s`", committee.Slot) return nil, errors.Wrapf(err, "failed to parse slot `%s`", committee.Slot)
} }
validatorIndices := make([]primitives.ValidatorIndex, len(committee.Validators)) validatorIndices := make([]primitives.ValidatorIndex, len(committee.Validators))
for index, validatorIndexString := range committee.Validators { for index, validatorIndexString := range committee.Validators {
validatorIndex, err := strconv.ParseUint(validatorIndexString, 10, 64) validatorIndex, err := strconv.ParseUint(validatorIndexString, 10, 64)
@@ -176,7 +198,6 @@ func (c beaconApiValidatorClient) getDutiesForEpoch(
} }
validatorIndices[index] = primitives.ValidatorIndex(validatorIndex) validatorIndices[index] = primitives.ValidatorIndex(validatorIndex)
} }
key := committeeIndexSlotPair{ key := committeeIndexSlotPair{
committeeIndex: primitives.CommitteeIndex(committeeIndex), committeeIndex: primitives.CommitteeIndex(committeeIndex),
slot: primitives.Slot(slot), slot: primitives.Slot(slot),
@@ -184,16 +205,19 @@ func (c beaconApiValidatorClient) getDutiesForEpoch(
committeeMapping[key] = validatorIndices committeeMapping[key] = validatorIndices
} }
duties := make([]*ethpb.DutiesResponse_Duty, len(multipleValidatorStatus.Statuses)) if err = wg.Wait(); err != nil {
for index, validatorStatus := range multipleValidatorStatus.Statuses { return nil, err
validatorIndex := multipleValidatorStatus.Indices[index] }
pubkey := multipleValidatorStatus.PublicKeys[index]
var attesterSlot primitives.Slot duties := make([]*ethpb.DutiesResponse_Duty, len(vals))
var committeeIndex primitives.CommitteeIndex for i, v := range vals {
var committeeValidatorIndices []primitives.ValidatorIndex var (
attesterSlot primitives.Slot
committeeIndex primitives.CommitteeIndex
committeeValidatorIndices []primitives.ValidatorIndex
)
if committeeMappingKey, ok := attesterDutiesMapping[validatorIndex]; ok { if committeeMappingKey, ok := attesterDutiesMapping[v.index]; ok {
committeeIndex = committeeMappingKey.committeeIndex committeeIndex = committeeMappingKey.committeeIndex
attesterSlot = committeeMappingKey.slot attesterSlot = committeeMappingKey.slot
@@ -202,15 +226,15 @@ func (c beaconApiValidatorClient) getDutiesForEpoch(
} }
} }
duties[index] = &ethpb.DutiesResponse_Duty{ duties[i] = &ethpb.DutiesResponse_Duty{
Committee: committeeValidatorIndices, Committee: committeeValidatorIndices,
CommitteeIndex: committeeIndex, CommitteeIndex: committeeIndex,
AttesterSlot: attesterSlot, AttesterSlot: attesterSlot,
ProposerSlots: proposerDutySlots[validatorIndex], ProposerSlots: proposerDutySlots[v.index],
PublicKey: pubkey, PublicKey: v.pubkey,
Status: validatorStatus.Status, Status: v.status,
ValidatorIndex: validatorIndex, ValidatorIndex: v.index,
IsSyncCommittee: syncDutiesMapping[validatorIndex], IsSyncCommittee: syncDutiesMapping[v.index],
CommitteesAtSlot: slotCommittees[strconv.FormatUint(uint64(attesterSlot), 10)], CommitteesAtSlot: slotCommittees[strconv.FormatUint(uint64(attesterSlot), 10)],
} }
} }
@@ -218,6 +242,51 @@ func (c beaconApiValidatorClient) getDutiesForEpoch(
return duties, nil return duties, nil
} }
func (c *beaconApiValidatorClient) getValidatorsForDuties(ctx context.Context, pubkeys [][]byte) ([]validatorForDuty, error) {
vals := make([]validatorForDuty, 0, len(pubkeys))
stringPubkeysToPubkeys := make(map[string][]byte, len(pubkeys))
stringPubkeys := make([]string, len(pubkeys))
for i, pk := range pubkeys {
stringPk := hexutil.Encode(pk)
stringPubkeysToPubkeys[stringPk] = pk
stringPubkeys[i] = stringPk
}
statusesWithDuties := []string{validator.ActiveOngoing.String(), validator.ActiveExiting.String()}
stateValidatorsResponse, err := c.stateValidatorsProvider.GetStateValidators(ctx, stringPubkeys, nil, statusesWithDuties)
if err != nil {
return nil, errors.Wrap(err, "failed to get state validators")
}
for _, validatorContainer := range stateValidatorsResponse.Data {
val := validatorForDuty{}
validatorIndex, err := strconv.ParseUint(validatorContainer.Index, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse validator index %s", validatorContainer.Index)
}
val.index = primitives.ValidatorIndex(validatorIndex)
stringPubkey := validatorContainer.Validator.Pubkey
pubkey, ok := stringPubkeysToPubkeys[stringPubkey]
if !ok {
return nil, errors.Wrapf(err, "returned public key %s not requested", stringPubkey)
}
val.pubkey = pubkey
status, ok := beaconAPITogRPCValidatorStatus[validatorContainer.Status]
if !ok {
return nil, errors.New("invalid validator status " + validatorContainer.Status)
}
val.status = status
vals = append(vals, val)
}
return vals, nil
}
// GetCommittees retrieves the committees for the given epoch // GetCommittees retrieves the committees for the given epoch
func (c beaconApiDutiesProvider) GetCommittees(ctx context.Context, epoch primitives.Epoch) ([]*structs.Committee, error) { func (c beaconApiDutiesProvider) GetCommittees(ctx context.Context, epoch primitives.Epoch) ([]*structs.Committee, error) {
committeeParams := url.Values{} committeeParams := url.Values{}

View File

@@ -9,11 +9,8 @@ import (
"strconv" "strconv"
"testing" "testing"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
validatormock "github.com/prysmaticlabs/prysm/v5/testing/validator-mock"
"github.com/prysmaticlabs/prysm/v5/validator/client/iface"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
@@ -541,7 +538,6 @@ func TestGetDutiesForEpoch_Error(t *testing.T) {
{ {
name: "get proposer duties failed", name: "get proposer duties failed",
expectedError: "failed to get proposer duties for epoch `1`: foo error", expectedError: "failed to get proposer duties for epoch `1`: foo error",
fetchAttesterDutiesError: nil,
fetchProposerDutiesError: errors.New("foo error"), fetchProposerDutiesError: errors.New("foo error"),
}, },
{ {
@@ -720,28 +716,20 @@ func TestGetDutiesForEpoch_Error(t *testing.T) {
testCase.fetchCommitteesError, testCase.fetchCommitteesError,
).AnyTimes() ).AnyTimes()
vals := make([]validatorForDuty, len(pubkeys))
for i := 0; i < len(pubkeys); i++ {
vals[i] = validatorForDuty{
pubkey: pubkeys[i],
index: validatorIndices[i],
status: ethpb.ValidatorStatus_ACTIVE,
}
}
validatorClient := &beaconApiValidatorClient{dutiesProvider: dutiesProvider} validatorClient := &beaconApiValidatorClient{dutiesProvider: dutiesProvider}
_, err := validatorClient.getDutiesForEpoch( _, err := validatorClient.getDutiesForEpoch(
ctx, ctx,
epoch, epoch,
&ethpb.MultipleValidatorStatusResponse{ vals,
PublicKeys: pubkeys,
Indices: validatorIndices,
Statuses: []*ethpb.ValidatorStatusResponse{
{Status: ethpb.ValidatorStatus_UNKNOWN_STATUS},
{Status: ethpb.ValidatorStatus_DEPOSITED},
{Status: ethpb.ValidatorStatus_PENDING},
{Status: ethpb.ValidatorStatus_ACTIVE},
{Status: ethpb.ValidatorStatus_EXITING},
{Status: ethpb.ValidatorStatus_SLASHING},
{Status: ethpb.ValidatorStatus_EXITED},
{Status: ethpb.ValidatorStatus_INVALID},
{Status: ethpb.ValidatorStatus_PARTIALLY_DEPOSITED},
{Status: ethpb.ValidatorStatus_UNKNOWN_STATUS},
{Status: ethpb.ValidatorStatus_DEPOSITED},
{Status: ethpb.ValidatorStatus_PENDING},
},
},
true, true,
) )
assert.ErrorContains(t, testCase.expectedError, err) assert.ErrorContains(t, testCase.expectedError, err)
@@ -773,40 +761,6 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
committeeSlots := []primitives.Slot{28, 29, 30} committeeSlots := []primitives.Slot{28, 29, 30}
proposerSlots := []primitives.Slot{31, 32, 33, 34, 35, 36, 37, 38} proposerSlots := []primitives.Slot{31, 32, 33, 34, 35, 36, 37, 38}
statuses := []ethpb.ValidatorStatus{
ethpb.ValidatorStatus_UNKNOWN_STATUS,
ethpb.ValidatorStatus_DEPOSITED,
ethpb.ValidatorStatus_PENDING,
ethpb.ValidatorStatus_ACTIVE,
ethpb.ValidatorStatus_EXITING,
ethpb.ValidatorStatus_SLASHING,
ethpb.ValidatorStatus_EXITED,
ethpb.ValidatorStatus_INVALID,
ethpb.ValidatorStatus_PARTIALLY_DEPOSITED,
ethpb.ValidatorStatus_UNKNOWN_STATUS,
ethpb.ValidatorStatus_DEPOSITED,
ethpb.ValidatorStatus_PENDING,
}
multipleValidatorStatus := &ethpb.MultipleValidatorStatusResponse{
PublicKeys: pubkeys,
Indices: validatorIndices,
Statuses: []*ethpb.ValidatorStatusResponse{
{Status: statuses[0]},
{Status: statuses[1]},
{Status: statuses[2]},
{Status: statuses[3]},
{Status: statuses[4]},
{Status: statuses[5]},
{Status: statuses[6]},
{Status: statuses[7]},
{Status: statuses[8]},
{Status: statuses[9]},
{Status: statuses[10]},
{Status: statuses[11]},
},
}
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
@@ -824,7 +778,7 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
dutiesProvider.EXPECT().GetAttesterDuties( dutiesProvider.EXPECT().GetAttesterDuties(
ctx, ctx,
epoch, epoch,
multipleValidatorStatus.Indices, validatorIndices,
).Return( ).Return(
generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots), generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots),
nil, nil,
@@ -842,7 +796,7 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
dutiesProvider.EXPECT().GetSyncDuties( dutiesProvider.EXPECT().GetSyncDuties(
ctx, ctx,
epoch, epoch,
multipleValidatorStatus.Indices, validatorIndices,
).Return( ).Return(
generateValidSyncDuties(pubkeys, validatorIndices), generateValidSyncDuties(pubkeys, validatorIndices),
nil, nil,
@@ -883,7 +837,7 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
CommitteeIndex: committeeIndices[0], CommitteeIndex: committeeIndices[0],
AttesterSlot: committeeSlots[0], AttesterSlot: committeeSlots[0],
PublicKey: pubkeys[0], PublicKey: pubkeys[0],
Status: statuses[0], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[0], ValidatorIndex: validatorIndices[0],
CommitteesAtSlot: 1, CommitteesAtSlot: 1,
}, },
@@ -895,7 +849,7 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
CommitteeIndex: committeeIndices[0], CommitteeIndex: committeeIndices[0],
AttesterSlot: committeeSlots[0], AttesterSlot: committeeSlots[0],
PublicKey: pubkeys[1], PublicKey: pubkeys[1],
Status: statuses[1], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[1], ValidatorIndex: validatorIndices[1],
CommitteesAtSlot: 1, CommitteesAtSlot: 1,
}, },
@@ -907,7 +861,7 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
CommitteeIndex: committeeIndices[1], CommitteeIndex: committeeIndices[1],
AttesterSlot: committeeSlots[1], AttesterSlot: committeeSlots[1],
PublicKey: pubkeys[2], PublicKey: pubkeys[2],
Status: statuses[2], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[2], ValidatorIndex: validatorIndices[2],
CommitteesAtSlot: 1, CommitteesAtSlot: 1,
}, },
@@ -919,7 +873,7 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
CommitteeIndex: committeeIndices[1], CommitteeIndex: committeeIndices[1],
AttesterSlot: committeeSlots[1], AttesterSlot: committeeSlots[1],
PublicKey: pubkeys[3], PublicKey: pubkeys[3],
Status: statuses[3], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[3], ValidatorIndex: validatorIndices[3],
CommitteesAtSlot: 1, CommitteesAtSlot: 1,
}, },
@@ -931,7 +885,7 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
CommitteeIndex: committeeIndices[2], CommitteeIndex: committeeIndices[2],
AttesterSlot: committeeSlots[2], AttesterSlot: committeeSlots[2],
PublicKey: pubkeys[4], PublicKey: pubkeys[4],
Status: statuses[4], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[4], ValidatorIndex: validatorIndices[4],
ProposerSlots: expectedProposerSlots1, ProposerSlots: expectedProposerSlots1,
CommitteesAtSlot: 1, CommitteesAtSlot: 1,
@@ -944,7 +898,7 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
CommitteeIndex: committeeIndices[2], CommitteeIndex: committeeIndices[2],
AttesterSlot: committeeSlots[2], AttesterSlot: committeeSlots[2],
PublicKey: pubkeys[5], PublicKey: pubkeys[5],
Status: statuses[5], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[5], ValidatorIndex: validatorIndices[5],
ProposerSlots: expectedProposerSlots2, ProposerSlots: expectedProposerSlots2,
IsSyncCommittee: testCase.fetchSyncDuties, IsSyncCommittee: testCase.fetchSyncDuties,
@@ -952,47 +906,55 @@ func TestGetDutiesForEpoch_Valid(t *testing.T) {
}, },
{ {
PublicKey: pubkeys[6], PublicKey: pubkeys[6],
Status: statuses[6], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[6], ValidatorIndex: validatorIndices[6],
ProposerSlots: expectedProposerSlots3, ProposerSlots: expectedProposerSlots3,
IsSyncCommittee: testCase.fetchSyncDuties, IsSyncCommittee: testCase.fetchSyncDuties,
}, },
{ {
PublicKey: pubkeys[7], PublicKey: pubkeys[7],
Status: statuses[7], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[7], ValidatorIndex: validatorIndices[7],
ProposerSlots: expectedProposerSlots4, ProposerSlots: expectedProposerSlots4,
IsSyncCommittee: testCase.fetchSyncDuties, IsSyncCommittee: testCase.fetchSyncDuties,
}, },
{ {
PublicKey: pubkeys[8], PublicKey: pubkeys[8],
Status: statuses[8], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[8], ValidatorIndex: validatorIndices[8],
IsSyncCommittee: testCase.fetchSyncDuties, IsSyncCommittee: testCase.fetchSyncDuties,
}, },
{ {
PublicKey: pubkeys[9], PublicKey: pubkeys[9],
Status: statuses[9], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[9], ValidatorIndex: validatorIndices[9],
IsSyncCommittee: testCase.fetchSyncDuties, IsSyncCommittee: testCase.fetchSyncDuties,
}, },
{ {
PublicKey: pubkeys[10], PublicKey: pubkeys[10],
Status: statuses[10], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[10], ValidatorIndex: validatorIndices[10],
}, },
{ {
PublicKey: pubkeys[11], PublicKey: pubkeys[11],
Status: statuses[11], Status: ethpb.ValidatorStatus_ACTIVE,
ValidatorIndex: validatorIndices[11], ValidatorIndex: validatorIndices[11],
}, },
} }
validatorClient := &beaconApiValidatorClient{dutiesProvider: dutiesProvider} validatorClient := &beaconApiValidatorClient{dutiesProvider: dutiesProvider}
vals := make([]validatorForDuty, len(pubkeys))
for i := 0; i < len(pubkeys); i++ {
vals[i] = validatorForDuty{
pubkey: pubkeys[i],
index: validatorIndices[i],
status: ethpb.ValidatorStatus_ACTIVE,
}
}
duties, err := validatorClient.getDutiesForEpoch( duties, err := validatorClient.getDutiesForEpoch(
ctx, ctx,
epoch, epoch,
multipleValidatorStatus, vals,
testCase.fetchSyncDuties, testCase.fetchSyncDuties,
) )
require.NoError(t, err) require.NoError(t, err)
@@ -1018,41 +980,24 @@ func TestGetDuties_Valid(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
statuses := []ethpb.ValidatorStatus{ valCount := 12
ethpb.ValidatorStatus_DEPOSITED, pubkeys := make([][]byte, valCount)
ethpb.ValidatorStatus_PENDING, validatorIndices := make([]primitives.ValidatorIndex, valCount)
ethpb.ValidatorStatus_ACTIVE, vals := make([]validatorForDuty, valCount)
ethpb.ValidatorStatus_EXITING, for i := 0; i < valCount; i++ {
ethpb.ValidatorStatus_SLASHING,
ethpb.ValidatorStatus_EXITED,
ethpb.ValidatorStatus_EXITED,
ethpb.ValidatorStatus_EXITED,
ethpb.ValidatorStatus_EXITED,
ethpb.ValidatorStatus_DEPOSITED,
ethpb.ValidatorStatus_PENDING,
ethpb.ValidatorStatus_ACTIVE,
}
pubkeys := make([][]byte, len(statuses))
validatorIndices := make([]primitives.ValidatorIndex, len(statuses))
for i := range statuses {
pubkeys[i] = []byte(strconv.Itoa(i)) pubkeys[i] = []byte(strconv.Itoa(i))
validatorIndices[i] = primitives.ValidatorIndex(i) validatorIndices[i] = primitives.ValidatorIndex(i)
vals[i] = validatorForDuty{
pubkey: pubkeys[i],
index: validatorIndices[i],
status: ethpb.ValidatorStatus_ACTIVE,
}
} }
committeeIndices := []primitives.CommitteeIndex{25, 26, 27} committeeIndices := []primitives.CommitteeIndex{25, 26, 27}
committeeSlots := []primitives.Slot{28, 29, 30} committeeSlots := []primitives.Slot{28, 29, 30}
proposerSlots := []primitives.Slot{31, 32, 33, 34, 35, 36, 37, 38} proposerSlots := []primitives.Slot{31, 32, 33, 34, 35, 36, 37, 38}
statusResps := make([]*ethpb.ValidatorStatusResponse, len(statuses))
for i, s := range statuses {
statusResps[i] = &ethpb.ValidatorStatusResponse{Status: s}
}
multipleValidatorStatus := &ethpb.MultipleValidatorStatusResponse{
PublicKeys: pubkeys,
Indices: validatorIndices,
Statuses: statusResps,
}
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
@@ -1070,7 +1015,7 @@ func TestGetDuties_Valid(t *testing.T) {
dutiesProvider.EXPECT().GetAttesterDuties( dutiesProvider.EXPECT().GetAttesterDuties(
ctx, ctx,
testCase.epoch, testCase.epoch,
multipleValidatorStatus.Indices, validatorIndices,
).Return( ).Return(
generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots), generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots),
nil, nil,
@@ -1089,7 +1034,7 @@ func TestGetDuties_Valid(t *testing.T) {
dutiesProvider.EXPECT().GetSyncDuties( dutiesProvider.EXPECT().GetSyncDuties(
ctx, ctx,
testCase.epoch, testCase.epoch,
multipleValidatorStatus.Indices, validatorIndices,
).Return( ).Return(
generateValidSyncDuties(pubkeys, validatorIndices), generateValidSyncDuties(pubkeys, validatorIndices),
nil, nil,
@@ -1143,7 +1088,7 @@ func TestGetDuties_Valid(t *testing.T) {
Data: []*structs.ValidatorContainer{ Data: []*structs.ValidatorContainer{
{ {
Index: strconv.FormatUint(uint64(validatorIndices[0]), 10), Index: strconv.FormatUint(uint64(validatorIndices[0]), 10),
Status: "pending_initialized", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[0]), Pubkey: hexutil.Encode(pubkeys[0]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1151,7 +1096,7 @@ func TestGetDuties_Valid(t *testing.T) {
}, },
{ {
Index: strconv.FormatUint(uint64(validatorIndices[1]), 10), Index: strconv.FormatUint(uint64(validatorIndices[1]), 10),
Status: "pending_queued", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[1]), Pubkey: hexutil.Encode(pubkeys[1]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1167,7 +1112,7 @@ func TestGetDuties_Valid(t *testing.T) {
}, },
{ {
Index: strconv.FormatUint(uint64(validatorIndices[3]), 10), Index: strconv.FormatUint(uint64(validatorIndices[3]), 10),
Status: "active_exiting", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[3]), Pubkey: hexutil.Encode(pubkeys[3]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1175,7 +1120,7 @@ func TestGetDuties_Valid(t *testing.T) {
}, },
{ {
Index: strconv.FormatUint(uint64(validatorIndices[4]), 10), Index: strconv.FormatUint(uint64(validatorIndices[4]), 10),
Status: "active_slashed", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[4]), Pubkey: hexutil.Encode(pubkeys[4]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1183,7 +1128,7 @@ func TestGetDuties_Valid(t *testing.T) {
}, },
{ {
Index: strconv.FormatUint(uint64(validatorIndices[5]), 10), Index: strconv.FormatUint(uint64(validatorIndices[5]), 10),
Status: "exited_unslashed", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[5]), Pubkey: hexutil.Encode(pubkeys[5]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1191,7 +1136,7 @@ func TestGetDuties_Valid(t *testing.T) {
}, },
{ {
Index: strconv.FormatUint(uint64(validatorIndices[6]), 10), Index: strconv.FormatUint(uint64(validatorIndices[6]), 10),
Status: "exited_slashed", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[6]), Pubkey: hexutil.Encode(pubkeys[6]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1199,7 +1144,7 @@ func TestGetDuties_Valid(t *testing.T) {
}, },
{ {
Index: strconv.FormatUint(uint64(validatorIndices[7]), 10), Index: strconv.FormatUint(uint64(validatorIndices[7]), 10),
Status: "withdrawal_possible", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[7]), Pubkey: hexutil.Encode(pubkeys[7]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1207,7 +1152,7 @@ func TestGetDuties_Valid(t *testing.T) {
}, },
{ {
Index: strconv.FormatUint(uint64(validatorIndices[8]), 10), Index: strconv.FormatUint(uint64(validatorIndices[8]), 10),
Status: "withdrawal_done", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[8]), Pubkey: hexutil.Encode(pubkeys[8]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1215,7 +1160,7 @@ func TestGetDuties_Valid(t *testing.T) {
}, },
{ {
Index: strconv.FormatUint(uint64(validatorIndices[9]), 10), Index: strconv.FormatUint(uint64(validatorIndices[9]), 10),
Status: "pending_initialized", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[9]), Pubkey: hexutil.Encode(pubkeys[9]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1223,7 +1168,7 @@ func TestGetDuties_Valid(t *testing.T) {
}, },
{ {
Index: strconv.FormatUint(uint64(validatorIndices[10]), 10), Index: strconv.FormatUint(uint64(validatorIndices[10]), 10),
Status: "pending_queued", Status: "active_ongoing",
Validator: &structs.Validator{ Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkeys[10]), Pubkey: hexutil.Encode(pubkeys[10]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10), ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
@@ -1242,27 +1187,16 @@ func TestGetDuties_Valid(t *testing.T) {
nil, nil,
).MinTimes(1) ).MinTimes(1)
prysmBeaconChainClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
prysmBeaconChainClient.EXPECT().GetValidatorCount(
ctx,
gomock.Any(),
gomock.Any(),
).Return(
nil,
iface.ErrNotSupported,
).MinTimes(1)
// Make sure that our values are equal to what would be returned by calling getDutiesForEpoch individually // Make sure that our values are equal to what would be returned by calling getDutiesForEpoch individually
validatorClient := &beaconApiValidatorClient{ validatorClient := &beaconApiValidatorClient{
dutiesProvider: dutiesProvider, dutiesProvider: dutiesProvider,
stateValidatorsProvider: stateValidatorsProvider, stateValidatorsProvider: stateValidatorsProvider,
prysmBeaconChainCLient: prysmBeaconChainClient,
} }
expectedCurrentEpochDuties, err := validatorClient.getDutiesForEpoch( expectedCurrentEpochDuties, err := validatorClient.getDutiesForEpoch(
ctx, ctx,
testCase.epoch, testCase.epoch,
multipleValidatorStatus, vals,
fetchSyncDuties, fetchSyncDuties,
) )
require.NoError(t, err) require.NoError(t, err)
@@ -1270,7 +1204,7 @@ func TestGetDuties_Valid(t *testing.T) {
expectedNextEpochDuties, err := validatorClient.getDutiesForEpoch( expectedNextEpochDuties, err := validatorClient.getDutiesForEpoch(
ctx, ctx,
testCase.epoch+1, testCase.epoch+1,
multipleValidatorStatus, vals,
fetchSyncDuties, fetchSyncDuties,
) )
require.NoError(t, err) require.NoError(t, err)
@@ -1291,7 +1225,7 @@ func TestGetDuties_Valid(t *testing.T) {
} }
} }
func TestGetDuties_GetValidatorStatusFailed(t *testing.T) { func TestGetDuties_GetStateValidatorsFailed(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
@@ -1316,7 +1250,7 @@ func TestGetDuties_GetValidatorStatusFailed(t *testing.T) {
Epoch: 1, Epoch: 1,
PublicKeys: [][]byte{}, PublicKeys: [][]byte{},
}) })
assert.ErrorContains(t, "failed to get validator status", err) assert.ErrorContains(t, "failed to get state validators", err)
assert.ErrorContains(t, "foo error", err) assert.ErrorContains(t, "foo error", err)
} }
@@ -1325,6 +1259,7 @@ func TestGetDuties_GetDutiesForEpochFailed(t *testing.T) {
defer ctrl.Finish() defer ctrl.Finish()
ctx := context.Background() ctx := context.Background()
pubkey := []byte{1, 2, 3}
stateValidatorsProvider := mock.NewMockStateValidatorsProvider(ctrl) stateValidatorsProvider := mock.NewMockStateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidators( stateValidatorsProvider.EXPECT().GetStateValidators(
@@ -1334,7 +1269,13 @@ func TestGetDuties_GetDutiesForEpochFailed(t *testing.T) {
gomock.Any(), gomock.Any(),
).Return( ).Return(
&structs.GetValidatorsResponse{ &structs.GetValidatorsResponse{
Data: []*structs.ValidatorContainer{}, Data: []*structs.ValidatorContainer{{
Index: "0",
Status: "active_ongoing",
Validator: &structs.Validator{
Pubkey: hexutil.Encode(pubkey),
},
}},
}, },
nil, nil,
).Times(1) ).Times(1)
@@ -1348,26 +1289,28 @@ func TestGetDuties_GetDutiesForEpochFailed(t *testing.T) {
nil, nil,
errors.New("foo error"), errors.New("foo error"),
).Times(1) ).Times(1)
dutiesProvider.EXPECT().GetAttesterDuties(
prysmBeaconChainClient := validatormock.NewMockPrysmBeaconChainClient(ctrl) ctx,
prysmBeaconChainClient.EXPECT().GetValidatorCount( primitives.Epoch(2),
gomock.Any(),
).Times(1)
dutiesProvider.EXPECT().GetProposerDuties(
ctx, ctx,
gomock.Any(), gomock.Any(),
).Times(2)
dutiesProvider.EXPECT().GetCommittees(
ctx,
gomock.Any(), gomock.Any(),
).Return( ).Times(2)
nil,
iface.ErrNotSupported,
).MinTimes(1)
validatorClient := &beaconApiValidatorClient{ validatorClient := &beaconApiValidatorClient{
stateValidatorsProvider: stateValidatorsProvider, stateValidatorsProvider: stateValidatorsProvider,
dutiesProvider: dutiesProvider, dutiesProvider: dutiesProvider,
prysmBeaconChainCLient: prysmBeaconChainClient,
} }
_, err := validatorClient.getDuties(ctx, &ethpb.DutiesRequest{ _, err := validatorClient.getDuties(ctx, &ethpb.DutiesRequest{
Epoch: 1, Epoch: 1,
PublicKeys: [][]byte{}, PublicKeys: [][]byte{pubkey},
}) })
assert.ErrorContains(t, "failed to get duties for current epoch `1`", err) assert.ErrorContains(t, "failed to get duties for current epoch `1`", err)
assert.ErrorContains(t, "foo error", err) assert.ErrorContains(t, "foo error", err)