Skip to content

Commit

Permalink
Added /ready/sync endpoint (#2060)
Browse files Browse the repository at this point in the history
  • Loading branch information
onuruci authored Aug 28, 2024
1 parent 54e20a6 commit 90d04be
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 3 deletions.
48 changes: 47 additions & 1 deletion node/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import (
"strings"
"time"

"github.com/NethermindEth/juno/blockchain"
"github.com/NethermindEth/juno/db"
junogrpc "github.com/NethermindEth/juno/grpc"
"github.com/NethermindEth/juno/grpc/gen"
"github.com/NethermindEth/juno/jsonrpc"
"github.com/NethermindEth/juno/service"
"github.com/NethermindEth/juno/sync"
"github.com/NethermindEth/juno/utils"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand Down Expand Up @@ -72,7 +74,7 @@ func exactPathServer(path string, handler http.Handler) http.HandlerFunc {
}

func makeRPCOverHTTP(host string, port uint16, servers map[string]*jsonrpc.Server,
log utils.SimpleLogger, metricsEnabled bool, corsEnabled bool,
httpHandlers map[string]http.HandlerFunc, log utils.SimpleLogger, metricsEnabled bool, corsEnabled bool,
) *httpService {
var listener jsonrpc.NewRequestListener
if metricsEnabled {
Expand All @@ -87,6 +89,9 @@ func makeRPCOverHTTP(host string, port uint16, servers map[string]*jsonrpc.Serve
}
mux.Handle(path, exactPathServer(path, httpHandler))
}
for path, handler := range httpHandlers {
mux.HandleFunc(path, handler)
}

var handler http.Handler = mux
if corsEnabled {
Expand All @@ -110,6 +115,7 @@ func makeRPCOverWebsocket(host string, port uint16, servers map[string]*jsonrpc.
wsHandler = wsHandler.WithListener(listener)
}
mux.Handle(path, exactPathServer(path, wsHandler))

wsPrefixedPath := strings.TrimSuffix("/ws"+path, "/")
mux.Handle(wsPrefixedPath, exactPathServer(wsPrefixedPath, wsHandler))
}
Expand Down Expand Up @@ -179,3 +185,43 @@ func makePPROF(host string, port uint16) *httpService {
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
return makeHTTPService(host, port, mux)
}

const SyncBlockRange = 6

type readinessHandlers struct {
bcReader blockchain.Reader
syncReader sync.Reader
}

func NewReadinessHandlers(bcReader blockchain.Reader, syncReader sync.Reader) *readinessHandlers {
return &readinessHandlers{
bcReader: bcReader,
syncReader: syncReader,
}
}

func (h *readinessHandlers) HandleReadySync(w http.ResponseWriter, r *http.Request) {
if !h.isSynced() {
w.WriteHeader(http.StatusServiceUnavailable)
return
}

w.WriteHeader(http.StatusOK)
}

func (h *readinessHandlers) isSynced() bool {
head, err := h.bcReader.HeadsHeader()
if err != nil {
return false
}
highestBlockHeader := h.syncReader.HighestBlockHeader()
if highestBlockHeader == nil {
return false
}

if head.Number > highestBlockHeader.Number {
return false
}

return head.Number+SyncBlockRange >= highestBlockHeader.Number
}
75 changes: 75 additions & 0 deletions node/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package node_test

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/NethermindEth/juno/core"
"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/juno/mocks"
"github.com/NethermindEth/juno/node"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)

func TestHandleReadySync(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

synchronizer := mocks.NewMockSyncReader(mockCtrl)
mockReader := mocks.NewMockReader(mockCtrl)
readinessHandlers := node.NewReadinessHandlers(mockReader, synchronizer)
ctx := context.Background()

t.Run("ready and blockNumber outside blockRange to highestBlock", func(t *testing.T) {
blockNum := uint64(2)
highestBlock := blockNum + node.SyncBlockRange + 1
mockReader.EXPECT().HeadsHeader().Return(&core.Header{Number: blockNum}, nil)
synchronizer.EXPECT().HighestBlockHeader().Return(&core.Header{Number: highestBlock, Hash: new(felt.Felt).SetUint64(highestBlock)})

req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/ready/sync", http.NoBody)
assert.Nil(t, err)

rr := httptest.NewRecorder()

readinessHandlers.HandleReadySync(rr, req)

assert.Equal(t, http.StatusServiceUnavailable, rr.Code)
})

t.Run("ready & blockNumber is larger than highestBlock", func(t *testing.T) {
blockNum := uint64(2)
highestBlock := uint64(1)

mockReader.EXPECT().HeadsHeader().Return(&core.Header{Number: blockNum}, nil)
synchronizer.EXPECT().HighestBlockHeader().Return(&core.Header{Number: highestBlock, Hash: new(felt.Felt).SetUint64(highestBlock)})

req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/ready/sync", http.NoBody)
assert.Nil(t, err)

rr := httptest.NewRecorder()

readinessHandlers.HandleReadySync(rr, req)

assert.Equal(t, http.StatusServiceUnavailable, rr.Code)
})

t.Run("ready & blockNumber is in blockRange of highestBlock", func(t *testing.T) {
blockNum := uint64(3)
highestBlock := blockNum + node.SyncBlockRange

mockReader.EXPECT().HeadsHeader().Return(&core.Header{Number: blockNum}, nil)
synchronizer.EXPECT().HighestBlockHeader().Return(&core.Header{Number: highestBlock, Hash: new(felt.Felt).SetUint64(highestBlock)})

req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/ready/sync", http.NoBody)
assert.Nil(t, err)

rr := httptest.NewRecorder()

readinessHandlers.HandleReadySync(rr, req)

assert.Equal(t, http.StatusOK, rr.Code)
})
}
10 changes: 8 additions & 2 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"reflect"
"runtime"
Expand Down Expand Up @@ -209,10 +210,15 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen
"/rpc" + legacyPath: jsonrpcServerLegacy,
}
if cfg.HTTP {
services = append(services, makeRPCOverHTTP(cfg.HTTPHost, cfg.HTTPPort, rpcServers, log, cfg.Metrics, cfg.RPCCorsEnable))
readinessHandlers := NewReadinessHandlers(chain, synchronizer)
httpHandlers := map[string]http.HandlerFunc{
"/ready/sync": readinessHandlers.HandleReadySync,
}
services = append(services, makeRPCOverHTTP(cfg.HTTPHost, cfg.HTTPPort, rpcServers, httpHandlers, log, cfg.Metrics, cfg.RPCCorsEnable))
}
if cfg.Websocket {
services = append(services, makeRPCOverWebsocket(cfg.WebsocketHost, cfg.WebsocketPort, rpcServers, log, cfg.Metrics, cfg.RPCCorsEnable))
services = append(services,
makeRPCOverWebsocket(cfg.WebsocketHost, cfg.WebsocketPort, rpcServers, log, cfg.Metrics, cfg.RPCCorsEnable))
}
var metricsService service.Service
if cfg.Metrics {
Expand Down

0 comments on commit 90d04be

Please sign in to comment.