diff --git a/core/validatorapi/router.go b/core/validatorapi/router.go index b0ed8b3de..d157865db 100644 --- a/core/validatorapi/router.go +++ b/core/validatorapi/router.go @@ -86,142 +86,178 @@ func NewRouter(ctx context.Context, h Handler, eth2Cl eth2wrap.Client) (*mux.Rou Name string Path string Handler handlerFunc + Methods []string }{ { Name: "attester_duties", Path: "/eth/v1/validator/duties/attester/{epoch}", Handler: attesterDuties(h), + Methods: []string{http.MethodPost}, }, { Name: "proposer_duties", Path: "/eth/v1/validator/duties/proposer/{epoch}", Handler: proposerDuties(h), + Methods: []string{http.MethodGet}, }, { Name: "sync_committee_duties", Path: "/eth/v1/validator/duties/sync/{epoch}", Handler: syncCommitteeDuties(h), + Methods: []string{http.MethodPost}, }, { Name: "attestation_data", Path: "/eth/v1/validator/attestation_data", Handler: attestationData(h), + Methods: []string{http.MethodGet}, }, { Name: "submit_attestations", Path: "/eth/v1/beacon/pool/attestations", Handler: submitAttestations(h), + Methods: []string{http.MethodPost}, }, { Name: "get_validators", Path: "/eth/v1/beacon/states/{state_id}/validators", Handler: getValidators(h), + Methods: []string{http.MethodPost, http.MethodGet}, }, { Name: "get_validator", Path: "/eth/v1/beacon/states/{state_id}/validators/{validator_id}", Handler: getValidator(h), + Methods: []string{http.MethodGet}, }, { Name: "propose_block", Path: "/eth/v2/validator/blocks/{slot}", Handler: proposeBlock(h), + Methods: []string{http.MethodGet}, + }, + { + Name: "propose_block_v3", + Path: "/eth/v3/validator/blocks/{slot}", + Handler: proposeBlockV3(), + Methods: []string{http.MethodGet}, }, { Name: "submit_proposal_v1", Path: "/eth/v1/beacon/blocks", Handler: submitProposal(h), + Methods: []string{http.MethodPost}, }, { Name: "submit_proposal_v2", Path: "/eth/v2/beacon/blocks", Handler: submitProposal(h), + Methods: []string{http.MethodPost}, }, { Name: "propose_blinded_block", Path: "/eth/v1/validator/blinded_blocks/{slot}", Handler: proposeBlindedBlock(h), + Methods: []string{http.MethodGet}, }, { Name: "submit_blinded_block_v1", Path: "/eth/v1/beacon/blinded_blocks", Handler: submitBlindedBlock(h), + Methods: []string{http.MethodPost}, }, { Name: "submit_blinded_block_v2", Path: "/eth/v2/beacon/blinded_blocks", Handler: submitBlindedBlock(h), + Methods: []string{http.MethodPost}, }, { Name: "submit_validator_registration", Path: "/eth/v1/validator/register_validator", Handler: submitValidatorRegistrations(h), + Methods: []string{http.MethodPost}, }, { Name: "submit_voluntary_exit", Path: "/eth/v1/beacon/pool/voluntary_exits", Handler: submitExit(h), + Methods: []string{http.MethodPost}, }, { Name: "teku_proposer_config", Path: "/teku_proposer_config", Handler: proposerConfig(h), + Methods: []string{http.MethodGet}, }, { Name: "proposer_config", Path: "/proposer_config", Handler: proposerConfig(h), + Methods: []string{http.MethodGet}, }, { Name: "aggregate_beacon_committee_selections", Path: "/eth/v1/validator/beacon_committee_selections", Handler: aggregateBeaconCommitteeSelections(h), + Methods: []string{http.MethodPost}, }, { Name: "aggregate_attestation", Path: "/eth/v1/validator/aggregate_attestation", Handler: aggregateAttestation(h), + Methods: []string{http.MethodGet}, }, { Name: "submit_aggregate_and_proofs", Path: "/eth/v1/validator/aggregate_and_proofs", Handler: submitAggregateAttestations(h), + Methods: []string{http.MethodPost}, }, { Name: "submit_sync_committee_messages", Path: "/eth/v1/beacon/pool/sync_committees", Handler: submitSyncCommitteeMessages(h), + Methods: []string{http.MethodPost}, }, { Name: "sync_committee_contribution", Path: "/eth/v1/validator/sync_committee_contribution", Handler: syncCommitteeContribution(h), + Methods: []string{http.MethodGet}, }, { Name: "submit_contribution_and_proofs", Path: "/eth/v1/validator/contribution_and_proofs", Handler: submitContributionAndProofs(h), + Methods: []string{http.MethodPost}, }, { Name: "submit_proposal_preparations", Path: "/eth/v1/validator/prepare_beacon_proposer", Handler: submitProposalPreparations(), + Methods: []string{http.MethodPost}, }, { Name: "aggregate_sync_committee_selections", Path: "/eth/v1/validator/sync_committee_selections", Handler: aggregateSyncCommitteeSelections(h), + Methods: []string{http.MethodPost}, }, { Name: "node_version", Path: "/eth/v1/node/version", Handler: nodeVersion(h), + Methods: []string{http.MethodGet}, }, } r := mux.NewRouter() for _, e := range endpoints { - r.Handle(e.Path, wrap(e.Name, e.Handler)) + handler := r.Handle(e.Path, wrap(e.Name, e.Handler)) + if len(e.Methods) != 0 { + handler.Methods(e.Methods...) + } } // Everything else is proxied @@ -323,6 +359,17 @@ func wrapTrace(endpoint string, handler http.HandlerFunc) http.Handler { return otelhttp.NewHandler(handler, "core/validatorapi."+endpoint) } +// proposeBlockV3 returns a handler function which receives the randao from the validator and returns an unsigned +// BeaconBlock or BlindedBeaconBlock. +func proposeBlockV3() handlerFunc { + return func(context.Context, map[string]string, url.Values, contentType, []byte) (res any, headers http.Header, err error) { + return nil, nil, apiError{ + StatusCode: 404, + Message: "endpoint not supported", + } + } +} + // getValidators returns a handler function for the get validators by pubkey or index endpoint. func getValidators(p eth2client.ValidatorsProvider) handlerFunc { return func(ctx context.Context, params map[string]string, query url.Values, _ contentType, body []byte) (any, http.Header, error) { @@ -702,7 +749,7 @@ func proposeBlindedBlock(p eth2client.BlindedProposalProvider) handlerFunc { } return proposeBlindedBlockResponseBellatrix{ - Version: "BELLATRIX", + Version: eth2spec.DataVersionBellatrix.String(), Data: block.Bellatrix, }, resHeaders, nil case eth2spec.DataVersionCapella: @@ -711,7 +758,7 @@ func proposeBlindedBlock(p eth2client.BlindedProposalProvider) handlerFunc { } return proposeBlindedBlockResponseCapella{ - Version: "CAPELLA", + Version: eth2spec.DataVersionCapella.String(), Data: block.Capella, }, resHeaders, nil case eth2spec.DataVersionDeneb: @@ -720,7 +767,7 @@ func proposeBlindedBlock(p eth2client.BlindedProposalProvider) handlerFunc { } return proposeBlindedBlockResponseDeneb{ - Version: "DENEB", + Version: eth2spec.DataVersionDeneb.String(), Data: block.Deneb, }, resHeaders, nil default: diff --git a/core/validatorapi/router_internal_test.go b/core/validatorapi/router_internal_test.go index 5b3aaf165..7ea375915 100644 --- a/core/validatorapi/router_internal_test.go +++ b/core/validatorapi/router_internal_test.go @@ -121,7 +121,7 @@ func TestRawRouter(t *testing.T) { handler := testHandler{} callback := func(ctx context.Context, baseURL string) { - res, err := http.Get(baseURL + "/eth/v1/validator/duties/attester/not_a_number") + res, err := http.Post(baseURL+"/eth/v1/validator/duties/attester/not_a_number", "application/json", bytes.NewReader([]byte("{}"))) require.NoError(t, err) var errRes errorResponse @@ -140,7 +140,7 @@ func TestRawRouter(t *testing.T) { handler := testHandler{} callback := func(ctx context.Context, baseURL string) { - res, err := http.Post(baseURL+"/eth/v2/validator/blocks/123", "", nil) + res, err := http.Get(baseURL + "/eth/v2/validator/blocks/123") require.NoError(t, err) var errRes errorResponse @@ -159,7 +159,7 @@ func TestRawRouter(t *testing.T) { handler := testHandler{} callback := func(ctx context.Context, baseURL string) { - res, err := http.Post(baseURL+"/eth/v2/validator/blocks/123?randao_reveal=0x0000", "", nil) + res, err := http.Get(baseURL + "/eth/v2/validator/blocks/123?randao_reveal=0x0000") require.NoError(t, err) var errRes errorResponse @@ -185,7 +185,7 @@ func TestRawRouter(t *testing.T) { callback := func(ctx context.Context, baseURL string) { randao := testutil.RandomEth2Signature().String() - res, err := http.Post(baseURL+"/eth/v2/validator/blocks/123?randao_reveal="+randao, "", nil) + res, err := http.Get(baseURL + "/eth/v2/validator/blocks/123?randao_reveal=" + randao) require.NoError(t, err) var okResp struct{ Data json.RawMessage } @@ -201,7 +201,7 @@ func TestRawRouter(t *testing.T) { handler := testHandler{} callback := func(ctx context.Context, baseURL string) { - res, err := http.Get(baseURL + "/eth/v1/validator/duties/attester/1") + res, err := http.Post(baseURL+"/eth/v1/validator/duties/attester/1", "application/json", bytes.NewReader([]byte(""))) require.NoError(t, err) var errRes errorResponse @@ -573,6 +573,57 @@ func TestRouter(t *testing.T) { "dependent_root": dependentRoot, } + t.Run("wrong http method", func(t *testing.T) { + ctx := context.Background() + + h := testHandler{} + + proxy := httptest.NewServer(h.newBeaconHandler(t)) + defer proxy.Close() + + r, err := NewRouter(ctx, h, testBeaconAddr{addr: proxy.URL}) + require.NoError(t, err) + + server := httptest.NewServer(r) + defer server.Close() + + endpointURL := fmt.Sprintf("%s/eth/v1/node/version", server.URL) + + // node_version is a GET-only endpoint, we expect it to fail + resp, err := http.Post( + endpointURL, + "application/json", + bytes.NewReader([]byte("{}")), + ) + + require.NoError(t, err) + + require.Equal( + t, + http.StatusNotFound, + resp.StatusCode, + ) + + // use the right http method and expect a response, and status code 200 + resp, err = http.Get(endpointURL) + require.NoError(t, err) + + require.Equal( + t, + http.StatusOK, + resp.StatusCode, + ) + + data, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + defer func() { + _ = resp.Body.Close() + }() + + require.NotEmpty(t, data) + }) + t.Run("attesterduty", func(t *testing.T) { handler := testHandler{ AttesterDutiesFunc: func(ctx context.Context, opts *eth2api.AttesterDutiesOpts) (*eth2api.Response[[]*eth2v1.AttesterDuty], error) { diff --git a/go.mod b/go.mod index f7b0dcd2d..97f07b961 100644 --- a/go.mod +++ b/go.mod @@ -158,7 +158,7 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.3.2 // indirect - github.com/quic-go/quic-go v0.37.6 // indirect + github.com/quic-go/quic-go v0.37.7 // indirect github.com/quic-go/webtransport-go v0.5.3 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rs/cors v1.10.1 // indirect diff --git a/go.sum b/go.sum index 98be9731e..2d2635e7a 100644 --- a/go.sum +++ b/go.sum @@ -414,8 +414,8 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= -github.com/quic-go/quic-go v0.37.6 h1:2IIUmQzT5YNxAiaPGjs++Z4hGOtIR0q79uS5qE9ccfY= -github.com/quic-go/quic-go v0.37.6/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= +github.com/quic-go/quic-go v0.37.7 h1:AgKsQLZ1+YCwZd2GYhBUsJDYZwEkA5gENtAjb+MxONU= +github.com/quic-go/quic-go v0.37.7/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0=