Skip to content

Commit 90d04be

Browse files
authored
Added /ready/sync endpoint (#2060)
1 parent 54e20a6 commit 90d04be

File tree

3 files changed

+130
-3
lines changed

3 files changed

+130
-3
lines changed

node/http.go

+47-1
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import (
1111
"strings"
1212
"time"
1313

14+
"github.com/NethermindEth/juno/blockchain"
1415
"github.com/NethermindEth/juno/db"
1516
junogrpc "github.com/NethermindEth/juno/grpc"
1617
"github.com/NethermindEth/juno/grpc/gen"
1718
"github.com/NethermindEth/juno/jsonrpc"
1819
"github.com/NethermindEth/juno/service"
20+
"github.com/NethermindEth/juno/sync"
1921
"github.com/NethermindEth/juno/utils"
2022
"github.com/prometheus/client_golang/prometheus"
2123
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -72,7 +74,7 @@ func exactPathServer(path string, handler http.Handler) http.HandlerFunc {
7274
}
7375

7476
func makeRPCOverHTTP(host string, port uint16, servers map[string]*jsonrpc.Server,
75-
log utils.SimpleLogger, metricsEnabled bool, corsEnabled bool,
77+
httpHandlers map[string]http.HandlerFunc, log utils.SimpleLogger, metricsEnabled bool, corsEnabled bool,
7678
) *httpService {
7779
var listener jsonrpc.NewRequestListener
7880
if metricsEnabled {
@@ -87,6 +89,9 @@ func makeRPCOverHTTP(host string, port uint16, servers map[string]*jsonrpc.Serve
8789
}
8890
mux.Handle(path, exactPathServer(path, httpHandler))
8991
}
92+
for path, handler := range httpHandlers {
93+
mux.HandleFunc(path, handler)
94+
}
9095

9196
var handler http.Handler = mux
9297
if corsEnabled {
@@ -110,6 +115,7 @@ func makeRPCOverWebsocket(host string, port uint16, servers map[string]*jsonrpc.
110115
wsHandler = wsHandler.WithListener(listener)
111116
}
112117
mux.Handle(path, exactPathServer(path, wsHandler))
118+
113119
wsPrefixedPath := strings.TrimSuffix("/ws"+path, "/")
114120
mux.Handle(wsPrefixedPath, exactPathServer(wsPrefixedPath, wsHandler))
115121
}
@@ -179,3 +185,43 @@ func makePPROF(host string, port uint16) *httpService {
179185
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
180186
return makeHTTPService(host, port, mux)
181187
}
188+
189+
const SyncBlockRange = 6
190+
191+
type readinessHandlers struct {
192+
bcReader blockchain.Reader
193+
syncReader sync.Reader
194+
}
195+
196+
func NewReadinessHandlers(bcReader blockchain.Reader, syncReader sync.Reader) *readinessHandlers {
197+
return &readinessHandlers{
198+
bcReader: bcReader,
199+
syncReader: syncReader,
200+
}
201+
}
202+
203+
func (h *readinessHandlers) HandleReadySync(w http.ResponseWriter, r *http.Request) {
204+
if !h.isSynced() {
205+
w.WriteHeader(http.StatusServiceUnavailable)
206+
return
207+
}
208+
209+
w.WriteHeader(http.StatusOK)
210+
}
211+
212+
func (h *readinessHandlers) isSynced() bool {
213+
head, err := h.bcReader.HeadsHeader()
214+
if err != nil {
215+
return false
216+
}
217+
highestBlockHeader := h.syncReader.HighestBlockHeader()
218+
if highestBlockHeader == nil {
219+
return false
220+
}
221+
222+
if head.Number > highestBlockHeader.Number {
223+
return false
224+
}
225+
226+
return head.Number+SyncBlockRange >= highestBlockHeader.Number
227+
}

node/http_test.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package node_test
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/NethermindEth/juno/core"
10+
"github.com/NethermindEth/juno/core/felt"
11+
"github.com/NethermindEth/juno/mocks"
12+
"github.com/NethermindEth/juno/node"
13+
"github.com/stretchr/testify/assert"
14+
"go.uber.org/mock/gomock"
15+
)
16+
17+
func TestHandleReadySync(t *testing.T) {
18+
mockCtrl := gomock.NewController(t)
19+
t.Cleanup(mockCtrl.Finish)
20+
21+
synchronizer := mocks.NewMockSyncReader(mockCtrl)
22+
mockReader := mocks.NewMockReader(mockCtrl)
23+
readinessHandlers := node.NewReadinessHandlers(mockReader, synchronizer)
24+
ctx := context.Background()
25+
26+
t.Run("ready and blockNumber outside blockRange to highestBlock", func(t *testing.T) {
27+
blockNum := uint64(2)
28+
highestBlock := blockNum + node.SyncBlockRange + 1
29+
mockReader.EXPECT().HeadsHeader().Return(&core.Header{Number: blockNum}, nil)
30+
synchronizer.EXPECT().HighestBlockHeader().Return(&core.Header{Number: highestBlock, Hash: new(felt.Felt).SetUint64(highestBlock)})
31+
32+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/ready/sync", http.NoBody)
33+
assert.Nil(t, err)
34+
35+
rr := httptest.NewRecorder()
36+
37+
readinessHandlers.HandleReadySync(rr, req)
38+
39+
assert.Equal(t, http.StatusServiceUnavailable, rr.Code)
40+
})
41+
42+
t.Run("ready & blockNumber is larger than highestBlock", func(t *testing.T) {
43+
blockNum := uint64(2)
44+
highestBlock := uint64(1)
45+
46+
mockReader.EXPECT().HeadsHeader().Return(&core.Header{Number: blockNum}, nil)
47+
synchronizer.EXPECT().HighestBlockHeader().Return(&core.Header{Number: highestBlock, Hash: new(felt.Felt).SetUint64(highestBlock)})
48+
49+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/ready/sync", http.NoBody)
50+
assert.Nil(t, err)
51+
52+
rr := httptest.NewRecorder()
53+
54+
readinessHandlers.HandleReadySync(rr, req)
55+
56+
assert.Equal(t, http.StatusServiceUnavailable, rr.Code)
57+
})
58+
59+
t.Run("ready & blockNumber is in blockRange of highestBlock", func(t *testing.T) {
60+
blockNum := uint64(3)
61+
highestBlock := blockNum + node.SyncBlockRange
62+
63+
mockReader.EXPECT().HeadsHeader().Return(&core.Header{Number: blockNum}, nil)
64+
synchronizer.EXPECT().HighestBlockHeader().Return(&core.Header{Number: highestBlock, Hash: new(felt.Felt).SetUint64(highestBlock)})
65+
66+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/ready/sync", http.NoBody)
67+
assert.Nil(t, err)
68+
69+
rr := httptest.NewRecorder()
70+
71+
readinessHandlers.HandleReadySync(rr, req)
72+
73+
assert.Equal(t, http.StatusOK, rr.Code)
74+
})
75+
}

node/node.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"net/http"
78
"net/url"
89
"reflect"
910
"runtime"
@@ -209,10 +210,15 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen
209210
"/rpc" + legacyPath: jsonrpcServerLegacy,
210211
}
211212
if cfg.HTTP {
212-
services = append(services, makeRPCOverHTTP(cfg.HTTPHost, cfg.HTTPPort, rpcServers, log, cfg.Metrics, cfg.RPCCorsEnable))
213+
readinessHandlers := NewReadinessHandlers(chain, synchronizer)
214+
httpHandlers := map[string]http.HandlerFunc{
215+
"/ready/sync": readinessHandlers.HandleReadySync,
216+
}
217+
services = append(services, makeRPCOverHTTP(cfg.HTTPHost, cfg.HTTPPort, rpcServers, httpHandlers, log, cfg.Metrics, cfg.RPCCorsEnable))
213218
}
214219
if cfg.Websocket {
215-
services = append(services, makeRPCOverWebsocket(cfg.WebsocketHost, cfg.WebsocketPort, rpcServers, log, cfg.Metrics, cfg.RPCCorsEnable))
220+
services = append(services,
221+
makeRPCOverWebsocket(cfg.WebsocketHost, cfg.WebsocketPort, rpcServers, log, cfg.Metrics, cfg.RPCCorsEnable))
216222
}
217223
var metricsService service.Service
218224
if cfg.Metrics {

0 commit comments

Comments
 (0)