From fa91ac765aa8ec90cb02b74e0c30868be6864a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kapka?= Date: Mon, 15 Apr 2024 14:56:47 +0900 Subject: [PATCH] Upgrade the Beacon API e2e evaluator (#13868) * GET * POST * Revert "Auxiliary commit to revert individual files from 615feb104004d6a945ededf5862ae38325fc7ec2" This reverts commit 55cf071c684019f3d6124179154c10b2277fda49. * comment fix * deepsource --- .../endtoend/evaluators/beaconapi/requests.go | 284 ++++++++---------- .../endtoend/evaluators/beaconapi/types.go | 45 ++- testing/endtoend/evaluators/beaconapi/util.go | 43 +-- .../endtoend/evaluators/beaconapi/verify.go | 122 ++++++-- 4 files changed, 281 insertions(+), 213 deletions(-) diff --git a/testing/endtoend/evaluators/beaconapi/requests.go b/testing/endtoend/evaluators/beaconapi/requests.go index b2593993635d..291b70741f5c 100644 --- a/testing/endtoend/evaluators/beaconapi/requests.go +++ b/testing/endtoend/evaluators/beaconapi/requests.go @@ -10,129 +10,117 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ) -var requests = map[string]endpoint{ +var getRequests = map[string]endpoint{ "/beacon/genesis": newMetadata[structs.GetGenesisResponse](v1PathTemplate), - "/beacon/states/{param1}/root": newMetadata[structs.GetStateRootResponse](v1PathTemplate, + "/beacon/states/{param1}/root": newMetadata[structs.GetStateRootResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/states/{param1}/fork": newMetadata[structs.GetStateForkResponse](v1PathTemplate, + "/beacon/states/{param1}/fork": newMetadata[structs.GetStateForkResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/states/{param1}/finality_checkpoints": newMetadata[structs.GetFinalityCheckpointsResponse](v1PathTemplate, + "/beacon/states/{param1}/finality_checkpoints": newMetadata[structs.GetFinalityCheckpointsResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), // we want to test comma-separated query params - "/beacon/states/{param1}/validators?id=0,1": newMetadata[structs.GetValidatorsResponse](v1PathTemplate, + "/beacon/states/{param1}/validators?id=0,1": newMetadata[structs.GetValidatorsResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/states/{param1}/validators/{param2}": newMetadata[structs.GetValidatorResponse](v1PathTemplate, + "/beacon/states/{param1}/validators/{param2}": newMetadata[structs.GetValidatorResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head", "0"} })), - "/beacon/states/{param1}/validator_balances?id=0,1": newMetadata[structs.GetValidatorBalancesResponse](v1PathTemplate, + "/beacon/states/{param1}/validator_balances?id=0,1": newMetadata[structs.GetValidatorBalancesResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/states/{param1}/committees?index=0": newMetadata[structs.GetCommitteesResponse](v1PathTemplate, + "/beacon/states/{param1}/committees?index=0": newMetadata[structs.GetCommitteesResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/states/{param1}/sync_committees": newMetadata[structs.GetSyncCommitteeResponse](v1PathTemplate, + "/beacon/states/{param1}/sync_committees": newMetadata[structs.GetSyncCommitteeResponse]( + v1PathTemplate, withStart(params.BeaconConfig().AltairForkEpoch), withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/states/{param1}/randao": newMetadata[structs.GetRandaoResponse](v1PathTemplate, + "/beacon/states/{param1}/randao": newMetadata[structs.GetRandaoResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), "/beacon/headers": newMetadata[structs.GetBlockHeadersResponse](v1PathTemplate), - "/beacon/headers/{param1}": newMetadata[structs.GetBlockHeaderResponse](v1PathTemplate, - withParams(func(e primitives.Epoch) []string { + "/beacon/headers/{param1}": newMetadata[structs.GetBlockHeaderResponse]( + v1PathTemplate, + withParams(func(currentEpoch primitives.Epoch) []string { slot := uint64(0) - if e > 0 { - slot = (uint64(e) * uint64(params.BeaconConfig().SlotsPerEpoch)) - 1 + if currentEpoch > 0 { + slot = (uint64(currentEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch)) - 1 } return []string{fmt.Sprintf("%v", slot)} })), - "/beacon/blocks/{param1}": newMetadata[structs.GetBlockV2Response](v2PathTemplate, + "/beacon/blocks/{param1}": newMetadata[structs.GetBlockV2Response]( + v2PathTemplate, withSsz(), withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/blocks/{param1}/root": newMetadata[structs.BlockRootResponse](v1PathTemplate, + "/beacon/blocks/{param1}/root": newMetadata[structs.BlockRootResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/blocks/{param1}/attestations": newMetadata[structs.GetBlockAttestationsResponse](v1PathTemplate, + "/beacon/blocks/{param1}/attestations": newMetadata[structs.GetBlockAttestationsResponse]( + v1PathTemplate, withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/blinded_blocks/{param1}": newMetadata[structs.GetBlockV2Response](v1PathTemplate, + "/beacon/blob_sidecars/{param1}": newMetadata[structs.SidecarsResponse]( + v1PathTemplate, + withStart(params.BeaconConfig().DenebForkEpoch), withSsz(), withParams(func(_ primitives.Epoch) []string { return []string{"head"} })), - "/beacon/pool/attestations": newMetadata[structs.ListAttestationsResponse](v1PathTemplate, - withCustomEval(func(p interface{}, _ interface{}) error { - pResp, ok := p.(*structs.ListAttestationsResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.ListAttestationsResponse{}, p) - } - if pResp.Data == nil { - return errEmptyPrysmData - } - return nil - })), - "/beacon/pool/attester_slashings": newMetadata[structs.GetAttesterSlashingsResponse](v1PathTemplate, - withCustomEval(func(p interface{}, _ interface{}) error { - pResp, ok := p.(*structs.GetAttesterSlashingsResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.GetAttesterSlashingsResponse{}, p) - } - if pResp.Data == nil { - return errEmptyPrysmData - } - return nil - })), - "/beacon/pool/proposer_slashings": newMetadata[structs.GetProposerSlashingsResponse](v1PathTemplate, - withCustomEval(func(p interface{}, _ interface{}) error { - pResp, ok := p.(*structs.GetProposerSlashingsResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.GetProposerSlashingsResponse{}, p) - } - if pResp.Data == nil { - return errEmptyPrysmData - } - return nil - })), - "/beacon/pool/voluntary_exits": newMetadata[structs.ListVoluntaryExitsResponse](v1PathTemplate, - withCustomEval(func(p interface{}, _ interface{}) error { - pResp, ok := p.(*structs.ListVoluntaryExitsResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.ListVoluntaryExitsResponse{}, p) - } - if pResp.Data == nil { - return errEmptyPrysmData - } - return nil + "/beacon/blinded_blocks/{param1}": newMetadata[structs.GetBlockV2Response]( + v1PathTemplate, + withSsz(), + withParams(func(_ primitives.Epoch) []string { + return []string{"head"} })), - "/beacon/pool/bls_to_execution_changes": newMetadata[structs.BLSToExecutionChangesPoolResponse](v1PathTemplate, - withCustomEval(func(p interface{}, _ interface{}) error { - pResp, ok := p.(*structs.BLSToExecutionChangesPoolResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.BLSToExecutionChangesPoolResponse{}, p) - } - if pResp.Data == nil { - return errEmptyPrysmData - } - return nil + "/beacon/pool/attestations": newMetadata[structs.ListAttestationsResponse]( + v1PathTemplate, + withSanityCheckOnly()), + "/beacon/pool/attester_slashings": newMetadata[structs.GetAttesterSlashingsResponse]( + v1PathTemplate, + withSanityCheckOnly()), + "/beacon/pool/proposer_slashings": newMetadata[structs.GetProposerSlashingsResponse]( + v1PathTemplate, + withSanityCheckOnly()), + "/beacon/pool/voluntary_exits": newMetadata[structs.ListVoluntaryExitsResponse]( + v1PathTemplate, + withSanityCheckOnly()), + "/beacon/pool/bls_to_execution_changes": newMetadata[structs.BLSToExecutionChangesPoolResponse]( + v1PathTemplate, + withSanityCheckOnly()), + "/builder/states/{param1}/expected_withdrawals": newMetadata[structs.ExpectedWithdrawalsResponse]( + v1PathTemplate, + withStart(params.CapellaE2EForkEpoch), + withParams(func(_ primitives.Epoch) []string { + return []string{"head"} })), - "/config/fork_schedule": newMetadata[structs.GetForkScheduleResponse](v1PathTemplate, + "/config/fork_schedule": newMetadata[structs.GetForkScheduleResponse]( + v1PathTemplate, withCustomEval(func(p interface{}, lh interface{}) error { pResp, ok := p.(*structs.GetForkScheduleResponse) if !ok { @@ -142,12 +130,6 @@ var requests = map[string]endpoint{ if !ok { return fmt.Errorf(msgWrongJson, &structs.GetForkScheduleResponse{}, lh) } - if pResp.Data == nil { - return errEmptyPrysmData - } - if lhResp.Data == nil { - return errEmptyLighthouseData - } // remove all forks with far-future epoch for i := len(pResp.Data) - 1; i >= 0; i-- { if pResp.Data[i].Epoch == fmt.Sprintf("%d", params.BeaconConfig().FarFutureEpoch) { @@ -161,69 +143,52 @@ var requests = map[string]endpoint{ } return compareJSON(pResp, lhResp) })), + "/config/spec": newMetadata[structs.GetSpecResponse]( + v1PathTemplate, + withSanityCheckOnly()), "/config/deposit_contract": newMetadata[structs.GetDepositContractResponse](v1PathTemplate), - "/debug/beacon/heads": newMetadata[structs.GetForkChoiceHeadsV2Response](v2PathTemplate), - "/node/identity": newMetadata[structs.GetIdentityResponse](v1PathTemplate, - withCustomEval(func(p interface{}, _ interface{}) error { - pResp, ok := p.(*structs.GetIdentityResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.GetIdentityResponse{}, p) - } - if pResp.Data == nil { - return errEmptyPrysmData - } - return nil - })), - "/node/peers": newMetadata[structs.GetPeersResponse](v1PathTemplate, - withCustomEval(func(p interface{}, _ interface{}) error { - pResp, ok := p.(*structs.GetPeersResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.GetPeersResponse{}, p) - } - if pResp.Data == nil { - return errEmptyPrysmData - } - return nil + "/debug/beacon/states/{param1}": newMetadata[structs.GetBeaconStateV2Response]( + v2PathTemplate, + withSanityCheckOnly(), + withSsz(), + withParams(func(_ primitives.Epoch) []string { + return []string{"head"} })), - "/node/peer_count": newMetadata[structs.GetPeerCountResponse](v1PathTemplate, + "/debug/beacon/heads": newMetadata[structs.GetForkChoiceHeadsV2Response]( + v2PathTemplate, + withSanityCheckOnly()), + "/debug/fork_choice": newMetadata[structs.GetForkChoiceDumpResponse]( + v1PathTemplate, + withSanityCheckOnly()), + "/node/identity": newMetadata[structs.GetIdentityResponse]( + v1PathTemplate, + withSanityCheckOnly()), + "/node/peers": newMetadata[structs.GetPeersResponse]( + v1PathTemplate, + withSanityCheckOnly()), + "/node/peer_count": newMetadata[structs.GetPeerCountResponse]( + v1PathTemplate, + withSanityCheckOnly()), + "/node/version": newMetadata[structs.GetVersionResponse]( + v1PathTemplate, withCustomEval(func(p interface{}, _ interface{}) error { - pResp, ok := p.(*structs.GetPeerCountResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.GetPeerCountResponse{}, p) - } - if pResp.Data == nil { - return errEmptyPrysmData - } - return nil - })), - "/node/version": newMetadata[structs.GetVersionResponse](v1PathTemplate, - withCustomEval(func(p interface{}, lh interface{}) error { pResp, ok := p.(*structs.GetVersionResponse) if !ok { return fmt.Errorf(msgWrongJson, &structs.ListAttestationsResponse{}, p) } - lhResp, ok := lh.(*structs.GetVersionResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.ListAttestationsResponse{}, p) - } if pResp.Data == nil { return errEmptyPrysmData } if !strings.Contains(pResp.Data.Version, "Prysm") { return errors.New("version response does not contain Prysm client name") } - if lhResp.Data == nil { - return errEmptyLighthouseData - } - if !strings.Contains(lhResp.Data.Version, "Lighthouse") { - return errors.New("version response does not contain Lighthouse client name") - } return nil })), "/node/syncing": newMetadata[structs.SyncStatusResponse](v1PathTemplate), - "/validator/duties/proposer/{param1}": newMetadata[structs.GetProposerDutiesResponse](v1PathTemplate, - withParams(func(e primitives.Epoch) []string { - return []string{fmt.Sprintf("%v", e)} + "/validator/duties/proposer/{param1}": newMetadata[structs.GetProposerDutiesResponse]( + v1PathTemplate, + withParams(func(currentEpoch primitives.Epoch) []string { + return []string{fmt.Sprintf("%v", currentEpoch)} }), withCustomEval(func(p interface{}, lh interface{}) error { pResp, ok := p.(*structs.GetProposerDutiesResponse) @@ -241,39 +206,56 @@ var requests = map[string]endpoint{ return errEmptyLighthouseData } if lhResp.Data[0].Slot == "0" { - // remove the first item from lighthouse data since lighthouse is returning a value despite no proposer - // there is no proposer on slot 0 so prysm don't return anything for slot 0 + // Lighthouse returns a proposer for slot 0 and Prysm doesn't lhResp.Data = lhResp.Data[1:] } return compareJSON(pResp, lhResp) })), - "/validator/duties/attester/{param1}": newMetadata[structs.GetAttesterDutiesResponse](v1PathTemplate, - withParams(func(e primitives.Epoch) []string { - //ask for a future epoch to test this case - return []string{fmt.Sprintf("%v", e+1)} +} + +var postRequests = map[string]endpoint{ + "/beacon/states/{param1}/validators": newMetadata[structs.GetValidatorsResponse]( + v1PathTemplate, + withParams(func(_ primitives.Epoch) []string { + return []string{"head"} }), - withReq(func() []string { + withPOSTObj(func() interface{} { + return struct { + Ids []string `json:"ids"` + Statuses []string `json:"statuses"` + }{Ids: []string{"0", "1"}, Statuses: nil} + }())), + "/beacon/states/{param1}/validator_balances": newMetadata[structs.GetValidatorBalancesResponse]( + v1PathTemplate, + withParams(func(_ primitives.Epoch) []string { + return []string{"head"} + }), + withPOSTObj(func() []string { + return []string{"0", "1"} + }())), + "/validator/duties/attester/{param1}": newMetadata[structs.GetAttesterDutiesResponse]( + v1PathTemplate, + withParams(func(currentEpoch primitives.Epoch) []string { + return []string{fmt.Sprintf("%v", currentEpoch)} + }), + withPOSTObj(func() []string { validatorIndices := make([]string, 64) - for key := range validatorIndices { - validatorIndices[key] = fmt.Sprintf("%d", key) + for i := range validatorIndices { + validatorIndices[i] = fmt.Sprintf("%d", i) } return validatorIndices - }()), - withCustomEval(func(p interface{}, lh interface{}) error { - pResp, ok := p.(*structs.GetAttesterDutiesResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.GetAttesterDutiesResponse{}, p) - } - lhResp, ok := lh.(*structs.GetAttesterDutiesResponse) - if !ok { - return fmt.Errorf(msgWrongJson, &structs.GetAttesterDutiesResponse{}, lh) - } - if pResp.Data == nil { - return errEmptyPrysmData - } - if lhResp.Data == nil { - return errEmptyLighthouseData + }())), + "/validator/duties/sync/{param1}": newMetadata[structs.GetSyncCommitteeDutiesResponse]( + v1PathTemplate, + withStart(params.AltairE2EForkEpoch), + withParams(func(currentEpoch primitives.Epoch) []string { + return []string{fmt.Sprintf("%v", currentEpoch)} + }), + withPOSTObj(func() []string { + validatorIndices := make([]string, 64) + for i := range validatorIndices { + validatorIndices[i] = fmt.Sprintf("%d", i) } - return compareJSON(pResp, lhResp) - })), + return validatorIndices + }())), } diff --git a/testing/endtoend/evaluators/beaconapi/types.go b/testing/endtoend/evaluators/beaconapi/types.go index 27b122500c6a..5de88160d1f5 100644 --- a/testing/endtoend/evaluators/beaconapi/types.go +++ b/testing/endtoend/evaluators/beaconapi/types.go @@ -1,17 +1,21 @@ package beaconapi -import "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" +import ( + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" +) type endpoint interface { getBasePath() string + sanityCheckOnlyEnabled() bool + enableSanityCheckOnly() sszEnabled() bool enableSsz() getSszResp() []byte // retrieves the Prysm SSZ response setSszResp(resp []byte) // sets the Prysm SSZ response getStart() primitives.Epoch setStart(start primitives.Epoch) - getReq() interface{} - setReq(req interface{}) + getPOSTObj() interface{} + setPOSTObj(obj interface{}) getPResp() interface{} // retrieves the Prysm JSON response getLHResp() interface{} // retrieves the Lighthouse JSON response getParams(epoch primitives.Epoch) []string @@ -22,9 +26,10 @@ type endpoint interface { type apiEndpoint[Resp any] struct { basePath string + sanity bool ssz bool start primitives.Epoch - req interface{} + postObj interface{} pResp *Resp // Prysm JSON response lhResp *Resp // Lighthouse JSON response sszResp []byte // Prysm SSZ response @@ -36,6 +41,14 @@ func (e *apiEndpoint[Resp]) getBasePath() string { return e.basePath } +func (e *apiEndpoint[Resp]) sanityCheckOnlyEnabled() bool { + return e.sanity +} + +func (e *apiEndpoint[Resp]) enableSanityCheckOnly() { + e.sanity = true +} + func (e *apiEndpoint[Resp]) sszEnabled() bool { return e.ssz } @@ -60,12 +73,12 @@ func (e *apiEndpoint[Resp]) setStart(start primitives.Epoch) { e.start = start } -func (e *apiEndpoint[Resp]) getReq() interface{} { - return e.req +func (e *apiEndpoint[Resp]) getPOSTObj() interface{} { + return e.postObj } -func (e *apiEndpoint[Resp]) setReq(req interface{}) { - e.req = req +func (e *apiEndpoint[Resp]) setPOSTObj(obj interface{}) { + e.postObj = obj } func (e *apiEndpoint[Resp]) getPResp() interface{} { @@ -109,30 +122,42 @@ func newMetadata[Resp any](basePath string, opts ...endpointOpt) *apiEndpoint[Re type endpointOpt func(endpoint) +// We only care if the request was successful, without comparing responses. +func withSanityCheckOnly() endpointOpt { + return func(e endpoint) { + e.enableSanityCheckOnly() + } +} + +// We request SSZ data too. func withSsz() endpointOpt { return func(e endpoint) { e.enableSsz() } } +// We begin issuing the request at a particular epoch. func withStart(start primitives.Epoch) endpointOpt { return func(e endpoint) { e.setStart(start) } } -func withReq(req interface{}) endpointOpt { +// We perform a POST instead of GET, sending an object. +func withPOSTObj(obj interface{}) endpointOpt { return func(e endpoint) { - e.setReq(req) + e.setPOSTObj(obj) } } +// We specify URL parameters. func withParams(f func(currentEpoch primitives.Epoch) []string) endpointOpt { return func(e endpoint) { e.setParams(f) } } +// We perform custom evaluation on responses. func withCustomEval(f func(interface{}, interface{}) error) endpointOpt { return func(e endpoint) { e.setCustomEval(f) diff --git a/testing/endtoend/evaluators/beaconapi/util.go b/testing/endtoend/evaluators/beaconapi/util.go index cf26eccc8543..5c8aa1d70fb5 100644 --- a/testing/endtoend/evaluators/beaconapi/util.go +++ b/testing/endtoend/evaluators/beaconapi/util.go @@ -25,16 +25,16 @@ const ( msgSSZUnmarshalFailed = "failed to unmarshal SSZ" ) -func doJSONGetRequest(template string, requestPath string, beaconNodeIdx int, resp interface{}, bnType ...string) error { +func doJSONGetRequest(template, requestPath string, beaconNodeIdx int, resp interface{}, bnType ...string) error { if len(bnType) == 0 { - bnType = []string{"prysm"} + bnType = []string{"Prysm"} } var port int switch bnType[0] { - case "prysm": + case "Prysm": port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort - case "lighthouse": + case "Lighthouse": port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort default: return fmt.Errorf(msgUnknownNode, bnType[0]) @@ -55,6 +55,7 @@ func doJSONGetRequest(template string, requestPath string, beaconNodeIdx int, re return err } } else { + defer closeBody(httpResp.Body) body, err = io.ReadAll(httpResp.Body) if err != nil { return err @@ -65,17 +66,16 @@ func doJSONGetRequest(template string, requestPath string, beaconNodeIdx int, re return json.NewDecoder(httpResp.Body).Decode(&resp) } -func doSSZGetRequest(template string, requestPath string, beaconNodeIdx int, bnType ...string) ([]byte, error) { +func doSSZGetRequest(template, requestPath string, beaconNodeIdx int, bnType ...string) ([]byte, error) { if len(bnType) == 0 { - bnType = []string{"prysm"} + bnType = []string{"Prysm"} } - client := &http.Client{} var port int switch bnType[0] { - case "prysm": + case "Prysm": port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort - case "lighthouse": + case "Lighthouse": port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort default: return nil, fmt.Errorf(msgUnknownNode, bnType[0]) @@ -83,24 +83,24 @@ func doSSZGetRequest(template string, requestPath string, beaconNodeIdx int, bnT basePath := fmt.Sprintf(template, port+beaconNodeIdx) - req, err := http.NewRequest(http.MethodGet, basePath+requestPath, nil) + req, err := http.NewRequest(http.MethodGet, basePath+requestPath, http.NoBody) if err != nil { return nil, err } req.Header.Set("Accept", "application/octet-stream") - rsp, err := client.Do(req) + resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } - if rsp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusOK { var body interface{} - if err := json.NewDecoder(rsp.Body).Decode(&body); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return nil, err } - return nil, fmt.Errorf(msgRequestFailed, bnType[0], rsp.StatusCode, body) + return nil, fmt.Errorf(msgRequestFailed, bnType[0], resp.StatusCode, body) } - defer closeBody(rsp.Body) - body, err := io.ReadAll(rsp.Body) + defer closeBody(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } @@ -108,23 +108,23 @@ func doSSZGetRequest(template string, requestPath string, beaconNodeIdx int, bnT return body, nil } -func doJSONPostRequest(template string, requestPath string, beaconNodeIdx int, postData, resp interface{}, bnType ...string) error { +func doJSONPostRequest(template, requestPath string, beaconNodeIdx int, postObj, resp interface{}, bnType ...string) error { if len(bnType) == 0 { - bnType = []string{"prysm"} + bnType = []string{"Prysm"} } var port int switch bnType[0] { - case "prysm": + case "Prysm": port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort - case "lighthouse": + case "Lighthouse": port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort default: return fmt.Errorf(msgUnknownNode, bnType[0]) } basePath := fmt.Sprintf(template, port+beaconNodeIdx) - b, err := json.Marshal(postData) + b, err := json.Marshal(postObj) if err != nil { return err } @@ -144,6 +144,7 @@ func doJSONPostRequest(template string, requestPath string, beaconNodeIdx int, p return err } } else { + defer closeBody(httpResp.Body) body, err = io.ReadAll(httpResp.Body) if err != nil { return err diff --git a/testing/endtoend/evaluators/beaconapi/verify.go b/testing/endtoend/evaluators/beaconapi/verify.go index a16fbc94688b..9ee9e4d66325 100644 --- a/testing/endtoend/evaluators/beaconapi/verify.go +++ b/testing/endtoend/evaluators/beaconapi/verify.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "net/http" "reflect" "strconv" "strings" @@ -14,6 +15,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + params2 "github.com/prysmaticlabs/prysm/v5/testing/endtoend/params" "github.com/prysmaticlabs/prysm/v5/testing/endtoend/policies" e2etypes "github.com/prysmaticlabs/prysm/v5/testing/endtoend/types" "github.com/prysmaticlabs/prysm/v5/time/slots" @@ -54,7 +56,7 @@ func run(nodeIdx int) error { } currentEpoch := slots.EpochsSinceGenesis(time.Unix(genesisTime, 0)) - for path, m := range requests { + for path, m := range getRequests { if currentEpoch < m.getStart() { continue } @@ -62,26 +64,57 @@ func run(nodeIdx int) error { if m.getParams(currentEpoch) != nil { apiPath = pathFromParams(path, m.getParams(currentEpoch)) } - fmt.Printf("executing JSON path: %s\n", apiPath) - if err = compareJSONMultiClient(nodeIdx, m.getBasePath(), apiPath, m.getReq(), m.getPResp(), m.getLHResp(), m.getCustomEval()); err != nil { - return err - } - if m.sszEnabled() { - fmt.Printf("executing SSZ path: %s\n", apiPath) - b, err := compareSSZMultiClient(nodeIdx, m.getBasePath(), apiPath) - if err != nil { + + if m.sanityCheckOnlyEnabled() { + resp := m.getPResp() + if err = doJSONGetRequest(m.getBasePath(), apiPath, nodeIdx, resp); err != nil { + return errors.Wrapf(err, "issue during Prysm JSON GET request for path %s", apiPath) + } + if resp == nil { + return fmt.Errorf("nil response from Prysm JSON GET request for path %s", apiPath) + } + if m.sszEnabled() { + sszResp, err := doSSZGetRequest(m.getBasePath(), apiPath, nodeIdx) + if err != nil { + return errors.Wrapf(err, "issue during Prysm SSZ GET request for path %s", apiPath) + } + if sszResp == nil { + return fmt.Errorf("nil response from Prysm SSZ GET request for path %s", apiPath) + } + } + } else { + if err = compareGETJSON(nodeIdx, m.getBasePath(), apiPath, m.getPResp(), m.getLHResp(), m.getCustomEval()); err != nil { return err } - m.setSszResp(b) + if m.sszEnabled() { + b, err := compareGETSSZ(nodeIdx, m.getBasePath(), apiPath) + if err != nil { + return err + } + m.setSszResp(b) + } + } + } + + for path, m := range postRequests { + if currentEpoch < m.getStart() { + continue + } + apiPath := path + if m.getParams(currentEpoch) != nil { + apiPath = pathFromParams(path, m.getParams(currentEpoch)) + } + if err = comparePOSTJSON(nodeIdx, m.getBasePath(), apiPath, m.getPOSTObj(), m.getPResp(), m.getLHResp(), m.getCustomEval()); err != nil { + return err } } - return postEvaluation(requests, currentEpoch) + return postEvaluation(nodeIdx, getRequests, currentEpoch) } // postEvaluation performs additional evaluation after all requests have been completed. // It is useful for things such as checking if specific fields match between endpoints. -func postEvaluation(requests map[string]endpoint, epoch primitives.Epoch) error { +func postEvaluation(nodeIdx int, requests map[string]endpoint, epoch primitives.Epoch) error { // verify that block SSZ responses have the correct structure blockData := requests["/beacon/blocks/{param1}"] blindedBlockData := requests["/beacon/blinded_blocks/{param1}"] @@ -147,24 +180,51 @@ func postEvaluation(requests map[string]endpoint, epoch primitives.Epoch) error return fmt.Errorf("header root %s does not match duties root %s ", header.Data.Root, duties.DependentRoot) } + // perform a health check + basePath := fmt.Sprintf(v1PathTemplate, params2.PrysmBeaconNodeGatewayPort+nodeIdx) + resp, err := http.Get(basePath + "/node/health") + if err != nil { + return errors.Wrap(err, "could not perform a health check") + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("health check response's status code is %d", resp.StatusCode) + } + return nil } -func compareJSONMultiClient(nodeIdx int, base, path string, req, pResp, lhResp interface{}, customEval func(interface{}, interface{}) error) error { - if req != nil { - if err := doJSONPostRequest(base, path, nodeIdx, req, pResp); err != nil { - return errors.Wrapf(err, "could not perform Prysm JSON POST request for path %s", path) - } - if err := doJSONPostRequest(base, path, nodeIdx, req, lhResp, "lighthouse"); err != nil { - return errors.Wrapf(err, "could not perform Lighthouse JSON POST request for path %s", path) - } +func compareGETJSON(nodeIdx int, base, path string, pResp, lhResp interface{}, customEval func(interface{}, interface{}) error) error { + if err := doJSONGetRequest(base, path, nodeIdx, pResp); err != nil { + return errors.Wrapf(err, "issue during Prysm JSON GET request for path %s", path) + } + if err := doJSONGetRequest(base, path, nodeIdx, lhResp, "Lighthouse"); err != nil { + return errors.Wrapf(err, "issue during Lighthouse JSON GET request for path %s", path) + } + if pResp == nil { + return errEmptyPrysmData + } + if lhResp == nil { + return errEmptyLighthouseData + } + if customEval != nil { + return customEval(pResp, lhResp) } else { - if err := doJSONGetRequest(base, path, nodeIdx, pResp); err != nil { - return errors.Wrapf(err, "could not perform Prysm JSON GET request for path %s", path) - } - if err := doJSONGetRequest(base, path, nodeIdx, lhResp, "lighthouse"); err != nil { - return errors.Wrapf(err, "could not perform Lighthouse JSON GET request for path %s", path) - } + return compareJSON(pResp, lhResp) + } +} + +func comparePOSTJSON(nodeIdx int, base, path string, postObj, pResp, lhResp interface{}, customEval func(interface{}, interface{}) error) error { + if err := doJSONPostRequest(base, path, nodeIdx, postObj, pResp); err != nil { + return errors.Wrapf(err, "issue during Prysm JSON POST request for path %s", path) + } + if err := doJSONPostRequest(base, path, nodeIdx, postObj, lhResp, "Lighthouse"); err != nil { + return errors.Wrapf(err, "issue during Lighthouse JSON POST request for path %s", path) + } + if pResp == nil { + return errEmptyPrysmData + } + if lhResp == nil { + return errEmptyLighthouseData } if customEval != nil { return customEval(pResp, lhResp) @@ -173,14 +233,14 @@ func compareJSONMultiClient(nodeIdx int, base, path string, req, pResp, lhResp i } } -func compareSSZMultiClient(nodeIdx int, base, path string) ([]byte, error) { +func compareGETSSZ(nodeIdx int, base, path string) ([]byte, error) { pResp, err := doSSZGetRequest(base, path, nodeIdx) if err != nil { - return nil, errors.Wrapf(err, "could not perform Prysm SSZ GET request for path %s", path) + return nil, errors.Wrapf(err, "issue during Prysm SSZ GET request for path %s", path) } - lhResp, err := doSSZGetRequest(base, path, nodeIdx, "lighthouse") + lhResp, err := doSSZGetRequest(base, path, nodeIdx, "Lighthouse") if err != nil { - return nil, errors.Wrapf(err, "could not perform Lighthouse SSZ GET request for path %s", path) + return nil, errors.Wrapf(err, "issue during Lighthouse SSZ GET request for path %s", path) } if !bytes.Equal(pResp, lhResp) { return nil, errors.New("Prysm SSZ response does not match Lighthouse SSZ response") @@ -188,7 +248,7 @@ func compareSSZMultiClient(nodeIdx int, base, path string) ([]byte, error) { return pResp, nil } -func compareJSON(pResp interface{}, lhResp interface{}) error { +func compareJSON(pResp, lhResp interface{}) error { if !reflect.DeepEqual(pResp, lhResp) { p, err := json.Marshal(pResp) if err != nil {