mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-15 07:25:14 -05:00
**What type of PR is this?** Other **What does this PR do? Why is it needed?** Follow up to https://github.com/OffchainLabs/prysm/pull/16215 this pr improves logging, fixes stuttering in package naming, adds additional unit tests, and deduplicates fallback node code. **Which issues(s) does this PR fix?** fixes a potential race if reconnecting to the same host very quickly which has a stale connection still. **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).
348 lines
9.0 KiB
Go
348 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{}
|
|
handler := mock.NewMockJsonRestHandler(ctrl)
|
|
|
|
if testCase.queriesDepositContract {
|
|
handler.EXPECT().Get(
|
|
gomock.Any(),
|
|
"/eth/v1/config/deposit_contract",
|
|
&depositContractJson,
|
|
).Return(
|
|
testCase.depositContractError,
|
|
).SetArg(
|
|
2,
|
|
testCase.depositContractResponse,
|
|
)
|
|
}
|
|
|
|
nodeClient := &beaconApiNodeClient{
|
|
genesisProvider: genesisProvider,
|
|
handler: handler,
|
|
}
|
|
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{}
|
|
handler := mock.NewMockJsonRestHandler(ctrl)
|
|
handler.EXPECT().Get(
|
|
gomock.Any(),
|
|
syncingEndpoint,
|
|
&syncingResponse,
|
|
).Return(
|
|
testCase.restEndpointError,
|
|
).SetArg(
|
|
2,
|
|
testCase.restEndpointResponse,
|
|
)
|
|
|
|
nodeClient := &beaconApiNodeClient{handler: handler}
|
|
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
|
|
handler := mock.NewMockJsonRestHandler(ctrl)
|
|
handler.EXPECT().Get(
|
|
gomock.Any(),
|
|
versionEndpoint,
|
|
&versionResponse,
|
|
).Return(
|
|
testCase.restEndpointError,
|
|
).SetArg(
|
|
2,
|
|
testCase.restEndpointResponse,
|
|
)
|
|
|
|
nodeClient := &beaconApiNodeClient{handler: handler}
|
|
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()
|
|
|
|
handler := mock.NewMockJsonRestHandler(ctrl)
|
|
handler.EXPECT().GetStatusCode(
|
|
gomock.Any(),
|
|
healthEndpoint,
|
|
).Return(tc.statusCode, tc.err)
|
|
handler.EXPECT().Host().Return("http://localhost:3500").AnyTimes()
|
|
|
|
nodeClient := &beaconApiNodeClient{handler: handler}
|
|
result := nodeClient.IsReady(ctx)
|
|
|
|
assert.Equal(t, tc.expectedResult, result)
|
|
})
|
|
}
|
|
}
|