Skip to content

Commit d762023

Browse files
authored
feat(api): Add a /ready endpoint (#192)
* Add a /ready endpoint This helps to know when the node is on sync with the data on the chain. Changed the /health response to have the same format as the /ready one. Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com> * linter Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com> * linter field alignment Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com> * linter line lenght Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com> --------- Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com>
1 parent 7e98856 commit d762023

File tree

4 files changed

+70
-11
lines changed

4 files changed

+70
-11
lines changed

cmd/start.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func (c *startCfg) exec(ctx context.Context) error {
197197

198198
mux = j.SetupRoutes(mux)
199199
mux = graph.Setup(db, em, mux, c.disableIntrospection)
200-
mux = health.Setup(db, mux)
200+
mux = health.Setup(db, f, mux)
201201

202202
// Create the HTTP server
203203
hs := serve.NewHTTPServer(mux, c.listenAddress, logger.Named("http-server"))

fetch/fetch.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import (
88
"sort"
99
"time"
1010

11-
queue "github.com/madz-lab/insertion-queue"
12-
"go.uber.org/zap"
13-
1411
"github.com/gnolang/gno/gno.land/pkg/gnoland"
1512
"github.com/gnolang/gno/tm2/pkg/amino"
1613
bft_types "github.com/gnolang/gno/tm2/pkg/bft/types"
14+
queue "github.com/madz-lab/insertion-queue"
15+
"go.uber.org/zap"
16+
1717
"github.com/gnolang/tx-indexer/storage"
1818
storageErrors "github.com/gnolang/tx-indexer/storage/errors"
1919
"github.com/gnolang/tx-indexer/types"
@@ -36,8 +36,9 @@ type Fetcher struct {
3636
logger *zap.Logger
3737
chunkBuffer *slots
3838

39-
maxSlots int
40-
maxChunkSize int64
39+
maxSlots int
40+
maxChunkSize int64
41+
latestChunkSize int
4142

4243
queryInterval time.Duration // block query interval
4344
}
@@ -316,9 +317,25 @@ func (f *Fetcher) writeSlot(s *slot) error {
316317
return fmt.Errorf("error persisting block information into storage, %w", err)
317318
}
318319

320+
f.latestChunkSize = len(s.chunk.blocks)
321+
319322
return nil
320323
}
321324

325+
func (f *Fetcher) IsReady() (bool, error) {
326+
if f.latestChunkSize == int(f.maxChunkSize) {
327+
return false, fmt.Errorf("the data synchronization process is still in progress and hasn't "+
328+
"caught up with the current blockchain state. Chunk size: %d", f.latestChunkSize)
329+
}
330+
331+
_, err := f.client.GetLatestBlockNumber()
332+
if err != nil {
333+
return false, fmt.Errorf("node RPC method is not reachable: %w", err)
334+
}
335+
336+
return true, nil
337+
}
338+
322339
func getGenesisBlock(client Client) (*bft_types.Block, error) {
323340
gblock, err := client.GetGenesis()
324341
if err != nil {

fetch/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ func WithMaxSlots(maxSlots int) Option {
2525
func WithMaxChunkSize(maxChunkSize int64) Option {
2626
return func(f *Fetcher) {
2727
f.maxChunkSize = maxChunkSize
28+
f.latestChunkSize = int(maxChunkSize)
2829
}
2930
}

serve/health/health.go

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,61 @@ import (
1111
"github.com/gnolang/tx-indexer/storage"
1212
)
1313

14-
func Setup(s storage.Storage, m *chi.Mux) *chi.Mux {
14+
func Setup(s storage.Storage, rc ReadyChecker, m *chi.Mux) *chi.Mux {
1515
m.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
1616
h, err := s.GetLatestHeight()
1717
if err != nil {
18-
fmt.Fprintf(w, "ERROR: %s\n", err)
18+
render.JSON(w, r, &response{
19+
Message: fmt.Sprintf("storage is not reachable: %s", err.Error()),
20+
Info: map[string]any{
21+
"time": time.Now().String(),
22+
},
23+
})
24+
25+
render.Status(r, http.StatusInternalServerError)
26+
27+
return
28+
}
29+
30+
render.JSON(w, r, &response{
31+
Message: "Server is responding",
32+
Info: map[string]any{
33+
"time": time.Now().String(),
34+
"height": h,
35+
},
36+
})
37+
})
38+
39+
m.HandleFunc("/ready", func(w http.ResponseWriter, r *http.Request) {
40+
ok, err := rc.IsReady()
41+
if !ok {
42+
render.JSON(w, r, &response{
43+
Message: fmt.Sprintf("node not ready: %s", err.Error()),
44+
Info: map[string]any{
45+
"time": time.Now().String(),
46+
},
47+
})
1948
render.Status(r, http.StatusInternalServerError)
2049

2150
return
2251
}
2352

24-
fmt.Fprintf(w, "Server is responding\n")
25-
fmt.Fprintf(w, "- Time: %s\n", time.Now())
26-
fmt.Fprintf(w, "- Latest Height: %d\n", h)
53+
render.JSON(w, r, &response{
54+
Message: "node is ready",
55+
Info: map[string]any{
56+
"time": time.Now().String(),
57+
},
58+
})
2759
})
2860

2961
return m
3062
}
63+
64+
type response struct {
65+
Info map[string]any `json:"info"`
66+
Message string `json:"message"`
67+
}
68+
69+
type ReadyChecker interface {
70+
IsReady() (bool, error)
71+
}

0 commit comments

Comments
 (0)