mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
Redesign of Beacon API evaluator (#13229)
* redesign * ssz * small fixes * capitalize json and ssz * rename and split files * clearer names and comments * bazel fix * one more simplification
This commit is contained in:
@@ -65,7 +65,7 @@ common_deps = [
|
||||
"//testing/endtoend/components:go_default_library",
|
||||
"//testing/endtoend/components/eth1:go_default_library",
|
||||
"//testing/endtoend/evaluators:go_default_library",
|
||||
"//testing/endtoend/evaluators/beaconapi_evaluators:go_default_library",
|
||||
"//testing/endtoend/evaluators/beaconapi:go_default_library",
|
||||
"//testing/endtoend/helpers:go_default_library",
|
||||
"//testing/endtoend/params:go_default_library",
|
||||
"//testing/endtoend/types:go_default_library",
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
ev "github.com/prysmaticlabs/prysm/v4/testing/endtoend/evaluators"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/evaluators/beaconapi_evaluators"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/evaluators/beaconapi"
|
||||
e2eParams "github.com/prysmaticlabs/prysm/v4/testing/endtoend/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/types"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
@@ -166,7 +166,7 @@ func e2eMainnet(t *testing.T, usePrysmSh, useMultiClient bool, cfg *params.Beaco
|
||||
// In the event we use the cross-client e2e option, we add in an additional
|
||||
// evaluator for multiclient runs to verify the beacon api conformance.
|
||||
if testConfig.UseValidatorCrossClient {
|
||||
testConfig.Evaluators = append(testConfig.Evaluators, beaconapi_evaluators.BeaconAPIMultiClientVerifyIntegrity)
|
||||
testConfig.Evaluators = append(testConfig.Evaluators, beaconapi.MultiClientVerifyIntegrity)
|
||||
}
|
||||
if testConfig.UseBuilder {
|
||||
testConfig.Evaluators = append(testConfig.Evaluators, ev.BuilderIsActive)
|
||||
|
||||
@@ -4,15 +4,15 @@ go_library(
|
||||
name = "go_default_library",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"beacon_api.go",
|
||||
"beacon_api_verify.go",
|
||||
"requests.go",
|
||||
"types.go",
|
||||
"util.go",
|
||||
"verify.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/testing/endtoend/evaluators/beaconapi_evaluators",
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/testing/endtoend/evaluators/beaconapi",
|
||||
visibility = ["//testing/endtoend:__subpackages__"],
|
||||
deps = [
|
||||
"//api:go_default_library",
|
||||
"//beacon-chain/rpc/apimiddleware:go_default_library",
|
||||
"//beacon-chain/rpc/eth/beacon:go_default_library",
|
||||
"//beacon-chain/rpc/eth/config:go_default_library",
|
||||
"//beacon-chain/rpc/eth/debug:go_default_library",
|
||||
294
testing/endtoend/evaluators/beaconapi/requests.go
Normal file
294
testing/endtoend/evaluators/beaconapi/requests.go
Normal file
@@ -0,0 +1,294 @@
|
||||
package beaconapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/config"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/debug"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/node"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/validator"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/helpers"
|
||||
)
|
||||
|
||||
var requests = map[string]endpoint{
|
||||
"/beacon/genesis": newMetadata[beacon.GetGenesisResponse](v1PathTemplate),
|
||||
"/beacon/states/{param1}/root": newMetadata[beacon.GetStateRootResponse](v1PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
})),
|
||||
"/beacon/states/{param1}/fork": newMetadata[beacon.GetStateForkResponse](v1PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"finalized"}
|
||||
})),
|
||||
"/beacon/states/{param1}/finality_checkpoints": newMetadata[beacon.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[beacon.GetValidatorsResponse](v1PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
})),
|
||||
"/beacon/states/{param1}/validators/{param2}": newMetadata[beacon.GetValidatorResponse](v1PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head", "0"}
|
||||
})),
|
||||
"/beacon/states/{param1}/validator_balances?id=0,1": newMetadata[beacon.GetValidatorBalancesResponse](v1PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
})),
|
||||
"/beacon/states/{param1}/committees?index=0": newMetadata[beacon.GetCommitteesResponse](v1PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
})),
|
||||
"/beacon/states/{param1}/sync_committees": newMetadata[beacon.GetSyncCommitteeResponse](v1PathTemplate,
|
||||
withStart(helpers.AltairE2EForkEpoch),
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
})),
|
||||
"/beacon/states/{param1}/randao": newMetadata[beacon.GetRandaoResponse](v1PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
})),
|
||||
"/beacon/headers": newMetadata[beacon.GetBlockHeadersResponse](v1PathTemplate),
|
||||
"/beacon/headers/{param1}": newMetadata[beacon.GetBlockHeaderResponse](v1PathTemplate,
|
||||
withParams(func(e primitives.Epoch) []string {
|
||||
slot := uint64(0)
|
||||
if e > 0 {
|
||||
slot = (uint64(e) * uint64(params.BeaconConfig().SlotsPerEpoch)) - 1
|
||||
}
|
||||
return []string{fmt.Sprintf("%v", slot)}
|
||||
})),
|
||||
"/beacon/blocks/{param1}": newMetadata[beacon.GetBlockV2Response](v2PathTemplate,
|
||||
withSsz(),
|
||||
withParams(func(e primitives.Epoch) []string {
|
||||
if e < 4 {
|
||||
return []string{"head"}
|
||||
}
|
||||
return []string{"finalized"}
|
||||
})),
|
||||
"/beacon/blocks/{param1}/root": newMetadata[beacon.BlockRootResponse](v1PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
})),
|
||||
"/beacon/blocks/{param1}/attestations": newMetadata[beacon.GetBlockAttestationsResponse](v1PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
})),
|
||||
"/beacon/blinded_blocks/{param1}": newMetadata[beacon.GetBlockV2Response](v1PathTemplate,
|
||||
withSsz(),
|
||||
withParams(func(e primitives.Epoch) []string {
|
||||
if e < 4 {
|
||||
return []string{"head"}
|
||||
}
|
||||
return []string{"finalized"}
|
||||
})),
|
||||
"/beacon/pool/attestations": newMetadata[beacon.ListAttestationsResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, _ interface{}) error {
|
||||
pResp, ok := p.(*beacon.ListAttestationsResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &beacon.ListAttestationsResponse{}, p)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
"/beacon/pool/attester_slashings": newMetadata[beacon.GetAttesterSlashingsResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, _ interface{}) error {
|
||||
pResp, ok := p.(*beacon.GetAttesterSlashingsResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &beacon.GetAttesterSlashingsResponse{}, p)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
"/beacon/pool/proposer_slashings": newMetadata[beacon.GetProposerSlashingsResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, _ interface{}) error {
|
||||
pResp, ok := p.(*beacon.GetProposerSlashingsResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &beacon.GetProposerSlashingsResponse{}, p)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
"/beacon/pool/voluntary_exits": newMetadata[beacon.ListVoluntaryExitsResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, _ interface{}) error {
|
||||
pResp, ok := p.(*beacon.ListVoluntaryExitsResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &beacon.ListVoluntaryExitsResponse{}, p)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
"/beacon/pool/bls_to_execution_changes": newMetadata[beacon.BLSToExecutionChangesPoolResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, _ interface{}) error {
|
||||
pResp, ok := p.(*beacon.BLSToExecutionChangesPoolResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &beacon.BLSToExecutionChangesPoolResponse{}, p)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
"/config/fork_schedule": newMetadata[config.GetForkScheduleResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, lh interface{}) error {
|
||||
pResp, ok := p.(*config.GetForkScheduleResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &config.GetForkScheduleResponse{}, p)
|
||||
}
|
||||
lhResp, ok := lh.(*config.GetForkScheduleResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &config.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) {
|
||||
pResp.Data = append(pResp.Data[:i], pResp.Data[i+1:]...)
|
||||
}
|
||||
}
|
||||
for i := len(lhResp.Data) - 1; i >= 0; i-- {
|
||||
if lhResp.Data[i].Epoch == fmt.Sprintf("%d", params.BeaconConfig().FarFutureEpoch) {
|
||||
lhResp.Data = append(lhResp.Data[:i], lhResp.Data[i+1:]...)
|
||||
}
|
||||
}
|
||||
return compareJSON(pResp, lhResp)
|
||||
})),
|
||||
"/config/deposit_contract": newMetadata[config.GetDepositContractResponse](v1PathTemplate),
|
||||
"/debug/beacon/states/{param1}": newMetadata[debug.GetBeaconStateV2Response](v2PathTemplate,
|
||||
withParams(func(_ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
})),
|
||||
"/debug/beacon/heads": newMetadata[debug.GetForkChoiceHeadsV2Response](v2PathTemplate),
|
||||
"/node/identity": newMetadata[node.GetIdentityResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, _ interface{}) error {
|
||||
pResp, ok := p.(*node.GetIdentityResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &node.GetIdentityResponse{}, p)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
"/node/peers": newMetadata[node.GetPeersResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, _ interface{}) error {
|
||||
pResp, ok := p.(*node.GetPeersResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &node.GetPeersResponse{}, p)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
"/node/peer_count": newMetadata[node.GetPeerCountResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, _ interface{}) error {
|
||||
pResp, ok := p.(*node.GetPeerCountResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &node.GetPeerCountResponse{}, p)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
"/node/version": newMetadata[node.GetVersionResponse](v1PathTemplate,
|
||||
withCustomEval(func(p interface{}, lh interface{}) error {
|
||||
pResp, ok := p.(*node.GetVersionResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &beacon.ListAttestationsResponse{}, p)
|
||||
}
|
||||
lhResp, ok := lh.(*node.GetVersionResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &beacon.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[node.SyncStatusResponse](v1PathTemplate),
|
||||
"/validator/duties/proposer/{param1}": newMetadata[validator.GetProposerDutiesResponse](v1PathTemplate,
|
||||
withParams(func(e primitives.Epoch) []string {
|
||||
return []string{fmt.Sprintf("%v", e)}
|
||||
}),
|
||||
withCustomEval(func(p interface{}, lh interface{}) error {
|
||||
pResp, ok := p.(*validator.GetProposerDutiesResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &validator.GetProposerDutiesResponse{}, p)
|
||||
}
|
||||
lhResp, ok := lh.(*validator.GetProposerDutiesResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &validator.GetProposerDutiesResponse{}, lh)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
if lhResp.Data == nil {
|
||||
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
|
||||
lhResp.Data = lhResp.Data[1:]
|
||||
}
|
||||
return compareJSON(pResp, lhResp)
|
||||
})),
|
||||
"/validator/duties/attester/{param1}": newMetadata[validator.GetAttesterDutiesResponse](v1PathTemplate,
|
||||
withParams(func(e primitives.Epoch) []string {
|
||||
//ask for a future epoch to test this case
|
||||
return []string{fmt.Sprintf("%v", e+1)}
|
||||
}),
|
||||
withReq(func() []string {
|
||||
validatorIndices := make([]string, 64)
|
||||
for key := range validatorIndices {
|
||||
validatorIndices[key] = fmt.Sprintf("%d", key)
|
||||
}
|
||||
return validatorIndices
|
||||
}()),
|
||||
withCustomEval(func(p interface{}, lh interface{}) error {
|
||||
pResp, ok := p.(*validator.GetAttesterDutiesResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &validator.GetAttesterDutiesResponse{}, p)
|
||||
}
|
||||
lhResp, ok := lh.(*validator.GetAttesterDutiesResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &validator.GetAttesterDutiesResponse{}, lh)
|
||||
}
|
||||
if pResp.Data == nil {
|
||||
return errEmptyPrysmData
|
||||
}
|
||||
if lhResp.Data == nil {
|
||||
return errEmptyLighthouseData
|
||||
}
|
||||
return compareJSON(pResp, lhResp)
|
||||
})),
|
||||
}
|
||||
140
testing/endtoend/evaluators/beaconapi/types.go
Normal file
140
testing/endtoend/evaluators/beaconapi/types.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package beaconapi
|
||||
|
||||
import "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
|
||||
type endpoint interface {
|
||||
getBasePath() string
|
||||
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{})
|
||||
getPResp() interface{} // retrieves the Prysm JSON response
|
||||
getLHResp() interface{} // retrieves the Lighthouse JSON response
|
||||
getParams(epoch primitives.Epoch) []string
|
||||
setParams(f func(primitives.Epoch) []string)
|
||||
getCustomEval() func(interface{}, interface{}) error
|
||||
setCustomEval(f func(interface{}, interface{}) error)
|
||||
}
|
||||
|
||||
type apiEndpoint[Resp any] struct {
|
||||
basePath string
|
||||
ssz bool
|
||||
start primitives.Epoch
|
||||
req interface{}
|
||||
pResp *Resp // Prysm JSON response
|
||||
lhResp *Resp // Lighthouse JSON response
|
||||
sszResp []byte // Prysm SSZ response
|
||||
params func(currentEpoch primitives.Epoch) []string
|
||||
customEval func(interface{}, interface{}) error
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) getBasePath() string {
|
||||
return e.basePath
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) sszEnabled() bool {
|
||||
return e.ssz
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) enableSsz() {
|
||||
e.ssz = true
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) getSszResp() []byte {
|
||||
return e.sszResp
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) setSszResp(resp []byte) {
|
||||
e.sszResp = resp
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) getStart() primitives.Epoch {
|
||||
return e.start
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) setStart(start primitives.Epoch) {
|
||||
e.start = start
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) getReq() interface{} {
|
||||
return e.req
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) setReq(req interface{}) {
|
||||
e.req = req
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) getPResp() interface{} {
|
||||
return e.pResp
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) getLHResp() interface{} {
|
||||
return e.lhResp
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) getParams(epoch primitives.Epoch) []string {
|
||||
if e.params == nil {
|
||||
return nil
|
||||
}
|
||||
return e.params(epoch)
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) setParams(f func(currentEpoch primitives.Epoch) []string) {
|
||||
e.params = f
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) getCustomEval() func(interface{}, interface{}) error {
|
||||
return e.customEval
|
||||
}
|
||||
|
||||
func (e *apiEndpoint[Resp]) setCustomEval(f func(interface{}, interface{}) error) {
|
||||
e.customEval = f
|
||||
}
|
||||
|
||||
func newMetadata[Resp any](basePath string, opts ...endpointOpt) *apiEndpoint[Resp] {
|
||||
m := &apiEndpoint[Resp]{
|
||||
basePath: basePath,
|
||||
pResp: new(Resp),
|
||||
lhResp: new(Resp),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(m)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
type endpointOpt func(endpoint)
|
||||
|
||||
func withSsz() endpointOpt {
|
||||
return func(e endpoint) {
|
||||
e.enableSsz()
|
||||
}
|
||||
}
|
||||
|
||||
func withStart(start primitives.Epoch) endpointOpt {
|
||||
return func(e endpoint) {
|
||||
e.setStart(start)
|
||||
}
|
||||
}
|
||||
|
||||
func withReq(req interface{}) endpointOpt {
|
||||
return func(e endpoint) {
|
||||
e.setReq(req)
|
||||
}
|
||||
}
|
||||
|
||||
func withParams(f func(currentEpoch primitives.Epoch) []string) endpointOpt {
|
||||
return func(e endpoint) {
|
||||
e.setParams(f)
|
||||
}
|
||||
}
|
||||
|
||||
func withCustomEval(f func(interface{}, interface{}) error) endpointOpt {
|
||||
return func(e endpoint) {
|
||||
e.setCustomEval(f)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package beaconapi_evaluators
|
||||
package beaconapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -7,12 +7,26 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/api"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/params"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func doJSONGetRequest(template string, requestPath string, beaconNodeIdx int, dst interface{}, bnType ...string) error {
|
||||
var (
|
||||
errSszCast = errors.New("SSZ response is not a byte array")
|
||||
errEmptyPrysmData = errors.New("Prysm data is empty")
|
||||
errEmptyLighthouseData = errors.New("Lighthouse data is empty")
|
||||
)
|
||||
|
||||
const (
|
||||
msgWrongJson = "JSON response has wrong structure, expected %T, got %T"
|
||||
msgRequestFailed = "%s request failed with response code %d with response body %s"
|
||||
msgUnknownNode = "unknown node type %s"
|
||||
msgSSZUnmarshalFailed = "failed to unmarshal SSZ"
|
||||
)
|
||||
|
||||
func doJSONGetRequest(template string, requestPath string, beaconNodeIdx int, resp interface{}, bnType ...string) error {
|
||||
if len(bnType) == 0 {
|
||||
bnType = []string{"prysm"}
|
||||
}
|
||||
@@ -24,7 +38,7 @@ func doJSONGetRequest(template string, requestPath string, beaconNodeIdx int, ds
|
||||
case "lighthouse":
|
||||
port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort
|
||||
default:
|
||||
return fmt.Errorf("unknown node type %s", bnType[0])
|
||||
return fmt.Errorf(msgUnknownNode, bnType[0])
|
||||
}
|
||||
|
||||
basePath := fmt.Sprintf(template, port+beaconNodeIdx)
|
||||
@@ -47,9 +61,9 @@ func doJSONGetRequest(template string, requestPath string, beaconNodeIdx int, ds
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s request failed with response code %d with response body %s", bnType[0], httpResp.StatusCode, body)
|
||||
return fmt.Errorf(msgRequestFailed, bnType[0], httpResp.StatusCode, body)
|
||||
}
|
||||
return json.NewDecoder(httpResp.Body).Decode(&dst)
|
||||
return json.NewDecoder(httpResp.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
func doSSZGetRequest(template string, requestPath string, beaconNodeIdx int, bnType ...string) ([]byte, error) {
|
||||
@@ -65,7 +79,7 @@ func doSSZGetRequest(template string, requestPath string, beaconNodeIdx int, bnT
|
||||
case "lighthouse":
|
||||
port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown node type %s", bnType[0])
|
||||
return nil, fmt.Errorf(msgUnknownNode, bnType[0])
|
||||
}
|
||||
|
||||
basePath := fmt.Sprintf(template, port+beaconNodeIdx)
|
||||
@@ -84,7 +98,7 @@ func doSSZGetRequest(template string, requestPath string, beaconNodeIdx int, bnT
|
||||
if err := json.NewDecoder(rsp.Body).Decode(&body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("%s request failed with response code: %d with response body %s", bnType[0], rsp.StatusCode, body)
|
||||
return nil, fmt.Errorf(msgRequestFailed, bnType[0], rsp.StatusCode, body)
|
||||
}
|
||||
defer closeBody(rsp.Body)
|
||||
body, err := io.ReadAll(rsp.Body)
|
||||
@@ -95,7 +109,7 @@ func doSSZGetRequest(template string, requestPath string, beaconNodeIdx int, bnT
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func doJSONPostRequest(template string, requestPath string, beaconNodeIdx int, postData, dst interface{}, bnType ...string) error {
|
||||
func doJSONPostRequest(template string, requestPath string, beaconNodeIdx int, postData, resp interface{}, bnType ...string) error {
|
||||
if len(bnType) == 0 {
|
||||
bnType = []string{"prysm"}
|
||||
}
|
||||
@@ -107,7 +121,7 @@ func doJSONPostRequest(template string, requestPath string, beaconNodeIdx int, p
|
||||
case "lighthouse":
|
||||
port = params.TestParams.Ports.LighthouseBeaconNodeHTTPPort
|
||||
default:
|
||||
return fmt.Errorf("unknown node type %s", bnType[0])
|
||||
return fmt.Errorf(msgUnknownNode, bnType[0])
|
||||
}
|
||||
|
||||
basePath := fmt.Sprintf(template, port+beaconNodeIdx)
|
||||
@@ -136,9 +150,9 @@ func doJSONPostRequest(template string, requestPath string, beaconNodeIdx int, p
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s request failed with response code %d with response body %s", bnType[0], httpResp.StatusCode, body)
|
||||
return fmt.Errorf(msgRequestFailed, bnType[0], httpResp.StatusCode, body)
|
||||
}
|
||||
return json.NewDecoder(httpResp.Body).Decode(&dst)
|
||||
return json.NewDecoder(httpResp.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
func closeBody(body io.Closer) {
|
||||
224
testing/endtoend/evaluators/beaconapi/verify.go
Normal file
224
testing/endtoend/evaluators/beaconapi/verify.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package beaconapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/validator"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/policies"
|
||||
e2etypes "github.com/prysmaticlabs/prysm/v4/testing/endtoend/types"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// MultiClientVerifyIntegrity 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 MultiClientVerifyIntegrity = e2etypes.Evaluator{
|
||||
Name: "beacon_api_multi-client_verify_integrity_epoch_%d",
|
||||
Policy: policies.EveryNEpochs(1, 2),
|
||||
Evaluation: verify,
|
||||
}
|
||||
|
||||
const (
|
||||
v1PathTemplate = "http://localhost:%d/eth/v1"
|
||||
v2PathTemplate = "http://localhost:%d/eth/v2"
|
||||
)
|
||||
|
||||
func verify(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error {
|
||||
for beaconNodeIdx := range conns {
|
||||
if err := run(beaconNodeIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(nodeIdx int) error {
|
||||
genesisResp := &beacon.GetGenesisResponse{}
|
||||
if err := doJSONGetRequest(v1PathTemplate, "/beacon/genesis", nodeIdx, genesisResp); err != nil {
|
||||
return errors.Wrap(err, "error getting genesis data")
|
||||
}
|
||||
genesisTime, err := strconv.ParseInt(genesisResp.Data.GenesisTime, 10, 64)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse genesis time")
|
||||
}
|
||||
currentEpoch := slots.EpochsSinceGenesis(time.Unix(genesisTime, 0))
|
||||
|
||||
for path, m := range requests {
|
||||
if currentEpoch < m.getStart() {
|
||||
continue
|
||||
}
|
||||
apiPath := path
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
m.setSszResp(b)
|
||||
}
|
||||
}
|
||||
|
||||
return postEvaluation(requests)
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
// verify that block SSZ responses have the correct structure
|
||||
forkData := requests["/beacon/states/{param1}/fork"]
|
||||
fork, ok := forkData.getPResp().(*beacon.GetStateForkResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &beacon.GetStateForkResponse{}, forkData.getPResp())
|
||||
}
|
||||
finalizedEpoch, err := strconv.ParseUint(fork.Data.Epoch, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockData := requests["/beacon/blocks/{param1}"]
|
||||
blindedBlockData := requests["/beacon/blinded_blocks/{param1}"]
|
||||
if !ok {
|
||||
return errSszCast
|
||||
}
|
||||
if finalizedEpoch < helpers.AltairE2EForkEpoch+2 {
|
||||
b := ðpb.SignedBeaconBlock{}
|
||||
if err := b.UnmarshalSSZ(blockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
bb := ðpb.SignedBeaconBlock{}
|
||||
if err := bb.UnmarshalSSZ(blindedBlockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
} else if finalizedEpoch >= helpers.AltairE2EForkEpoch+2 && finalizedEpoch < helpers.BellatrixE2EForkEpoch {
|
||||
b := ðpb.SignedBeaconBlockAltair{}
|
||||
if err := b.UnmarshalSSZ(blockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
bb := ðpb.SignedBeaconBlockAltair{}
|
||||
if err := bb.UnmarshalSSZ(blindedBlockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
} else if finalizedEpoch >= helpers.BellatrixE2EForkEpoch && finalizedEpoch < helpers.CapellaE2EForkEpoch {
|
||||
b := ðpb.SignedBeaconBlockBellatrix{}
|
||||
if err := b.UnmarshalSSZ(blockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
bb := ðpb.SignedBlindedBeaconBlockBellatrix{}
|
||||
if err := bb.UnmarshalSSZ(blindedBlockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
} else if finalizedEpoch >= helpers.CapellaE2EForkEpoch && finalizedEpoch < helpers.DenebE2EForkEpoch {
|
||||
b := ðpb.SignedBeaconBlockCapella{}
|
||||
if err := b.UnmarshalSSZ(blockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
bb := ðpb.SignedBlindedBeaconBlockCapella{}
|
||||
if err := bb.UnmarshalSSZ(blindedBlockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
} else {
|
||||
b := ðpb.SignedBeaconBlockDeneb{}
|
||||
if err := b.UnmarshalSSZ(blockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
bb := ðpb.SignedBlindedBeaconBlockDeneb{}
|
||||
if err := bb.UnmarshalSSZ(blindedBlockData.getSszResp()); err != nil {
|
||||
return errors.Wrap(err, msgSSZUnmarshalFailed)
|
||||
}
|
||||
}
|
||||
|
||||
// verify that dependent root of proposer duties matches block header
|
||||
blockHeaderData := requests["/beacon/headers/{param1}"]
|
||||
header, ok := blockHeaderData.getPResp().(*beacon.GetBlockHeaderResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &beacon.GetBlockHeaderResponse{}, blockHeaderData.getPResp())
|
||||
}
|
||||
dutiesData := requests["/validator/duties/proposer/{param1}"]
|
||||
duties, ok := dutiesData.getPResp().(*validator.GetProposerDutiesResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf(msgWrongJson, &validator.GetProposerDutiesResponse{}, dutiesData.getPResp())
|
||||
}
|
||||
if header.Data.Root != duties.DependentRoot {
|
||||
return fmt.Errorf("header root %s does not match duties root %s ", header.Data.Root, duties.DependentRoot)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
if customEval != nil {
|
||||
return customEval(pResp, lhResp)
|
||||
} else {
|
||||
return compareJSON(pResp, lhResp)
|
||||
}
|
||||
}
|
||||
|
||||
func compareSSZMultiClient(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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
if !bytes.Equal(pResp, lhResp) {
|
||||
return nil, errors.New("Prysm SSZ response does not match Lighthouse SSZ response")
|
||||
}
|
||||
return pResp, nil
|
||||
}
|
||||
|
||||
func compareJSON(pResp interface{}, lhResp interface{}) error {
|
||||
if !reflect.DeepEqual(pResp, lhResp) {
|
||||
p, err := json.Marshal(pResp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal Prysm response to JSON")
|
||||
}
|
||||
lh, err := json.Marshal(lhResp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal Lighthouse response to JSON")
|
||||
}
|
||||
return fmt.Errorf("Prysm response %s does not match Lighthouse response %s", string(p), string(lh))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pathFromParams(path string, params []string) string {
|
||||
apiPath := path
|
||||
for i := range params {
|
||||
apiPath = strings.Replace(apiPath, fmt.Sprintf("{param%d}", i+1), params[i], 1)
|
||||
}
|
||||
return apiPath
|
||||
}
|
||||
@@ -1,876 +0,0 @@
|
||||
package beaconapi_evaluators
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/config"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/debug"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/node"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/validator"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/helpers"
|
||||
"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{}
|
||||
prysmResps map[string]interface{}
|
||||
lighthouseResps map[string]interface{}
|
||||
customEvaluation func(interface{}, interface{}) error
|
||||
}
|
||||
|
||||
var requests = map[string]metadata{
|
||||
"/beacon/genesis": {
|
||||
basepath: v1PathTemplate,
|
||||
prysmResps: map[string]interface{}{
|
||||
"json": &beacon.GetGenesisResponse{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &beacon.GetGenesisResponse{},
|
||||
},
|
||||
},
|
||||
"/beacon/states/{param1}/root": {
|
||||
basepath: v1PathTemplate,
|
||||
params: func(_ string, _ primitives.Epoch) []string {
|
||||
return []string{"head"}
|
||||
},
|
||||
prysmResps: map[string]interface{}{
|
||||
"json": &beacon.GetStateRootResponse{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &beacon.GetStateRootResponse{},
|
||||
},
|
||||
},
|
||||
"/beacon/states/{param1}/fork": {
|
||||
basepath: v1PathTemplate,
|
||||
params: func(_ string, _ primitives.Epoch) []string {
|
||||
return []string{"finalized"}
|
||||
},
|
||||
prysmResps: map[string]interface{}{
|
||||
"json": &beacon.GetStateForkResponse{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &beacon.GetStateForkResponse{},
|
||||
},
|
||||
},
|
||||
"/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": &beacon.GetAttesterSlashingsResponse{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &beacon.GetAttesterSlashingsResponse{},
|
||||
},
|
||||
customEvaluation: func(p interface{}, l interface{}) error {
|
||||
pResp, ok := p.(*beacon.GetAttesterSlashingsResponse)
|
||||
if !ok {
|
||||
return errJsonCast
|
||||
}
|
||||
lResp, ok := l.(*beacon.GetAttesterSlashingsResponse)
|
||||
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": &beacon.GetProposerSlashingsResponse{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &beacon.GetProposerSlashingsResponse{},
|
||||
},
|
||||
customEvaluation: func(p interface{}, l interface{}) error {
|
||||
pResp, ok := p.(*beacon.GetProposerSlashingsResponse)
|
||||
if !ok {
|
||||
return errJsonCast
|
||||
}
|
||||
lResp, ok := l.(*beacon.GetProposerSlashingsResponse)
|
||||
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": &config.GetForkScheduleResponse{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &config.GetForkScheduleResponse{},
|
||||
},
|
||||
customEvaluation: func(p interface{}, l interface{}) error {
|
||||
// remove all forks with far-future epoch
|
||||
pSchedule, ok := p.(*config.GetForkScheduleResponse)
|
||||
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.(*config.GetForkScheduleResponse)
|
||||
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{}{
|
||||
"json": &debug.GetBeaconStateV2Response{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &debug.GetBeaconStateV2Response{},
|
||||
},
|
||||
},
|
||||
"/debug/beacon/heads": {
|
||||
basepath: v2PathTemplate,
|
||||
prysmResps: map[string]interface{}{
|
||||
"json": &debug.GetForkChoiceHeadsV2Response{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &debug.GetForkChoiceHeadsV2Response{},
|
||||
},
|
||||
},
|
||||
"/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: v1PathTemplate,
|
||||
params: func(_ string, e primitives.Epoch) []string {
|
||||
return []string{fmt.Sprintf("%v", e)}
|
||||
},
|
||||
prysmResps: map[string]interface{}{
|
||||
"json": &validator.GetProposerDutiesResponse{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &validator.GetProposerDutiesResponse{},
|
||||
},
|
||||
customEvaluation: func(prysmResp interface{}, lhouseResp interface{}) error {
|
||||
castedl, ok := lhouseResp.(*validator.GetProposerDutiesResponse)
|
||||
if !ok {
|
||||
return errors.New("failed to cast type")
|
||||
}
|
||||
if castedl.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
|
||||
castedl.Data = castedl.Data[1:]
|
||||
}
|
||||
return compareJSONResponseObjects(prysmResp, castedl)
|
||||
},
|
||||
},
|
||||
"/validator/duties/attester/{param1}": {
|
||||
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)}
|
||||
},
|
||||
requestObject: func() []string {
|
||||
validatorIndices := make([]string, 64)
|
||||
for key := range validatorIndices {
|
||||
validatorIndices[key] = fmt.Sprintf("%d", key)
|
||||
}
|
||||
return validatorIndices
|
||||
}(),
|
||||
prysmResps: map[string]interface{}{
|
||||
"json": &validator.GetAttesterDutiesResponse{},
|
||||
},
|
||||
lighthouseResps: map[string]interface{}{
|
||||
"json": &validator.GetAttesterDutiesResponse{},
|
||||
},
|
||||
customEvaluation: func(prysmResp interface{}, lhouseResp interface{}) error {
|
||||
castedp, ok := lhouseResp.(*validator.GetAttesterDutiesResponse)
|
||||
if !ok {
|
||||
return errors.New("failed to cast type")
|
||||
}
|
||||
castedl, ok := lhouseResp.(*validator.GetAttesterDutiesResponse)
|
||||
if !ok {
|
||||
return errors.New("failed to cast type")
|
||||
}
|
||||
if len(castedp.Data) == 0 ||
|
||||
len(castedl.Data) == 0 ||
|
||||
len(castedp.Data) != len(castedl.Data) {
|
||||
return fmt.Errorf("attester data does not match, prysm: %d lighthouse: %d", len(castedp.Data), len(castedl.Data))
|
||||
}
|
||||
return compareJSONResponseObjects(prysmResp, castedl)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func withCompareBeaconAPIs(beaconNodeIdx int) error {
|
||||
genesisResp := &beacon.GetGenesisResponse{}
|
||||
err := doJSONGetRequest(
|
||||
v1PathTemplate,
|
||||
"/beacon/genesis",
|
||||
beaconNodeIdx,
|
||||
genesisResp,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting genesis data")
|
||||
}
|
||||
genesisTime, err := strconv.ParseInt(genesisResp.Data.GenesisTime, 10, 64)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse genesis time")
|
||||
}
|
||||
currentEpoch := slots.EpochsSinceGenesis(time.Unix(genesisTime, 0))
|
||||
|
||||
for path, meta := range requests {
|
||||
if currentEpoch < meta.start {
|
||||
continue
|
||||
}
|
||||
for key := range meta.prysmResps {
|
||||
switch key {
|
||||
case "json":
|
||||
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,
|
||||
requests[path].prysmResps[key],
|
||||
requests[path].lighthouseResps[key],
|
||||
meta.customEvaluation,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
case "ssz":
|
||||
apipath := path
|
||||
if meta.params != nil {
|
||||
sszparams := meta.params("ssz", currentEpoch)
|
||||
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
|
||||
}
|
||||
requests[path].prysmResps[key] = prysmr
|
||||
requests[path].lighthouseResps[key] = lighthouser
|
||||
default:
|
||||
return fmt.Errorf("unknown encoding type %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return postEvaluation(beaconNodeIdx, requests)
|
||||
}
|
||||
|
||||
// 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 errJsonCast
|
||||
}
|
||||
finalizedEpoch, err := strconv.ParseUint(fork.Data.Epoch, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockData := requests["/beacon/blocks/{param1}"]
|
||||
blockSsz, ok := blockData.prysmResps["ssz"].([]byte)
|
||||
if !ok {
|
||||
return errSszCast
|
||||
}
|
||||
blindedBlockData := requests["/beacon/blinded_blocks/{param1}"]
|
||||
blindedBlockSsz, ok := blindedBlockData.prysmResps["ssz"].([]byte)
|
||||
if !ok {
|
||||
return errSszCast
|
||||
}
|
||||
if finalizedEpoch < helpers.AltairE2EForkEpoch+2 {
|
||||
b := ðpb.SignedBeaconBlock{}
|
||||
if err := b.UnmarshalSSZ(blockSsz); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal ssz")
|
||||
}
|
||||
bb := ðpb.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 {
|
||||
b := ðpb.SignedBeaconBlockAltair{}
|
||||
if err := b.UnmarshalSSZ(blockSsz); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal ssz")
|
||||
}
|
||||
bb := ðpb.SignedBeaconBlockAltair{}
|
||||
if err := bb.UnmarshalSSZ(blindedBlockSsz); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal ssz")
|
||||
}
|
||||
} else if finalizedEpoch >= helpers.BellatrixE2EForkEpoch && finalizedEpoch < helpers.CapellaE2EForkEpoch {
|
||||
b := ðpb.SignedBeaconBlockBellatrix{}
|
||||
if err := b.UnmarshalSSZ(blockSsz); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal ssz")
|
||||
}
|
||||
bb := ðpb.SignedBlindedBeaconBlockBellatrix{}
|
||||
if err := bb.UnmarshalSSZ(blindedBlockSsz); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal ssz")
|
||||
}
|
||||
} else if finalizedEpoch >= helpers.CapellaE2EForkEpoch && finalizedEpoch < helpers.DenebE2EForkEpoch {
|
||||
b := ðpb.SignedBeaconBlockCapella{}
|
||||
if err := b.UnmarshalSSZ(blockSsz); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal ssz")
|
||||
}
|
||||
bb := ðpb.SignedBlindedBeaconBlockCapella{}
|
||||
if err := bb.UnmarshalSSZ(blindedBlockSsz); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal ssz")
|
||||
}
|
||||
} else {
|
||||
b := ðpb.SignedBeaconBlockDeneb{}
|
||||
if err := b.UnmarshalSSZ(blockSsz); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal ssz")
|
||||
}
|
||||
bb := ðpb.SignedBlindedBeaconBlockDeneb{}
|
||||
if err := bb.UnmarshalSSZ(blindedBlockSsz); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal ssz")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 errJsonCast
|
||||
}
|
||||
dutiesData := requests["/validator/duties/proposer/{param1}"]
|
||||
duties, ok := dutiesData.prysmResps["json"].(*validator.GetProposerDutiesResponse)
|
||||
if !ok {
|
||||
return errJsonCast
|
||||
}
|
||||
if header.Data.Root != duties.DependentRoot {
|
||||
return fmt.Errorf("header root %s does not match duties root %s ", header.Data.Root, duties.DependentRoot)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareJSONMulticlient(
|
||||
beaconNodeIdx int,
|
||||
base string,
|
||||
path string,
|
||||
requestObj, respJSONPrysm, respJSONLighthouse interface{},
|
||||
customEvaluator func(interface{}, interface{}) error,
|
||||
) error {
|
||||
if requestObj != nil {
|
||||
if err := doJSONPostRequest(
|
||||
base,
|
||||
path,
|
||||
beaconNodeIdx,
|
||||
requestObj,
|
||||
respJSONPrysm,
|
||||
); err != nil {
|
||||
return errors.Wrapf(err, "could not perform Prysm JSON POST request for path %s", path)
|
||||
}
|
||||
|
||||
if err := doJSONPostRequest(
|
||||
base,
|
||||
path,
|
||||
beaconNodeIdx,
|
||||
requestObj,
|
||||
respJSONLighthouse,
|
||||
"lighthouse",
|
||||
); err != nil {
|
||||
return errors.Wrapf(err, "could not perform Lighthouse JSON POST request for path %s", path)
|
||||
}
|
||||
} else {
|
||||
if err := doJSONGetRequest(
|
||||
base,
|
||||
path,
|
||||
beaconNodeIdx,
|
||||
respJSONPrysm,
|
||||
); err != nil {
|
||||
return errors.Wrapf(err, "could not perform Prysm JSON GET request for path %s", path)
|
||||
}
|
||||
|
||||
if err := doJSONGetRequest(
|
||||
base,
|
||||
path,
|
||||
beaconNodeIdx,
|
||||
respJSONLighthouse,
|
||||
"lighthouse",
|
||||
); err != nil {
|
||||
return errors.Wrapf(err, "could not perform Lighthouse JSON GET request for path %s", path)
|
||||
}
|
||||
}
|
||||
if customEvaluator != nil {
|
||||
return customEvaluator(respJSONPrysm, respJSONLighthouse)
|
||||
} else {
|
||||
return compareJSONResponseObjects(respJSONPrysm, respJSONLighthouse)
|
||||
}
|
||||
}
|
||||
|
||||
func compareSSZMulticlient(beaconNodeIdx int, base string, path string) ([]byte, []byte, error) {
|
||||
sszrspL, err := doSSZGetRequest(
|
||||
base,
|
||||
path,
|
||||
beaconNodeIdx,
|
||||
"lighthouse",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not perform GET request for Lighthouse SSZ")
|
||||
}
|
||||
|
||||
sszrspP, err := doSSZGetRequest(
|
||||
base,
|
||||
path,
|
||||
beaconNodeIdx,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not perform GET request for Prysm SSZ")
|
||||
}
|
||||
if !bytes.Equal(sszrspL, sszrspP) {
|
||||
return nil, nil, errors.New("prysm ssz response does not match lighthouse ssz response")
|
||||
}
|
||||
return sszrspP, sszrspL, nil
|
||||
}
|
||||
|
||||
func compareJSONResponseObjects(prysmResp interface{}, lighthouseResp interface{}) error {
|
||||
if !reflect.DeepEqual(prysmResp, lighthouseResp) {
|
||||
p, err := json.Marshal(prysmResp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal Prysm response to JSON")
|
||||
}
|
||||
l, err := json.Marshal(lighthouseResp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal Lighthouse response to JSON")
|
||||
}
|
||||
return fmt.Errorf("prysm response %s does not match lighthouse response %s",
|
||||
string(p),
|
||||
string(l))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pathFromParams(path string, params []string) string {
|
||||
apiPath := path
|
||||
for index := range params {
|
||||
apiPath = strings.Replace(apiPath, fmt.Sprintf("{param%d}", index+1), params[index], 1)
|
||||
}
|
||||
return apiPath
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package beaconapi_evaluators
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/policies"
|
||||
e2etypes "github.com/prysmaticlabs/prysm/v4/testing/endtoend/types"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// 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.EveryNEpochs(1, 2),
|
||||
Evaluation: beaconAPIVerify,
|
||||
}
|
||||
|
||||
const (
|
||||
v1PathTemplate = "http://localhost:%d/eth/v1"
|
||||
v2PathTemplate = "http://localhost:%d/eth/v2"
|
||||
)
|
||||
|
||||
type apiComparisonFunc func(beaconNodeIdx int) error
|
||||
|
||||
func beaconAPIVerify(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error {
|
||||
beacon := []apiComparisonFunc{
|
||||
withCompareBeaconAPIs,
|
||||
}
|
||||
for beaconNodeIdx := range conns {
|
||||
if err := runAPIComparisonFunctions(
|
||||
beaconNodeIdx,
|
||||
beacon...,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAPIComparisonFunctions(beaconNodeIdx int, fs ...apiComparisonFunc) error {
|
||||
for _, f := range fs {
|
||||
if err := f(beaconNodeIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user