Files
prysm/validator/client/beacon-api/beacon_api_helpers_test.go
james-prysm cf94ccbf72 node fallback cleanup (#16316)
**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).
2026-02-04 15:59:42 +00:00

297 lines
5.8 KiB
Go

package beacon_api
import (
"bytes"
"encoding/json"
"errors"
"testing"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
"github.com/OffchainLabs/prysm/v7/testing/assert"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/validator/client/beacon-api/mock"
"go.uber.org/mock/gomock"
)
const forkEndpoint = "/eth/v1/beacon/states/head/fork"
func TestGetFork_Nominal(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
stateForkResponseJson := structs.GetStateForkResponse{}
handler := mock.NewMockJsonRestHandler(ctrl)
expected := structs.GetStateForkResponse{
Data: &structs.Fork{
PreviousVersion: "0x1",
CurrentVersion: "0x2",
Epoch: "3",
},
}
ctx := t.Context()
handler.EXPECT().Get(
gomock.Any(),
forkEndpoint,
&stateForkResponseJson,
).Return(
nil,
).SetArg(
2,
expected,
).Times(1)
validatorClient := beaconApiValidatorClient{
handler: handler,
}
fork, err := validatorClient.fork(ctx)
require.NoError(t, err)
assert.DeepEqual(t, &expected, fork)
}
func TestGetFork_Invalid(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
handler := mock.NewMockJsonRestHandler(ctrl)
ctx := t.Context()
handler.EXPECT().Get(
gomock.Any(),
forkEndpoint,
gomock.Any(),
).Return(
errors.New("custom error"),
).Times(1)
validatorClient := beaconApiValidatorClient{
handler: handler,
}
_, err := validatorClient.fork(ctx)
require.ErrorContains(t, "custom error", err)
}
const headersEndpoint = "/eth/v1/beacon/headers"
func TestGetHeaders_Nominal(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
blockHeadersResponseJson := structs.GetBlockHeadersResponse{}
handler := mock.NewMockJsonRestHandler(ctrl)
expected := structs.GetBlockHeadersResponse{
Data: []*structs.SignedBeaconBlockHeaderContainer{
{
Header: &structs.SignedBeaconBlockHeader{
Message: &structs.BeaconBlockHeader{
Slot: "42",
},
},
},
},
}
ctx := t.Context()
handler.EXPECT().Get(
gomock.Any(),
headersEndpoint,
&blockHeadersResponseJson,
).Return(
nil,
).SetArg(
2,
expected,
).Times(1)
validatorClient := beaconApiValidatorClient{
handler: handler,
}
headers, err := validatorClient.headers(ctx)
require.NoError(t, err)
assert.DeepEqual(t, &expected, headers)
}
func TestGetHeaders_Invalid(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
handler := mock.NewMockJsonRestHandler(ctrl)
ctx := t.Context()
handler.EXPECT().Get(
gomock.Any(),
headersEndpoint,
gomock.Any(),
).Return(
errors.New("custom error"),
).Times(1)
validatorClient := beaconApiValidatorClient{
handler: handler,
}
_, err := validatorClient.headers(ctx)
require.ErrorContains(t, "custom error", err)
}
const livenessEndpoint = "/eth/v1/validator/liveness/42"
func TestGetLiveness_Nominal(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
livenessResponseJson := structs.GetLivenessResponse{}
indexes := []string{"1", "2"}
marshalledIndexes, err := json.Marshal(indexes)
require.NoError(t, err)
expected := structs.GetLivenessResponse{
Data: []*structs.Liveness{
{
Index: "1",
IsLive: true,
},
{
Index: "2",
IsLive: false,
},
},
}
ctx := t.Context()
handler := mock.NewMockJsonRestHandler(ctrl)
handler.EXPECT().Post(
gomock.Any(),
livenessEndpoint,
nil,
bytes.NewBuffer(marshalledIndexes),
&livenessResponseJson,
).SetArg(
4,
expected,
).Return(
nil,
).Times(1)
validatorClient := &beaconApiValidatorClient{handler: handler}
liveness, err := validatorClient.liveness(ctx, 42, indexes)
require.NoError(t, err)
assert.DeepEqual(t, &expected, liveness)
}
func TestGetLiveness_Invalid(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := t.Context()
handler := mock.NewMockJsonRestHandler(ctrl)
handler.EXPECT().Post(
gomock.Any(),
livenessEndpoint,
nil,
gomock.Any(),
gomock.Any(),
).Return(
errors.New("custom error"),
).Times(1)
validatorClient := &beaconApiValidatorClient{handler: handler}
_, err := validatorClient.liveness(ctx, 42, nil)
require.ErrorContains(t, "custom error", err)
}
const syncingEndpoint = "/eth/v1/node/syncing"
func TestGetIsSyncing_Nominal(t *testing.T) {
testCases := []struct {
name string
isSyncing bool
}{
{
name: "Syncing",
isSyncing: true,
},
{
name: "Not syncing",
isSyncing: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
syncingResponseJson := structs.SyncStatusResponse{}
handler := mock.NewMockJsonRestHandler(ctrl)
expected := structs.SyncStatusResponse{
Data: &structs.SyncStatusResponseData{
IsSyncing: testCase.isSyncing,
},
}
ctx := t.Context()
handler.EXPECT().Get(
gomock.Any(),
syncingEndpoint,
&syncingResponseJson,
).Return(
nil,
).SetArg(
2,
expected,
).Times(1)
validatorClient := beaconApiValidatorClient{
handler: handler,
}
isSyncing, err := validatorClient.isSyncing(ctx)
require.NoError(t, err)
assert.Equal(t, testCase.isSyncing, isSyncing)
})
}
}
func TestGetIsSyncing_Invalid(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
syncingResponseJson := structs.SyncStatusResponse{}
handler := mock.NewMockJsonRestHandler(ctrl)
ctx := t.Context()
handler.EXPECT().Get(
gomock.Any(),
syncingEndpoint,
&syncingResponseJson,
).Return(
errors.New("custom error"),
).Times(1)
validatorClient := beaconApiValidatorClient{
handler: handler,
}
isSyncing, err := validatorClient.isSyncing(ctx)
assert.Equal(t, true, isSyncing)
assert.ErrorContains(t, "failed to get syncing status", err)
}