Middleware for Caplin Beacon API (#8103)

This commit is contained in:
Giulio rebuffo 2023-08-31 01:18:12 +02:00 committed by GitHub
parent 9b63764b16
commit e7dbc69571
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 19 deletions

View File

@ -0,0 +1,64 @@
package handler
import (
"encoding/json"
"io"
"net/http"
"strings"
"github.com/ledgerwatch/erigon-lib/types/ssz"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/log/v3"
)
type BeaconResponse struct {
Finalized *bool `json:"finalized,omitempty"`
Version string `json:"version,omitempty"`
ExecutionOptimistic *bool `json:"execution_optimistic,omitempty"`
Data ssz.Marshaler `json:"data,omitempty"`
}
// In case of it being a json we need to also expose finalization, version, etc...
type beaconHandlerFn func(r *http.Request) (data ssz.Marshaler, finalized *bool, version *clparams.StateVersion, httpStatus int, err error)
func beaconHandlerWrapper(fn beaconHandlerFn) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
accept := r.Header.Get("Accept")
isSSZ := !strings.Contains(accept, "application/json") && strings.Contains(accept, "application/stream-octect")
data, finalized, version, httpStatus, err := fn(r)
if err != nil {
w.WriteHeader(httpStatus)
io.WriteString(w, err.Error())
log.Debug("[Beacon API] failed", "method", r.Method, "err", err, "ssz", isSSZ)
return
}
if isSSZ {
// SSZ encoding
encoded, err := data.EncodeSSZ(nil)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
io.WriteString(w, err.Error())
log.Debug("[Beacon API] failed", "method", r.Method, "err", err, "accepted", accept)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.WriteHeader(httpStatus)
w.Write(encoded)
log.Debug("[Beacon API] genesis handler failed", err)
return
}
resp := &BeaconResponse{Data: data}
if version != nil {
resp.Version = clparams.ClVersionToString(*version)
}
if finalized != nil {
resp.ExecutionOptimistic = new(bool)
resp.Finalized = new(bool)
*resp.Finalized = *finalized
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(httpStatus)
json.NewEncoder(w).Encode(resp)
}
}

View File

@ -1,14 +1,15 @@
package handler
import (
"encoding/json"
"io"
"errors"
"net/http"
"github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/types/ssz"
"github.com/ledgerwatch/erigon/cl/beacon/types"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/fork"
"github.com/ledgerwatch/log/v3"
ssz2 "github.com/ledgerwatch/erigon/cl/ssz"
)
type genesisReponse struct {
@ -17,26 +18,33 @@ type genesisReponse struct {
GenesisForkVersion types.Bytes4 `json:"genesis_fork_version,omitempty"`
}
func (a *ApiHandler) getGenesis(w http.ResponseWriter, _ *http.Request) {
func (g *genesisReponse) EncodeSSZ(buf []byte) ([]byte, error) {
return ssz2.MarshalSSZ(buf, g.GenesisTime, g.GenesisValidatorRoot[:], g.GenesisForkVersion[:])
}
func (g *genesisReponse) EncodingSizeSSZ() int {
return 44
}
func (a *ApiHandler) getGenesis(r *http.Request) (data ssz.Marshaler, finalized *bool, version *clparams.StateVersion, httpStatus int, err error) {
if a.genesisCfg == nil {
w.WriteHeader(http.StatusNotFound)
io.WriteString(w, "Genesis Config is missing")
err = errors.New("Genesis Config is missing")
httpStatus = http.StatusNotFound
return
}
digest, err := fork.ComputeForkDigest(a.beaconChainCfg, a.genesisCfg)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
io.WriteString(w, "Failed to compute fork digest")
log.Error("[Beacon API] genesis handler failed", err)
err = errors.New("Failed to compute fork digest")
httpStatus = http.StatusInternalServerError
return
}
w.Header().Set("Content-Type", "Application/json")
w.WriteHeader(http.StatusAccepted)
json.NewEncoder(w).Encode(genesisReponse{
data = &genesisReponse{
GenesisTime: a.genesisCfg.GenesisTime,
GenesisValidatorRoot: a.genesisCfg.GenesisValidatorRoot,
GenesisForkVersion: types.Bytes4(digest),
})
}
httpStatus = http.StatusAccepted
return
}

View File

@ -35,12 +35,17 @@ func (a *ApiHandler) init() {
r.Route("/v1", func(r chi.Router) {
r.Get("/events", nil)
r.Route("/beacon", func(r chi.Router) {
r.Get("/headers/{tag}", nil) // otterscan
r.Get("/blocks/{block_id}", a.getBlock) //otterscan
r.Get("/blocks/{block_id}/root", a.getBlockRoot) //otterscan
r.Get("/genesis", a.getGenesis)
r.Route("/headers", func(r chi.Router) {
r.Get("/", nil)
r.Get("/{block_id}", nil)
})
r.Route("/blocks", func(r chi.Router) {
r.Post("/", nil)
r.Get("/{block_id}", a.getBlock)
r.Get("/block_id}/root", a.getBlockRoot)
})
r.Get("/genesis", beaconHandlerWrapper(a.getGenesis))
r.Post("/binded_blocks", nil)
r.Post("/blocks", nil)
r.Route("/pool", func(r chi.Router) {
r.Post("/attestations", nil)
r.Post("/sync_committees", nil)

19
cl/beacon/middleware.go Normal file
View File

@ -0,0 +1,19 @@
package beacon
import (
"fmt"
"net/http"
)
func newBeaconMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" && contentType != "" {
fmt.Println(contentType)
http.Error(w, "Content-Type header must be application/json", http.StatusUnsupportedMediaType)
return
}
next.ServeHTTP(w, r)
})
}

View File

@ -23,7 +23,7 @@ type RouterConfiguration struct {
func ListenAndServe(api *handler.ApiHandler, routerCfg *RouterConfiguration) {
listener, err := net.Listen(routerCfg.Protocol, routerCfg.Address)
server := &http.Server{
Handler: api,
Handler: newBeaconMiddleware(api),
ReadTimeout: routerCfg.ReadTimeTimeout,
IdleTimeout: routerCfg.IdleTimeout,
WriteTimeout: routerCfg.IdleTimeout,

View File

@ -27,3 +27,20 @@ func StringToClVersion(s string) StateVersion {
panic("unsupported fork version: " + s)
}
}
func ClVersionToString(s StateVersion) string {
switch s {
case Phase0Version:
return "phase0"
case AltairVersion:
return "altair"
case BellatrixVersion:
return "bellatrix"
case CapellaVersion:
return "capella"
case DenebVersion:
return "deneb"
default:
panic("unsupported fork version")
}
}