Skip to content

Commit d2d4393

Browse files
committed
docs: update CHANGELOG with new health and readiness checks, including endpoint modifications and test updates
1 parent ea5219b commit d2d4393

File tree

7 files changed

+23
-416
lines changed

7 files changed

+23
-416
lines changed

CHANGELOG.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Added comprehensive health endpoint documentation in `docs/learn/config.md#health-endpoints` explaining liveness vs readiness checks, Kubernetes probe configuration, and usage examples ([#2800](https://github.com/evstack/ev-node/pull/2800))
1515
- Added P2P listening check to `/health/ready` endpoint to verify P2P network is ready to accept connections ([#2800](https://github.com/evstack/ev-node/pull/2800))
1616
- Added aggregator block production rate check to `/health/ready` endpoint to ensure aggregators are producing blocks within expected timeframe (5x block time) ([#2800](https://github.com/evstack/ev-node/pull/2800))
17-
- Added `GetReadiness()` method to Go RPC client for checking `/health/ready` endpoint ([#2800](https://github.com/evstack/ev-node/pull/2800))
18-
- Added `ReadinessStatus` type to Go RPC client with READY/UNREADY/UNKNOWN states ([#2800](https://github.com/evstack/ev-node/pull/2800))
1917
- Added `readyz()` and `is_ready()` methods to Rust `HealthClient` for checking `/health/ready` endpoint ([#2800](https://github.com/evstack/ev-node/pull/2800))
2018
- Added `ReadinessStatus` enum to Rust client with Ready/Unready/Unknown states ([#2800](https://github.com/evstack/ev-node/pull/2800))
2119

@@ -33,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3331
- **BREAKING:** Removed `evnode.v1.HealthService` gRPC endpoint in favor of HTTP health endpoints ([#2800](https://github.com/evstack/ev-node/pull/2800))
3432
- Migration: Use `GET /health/live` instead of `HealthService.Livez()` gRPC call
3533
- See migration guide: `docs/learn/config.md#health-endpoints`
36-
- Affected clients: Go client (`pkg/rpc/client`), Rust client (`client/crates/client`), and any external services using the gRPC health endpoint
34+
- Affected clients: Rust client (`client/crates/client`) and any external services using the gRPC health endpoint
3735
- Removed `proto/evnode/v1/health.proto` and generated protobuf files ([#2800](https://github.com/evstack/ev-node/pull/2800))
3836

3937
## v1.0.0-beta.9

node/single_sequencer_integration_test.go

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -419,19 +419,13 @@ func waitForBlockN(t *testing.T, n uint64, node *FullNode, blockInterval time.Du
419419
return got >= n
420420
}, timeout[0], blockInterval/2)
421421
}
422-
// TestReadinessEndpointWhenBlockProductionStops verifies that the readiness endpoint
423-
// correctly reports UNREADY state when an aggregator stops producing blocks.
424422
func TestReadinessEndpointWhenBlockProductionStops(t *testing.T) {
425423
require := require.New(t)
426424

427-
// Set up configuration with specific block time for predictable readiness checks
428425
config := getTestConfig(t, 1)
429426
config.Node.Aggregator = true
430427
config.Node.BlockTime = evconfig.DurationWrapper{Duration: 500 * time.Millisecond}
431428
config.Node.MaxPendingHeadersAndData = 2
432-
433-
// Set DA block time large enough to avoid header submission to DA layer
434-
// This will cause block production to stop once MaxPendingHeadersAndData is reached
435429
config.DA.BlockTime = evconfig.DurationWrapper{Duration: 100 * time.Second}
436430

437431
node, cleanup := createNodeWithCleanup(t, config)
@@ -443,32 +437,20 @@ func TestReadinessEndpointWhenBlockProductionStops(t *testing.T) {
443437
var runningWg sync.WaitGroup
444438
startNodeInBackground(t, []*FullNode{node}, []context.Context{ctx}, &runningWg, 0, nil)
445439

446-
// Wait for first block to be produced
447440
waitForBlockN(t, 1, node, config.Node.BlockTime.Duration)
448441

449-
// Create RPC client
450442
rpcClient := NewRPCClient(config.RPC.Address)
451443

452-
// Verify readiness is READY while blocks are being produced
453444
readiness, err := rpcClient.GetReadiness(ctx)
454445
require.NoError(err)
455446
require.Equal(client.ReadinessStatus_READY, readiness, "Readiness should be READY while producing blocks")
456447

457-
// Wait for block production to stop (when MaxPendingHeadersAndData is reached)
458448
time.Sleep(time.Duration(config.Node.MaxPendingHeadersAndData+2) * config.Node.BlockTime.Duration)
459449

460-
// Get the height to confirm blocks stopped
461450
height, err := getNodeHeight(node, Store)
462451
require.NoError(err)
463452
require.LessOrEqual(height, config.Node.MaxPendingHeadersAndData)
464453

465-
// Readiness check threshold for aggregators:
466-
// blockTime = 500ms
467-
// maxAllowedDelay = blockTime * 5 = 2500ms = 2.5s
468-
// After 2.5s without producing a block, aggregator should be UNREADY
469-
470-
// Poll for readiness to transition to UNREADY
471-
// This is more robust than fixed time.Sleep as it handles timing variations
472454
require.Eventually(func() bool {
473455
readiness, err := rpcClient.GetReadiness(ctx)
474456
if err != nil {
@@ -477,6 +459,5 @@ func TestReadinessEndpointWhenBlockProductionStops(t *testing.T) {
477459
return readiness == client.ReadinessStatus_UNREADY
478460
}, 10*time.Second, 100*time.Millisecond, "Readiness should be UNREADY after aggregator stops producing blocks (5x block time)")
479461

480-
// Stop the node and wait for shutdown
481462
shutdownAndWait(t, []context.CancelFunc{cancel}, &runningWg, 10*time.Second)
482463
}

pkg/rpc/client/client.go

Lines changed: 0 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ package client
22

33
import (
44
"context"
5-
"fmt"
6-
"io"
75
"net/http"
8-
"strings"
96

107
"connectrpc.com/connect"
118
"google.golang.org/protobuf/types/known/emptypb"
@@ -14,40 +11,11 @@ import (
1411
rpc "github.com/evstack/ev-node/types/pb/evnode/v1/v1connect"
1512
)
1613

17-
// HealthStatus represents the health status of a node
18-
type HealthStatus int32
19-
20-
const (
21-
// HealthStatus_UNKNOWN represents an unknown health status
22-
HealthStatus_UNKNOWN HealthStatus = 0
23-
// HealthStatus_PASS represents a healthy node
24-
HealthStatus_PASS HealthStatus = 1
25-
// HealthStatus_WARN represents a degraded but still serving node
26-
HealthStatus_WARN HealthStatus = 2
27-
// HealthStatus_FAIL represents a failed node
28-
HealthStatus_FAIL HealthStatus = 3
29-
)
30-
31-
func (h HealthStatus) String() string {
32-
switch h {
33-
case HealthStatus_PASS:
34-
return "PASS"
35-
case HealthStatus_WARN:
36-
return "WARN"
37-
case HealthStatus_FAIL:
38-
return "FAIL"
39-
default:
40-
return "UNKNOWN"
41-
}
42-
}
43-
4414
// Client is the client for StoreService, P2PService, and ConfigService
4515
type Client struct {
4616
storeClient rpc.StoreServiceClient
4717
p2pClient rpc.P2PServiceClient
4818
configClient rpc.ConfigServiceClient
49-
baseURL string
50-
httpClient *http.Client
5119
}
5220

5321
// NewClient creates a new RPC client
@@ -61,8 +29,6 @@ func NewClient(baseURL string) *Client {
6129
storeClient: storeClient,
6230
p2pClient: p2pClient,
6331
configClient: configClient,
64-
baseURL: baseURL,
65-
httpClient: httpClient,
6632
}
6733
}
6834

@@ -145,92 +111,6 @@ func (c *Client) GetNetInfo(ctx context.Context) (*pb.NetInfo, error) {
145111
return resp.Msg.NetInfo, nil
146112
}
147113

148-
// GetHealth calls the /health/live HTTP endpoint and returns the HealthStatus
149-
// This endpoint checks liveness (is the process alive and responsive?).
150-
// For readiness checks (can the node serve correct data?), use GetReadiness().
151-
func (c *Client) GetHealth(ctx context.Context) (HealthStatus, error) {
152-
healthURL := fmt.Sprintf("%s/health/live", c.baseURL)
153-
req, err := http.NewRequestWithContext(ctx, http.MethodGet, healthURL, nil)
154-
if err != nil {
155-
return HealthStatus_UNKNOWN, fmt.Errorf("failed to create health request: %w", err)
156-
}
157-
158-
resp, err := c.httpClient.Do(req)
159-
if err != nil {
160-
return HealthStatus_UNKNOWN, fmt.Errorf("failed to get health: %w", err)
161-
}
162-
defer resp.Body.Close()
163-
164-
body, err := io.ReadAll(resp.Body)
165-
if err != nil {
166-
return HealthStatus_UNKNOWN, fmt.Errorf("failed to read health response: %w", err)
167-
}
168-
169-
// Parse the text response
170-
status := strings.TrimSpace(string(body))
171-
switch status {
172-
case "OK":
173-
return HealthStatus_PASS, nil
174-
case "WARN":
175-
return HealthStatus_WARN, nil
176-
case "FAIL":
177-
return HealthStatus_FAIL, nil
178-
default:
179-
return HealthStatus_UNKNOWN, fmt.Errorf("unknown health status: %s", status)
180-
}
181-
}
182-
183-
// ReadinessStatus represents the readiness state of a node
184-
type ReadinessStatus int32
185-
186-
const (
187-
ReadinessStatus_UNKNOWN ReadinessStatus = 0
188-
ReadinessStatus_READY ReadinessStatus = 1
189-
ReadinessStatus_UNREADY ReadinessStatus = 2
190-
)
191-
192-
func (s ReadinessStatus) String() string {
193-
switch s {
194-
case ReadinessStatus_READY:
195-
return "READY"
196-
case ReadinessStatus_UNREADY:
197-
return "UNREADY"
198-
default:
199-
return "UNKNOWN"
200-
}
201-
}
202-
203-
// GetReadiness calls the /health/ready HTTP endpoint and returns the ReadinessStatus
204-
// This endpoint checks if the node can serve correct data to clients.
205-
// For liveness checks (is the process alive?), use GetHealth().
206-
func (c *Client) GetReadiness(ctx context.Context) (ReadinessStatus, error) {
207-
readinessURL := fmt.Sprintf("%s/health/ready", c.baseURL)
208-
req, err := http.NewRequestWithContext(ctx, http.MethodGet, readinessURL, nil)
209-
if err != nil {
210-
return ReadinessStatus_UNKNOWN, fmt.Errorf("failed to create readiness request: %w", err)
211-
}
212-
213-
resp, err := c.httpClient.Do(req)
214-
if err != nil {
215-
return ReadinessStatus_UNKNOWN, fmt.Errorf("failed to get readiness: %w", err)
216-
}
217-
defer resp.Body.Close()
218-
219-
body, err := io.ReadAll(resp.Body)
220-
if err != nil {
221-
return ReadinessStatus_UNKNOWN, fmt.Errorf("failed to read readiness response: %w", err)
222-
}
223-
224-
// Parse the text response
225-
status := strings.TrimSpace(string(body))
226-
if strings.HasPrefix(status, "READY") {
227-
return ReadinessStatus_READY, nil
228-
} else if strings.HasPrefix(status, "UNREADY") {
229-
return ReadinessStatus_UNREADY, nil
230-
}
231-
return ReadinessStatus_UNKNOWN, fmt.Errorf("unknown readiness status: %s", status)
232-
}
233-
234114
// GetNamespace returns the namespace configuration for this network
235115
func (c *Client) GetNamespace(ctx context.Context) (*pb.GetNamespaceResponse, error) {
236116
req := connect.NewRequest(&emptypb.Empty{})

0 commit comments

Comments
 (0)