changing isHealthy to isReady (#16167)

<!-- 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).
This commit is contained in:
james-prysm
2026-01-06 10:58:12 -08:00
committed by GitHub
parent 27c009e7ff
commit 1a6252ade4
10 changed files with 221 additions and 49 deletions

View File

@@ -0,0 +1,3 @@
### Changed
- changed IsHealthy check to IsReady for validator client's interpretation from /eth/v1/node/health, 206 will now return false as the node is syncing.

View File

@@ -56,18 +56,18 @@ func (mr *MockNodeClientMockRecorder) Genesis(arg0, arg1 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Genesis", reflect.TypeOf((*MockNodeClient)(nil).Genesis), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Genesis", reflect.TypeOf((*MockNodeClient)(nil).Genesis), arg0, arg1)
} }
// IsHealthy mocks base method. // IsReady mocks base method.
func (m *MockNodeClient) IsHealthy(arg0 context.Context) bool { func (m *MockNodeClient) IsReady(arg0 context.Context) bool {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsHealthy", arg0) ret := m.ctrl.Call(m, "IsReady", arg0)
ret0, _ := ret[0].(bool) ret0, _ := ret[0].(bool)
return ret0 return ret0
} }
// IsHealthy indicates an expected call of IsHealthy. // IsReady indicates an expected call of IsReady.
func (mr *MockNodeClientMockRecorder) IsHealthy(arg0 any) *gomock.Call { func (mr *MockNodeClientMockRecorder) IsReady(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsHealthy", reflect.TypeOf((*MockNodeClient)(nil).IsHealthy), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsReady", reflect.TypeOf((*MockNodeClient)(nil).IsReady), arg0)
} }
// Peers mocks base method. // Peers mocks base method.

View File

@@ -2,6 +2,7 @@ package beacon_api
import ( import (
"context" "context"
"net/http"
"strconv" "strconv"
"github.com/OffchainLabs/prysm/v7/api/server/structs" "github.com/OffchainLabs/prysm/v7/api/server/structs"
@@ -101,12 +102,17 @@ func (c *beaconApiNodeClient) Peers(ctx context.Context, in *empty.Empty) (*ethp
return nil, errors.New("beaconApiNodeClient.Peers is not implemented. To use a fallback client, pass a fallback client as the last argument of NewBeaconApiNodeClientWithFallback.") return nil, errors.New("beaconApiNodeClient.Peers is not implemented. To use a fallback client, pass a fallback client as the last argument of NewBeaconApiNodeClientWithFallback.")
} }
func (c *beaconApiNodeClient) IsHealthy(ctx context.Context) bool { // IsReady returns true only if the node is fully synced (200 OK).
if err := c.jsonRestHandler.Get(ctx, "/eth/v1/node/health", nil); err != nil { // A 206 Partial Content response indicates the node is syncing and not ready.
func (c *beaconApiNodeClient) IsReady(ctx context.Context) bool {
statusCode, err := c.jsonRestHandler.GetStatusCode(ctx, "/eth/v1/node/health")
if err != nil {
log.WithError(err).Error("failed to get health of node") log.WithError(err).Error("failed to get health of node")
return false return false
} }
return true // Only 200 OK means the node is fully synced and ready.
// 206 Partial Content means syncing, 503 means unavailable.
return statusCode == http.StatusOK
} }
func NewNodeClientWithFallback(jsonRestHandler RestHandler, fallbackClient iface.NodeClient) iface.NodeClient { func NewNodeClientWithFallback(jsonRestHandler RestHandler, fallbackClient iface.NodeClient) iface.NodeClient {

View File

@@ -2,6 +2,7 @@ package beacon_api
import ( import (
"errors" "errors"
"net/http"
"testing" "testing"
"github.com/OffchainLabs/prysm/v7/api/server/structs" "github.com/OffchainLabs/prysm/v7/api/server/structs"
@@ -287,3 +288,59 @@ func TestGetVersion(t *testing.T) {
}) })
} }
} }
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)
})
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: validator/client/beacon-api/json_rest_handler.go // Source: validator/client/beacon-api/rest_handler_client.go
// //
// Generated by this command: // Generated by this command:
// //
// mockgen -package=mock -source=validator/client/beacon-api/json_rest_handler.go -destination=validator/client/beacon-api/mock/json_rest_handler_mock.go // mockgen -package=mock -source=validator/client/beacon-api/rest_handler_client.go -destination=validator/client/beacon-api/mock/json_rest_handler_mock.go RestHandler
// //
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
@@ -18,32 +18,37 @@ import (
gomock "go.uber.org/mock/gomock" gomock "go.uber.org/mock/gomock"
) )
// MockJsonRestHandler is a mock of JsonRestHandler interface. // Backward compatibility aliases for the renamed mock type.
type MockJsonRestHandler struct { type MockJsonRestHandler = MockRestHandler
type MockJsonRestHandlerMockRecorder = MockRestHandlerMockRecorder
var NewMockJsonRestHandler = NewMockRestHandler
// MockRestHandler is a mock of RestHandler interface.
type MockRestHandler struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockJsonRestHandlerMockRecorder recorder *MockRestHandlerMockRecorder
isgomock struct{}
} }
// MockJsonRestHandlerMockRecorder is the mock recorder for MockJsonRestHandler. // MockRestHandlerMockRecorder is the mock recorder for MockRestHandler.
type MockJsonRestHandlerMockRecorder struct { type MockRestHandlerMockRecorder struct {
mock *MockJsonRestHandler mock *MockRestHandler
} }
// NewMockJsonRestHandler creates a new mock instance. // NewMockRestHandler creates a new mock instance.
func NewMockJsonRestHandler(ctrl *gomock.Controller) *MockJsonRestHandler { func NewMockRestHandler(ctrl *gomock.Controller) *MockRestHandler {
mock := &MockJsonRestHandler{ctrl: ctrl} mock := &MockRestHandler{ctrl: ctrl}
mock.recorder = &MockJsonRestHandlerMockRecorder{mock} mock.recorder = &MockRestHandlerMockRecorder{mock}
return mock return mock
} }
// EXPECT returns an object that allows the caller to indicate expected use. // EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockJsonRestHandler) EXPECT() *MockJsonRestHandlerMockRecorder { func (m *MockRestHandler) EXPECT() *MockRestHandlerMockRecorder {
return m.recorder return m.recorder
} }
// Get mocks base method. // Get mocks base method.
func (m *MockJsonRestHandler) Get(ctx context.Context, endpoint string, resp any) error { func (m *MockRestHandler) Get(ctx context.Context, endpoint string, resp any) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, endpoint, resp) ret := m.ctrl.Call(m, "Get", ctx, endpoint, resp)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
@@ -51,13 +56,13 @@ func (m *MockJsonRestHandler) Get(ctx context.Context, endpoint string, resp any
} }
// Get indicates an expected call of Get. // Get indicates an expected call of Get.
func (mr *MockJsonRestHandlerMockRecorder) Get(ctx, endpoint, resp any) *gomock.Call { func (mr *MockRestHandlerMockRecorder) Get(ctx, endpoint, resp any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockJsonRestHandler)(nil).Get), ctx, endpoint, resp) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRestHandler)(nil).Get), ctx, endpoint, resp)
} }
// GetSSZ mocks base method. // GetSSZ mocks base method.
func (m *MockJsonRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) { func (m *MockRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSSZ", ctx, endpoint) ret := m.ctrl.Call(m, "GetSSZ", ctx, endpoint)
ret0, _ := ret[0].([]byte) ret0, _ := ret[0].([]byte)
@@ -67,13 +72,28 @@ func (m *MockJsonRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]by
} }
// GetSSZ indicates an expected call of GetSSZ. // GetSSZ indicates an expected call of GetSSZ.
func (mr *MockJsonRestHandlerMockRecorder) GetSSZ(ctx, endpoint any) *gomock.Call { func (mr *MockRestHandlerMockRecorder) GetSSZ(ctx, endpoint any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSSZ", reflect.TypeOf((*MockJsonRestHandler)(nil).GetSSZ), ctx, endpoint) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSSZ", reflect.TypeOf((*MockRestHandler)(nil).GetSSZ), ctx, endpoint)
}
// GetStatusCode mocks base method.
func (m *MockRestHandler) GetStatusCode(ctx context.Context, endpoint string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStatusCode", ctx, endpoint)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStatusCode indicates an expected call of GetStatusCode.
func (mr *MockRestHandlerMockRecorder) GetStatusCode(ctx, endpoint any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatusCode", reflect.TypeOf((*MockRestHandler)(nil).GetStatusCode), ctx, endpoint)
} }
// Host mocks base method. // Host mocks base method.
func (m *MockJsonRestHandler) Host() string { func (m *MockRestHandler) Host() string {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Host") ret := m.ctrl.Call(m, "Host")
ret0, _ := ret[0].(string) ret0, _ := ret[0].(string)
@@ -81,13 +101,13 @@ func (m *MockJsonRestHandler) Host() string {
} }
// Host indicates an expected call of Host. // Host indicates an expected call of Host.
func (mr *MockJsonRestHandlerMockRecorder) Host() *gomock.Call { func (mr *MockRestHandlerMockRecorder) Host() *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Host", reflect.TypeOf((*MockJsonRestHandler)(nil).Host)) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Host", reflect.TypeOf((*MockRestHandler)(nil).Host))
} }
// HttpClient mocks base method. // HttpClient mocks base method.
func (m *MockJsonRestHandler) HttpClient() *http.Client { func (m *MockRestHandler) HttpClient() *http.Client {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HttpClient") ret := m.ctrl.Call(m, "HttpClient")
ret0, _ := ret[0].(*http.Client) ret0, _ := ret[0].(*http.Client)
@@ -95,13 +115,13 @@ func (m *MockJsonRestHandler) HttpClient() *http.Client {
} }
// HttpClient indicates an expected call of HttpClient. // HttpClient indicates an expected call of HttpClient.
func (mr *MockJsonRestHandlerMockRecorder) HttpClient() *gomock.Call { func (mr *MockRestHandlerMockRecorder) HttpClient() *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HttpClient", reflect.TypeOf((*MockJsonRestHandler)(nil).HttpClient)) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HttpClient", reflect.TypeOf((*MockRestHandler)(nil).HttpClient))
} }
// Post mocks base method. // Post mocks base method.
func (m *MockJsonRestHandler) Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp any) error { func (m *MockRestHandler) Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp any) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Post", ctx, endpoint, headers, data, resp) ret := m.ctrl.Call(m, "Post", ctx, endpoint, headers, data, resp)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
@@ -109,13 +129,13 @@ func (m *MockJsonRestHandler) Post(ctx context.Context, endpoint string, headers
} }
// Post indicates an expected call of Post. // Post indicates an expected call of Post.
func (mr *MockJsonRestHandlerMockRecorder) Post(ctx, endpoint, headers, data, resp any) *gomock.Call { func (mr *MockRestHandlerMockRecorder) Post(ctx, endpoint, headers, data, resp any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Post", reflect.TypeOf((*MockJsonRestHandler)(nil).Post), ctx, endpoint, headers, data, resp) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Post", reflect.TypeOf((*MockRestHandler)(nil).Post), ctx, endpoint, headers, data, resp)
} }
// Post mocks base method. // PostSSZ mocks base method.
func (m *MockJsonRestHandler) PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error) { func (m *MockRestHandler) PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PostSSZ", ctx, endpoint, headers, data) ret := m.ctrl.Call(m, "PostSSZ", ctx, endpoint, headers, data)
ret0, _ := ret[0].([]byte) ret0, _ := ret[0].([]byte)
@@ -124,20 +144,20 @@ func (m *MockJsonRestHandler) PostSSZ(ctx context.Context, endpoint string, head
return ret0, ret1, ret2 return ret0, ret1, ret2
} }
// Post indicates an expected call of Post. // PostSSZ indicates an expected call of PostSSZ.
func (mr *MockJsonRestHandlerMockRecorder) PostSSZ(ctx, endpoint, headers, data any) *gomock.Call { func (mr *MockRestHandlerMockRecorder) PostSSZ(ctx, endpoint, headers, data any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostSSZ", reflect.TypeOf((*MockJsonRestHandler)(nil).PostSSZ), ctx, endpoint, headers, data) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostSSZ", reflect.TypeOf((*MockRestHandler)(nil).PostSSZ), ctx, endpoint, headers, data)
} }
// SetHost mocks base method. // SetHost mocks base method.
func (m *MockJsonRestHandler) SetHost(host string) { func (m *MockRestHandler) SetHost(host string) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
m.ctrl.Call(m, "SetHost", host) m.ctrl.Call(m, "SetHost", host)
} }
// SetHost indicates an expected call of SetHost. // SetHost indicates an expected call of SetHost.
func (mr *MockJsonRestHandlerMockRecorder) SetHost(host any) *gomock.Call { func (mr *MockRestHandlerMockRecorder) SetHost(host any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHost", reflect.TypeOf((*MockJsonRestHandler)(nil).SetHost), host) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHost", reflect.TypeOf((*MockRestHandler)(nil).SetHost), host)
} }

View File

@@ -23,6 +23,7 @@ type reqOption func(*http.Request)
type RestHandler interface { type RestHandler interface {
Get(ctx context.Context, endpoint string, resp any) error Get(ctx context.Context, endpoint string, resp any) error
GetStatusCode(ctx context.Context, endpoint string) (int, error)
GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error)
Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp any) error Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp any) error
PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error) PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error)
@@ -90,6 +91,28 @@ func (c *BeaconApiRestHandler) Get(ctx context.Context, endpoint string, resp an
return decodeResp(httpResp, resp) return decodeResp(httpResp, resp)
} }
// GetStatusCode sends a GET request and returns only the HTTP status code.
// This is useful for endpoints like /eth/v1/node/health that communicate status via HTTP codes
// (200 = ready, 206 = syncing, 503 = unavailable) rather than response bodies.
func (c *BeaconApiRestHandler) GetStatusCode(ctx context.Context, endpoint string) (int, error) {
url := c.host + endpoint
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return 0, errors.Wrapf(err, "failed to create request for endpoint %s", url)
}
req.Header.Set("User-Agent", version.BuildData())
httpResp, err := c.client.Do(req)
if err != nil {
return 0, errors.Wrapf(err, "failed to perform request for endpoint %s", url)
}
defer func() {
if err := httpResp.Body.Close(); err != nil {
return
}
}()
return httpResp.StatusCode, nil
}
func (c *BeaconApiRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) { func (c *BeaconApiRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) {
url := c.host + endpoint url := c.host + endpoint
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)

View File

@@ -359,3 +359,66 @@ func Test_decodeResp(t *testing.T) {
assert.ErrorContains(t, "HTTP request unsuccessful (500: foo)", err) assert.ErrorContains(t, "HTTP request unsuccessful (500: foo)", err)
}) })
} }
func TestGetStatusCode(t *testing.T) {
ctx := t.Context()
const endpoint = "/eth/v1/node/health"
testCases := []struct {
name string
serverStatusCode int
expectedStatusCode int
}{
{
name: "returns 200 OK",
serverStatusCode: http.StatusOK,
expectedStatusCode: http.StatusOK,
},
{
name: "returns 206 Partial Content",
serverStatusCode: http.StatusPartialContent,
expectedStatusCode: http.StatusPartialContent,
},
{
name: "returns 503 Service Unavailable",
serverStatusCode: http.StatusServiceUnavailable,
expectedStatusCode: http.StatusServiceUnavailable,
},
{
name: "returns 500 Internal Server Error",
serverStatusCode: http.StatusInternalServerError,
expectedStatusCode: http.StatusInternalServerError,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, version.BuildData(), r.Header.Get("User-Agent"))
w.WriteHeader(tc.serverStatusCode)
})
server := httptest.NewServer(mux)
defer server.Close()
jsonRestHandler := BeaconApiRestHandler{
client: http.Client{Timeout: time.Second * 5},
host: server.URL,
}
statusCode, err := jsonRestHandler.GetStatusCode(ctx, endpoint)
require.NoError(t, err)
assert.Equal(t, tc.expectedStatusCode, statusCode)
})
}
t.Run("returns error on connection failure", func(t *testing.T) {
jsonRestHandler := BeaconApiRestHandler{
client: http.Client{Timeout: time.Millisecond * 100},
host: "http://localhost:99999", // Invalid port
}
_, err := jsonRestHandler.GetStatusCode(ctx, endpoint)
require.ErrorContains(t, "failed to perform request", err)
})
}

View File

@@ -33,7 +33,7 @@ func (c *grpcNodeClient) Peers(ctx context.Context, in *empty.Empty) (*ethpb.Pee
return c.nodeClient.ListPeers(ctx, in) return c.nodeClient.ListPeers(ctx, in)
} }
func (c *grpcNodeClient) IsHealthy(ctx context.Context) bool { func (c *grpcNodeClient) IsReady(ctx context.Context) bool {
_, err := c.nodeClient.GetHealth(ctx, &ethpb.HealthRequest{}) _, err := c.nodeClient.GetHealth(ctx, &ethpb.HealthRequest{})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get health of node") log.WithError(err).Error("Failed to get health of node")

View File

@@ -12,5 +12,5 @@ type NodeClient interface {
Genesis(ctx context.Context, in *empty.Empty) (*ethpb.Genesis, error) Genesis(ctx context.Context, in *empty.Empty) (*ethpb.Genesis, error)
Version(ctx context.Context, in *empty.Empty) (*ethpb.Version, error) Version(ctx context.Context, in *empty.Empty) (*ethpb.Version, error)
Peers(ctx context.Context, in *empty.Empty) (*ethpb.Peers, error) Peers(ctx context.Context, in *empty.Empty) (*ethpb.Peers, error)
IsHealthy(ctx context.Context) bool IsReady(ctx context.Context) bool
} }

View File

@@ -1274,7 +1274,7 @@ func (v *validator) FindHealthyHost(ctx context.Context) bool {
// Tail-recursive closure keeps retry count private. // Tail-recursive closure keeps retry count private.
var check func(remaining int) bool var check func(remaining int) bool
check = func(remaining int) bool { check = func(remaining int) bool {
if v.nodeClient.IsHealthy(ctx) { // healthy → done if v.nodeClient.IsReady(ctx) { // ready → done
return true return true
} }
if len(v.beaconNodeHosts) == 1 && features.Get().EnableBeaconRESTApi { if len(v.beaconNodeHosts) == 1 && features.Get().EnableBeaconRESTApi {