mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
<!-- Thanks for sending a PR! Before submitting: 1. If this is your first PR, check out our contribution guide here https://docs.prylabs.network/docs/contribute/contribution-guidelines You will then need to sign our Contributor License Agreement (CLA), which will show up as a comment from a bot in this pull request after you open it. We cannot review code without a signed CLA. 2. Please file an associated tracking issue if this pull request is non-trivial and requires context for our team to understand. All features and most bug fixes should have an associated issue with a design discussed and decided upon. Small bug fixes and documentation improvements don't need issues. 3. New features and bug fixes must have tests. Documentation may need to be updated. If you're unsure what to update, send the PR, and we'll discuss in review. 4. Note that PRs updating dependencies and new Go versions are not accepted. Please file an issue instead. 5. A changelog entry is required for user facing issues. --> **What type of PR is this?** Bug fix **What does this PR do? Why is it needed?** validator fallbacks shouldn't work on nodes that are syncing as many of the tasks validators perform require the node to be fully synced. - 206 or any other code is interpreted as "not ready" - 200 interpreted as "ready" **Which issues(s) does this PR fix?** continuation of https://github.com/OffchainLabs/prysm/pull/15401 **Other notes for review** **Acknowledgements** - [x] I have read [CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md). - [x] I have included a uniquely named [changelog fragment file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd). - [x] I have added a description with sufficient context for reviewers to understand this PR. - [x] I have tested that my changes work as expected and I added a testing plan to the PR description (if applicable).
347 lines
9.0 KiB
Go
347 lines
9.0 KiB
Go
package beacon_api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/OffchainLabs/prysm/v7/api/server/structs"
|
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
|
"github.com/OffchainLabs/prysm/v7/validator/client/beacon-api/mock"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"go.uber.org/mock/gomock"
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
func TestGetGenesis(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
genesisResponse *structs.Genesis
|
|
genesisError error
|
|
depositContractResponse structs.GetDepositContractResponse
|
|
depositContractError error
|
|
queriesDepositContract bool
|
|
expectedResponse *ethpb.Genesis
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "fails to get genesis",
|
|
genesisError: errors.New("foo error"),
|
|
expectedError: "failed to get genesis: foo error",
|
|
},
|
|
{
|
|
name: "fails to decode genesis validator root",
|
|
genesisResponse: &structs.Genesis{
|
|
GenesisTime: "1",
|
|
GenesisValidatorsRoot: "foo",
|
|
},
|
|
expectedError: "failed to decode genesis validator root `foo`",
|
|
},
|
|
{
|
|
name: "fails to parse genesis time",
|
|
genesisResponse: &structs.Genesis{
|
|
GenesisTime: "foo",
|
|
GenesisValidatorsRoot: hexutil.Encode([]byte{1}),
|
|
},
|
|
expectedError: "failed to parse genesis time `foo`",
|
|
},
|
|
{
|
|
name: "fails to query contract information",
|
|
genesisResponse: &structs.Genesis{
|
|
GenesisTime: "1",
|
|
GenesisValidatorsRoot: hexutil.Encode([]byte{2}),
|
|
},
|
|
depositContractError: errors.New("foo error"),
|
|
queriesDepositContract: true,
|
|
expectedError: "foo error",
|
|
},
|
|
{
|
|
name: "fails to read nil deposit contract data",
|
|
genesisResponse: &structs.Genesis{
|
|
GenesisTime: "1",
|
|
GenesisValidatorsRoot: hexutil.Encode([]byte{2}),
|
|
},
|
|
queriesDepositContract: true,
|
|
depositContractResponse: structs.GetDepositContractResponse{
|
|
Data: nil,
|
|
},
|
|
expectedError: "deposit contract data is nil",
|
|
},
|
|
{
|
|
name: "fails to decode deposit contract address",
|
|
genesisResponse: &structs.Genesis{
|
|
GenesisTime: "1",
|
|
GenesisValidatorsRoot: hexutil.Encode([]byte{2}),
|
|
},
|
|
queriesDepositContract: true,
|
|
depositContractResponse: structs.GetDepositContractResponse{
|
|
Data: &structs.DepositContractData{
|
|
Address: "foo",
|
|
},
|
|
},
|
|
expectedError: "failed to decode deposit contract address `foo`",
|
|
},
|
|
{
|
|
name: "successfully retrieves genesis info",
|
|
genesisResponse: &structs.Genesis{
|
|
GenesisTime: "654812",
|
|
GenesisValidatorsRoot: hexutil.Encode([]byte{2}),
|
|
},
|
|
queriesDepositContract: true,
|
|
depositContractResponse: structs.GetDepositContractResponse{
|
|
Data: &structs.DepositContractData{
|
|
Address: hexutil.Encode([]byte{3}),
|
|
},
|
|
},
|
|
expectedResponse: ðpb.Genesis{
|
|
GenesisTime: ×tamppb.Timestamp{
|
|
Seconds: 654812,
|
|
},
|
|
DepositContractAddress: []byte{3},
|
|
GenesisValidatorsRoot: []byte{2},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
ctx := t.Context()
|
|
|
|
genesisProvider := mock.NewMockGenesisProvider(ctrl)
|
|
genesisProvider.EXPECT().Genesis(
|
|
gomock.Any(),
|
|
).Return(
|
|
testCase.genesisResponse,
|
|
testCase.genesisError,
|
|
)
|
|
|
|
depositContractJson := structs.GetDepositContractResponse{}
|
|
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
|
|
|
|
if testCase.queriesDepositContract {
|
|
jsonRestHandler.EXPECT().Get(
|
|
gomock.Any(),
|
|
"/eth/v1/config/deposit_contract",
|
|
&depositContractJson,
|
|
).Return(
|
|
testCase.depositContractError,
|
|
).SetArg(
|
|
2,
|
|
testCase.depositContractResponse,
|
|
)
|
|
}
|
|
|
|
nodeClient := &beaconApiNodeClient{
|
|
genesisProvider: genesisProvider,
|
|
jsonRestHandler: jsonRestHandler,
|
|
}
|
|
response, err := nodeClient.Genesis(ctx, &emptypb.Empty{})
|
|
|
|
if testCase.expectedResponse == nil {
|
|
assert.ErrorContains(t, testCase.expectedError, err)
|
|
} else {
|
|
assert.DeepEqual(t, testCase.expectedResponse, response)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetSyncStatus(t *testing.T) {
|
|
const syncingEndpoint = "/eth/v1/node/syncing"
|
|
|
|
testCases := []struct {
|
|
name string
|
|
restEndpointResponse structs.SyncStatusResponse
|
|
restEndpointError error
|
|
expectedResponse *ethpb.SyncStatus
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "fails to query REST endpoint",
|
|
restEndpointError: errors.New("foo error"),
|
|
expectedError: "foo error",
|
|
},
|
|
{
|
|
name: "returns nil syncing data",
|
|
restEndpointResponse: structs.SyncStatusResponse{Data: nil},
|
|
expectedError: "syncing data is nil",
|
|
},
|
|
{
|
|
name: "returns false syncing status",
|
|
restEndpointResponse: structs.SyncStatusResponse{
|
|
Data: &structs.SyncStatusResponseData{
|
|
IsSyncing: false,
|
|
},
|
|
},
|
|
expectedResponse: ðpb.SyncStatus{
|
|
Syncing: false,
|
|
},
|
|
},
|
|
{
|
|
name: "returns true syncing status",
|
|
restEndpointResponse: structs.SyncStatusResponse{
|
|
Data: &structs.SyncStatusResponseData{
|
|
IsSyncing: true,
|
|
},
|
|
},
|
|
expectedResponse: ðpb.SyncStatus{
|
|
Syncing: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
ctx := t.Context()
|
|
|
|
syncingResponse := structs.SyncStatusResponse{}
|
|
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
|
|
jsonRestHandler.EXPECT().Get(
|
|
gomock.Any(),
|
|
syncingEndpoint,
|
|
&syncingResponse,
|
|
).Return(
|
|
testCase.restEndpointError,
|
|
).SetArg(
|
|
2,
|
|
testCase.restEndpointResponse,
|
|
)
|
|
|
|
nodeClient := &beaconApiNodeClient{jsonRestHandler: jsonRestHandler}
|
|
syncStatus, err := nodeClient.SyncStatus(ctx, &emptypb.Empty{})
|
|
|
|
if testCase.expectedResponse == nil {
|
|
assert.ErrorContains(t, testCase.expectedError, err)
|
|
} else {
|
|
assert.DeepEqual(t, testCase.expectedResponse, syncStatus)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetVersion(t *testing.T) {
|
|
const versionEndpoint = "/eth/v1/node/version"
|
|
|
|
testCases := []struct {
|
|
name string
|
|
restEndpointResponse structs.GetVersionResponse
|
|
restEndpointError error
|
|
expectedResponse *ethpb.Version
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "fails to query REST endpoint",
|
|
restEndpointError: errors.New("foo error"),
|
|
expectedError: "foo error",
|
|
},
|
|
{
|
|
name: "returns nil version data",
|
|
restEndpointResponse: structs.GetVersionResponse{Data: nil},
|
|
expectedError: "empty version response",
|
|
},
|
|
{
|
|
name: "returns proper version response",
|
|
restEndpointResponse: structs.GetVersionResponse{
|
|
Data: &structs.Version{
|
|
Version: "prysm/local",
|
|
},
|
|
},
|
|
expectedResponse: ðpb.Version{
|
|
Version: "prysm/local",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
ctx := t.Context()
|
|
|
|
var versionResponse structs.GetVersionResponse
|
|
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
|
|
jsonRestHandler.EXPECT().Get(
|
|
gomock.Any(),
|
|
versionEndpoint,
|
|
&versionResponse,
|
|
).Return(
|
|
testCase.restEndpointError,
|
|
).SetArg(
|
|
2,
|
|
testCase.restEndpointResponse,
|
|
)
|
|
|
|
nodeClient := &beaconApiNodeClient{jsonRestHandler: jsonRestHandler}
|
|
version, err := nodeClient.Version(ctx, &emptypb.Empty{})
|
|
|
|
if testCase.expectedResponse == nil {
|
|
assert.ErrorContains(t, testCase.expectedError, err)
|
|
} else {
|
|
assert.DeepEqual(t, testCase.expectedResponse, version)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsReady(t *testing.T) {
|
|
const healthEndpoint = "/eth/v1/node/health"
|
|
|
|
testCases := []struct {
|
|
name string
|
|
statusCode int
|
|
err error
|
|
expectedResult bool
|
|
}{
|
|
{
|
|
name: "returns true for 200 OK (fully synced)",
|
|
statusCode: http.StatusOK,
|
|
expectedResult: true,
|
|
},
|
|
{
|
|
name: "returns false for 206 Partial Content (syncing)",
|
|
statusCode: http.StatusPartialContent,
|
|
expectedResult: false,
|
|
},
|
|
{
|
|
name: "returns false for 503 Service Unavailable",
|
|
statusCode: http.StatusServiceUnavailable,
|
|
expectedResult: false,
|
|
},
|
|
{
|
|
name: "returns false for 500 Internal Server Error",
|
|
statusCode: http.StatusInternalServerError,
|
|
expectedResult: false,
|
|
},
|
|
{
|
|
name: "returns false on error",
|
|
err: errors.New("request failed"),
|
|
expectedResult: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
ctx := t.Context()
|
|
|
|
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
|
|
jsonRestHandler.EXPECT().GetStatusCode(
|
|
gomock.Any(),
|
|
healthEndpoint,
|
|
).Return(tc.statusCode, tc.err)
|
|
|
|
nodeClient := &beaconApiNodeClient{jsonRestHandler: jsonRestHandler}
|
|
result := nodeClient.IsReady(ctx)
|
|
|
|
assert.Equal(t, tc.expectedResult, result)
|
|
})
|
|
}
|
|
}
|