diff --git a/pkg/beacon/api/api.go b/pkg/beacon/api/api.go index 8a7d7d0..21c856b 100644 --- a/pkg/beacon/api/api.go +++ b/pkg/beacon/api/api.go @@ -24,10 +24,10 @@ type ConsensusClient interface { RawDebugBeaconState(ctx context.Context, stateID string, contentType string) ([]byte, error) DepositSnapshot(ctx context.Context) (*types.DepositSnapshot, error) NodeIdentity(ctx context.Context) (*types.Identity, error) - LightClientBootstrap(ctx context.Context, blockRoot string) (*lightclient.Bootstrap, error) - LightClientUpdate(ctx context.Context, startPeriod, count int) (*lightclient.Update, error) - LightClientFinalityUpdate(ctx context.Context) (*lightclient.FinalityUpdate, error) - LightClientOptimisticUpdate(ctx context.Context) (*lightclient.OptimisticUpdate, error) + LightClientBootstrap(ctx context.Context, blockRoot string) (*LightClientBootstrapResponse, error) + LightClientUpdates(ctx context.Context, startPeriod, count int) (*LightClientUpdatesResponse, error) + LightClientFinalityUpdate(ctx context.Context) (*LightClientFinalityUpdateResponse, error) + LightClientOptimisticUpdate(ctx context.Context) (*LightClientOptimisticUpdateResponse, error) } type consensusClient struct { @@ -47,12 +47,13 @@ func NewConsensusClient(ctx context.Context, log logrus.FieldLogger, url string, } } -type apiResponse struct { - Data json.RawMessage `json:"data"` +type BeaconAPIResponse struct { + Data json.RawMessage `json:"data"` + Version string `json:"version"` } //nolint:unused // this is used in the future -func (c *consensusClient) post(ctx context.Context, path string, body map[string]interface{}) (json.RawMessage, error) { +func (c *consensusClient) post(ctx context.Context, path string, body map[string]interface{}) (*BeaconAPIResponse, error) { jsonData, err := json.Marshal(body) if err != nil { return nil, err @@ -84,16 +85,16 @@ func (c *consensusClient) post(ctx context.Context, path string, body map[string return nil, err } - resp := new(apiResponse) + resp := new(BeaconAPIResponse) if err := json.Unmarshal(data, resp); err != nil { return nil, err } - return resp.Data, nil + return resp, nil } //nolint:unparam // ctx will probably be used in the future -func (c *consensusClient) get(ctx context.Context, path string) (json.RawMessage, error) { +func (c *consensusClient) get(ctx context.Context, path string) (*BeaconAPIResponse, error) { req, err := http.NewRequestWithContext(ctx, "GET", c.url+path, nil) if err != nil { return nil, err @@ -120,12 +121,12 @@ func (c *consensusClient) get(ctx context.Context, path string) (json.RawMessage return nil, err } - resp := new(apiResponse) + resp := new(BeaconAPIResponse) if err := json.Unmarshal(data, resp); err != nil { return nil, err } - return resp.Data, nil + return resp, nil } func (c *consensusClient) getRaw(ctx context.Context, path string, contentType string) ([]byte, error) { @@ -171,7 +172,7 @@ func (c *consensusClient) NodePeers(ctx context.Context) (types.Peers, error) { } rsp := types.Peers{} - if err := json.Unmarshal(data, &rsp); err != nil { + if err := json.Unmarshal(data.Data, &rsp); err != nil { return nil, err } @@ -186,7 +187,7 @@ func (c *consensusClient) NodePeer(ctx context.Context, peerID string) (types.Pe } rsp := types.Peer{} - if err := json.Unmarshal(data, &rsp); err != nil { + if err := json.Unmarshal(data.Data, &rsp); err != nil { return types.Peer{}, err } @@ -201,7 +202,7 @@ func (c *consensusClient) NodePeerCount(ctx context.Context) (types.PeerCount, e } rsp := types.PeerCount{} - if err := json.Unmarshal(data, &rsp); err != nil { + if err := json.Unmarshal(data.Data, &rsp); err != nil { return types.PeerCount{}, err } @@ -236,7 +237,7 @@ func (c *consensusClient) DepositSnapshot(ctx context.Context) (*types.DepositSn } rsp := types.DepositSnapshot{} - if err := json.Unmarshal(data, &rsp); err != nil { + if err := json.Unmarshal(data.Data, &rsp); err != nil { return nil, err } @@ -250,28 +251,35 @@ func (c *consensusClient) NodeIdentity(ctx context.Context) (*types.Identity, er } rsp := types.Identity{} - if err := json.Unmarshal(data, &rsp); err != nil { + if err := json.Unmarshal(data.Data, &rsp); err != nil { return nil, err } return &rsp, nil } -func (c *consensusClient) LightClientBootstrap(ctx context.Context, blockRoot string) (*lightclient.Bootstrap, error) { +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 { return nil, err } - rsp := lightclient.Bootstrap{} - if err := json.Unmarshal(data, &rsp); err != nil { + rsp := LightClientBootstrapResponse{ + Response: Response[*lightclient.Bootstrap]{ + Data: &lightclient.Bootstrap{}, + Metadata: map[string]any{ + "version": data.Version, + }, + }, + } + if err := json.Unmarshal(data.Data, &rsp.Data); err != nil { return nil, err } return &rsp, nil } -func (c *consensusClient) LightClientUpdate(ctx context.Context, startPeriod, count int) (*lightclient.Update, error) { +func (c *consensusClient) LightClientUpdates(ctx context.Context, startPeriod, count int) (*LightClientUpdatesResponse, error) { if count == 0 { return nil, errors.New("count must be greater than 0") } @@ -285,36 +293,57 @@ func (c *consensusClient) LightClientUpdate(ctx context.Context, startPeriod, co return nil, err } - rsp := lightclient.Update{} - if err := json.Unmarshal(data, &rsp); err != nil { + rsp := LightClientUpdatesResponse{ + Response: Response[*lightclient.Updates]{ + Data: &lightclient.Updates{}, + Metadata: map[string]any{ + "version": data.Version, + }, + }, + } + if err := json.Unmarshal(data.Data, &rsp.Data); err != nil { return nil, err } return &rsp, nil } -func (c *consensusClient) LightClientFinalityUpdate(ctx context.Context) (*lightclient.FinalityUpdate, error) { +func (c *consensusClient) LightClientFinalityUpdate(ctx context.Context) (*LightClientFinalityUpdateResponse, error) { data, err := c.get(ctx, "/eth/v1/beacon/light_client/finality_update") if err != nil { return nil, err } - rsp := lightclient.FinalityUpdate{} - if err := json.Unmarshal(data, &rsp); err != nil { + rsp := LightClientFinalityUpdateResponse{ + Response: Response[*lightclient.FinalityUpdate]{ + Data: &lightclient.FinalityUpdate{}, + Metadata: map[string]any{ + "version": data.Version, + }, + }, + } + if err := json.Unmarshal(data.Data, &rsp.Data); err != nil { return nil, err } return &rsp, nil } -func (c *consensusClient) LightClientOptimisticUpdate(ctx context.Context) (*lightclient.OptimisticUpdate, error) { +func (c *consensusClient) LightClientOptimisticUpdate(ctx context.Context) (*LightClientOptimisticUpdateResponse, error) { data, err := c.get(ctx, "/eth/v1/beacon/light_client/optimistic_update") if err != nil { return nil, err } - rsp := lightclient.OptimisticUpdate{} - if err := json.Unmarshal(data, &rsp); err != nil { + rsp := LightClientOptimisticUpdateResponse{ + Response: Response[*lightclient.OptimisticUpdate]{ + Data: &lightclient.OptimisticUpdate{}, + Metadata: map[string]any{ + "version": data.Version, + }, + }, + } + if err := json.Unmarshal(data.Data, &rsp.Data); err != nil { return nil, err } diff --git a/pkg/beacon/api/responses.go b/pkg/beacon/api/responses.go new file mode 100644 index 0000000..4b5ed08 --- /dev/null +++ b/pkg/beacon/api/responses.go @@ -0,0 +1,24 @@ +package api + +import "github.com/ethpandaops/beacon/pkg/beacon/api/types/lightclient" + +type Response[T any] struct { + Data T `json:"data"` + Metadata map[string]any `json:"metadata"` +} + +type LightClientUpdatesResponse struct { + Response[*lightclient.Updates] +} + +type LightClientBootstrapResponse struct { + Response[*lightclient.Bootstrap] +} + +type LightClientFinalityUpdateResponse struct { + Response[*lightclient.FinalityUpdate] +} + +type LightClientOptimisticUpdateResponse struct { + Response[*lightclient.OptimisticUpdate] +} diff --git a/pkg/beacon/api/types/agents.go b/pkg/beacon/api/types/agents.go index ff081c6..003bdb9 100644 --- a/pkg/beacon/api/types/agents.go +++ b/pkg/beacon/api/types/agents.go @@ -20,6 +20,8 @@ const ( AgentPrysm Agent = "prysm" // AgentLodestar is a Lodestar agent. AgentLodestar Agent = "lodestar" + // AgentGrandine is a Grandine agent. + AgentGrandine Agent = "grandine" ) // AllAgents is a list of all agents. @@ -30,6 +32,7 @@ var AllAgents = []Agent{ AgentTeku, AgentPrysm, AgentLodestar, + AgentGrandine, } // AgentCount represents the number of peers with each agent. @@ -40,6 +43,7 @@ type AgentCount struct { Teku int `json:"teku"` Prysm int `json:"prysm"` Lodestar int `json:"lodestar"` + Grandine int `json:"grandine"` } // AgentFromString returns the agent from the given string. @@ -66,5 +70,9 @@ func AgentFromString(agent string) Agent { return AgentLodestar } + if strings.Contains(asLower, "grandine") { + return AgentGrandine + } + return AgentUnknown } diff --git a/pkg/beacon/api/types/lightclient/finality_update.go b/pkg/beacon/api/types/lightclient/finality_update.go index 5e4d08d..4bef398 100644 --- a/pkg/beacon/api/types/lightclient/finality_update.go +++ b/pkg/beacon/api/types/lightclient/finality_update.go @@ -81,7 +81,7 @@ func (f FinalityUpdate) MarshalJSON() ([]byte, error) { func (f *FinalityUpdate) ToJSON() finalityUpdateJSON { finalityBranch := make([]string, len(f.FinalityBranch)) for i, root := range f.FinalityBranch { - finalityBranch[i] = fmt.Sprintf("%x", root) + finalityBranch[i] = fmt.Sprintf("%#x", root) } return finalityUpdateJSON{ diff --git a/pkg/beacon/api/types/lightclient/update.go b/pkg/beacon/api/types/lightclient/update.go index 35b7753..25cd571 100644 --- a/pkg/beacon/api/types/lightclient/update.go +++ b/pkg/beacon/api/types/lightclient/update.go @@ -11,6 +11,9 @@ import ( "github.com/pkg/errors" ) +// Updates represents a light client updates. +type Updates []*Update + // Update represents a light client update. type Update struct { AttestedHeader LightClientHeader `json:"attested_header"` diff --git a/pkg/beacon/beacon.go b/pkg/beacon/beacon.go index 1ccab71..d6e80c4 100644 --- a/pkg/beacon/beacon.go +++ b/pkg/beacon/beacon.go @@ -102,6 +102,14 @@ type Node interface { FetchBeaconBlockHeader(ctx context.Context, opts *eapi.BeaconBlockHeaderOpts) (*v1.BeaconBlockHeader, error) // FetchNodeIdentity fetches the node identity. FetchNodeIdentity(ctx context.Context) (*types.Identity, error) + // FetchLightClientBootstrap fetches the light client bootstrap. + FetchLightClientBootstrap(ctx context.Context, root phase0.Root) (*api.LightClientBootstrapResponse, error) + // FetchLightClientFinalityUpdate fetches the light client finality update. + FetchLightClientFinalityUpdate(ctx context.Context) (*api.LightClientFinalityUpdateResponse, error) + // FetchLightClientOptimisticUpdate fetches the light client optimistic update. + FetchLightClientOptimisticUpdate(ctx context.Context) (*api.LightClientOptimisticUpdateResponse, error) + // FetchLightClientUpdates fetches the light client updates. + FetchLightClientUpdates(ctx context.Context, startPeriod, count int) (*api.LightClientUpdatesResponse, error) // Subscriptions // - Proxied Beacon events diff --git a/pkg/beacon/fetch.go b/pkg/beacon/fetch.go index 0963162..4ca8747 100644 --- a/pkg/beacon/fetch.go +++ b/pkg/beacon/fetch.go @@ -3,6 +3,7 @@ package beacon import ( "context" "errors" + "fmt" eth2client "github.com/attestantio/go-eth2-client" "github.com/attestantio/go-eth2-client/api" @@ -10,6 +11,7 @@ import ( "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" + bapi "github.com/ethpandaops/beacon/pkg/beacon/api" "github.com/ethpandaops/beacon/pkg/beacon/api/types" "github.com/ethpandaops/beacon/pkg/beacon/state" ) @@ -526,3 +528,73 @@ func (n *node) FetchBeaconBlockHeader(ctx context.Context, opts *api.BeaconBlock return rsp.Data, nil } + +func (n *node) FetchLightClientBootstrap(ctx context.Context, root phase0.Root) (*bapi.LightClientBootstrapResponse, error) { + rootAsHex := fmt.Sprintf("0x%x", root) + + logCtx := n.log.WithField("method", "FetchLightClientBootstrap").WithField("root", rootAsHex) + + logCtx.Debug("Fetching light client bootstrap") + + rsp, err := n.api.LightClientBootstrap(ctx, rootAsHex) + if err != nil { + logCtx.WithError(err).Error("failed to fetch light client bootstrap") + + return nil, err + } + + logCtx.Debug("Successfully fetched light client bootstrap") + + return rsp, nil +} + +func (n *node) FetchLightClientFinalityUpdate(ctx context.Context) (*bapi.LightClientFinalityUpdateResponse, error) { + logCtx := n.log.WithField("method", "FetchLightClientFinalityUpdate") + + logCtx.Debug("Fetching light client finality update") + + rsp, err := n.api.LightClientFinalityUpdate(ctx) + if err != nil { + logCtx.WithError(err).Error("failed to fetch light client finality update") + + return nil, err + } + + logCtx.Debug("Successfully fetched light client finality update") + + return rsp, nil +} + +func (n *node) FetchLightClientOptimisticUpdate(ctx context.Context) (*bapi.LightClientOptimisticUpdateResponse, error) { + logCtx := n.log.WithField("method", "FetchLightClientOptimisticUpdate") + + logCtx.Debug("Fetching light client optimistic update") + + rsp, err := n.api.LightClientOptimisticUpdate(ctx) + if err != nil { + logCtx.WithError(err).Error("failed to fetch light client optimistic update") + + return nil, err + } + + logCtx.Debug("Successfully fetched light client optimistic update") + + return rsp, nil +} + +func (n *node) FetchLightClientUpdates(ctx context.Context, startPeriod, count int) (*bapi.LightClientUpdatesResponse, error) { + logCtx := n.log.WithField("method", "FetchLightClientUpdates").WithField("start_period", startPeriod).WithField("count", count) + + logCtx.Debug("Fetching light client update") + + rsp, err := n.api.LightClientUpdates(ctx, startPeriod, count) + if err != nil { + logCtx.WithError(err).Error("failed to fetch light client update") + + return nil, err + } + + logCtx.Debug("Successfully fetched light client update") + + return rsp, nil +}