From 13ae2d059570a8472c83f173aaf3f0487660d7ed Mon Sep 17 00:00:00 2001 From: rkapka Date: Thu, 16 Nov 2023 18:26:12 +0100 Subject: [PATCH 1/5] POST versions of GetValidators and GetValidatorBalances --- .../rpc/eth/beacon/handlers_validator.go | 34 ++++++++- .../eth/beacon/handlers_validators_test.go | 72 ++++++++++++++++++- beacon-chain/rpc/service.go | 4 +- 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/beacon-chain/rpc/eth/beacon/handlers_validator.go b/beacon-chain/rpc/eth/beacon/handlers_validator.go index a9d1a804a5b9..c78d83df2bb0 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_validator.go +++ b/beacon-chain/rpc/eth/beacon/handlers_validator.go @@ -1,7 +1,9 @@ package beacon import ( + "encoding/json" "fmt" + "io" "net/http" "strconv" "strings" @@ -49,7 +51,21 @@ func (s *Server) GetValidators(w http.ResponseWriter, r *http.Request) { } isFinalized := s.FinalizationFetcher.IsFinalized(ctx, blockRoot) - rawIds := r.URL.Query()["id"] + var rawIds []string + if r.Method == http.MethodGet { + rawIds = r.URL.Query()["id"] + } else { + err = json.NewDecoder(r.Body).Decode(&rawIds) + switch { + case err == io.EOF: + http2.HandleError(w, "No data submitted", http.StatusBadRequest) + return + case err != nil: + http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + return + } + } + ids, ok := decodeIds(w, st, rawIds, true /* ignore unknown */) if !ok { return @@ -234,7 +250,21 @@ func (bs *Server) GetValidatorBalances(w http.ResponseWriter, r *http.Request) { } isFinalized := bs.FinalizationFetcher.IsFinalized(ctx, blockRoot) - rawIds := r.URL.Query()["id"] + var rawIds []string + if r.Method == http.MethodGet { + rawIds = r.URL.Query()["id"] + } else { + err = json.NewDecoder(r.Body).Decode(&rawIds) + switch { + case err == io.EOF: + http2.HandleError(w, "No data submitted", http.StatusBadRequest) + return + case err != nil: + http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + return + } + } + ids, ok := decodeIds(w, st, rawIds, true /* ignore unknown */) if !ok { return diff --git a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go index 7e38debcac67..0c6084105377 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go @@ -275,9 +275,44 @@ func TestGetValidators(t *testing.T) { require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) assert.Equal(t, true, resp.Finalized) }) + t.Run("POST", func(t *testing.T) { + chainService := &chainMock.ChainService{} + s := Server{ + Stater: &testutil.MockStater{ + BeaconState: st, + }, + HeadFetcher: chainService, + OptimisticModeFetcher: chainService, + FinalizationFetcher: chainService, + } + + pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(20)) + pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(66)) + hexPubkey1 := hexutil.Encode(pubkey1[:]) + hexPubkey2 := hexutil.Encode(pubkey2[:]) + var body bytes.Buffer + _, err := body.WriteString(fmt.Sprintf("[\"%s\",\"%s\"]", hexPubkey1, hexPubkey2)) + require.NoError(t, err) + request := httptest.NewRequest( + http.MethodPost, + "http://example.com/eth/v1/beacon/states/{state_id}/validators", + &body, + ) + request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetValidators(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &GetValidatorsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.Equal(t, 2, len(resp.Data)) + assert.Equal(t, "20", resp.Data[0].Index) + assert.Equal(t, "66", resp.Data[1].Index) + }) } -func TestListValidators_FilterByStatus(t *testing.T) { +func TestGetValidators_FilterByStatus(t *testing.T) { var st state.BeaconState st, _ = util.DeterministicGenesisState(t, 8192) @@ -951,4 +986,39 @@ func TestGetValidatorBalances(t *testing.T) { require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) assert.Equal(t, true, resp.Finalized) }) + t.Run("POST", func(t *testing.T) { + chainService := &chainMock.ChainService{} + s := Server{ + Stater: &testutil.MockStater{ + BeaconState: st, + }, + HeadFetcher: chainService, + OptimisticModeFetcher: chainService, + FinalizationFetcher: chainService, + } + + pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(20)) + pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(66)) + hexPubkey1 := hexutil.Encode(pubkey1[:]) + hexPubkey2 := hexutil.Encode(pubkey2[:]) + var body bytes.Buffer + _, err := body.WriteString(fmt.Sprintf("[\"%s\",\"%s\"]", hexPubkey1, hexPubkey2)) + require.NoError(t, err) + request := httptest.NewRequest( + http.MethodPost, + "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances", + &body, + ) + request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetValidatorBalances(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &GetValidatorBalancesResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.Equal(t, 2, len(resp.Data)) + assert.Equal(t, "20", resp.Data[0].Index) + assert.Equal(t, "66", resp.Data[1].Index) + }) } diff --git a/beacon-chain/rpc/service.go b/beacon-chain/rpc/service.go index 106faee7f872..ab7a5dbc8c39 100644 --- a/beacon-chain/rpc/service.go +++ b/beacon-chain/rpc/service.go @@ -469,9 +469,9 @@ func (s *Service) Start() { s.cfg.Router.HandleFunc("/eth/v1/beacon/headers/{block_id}", beaconChainServerV1.GetBlockHeader).Methods(http.MethodGet) s.cfg.Router.HandleFunc("/eth/v1/beacon/genesis", beaconChainServerV1.GetGenesis).Methods(http.MethodGet) s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/finality_checkpoints", beaconChainServerV1.GetFinalityCheckpoints).Methods(http.MethodGet) - s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validators", beaconChainServerV1.GetValidators).Methods(http.MethodGet) + s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validators", beaconChainServerV1.GetValidators).Methods(http.MethodGet, http.MethodPost) s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validators/{validator_id}", beaconChainServerV1.GetValidator).Methods(http.MethodGet) - s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_balances", beaconChainServerV1.GetValidatorBalances).Methods(http.MethodGet) + s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_balances", beaconChainServerV1.GetValidatorBalances).Methods(http.MethodGet, http.MethodPost) s.cfg.Router.HandleFunc("/eth/v1/config/deposit_contract", config.GetDepositContract).Methods(http.MethodGet) s.cfg.Router.HandleFunc("/eth/v1/config/fork_schedule", config.GetForkSchedule).Methods(http.MethodGet) From d5e630924868e516bb071bda00b2a0ed6d113a38 Mon Sep 17 00:00:00 2001 From: rkapka Date: Wed, 22 Nov 2023 16:57:40 +0100 Subject: [PATCH 2/5] post statuses --- .../rpc/eth/beacon/handlers_validator.go | 22 +++- .../eth/beacon/handlers_validators_test.go | 117 ++++++++++++------ beacon-chain/rpc/eth/beacon/structs.go | 5 + 3 files changed, 100 insertions(+), 44 deletions(-) diff --git a/beacon-chain/rpc/eth/beacon/handlers_validator.go b/beacon-chain/rpc/eth/beacon/handlers_validator.go index c78d83df2bb0..8dc891497fdb 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_validator.go +++ b/beacon-chain/rpc/eth/beacon/handlers_validator.go @@ -51,11 +51,9 @@ func (s *Server) GetValidators(w http.ResponseWriter, r *http.Request) { } isFinalized := s.FinalizationFetcher.IsFinalized(ctx, blockRoot) - var rawIds []string - if r.Method == http.MethodGet { - rawIds = r.URL.Query()["id"] - } else { - err = json.NewDecoder(r.Body).Decode(&rawIds) + var req GetValidatorsRequest + if r.Method == http.MethodPost { + err = json.NewDecoder(r.Body).Decode(&req) switch { case err == io.EOF: http2.HandleError(w, "No data submitted", http.StatusBadRequest) @@ -66,6 +64,13 @@ func (s *Server) GetValidators(w http.ResponseWriter, r *http.Request) { } } + var rawIds []string + if r.Method == http.MethodGet { + rawIds = r.URL.Query()["id"] + } else { + rawIds = req.Ids + } + ids, ok := decodeIds(w, st, rawIds, true /* ignore unknown */) if !ok { return @@ -88,7 +93,12 @@ func (s *Server) GetValidators(w http.ResponseWriter, r *http.Request) { epoch := slots.ToEpoch(st.Slot()) allBalances := st.Balances() - statuses := r.URL.Query()["status"] + var statuses []string + if r.Method == http.MethodGet { + statuses = r.URL.Query()["status"] + } else { + statuses = req.Statuses + } for i, ss := range statuses { statuses[i] = strings.ToLower(ss) } diff --git a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go index 0c6084105377..288a0c7e9fb3 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strconv" "strings" "testing" @@ -26,8 +27,12 @@ import ( ) func TestGetValidators(t *testing.T) { + const exitedValIndex = 3 var st state.BeaconState - st, _ = util.DeterministicGenesisState(t, 8192) + st, _ = util.DeterministicGenesisState(t, 4) + vals := st.Validators() + vals[exitedValIndex].ExitEpoch = 0 + require.NoError(t, st.SetValidators(vals)) t.Run("get all", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -49,7 +54,7 @@ func TestGetValidators(t *testing.T) { assert.Equal(t, http.StatusOK, writer.Code) resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, 8192, len(resp.Data)) + require.Equal(t, 4, len(resp.Data)) val := resp.Data[0] assert.Equal(t, "0", val.Index) assert.Equal(t, "32000000000", val.Balance) @@ -77,7 +82,7 @@ func TestGetValidators(t *testing.T) { request := httptest.NewRequest( http.MethodGet, - "http://example.com/eth/v1/beacon/states/{state_id}/validators?id=15&id=26", + "http://example.com/eth/v1/beacon/states/{state_id}/validators?id=0&id=1", nil, ) request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) @@ -89,8 +94,8 @@ func TestGetValidators(t *testing.T) { resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 2, len(resp.Data)) - assert.Equal(t, "15", resp.Data[0].Index) - assert.Equal(t, "26", resp.Data[1].Index) + assert.Equal(t, "0", resp.Data[0].Index) + assert.Equal(t, "1", resp.Data[1].Index) }) t.Run("get by pubkey", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -103,8 +108,8 @@ func TestGetValidators(t *testing.T) { FinalizationFetcher: chainService, } - pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(20)) - pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(66)) + pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(0)) + pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(1)) hexPubkey1 := hexutil.Encode(pubkey1[:]) hexPubkey2 := hexutil.Encode(pubkey2[:]) request := httptest.NewRequest( @@ -121,8 +126,8 @@ func TestGetValidators(t *testing.T) { resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 2, len(resp.Data)) - assert.Equal(t, "20", resp.Data[0].Index) - assert.Equal(t, "66", resp.Data[1].Index) + assert.Equal(t, "0", resp.Data[0].Index) + assert.Equal(t, "1", resp.Data[1].Index) }) t.Run("get by both index and pubkey", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -135,11 +140,11 @@ func TestGetValidators(t *testing.T) { FinalizationFetcher: chainService, } - pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(20)) + pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(0)) hexPubkey := hexutil.Encode(pubkey[:]) request := httptest.NewRequest( http.MethodGet, - fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s&id=60", hexPubkey), + fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s&id=1", hexPubkey), nil, ) request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) @@ -151,8 +156,8 @@ func TestGetValidators(t *testing.T) { resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 2, len(resp.Data)) - assert.Equal(t, "20", resp.Data[0].Index) - assert.Equal(t, "60", resp.Data[1].Index) + assert.Equal(t, "0", resp.Data[0].Index) + assert.Equal(t, "1", resp.Data[1].Index) }) t.Run("state ID required", func(t *testing.T) { s := Server{ @@ -184,7 +189,7 @@ func TestGetValidators(t *testing.T) { FinalizationFetcher: chainService, } - pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(1)) + pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(0)) hexPubkey := hexutil.Encode(pubkey[:]) request := httptest.NewRequest( http.MethodGet, @@ -200,7 +205,7 @@ func TestGetValidators(t *testing.T) { resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 1, len(resp.Data)) - assert.Equal(t, "1", resp.Data[0].Index) + assert.Equal(t, "0", resp.Data[0].Index) }) t.Run("unknown index is ignored", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -213,7 +218,7 @@ func TestGetValidators(t *testing.T) { FinalizationFetcher: chainService, } - request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators?id=1&id=99999", nil) + request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators?id=0&id=99999", nil) request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -223,7 +228,7 @@ func TestGetValidators(t *testing.T) { resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 1, len(resp.Data)) - assert.Equal(t, "1", resp.Data[0].Index) + assert.Equal(t, "0", resp.Data[0].Index) }) t.Run("execution optimistic", func(t *testing.T) { chainService := &chainMock.ChainService{Optimistic: true} @@ -286,12 +291,14 @@ func TestGetValidators(t *testing.T) { FinalizationFetcher: chainService, } - pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(20)) - pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(66)) - hexPubkey1 := hexutil.Encode(pubkey1[:]) - hexPubkey2 := hexutil.Encode(pubkey2[:]) var body bytes.Buffer - _, err := body.WriteString(fmt.Sprintf("[\"%s\",\"%s\"]", hexPubkey1, hexPubkey2)) + req := &GetValidatorsRequest{ + Ids: []string{"0", strconv.Itoa(exitedValIndex)}, + Statuses: []string{"exited"}, + } + b, err := json.Marshal(req) + require.NoError(t, err) + _, err = body.Write(b) require.NoError(t, err) request := httptest.NewRequest( http.MethodPost, @@ -306,15 +313,49 @@ func TestGetValidators(t *testing.T) { assert.Equal(t, http.StatusOK, writer.Code) resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, 2, len(resp.Data)) - assert.Equal(t, "20", resp.Data[0].Index) - assert.Equal(t, "66", resp.Data[1].Index) + require.Equal(t, 1, len(resp.Data)) + assert.Equal(t, "3", resp.Data[0].Index) + }) + t.Run("POST nil values", func(t *testing.T) { + chainService := &chainMock.ChainService{} + s := Server{ + Stater: &testutil.MockStater{ + BeaconState: st, + }, + HeadFetcher: chainService, + OptimisticModeFetcher: chainService, + FinalizationFetcher: chainService, + } + + var body bytes.Buffer + req := &GetValidatorsRequest{ + Ids: nil, + Statuses: nil, + } + b, err := json.Marshal(req) + require.NoError(t, err) + _, err = body.Write(b) + require.NoError(t, err) + request := httptest.NewRequest( + http.MethodPost, + "http://example.com/eth/v1/beacon/states/{state_id}/validators", + &body, + ) + request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetValidators(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &GetValidatorsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.Equal(t, 4, len(resp.Data)) }) } func TestGetValidators_FilterByStatus(t *testing.T) { var st state.BeaconState - st, _ = util.DeterministicGenesisState(t, 8192) + st, _ = util.DeterministicGenesisState(t, 1) farFutureEpoch := params.BeaconConfig().FarFutureEpoch validators := []*eth.Validator{ @@ -401,7 +442,7 @@ func TestGetValidators_FilterByStatus(t *testing.T) { assert.Equal(t, http.StatusOK, writer.Code) resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, 8192+2, len(resp.Data)) + assert.Equal(t, 3, len(resp.Data)) for _, vc := range resp.Data { assert.Equal( t, @@ -432,7 +473,7 @@ func TestGetValidators_FilterByStatus(t *testing.T) { assert.Equal(t, http.StatusOK, writer.Code) resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, 8192+1, len(resp.Data)) + assert.Equal(t, 2, len(resp.Data)) for _, vc := range resp.Data { require.Equal( t, @@ -541,7 +582,7 @@ func TestGetValidators_FilterByStatus(t *testing.T) { func TestGetValidator(t *testing.T) { var st state.BeaconState - st, _ = util.DeterministicGenesisState(t, 8192) + st, _ = util.DeterministicGenesisState(t, 2) t.Run("get by index", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -555,7 +596,7 @@ func TestGetValidator(t *testing.T) { } request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil) - request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "15"}) + request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "0"}) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -563,12 +604,12 @@ func TestGetValidator(t *testing.T) { assert.Equal(t, http.StatusOK, writer.Code) resp := &GetValidatorResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, "15", resp.Data.Index) + assert.Equal(t, "0", resp.Data.Index) assert.Equal(t, "32000000000", resp.Data.Balance) assert.Equal(t, "active_ongoing", resp.Data.Status) require.NotNil(t, resp.Data.Validator) - assert.Equal(t, "0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f", resp.Data.Validator.Pubkey) - assert.Equal(t, "0x00b24fc624e56a5ed42a9639691e27e34b783c7237030367bd17cbef65fa6ccf", resp.Data.Validator.WithdrawalCredentials) + assert.Equal(t, "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", resp.Data.Validator.Pubkey) + assert.Equal(t, "0x00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594", resp.Data.Validator.WithdrawalCredentials) assert.Equal(t, "32000000000", resp.Data.Validator.EffectiveBalance) assert.Equal(t, false, resp.Data.Validator.Slashed) assert.Equal(t, "0", resp.Data.Validator.ActivationEligibilityEpoch) @@ -587,7 +628,7 @@ func TestGetValidator(t *testing.T) { FinalizationFetcher: chainService, } - pubKey := st.PubkeyAtIndex(primitives.ValidatorIndex(20)) + pubKey := st.PubkeyAtIndex(primitives.ValidatorIndex(0)) hexPubkey := hexutil.Encode(pubKey[:]) request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil) request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": hexPubkey}) @@ -598,7 +639,7 @@ func TestGetValidator(t *testing.T) { assert.Equal(t, http.StatusOK, writer.Code) resp := &GetValidatorResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, "20", resp.Data.Index) + assert.Equal(t, "0", resp.Data.Index) }) t.Run("state ID required", func(t *testing.T) { s := Server{ @@ -609,7 +650,7 @@ func TestGetValidator(t *testing.T) { } request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil) - request = mux.SetURLVars(request, map[string]string{"validator_id": "1"}) + request = mux.SetURLVars(request, map[string]string{"validator_id": "0"}) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -692,7 +733,7 @@ func TestGetValidator(t *testing.T) { } request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil) - request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "15"}) + request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "0"}) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -720,7 +761,7 @@ func TestGetValidator(t *testing.T) { } request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil) - request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "15"}) + request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "0"}) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} diff --git a/beacon-chain/rpc/eth/beacon/structs.go b/beacon-chain/rpc/eth/beacon/structs.go index d889358f6b31..496d8811ad54 100644 --- a/beacon-chain/rpc/eth/beacon/structs.go +++ b/beacon-chain/rpc/eth/beacon/structs.go @@ -76,6 +76,11 @@ type GetBlockHeaderResponse struct { Data *shared.SignedBeaconBlockHeaderContainer `json:"data"` } +type GetValidatorsRequest struct { + Ids []string `json:"ids"` + Statuses []string `json:"statuses"` +} + type GetValidatorsResponse struct { ExecutionOptimistic bool `json:"execution_optimistic"` Finalized bool `json:"finalized"` From 30838cd80d5a890a50656f2ade11b40fa98feba3 Mon Sep 17 00:00:00 2001 From: rkapka Date: Wed, 22 Nov 2023 17:04:14 +0100 Subject: [PATCH 3/5] balances test --- .../eth/beacon/handlers_validators_test.go | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go index 288a0c7e9fb3..a45616ca4ce8 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go @@ -189,7 +189,7 @@ func TestGetValidators(t *testing.T) { FinalizationFetcher: chainService, } - pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(0)) + pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(1)) hexPubkey := hexutil.Encode(pubkey[:]) request := httptest.NewRequest( http.MethodGet, @@ -205,7 +205,7 @@ func TestGetValidators(t *testing.T) { resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 1, len(resp.Data)) - assert.Equal(t, "0", resp.Data[0].Index) + assert.Equal(t, "1", resp.Data[0].Index) }) t.Run("unknown index is ignored", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -218,7 +218,7 @@ func TestGetValidators(t *testing.T) { FinalizationFetcher: chainService, } - request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators?id=0&id=99999", nil) + request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators?id=1&id=99999", nil) request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -228,7 +228,7 @@ func TestGetValidators(t *testing.T) { resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 1, len(resp.Data)) - assert.Equal(t, "0", resp.Data[0].Index) + assert.Equal(t, "1", resp.Data[0].Index) }) t.Run("execution optimistic", func(t *testing.T) { chainService := &chainMock.ChainService{Optimistic: true} @@ -650,7 +650,7 @@ func TestGetValidator(t *testing.T) { } request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil) - request = mux.SetURLVars(request, map[string]string{"validator_id": "0"}) + request = mux.SetURLVars(request, map[string]string{"validator_id": "1"}) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -775,7 +775,7 @@ func TestGetValidator(t *testing.T) { func TestGetValidatorBalances(t *testing.T) { var st state.BeaconState - count := uint64(8192) + count := uint64(4) st, _ = util.DeterministicGenesisState(t, count) balances := make([]uint64, count) for i := uint64(0); i < count; i++ { @@ -803,10 +803,10 @@ func TestGetValidatorBalances(t *testing.T) { assert.Equal(t, http.StatusOK, writer.Code) resp := &GetValidatorBalancesResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, 8192, len(resp.Data)) - val := resp.Data[123] - assert.Equal(t, "123", val.Index) - assert.Equal(t, "123", val.Balance) + require.Equal(t, 4, len(resp.Data)) + val := resp.Data[3] + assert.Equal(t, "3", val.Index) + assert.Equal(t, "3", val.Balance) }) t.Run("get by index", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -821,7 +821,7 @@ func TestGetValidatorBalances(t *testing.T) { request := httptest.NewRequest( http.MethodGet, - "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=15&id=26", + "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=0&id=1", nil, ) request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) @@ -833,8 +833,8 @@ func TestGetValidatorBalances(t *testing.T) { resp := &GetValidatorBalancesResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 2, len(resp.Data)) - assert.Equal(t, "15", resp.Data[0].Index) - assert.Equal(t, "26", resp.Data[1].Index) + assert.Equal(t, "0", resp.Data[0].Index) + assert.Equal(t, "1", resp.Data[1].Index) }) t.Run("get by pubkey", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -846,8 +846,8 @@ func TestGetValidatorBalances(t *testing.T) { OptimisticModeFetcher: chainService, FinalizationFetcher: chainService, } - pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(20)) - pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(66)) + pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(0)) + pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(1)) hexPubkey1 := hexutil.Encode(pubkey1[:]) hexPubkey2 := hexutil.Encode(pubkey2[:]) @@ -865,8 +865,8 @@ func TestGetValidatorBalances(t *testing.T) { resp := &GetValidatorBalancesResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 2, len(resp.Data)) - assert.Equal(t, "20", resp.Data[0].Index) - assert.Equal(t, "66", resp.Data[1].Index) + assert.Equal(t, "0", resp.Data[0].Index) + assert.Equal(t, "1", resp.Data[1].Index) }) t.Run("get by both index and pubkey", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -879,11 +879,11 @@ func TestGetValidatorBalances(t *testing.T) { FinalizationFetcher: chainService, } - pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(20)) + pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(0)) hexPubkey := hexutil.Encode(pubkey[:]) request := httptest.NewRequest( http.MethodGet, - fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s&id=60", hexPubkey), + fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s&id=1", hexPubkey), nil, ) request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) @@ -895,8 +895,8 @@ func TestGetValidatorBalances(t *testing.T) { resp := &GetValidatorsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 2, len(resp.Data)) - assert.Equal(t, "20", resp.Data[0].Index) - assert.Equal(t, "60", resp.Data[1].Index) + assert.Equal(t, "0", resp.Data[0].Index) + assert.Equal(t, "1", resp.Data[1].Index) }) t.Run("unknown pubkey is ignored", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -982,7 +982,7 @@ func TestGetValidatorBalances(t *testing.T) { request := httptest.NewRequest( http.MethodGet, - "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=15", + "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=0", nil, ) request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) @@ -1014,7 +1014,7 @@ func TestGetValidatorBalances(t *testing.T) { request := httptest.NewRequest( http.MethodGet, - "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=15", + "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=0", nil, ) request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) @@ -1038,8 +1038,8 @@ func TestGetValidatorBalances(t *testing.T) { FinalizationFetcher: chainService, } - pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(20)) - pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(66)) + pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(0)) + pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(1)) hexPubkey1 := hexutil.Encode(pubkey1[:]) hexPubkey2 := hexutil.Encode(pubkey2[:]) var body bytes.Buffer @@ -1059,7 +1059,7 @@ func TestGetValidatorBalances(t *testing.T) { resp := &GetValidatorBalancesResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 2, len(resp.Data)) - assert.Equal(t, "20", resp.Data[0].Index) - assert.Equal(t, "66", resp.Data[1].Index) + assert.Equal(t, "0", resp.Data[0].Index) + assert.Equal(t, "1", resp.Data[1].Index) }) } From 77b0b3242ae75edff0eed04eb7907e34a1a109d1 Mon Sep 17 00:00:00 2001 From: rkapka Date: Wed, 22 Nov 2023 18:02:27 +0100 Subject: [PATCH 4/5] group params --- .../rpc/eth/beacon/handlers_validator.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/beacon-chain/rpc/eth/beacon/handlers_validator.go b/beacon-chain/rpc/eth/beacon/handlers_validator.go index 8dc891497fdb..e9aa2963a26b 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_validator.go +++ b/beacon-chain/rpc/eth/beacon/handlers_validator.go @@ -64,11 +64,17 @@ func (s *Server) GetValidators(w http.ResponseWriter, r *http.Request) { } } + var statuses []string var rawIds []string if r.Method == http.MethodGet { rawIds = r.URL.Query()["id"] + statuses = r.URL.Query()["status"] } else { rawIds = req.Ids + statuses = req.Statuses + } + for i, ss := range statuses { + statuses[i] = strings.ToLower(ss) } ids, ok := decodeIds(w, st, rawIds, true /* ignore unknown */) @@ -93,16 +99,6 @@ func (s *Server) GetValidators(w http.ResponseWriter, r *http.Request) { epoch := slots.ToEpoch(st.Slot()) allBalances := st.Balances() - var statuses []string - if r.Method == http.MethodGet { - statuses = r.URL.Query()["status"] - } else { - statuses = req.Statuses - } - for i, ss := range statuses { - statuses[i] = strings.ToLower(ss) - } - // Exit early if no matching validators were found or we don't want to further filter validators by status. if len(readOnlyVals) == 0 || len(statuses) == 0 { containers := make([]*ValidatorContainer, len(readOnlyVals)) From 02cb20824ecd5855ff6566b44be9f1613cb45e0e Mon Sep 17 00:00:00 2001 From: rkapka Date: Wed, 22 Nov 2023 18:10:41 +0100 Subject: [PATCH 5/5] test error cases --- .../eth/beacon/handlers_validators_test.go | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go index a45616ca4ce8..14e5c181aa3c 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go @@ -351,6 +351,63 @@ func TestGetValidators(t *testing.T) { require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.Equal(t, 4, len(resp.Data)) }) + t.Run("POST empty", func(t *testing.T) { + chainService := &chainMock.ChainService{} + s := Server{ + Stater: &testutil.MockStater{ + BeaconState: st, + }, + HeadFetcher: chainService, + OptimisticModeFetcher: chainService, + FinalizationFetcher: chainService, + } + + request := httptest.NewRequest( + http.MethodPost, + "http://example.com/eth/v1/beacon/states/{state_id}/validators", + nil, + ) + request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetValidators(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &http2.DefaultErrorJson{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.StringContains(t, "No data submitted", e.Message) + }) + t.Run("POST invalid", func(t *testing.T) { + chainService := &chainMock.ChainService{} + s := Server{ + Stater: &testutil.MockStater{ + BeaconState: st, + }, + HeadFetcher: chainService, + OptimisticModeFetcher: chainService, + FinalizationFetcher: chainService, + } + + body := bytes.Buffer{} + _, err := body.WriteString("foo") + require.NoError(t, err) + request := httptest.NewRequest( + http.MethodPost, + "http://example.com/eth/v1/beacon/states/{state_id}/validators", + &body, + ) + request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetValidators(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &http2.DefaultErrorJson{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.StringContains(t, "Could not decode request body", e.Message) + }) } func TestGetValidators_FilterByStatus(t *testing.T) { @@ -1062,4 +1119,61 @@ func TestGetValidatorBalances(t *testing.T) { assert.Equal(t, "0", resp.Data[0].Index) assert.Equal(t, "1", resp.Data[1].Index) }) + t.Run("POST empty", func(t *testing.T) { + chainService := &chainMock.ChainService{} + s := Server{ + Stater: &testutil.MockStater{ + BeaconState: st, + }, + HeadFetcher: chainService, + OptimisticModeFetcher: chainService, + FinalizationFetcher: chainService, + } + + request := httptest.NewRequest( + http.MethodPost, + "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances", + nil, + ) + request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetValidatorBalances(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &http2.DefaultErrorJson{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.StringContains(t, "No data submitted", e.Message) + }) + t.Run("POST invalid", func(t *testing.T) { + chainService := &chainMock.ChainService{} + s := Server{ + Stater: &testutil.MockStater{ + BeaconState: st, + }, + HeadFetcher: chainService, + OptimisticModeFetcher: chainService, + FinalizationFetcher: chainService, + } + + body := bytes.Buffer{} + _, err := body.WriteString("foo") + require.NoError(t, err) + request := httptest.NewRequest( + http.MethodPost, + "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances", + &body, + ) + request = mux.SetURLVars(request, map[string]string{"state_id": "head"}) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetValidatorBalances(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &http2.DefaultErrorJson{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.StringContains(t, "Could not decode request body", e.Message) + }) }