cmd,eth,beacon: accept custom http headers for beacon api

This commit is contained in:
lightclient 2024-01-05 10:40:14 -07:00
parent 2333dcdb24
commit 70e2451c57
No known key found for this signature in database
GPG key ID: 75C916AFEE20183E
6 changed files with 50 additions and 17 deletions

View file

@ -22,6 +22,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
eth2client "github.com/attestantio/go-eth2-client"
eth2api "github.com/attestantio/go-eth2-client/api"
@ -41,26 +42,40 @@ var (
// Client is a wrapper around the attestantio/go-eth2-client beacon api client.
type Client struct {
ctx context.Context
url string
client eth2client.Service
ctx context.Context
url string
http *http.Client
extraHeaders map[string]string
client eth2client.Service
}
// NewClient creates a Client for the given server URL.
func NewClient(ctx context.Context, server string) (*Client, error) {
func NewClient(ctx context.Context, server string, headers []string) (*Client, error) {
// Parse additional headers.
extraHeaders := make(map[string]string)
for _, h := range headers {
s := strings.Split(h, ":")
if len(s) != 2 {
return nil, fmt.Errorf("malformed extra header: %s", h)
}
extraHeaders[s[0]] = s[1]
}
client, err := eth2http.New(
ctx,
eth2http.WithAddress(server),
eth2http.WithLogLevel(zerolog.WarnLevel),
eth2http.WithEnforceJSON(true),
eth2http.WithExtraHeaders(extraHeaders),
)
if err != nil {
return nil, err
}
return &Client{
ctx: ctx,
url: server,
client: client,
ctx: ctx,
url: server,
http: &http.Client{},
extraHeaders: extraHeaders,
client: client,
}, nil
}
@ -68,7 +83,7 @@ func NewClient(ctx context.Context, server string) (*Client, error) {
// the beacon server.
func (c *Client) Bootstrap(root common.Hash) (*types.Bootstrap, error) {
var bs types.Bootstrap
if err := fetch(fmt.Sprintf("%s/%s/%s", c.url, beaconLightClientBootstrap, root.String()), &bs); err != nil {
if err := c.fetch(fmt.Sprintf("%s/%s/%s", c.url, beaconLightClientBootstrap, root.String()), &bs); err != nil {
return nil, err
}
return &bs, nil
@ -78,7 +93,7 @@ func (c *Client) Bootstrap(root common.Hash) (*types.Bootstrap, error) {
// include the next sync committee and finalized header from the period.
func (c *Client) GetRangeUpdate(start, count int) ([]*types.LightClientUpdate, error) {
var u []*types.LightClientUpdate
if err := fetch(fmt.Sprintf("%s/%s?start_period=%d&count=%d", c.url, beaconLightClientUpdate, start, count), &u); err != nil {
if err := c.fetch(fmt.Sprintf("%s/%s?start_period=%d&count=%d", c.url, beaconLightClientUpdate, start, count), &u); err != nil {
return nil, err
}
return u, nil
@ -89,7 +104,7 @@ func (c *Client) GetRangeUpdate(start, count int) ([]*types.LightClientUpdate, e
// beacon api server.
func (c *Client) GetOptimisticUpdate() (*types.LightClientUpdate, error) {
var u types.LightClientUpdate
if err := fetch(fmt.Sprintf("%s/%s", c.url, beaconLightClientOptimisticUpdate), &u); err != nil {
if err := c.fetch(fmt.Sprintf("%s/%s", c.url, beaconLightClientOptimisticUpdate), &u); err != nil {
return nil, err
}
return &u, nil
@ -99,7 +114,7 @@ func (c *Client) GetOptimisticUpdate() (*types.LightClientUpdate, error) {
// beacon api server.
func (c *Client) GetFinalityUpdate() (*types.LightClientUpdate, error) {
var u types.LightClientUpdate
if err := fetch(fmt.Sprintf("%s/%s", c.url, beaconLightClientFinalityUpdate), &u); err != nil {
if err := c.fetch(fmt.Sprintf("%s/%s", c.url, beaconLightClientFinalityUpdate), &u); err != nil {
return nil, err
}
return &u, nil
@ -118,11 +133,21 @@ func (c *Client) GetBlock(root common.Hash) (*eth2spec.VersionedSignedBeaconBloc
return resp.Data, nil
}
func fetch(url string, val any) error {
resp, err := http.Get(url)
func (c *Client) fetch(url string, val any) error {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
for k, v := range c.extraHeaders {
req.Header.Set(k, v)
}
resp, err := c.http.Do(req)
if err != nil {
return fmt.Errorf("failed http request: %w", err)
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed http request: status %d", resp.StatusCode)
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)

View file

@ -45,8 +45,8 @@ type LightClient struct {
// Bootstrap retrieves a light client bootstrap and authenticates it against the
// provided trusted root.
func Bootstrap(ctx context.Context, server string, root common.Hash) (*LightClient, error) {
api, err := beaclient.NewClient(ctx, server)
func Bootstrap(server string, headers []string, root common.Hash) (*LightClient, error) {
api, err := beaclient.NewClient(context.Background(), server, headers)
if err != nil {
return nil, fmt.Errorf("failed to connect to beacon server: %w", err)
}

View file

@ -147,6 +147,7 @@ var (
utils.LogDebugFlag,
utils.LogBacktraceAtFlag,
utils.BeaconAPIFlag,
utils.BeaconAPIHeadersFlag,
utils.BeaconTrustedBlockRootFlag,
}, utils.NetworkFlags, utils.DatabaseFlags)

View file

@ -915,6 +915,11 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Usage: "server which provides the beacon light client apis",
Category: flags.BeaconCategory,
}
BeaconAPIHeadersFlag = &cli.StringSliceFlag{
Name: "beacon.api.headers",
Usage: "headers to provide with each request",
Category: flags.BeaconCategory,
}
BeaconTrustedBlockRootFlag = &cli.StringFlag{
Name: "beacon.wss",
Usage: "root of trusted block within the weak-subjectivity window",
@ -1599,6 +1604,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// Avoid conflicting network flags
CheckExclusive(ctx, MainnetFlag, DeveloperFlag, GoerliFlag, SepoliaFlag, HoleskyFlag)
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
CheckExclusive(ctx, DeveloperFlag, BeaconAPIFlag) // Can't use both simulated beacon and blsync
// Set configurations from CLI flags
setEtherbase(ctx, cfg)
@ -1859,6 +1865,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
Fatalf("Must set both beacon api flag and beacon trusted block root flag to use beacon light client")
}
cfg.BeaconAPI = ctx.String(BeaconAPIFlag.Name)
cfg.BeaconAPIHeaders = ctx.StringSlice(BeaconAPIHeadersFlag.Name)
cfg.BeaconTrustedBlockRoot = ctx.String(BeaconTrustedBlockRootFlag.Name)
}

View file

@ -18,7 +18,6 @@
package eth
import (
"context"
"errors"
"fmt"
"math/big"
@ -277,7 +276,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}
if config.BeaconAPI != "" && config.BeaconTrustedBlockRoot != "" {
beacon, err := light.Bootstrap(context.Background(), config.BeaconAPI, common.HexToHash(config.BeaconTrustedBlockRoot))
beacon, err := light.Bootstrap(config.BeaconAPI, config.BeaconAPIHeaders, common.HexToHash(config.BeaconTrustedBlockRoot))
if err != nil {
return nil, err
}

View file

@ -115,6 +115,7 @@ type Config struct {
// Beacon light client options
BeaconAPI string
BeaconAPIHeaders []string
BeaconTrustedBlockRoot string
// Database options