From 0e17097a164edd79734b7b97a2fb2923e798388c Mon Sep 17 00:00:00 2001 From: kalo <24719519+KaloyanTanev@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:28:36 +0300 Subject: [PATCH] Proposer duties v2 --- core/validatorapi/router.go | 7 +++ core/validatorapi/router_internal_test.go | 55 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/core/validatorapi/router.go b/core/validatorapi/router.go index 74cacbc1f..54f2d49e2 100644 --- a/core/validatorapi/router.go +++ b/core/validatorapi/router.go @@ -118,6 +118,13 @@ func NewRouter(h Handler, builderEnabled bool) (*mux.Router, error) { Methods: []string{http.MethodGet}, Encodings: []contentType{contentTypeJSON}, }, + { + Name: "proposer_duties_v2", + Path: "/eth/v2/validator/duties/proposer/{epoch}", + Handler: proposerDuties(h), + Methods: []string{http.MethodGet}, + Encodings: []contentType{contentTypeJSON}, + }, { Name: "sync_committee_duties", Path: "/eth/v1/validator/duties/sync/{epoch}", diff --git a/core/validatorapi/router_internal_test.go b/core/validatorapi/router_internal_test.go index 397d7ea78..6ba8babfa 100644 --- a/core/validatorapi/router_internal_test.go +++ b/core/validatorapi/router_internal_test.go @@ -349,6 +349,61 @@ func TestRawRouter(t *testing.T) { testRawRouter(t, handler, callback) }) + t.Run("proposer duties v2", func(t *testing.T) { + const ( + epoch = 4 + total = 2 + ) + + var dependentRoot eth2p0.Root + + _, _ = rand.Read(dependentRoot[:]) + + metadata := map[string]any{ + "execution_optimistic": true, + "dependent_root": dependentRoot, + } + + handler := testHandler{ + ProposerDutiesFunc: func(ctx context.Context, opts *eth2api.ProposerDutiesOpts) (*eth2api.Response[[]*eth2v1.ProposerDuty], error) { + var res []*eth2v1.ProposerDuty + for i := range total { + res = append(res, ð2v1.ProposerDuty{ + ValidatorIndex: eth2p0.ValidatorIndex(i), + Slot: eth2p0.Slot(int(opts.Epoch)*slotsPerEpoch + i), + }) + } + + return wrapResponseWithMetadata(res, metadata), nil + }, + } + + callback := func(ctx context.Context, baseURL string) { + res, err := http.Get(fmt.Sprintf("%s/eth/v2/validator/duties/proposer/%d", baseURL, epoch)) + require.NoError(t, err) + require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, "application/json", res.Header.Get("Content-Type")) + + var resp struct { + DependentRoot string `json:"dependent_root"` + Data []*eth2v1.ProposerDuty `json:"data"` + ExecutionOptimistic bool `json:"execution_optimistic"` + } + require.NoError(t, json.NewDecoder(res.Body).Decode(&resp)) + + require.True(t, resp.ExecutionOptimistic) + require.Equal(t, fmt.Sprintf("%#x", dependentRoot), resp.DependentRoot) + require.Len(t, resp.Data, total) + + for i, duty := range resp.Data { + require.EqualValues(t, i, duty.ValidatorIndex) + require.EqualValues(t, epoch*slotsPerEpoch+i, duty.Slot) + } + } + + testRawRouter(t, handler, callback) + }) + t.Run("valid content type in non-2xx response", func(t *testing.T) { handler := testHandler{}