Skip to content

Commit

Permalink
Made the healthcheck more robust and added some recovery mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
kpachhai committed Dec 4, 2024
1 parent 2742dd8 commit a5d377b
Show file tree
Hide file tree
Showing 16 changed files with 144 additions and 16 deletions.
3 changes: 3 additions & 0 deletions api/action.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package api

import (
Expand Down
3 changes: 3 additions & 0 deletions api/asset.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package api

import (
Expand Down
3 changes: 3 additions & 0 deletions api/block.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package api

import (
Expand Down
3 changes: 3 additions & 0 deletions api/genesis.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package api

import (
Expand Down
55 changes: 48 additions & 7 deletions api/health.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package api

import (
"database/sql"
"log"
"net"
"net/http"
"time"

"github.com/gin-gonic/gin"
)

// HealthCheck performs a comprehensive health check of the subscriber
func HealthCheck() gin.HandlerFunc {
// checkGRPC checks the reachability of the gRPC server.
func checkGRPC(grpcPort string) string {
conn, err := net.DialTimeout("tcp", grpcPort, 2*time.Second)
if err != nil {
log.Printf("gRPC connection failed: %v", err)
return "unreachable"
}
defer conn.Close()
return "reachable"
}

// checkDatabase checks the reachability of the database.
func checkDatabase(db *sql.DB) string {
if err := db.Ping(); err != nil {
log.Printf("Database connection failed: %v", err)
return "unreachable"
}
return "reachable"
}

// HealthCheck performs a comprehensive health check of the subscriber.
func HealthCheck(grpcPort string, db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
// If all checks pass
c.JSON(http.StatusOK, gin.H{
"status": "ok",
grpcStatus := checkGRPC(grpcPort)
databaseStatus := checkDatabase(db)

// Determine the overall status
status := "ok"
if grpcStatus == "unreachable" || databaseStatus == "unreachable" {
status = "service unavailable"
}

// Return the health status
httpStatusCode := http.StatusOK
if status == "service unavailable" {
httpStatusCode = http.StatusServiceUnavailable
}

c.JSON(httpStatusCode, gin.H{
"status": status,
"details": gin.H{
"database": "reachable",
"grpc": "reachable",
"database": databaseStatus,
"grpc": grpcStatus,
},
})
}
Expand Down
3 changes: 3 additions & 0 deletions api/transaction.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package api

import (
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package config

import (
Expand Down
3 changes: 3 additions & 0 deletions consts/action_names.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package consts

import "github.com/nuklai/nuklaivm/consts"
Expand Down
3 changes: 3 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package db

import (
Expand Down
50 changes: 45 additions & 5 deletions grpc/server.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package grpc

import (
"context"
"database/sql"
"encoding/json"
"errors"
"log"
"net"
"strings"
Expand Down Expand Up @@ -31,8 +35,14 @@ type Server struct {
parser chain.Parser
}

// startGRPCServer starts the gRPC server for receiving block data
func StartGRPCServer(db *sql.DB, port string) {
// StartGRPCServer starts the gRPC server for receiving block data
func StartGRPCServer(db *sql.DB, port string) error {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in StartGRPCServer: %v", r)
}
}()

// Load the whitelist
LoadWhitelist()

Expand All @@ -43,7 +53,8 @@ func StartGRPCServer(db *sql.DB, port string) {

lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("Failed to listen on port %s: %v", port, err)
log.Printf("Failed to listen on port %s: %v", port, err)
return err
}

// Use insecure credentials to allow plaintext communication
Expand All @@ -60,14 +71,32 @@ func StartGRPCServer(db *sql.DB, port string) {
reflection.Register(grpcServer)

log.Printf("External Subscriber server is listening on port %s...\n", port)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
return grpcServer.Serve(lis)
}

// StartGRPCServerWithRetries retries gRPC server startup in case of failure
func StartGRPCServerWithRetries(db *sql.DB, port string, retries int) {
for i := 0; i < retries; i++ {
err := StartGRPCServer(db, port)
if err != nil {
log.Printf("gRPC server failed to start: %v. Retrying (%d/%d)...", err, i+1, retries)
time.Sleep(5 * time.Second)
continue
}
return // Successful start
}
log.Fatal("gRPC server failed to start after maximum retries")
}

// Initialize receives genesis data for initialization and saves it to the database
// Initialize receives genesis data for initialization and saves it to the database
func (s *Server) Initialize(ctx context.Context, req *pb.InitializeRequest) (*emptypb.Empty, error) {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in Initialize: %v", r)
}
}()

log.Println("Initializing External Subscriber with genesis data...")

// Decode genesis data
Expand Down Expand Up @@ -108,6 +137,17 @@ func (s *Server) Initialize(ctx context.Context, req *pb.InitializeRequest) (*em

// AcceptBlock processes a new block, saves relevant data to the database, and stores transactions and actions
func (s *Server) AcceptBlock(ctx context.Context, req *pb.BlockRequest) (*emptypb.Empty, error) {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in AcceptBlock: %v", r)
}
}()

if s.parser == nil {
log.Println("Parser is not initialized. Rejecting the request.")
return nil, errors.New("parser not initialized")
}

blockData := req.GetBlockData()

// Attempt to unmarshal the executed block using UnmarshalExecutedBlock
Expand Down
3 changes: 3 additions & 0 deletions grpc/whitelist.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package grpc

import (
Expand Down
16 changes: 12 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Additional API endpoints to retrieve blockchain-related information from the database
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package main

import (
Expand All @@ -25,10 +27,16 @@ func main() {
defer database.Close()

// Start the gRPC server
go grpc.StartGRPCServer(database, "50051")
grpcPort := "50051"
go grpc.StartGRPCServerWithRetries(database, grpcPort, 60)

// Setup Gin router
r := gin.Default()
gin.SetMode(gin.ReleaseMode)

r := gin.New()
r.Use(gin.Logger(), gin.Recovery())

r.SetTrustedProxies(nil)

// Add CORS middleware
r.Use(cors.New(cors.Config{
Expand All @@ -40,7 +48,7 @@ func main() {
}))

// Health endpoint
r.GET("/health", api.HealthCheck())
r.GET("/health", api.HealthCheck(":"+grpcPort, database))

// Other endpoints
r.GET("/genesis", api.GetGenesisData(database))
Expand Down
3 changes: 3 additions & 0 deletions models/action.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package models

import (
Expand Down
3 changes: 3 additions & 0 deletions models/asset.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package models

import (
Expand Down
3 changes: 3 additions & 0 deletions models/block.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package models

import (
Expand Down
3 changes: 3 additions & 0 deletions models/transaction.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package models

import (
Expand Down

0 comments on commit a5d377b

Please sign in to comment.