Skip to content

Commit 52deb6e

Browse files
committed
improve /health, refactor error handling
fixes #1
1 parent 86af520 commit 52deb6e

File tree

9 files changed

+304
-97
lines changed

9 files changed

+304
-97
lines changed

controllers/blobs.go

Lines changed: 21 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,59 +3,28 @@ package controllers
33
import (
44
"blob-service/dto"
55
"blob-service/internal"
6-
pbbl "blob-service/pb/pinax/ethereum/blobs/v1"
6+
"blob-service/services"
77
"context"
8-
"encoding/binary"
9-
"fmt"
108
"strconv"
119
"strings"
1210
"time"
1311

14-
pbkv "github.com/streamingfast/substreams-sink-kv/pb/substreams/sink/kv/v1"
15-
16-
"github.com/eosnationftw/eosn-base-api/helper"
1712
"github.com/eosnationftw/eosn-base-api/response"
1813
"github.com/gin-gonic/gin"
19-
"github.com/golang/protobuf/proto"
2014
"google.golang.org/grpc/codes"
2115
"google.golang.org/grpc/status"
2216
)
2317

24-
const (
25-
NOT_FOUND_SLOT = "slot_not_found" // no slot found
26-
INVALID_SLOT = "invalid_slot" // invalid slot
27-
)
28-
2918
type BlobsController struct {
30-
sinkClient pbkv.KvClient
19+
blobsService *services.BlobsService
3120
}
3221

33-
func NewBlobsController(sinkClient pbkv.KvClient) *BlobsController {
34-
return &BlobsController{sinkClient: sinkClient}
22+
func NewBlobsController(blobsService *services.BlobsService) *BlobsController {
23+
return &BlobsController{blobsService: blobsService}
3524
}
3625

3726
type blobsBySlotRetType []*dto.Blob
3827

39-
func (bc *BlobsController) parseBlockId(ctx context.Context, block_id string) (uint64, error) {
40-
if block_id == "head" {
41-
resp, err := bc.sinkClient.Get(ctx, &pbkv.GetRequest{Key: "head"})
42-
if err != nil {
43-
return 0, err
44-
}
45-
return binary.BigEndian.Uint64(resp.GetValue()), nil
46-
}
47-
48-
if block_id[:2] == "0x" {
49-
resp, err := bc.sinkClient.Get(ctx, &pbkv.GetRequest{Key: "block_root:" + block_id})
50-
if err != nil {
51-
return 0, err
52-
}
53-
return binary.BigEndian.Uint64(resp.GetValue()), nil
54-
}
55-
56-
return strconv.ParseUint(block_id, 10, 64)
57-
}
58-
5928
// BlobsByBlockId
6029
//
6130
// @Summary Get Blobs by block id
@@ -64,61 +33,47 @@ func (bc *BlobsController) parseBlockId(ctx context.Context, block_id string) (u
6433
// @Param block_id path string true "Block identifier. Can be one of: 'head', slot number, hex encoded blockRoot with 0x prefix"
6534
// @Param indices query []string false "Array of indices for blob sidecars to request for in the specified block. Returns all blob sidecars in the block if not specified."
6635
// @Success 200 {object} response.ApiDataResponse{data=blobsBySlotRetType} "Successful response"
67-
// @Failure 400 {object} response.ApiErrorResponse "invalid_slot" "Invalid block id
36+
// @Failure 400 {object} response.ApiErrorResponse "invalid_slot" "Invalid block id"
6837
// @Failure 404 {object} response.ApiErrorResponse "slot_not_found" "Slot not found"
6938
// @Failure 500 {object} response.ApiErrorResponse
7039
// @Router /eth/v1/beacon/blob_sidecars/{block_id} [get]
7140
func (bc *BlobsController) BlobsByBlockId(c *gin.Context) {
7241

7342
blockId := c.Param("block_id")
74-
indices := strings.Split(c.Query("indices"), ",")
75-
if len(indices) == 1 && indices[0] == "" {
76-
indices = []string{}
77-
}
78-
79-
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
80-
defer cancel()
81-
82-
slotNum, err := bc.parseBlockId(ctx, blockId)
83-
if err != nil {
84-
if ctx.Err() == context.DeadlineExceeded {
85-
helper.ReportPublicErrorAndAbort(c, response.GatewayTimeout, err)
86-
return
43+
indices := []uint32{}
44+
for _, str := range strings.Split(c.Query("indices"), ",") {
45+
if str == "" {
46+
continue
8747
}
88-
st, ok := status.FromError(err)
89-
if ok && st.Code() == codes.NotFound {
90-
helper.ReportPublicErrorAndAbort(c, response.NewApiErrorNotFound(NOT_FOUND_SLOT), err)
48+
i, err := strconv.ParseUint(str, 10, 32)
49+
if err != nil {
50+
internal.WriteErrorResponse(c, internal.ErrInvalidIndex)
9151
return
9252
}
93-
helper.ReportPublicErrorAndAbort(c, response.BadGateway, err)
94-
return
53+
indices = append(indices, uint32(i))
9554
}
9655

97-
resp, err := bc.sinkClient.Get(ctx, &pbkv.GetRequest{Key: fmt.Sprintf("slot:%d", slotNum)})
56+
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
57+
defer cancel()
58+
59+
slot, err := bc.blobsService.GetSlotByBlockId(ctx, blockId)
9860
if err != nil {
9961
if ctx.Err() == context.DeadlineExceeded {
100-
helper.ReportPublicErrorAndAbort(c, response.GatewayTimeout, err)
62+
internal.WriteErrorResponse(c, internal.ErrSinkTimeout)
10163
return
10264
}
10365
st, ok := status.FromError(err)
10466
if ok && st.Code() == codes.NotFound {
105-
helper.ReportPublicErrorAndAbort(c, response.NewApiErrorNotFound(NOT_FOUND_SLOT), err)
67+
internal.WriteErrorResponse(c, internal.ErrSlotNotFound)
10668
return
10769
}
108-
helper.ReportPublicErrorAndAbort(c, response.BadGateway, err)
109-
return
110-
}
111-
112-
slot := &pbbl.Slot{}
113-
err = proto.Unmarshal(resp.GetValue(), slot)
114-
if err != nil {
115-
helper.ReportPublicErrorAndAbort(c, response.InternalServerError, err)
70+
internal.WriteErrorResponse(c, err)
11671
return
11772
}
11873

11974
resBlobs := []*dto.Blob{}
12075
for _, blob := range slot.Blobs {
121-
if len(indices) == 0 || internal.Contains(indices, fmt.Sprintf("%d", blob.Index)) {
76+
if len(indices) == 0 || internal.Contains(indices, blob.Index) {
12277
resBlobs = append(resBlobs, dto.NewBlob(blob, slot))
12378
}
12479
}

controllers/health.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package controllers
2+
3+
import (
4+
"blob-service/services"
5+
"context"
6+
"net/http"
7+
"time"
8+
9+
"github.com/gin-gonic/gin"
10+
)
11+
12+
type HealthController struct {
13+
blobsService *services.BlobsService
14+
}
15+
16+
func NewHealthController(blobsService *services.BlobsService) *HealthController {
17+
return &HealthController{blobsService: blobsService}
18+
}
19+
20+
type HealthResponse struct {
21+
Status string `json:"status"`
22+
Detail string `json:"detail,omitempty"`
23+
Head uint64 `json:"head,omitempty"`
24+
}
25+
26+
// Health
27+
// @Summary Returns health status of this API.
28+
// @Tags health
29+
// @Produce json
30+
// @Success 200 {object} HealthResponse
31+
// @Failure 500 {object} response.ApiErrorResponse
32+
// @Router /health [get]
33+
func (hc *HealthController) Health(c *gin.Context) {
34+
35+
ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second)
36+
defer cancel()
37+
38+
response := &HealthResponse{Status: "ok"}
39+
slotNum, err := hc.blobsService.GetSlotNumber(ctx, "head")
40+
if err != nil {
41+
response.Status = "error"
42+
response.Detail = err.Error()
43+
} else {
44+
response.Head = slotNum
45+
}
46+
47+
c.JSON(http.StatusOK, response)
48+
}

controllers/static.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package controllers
22

33
import (
44
"blob-service/flags"
5-
"net/http"
65

76
"github.com/eosnationftw/eosn-base-api/response"
87
"github.com/gin-gonic/gin"
@@ -28,19 +27,3 @@ func Version(c *gin.Context) {
2827
Features: flags.GetEnabledFeatures(),
2928
}})
3029
}
31-
32-
type HealthResponse struct {
33-
Status string `json:"status"`
34-
}
35-
36-
// Version
37-
// @Summary Returns health status of this API.
38-
// @Tags version
39-
// @Produce json
40-
// @Success 200 {object} HealthResponse
41-
// @Failure 500 {object} response.ApiErrorResponse
42-
// @Router /health [get]
43-
func Health(c *gin.Context) {
44-
response := &HealthResponse{Status: "ok"}
45-
c.JSON(http.StatusOK, response)
46-
}

internal/errors.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package internal
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/eosnationftw/eosn-base-api/helper"
7+
"github.com/eosnationftw/eosn-base-api/log"
8+
"github.com/eosnationftw/eosn-base-api/response"
9+
"github.com/friendsofgo/errors"
10+
"github.com/gin-gonic/gin"
11+
"go.uber.org/zap"
12+
)
13+
14+
var (
15+
ErrSinkTimeout = errors.New("timeout when trying to reach the sink service")
16+
ErrSlotNotFound = errors.New("slot not found")
17+
ErrInvalidSlot = errors.New("invalid slot")
18+
ErrInvalidIndex = errors.New("invalid index")
19+
)
20+
21+
const (
22+
NOT_FOUND_SLOT = "slot_not_found"
23+
INVALID_SLOT = "invalid_slot"
24+
INVALID_INDEX = "invalid_index"
25+
SINK_TIMEOUT = "sink_timeout"
26+
)
27+
28+
func WriteErrorResponse(c *gin.Context, err error) {
29+
30+
// 404 NOT FOUND
31+
if errors.Is(err, ErrSlotNotFound) {
32+
helper.ReportPublicErrorAndAbort(c, response.NewApiErrorNotFound(NOT_FOUND_SLOT), err)
33+
return
34+
}
35+
36+
// 400 BAD REQUEST
37+
if errors.Is(err, ErrInvalidSlot) {
38+
helper.ReportPublicErrorAndAbort(c, response.NewApiErrorBadRequest(INVALID_SLOT), err)
39+
return
40+
}
41+
if errors.Is(err, ErrInvalidIndex) {
42+
helper.ReportPublicErrorAndAbort(c, response.NewApiErrorBadRequest(INVALID_INDEX), err)
43+
return
44+
}
45+
46+
// 504 GATEWAY TIMEOUT
47+
if errors.Is(err, ErrSinkTimeout) {
48+
helper.ReportPublicErrorAndAbort(c, response.NewApiError(http.StatusGatewayTimeout, SINK_TIMEOUT), err)
49+
return
50+
}
51+
52+
log.Error("unknown error received, writing internal_server_error instead", zap.Error(err))
53+
helper.ReportPrivateErrorAndAbort(c, response.InternalServerError, err)
54+
}

server/http_server.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package server
22

33
import (
44
"blob-service/controllers"
5+
"blob-service/services"
56
"blob-service/swagger"
67
"context"
78
"fmt"
@@ -53,14 +54,17 @@ func (s *HttpServer) Initialize() {
5354

5455
s.Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
5556
s.Router.GET("/version", controllers.Version)
56-
s.Router.GET("/health", controllers.Health)
5757
s.Router.NoRoute(NoRoute)
5858
s.Router.NoMethod(NoMethod)
5959

60-
blobsController := controllers.NewBlobsController(s.App.SinkClient)
60+
blobsService := services.NewBlobsService(s.App.SinkClient)
61+
blobsController := controllers.NewBlobsController(blobsService)
62+
healthController := controllers.NewHealthController(blobsService)
6163

6264
v1 := s.Router.Group("/eth/v1")
6365
v1.GET("beacon/blob_sidecars/:block_id", blobsController.BlobsByBlockId)
66+
67+
s.Router.GET("/health", healthController.Health)
6468
}
6569

6670
func (s *HttpServer) Run(wg *sync.WaitGroup) {

services/blobs.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package services
2+
3+
import (
4+
"blob-service/internal"
5+
pbbl "blob-service/pb/pinax/ethereum/blobs/v1"
6+
"context"
7+
"encoding/binary"
8+
"fmt"
9+
"strconv"
10+
11+
pbkv "github.com/streamingfast/substreams-sink-kv/pb/substreams/sink/kv/v1"
12+
13+
"github.com/golang/protobuf/proto"
14+
)
15+
16+
type BlobsService struct {
17+
sinkClient pbkv.KvClient
18+
}
19+
20+
func NewBlobsService(sinkClient pbkv.KvClient) *BlobsService {
21+
return &BlobsService{sinkClient: sinkClient}
22+
}
23+
24+
func (bc *BlobsService) GetSlotNumber(ctx context.Context, block_id string) (uint64, error) {
25+
if block_id == "head" {
26+
resp, err := bc.sinkClient.Get(ctx, &pbkv.GetRequest{Key: "head"})
27+
if err != nil {
28+
return 0, err
29+
}
30+
return binary.BigEndian.Uint64(resp.GetValue()), nil
31+
}
32+
33+
if len(block_id) > 2 && block_id[:2] == "0x" {
34+
resp, err := bc.sinkClient.Get(ctx, &pbkv.GetRequest{Key: "block_root:" + block_id})
35+
if err != nil {
36+
return 0, err
37+
}
38+
return binary.BigEndian.Uint64(resp.GetValue()), nil
39+
}
40+
41+
slot, err := strconv.ParseUint(block_id, 10, 64)
42+
if err != nil {
43+
return 0, internal.ErrInvalidSlot
44+
}
45+
46+
return slot, nil
47+
}
48+
49+
func (bs *BlobsService) GetSlotByBlockId(ctx context.Context, blockId string) (*pbbl.Slot, error) {
50+
51+
slotNum, err := bs.GetSlotNumber(ctx, blockId)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
resp, err := bs.sinkClient.Get(ctx, &pbkv.GetRequest{Key: fmt.Sprintf("slot:%d", slotNum)})
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
slot := &pbbl.Slot{}
62+
err = proto.Unmarshal(resp.GetValue(), slot)
63+
if err != nil {
64+
return nil, err
65+
}
66+
67+
return slot, nil
68+
}

0 commit comments

Comments
 (0)