Better Beacon API evaluator part 1 (#13084)

* Better Beacon API evaluator part 1

* rename package

* more endpoints

* rename package back

* more endpoints

* small improvements

* remove the need for `params`

---------

Co-authored-by: Nishant Das <nishdas93@gmail.com>
This commit is contained in:
Radosław Kapka
2023-10-27 13:57:49 +02:00
committed by GitHub
parent 203dc5f63b
commit 022ee17af9
6 changed files with 657 additions and 261 deletions

View File

@@ -448,7 +448,7 @@ func (s *Service) Start() {
s.cfg.Router.HandleFunc("/eth/v2/beacon/blinded_blocks", beaconChainServerV1.PublishBlindedBlockV2).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blocks/{block_id}", beaconChainServerV1.GetBlock).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v2/beacon/blocks/{block_id}", beaconChainServerV1.GetBlockV2).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v2/beacon/blocks/{block_id}/attestations", beaconChainServerV1.GetBlockAttestations).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blocks/{block_id}/attestations", beaconChainServerV1.GetBlockAttestations).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blinded_blocks/{block_id}", beaconChainServerV1.GetBlindedBlock).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blocks/{block_id}/root", beaconChainServerV1.GetBlockRoot).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/attestations", beaconChainServerV1.ListAttestations).Methods(http.MethodGet)

View File

@@ -11,6 +11,7 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v4/testing/endtoend/evaluators/beaconapi_evaluators",
visibility = ["//testing/endtoend:__subpackages__"],
deps = [
"//beacon-chain/rpc/apimiddleware:go_default_library",
"//beacon-chain/rpc/eth/beacon:go_default_library",
"//beacon-chain/rpc/eth/debug:go_default_library",
"//beacon-chain/rpc/eth/node:go_default_library",
@@ -23,7 +24,6 @@ go_library(
"//testing/endtoend/policies:go_default_library",
"//testing/endtoend/types:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@org_golang_google_grpc//:go_default_library",

View File

@@ -9,8 +9,8 @@ import (
"strings"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/debug"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/node"
@@ -22,7 +22,15 @@ import (
"github.com/prysmaticlabs/prysm/v4/time/slots"
)
var (
errSszCast = errors.New("ssz response is not a byte array")
errJsonCast = errors.New("json response has wrong structure")
errEmptyPrysmData = errors.New("prysm data is empty")
errEmptyLighthouseData = errors.New("lighthouse data is empty")
)
type metadata struct {
start primitives.Epoch
basepath string
params func(encoding string, currentEpoch primitives.Epoch) []string
requestObject interface{}
@@ -31,12 +39,9 @@ type metadata struct {
customEvaluation func(interface{}, interface{}) error
}
var beaconPathsAndObjects = map[string]metadata{
var requests = map[string]metadata{
"/beacon/genesis": {
basepath: v1MiddlewarePathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{}
},
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &beacon.GetGenesisResponse{},
},
@@ -45,7 +50,7 @@ var beaconPathsAndObjects = map[string]metadata{
},
},
"/beacon/states/{param1}/root": {
basepath: v1MiddlewarePathTemplate,
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
@@ -56,41 +61,8 @@ var beaconPathsAndObjects = map[string]metadata{
"json": &beacon.GetStateRootResponse{},
},
},
"/beacon/states/{param1}/finality_checkpoints": {
basepath: v1MiddlewarePathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetFinalityCheckpointsResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetFinalityCheckpointsResponse{},
},
},
"/beacon/blocks/{param1}": {
basepath: v2MiddlewarePathTemplate,
params: func(t string, e primitives.Epoch) []string {
if t == "ssz" {
if e < 4 {
return []string{"genesis"}
}
return []string{"finalized"}
}
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetBlockV2Response{},
"ssz": []byte{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetBlockV2Response{},
"ssz": []byte{},
},
},
"/beacon/states/{param1}/fork": {
basepath: v1MiddlewarePathTemplate,
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"finalized"}
},
@@ -101,9 +73,354 @@ var beaconPathsAndObjects = map[string]metadata{
"json": &beacon.GetStateForkResponse{},
},
},
"/debug/beacon/states/{param1}": {
basepath: v2MiddlewarePathTemplate,
"/beacon/states/{param1}/finality_checkpoints": {
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetFinalityCheckpointsResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetFinalityCheckpointsResponse{},
},
},
// we want to test comma-separated query params
"/beacon/states/{param1}/validators?id=0,1": {
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetValidatorsResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetValidatorsResponse{},
},
},
"/beacon/states/{param1}/validators/{param2}": {
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head", "0"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetValidatorResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetValidatorResponse{},
},
},
"/beacon/states/{param1}/validator_balances?id=0,1": {
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetValidatorBalancesResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetValidatorBalancesResponse{},
},
},
"/beacon/states/{param1}/committees?index=0": {
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetCommitteesResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetCommitteesResponse{},
},
},
"/beacon/states/{param1}/sync_committees": {
start: helpers.AltairE2EForkEpoch,
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetSyncCommitteeResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetSyncCommitteeResponse{},
},
},
"/beacon/states/{param1}/randao": {
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetRandaoResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetRandaoResponse{},
},
},
"/beacon/headers": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &beacon.GetBlockHeadersResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetBlockHeadersResponse{},
},
},
"/beacon/headers/{param1}": {
basepath: v1PathTemplate,
params: func(_ string, e primitives.Epoch) []string {
slot := uint64(0)
if e > 0 {
slot = (uint64(e) * uint64(params.BeaconConfig().SlotsPerEpoch)) - 1
}
return []string{fmt.Sprintf("%v", slot)}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetBlockHeaderResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetBlockHeaderResponse{},
},
},
"/beacon/blocks/{param1}": {
basepath: v2PathTemplate,
params: func(t string, e primitives.Epoch) []string {
if t == "ssz" {
if e < 4 {
return []string{"genesis"}
}
return []string{"finalized"}
}
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetBlockV2Response{},
"ssz": []byte{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetBlockV2Response{},
"ssz": []byte{},
},
},
"/beacon/blocks/{param1}/root": {
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.BlockRootResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.BlockRootResponse{},
},
},
"/beacon/blocks/{param1}/attestations": {
basepath: v1PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetBlockAttestationsResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetBlockAttestationsResponse{},
},
},
"/beacon/blinded_blocks/{param1}": {
basepath: v1PathTemplate,
params: func(t string, e primitives.Epoch) []string {
if t == "ssz" {
if e < 4 {
return []string{"genesis"}
}
return []string{"finalized"}
}
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetBlockV2Response{},
"ssz": []byte{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetBlockV2Response{},
"ssz": []byte{},
},
},
"/beacon/pool/attestations": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &beacon.ListAttestationsResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.ListAttestationsResponse{},
},
customEvaluation: func(p interface{}, l interface{}) error {
pResp, ok := p.(*beacon.ListAttestationsResponse)
if !ok {
return errJsonCast
}
lResp, ok := l.(*beacon.ListAttestationsResponse)
if !ok {
return errJsonCast
}
if pResp.Data == nil {
return errEmptyPrysmData
}
if lResp.Data == nil {
return errEmptyLighthouseData
}
return nil
},
},
"/beacon/pool/attester_slashings": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &apimiddleware.AttesterSlashingsPoolResponseJson{},
},
lighthouseResps: map[string]interface{}{
"json": &apimiddleware.AttesterSlashingsPoolResponseJson{},
},
customEvaluation: func(p interface{}, l interface{}) error {
pResp, ok := p.(*apimiddleware.AttesterSlashingsPoolResponseJson)
if !ok {
return errJsonCast
}
lResp, ok := l.(*apimiddleware.AttesterSlashingsPoolResponseJson)
if !ok {
return errJsonCast
}
if pResp.Data == nil {
return errEmptyPrysmData
}
if lResp.Data == nil {
return errEmptyLighthouseData
}
return nil
},
},
"/beacon/pool/proposer_slashings": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &apimiddleware.ProposerSlashingsPoolResponseJson{},
},
lighthouseResps: map[string]interface{}{
"json": &apimiddleware.ProposerSlashingsPoolResponseJson{},
},
customEvaluation: func(p interface{}, l interface{}) error {
pResp, ok := p.(*apimiddleware.ProposerSlashingsPoolResponseJson)
if !ok {
return errJsonCast
}
lResp, ok := l.(*apimiddleware.ProposerSlashingsPoolResponseJson)
if !ok {
return errJsonCast
}
if pResp.Data == nil {
return errEmptyPrysmData
}
if lResp.Data == nil {
return errEmptyLighthouseData
}
return nil
},
},
"/beacon/pool/voluntary_exits": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &beacon.ListVoluntaryExitsResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.ListVoluntaryExitsResponse{},
},
customEvaluation: func(p interface{}, l interface{}) error {
pResp, ok := p.(*beacon.ListVoluntaryExitsResponse)
if !ok {
return errJsonCast
}
lResp, ok := l.(*beacon.ListVoluntaryExitsResponse)
if !ok {
return errJsonCast
}
if pResp.Data == nil {
return errEmptyPrysmData
}
if lResp.Data == nil {
return errEmptyLighthouseData
}
return nil
},
},
"/beacon/pool/bls_to_execution_changes": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &beacon.BLSToExecutionChangesPoolResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.BLSToExecutionChangesPoolResponse{},
},
customEvaluation: func(p interface{}, l interface{}) error {
pResp, ok := p.(*beacon.BLSToExecutionChangesPoolResponse)
if !ok {
return errJsonCast
}
lResp, ok := l.(*beacon.BLSToExecutionChangesPoolResponse)
if !ok {
return errJsonCast
}
if pResp.Data == nil {
return errEmptyPrysmData
}
if lResp.Data == nil {
return errEmptyLighthouseData
}
return nil
},
},
"/config/fork_schedule": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &apimiddleware.ForkScheduleResponseJson{},
},
lighthouseResps: map[string]interface{}{
"json": &apimiddleware.ForkScheduleResponseJson{},
},
customEvaluation: func(p interface{}, l interface{}) error {
// remove all forks with far-future epoch
pSchedule, ok := p.(*apimiddleware.ForkScheduleResponseJson)
if !ok {
return errJsonCast
}
for i := len(pSchedule.Data) - 1; i >= 0; i-- {
if pSchedule.Data[i].Epoch == fmt.Sprintf("%d", params.BeaconConfig().FarFutureEpoch) {
pSchedule.Data = append(pSchedule.Data[:i], pSchedule.Data[i+1:]...)
}
}
lSchedule, ok := l.(*apimiddleware.ForkScheduleResponseJson)
if !ok {
return errJsonCast
}
for i := len(lSchedule.Data) - 1; i >= 0; i-- {
if lSchedule.Data[i].Epoch == fmt.Sprintf("%d", params.BeaconConfig().FarFutureEpoch) {
lSchedule.Data = append(lSchedule.Data[:i], lSchedule.Data[i+1:]...)
}
}
return compareJSONResponseObjects(pSchedule, lSchedule)
},
},
"/config/deposit_contract": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &apimiddleware.DepositContractResponseJson{},
},
lighthouseResps: map[string]interface{}{
"json": &apimiddleware.DepositContractResponseJson{},
},
},
"/debug/beacon/states/{param1}": {
basepath: v2PathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
@@ -113,8 +430,136 @@ var beaconPathsAndObjects = map[string]metadata{
"json": &debug.GetBeaconStateV2Response{},
},
},
"/debug/beacon/heads": {
basepath: v2PathTemplate,
prysmResps: map[string]interface{}{
"json": &apimiddleware.V2ForkChoiceHeadsResponseJson{},
},
lighthouseResps: map[string]interface{}{
"json": &apimiddleware.V2ForkChoiceHeadsResponseJson{},
},
},
"/node/identity": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &node.GetIdentityResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &node.GetIdentityResponse{},
},
customEvaluation: func(p interface{}, l interface{}) error {
pResp, ok := p.(*node.GetIdentityResponse)
if !ok {
return errJsonCast
}
lResp, ok := l.(*node.GetIdentityResponse)
if !ok {
return errJsonCast
}
if pResp.Data == nil {
return errEmptyPrysmData
}
if lResp.Data == nil {
return errEmptyLighthouseData
}
return nil
},
},
"/node/peers": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &node.GetPeersResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &node.GetPeersResponse{},
},
customEvaluation: func(p interface{}, l interface{}) error {
pResp, ok := p.(*node.GetPeersResponse)
if !ok {
return errJsonCast
}
lResp, ok := l.(*node.GetPeersResponse)
if !ok {
return errJsonCast
}
if pResp.Data == nil {
return errEmptyPrysmData
}
if lResp.Data == nil {
return errEmptyLighthouseData
}
return nil
},
},
"/node/peer_count": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &node.GetPeerCountResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &node.GetPeerCountResponse{},
},
customEvaluation: func(p interface{}, l interface{}) error {
pResp, ok := p.(*node.GetPeerCountResponse)
if !ok {
return errJsonCast
}
lResp, ok := l.(*node.GetPeerCountResponse)
if !ok {
return errJsonCast
}
if pResp.Data == nil {
return errEmptyPrysmData
}
if lResp.Data == nil {
return errEmptyLighthouseData
}
return nil
},
},
"/node/version": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &node.GetVersionResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &node.GetVersionResponse{},
},
customEvaluation: func(p interface{}, l interface{}) error {
pResp, ok := p.(*node.GetVersionResponse)
if !ok {
return errJsonCast
}
lResp, ok := l.(*node.GetVersionResponse)
if !ok {
return errJsonCast
}
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 lResp.Data == nil {
return errEmptyLighthouseData
}
if !strings.Contains(lResp.Data.Version, "Lighthouse") {
return errors.New("version response does not contain Lighthouse client name")
}
return nil
},
},
"/node/syncing": {
basepath: v1PathTemplate,
prysmResps: map[string]interface{}{
"json": &node.SyncStatusResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &node.SyncStatusResponse{},
},
},
"/validator/duties/proposer/{param1}": {
basepath: v1MiddlewarePathTemplate,
basepath: v1PathTemplate,
params: func(_ string, e primitives.Epoch) []string {
return []string{fmt.Sprintf("%v", e)}
},
@@ -138,7 +583,7 @@ var beaconPathsAndObjects = map[string]metadata{
},
},
"/validator/duties/attester/{param1}": {
basepath: v1MiddlewarePathTemplate,
basepath: v1PathTemplate,
params: func(_ string, e primitives.Epoch) []string {
//ask for a future epoch to test this case
return []string{fmt.Sprintf("%v", e+1)}
@@ -173,99 +618,12 @@ var beaconPathsAndObjects = map[string]metadata{
return compareJSONResponseObjects(prysmResp, castedl)
},
},
"/beacon/headers/{param1}": {
basepath: v1MiddlewarePathTemplate,
params: func(_ string, e primitives.Epoch) []string {
slot := uint64(0)
if e > 0 {
slot = (uint64(e) * uint64(params.BeaconConfig().SlotsPerEpoch)) - 1
}
return []string{fmt.Sprintf("%v", slot)}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetBlockHeaderResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetBlockHeaderResponse{},
},
},
// we want to test comma-separated query params
"/beacon/states/{param1}/validators?id=0,1": {
basepath: v1MiddlewarePathTemplate,
params: func(_ string, e primitives.Epoch) []string {
return []string{"head"}
},
prysmResps: map[string]interface{}{
"json": &beacon.GetValidatorsResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &beacon.GetValidatorsResponse{},
},
},
"/node/identity": {
basepath: v1MiddlewarePathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{}
},
prysmResps: map[string]interface{}{
"json": &node.GetIdentityResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &node.GetIdentityResponse{},
},
customEvaluation: func(prysmResp interface{}, lhouseResp interface{}) error {
castedp, ok := prysmResp.(*node.GetIdentityResponse)
if !ok {
return errors.New("failed to cast type")
}
castedl, ok := lhouseResp.(*node.GetIdentityResponse)
if !ok {
return errors.New("failed to cast type")
}
if castedp.Data == nil {
return errors.New("prysm node identity was empty")
}
if castedl.Data == nil {
return errors.New("lighthouse node identity was empty")
}
return nil
},
},
"/node/peers": {
basepath: v1MiddlewarePathTemplate,
params: func(_ string, _ primitives.Epoch) []string {
return []string{}
},
prysmResps: map[string]interface{}{
"json": &node.GetPeersResponse{},
},
lighthouseResps: map[string]interface{}{
"json": &node.GetPeersResponse{},
},
customEvaluation: func(prysmResp interface{}, lhouseResp interface{}) error {
castedp, ok := prysmResp.(*node.GetPeersResponse)
if !ok {
return errors.New("failed to cast type")
}
castedl, ok := lhouseResp.(*node.GetPeersResponse)
if !ok {
return errors.New("failed to cast type")
}
if castedp.Data == nil {
return errors.New("prysm node identity was empty")
}
if castedl.Data == nil {
return errors.New("lighthouse node identity was empty")
}
return nil
},
},
}
func withCompareBeaconAPIs(beaconNodeIdx int) error {
genesisResp := &beacon.GetGenesisResponse{}
err := doMiddlewareJSONGetRequest(
v1MiddlewarePathTemplate,
err := doJSONGetRequest(
v1PathTemplate,
"/beacon/genesis",
beaconNodeIdx,
genesisResp,
@@ -279,151 +637,173 @@ func withCompareBeaconAPIs(beaconNodeIdx int) error {
}
currentEpoch := slots.EpochsSinceGenesis(time.Unix(genesisTime, 0))
for path, meta := range beaconPathsAndObjects {
for path, meta := range requests {
if currentEpoch < meta.start {
continue
}
for key := range meta.prysmResps {
switch key {
case "json":
jsonparams := meta.params("json", currentEpoch)
apipath := pathFromParams(path, jsonparams)
apipath := path
if meta.params != nil {
jsonparams := meta.params("json", currentEpoch)
apipath = pathFromParams(path, jsonparams)
}
fmt.Printf("executing json api path: %s\n", apipath)
if err := compareJSONMulticlient(beaconNodeIdx,
meta.basepath,
apipath,
meta.requestObject,
beaconPathsAndObjects[path].prysmResps[key],
beaconPathsAndObjects[path].lighthouseResps[key],
requests[path].prysmResps[key],
requests[path].lighthouseResps[key],
meta.customEvaluation,
); err != nil {
return err
}
case "ssz":
sszparams := meta.params("ssz", currentEpoch)
if len(sszparams) == 0 {
continue
apipath := path
if meta.params != nil {
sszparams := meta.params("ssz", currentEpoch)
apipath = pathFromParams(path, sszparams)
}
apipath := pathFromParams(path, sszparams)
fmt.Printf("executing ssz api path: %s\n", apipath)
prysmr, lighthouser, err := compareSSZMulticlient(beaconNodeIdx, meta.basepath, apipath)
if err != nil {
return err
}
beaconPathsAndObjects[path].prysmResps[key] = prysmr
beaconPathsAndObjects[path].lighthouseResps[key] = lighthouser
requests[path].prysmResps[key] = prysmr
requests[path].lighthouseResps[key] = lighthouser
default:
return fmt.Errorf("unknown encoding type %s", key)
}
}
}
return orderedEvaluationOnResponses(beaconPathsAndObjects, genesisResp)
return postEvaluation(beaconNodeIdx, requests)
}
func orderedEvaluationOnResponses(beaconPathsAndObjects map[string]metadata, genesisData *beacon.GetGenesisResponse) error {
forkPathData := beaconPathsAndObjects["/beacon/states/{param1}/fork"]
prysmForkData, ok := forkPathData.prysmResps["json"].(*beacon.GetStateForkResponse)
// 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(beaconNodeIdx int, requests map[string]metadata) error {
// verify that block SSZ responses have the correct structure
forkData := requests["/beacon/states/{param1}/fork"]
fork, ok := forkData.prysmResps["json"].(*beacon.GetStateForkResponse)
if !ok {
return errors.New("failed to cast type")
return errJsonCast
}
lighthouseForkData, ok := forkPathData.lighthouseResps["json"].(*beacon.GetStateForkResponse)
if !ok {
return errors.New("failed to cast type")
}
if prysmForkData.Data.Epoch != lighthouseForkData.Data.Epoch {
return fmt.Errorf("prysm epoch %v does not match lighthouse epoch %v",
prysmForkData.Data.Epoch,
lighthouseForkData.Data.Epoch)
}
finalizedEpoch, err := strconv.ParseUint(prysmForkData.Data.Epoch, 10, 64)
finalizedEpoch, err := strconv.ParseUint(fork.Data.Epoch, 10, 64)
if err != nil {
return err
}
blockPathData := beaconPathsAndObjects["/beacon/blocks/{param1}"]
sszrspL, ok := blockPathData.prysmResps["ssz"].([]byte)
blockData := requests["/beacon/blocks/{param1}"]
blockSsz, ok := blockData.prysmResps["ssz"].([]byte)
if !ok {
return errors.New("failed to cast type")
return errSszCast
}
sszrspP, ok := blockPathData.lighthouseResps["ssz"].([]byte)
blindedBlockData := requests["/beacon/blinded_blocks/{param1}"]
blindedBlockSsz, ok := blindedBlockData.prysmResps["ssz"].([]byte)
if !ok {
return errors.New("failed to cast type")
return errSszCast
}
if finalizedEpoch < helpers.AltairE2EForkEpoch+2 {
blockP := &ethpb.SignedBeaconBlock{}
blockL := &ethpb.SignedBeaconBlock{}
if err := blockL.UnmarshalSSZ(sszrspL); err != nil {
return errors.Wrap(err, "failed to unmarshal lighthouse ssz")
b := &ethpb.SignedBeaconBlock{}
if err := b.UnmarshalSSZ(blockSsz); err != nil {
return errors.Wrap(err, "failed to unmarshal ssz")
}
if err := blockP.UnmarshalSSZ(sszrspP); err != nil {
return errors.Wrap(err, "failed to unmarshal rysm ssz")
}
if len(blockP.Signature) == 0 || len(blockL.Signature) == 0 || hexutil.Encode(blockP.Signature) != hexutil.Encode(blockL.Signature) {
return errors.New("prysm signature does not match lighthouse signature")
bb := &ethpb.SignedBeaconBlock{}
if err := bb.UnmarshalSSZ(blindedBlockSsz); err != nil {
return errors.Wrap(err, "failed to unmarshal ssz")
}
} else if finalizedEpoch >= helpers.AltairE2EForkEpoch+2 && finalizedEpoch < helpers.BellatrixE2EForkEpoch {
blockP := &ethpb.SignedBeaconBlockAltair{}
blockL := &ethpb.SignedBeaconBlockAltair{}
if err := blockL.UnmarshalSSZ(sszrspL); err != nil {
return errors.Wrap(err, "lighthouse ssz error")
b := &ethpb.SignedBeaconBlockAltair{}
if err := b.UnmarshalSSZ(blockSsz); err != nil {
return errors.Wrap(err, "failed to unmarshal ssz")
}
if err := blockP.UnmarshalSSZ(sszrspP); err != nil {
return errors.Wrap(err, "prysm ssz error")
bb := &ethpb.SignedBeaconBlockAltair{}
if err := bb.UnmarshalSSZ(blindedBlockSsz); err != nil {
return errors.Wrap(err, "failed to unmarshal ssz")
}
if len(blockP.Signature) == 0 || len(blockL.Signature) == 0 || hexutil.Encode(blockP.Signature) != hexutil.Encode(blockL.Signature) {
return fmt.Errorf("prysm response %v does not match lighthouse response %v",
blockP,
blockL)
} else if finalizedEpoch >= helpers.BellatrixE2EForkEpoch && finalizedEpoch < helpers.CapellaE2EForkEpoch {
b := &ethpb.SignedBeaconBlockBellatrix{}
if err := b.UnmarshalSSZ(blockSsz); err != nil {
return errors.Wrap(err, "failed to unmarshal ssz")
}
bb := &ethpb.SignedBlindedBeaconBlockBellatrix{}
if err := bb.UnmarshalSSZ(blindedBlockSsz); err != nil {
return errors.Wrap(err, "failed to unmarshal ssz")
}
} else {
blockP := &ethpb.SignedBeaconBlockBellatrix{}
blockL := &ethpb.SignedBeaconBlockBellatrix{}
if err := blockL.UnmarshalSSZ(sszrspL); err != nil {
return errors.Wrap(err, "lighthouse ssz error")
b := &ethpb.SignedBeaconBlockCapella{}
if err := b.UnmarshalSSZ(blockSsz); err != nil {
return errors.Wrap(err, "failed to unmarshal ssz")
}
if err := blockP.UnmarshalSSZ(sszrspP); err != nil {
return errors.Wrap(err, "prysm ssz error")
bb := &ethpb.SignedBlindedBeaconBlockCapella{}
if err := bb.UnmarshalSSZ(blindedBlockSsz); err != nil {
return errors.Wrap(err, "failed to unmarshal ssz")
}
}
if len(blockP.Signature) == 0 || len(blockL.Signature) == 0 || hexutil.Encode(blockP.Signature) != hexutil.Encode(blockL.Signature) {
return fmt.Errorf("prysm response %v does not match lighthouse response %v",
blockP,
blockL)
}
}
blockheaderData := beaconPathsAndObjects["/beacon/headers/{param1}"]
prysmHeader, ok := blockheaderData.prysmResps["json"].(*beacon.GetBlockHeaderResponse)
// verify that dependent root of proposer duties matches block header
blockHeaderData := requests["/beacon/headers/{param1}"]
header, ok := blockHeaderData.prysmResps["json"].(*beacon.GetBlockHeaderResponse)
if !ok {
return errors.New("failed to cast type")
return errJsonCast
}
proposerdutiesData := beaconPathsAndObjects["/validator/duties/proposer/{param1}"]
prysmDuties, ok := proposerdutiesData.prysmResps["json"].(*validator.GetProposerDutiesResponse)
dutiesData := requests["/validator/duties/proposer/{param1}"]
duties, ok := dutiesData.prysmResps["json"].(*validator.GetProposerDutiesResponse)
if !ok {
return errors.New("failed to cast type")
return errJsonCast
}
if prysmHeader.Data.Root != prysmDuties.DependentRoot {
genesisTime, err := strconv.ParseUint(genesisData.Data.GenesisTime, 10, 64)
if err != nil {
return errors.Wrapf(err, "could not parse genesis time")
}
fmt.Printf("current slot: %v\n", slots.CurrentSlot(genesisTime))
return fmt.Errorf("header root %s does not match duties root %s ", prysmHeader.Data.Root, prysmDuties.DependentRoot)
if header.Data.Root != duties.DependentRoot {
return fmt.Errorf("header root %s does not match duties root %s ", header.Data.Root, duties.DependentRoot)
}
// get first peer returned from /node/peers and use it's ID to test the /node/peers/{peer_id} endpoint
peersData := requests["/node/peers"]
pPeers, ok := peersData.prysmResps["json"].(*node.GetPeersResponse)
if !ok {
return errJsonCast
}
pPeer := &node.GetPeerResponse{}
if err = doJSONGetRequest(v1PathTemplate, "/node/peers/"+pPeers.Data[0].PeerId, beaconNodeIdx, pPeer); err != nil {
return err
}
if pPeer.Data == nil {
return errEmptyPrysmData
}
lPeers, ok := peersData.lighthouseResps["json"].(*node.GetPeersResponse)
if !ok {
return errJsonCast
}
lPeer := &node.GetPeerResponse{}
if err = doJSONGetRequest(v1PathTemplate, "/node/peers/"+lPeers.Data[0].PeerId, beaconNodeIdx, lPeer, "lighthouse"); err != nil {
return err
}
if lPeer.Data == nil {
return errEmptyLighthouseData
}
return nil
}
func compareJSONMulticlient(beaconNodeIdx int, base string, path string, requestObj, respJSONPrysm interface{}, respJSONLighthouse interface{}, customEvaluator func(interface{}, interface{}) error) error {
func compareJSONMulticlient(
beaconNodeIdx int,
base string,
path string,
requestObj, respJSONPrysm, respJSONLighthouse interface{},
customEvaluator func(interface{}, interface{}) error,
) error {
if requestObj != nil {
if err := doMiddlewareJSONPostRequest(
if err := doJSONPostRequest(
base,
path,
beaconNodeIdx,
requestObj,
respJSONPrysm,
); err != nil {
return errors.Wrap(err, "could not perform POST request for Prysm JSON")
return errors.Wrapf(err, "could not perform Prysm JSON POST request for path %s", path)
}
if err := doMiddlewareJSONPostRequest(
if err := doJSONPostRequest(
base,
path,
beaconNodeIdx,
@@ -431,26 +811,26 @@ func compareJSONMulticlient(beaconNodeIdx int, base string, path string, request
respJSONLighthouse,
"lighthouse",
); err != nil {
return errors.Wrap(err, "could not perform POST request for Lighthouse JSON")
return errors.Wrapf(err, "could not perform Lighthouse JSON POST request for path %s", path)
}
} else {
if err := doMiddlewareJSONGetRequest(
if err := doJSONGetRequest(
base,
path,
beaconNodeIdx,
respJSONPrysm,
); err != nil {
return errors.Wrap(err, "could not perform GET request for Prysm JSON")
return errors.Wrapf(err, "could not perform Prysm JSON GET request for path %s", path)
}
if err := doMiddlewareJSONGetRequest(
if err := doJSONGetRequest(
base,
path,
beaconNodeIdx,
respJSONLighthouse,
"lighthouse",
); err != nil {
return errors.Wrap(err, "could not perform GET request for Lighthouse JSON")
return errors.Wrapf(err, "could not perform Lighthouse JSON GET request for path %s", path)
}
}
if customEvaluator != nil {
@@ -461,7 +841,7 @@ func compareJSONMulticlient(beaconNodeIdx int, base string, path string, request
}
func compareSSZMulticlient(beaconNodeIdx int, base string, path string) ([]byte, []byte, error) {
sszrspL, err := doMiddlewareSSZGetRequest(
sszrspL, err := doSSZGetRequest(
base,
path,
beaconNodeIdx,
@@ -471,7 +851,7 @@ func compareSSZMulticlient(beaconNodeIdx int, base string, path string) ([]byte,
return nil, nil, errors.Wrap(err, "could not perform GET request for Lighthouse SSZ")
}
sszrspP, err := doMiddlewareSSZGetRequest(
sszrspP, err := doSSZGetRequest(
base,
path,
beaconNodeIdx,
@@ -505,7 +885,7 @@ func compareJSONResponseObjects(prysmResp interface{}, lighthouseResp interface{
func pathFromParams(path string, params []string) string {
apiPath := path
for index := range params {
apiPath = strings.Replace(path, fmt.Sprintf("{param%d}", index+1), params[index], 1)
apiPath = strings.Replace(apiPath, fmt.Sprintf("{param%d}", index+1), params[index], 1)
}
return apiPath
}

View File

@@ -6,16 +6,18 @@ import (
"google.golang.org/grpc"
)
// BeaconAPIMultiClientVerifyIntegrity tests our API Middleware responses to other beacon nodes such as lighthouse.
// BeaconAPIMultiClientVerifyIntegrity tests Beacon API endpoints.
// It compares responses from Prysm and other beacon nodes such as Lighthouse.
// The evaluator is executed on every odd-numbered epoch.
var BeaconAPIMultiClientVerifyIntegrity = e2etypes.Evaluator{
Name: "beacon_api_multi-client_verify_integrity_epoch_%d",
Policy: policies.AfterNthEpoch(0),
Policy: policies.EveryNEpochs(1, 2),
Evaluation: beaconAPIVerify,
}
const (
v1MiddlewarePathTemplate = "http://localhost:%d/eth/v1"
v2MiddlewarePathTemplate = "http://localhost:%d/eth/v2"
v1PathTemplate = "http://localhost:%d/eth/v1"
v2PathTemplate = "http://localhost:%d/eth/v2"
)
type apiComparisonFunc func(beaconNodeIdx int) error

View File

@@ -11,17 +11,19 @@ import (
log "github.com/sirupsen/logrus"
)
func doMiddlewareJSONGetRequest(template string, requestPath string, beaconNodeIdx int, dst interface{}, bnType ...string) error {
func doJSONGetRequest(template string, requestPath string, beaconNodeIdx int, dst interface{}, bnType ...string) error {
if len(bnType) == 0 {
bnType = []string{"prysm"}
}
var port int
if len(bnType) > 0 {
switch bnType[0] {
case "lighthouse":
port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort
default:
port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort
}
} else {
switch bnType[0] {
case "prysm":
port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort
case "lighthouse":
port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort
default:
return fmt.Errorf("unknown node type %s", bnType[0])
}
basePath := fmt.Sprintf(template, port+beaconNodeIdx)
@@ -36,23 +38,25 @@ func doMiddlewareJSONGetRequest(template string, requestPath string, beaconNodeI
if err := json.NewDecoder(httpResp.Body).Decode(&body); err != nil {
return err
}
return fmt.Errorf("request failed with response code: %d with response body %s", httpResp.StatusCode, body)
return fmt.Errorf("%s request failed with response code: %d with response body %s", bnType[0], httpResp.StatusCode, body)
}
return json.NewDecoder(httpResp.Body).Decode(&dst)
}
func doMiddlewareSSZGetRequest(template string, requestPath string, beaconNodeIdx int, bnType ...string) ([]byte, error) {
func doSSZGetRequest(template string, requestPath string, beaconNodeIdx int, bnType ...string) ([]byte, error) {
if len(bnType) == 0 {
bnType = []string{"prysm"}
}
client := &http.Client{}
var port int
if len(bnType) > 0 {
switch bnType[0] {
case "lighthouse":
port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort
default:
port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort
}
} else {
switch bnType[0] {
case "prysm":
port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort
case "lighthouse":
port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort
default:
return nil, fmt.Errorf("unknown node type %s", bnType[0])
}
basePath := fmt.Sprintf(template, port+beaconNodeIdx)
@@ -71,7 +75,7 @@ func doMiddlewareSSZGetRequest(template string, requestPath string, beaconNodeId
if err := json.NewDecoder(rsp.Body).Decode(&body); err != nil {
return nil, err
}
return nil, fmt.Errorf("request failed with response code: %d with response body %s", rsp.StatusCode, body)
return nil, fmt.Errorf("%s request failed with response code: %d with response body %s", bnType[0], rsp.StatusCode, body)
}
defer closeBody(rsp.Body)
body, err := io.ReadAll(rsp.Body)
@@ -82,18 +86,21 @@ func doMiddlewareSSZGetRequest(template string, requestPath string, beaconNodeId
return body, nil
}
func doMiddlewareJSONPostRequest(template string, requestPath string, beaconNodeIdx int, postData, dst interface{}, bnType ...string) error {
var port int
if len(bnType) > 0 {
switch bnType[0] {
case "lighthouse":
port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort
default:
port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort
}
} else {
port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort
func doJSONPostRequest(template string, requestPath string, beaconNodeIdx int, postData, dst interface{}, bnType ...string) error {
if len(bnType) == 0 {
bnType = []string{"prysm"}
}
var port int
switch bnType[0] {
case "prysm":
port = params.TestParams.Ports.PrysmBeaconNodeGatewayPort
case "lighthouse":
port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort
default:
return fmt.Errorf("unknown node type %s", bnType[0])
}
basePath := fmt.Sprintf(template, port+beaconNodeIdx)
b, err := json.Marshal(postData)
if err != nil {
@@ -112,7 +119,7 @@ func doMiddlewareJSONPostRequest(template string, requestPath string, beaconNode
if err := json.NewDecoder(httpResp.Body).Decode(&body); err != nil {
return err
}
return fmt.Errorf("request failed with response code: %d with response body %s", httpResp.StatusCode, body)
return fmt.Errorf("%s request failed with response code: %d with response body %s", bnType[0], httpResp.StatusCode, body)
}
return json.NewDecoder(httpResp.Body).Decode(&dst)
}

View File

@@ -34,3 +34,10 @@ func BetweenEpochs(fromEpoch, toEpoch primitives.Epoch) func(primitives.Epoch) b
return fromEpoch < currentEpoch && currentEpoch < toEpoch
}
}
// EveryNEpochs runs every N epochs, starting with the provided epoch.
func EveryNEpochs(onwardsEpoch primitives.Epoch, n primitives.Epoch) func(epoch primitives.Epoch) bool {
return func(currentEpoch primitives.Epoch) bool {
return currentEpoch >= onwardsEpoch && ((currentEpoch-onwardsEpoch)%n == 0)
}
}