diff --git a/api/core.go b/api/core.go index b8d5946e1..088e9c7f7 100644 --- a/api/core.go +++ b/api/core.go @@ -483,6 +483,8 @@ type ( InfoResNodeStatus struct { // Whether the node is healthy. IsHealthy bool `serix:""` + // Whether the network is healthy (finalization is not delayed). + IsNetworkHealthy bool `serix:""` // The accepted tangle time. AcceptedTangleTime time.Time `serix:""` // The relative accepted tangle time. diff --git a/api/core_test.go b/api/core_test.go index f62a3d1e1..f0c92ba7f 100644 --- a/api/core_test.go +++ b/api/core_test.go @@ -23,6 +23,7 @@ func Test_CoreAPIDeSerialize(t *testing.T) { Version: "2.0.0", Status: &api.InfoResNodeStatus{ IsHealthy: false, + IsNetworkHealthy: false, AcceptedTangleTime: time.Unix(1690879505, 0).UTC(), RelativeAcceptedTangleTime: time.Unix(1690879505, 0).UTC(), ConfirmedTangleTime: time.Unix(1690879505, 0).UTC(), @@ -301,6 +302,7 @@ func Test_CoreAPIJSONSerialization(t *testing.T) { Version: "2.0.0", Status: &api.InfoResNodeStatus{ IsHealthy: false, + IsNetworkHealthy: false, AcceptedTangleTime: time.Unix(1690879505, 0).UTC(), RelativeAcceptedTangleTime: time.Unix(1690879505, 0).UTC(), ConfirmedTangleTime: time.Unix(1690879505, 0).UTC(), @@ -330,6 +332,7 @@ func Test_CoreAPIJSONSerialization(t *testing.T) { "version": "2.0.0", "status": { "isHealthy": false, + "isNetworkHealthy": false, "acceptedTangleTime": "1690879505000000000", "relativeAcceptedTangleTime": "1690879505000000000", "confirmedTangleTime": "1690879505000000000", diff --git a/api/endpoints.go b/api/endpoints.go index 3d85be9af..2ca2a6e3d 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -124,6 +124,10 @@ const ( // MIMEApplicationVendorIOTASerializerV2 => bytes. CoreEndpointInfo = "/info" + // CoreEndpointNetworkHealth is the endpoint for getting the network health. + // GET returns http status code 200 if the network is healthy (finalization is not delayed). + CoreEndpointNetworkHealth = "/network/health" + // CoreEndpointNetworkMetrics is the endpoint for getting the network metrics. // GET returns the network metrics. // "Accept" header: @@ -289,6 +293,7 @@ const ( var ( CoreRouteInfo = route(CorePluginName, CoreEndpointInfo) + CoreRouteNetworkHealth = route(CorePluginName, CoreEndpointNetworkHealth) CoreRouteNetworkMetrics = route(CorePluginName, CoreEndpointNetworkMetrics) CoreRouteBlocks = route(CorePluginName, CoreEndpointBlocks) CoreRouteBlock = route(CorePluginName, CoreEndpointBlock) diff --git a/nodeclient/http_api_client.go b/nodeclient/http_api_client.go index 1cb2c68c4..09fad318f 100644 --- a/nodeclient/http_api_client.go +++ b/nodeclient/http_api_client.go @@ -255,6 +255,20 @@ func (client *Client) Info(ctx context.Context) (*api.InfoResponse, error) { return res, nil } +// NetworkHealth returns whether the network is healthy (finalization is not delayed). +func (client *Client) NetworkHealth(ctx context.Context) (bool, error) { + //nolint:bodyclose + if _, err := client.Do(ctx, http.MethodGet, api.CoreRouteNetworkHealth, nil, nil); err != nil { + if ierrors.Is(err, ErrHTTPServiceUnavailable) { + return false, nil + } + + return false, err + } + + return true, nil +} + // NetworkMetrics gets the current network metrics. func (client *Client) NetworkMetrics(ctx context.Context) (*api.NetworkMetricsResponse, error) { res := new(api.NetworkMetricsResponse) diff --git a/nodeclient/http_api_client_test.go b/nodeclient/http_api_client_test.go index de8f3362e..117381f90 100644 --- a/nodeclient/http_api_client_test.go +++ b/nodeclient/http_api_client_test.go @@ -105,10 +105,11 @@ func nodeClient(t *testing.T) *nodeclient.Client { ts := time.Now() originInfo := &api.InfoResponse{ - Name: "HORNET", + Name: "iota-core", Version: "1.0.0", Status: &api.InfoResNodeStatus{ IsHealthy: true, + IsNetworkHealthy: true, LatestAcceptedBlockSlot: tpkg.RandSlot(), LatestConfirmedBlockSlot: tpkg.RandSlot(), LatestFinalizedSlot: iotago.SlotIndex(142857), @@ -163,6 +164,27 @@ func TestClient_Health(t *testing.T) { require.False(t, healthy) } +func TestClient_NetworkHealth(t *testing.T) { + defer gock.Off() + + gock.New(nodeAPIUrl). + Get(api.CoreRouteNetworkHealth). + Reply(200) + + nodeAPI := nodeClient(t) + healthy, err := nodeAPI.NetworkHealth(context.Background()) + require.NoError(t, err) + require.True(t, healthy) + + gock.New(nodeAPIUrl). + Get(api.CoreRouteNetworkHealth). + Reply(503) + + healthy, err = nodeAPI.NetworkHealth(context.Background()) + require.NoError(t, err) + require.False(t, healthy) +} + func TestClient_NetworkMetrics(t *testing.T) { defer gock.Off()