diff --git a/beacon/beaclient/client.go b/beacon/beaclient/client.go index f63b558715..cfb893a93b 100644 --- a/beacon/beaclient/client.go +++ b/beacon/beaclient/client.go @@ -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) diff --git a/beacon/light/chain.go b/beacon/light/chain.go index 9119b00080..247687fdf7 100644 --- a/beacon/light/chain.go +++ b/beacon/light/chain.go @@ -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) } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a81c7c386e..61c93f02c6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -147,6 +147,7 @@ var ( utils.LogDebugFlag, utils.LogBacktraceAtFlag, utils.BeaconAPIFlag, + utils.BeaconAPIHeadersFlag, utils.BeaconTrustedBlockRootFlag, }, utils.NetworkFlags, utils.DatabaseFlags) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 08d8b232f7..64a9a476a6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -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) } diff --git a/eth/backend.go b/eth/backend.go index 8de18f6581..bb2e41ddaa 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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 } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 0846fb4de7..26d50c9b90 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -115,6 +115,7 @@ type Config struct { // Beacon light client options BeaconAPI string + BeaconAPIHeaders []string BeaconTrustedBlockRoot string // Database options