Skip to content

Commit

Permalink
feat: Add content type constants and update API requests
Browse files Browse the repository at this point in the history
  • Loading branch information
samcm committed Oct 24, 2024
1 parent d1834c1 commit 8e0b4cd
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 169 deletions.
92 changes: 58 additions & 34 deletions pkg/beacon/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"net/http"
"net/url"
"strings"

"github.com/ethpandaops/beacon/pkg/beacon/api/types"
"github.com/ethpandaops/beacon/pkg/beacon/api/types/lightclient"
Expand Down Expand Up @@ -52,6 +53,8 @@ type BeaconAPIResponse struct {
Version string `json:"version"`
}

type BeaconAPIResponses[T any] []BeaconAPIResponse

//nolint:unused // this is used in the future
func (c *consensusClient) post(ctx context.Context, path string, body map[string]interface{}) (*BeaconAPIResponse, error) {
jsonData, err := json.Marshal(body)
Expand Down Expand Up @@ -94,45 +97,59 @@ func (c *consensusClient) post(ctx context.Context, path string, body map[string
}

//nolint:unparam // ctx will probably be used in the future
func (c *consensusClient) get(ctx context.Context, path string) (*BeaconAPIResponse, error) {
func (c *consensusClient) get(ctx context.Context, path string, contentType string, rspType any) error {
if contentType == "" {
contentType = "application/json"
}

req, err := http.NewRequestWithContext(ctx, "GET", c.url+path, nil)
if err != nil {
return nil, err
return err
}

req.Header.Set("Accept", contentType)

// Set headers from c.headers
for k, v := range c.headers {
req.Header.Set(k, v)
}

rsp, err := c.client.Do(req)
if err != nil {
return nil, err
return err
}

defer rsp.Body.Close()

if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("status code: %d", rsp.StatusCode)
return fmt.Errorf("status code: %d", rsp.StatusCode)
}

// Parse the content type header to handle parameters like charset
contentTypeHeader := rsp.Header.Get("Content-Type")
if contentTypeHeader != "" {
if !strings.Contains(contentTypeHeader, contentType) {
return fmt.Errorf("unexpected content type: wanted (%s): got (%s)", contentType, contentTypeHeader)
}
}

data, err := io.ReadAll(rsp.Body)
if err != nil {
return nil, err
return err
}

resp := new(BeaconAPIResponse)
if err := json.Unmarshal(data, resp); err != nil {
return nil, err
if err := json.Unmarshal(data, rspType); err != nil {
return err
}

return resp, nil
return nil
}

func (c *consensusClient) getRaw(ctx context.Context, path string, contentType string) ([]byte, error) {
if contentType == "" {
contentType = "application/json"
}

u, err := url.Parse(c.url + path)
if err != nil {
return nil, err
Expand Down Expand Up @@ -166,8 +183,8 @@ func (c *consensusClient) getRaw(ctx context.Context, path string, contentType s

// NodePeers returns the list of peers connected to the node.
func (c *consensusClient) NodePeers(ctx context.Context) (types.Peers, error) {
data, err := c.get(ctx, "/eth/v1/node/peers")
if err != nil {
data := new(BeaconAPIResponse)
if err := c.get(ctx, "/eth/v1/node/peers", ContentTypeJSON, data); err != nil {
return nil, err
}

Expand All @@ -181,8 +198,8 @@ func (c *consensusClient) NodePeers(ctx context.Context) (types.Peers, error) {

// NodePeer returns the peer with the given peer ID.
func (c *consensusClient) NodePeer(ctx context.Context, peerID string) (types.Peer, error) {
data, err := c.get(ctx, fmt.Sprintf("/eth/v1/node/peers/%s", peerID))
if err != nil {
data := new(BeaconAPIResponse)
if err := c.get(ctx, fmt.Sprintf("/eth/v1/node/peers/%s", peerID), ContentTypeJSON, data); err != nil {
return types.Peer{}, err
}

Expand All @@ -196,8 +213,8 @@ func (c *consensusClient) NodePeer(ctx context.Context, peerID string) (types.Pe

// NodePeerCount returns the number of peers connected to the node.
func (c *consensusClient) NodePeerCount(ctx context.Context) (types.PeerCount, error) {
data, err := c.get(ctx, "/eth/v1/node/peer_count")
if err != nil {
data := new(BeaconAPIResponse)
if err := c.get(ctx, "/eth/v1/node/peer_count", ContentTypeJSON, data); err != nil {
return types.PeerCount{}, err
}

Expand Down Expand Up @@ -231,8 +248,8 @@ func (c *consensusClient) RawBlock(ctx context.Context, stateID string, contentT

// DepositSnapshot returns the deposit snapshot in the requested format.
func (c *consensusClient) DepositSnapshot(ctx context.Context) (*types.DepositSnapshot, error) {
data, err := c.get(ctx, "/eth/v1/beacon/deposit_snapshot")
if err != nil {
data := new(BeaconAPIResponse)
if err := c.get(ctx, "/eth/v1/beacon/deposit_snapshot", ContentTypeJSON, data); err != nil {
return nil, err
}

Expand All @@ -245,8 +262,8 @@ func (c *consensusClient) DepositSnapshot(ctx context.Context) (*types.DepositSn
}

func (c *consensusClient) NodeIdentity(ctx context.Context) (*types.Identity, error) {
data, err := c.get(ctx, "/eth/v1/node/identity")
if err != nil {
data := new(BeaconAPIResponse)
if err := c.get(ctx, "/eth/v1/node/identity", ContentTypeJSON, data); err != nil {
return nil, err
}

Expand All @@ -259,8 +276,8 @@ func (c *consensusClient) NodeIdentity(ctx context.Context) (*types.Identity, er
}

func (c *consensusClient) LightClientBootstrap(ctx context.Context, blockRoot string) (*LightClientBootstrapResponse, error) {
data, err := c.get(ctx, fmt.Sprintf("/eth/v1/beacon/light_client/bootstrap/%s", blockRoot))
if err != nil {
data := new(BeaconAPIResponse)
if err := c.get(ctx, fmt.Sprintf("/eth/v1/beacon/light_client/bootstrap/%s", blockRoot), ContentTypeJSON, data); err != nil {
return nil, err
}

Expand All @@ -272,7 +289,7 @@ func (c *consensusClient) LightClientBootstrap(ctx context.Context, blockRoot st
},
},
}
if err := json.Unmarshal(data.Data, &rsp.Data); err != nil {
if err := json.Unmarshal(data.Data, &rsp.Response.Data); err != nil {
return nil, err
}

Expand All @@ -288,29 +305,36 @@ func (c *consensusClient) LightClientUpdates(ctx context.Context, startPeriod, c
params.Add("start_period", fmt.Sprintf("%d", startPeriod))
params.Add("count", fmt.Sprintf("%d", count))

data, err := c.get(ctx, "/eth/v1/beacon/light_client/updates?"+params.Encode())
if err != nil {
data := new(BeaconAPIResponses[*lightclient.Updates])
if err := c.get(ctx, "/eth/v1/beacon/light_client/updates?"+params.Encode(), ContentTypeJSON, data); err != nil {
return nil, err
}

rsp := LightClientUpdatesResponse{
Response: Response[*lightclient.Updates]{
Data: &lightclient.Updates{},
Metadata: map[string]any{
"version": data.Version,
},
Data: &lightclient.Updates{},
Metadata: map[string]any{},
},
}
if err := json.Unmarshal(data.Data, &rsp.Data); err != nil {
return nil, err

updates := make(lightclient.Updates, 0)
for _, resp := range *data {
update := lightclient.Update{}
if err := json.Unmarshal(resp.Data, &update); err != nil {
return nil, err
}

updates = append(updates, &update)
}

rsp.Response.Data = &updates

return &rsp, nil
}

func (c *consensusClient) LightClientFinalityUpdate(ctx context.Context) (*LightClientFinalityUpdateResponse, error) {
data, err := c.get(ctx, "/eth/v1/beacon/light_client/finality_update")
if err != nil {
data := new(BeaconAPIResponse)
if err := c.get(ctx, "/eth/v1/beacon/light_client/finality_update", ContentTypeJSON, data); err != nil {
return nil, err
}

Expand All @@ -330,8 +354,8 @@ func (c *consensusClient) LightClientFinalityUpdate(ctx context.Context) (*Light
}

func (c *consensusClient) LightClientOptimisticUpdate(ctx context.Context) (*LightClientOptimisticUpdateResponse, error) {
data, err := c.get(ctx, "/eth/v1/beacon/light_client/optimistic_update")
if err != nil {
data := new(BeaconAPIResponse)
if err := c.get(ctx, "/eth/v1/beacon/light_client/optimistic_update", ContentTypeJSON, data); err != nil {
return nil, err
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/beacon/api/content_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package api

const (
ContentTypeJSON = "application/json"
ContentTypeSSZ = "application/octet-stream"
)
118 changes: 3 additions & 115 deletions pkg/beacon/api/types/lightclient/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"

"github.com/attestantio/go-eth2-client/spec/phase0"
Expand All @@ -25,24 +24,6 @@ type bootstrapJSON struct {
CurrentSyncCommitteeBranch bootstrapCurrentSyncCommitteeBranchJSON `json:"current_sync_committee_branch"`
}

// BootstrapHeader is the header of a light client bootstrap.
type BootstrapHeader struct {
Slot phase0.Slot
ProposerIndex phase0.ValidatorIndex
ParentRoot phase0.Root
StateRoot phase0.Root
BodyRoot phase0.Root
}

// bootstrapHeaderJSON is the JSON representation of a bootstrap header.
type bootstrapHeaderJSON struct {
Slot string `json:"slot"`
ProposerIndex string `json:"proposer_index"`
ParentRoot string `json:"parent_root"`
StateRoot string `json:"state_root"`
BodyRoot string `json:"body_root"`
}

// BootstrapCurrentSyncCommittee is the current sync committee of a light client bootstrap.
type BootstrapCurrentSyncCommittee struct {
Pubkeys []phase0.BLSPubKey
Expand Down Expand Up @@ -70,13 +51,7 @@ func (b Bootstrap) MarshalJSON() ([]byte, error) {
}

return json.Marshal(&bootstrapJSON{
Header: bootstrapHeaderJSON{
Slot: fmt.Sprintf("%d", b.Header.Slot),
ProposerIndex: fmt.Sprintf("%d", b.Header.ProposerIndex),
ParentRoot: b.Header.ParentRoot.String(),
StateRoot: b.Header.StateRoot.String(),
BodyRoot: b.Header.BodyRoot.String(),
},
Header: b.Header.ToJSON(),
CurrentSyncCommittee: bootstrapCurrentSyncCommitteeJSON{
Pubkeys: pubkeys,
AggregatePubkey: b.CurrentSyncCommittee.AggregatePubkey.String(),
Expand All @@ -93,55 +68,9 @@ func (b *Bootstrap) UnmarshalJSON(input []byte) error {
return errors.Wrap(err, "invalid JSON")
}

if jsonData.Header.Slot == "" {
return errors.New("slot is required")
}

slot, err := strconv.ParseUint(jsonData.Header.Slot, 10, 64)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid slot: %s", jsonData.Header.Slot))
}
b.Header.Slot = phase0.Slot(slot)

if jsonData.Header.ProposerIndex == "" {
return errors.New("proposer index is required")
}

proposerIndex, err := strconv.ParseUint(jsonData.Header.ProposerIndex, 10, 64)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid proposer index: %s", jsonData.Header.ProposerIndex))
}
b.Header.ProposerIndex = phase0.ValidatorIndex(proposerIndex)

if jsonData.Header.ParentRoot == "" {
return errors.New("parent root is required")
}

parentRoot, err := hex.DecodeString(strings.TrimPrefix(jsonData.Header.ParentRoot, "0x"))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid parent root: %s", jsonData.Header.ParentRoot))
}
b.Header.ParentRoot = phase0.Root(parentRoot)

if jsonData.Header.StateRoot == "" {
return errors.New("state root is required")
}

stateRoot, err := hex.DecodeString(strings.TrimPrefix(jsonData.Header.StateRoot, "0x"))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid state root: %s", jsonData.Header.StateRoot))
}
b.Header.StateRoot = phase0.Root(stateRoot)

if jsonData.Header.BodyRoot == "" {
return errors.New("body root is required")
}

bodyRoot, err := hex.DecodeString(strings.TrimPrefix(jsonData.Header.BodyRoot, "0x"))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid body root: %s", jsonData.Header.BodyRoot))
if err = b.Header.Beacon.FromJSON(jsonData.Header.Beacon); err != nil {
return errors.Wrap(err, "invalid header")
}
b.Header.BodyRoot = phase0.Root(bodyRoot)

if len(jsonData.CurrentSyncCommitteeBranch) == 0 {
return errors.New("current sync committee branch is required")
Expand Down Expand Up @@ -187,47 +116,6 @@ func (b *Bootstrap) UnmarshalJSON(input []byte) error {
return nil
}

func (b *BootstrapHeader) UnmarshalJSON(input []byte) error {
var err error

var jsonData bootstrapHeaderJSON
if err = json.Unmarshal(input, &jsonData); err != nil {
return errors.Wrap(err, "invalid JSON")
}

slot, err := strconv.ParseUint(jsonData.Slot, 10, 64)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid slot: %s", jsonData.Slot))
}
b.Slot = phase0.Slot(slot)

proposerIndex, err := strconv.ParseUint(jsonData.ProposerIndex, 10, 64)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid proposer index: %s", jsonData.ProposerIndex))
}
b.ProposerIndex = phase0.ValidatorIndex(proposerIndex)

parentRoot, err := hex.DecodeString(jsonData.ParentRoot)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid parent root: %s", jsonData.ParentRoot))
}
b.ParentRoot = phase0.Root(parentRoot)

stateRoot, err := hex.DecodeString(jsonData.StateRoot)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid state root: %s", jsonData.StateRoot))
}
b.StateRoot = phase0.Root(stateRoot)

bodyRoot, err := hex.DecodeString(jsonData.BodyRoot)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid body root: %s", jsonData.BodyRoot))
}
b.BodyRoot = phase0.Root(bodyRoot)

return nil
}

func (b *BootstrapCurrentSyncCommittee) UnmarshalJSON(input []byte) error {
var err error

Expand Down
Loading

0 comments on commit 8e0b4cd

Please sign in to comment.