prysm-pulse/validator/client/beacon-api/duties_test.go
terencechain d17996f8b0
Update to V4 🚀 (#12134)
* Update V3 from V4

* Fix build v3 -> v4

* Update ssz

* Update beacon_chain.pb.go

* Fix formatter import

* Update update-mockgen.sh comment to v4

* Fix conflicts. Pass build and tests

* Fix test
2023-03-17 18:52:56 +00:00

1519 lines
44 KiB
Go

package beacon_api
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/mock/gomock"
"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"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/validator/client/beacon-api/mock"
)
const getAttesterDutiesTestEndpoint = "/eth/v1/validator/duties/attester"
const getProposerDutiesTestEndpoint = "/eth/v1/validator/duties/proposer"
const getSyncDutiesTestEndpoint = "/eth/v1/validator/duties/sync"
const getCommitteesTestEndpoint = "/eth/v1/beacon/states/head/committees"
func TestGetAttesterDuties_Valid(t *testing.T) {
stringValidatorIndices := []string{"2", "9"}
const epoch = primitives.Epoch(1)
validatorIndicesBytes, err := json.Marshal(stringValidatorIndices)
require.NoError(t, err)
expectedAttesterDuties := apimiddleware.AttesterDutiesResponseJson{
Data: []*apimiddleware.AttesterDutyJson{
{
Pubkey: hexutil.Encode([]byte{1}),
ValidatorIndex: "2",
CommitteeIndex: "3",
CommitteeLength: "4",
CommitteesAtSlot: "5",
ValidatorCommitteeIndex: "6",
Slot: "7",
},
{
Pubkey: hexutil.Encode([]byte{8}),
ValidatorIndex: "9",
CommitteeIndex: "10",
CommitteeLength: "11",
CommitteesAtSlot: "12",
ValidatorCommitteeIndex: "13",
Slot: "14",
},
},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
validatorIndices := []primitives.ValidatorIndex{2, 9}
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostRestJson(
ctx,
fmt.Sprintf("%s/%d", getAttesterDutiesTestEndpoint, epoch),
nil,
bytes.NewBuffer(validatorIndicesBytes),
&apimiddleware.AttesterDutiesResponseJson{},
).Return(
nil,
nil,
).SetArg(
4,
expectedAttesterDuties,
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
attesterDuties, err := dutiesProvider.GetAttesterDuties(ctx, epoch, validatorIndices)
require.NoError(t, err)
assert.DeepEqual(t, expectedAttesterDuties.Data, attesterDuties)
}
func TestGetAttesterDuties_HttpError(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostRestJson(
ctx,
fmt.Sprintf("%s/%d", getAttesterDutiesTestEndpoint, epoch),
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(
nil,
errors.New("foo error"),
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetAttesterDuties(ctx, epoch, nil)
assert.ErrorContains(t, "foo error", err)
assert.ErrorContains(t, "failed to send POST data to REST endpoint", err)
}
func TestGetAttesterDuties_NilAttesterDuty(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostRestJson(
ctx,
fmt.Sprintf("%s/%d", getAttesterDutiesTestEndpoint, epoch),
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(
nil,
nil,
).SetArg(
4,
apimiddleware.AttesterDutiesResponseJson{
Data: []*apimiddleware.AttesterDutyJson{nil},
},
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetAttesterDuties(ctx, epoch, nil)
assert.ErrorContains(t, "attester duty at index `0` is nil", err)
}
func TestGetProposerDuties_Valid(t *testing.T) {
const epoch = primitives.Epoch(1)
expectedProposerDuties := apimiddleware.ProposerDutiesResponseJson{
Data: []*apimiddleware.ProposerDutyJson{
{
Pubkey: hexutil.Encode([]byte{1}),
ValidatorIndex: "2",
Slot: "3",
},
{
Pubkey: hexutil.Encode([]byte{4}),
ValidatorIndex: "5",
Slot: "6",
},
},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("%s/%d", getProposerDutiesTestEndpoint, epoch),
&apimiddleware.ProposerDutiesResponseJson{},
).Return(
nil,
nil,
).SetArg(
2,
expectedProposerDuties,
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
proposerDuties, err := dutiesProvider.GetProposerDuties(ctx, epoch)
require.NoError(t, err)
assert.DeepEqual(t, expectedProposerDuties.Data, proposerDuties)
}
func TestGetProposerDuties_HttpError(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("%s/%d", getProposerDutiesTestEndpoint, epoch),
gomock.Any(),
).Return(
nil,
errors.New("foo error"),
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetProposerDuties(ctx, epoch)
assert.ErrorContains(t, "foo error", err)
assert.ErrorContains(t, "failed to query proposer duties for epoch `1`", err)
}
func TestGetProposerDuties_NilData(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("%s/%d", getProposerDutiesTestEndpoint, epoch),
gomock.Any(),
).Return(
nil,
nil,
).SetArg(
2,
apimiddleware.ProposerDutiesResponseJson{
Data: nil,
},
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetProposerDuties(ctx, epoch)
assert.ErrorContains(t, "proposer duties data is nil", err)
}
func TestGetProposerDuties_NilProposerDuty(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("%s/%d", getProposerDutiesTestEndpoint, epoch),
gomock.Any(),
).Return(
nil,
nil,
).SetArg(
2,
apimiddleware.ProposerDutiesResponseJson{
Data: []*apimiddleware.ProposerDutyJson{nil},
},
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetProposerDuties(ctx, epoch)
assert.ErrorContains(t, "proposer duty at index `0` is nil", err)
}
func TestGetSyncDuties_Valid(t *testing.T) {
stringValidatorIndices := []string{"2", "6"}
const epoch = primitives.Epoch(1)
validatorIndicesBytes, err := json.Marshal(stringValidatorIndices)
require.NoError(t, err)
expectedSyncDuties := apimiddleware.SyncCommitteeDutiesResponseJson{
Data: []*apimiddleware.SyncCommitteeDuty{
{
Pubkey: hexutil.Encode([]byte{1}),
ValidatorIndex: "2",
ValidatorSyncCommitteeIndices: []string{
"3",
"4",
},
},
{
Pubkey: hexutil.Encode([]byte{5}),
ValidatorIndex: "6",
ValidatorSyncCommitteeIndices: []string{
"7",
"8",
},
},
},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
validatorIndices := []primitives.ValidatorIndex{2, 6}
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostRestJson(
ctx,
fmt.Sprintf("%s/%d", getSyncDutiesTestEndpoint, epoch),
nil,
bytes.NewBuffer(validatorIndicesBytes),
&apimiddleware.SyncCommitteeDutiesResponseJson{},
).Return(
nil,
nil,
).SetArg(
4,
expectedSyncDuties,
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
syncDuties, err := dutiesProvider.GetSyncDuties(ctx, epoch, validatorIndices)
require.NoError(t, err)
assert.DeepEqual(t, expectedSyncDuties.Data, syncDuties)
}
func TestGetSyncDuties_HttpError(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostRestJson(
ctx,
fmt.Sprintf("%s/%d", getSyncDutiesTestEndpoint, epoch),
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(
nil,
errors.New("foo error"),
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetSyncDuties(ctx, epoch, nil)
assert.ErrorContains(t, "foo error", err)
assert.ErrorContains(t, "failed to send POST data to REST endpoint", err)
}
func TestGetSyncDuties_NilData(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostRestJson(
ctx,
fmt.Sprintf("%s/%d", getSyncDutiesTestEndpoint, epoch),
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(
nil,
nil,
).SetArg(
4,
apimiddleware.SyncCommitteeDutiesResponseJson{
Data: nil,
},
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetSyncDuties(ctx, epoch, nil)
assert.ErrorContains(t, "sync duties data is nil", err)
}
func TestGetSyncDuties_NilSyncDuty(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostRestJson(
ctx,
fmt.Sprintf("%s/%d", getSyncDutiesTestEndpoint, epoch),
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(
nil,
nil,
).SetArg(
4,
apimiddleware.SyncCommitteeDutiesResponseJson{
Data: []*apimiddleware.SyncCommitteeDuty{nil},
},
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetSyncDuties(ctx, epoch, nil)
assert.ErrorContains(t, "sync duty at index `0` is nil", err)
}
func TestGetCommittees_Valid(t *testing.T) {
const epoch = primitives.Epoch(1)
expectedCommittees := apimiddleware.StateCommitteesResponseJson{
Data: []*apimiddleware.CommitteeJson{
{
Index: "1",
Slot: "2",
Validators: []string{
"3",
"4",
},
},
{
Index: "5",
Slot: "6",
Validators: []string{
"7",
"8",
},
},
},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("%s?epoch=%d", getCommitteesTestEndpoint, epoch),
&apimiddleware.StateCommitteesResponseJson{},
).Return(
nil,
nil,
).SetArg(
2,
expectedCommittees,
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
committees, err := dutiesProvider.GetCommittees(ctx, epoch)
require.NoError(t, err)
assert.DeepEqual(t, expectedCommittees.Data, committees)
}
func TestGetCommittees_HttpError(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("%s?epoch=%d", getCommitteesTestEndpoint, epoch),
gomock.Any(),
).Return(
nil,
errors.New("foo error"),
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetCommittees(ctx, epoch)
assert.ErrorContains(t, "foo error", err)
assert.ErrorContains(t, "failed to query committees for epoch `1`", err)
}
func TestGetCommittees_NilData(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("%s?epoch=%d", getCommitteesTestEndpoint, epoch),
gomock.Any(),
).Return(
nil,
nil,
).SetArg(
2,
apimiddleware.StateCommitteesResponseJson{
Data: nil,
},
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetCommittees(ctx, epoch)
assert.ErrorContains(t, "state committees data is nil", err)
}
func TestGetCommittees_NilCommittee(t *testing.T) {
const epoch = primitives.Epoch(1)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("%s?epoch=%d", getCommitteesTestEndpoint, epoch),
gomock.Any(),
).Return(
nil,
nil,
).SetArg(
2,
apimiddleware.StateCommitteesResponseJson{
Data: []*apimiddleware.CommitteeJson{nil},
},
).Times(1)
dutiesProvider := &beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}
_, err := dutiesProvider.GetCommittees(ctx, epoch)
assert.ErrorContains(t, "committee at index `0` is nil", err)
}
func TestGetDutiesForEpoch_Error(t *testing.T) {
const epoch = primitives.Epoch(1)
pubkeys := [][]byte{{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}}
validatorIndices := []primitives.ValidatorIndex{13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}
committeeIndices := []primitives.CommitteeIndex{25, 26, 27}
committeeSlots := []primitives.Slot{28, 29, 30}
proposerSlots := []primitives.Slot{31, 32, 33, 34, 35, 36, 37, 38}
testCases := []struct {
name string
expectedError string
generateAttesterDuties func() []*apimiddleware.AttesterDutyJson
fetchAttesterDutiesError error
generateProposerDuties func() []*apimiddleware.ProposerDutyJson
fetchProposerDutiesError error
generateSyncDuties func() []*apimiddleware.SyncCommitteeDuty
fetchSyncDutiesError error
generateCommittees func() []*apimiddleware.CommitteeJson
fetchCommitteesError error
}{
{
name: "get attester duties failed",
expectedError: "failed to get attester duties for epoch `1`: foo error",
fetchAttesterDutiesError: errors.New("foo error"),
},
{
name: "get proposer duties failed",
expectedError: "failed to get proposer duties for epoch `1`: foo error",
fetchAttesterDutiesError: nil,
fetchProposerDutiesError: errors.New("foo error"),
},
{
name: "get sync duties failed",
expectedError: "failed to get sync duties for epoch `1`: foo error",
fetchSyncDutiesError: errors.New("foo error"),
},
{
name: "get committees failed",
expectedError: "failed to get committees for epoch `1`: foo error",
fetchCommitteesError: errors.New("foo error"),
},
{
name: "bad attester validator index",
expectedError: "failed to parse attester validator index `foo`",
generateAttesterDuties: func() []*apimiddleware.AttesterDutyJson {
attesterDuties := generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots)
attesterDuties[0].ValidatorIndex = "foo"
return attesterDuties
},
},
{
name: "bad attester slot",
expectedError: "failed to parse attester slot `foo`",
generateAttesterDuties: func() []*apimiddleware.AttesterDutyJson {
attesterDuties := generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots)
attesterDuties[0].Slot = "foo"
return attesterDuties
},
},
{
name: "bad attester committee index",
expectedError: "failed to parse attester committee index `foo`",
generateAttesterDuties: func() []*apimiddleware.AttesterDutyJson {
attesterDuties := generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots)
attesterDuties[0].CommitteeIndex = "foo"
return attesterDuties
},
},
{
name: "bad proposer validator index",
expectedError: "failed to parse proposer validator index `foo`",
generateProposerDuties: func() []*apimiddleware.ProposerDutyJson {
proposerDuties := generateValidProposerDuties(pubkeys, validatorIndices, proposerSlots)
proposerDuties[0].ValidatorIndex = "foo"
return proposerDuties
},
},
{
name: "bad proposer slot",
expectedError: "failed to parse proposer slot `foo`",
generateProposerDuties: func() []*apimiddleware.ProposerDutyJson {
proposerDuties := generateValidProposerDuties(pubkeys, validatorIndices, proposerSlots)
proposerDuties[0].Slot = "foo"
return proposerDuties
},
},
{
name: "bad sync validator index",
expectedError: "failed to parse sync validator index `foo`",
generateSyncDuties: func() []*apimiddleware.SyncCommitteeDuty {
syncDuties := generateValidSyncDuties(pubkeys, validatorIndices)
syncDuties[0].ValidatorIndex = "foo"
return syncDuties
},
},
{
name: "bad committee index",
expectedError: "failed to parse committee index `foo`",
generateCommittees: func() []*apimiddleware.CommitteeJson {
committees := generateValidCommittees(committeeIndices, committeeSlots, validatorIndices)
committees[0].Index = "foo"
return committees
},
},
{
name: "bad committee slot",
expectedError: "failed to parse slot `foo`",
generateCommittees: func() []*apimiddleware.CommitteeJson {
committees := generateValidCommittees(committeeIndices, committeeSlots, validatorIndices)
committees[0].Slot = "foo"
return committees
},
},
{
name: "bad committee validator index",
expectedError: "failed to parse committee validator index `foo`",
generateCommittees: func() []*apimiddleware.CommitteeJson {
committees := generateValidCommittees(committeeIndices, committeeSlots, validatorIndices)
committees[0].Validators[0] = "foo"
return committees
},
},
{
name: "committee index and slot not found in committees mapping",
expectedError: "failed to find validators for committee index `1` and slot `2`",
generateAttesterDuties: func() []*apimiddleware.AttesterDutyJson {
attesterDuties := generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots)
attesterDuties[0].CommitteeIndex = "1"
attesterDuties[0].Slot = "2"
return attesterDuties
},
generateCommittees: func() []*apimiddleware.CommitteeJson {
return []*apimiddleware.CommitteeJson{}
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
var attesterDuties []*apimiddleware.AttesterDutyJson
if testCase.generateAttesterDuties == nil {
attesterDuties = generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots)
} else {
attesterDuties = testCase.generateAttesterDuties()
}
var proposerDuties []*apimiddleware.ProposerDutyJson
if testCase.generateProposerDuties == nil {
proposerDuties = generateValidProposerDuties(pubkeys, validatorIndices, proposerSlots)
} else {
proposerDuties = testCase.generateProposerDuties()
}
var syncDuties []*apimiddleware.SyncCommitteeDuty
if testCase.generateSyncDuties == nil {
syncDuties = generateValidSyncDuties(pubkeys, validatorIndices)
} else {
syncDuties = testCase.generateSyncDuties()
}
var committees []*apimiddleware.CommitteeJson
if testCase.generateCommittees == nil {
committees = generateValidCommittees(committeeIndices, committeeSlots, validatorIndices)
} else {
committees = testCase.generateCommittees()
}
dutiesProvider := mock.NewMockdutiesProvider(ctrl)
dutiesProvider.EXPECT().GetAttesterDuties(
ctx,
epoch,
gomock.Any(),
).Return(
attesterDuties,
testCase.fetchAttesterDutiesError,
).AnyTimes()
dutiesProvider.EXPECT().GetProposerDuties(
ctx,
epoch,
).Return(
proposerDuties,
testCase.fetchProposerDutiesError,
).AnyTimes()
dutiesProvider.EXPECT().GetSyncDuties(
ctx,
epoch,
gomock.Any(),
).Return(
syncDuties,
testCase.fetchSyncDutiesError,
).AnyTimes()
dutiesProvider.EXPECT().GetCommittees(
ctx,
epoch,
).Return(
committees,
testCase.fetchCommitteesError,
).AnyTimes()
validatorClient := &beaconApiValidatorClient{dutiesProvider: dutiesProvider}
_, err := validatorClient.getDutiesForEpoch(
ctx,
epoch,
&ethpb.MultipleValidatorStatusResponse{
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,
)
assert.ErrorContains(t, testCase.expectedError, err)
})
}
}
func TestGetDutiesForEpoch_Valid(t *testing.T) {
testCases := []struct {
name string
fetchSyncDuties bool
}{
{
name: "fetch attester and proposer duties",
fetchSyncDuties: false,
},
{
name: "fetch attester and sync and proposer duties",
fetchSyncDuties: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
const epoch = primitives.Epoch(1)
pubkeys := [][]byte{{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}}
validatorIndices := []primitives.ValidatorIndex{13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}
committeeIndices := []primitives.CommitteeIndex{25, 26, 27}
committeeSlots := []primitives.Slot{28, 29, 30}
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)
defer ctrl.Finish()
ctx := context.Background()
dutiesProvider := mock.NewMockdutiesProvider(ctrl)
dutiesProvider.EXPECT().GetCommittees(
ctx,
epoch,
).Return(
generateValidCommittees(committeeIndices, committeeSlots, validatorIndices),
nil,
).Times(1)
dutiesProvider.EXPECT().GetAttesterDuties(
ctx,
epoch,
multipleValidatorStatus.Indices,
).Return(
generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots),
nil,
).Times(1)
dutiesProvider.EXPECT().GetProposerDuties(
ctx,
epoch,
).Return(
generateValidProposerDuties(pubkeys, validatorIndices, proposerSlots),
nil,
).Times(1)
if testCase.fetchSyncDuties {
dutiesProvider.EXPECT().GetSyncDuties(
ctx,
epoch,
multipleValidatorStatus.Indices,
).Return(
generateValidSyncDuties(pubkeys, validatorIndices),
nil,
).Times(1)
}
var expectedProposerSlots1 []primitives.Slot
var expectedProposerSlots2 []primitives.Slot
var expectedProposerSlots3 []primitives.Slot
var expectedProposerSlots4 []primitives.Slot
expectedProposerSlots1 = []primitives.Slot{
proposerSlots[0],
proposerSlots[1],
}
expectedProposerSlots2 = []primitives.Slot{
proposerSlots[2],
proposerSlots[3],
}
expectedProposerSlots3 = []primitives.Slot{
proposerSlots[4],
proposerSlots[5],
}
expectedProposerSlots4 = []primitives.Slot{
proposerSlots[6],
proposerSlots[7],
}
expectedDuties := []*ethpb.DutiesResponse_Duty{
{
Committee: []primitives.ValidatorIndex{
validatorIndices[0],
validatorIndices[1],
},
CommitteeIndex: committeeIndices[0],
AttesterSlot: committeeSlots[0],
PublicKey: pubkeys[0],
Status: statuses[0],
ValidatorIndex: validatorIndices[0],
},
{
Committee: []primitives.ValidatorIndex{
validatorIndices[0],
validatorIndices[1],
},
CommitteeIndex: committeeIndices[0],
AttesterSlot: committeeSlots[0],
PublicKey: pubkeys[1],
Status: statuses[1],
ValidatorIndex: validatorIndices[1],
},
{
Committee: []primitives.ValidatorIndex{
validatorIndices[2],
validatorIndices[3],
},
CommitteeIndex: committeeIndices[1],
AttesterSlot: committeeSlots[1],
PublicKey: pubkeys[2],
Status: statuses[2],
ValidatorIndex: validatorIndices[2],
},
{
Committee: []primitives.ValidatorIndex{
validatorIndices[2],
validatorIndices[3],
},
CommitteeIndex: committeeIndices[1],
AttesterSlot: committeeSlots[1],
PublicKey: pubkeys[3],
Status: statuses[3],
ValidatorIndex: validatorIndices[3],
},
{
Committee: []primitives.ValidatorIndex{
validatorIndices[4],
validatorIndices[5],
},
CommitteeIndex: committeeIndices[2],
AttesterSlot: committeeSlots[2],
PublicKey: pubkeys[4],
Status: statuses[4],
ValidatorIndex: validatorIndices[4],
ProposerSlots: expectedProposerSlots1,
},
{
Committee: []primitives.ValidatorIndex{
validatorIndices[4],
validatorIndices[5],
},
CommitteeIndex: committeeIndices[2],
AttesterSlot: committeeSlots[2],
PublicKey: pubkeys[5],
Status: statuses[5],
ValidatorIndex: validatorIndices[5],
ProposerSlots: expectedProposerSlots2,
IsSyncCommittee: testCase.fetchSyncDuties,
},
{
PublicKey: pubkeys[6],
Status: statuses[6],
ValidatorIndex: validatorIndices[6],
ProposerSlots: expectedProposerSlots3,
IsSyncCommittee: testCase.fetchSyncDuties,
},
{
PublicKey: pubkeys[7],
Status: statuses[7],
ValidatorIndex: validatorIndices[7],
ProposerSlots: expectedProposerSlots4,
IsSyncCommittee: testCase.fetchSyncDuties,
},
{
PublicKey: pubkeys[8],
Status: statuses[8],
ValidatorIndex: validatorIndices[8],
IsSyncCommittee: testCase.fetchSyncDuties,
},
{
PublicKey: pubkeys[9],
Status: statuses[9],
ValidatorIndex: validatorIndices[9],
IsSyncCommittee: testCase.fetchSyncDuties,
},
{
PublicKey: pubkeys[10],
Status: statuses[10],
ValidatorIndex: validatorIndices[10],
},
{
PublicKey: pubkeys[11],
Status: statuses[11],
ValidatorIndex: validatorIndices[11],
},
}
validatorClient := &beaconApiValidatorClient{dutiesProvider: dutiesProvider}
duties, err := validatorClient.getDutiesForEpoch(
ctx,
epoch,
multipleValidatorStatus,
testCase.fetchSyncDuties,
)
require.NoError(t, err)
assert.DeepEqual(t, expectedDuties, duties)
})
}
}
func TestGetDuties_Valid(t *testing.T) {
testCases := []struct {
name string
epoch primitives.Epoch
}{
{
name: "genesis epoch",
epoch: params.BeaconConfig().GenesisEpoch,
},
{
name: "altair epoch",
epoch: params.BeaconConfig().AltairForkEpoch,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
pubkeys := [][]byte{{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}}
validatorIndices := []primitives.ValidatorIndex{13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}
committeeIndices := []primitives.CommitteeIndex{25, 26, 27}
committeeSlots := []primitives.Slot{28, 29, 30}
proposerSlots := []primitives.Slot{31, 32, 33, 34, 35, 36, 37, 38}
statuses := []ethpb.ValidatorStatus{
ethpb.ValidatorStatus_DEPOSITED,
ethpb.ValidatorStatus_PENDING,
ethpb.ValidatorStatus_ACTIVE,
ethpb.ValidatorStatus_EXITING,
ethpb.ValidatorStatus_SLASHING,
ethpb.ValidatorStatus_EXITED,
ethpb.ValidatorStatus_EXITED,
ethpb.ValidatorStatus_EXITED,
ethpb.ValidatorStatus_EXITED,
ethpb.ValidatorStatus_DEPOSITED,
ethpb.ValidatorStatus_PENDING,
ethpb.ValidatorStatus_ACTIVE,
}
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)
defer ctrl.Finish()
ctx := context.Background()
dutiesProvider := mock.NewMockdutiesProvider(ctrl)
dutiesProvider.EXPECT().GetCommittees(
ctx,
testCase.epoch,
).Return(
generateValidCommittees(committeeIndices, committeeSlots, validatorIndices),
nil,
).Times(2)
dutiesProvider.EXPECT().GetAttesterDuties(
ctx,
testCase.epoch,
multipleValidatorStatus.Indices,
).Return(
generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots),
nil,
).Times(2)
dutiesProvider.EXPECT().GetProposerDuties(
ctx,
testCase.epoch,
).Return(
generateValidProposerDuties(pubkeys, validatorIndices, proposerSlots),
nil,
).Times(2)
fetchSyncDuties := testCase.epoch >= params.BeaconConfig().AltairForkEpoch
if fetchSyncDuties {
dutiesProvider.EXPECT().GetSyncDuties(
ctx,
testCase.epoch,
multipleValidatorStatus.Indices,
).Return(
generateValidSyncDuties(pubkeys, validatorIndices),
nil,
).Times(2)
}
dutiesProvider.EXPECT().GetCommittees(
ctx,
testCase.epoch+1,
).Return(
reverseSlice(generateValidCommittees(committeeIndices, committeeSlots, validatorIndices)),
nil,
).Times(2)
dutiesProvider.EXPECT().GetAttesterDuties(
ctx,
testCase.epoch+1,
validatorIndices,
).Return(
reverseSlice(generateValidAttesterDuties(pubkeys, validatorIndices, committeeIndices, committeeSlots)),
nil,
).Times(2)
dutiesProvider.EXPECT().GetProposerDuties(
ctx,
testCase.epoch+1,
).Return(
generateValidProposerDuties(pubkeys, validatorIndices, proposerSlots),
nil,
).Times(2)
if fetchSyncDuties {
dutiesProvider.EXPECT().GetSyncDuties(
ctx,
testCase.epoch+1,
validatorIndices,
).Return(
reverseSlice(generateValidSyncDuties(pubkeys, validatorIndices)),
nil,
).Times(2)
}
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidators(
ctx,
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(
&apimiddleware.StateValidatorsResponseJson{
Data: []*apimiddleware.ValidatorContainerJson{
{
Index: strconv.FormatUint(uint64(validatorIndices[0]), 10),
Status: "pending_initialized",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[0]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[1]), 10),
Status: "pending_queued",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[1]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[2]), 10),
Status: "active_ongoing",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[2]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[3]), 10),
Status: "active_exiting",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[3]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[4]), 10),
Status: "active_slashed",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[4]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[5]), 10),
Status: "exited_unslashed",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[5]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[6]), 10),
Status: "exited_slashed",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[6]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[7]), 10),
Status: "withdrawal_possible",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[7]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[8]), 10),
Status: "withdrawal_done",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[8]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[9]), 10),
Status: "pending_initialized",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[9]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[10]), 10),
Status: "pending_queued",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[10]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
{
Index: strconv.FormatUint(uint64(validatorIndices[11]), 10),
Status: "active_ongoing",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode(pubkeys[11]),
ActivationEpoch: strconv.FormatUint(uint64(testCase.epoch), 10),
},
},
},
},
nil,
).MinTimes(1)
// Make sure that our values are equal to what would be returned by calling getDutiesForEpoch individually
validatorClient := &beaconApiValidatorClient{
dutiesProvider: dutiesProvider,
stateValidatorsProvider: stateValidatorsProvider,
}
expectedCurrentEpochDuties, err := validatorClient.getDutiesForEpoch(
ctx,
testCase.epoch,
multipleValidatorStatus,
fetchSyncDuties,
)
require.NoError(t, err)
expectedNextEpochDuties, err := validatorClient.getDutiesForEpoch(
ctx,
testCase.epoch+1,
multipleValidatorStatus,
fetchSyncDuties,
)
require.NoError(t, err)
expectedDuties := &ethpb.DutiesResponse{
Duties: expectedCurrentEpochDuties,
CurrentEpochDuties: expectedCurrentEpochDuties,
NextEpochDuties: expectedNextEpochDuties,
}
duties, err := validatorClient.getDuties(ctx, &ethpb.DutiesRequest{
Epoch: testCase.epoch,
PublicKeys: pubkeys,
})
require.NoError(t, err)
assert.DeepEqual(t, expectedDuties, duties)
})
}
}
func TestGetDuties_GetValidatorStatusFailed(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidators(
ctx,
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(
nil,
errors.New("foo error"),
).Times(1)
validatorClient := &beaconApiValidatorClient{
stateValidatorsProvider: stateValidatorsProvider,
}
_, err := validatorClient.getDuties(ctx, &ethpb.DutiesRequest{
Epoch: 1,
PublicKeys: [][]byte{},
})
assert.ErrorContains(t, "failed to get validator status", err)
assert.ErrorContains(t, "foo error", err)
}
func TestGetDuties_GetDutiesForEpochFailed(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidators(
ctx,
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(
&apimiddleware.StateValidatorsResponseJson{
Data: []*apimiddleware.ValidatorContainerJson{},
},
nil,
).Times(1)
dutiesProvider := mock.NewMockdutiesProvider(ctrl)
dutiesProvider.EXPECT().GetAttesterDuties(
ctx,
primitives.Epoch(1),
gomock.Any(),
).Return(
nil,
errors.New("foo error"),
).Times(1)
validatorClient := &beaconApiValidatorClient{
stateValidatorsProvider: stateValidatorsProvider,
dutiesProvider: dutiesProvider,
}
_, err := validatorClient.getDuties(ctx, &ethpb.DutiesRequest{
Epoch: 1,
PublicKeys: [][]byte{},
})
assert.ErrorContains(t, "failed to get duties for current epoch `1`", err)
assert.ErrorContains(t, "foo error", err)
}
func generateValidCommittees(committeeIndices []primitives.CommitteeIndex, slots []primitives.Slot, validatorIndices []primitives.ValidatorIndex) []*apimiddleware.CommitteeJson {
return []*apimiddleware.CommitteeJson{
{
Index: strconv.FormatUint(uint64(committeeIndices[0]), 10),
Slot: strconv.FormatUint(uint64(slots[0]), 10),
Validators: []string{
strconv.FormatUint(uint64(validatorIndices[0]), 10),
strconv.FormatUint(uint64(validatorIndices[1]), 10),
},
},
{
Index: strconv.FormatUint(uint64(committeeIndices[1]), 10),
Slot: strconv.FormatUint(uint64(slots[1]), 10),
Validators: []string{
strconv.FormatUint(uint64(validatorIndices[2]), 10),
strconv.FormatUint(uint64(validatorIndices[3]), 10),
},
},
{
Index: strconv.FormatUint(uint64(committeeIndices[2]), 10),
Slot: strconv.FormatUint(uint64(slots[2]), 10),
Validators: []string{
strconv.FormatUint(uint64(validatorIndices[4]), 10),
strconv.FormatUint(uint64(validatorIndices[5]), 10),
},
},
}
}
func generateValidAttesterDuties(pubkeys [][]byte, validatorIndices []primitives.ValidatorIndex, committeeIndices []primitives.CommitteeIndex, slots []primitives.Slot) []*apimiddleware.AttesterDutyJson {
return []*apimiddleware.AttesterDutyJson{
{
Pubkey: hexutil.Encode(pubkeys[0]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[0]), 10),
CommitteeIndex: strconv.FormatUint(uint64(committeeIndices[0]), 10),
Slot: strconv.FormatUint(uint64(slots[0]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[1]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[1]), 10),
CommitteeIndex: strconv.FormatUint(uint64(committeeIndices[0]), 10),
Slot: strconv.FormatUint(uint64(slots[0]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[2]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[2]), 10),
CommitteeIndex: strconv.FormatUint(uint64(committeeIndices[1]), 10),
Slot: strconv.FormatUint(uint64(slots[1]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[3]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[3]), 10),
CommitteeIndex: strconv.FormatUint(uint64(committeeIndices[1]), 10),
Slot: strconv.FormatUint(uint64(slots[1]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[4]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[4]), 10),
CommitteeIndex: strconv.FormatUint(uint64(committeeIndices[2]), 10),
Slot: strconv.FormatUint(uint64(slots[2]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[5]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[5]), 10),
CommitteeIndex: strconv.FormatUint(uint64(committeeIndices[2]), 10),
Slot: strconv.FormatUint(uint64(slots[2]), 10),
},
}
}
func generateValidProposerDuties(pubkeys [][]byte, validatorIndices []primitives.ValidatorIndex, slots []primitives.Slot) []*apimiddleware.ProposerDutyJson {
return []*apimiddleware.ProposerDutyJson{
{
Pubkey: hexutil.Encode(pubkeys[4]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[4]), 10),
Slot: strconv.FormatUint(uint64(slots[0]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[4]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[4]), 10),
Slot: strconv.FormatUint(uint64(slots[1]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[5]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[5]), 10),
Slot: strconv.FormatUint(uint64(slots[2]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[5]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[5]), 10),
Slot: strconv.FormatUint(uint64(slots[3]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[6]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[6]), 10),
Slot: strconv.FormatUint(uint64(slots[4]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[6]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[6]), 10),
Slot: strconv.FormatUint(uint64(slots[5]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[7]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[7]), 10),
Slot: strconv.FormatUint(uint64(slots[6]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[7]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[7]), 10),
Slot: strconv.FormatUint(uint64(slots[7]), 10),
},
}
}
func generateValidSyncDuties(pubkeys [][]byte, validatorIndices []primitives.ValidatorIndex) []*apimiddleware.SyncCommitteeDuty {
return []*apimiddleware.SyncCommitteeDuty{
{
Pubkey: hexutil.Encode(pubkeys[5]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[5]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[6]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[6]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[7]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[7]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[8]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[8]), 10),
},
{
Pubkey: hexutil.Encode(pubkeys[9]),
ValidatorIndex: strconv.FormatUint(uint64(validatorIndices[9]), 10),
},
}
}
// We will use a reverse function to easily make sure that the current epoch and next epoch data returned by getDutiesForEpoch
// are not the same
func reverseSlice[T interface{}](slice []T) []T {
reversedSlice := make([]T, len(slice))
for i := range slice {
reversedSlice[len(reversedSlice)-1-i] = slice[i]
}
return reversedSlice
}