Skip to content

Commit

Permalink
Merge pull request #24 from Nuklai/patch/endpoint/fixes
Browse files Browse the repository at this point in the history
Few fixes & few additions
  • Loading branch information
Developerayo authored Feb 10, 2025
2 parents 5054403 + e326d45 commit 2e3b734
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 30 deletions.
33 changes: 20 additions & 13 deletions api/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"database/sql"
"log"
"net/http"
"strconv"

"github.com/nuklai/nuklaivm-external-subscriber/models"

Expand Down Expand Up @@ -57,18 +58,24 @@ func GetAllBlocks(db *sql.DB) gin.HandlerFunc {
}
}

// func GetBlock(db *sql.DB) gin.HandlerFunc {
// return func(c *gin.Context) {
// blockIdentifier := c.Param("identifier")
func GetBlock(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
blockIdentifier := c.Param("identifier")

var height, hash string
if _, err := strconv.ParseInt(blockIdentifier, 10, 64); err == nil {
height = blockIdentifier
} else {
hash = blockIdentifier
}

// block, err := models.FetchBlock(db, blockIdentifier)
// // Check for query errors
// if err != nil {
// log.Printf("Error fetching block: %v", err)
// c.JSON(http.StatusNotFound, gin.H{"error": "Block not found"})
// return
// }
block, err := models.FetchBlock(db, height, hash)
if err != nil {
log.Printf("Error fetching block: %v", err)
c.JSON(http.StatusNotFound, gin.H{"error": "Block not found"})
return
}

// c.JSON(http.StatusOK, block)
// }
// }
c.JSON(http.StatusOK, block)
}
}
14 changes: 14 additions & 0 deletions api/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,17 @@ func GetHealthHistory(db *sql.DB) gin.HandlerFunc {
c.JSON(http.StatusOK, events)
}
}

// Get90DayHealth retrives a 90day health summary
func Get90DayHealth(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
summaries, err := models.Fetch90DayHealth(db)
if err != nil {
log.Printf("Error fetching 90-day health history: %v", err)
c.JSON(http.StatusInternalServerError,
gin.H{"error": "Unable to retrieve health history"})
return
}
c.JSON(http.StatusOK, summaries)
}
}
13 changes: 13 additions & 0 deletions api/health_monitor.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"context"
"database/sql"
"fmt"
"log"
Expand Down Expand Up @@ -199,6 +200,14 @@ func (h *HealthMonitor) GetHealthStatus() models.HealthStatus {
h.mu.Lock()
defer h.mu.Unlock()

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := h.db.QueryRowContext(ctx, `SELECT 1 FROM daily_health_summaries WHERE date = $1`,
time.Now().UTC().Truncate(24*time.Hour)).Err(); err == sql.ErrNoRows {
models.UpdateDailyHealthSummary(h.db, h.currentStatus)
}

blockchainStatus, blockchainStats := h.FetchBlockchainHealth()

h.currentStatus.Details = map[string]bool{
Expand Down Expand Up @@ -227,5 +236,9 @@ func (h *HealthMonitor) GetHealthStatus() models.HealthStatus {
h.UpdateHealthState(models.HealthStateGreen, "", nil)
}

if err := models.UpdateDailyHealthSummary(h.db, h.currentStatus); err != nil {
log.Printf("Error updating daily health summary: %v", err)
}

return h.currentStatus
}
13 changes: 13 additions & 0 deletions api/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,19 @@ func GetActionVolumesByName(db *sql.DB) gin.HandlerFunc {
}
}

func GetTotalActionCounts(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
totals, err := models.FetchActionVolumes(db)
if err != nil {
log.Printf("Error fetching action totals: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to retrieve action totals"})
return
}

c.JSON(http.StatusOK, totals)
}
}

// GetEstimatedFeeByActionType retrieves the estimated fee for a specific action type
func GetEstimatedFeeByActionType(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
Expand Down
23 changes: 23 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"database/sql"
"fmt"
"log"
"time"

_ "github.com/lib/pq"
"github.com/nuklai/nuklaivm-external-subscriber/config"
Expand All @@ -19,6 +20,12 @@ func InitDB(connStr string) (*sql.DB, error) {
return nil, fmt.Errorf("error connecting to the database: %w", err)
}

// Set connection pool parameters
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(5 * time.Minute)

if err = db.Ping(); err != nil {
return nil, fmt.Errorf("error pinging the database: %w", err)
}
Expand Down Expand Up @@ -134,6 +141,18 @@ func CreateSchema(db *sql.DB) error {
timestamp TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS daily_health_summaries (
date DATE PRIMARY KEY,
state VARCHAR(10) NOT NULL,
incidents TEXT[],
last_updated TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS action_volumes (
action_type SMALLINT PRIMARY KEY,
action_name TEXT NOT NULL,
total_count BIGINT NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS genesis_data (
id SERIAL PRIMARY KEY,
Expand Down Expand Up @@ -162,6 +181,10 @@ func CreateSchema(db *sql.DB) error {
CREATE INDEX IF NOT EXISTS idx_validator_stake_timestamp ON validator_stake(timestamp);
CREATE INDEX IF NOT EXISTS idx_health_events_state ON health_events(state);
CREATE INDEX IF NOT EXISTS idx_health_events_timestamp ON health_events(timestamp);
CREATE INDEX IF NOT EXISTS idx_daily_health_summaries_date ON daily_health_summaries(date);
CREATE INDEX IF NOT EXISTS idx_daily_health_summaries_state ON daily_health_summaries(state);
CREATE INDEX IF NOT EXISTS idx_daily_health_summaries_last_updated ON daily_health_summaries(last_updated);
CREATE INDEX IF NOT EXISTS idx_action_volumes_name ON action_volumes(action_name);
`

Expand Down
29 changes: 29 additions & 0 deletions docs/rest_api/health.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,32 @@
}
]
```

## Get 90-Day Health History

- **Endpoint**: `/health/history/90days`
- **Description**: Retrieves data for health daily for the last 90 days. Each day records the worst health state.
- **Example**: `curl http://localhost:8080/health/history/90days`
- **Output**:

```json
[
{
"date": "2025-02-06T00:00:00Z",
"state": "red",
"incidents": [
"CRITICAL: NuklaiVM Unresponsive\n- Error: Connection to NuklaiVM lost - no new blocks in 53h3m12s\n- Last Block Height: 138\n- Last Block Time: 2025-02-04T09:48:45Z\n- Block Age: 53h3m12s\n"
]
},
{
"date": "2025-02-05T00:00:00Z",
"state": "yellow",
"incidents": ["High Latency - Response Time: 2.50s"]
},
{
"date": "2025-02-04T00:00:00Z",
"state": "green",
"incidents": []
}
]
```
22 changes: 22 additions & 0 deletions docs/rest_api/transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,28 @@
}
```

## Get Total Action Destribution Counts

- **Endpoint**: `/transactions/volumes/actions/total`
- **Description**: Retrieves the all-time total count for each action type
- **Example**: `curl http://localhost:8080/transactions/volumes/actions/total`
- **Output**:

```json
[
{
"action_type": 0,
"action_name": "Transfer",
"total_count": 8350724
},
{
"action_type": 4,
"action_name": "CreateAsset",
"total_count": 37423
}
]
```

## Get Aggregated Estimated Fees for different Transactions

- **Endpoint**: `/transactions/estimated_fee`
Expand Down
20 changes: 15 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,24 @@ func main() {
}))

// Health endpoint
r.GET("/health", api.GetHealth(healthMonitor)) // Get the current health status
r.GET("/health/history", api.GetHealthHistory(database)) // Get health insidents
r.GET("/health", api.GetHealth(healthMonitor)) // Get the current health status
r.GET("/health/history", api.GetHealthHistory(database)) // Get health insidents
r.GET("/health/history/90days", api.Get90DayHealth(database)) // Get 90-day health history

// Other endpoints
r.GET("/genesis", api.GetGenesisData(database))

r.GET("/blocks", api.GetAllBlocks(database))
r.GET("/blocks", api.GetAllBlocks(database)) // Get all blocks
r.GET("/blocks/:identifier", api.GetBlock(database)) // Get blocks by height or hash

r.GET("/transactions", api.GetAllTransactions(database))
r.GET("/transactions/:tx_hash", api.GetTransactionByHash(database)) // Fetch by transaction hash
r.GET("/transactions/block/:identifier", api.GetTransactionsByBlock(database)) // Fetch transactions by block height or hash
r.GET("/transactions/user/:user", api.GetTransactionsByUser(database))
r.GET("/transactions/volumes", api.GetAllActionVolumes(database))
r.GET("/transactions/volumes/:action_name", api.GetActionVolumesByName(database))
r.GET("/transactions/volumes/total", api.GetTotalTransferVolume(database)) // Fetch transactions by user with pagination
r.GET("/transactions/volumes/actions/total", api.GetTotalActionCounts(database)) // Fetch alltime actions volume
r.GET("/transactions/volumes/total", api.GetTotalTransferVolume(database)) // Fetch alltime transfer volume
r.GET("/transactions/estimated_fee/action_type/:action_type", api.GetEstimatedFeeByActionType(database)) // Fetch estimated fee by action type
r.GET("/transactions/estimated_fee/action_name/:action_name", api.GetEstimatedFeeByActionName(database)) // Fetch estimated fee by action name
r.GET("/transactions/estimated_fee", api.GetAggregateEstimatedFees(database)) // Fetch aggregate estimated fees
Expand All @@ -93,17 +96,24 @@ func main() {
defer ticker.Stop()

var lastState models.HealthState
var lastDate time.Time

status := healthMonitor.GetHealthStatus()
lastState = status.State
lastDate = time.Now().UTC().Truncate(24 * time.Hour)

// Check every 6 seconds
for range ticker.C {
status := healthMonitor.GetHealthStatus()
currentDate := time.Now().UTC().Truncate(24 * time.Hour)

if status.State != lastState {
lastState = status.State
}

if !currentDate.Equal(lastDate) {
lastDate = currentDate
lastState = status.State
}
}
}()

Expand Down
99 changes: 99 additions & 0 deletions models/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
package models

import (
"context"
"database/sql"
"fmt"
"time"

"github.com/lib/pq"
)

type HealthState string
Expand Down Expand Up @@ -49,3 +54,97 @@ type HealthStatus struct {
BlockchainStats *BlockchainStats `json:"blockchain_stats"`
CurrentIncident *HealthEvent `json:"current_incident"`
}

type DailyHealthSummary struct {
Date time.Time `json:"date"`
State HealthState `json:"state"`
Incidents []string `json:"incidents"`
}

func UpdateDailyHealthSummary(db *sql.DB, currentStatus HealthStatus) error {
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if tx != nil {
if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}
}()

today := time.Now().UTC().Truncate(24 * time.Hour)

var currentState HealthState
var incidents []string

if currentStatus.State == HealthStateRed {
currentState = HealthStateRed
} else if currentStatus.State == HealthStateYellow {
currentState = HealthStateYellow
} else {
currentState = HealthStateGreen
}

if currentStatus.CurrentIncident != nil {
incidents = []string{currentStatus.CurrentIncident.Description}
}

_, execErr := tx.Exec(`
INSERT INTO daily_health_summaries (date, state, incidents, last_updated)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (date) DO UPDATE
SET state = CASE
WHEN daily_health_summaries.state = 'red' OR EXCLUDED.state = 'red' THEN 'red'
WHEN daily_health_summaries.state = 'yellow' OR EXCLUDED.state = 'yellow' THEN 'yellow'
ELSE 'green'
END,
incidents = CASE
WHEN EXCLUDED.incidents IS NOT NULL AND EXCLUDED.incidents != '{}'::text[]
THEN EXCLUDED.incidents -- Keep only latest incident
ELSE daily_health_summaries.incidents
END,
last_updated = NOW()`,
today, currentState, pq.Array(incidents))

if execErr != nil {
err = execErr
return err
}

return nil
}

func Fetch90DayHealth(db *sql.DB) ([]DailyHealthSummary, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

rows, err := db.QueryContext(ctx, `
SELECT date, state, incidents
FROM daily_health_summaries
WHERE date > NOW() - INTERVAL '90 days'
ORDER BY date DESC`)
if err != nil {
return nil, fmt.Errorf("failed to query health summaries: %w", err)
}
defer rows.Close()

var summaries []DailyHealthSummary
for rows.Next() {
var summary DailyHealthSummary
if err := rows.Scan(&summary.Date, &summary.State,
pq.Array(&summary.Incidents)); err != nil {
return nil, fmt.Errorf("failed to scan health summary: %w", err)
}
summaries = append(summaries, summary)
}

if err = rows.Err(); err != nil {
return nil, fmt.Errorf("error: %w", err)
}

return summaries, nil
}
Loading

0 comments on commit 2e3b734

Please sign in to comment.