mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-27 21:57:16 +00:00
54915850a2
* Refactor e2e port registration * uncomment tests * explain calculation * fix things * change param to pointer * fix errors * unit test and constant
271 lines
8.6 KiB
Go
271 lines
8.6 KiB
Go
package evaluators
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/pkg/errors"
|
|
types "github.com/prysmaticlabs/eth2-types"
|
|
"github.com/prysmaticlabs/prysm/proto/eth/service"
|
|
ethpbv1 "github.com/prysmaticlabs/prysm/proto/eth/v1"
|
|
ethpbv2 "github.com/prysmaticlabs/prysm/proto/eth/v2"
|
|
"github.com/prysmaticlabs/prysm/testing/endtoend/helpers"
|
|
"github.com/prysmaticlabs/prysm/testing/endtoend/params"
|
|
"github.com/prysmaticlabs/prysm/testing/endtoend/policies"
|
|
e2etypes "github.com/prysmaticlabs/prysm/testing/endtoend/types"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
type validatorJson struct {
|
|
PublicKey string `json:"pubkey"`
|
|
WithdrawalCredentials string `json:"withdrawal_credentials"`
|
|
EffectiveBalance string `json:"effective_balance"`
|
|
Slashed bool `json:"slashed"`
|
|
ActivationEligibilityEpoch string `json:"activation_eligibility_epoch"`
|
|
ActivationEpoch string `json:"activation_epoch"`
|
|
ExitEpoch string `json:"exit_epoch"`
|
|
WithdrawableEpoch string `json:"withdrawable_epoch"`
|
|
}
|
|
type validatorContainerJson struct {
|
|
Index string `json:"index"`
|
|
Balance string `json:"balance"`
|
|
Status string `json:"status"`
|
|
Validator *validatorJson `json:"validator"`
|
|
}
|
|
|
|
// APIMiddlewareVerifyIntegrity tests our API Middleware for the official Ethereum API.
|
|
// This ensures our API Middleware returns good data compared to gRPC.
|
|
var APIMiddlewareVerifyIntegrity = e2etypes.Evaluator{
|
|
Name: "api_middleware_verify_integrity_epoch_%d",
|
|
Policy: policies.OnEpoch(helpers.AltairE2EForkEpoch),
|
|
Evaluation: apiMiddlewareVerify,
|
|
}
|
|
|
|
const (
|
|
v1MiddlewarePathTemplate = "http://localhost:%d/eth/v1"
|
|
)
|
|
|
|
func apiMiddlewareVerify(conns ...*grpc.ClientConn) error {
|
|
for beaconNodeIdx, conn := range conns {
|
|
if err := runAPIComparisonFunctions(
|
|
beaconNodeIdx,
|
|
conn,
|
|
withCompareValidatorsEth,
|
|
withCompareSyncCommittee,
|
|
withCompareAttesterDuties,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func withCompareValidatorsEth(beaconNodeIdx int, conn *grpc.ClientConn) error {
|
|
type stateValidatorsResponseJson struct {
|
|
Data []*validatorContainerJson `json:"data"`
|
|
}
|
|
ctx := context.Background()
|
|
beaconClient := service.NewBeaconChainClient(conn)
|
|
resp, err := beaconClient.ListValidators(ctx, ðpbv1.StateValidatorsRequest{
|
|
StateId: []byte("head"),
|
|
Status: []ethpbv1.ValidatorStatus{ethpbv1.ValidatorStatus_EXITED},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
respJSON := &stateValidatorsResponseJson{}
|
|
if err := doMiddlewareJSONGetRequestV1(
|
|
"/beacon/states/head/validators?status=exited",
|
|
beaconNodeIdx,
|
|
respJSON,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
if len(respJSON.Data) != len(resp.Data) {
|
|
return fmt.Errorf(
|
|
"API Middleware number of validators %d does not match gRPC %d",
|
|
len(respJSON.Data),
|
|
len(resp.Data),
|
|
)
|
|
}
|
|
resp, err = beaconClient.ListValidators(ctx, ðpbv1.StateValidatorsRequest{
|
|
StateId: []byte("head"),
|
|
Id: [][]byte{[]byte("100"), []byte("200")},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := doMiddlewareJSONGetRequestV1(
|
|
"/beacon/states/head/validators?id=100&id=200",
|
|
beaconNodeIdx,
|
|
respJSON,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
if len(respJSON.Data) != len(resp.Data) {
|
|
return fmt.Errorf(
|
|
"API Middleware number of validators %d does not match gRPC %d",
|
|
len(respJSON.Data),
|
|
len(resp.Data),
|
|
)
|
|
}
|
|
if err = assertValidator(respJSON.Data[0], resp.Data[0]); err != nil {
|
|
return errors.Wrapf(err, "incorrect validator data returned from the API request")
|
|
}
|
|
if err = assertValidator(respJSON.Data[1], resp.Data[1]); err != nil {
|
|
return errors.Wrapf(err, "incorrect validator data returned from the API request")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func assertValidator(jsonVal *validatorContainerJson, val *ethpbv1.ValidatorContainer) error {
|
|
if jsonVal == nil {
|
|
return errors.New("validator is nil")
|
|
}
|
|
if jsonVal.Index != "100" && jsonVal.Index != "200" {
|
|
return fmt.Errorf("unexpected validator index '%s'", jsonVal.Index)
|
|
}
|
|
if jsonVal.Balance != strconv.FormatUint(val.Balance, 10) {
|
|
return buildFieldError("Balance", strconv.FormatUint(val.Balance, 10), jsonVal.Balance)
|
|
}
|
|
if jsonVal.Status != strings.ToLower(val.Status.String()) {
|
|
return buildFieldError("Status", strings.ToLower(val.Status.String()), jsonVal.Status)
|
|
}
|
|
if jsonVal.Validator == nil {
|
|
return errors.New("validator is nil")
|
|
}
|
|
if jsonVal.Validator.PublicKey != hexutil.Encode(val.Validator.Pubkey) {
|
|
return buildFieldError("PublicKey", hexutil.Encode(val.Validator.Pubkey), jsonVal.Validator.PublicKey)
|
|
}
|
|
if jsonVal.Validator.Slashed != val.Validator.Slashed {
|
|
return buildFieldError("Slashed", strconv.FormatBool(val.Validator.Slashed), strconv.FormatBool(jsonVal.Validator.Slashed))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func withCompareSyncCommittee(beaconNodeIdx int, conn *grpc.ClientConn) error {
|
|
type syncCommitteeValidatorsJson struct {
|
|
Validators []string `json:"validators"`
|
|
ValidatorAggregates [][]string `json:"validator_aggregates"`
|
|
}
|
|
type syncCommitteesResponseJson struct {
|
|
Data *syncCommitteeValidatorsJson `json:"data"`
|
|
}
|
|
ctx := context.Background()
|
|
beaconClient := service.NewBeaconChainClient(conn)
|
|
resp, err := beaconClient.ListSyncCommittees(ctx, ðpbv2.StateSyncCommitteesRequest{
|
|
StateId: []byte("head"),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
respJSON := &syncCommitteesResponseJson{}
|
|
if err := doMiddlewareJSONGetRequestV1(
|
|
"/beacon/states/head/sync_committees",
|
|
beaconNodeIdx,
|
|
respJSON,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
if len(respJSON.Data.Validators) != len(resp.Data.Validators) {
|
|
return fmt.Errorf(
|
|
"API Middleware number of validators %d does not match gRPC %d",
|
|
len(respJSON.Data.Validators),
|
|
len(resp.Data.Validators),
|
|
)
|
|
}
|
|
if len(respJSON.Data.ValidatorAggregates) != len(resp.Data.ValidatorAggregates) {
|
|
return fmt.Errorf(
|
|
"API Middleware number of validator aggregates %d does not match gRPC %d",
|
|
len(respJSON.Data.ValidatorAggregates),
|
|
len(resp.Data.ValidatorAggregates),
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func withCompareAttesterDuties(beaconNodeIdx int, conn *grpc.ClientConn) error {
|
|
type attesterDutyJson struct {
|
|
Pubkey string `json:"pubkey" hex:"true"`
|
|
ValidatorIndex string `json:"validator_index"`
|
|
CommitteeIndex string `json:"committee_index"`
|
|
CommitteeLength string `json:"committee_length"`
|
|
CommitteesAtSlot string `json:"committees_at_slot"`
|
|
ValidatorCommitteeIndex string `json:"validator_committee_index"`
|
|
Slot string `json:"slot"`
|
|
}
|
|
type attesterDutiesResponseJson struct {
|
|
DependentRoot string `json:"dependent_root" hex:"true"`
|
|
Data []*attesterDutyJson `json:"data"`
|
|
}
|
|
ctx := context.Background()
|
|
validatorClient := service.NewBeaconValidatorClient(conn)
|
|
resp, err := validatorClient.GetAttesterDuties(ctx, ðpbv1.AttesterDutiesRequest{
|
|
Epoch: helpers.AltairE2EForkEpoch,
|
|
Index: []types.ValidatorIndex{0},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// We post a top-level array, not an object, as per the spec.
|
|
reqJSON := []string{"0"}
|
|
respJSON := &attesterDutiesResponseJson{}
|
|
if err := doMiddlewareJSONPostRequestV1(
|
|
"/validator/duties/attester/"+strconv.Itoa(helpers.AltairE2EForkEpoch),
|
|
beaconNodeIdx,
|
|
reqJSON,
|
|
respJSON,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
if respJSON.DependentRoot != hexutil.Encode(resp.DependentRoot) {
|
|
return buildFieldError("DependentRoot", string(resp.DependentRoot), respJSON.DependentRoot)
|
|
}
|
|
if len(respJSON.Data) != len(resp.Data) {
|
|
return fmt.Errorf(
|
|
"API Middleware number of duties %d does not match gRPC %d",
|
|
len(respJSON.Data),
|
|
len(resp.Data),
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func doMiddlewareJSONGetRequestV1(requestPath string, beaconNodeIdx int, dst interface{}) error {
|
|
basePath := fmt.Sprintf(v1MiddlewarePathTemplate, params.TestParams.Ports.PrysmBeaconNodeGatewayPort+beaconNodeIdx)
|
|
httpResp, err := http.Get(
|
|
basePath + requestPath,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.NewDecoder(httpResp.Body).Decode(&dst)
|
|
}
|
|
|
|
func doMiddlewareJSONPostRequestV1(requestPath string, beaconNodeIdx int, postData, dst interface{}) error {
|
|
b, err := json.Marshal(postData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
basePath := fmt.Sprintf(v1MiddlewarePathTemplate, params.TestParams.Ports.PrysmBeaconNodeGatewayPort+beaconNodeIdx)
|
|
httpResp, err := http.Post(
|
|
basePath+requestPath,
|
|
"application/json",
|
|
bytes.NewBuffer(b),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.NewDecoder(httpResp.Body).Decode(&dst)
|
|
}
|
|
|
|
func buildFieldError(field, expected, actual string) error {
|
|
return fmt.Errorf("value of '%s' was expected to be '%s' but was '%s'", field, expected, actual)
|
|
}
|