Skip to content

Commit 765a404

Browse files
authored
Merge branch 'main' into marko/2603
2 parents 5809f73 + 727e591 commit 765a404

File tree

11 files changed

+159
-18
lines changed

11 files changed

+159
-18
lines changed

node/full.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,13 @@ func (n *FullNode) Run(parentCtx context.Context) error {
281281
}
282282

283283
// Start RPC server
284-
handler, err := rpcserver.NewServiceHandler(n.Store, n.p2pClient, n.genesis.ProposerAddress, n.Logger, n.nodeConfig)
284+
bestKnownHeightProvider := func() uint64 {
285+
hHeight := n.hSyncService.Store().Height()
286+
dHeight := n.dSyncService.Store().Height()
287+
return min(hHeight, dHeight)
288+
}
289+
290+
handler, err := rpcserver.NewServiceHandler(n.Store, n.p2pClient, n.genesis.ProposerAddress, n.Logger, n.nodeConfig, bestKnownHeightProvider)
285291
if err != nil {
286292
return fmt.Errorf("error creating RPC handler: %w", err)
287293
}

node/light.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,13 @@ func (ln *LightNode) Run(parentCtx context.Context) error {
7777

7878
ln.running = true
7979
// Start RPC server (light node doesn't have a signer)
80-
handler, err := rpcserver.NewServiceHandler(ln.Store, ln.P2P, nil, ln.Logger, ln.nodeConfig)
80+
81+
// for light nodes use the header sync service height as the best known height.
82+
bestKnown := func() uint64 {
83+
return ln.hSyncService.Store().Height()
84+
}
85+
86+
handler, err := rpcserver.NewServiceHandler(ln.Store, ln.P2P, nil, ln.Logger, ln.nodeConfig, bestKnown)
8187
if err != nil {
8288
return fmt.Errorf("error creating RPC handler: %w", err)
8389
}

pkg/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ const (
4545
FlagMaxPendingHeadersAndData = FlagPrefixEvnode + "node.max_pending_headers_and_data"
4646
// FlagLazyBlockTime is a flag for specifying the maximum interval between blocks in lazy aggregation mode
4747
FlagLazyBlockTime = FlagPrefixEvnode + "node.lazy_block_interval"
48+
// FlagReadinessMaxBlocksBehind configures how many blocks behind best-known head is still considered ready
49+
FlagReadinessMaxBlocksBehind = FlagPrefixEvnode + "node.readiness_max_blocks_behind"
4850

4951
// Data Availability configuration flags
5052

@@ -193,6 +195,9 @@ type NodeConfig struct {
193195

194196
// Header configuration
195197
TrustedHash string `mapstructure:"trusted_hash" yaml:"trusted_hash" comment:"Initial trusted hash used to bootstrap the header exchange service. Allows nodes to start synchronizing from a specific trusted point in the chain instead of genesis. When provided, the node will fetch the corresponding header/block from peers using this hash and use it as a starting point for synchronization. If not provided, the node will attempt to fetch the genesis block instead."`
198+
199+
// Readiness / health configuration
200+
ReadinessMaxBlocksBehind uint64 `mapstructure:"readiness_max_blocks_behind" yaml:"readiness_max_blocks_behind" comment:"How many blocks behind best-known head the node can be and still be considered ready. 0 means must be exactly at head."`
196201
}
197202

198203
// LogConfig contains all logging configuration parameters
@@ -307,6 +312,7 @@ func AddFlags(cmd *cobra.Command) {
307312
cmd.Flags().Bool(FlagLazyAggregator, def.Node.LazyMode, "produce blocks only when transactions are available or after lazy block time")
308313
cmd.Flags().Uint64(FlagMaxPendingHeadersAndData, def.Node.MaxPendingHeadersAndData, "maximum headers or data pending DA confirmation before pausing block production (0 for no limit)")
309314
cmd.Flags().Duration(FlagLazyBlockTime, def.Node.LazyBlockInterval.Duration, "maximum interval between blocks in lazy aggregation mode")
315+
cmd.Flags().Uint64(FlagReadinessMaxBlocksBehind, def.Node.ReadinessMaxBlocksBehind, "how many blocks behind best-known head the node can be and still be considered ready (0 = must be at head)")
310316

311317
// Data Availability configuration flags
312318
cmd.Flags().String(FlagDAAddress, def.DA.Address, "DA address (host:port)")

pkg/config/config_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ func TestAddFlags(t *testing.T) {
6363
assertFlagValue(t, flags, FlagLazyAggregator, DefaultConfig().Node.LazyMode)
6464
assertFlagValue(t, flags, FlagMaxPendingHeadersAndData, DefaultConfig().Node.MaxPendingHeadersAndData)
6565
assertFlagValue(t, flags, FlagLazyBlockTime, DefaultConfig().Node.LazyBlockInterval.Duration)
66+
assertFlagValue(t, flags, FlagReadinessMaxBlocksBehind, DefaultConfig().Node.ReadinessMaxBlocksBehind)
6667

6768
// DA flags
6869
assertFlagValue(t, flags, FlagDAAddress, DefaultConfig().DA.Address)
@@ -104,7 +105,7 @@ func TestAddFlags(t *testing.T) {
104105
assertFlagValue(t, flags, FlagRPCAddress, DefaultConfig().RPC.Address)
105106

106107
// Count the number of flags we're explicitly checking
107-
expectedFlagCount := 37 // Update this number if you add more flag checks above
108+
expectedFlagCount := 38 // Update this number if you add more flag checks above
108109

109110
// Get the actual number of flags (both regular and persistent)
110111
actualFlagCount := 0

pkg/config/defaults.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ func DefaultConfig() Config {
4646
Peers: "",
4747
},
4848
Node: NodeConfig{
49-
Aggregator: false,
50-
BlockTime: DurationWrapper{1 * time.Second},
51-
LazyMode: false,
52-
LazyBlockInterval: DurationWrapper{60 * time.Second},
53-
Light: false,
54-
TrustedHash: "",
49+
Aggregator: false,
50+
BlockTime: DurationWrapper{1 * time.Second},
51+
LazyMode: false,
52+
LazyBlockInterval: DurationWrapper{60 * time.Second},
53+
Light: false,
54+
TrustedHash: "",
55+
ReadinessMaxBlocksBehind: 3,
5556
},
5657
DA: DAConfig{
5758
Address: "http://localhost:7980",

pkg/rpc/example/example.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ func StartStoreServer(s store.Store, address string, logger zerolog.Logger) {
2020
// Create and start the server
2121
// Start RPC server
2222
rpcAddr := fmt.Sprintf("%s:%d", "localhost", 8080)
23+
2324
cfg := config.DefaultConfig()
24-
handler, err := server.NewServiceHandler(s, nil, nil, logger, cfg)
25+
handler, err := server.NewServiceHandler(s, nil, nil, logger, cfg, nil)
2526
if err != nil {
2627
panic(err)
2728
}
@@ -81,7 +82,7 @@ func ExampleServer(s store.Store) {
8182
// Start RPC server
8283
rpcAddr := fmt.Sprintf("%s:%d", "localhost", 8080)
8384
cfg := config.DefaultConfig()
84-
handler, err := server.NewServiceHandler(s, nil, nil, logger, cfg)
85+
handler, err := server.NewServiceHandler(s, nil, nil, logger, cfg, nil)
8586
if err != nil {
8687
panic(err)
8788
}

pkg/rpc/server/da_visualization_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
coreda "github.com/evstack/ev-node/core/da"
13+
"github.com/evstack/ev-node/pkg/config"
1314
"github.com/evstack/ev-node/test/mocks"
1415
"github.com/rs/zerolog"
1516
"github.com/stretchr/testify/assert"
@@ -254,7 +255,7 @@ func TestRegisterCustomHTTPEndpointsDAVisualization(t *testing.T) {
254255

255256
// Create mux and register endpoints
256257
mux := http.NewServeMux()
257-
RegisterCustomHTTPEndpoints(mux)
258+
RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil)
258259

259260
// Test /da endpoint
260261
req, err := http.NewRequest("GET", "/da", nil)
@@ -291,7 +292,7 @@ func TestRegisterCustomHTTPEndpointsWithoutServer(t *testing.T) {
291292
SetDAVisualizationServer(nil)
292293

293294
mux := http.NewServeMux()
294-
RegisterCustomHTTPEndpoints(mux)
295+
RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil)
295296

296297
// Test that endpoints return service unavailable when server is not set
297298
endpoints := []string{"/da", "/da/submissions", "/da/blob"}

pkg/rpc/server/http.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,83 @@ package server
33
import (
44
"fmt"
55
"net/http"
6+
7+
"github.com/evstack/ev-node/pkg/config"
8+
"github.com/evstack/ev-node/pkg/p2p"
9+
"github.com/evstack/ev-node/pkg/store"
610
)
711

12+
// BestKnownHeightProvider should return the best-known network height observed by the node
13+
// (e.g. min(headerSyncHeight, dataSyncHeight) for full nodes, or header height for light nodes).
14+
type BestKnownHeightProvider func() uint64
15+
816
// RegisterCustomHTTPEndpoints is the designated place to add new, non-gRPC, plain HTTP handlers.
917
// Additional custom HTTP endpoints can be registered on the mux here.
10-
func RegisterCustomHTTPEndpoints(mux *http.ServeMux) {
18+
func RegisterCustomHTTPEndpoints(mux *http.ServeMux, s store.Store, pm p2p.P2PRPC, cfg config.Config, bestKnownHeightProvider BestKnownHeightProvider) {
1119
mux.HandleFunc("/health/live", func(w http.ResponseWriter, r *http.Request) {
1220
w.Header().Set("Content-Type", "text/plain")
1321
w.WriteHeader(http.StatusOK)
1422
fmt.Fprintln(w, "OK")
1523
})
1624

25+
// Readiness endpoint
26+
mux.HandleFunc("/health/ready", func(w http.ResponseWriter, r *http.Request) {
27+
w.Header().Set("Content-Type", "text/plain")
28+
29+
// Peer readiness: non-aggregator nodes should have at least 1 peer
30+
if pm != nil && !cfg.Node.Aggregator {
31+
peers, err := pm.GetPeers()
32+
if err != nil {
33+
http.Error(w, "UNREADY: failed to query peers", http.StatusServiceUnavailable)
34+
return
35+
}
36+
if len(peers) == 0 {
37+
http.Error(w, "UNREADY: no peers connected", http.StatusServiceUnavailable)
38+
return
39+
}
40+
}
41+
42+
localHeight, err := s.Height(r.Context())
43+
if err != nil {
44+
http.Error(w, "UNREADY: state unavailable", http.StatusServiceUnavailable)
45+
return
46+
}
47+
48+
// If no blocks yet, consider unready
49+
if localHeight == 0 {
50+
http.Error(w, "UNREADY: no blocks yet", http.StatusServiceUnavailable)
51+
return
52+
}
53+
54+
// Require best-known height to make the readiness decision
55+
if bestKnownHeightProvider == nil {
56+
http.Error(w, "UNREADY: best-known height unavailable", http.StatusServiceUnavailable)
57+
return
58+
}
59+
60+
bestKnownHeight := bestKnownHeightProvider()
61+
if bestKnownHeight == 0 {
62+
http.Error(w, "UNREADY: best-known height unknown", http.StatusServiceUnavailable)
63+
return
64+
}
65+
66+
allowedBlocksBehind := cfg.Node.ReadinessMaxBlocksBehind
67+
if bestKnownHeight <= localHeight {
68+
// local is ahead of our observed best-known consider ready
69+
w.WriteHeader(http.StatusOK)
70+
fmt.Fprintln(w, "READY")
71+
return
72+
}
73+
74+
if bestKnownHeight-localHeight > allowedBlocksBehind {
75+
http.Error(w, "UNREADY: behind best-known head", http.StatusServiceUnavailable)
76+
return
77+
}
78+
79+
w.WriteHeader(http.StatusOK)
80+
fmt.Fprintln(w, "READY")
81+
})
82+
1783
// DA Visualization endpoints
1884
mux.HandleFunc("/da", func(w http.ResponseWriter, r *http.Request) {
1985
server := GetDAVisualizationServer()

pkg/rpc/server/http_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http/httptest"
77
"testing"
88

9+
"github.com/evstack/ev-node/pkg/config"
910
"github.com/stretchr/testify/assert"
1011
)
1112

@@ -14,7 +15,7 @@ func TestRegisterCustomHTTPEndpoints(t *testing.T) {
1415
mux := http.NewServeMux()
1516

1617
// Register custom HTTP endpoints
17-
RegisterCustomHTTPEndpoints(mux)
18+
RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil)
1819

1920
// Create a new HTTP test server with the mux
2021
testServer := httptest.NewServer(mux)

pkg/rpc/server/server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ func (h *HealthServer) Livez(
308308
}
309309

310310
// NewServiceHandler creates a new HTTP handler for Store, P2P and Health services
311-
func NewServiceHandler(store store.Store, peerManager p2p.P2PRPC, proposerAddress []byte, logger zerolog.Logger, config config.Config) (http.Handler, error) {
311+
func NewServiceHandler(store store.Store, peerManager p2p.P2PRPC, proposerAddress []byte, logger zerolog.Logger, config config.Config, bestKnown BestKnownHeightProvider) (http.Handler, error) {
312312
storeServer := NewStoreServer(store, logger)
313313
p2pServer := NewP2PServer(peerManager)
314314
healthServer := NewHealthServer()
@@ -342,7 +342,7 @@ func NewServiceHandler(store store.Store, peerManager p2p.P2PRPC, proposerAddres
342342
mux.Handle(configPath, configHandler)
343343

344344
// Register custom HTTP endpoints
345-
RegisterCustomHTTPEndpoints(mux)
345+
RegisterCustomHTTPEndpoints(mux, store, peerManager, config, bestKnown)
346346

347347
// Use h2c to support HTTP/2 without TLS
348348
return h2c.NewHandler(mux, &http2.Server{

0 commit comments

Comments
 (0)