Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions api/client/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ go_library(
"client.go",
"errors.go",
"options.go",
"transport.go",
],
importpath = "github.com/OffchainLabs/prysm/v6/api/client",
visibility = ["//visibility:public"],
Expand All @@ -14,7 +15,13 @@ go_library(

go_test(
name = "go_default_test",
srcs = ["client_test.go"],
srcs = [
"client_test.go",
"transport_test.go",
],
embed = [":go_default_library"],
deps = ["//testing/require:go_default_library"],
deps = [
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
],
)
25 changes: 25 additions & 0 deletions api/client/transport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package client

import "net/http"

// CustomHeadersTransport adds custom headers to each request
type CustomHeadersTransport struct {
base http.RoundTripper
headers map[string][]string
}

func NewCustomHeadersTransport(base http.RoundTripper, headers map[string][]string) *CustomHeadersTransport {
return &CustomHeadersTransport{
base: base,
headers: headers,
}
}

func (t *CustomHeadersTransport) RoundTrip(req *http.Request) (*http.Response, error) {
for header, values := range t.headers {
for _, value := range values {
req.Header.Add(header, value)
}
}
return t.base.RoundTrip(req)
}
25 changes: 25 additions & 0 deletions api/client/transport_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package client

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
)

type noopTransport struct{}

func (*noopTransport) RoundTrip(*http.Request) (*http.Response, error) {
return nil, nil
}

func TestRoundTrip(t *testing.T) {
tr := &CustomHeadersTransport{base: &noopTransport{}, headers: map[string][]string{"key1": []string{"value1", "value2"}, "key2": []string{"value3"}}}
req := httptest.NewRequest("GET", "http://foo", nil)
_, err := tr.RoundTrip(req)
require.NoError(t, err)
assert.DeepEqual(t, []string{"value1", "value2"}, req.Header.Values("key1"))
assert.DeepEqual(t, []string{"value3"}, req.Header.Values("key2"))
}
3 changes: 3 additions & 0 deletions changelog/radek_rest-custom-headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Allow custom headers in validator client HTTP requests.
6 changes: 6 additions & 0 deletions cmd/validator/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ var (
Usage: "Beacon node REST API provider endpoint.",
Value: "http://127.0.0.1:3500",
}
// BeaconRESTApiHeaders defines a list of headers to send with all HTTP requests to the beacon node.
BeaconRESTApiHeaders = &cli.StringFlag{
Name: "beacon-rest-api-headers",
Copy link
Contributor Author

@rkapka rkapka Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is a little verbose but I wanted to keep it aligned with the --beacon-rest-api-provider flag

Usage: `Comma-separated list of key value pairs to pass as headers for all HTTP calls to the beacon node.
Example: --grpc-headers=key1=value1, key2=value2`,
}
// CertFlag defines a flag for the node's TLS certificate.
CertFlag = &cli.StringFlag{
Name: "tls-cert",
Expand Down
1 change: 1 addition & 0 deletions cmd/validator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func startNode(ctx *cli.Context) error {
var appFlags = []cli.Flag{
flags.BeaconRPCProviderFlag,
flags.BeaconRESTApiProviderFlag,
flags.BeaconRESTApiHeaders,
flags.CertFlag,
flags.GraffitiFlag,
flags.DisablePenaltyRewardLogFlag,
Expand Down
1 change: 1 addition & 0 deletions cmd/validator/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ var appHelpFlagGroups = []flagGroup{
Flags: []cli.Flag{
flags.CertFlag,
flags.BeaconRPCProviderFlag,
flags.BeaconRESTApiHeaders,
flags.EnableRPCFlag,
flags.RPCHost,
flags.RPCPort,
Expand Down
1 change: 1 addition & 0 deletions validator/accounts/cli_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func (acm *CLIManager) prepareBeaconClients(ctx context.Context) (*iface.Validat
conn := validatorHelpers.NewNodeConnection(
grpcConn,
acm.beaconApiEndpoint,
nil,
acm.beaconApiTimeout,
)

Expand Down
6 changes: 5 additions & 1 deletion validator/client/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"time"

api "github.com/OffchainLabs/prysm/v6/api/client"
eventClient "github.com/OffchainLabs/prysm/v6/api/client/event"
grpcutil "github.com/OffchainLabs/prysm/v6/api/grpc"
"github.com/OffchainLabs/prysm/v6/async/event"
Expand Down Expand Up @@ -79,6 +80,7 @@ type Config struct {
BeaconNodeGRPCEndpoint string
BeaconNodeCert string
BeaconApiEndpoint string
BeaconApiHeaders map[string][]string
BeaconApiTimeout time.Duration
Graffiti string
GraffitiStruct *graffiti.Graffiti
Expand Down Expand Up @@ -142,6 +144,7 @@ func NewValidatorService(ctx context.Context, cfg *Config) (*ValidatorService, e
s.conn = validatorHelpers.NewNodeConnection(
grpcConn,
cfg.BeaconApiEndpoint,
cfg.BeaconApiHeaders,
cfg.BeaconApiTimeout,
)

Expand Down Expand Up @@ -185,8 +188,9 @@ func (v *ValidatorService) Start() {
return
}

headersTransport := api.NewCustomHeadersTransport(http.DefaultTransport, v.conn.GetBeaconApiHeaders())
restHandler := beaconApi.NewBeaconApiRestHandler(
http.Client{Timeout: v.conn.GetBeaconApiTimeout(), Transport: otelhttp.NewTransport(http.DefaultTransport)},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the DefaultTransport do nothing? or does it add some default parameters to the request? because maybe we need to extend it instead of replacing it?

http.Client{Timeout: v.conn.GetBeaconApiTimeout(), Transport: otelhttp.NewTransport(headersTransport)},
Comment on lines 192 to +193
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if possible, we should have tests to see if the headers get added after the Start()

hosts[0],
)

Expand Down
9 changes: 8 additions & 1 deletion validator/helpers/node_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import (
type NodeConnection interface {
GetGrpcClientConn() *grpc.ClientConn
GetBeaconApiUrl() string
GetBeaconApiHeaders() map[string][]string
GetBeaconApiTimeout() time.Duration
dummy()
}

type nodeConnection struct {
grpcClientConn *grpc.ClientConn
beaconApiUrl string
beaconApiHeaders map[string][]string
beaconApiTimeout time.Duration
}

Expand All @@ -28,16 +30,21 @@ func (c *nodeConnection) GetBeaconApiUrl() string {
return c.beaconApiUrl
}

func (c *nodeConnection) GetBeaconApiHeaders() map[string][]string {
return c.beaconApiHeaders
}

func (c *nodeConnection) GetBeaconApiTimeout() time.Duration {
return c.beaconApiTimeout
}

func (*nodeConnection) dummy() {}

func NewNodeConnection(grpcConn *grpc.ClientConn, beaconApiUrl string, beaconApiTimeout time.Duration) NodeConnection {
func NewNodeConnection(grpcConn *grpc.ClientConn, beaconApiUrl string, beaconApiHeaders map[string][]string, beaconApiTimeout time.Duration) NodeConnection {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe for stuff liek api headers and beacon api timeout we can turn those into functional parameters and add them in that way

conn := &nodeConnection{}
conn.grpcClientConn = grpcConn
conn.beaconApiUrl = beaconApiUrl
conn.beaconApiHeaders = beaconApiHeaders
conn.beaconApiTimeout = beaconApiTimeout
return conn
}
18 changes: 18 additions & 0 deletions validator/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ func (c *ValidatorClient) registerValidatorService(cliCtx *cli.Context) error {
BeaconNodeGRPCEndpoint: cliCtx.String(flags.BeaconRPCProviderFlag.Name),
BeaconNodeCert: cliCtx.String(flags.CertFlag.Name),
BeaconApiEndpoint: cliCtx.String(flags.BeaconRESTApiProviderFlag.Name),
BeaconApiHeaders: parseBeaconApiHeaders(cliCtx.String(flags.BeaconRESTApiHeaders.Name)),
BeaconApiTimeout: time.Second * 30,
Graffiti: g.ParseHexGraffiti(cliCtx.String(flags.GraffitiFlag.Name)),
GraffitiStruct: graffitiStruct,
Expand Down Expand Up @@ -552,6 +553,7 @@ func (c *ValidatorClient) registerRPCService(cliCtx *cli.Context) error {
GRPCHeaders: strings.Split(cliCtx.String(flags.GRPCHeadersFlag.Name), ","),
BeaconNodeGRPCEndpoint: cliCtx.String(flags.BeaconRPCProviderFlag.Name),
BeaconApiEndpoint: cliCtx.String(flags.BeaconRESTApiProviderFlag.Name),
BeaconAPIHeaders: parseBeaconApiHeaders(cliCtx.String(flags.BeaconRESTApiHeaders.Name)),
BeaconApiTimeout: time.Second * 30,
BeaconNodeCert: cliCtx.String(flags.CertFlag.Name),
DB: c.db,
Expand Down Expand Up @@ -636,3 +638,19 @@ func clearDB(ctx context.Context, dataDir string, force bool, isDatabaseMinimal

return nil
}

func parseBeaconApiHeaders(rawHeaders string) map[string][]string {
result := make(map[string][]string)
pairs := strings.Split(rawHeaders, ",")
for _, pair := range pairs {
key, value, found := strings.Cut(pair, "=")
if !found {
// Skip malformed pairs
continue
Comment on lines +648 to +649
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still allows for some weird malformed data like key===val=ue but I don't think it's that important to handle all edge cases

Copy link
Contributor

@Inspector-Butters Inspector-Butters Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add some tests for this function (parseBeaconApiHeaders())

}
key = strings.TrimSpace(key)
value = strings.TrimSpace(value)
result[key] = append(result[key], value)
}
return result
}
1 change: 1 addition & 0 deletions validator/rpc/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ go_library(
],
deps = [
"//api:go_default_library",
"//api/client:go_default_library",
"//api/grpc:go_default_library",
"//api/pagination:go_default_library",
"//api/server:go_default_library",
Expand Down
5 changes: 4 additions & 1 deletion validator/rpc/beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rpc
import (
"net/http"

api "github.com/OffchainLabs/prysm/v6/api/client"
grpcutil "github.com/OffchainLabs/prysm/v6/api/grpc"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/validator/client"
Expand Down Expand Up @@ -52,11 +53,13 @@ func (s *Server) registerBeaconClient() error {
conn := validatorHelpers.NewNodeConnection(
grpcConn,
s.beaconApiEndpoint,
s.beaconApiHeaders,
s.beaconApiTimeout,
)

headersTransport := api.NewCustomHeadersTransport(http.DefaultTransport, conn.GetBeaconApiHeaders())
restHandler := beaconApi.NewBeaconApiRestHandler(
http.Client{Timeout: s.beaconApiTimeout, Transport: otelhttp.NewTransport(http.DefaultTransport)},
http.Client{Timeout: s.beaconApiTimeout, Transport: otelhttp.NewTransport(headersTransport)},
s.beaconApiEndpoint,
Comment on lines +60 to 63
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here too, if possible, we should test if headers are being added after registerBeaconClient()

)

Expand Down
3 changes: 3 additions & 0 deletions validator/rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Config struct {
GRPCHeaders []string
BeaconNodeGRPCEndpoint string
BeaconApiEndpoint string
BeaconAPIHeaders map[string][]string
BeaconApiTimeout time.Duration
BeaconNodeCert string
DB db.Database
Expand Down Expand Up @@ -64,6 +65,7 @@ type Server struct {
authTokenPath string
beaconNodeCert string
beaconApiEndpoint string
beaconApiHeaders map[string][]string
beaconNodeEndpoint string
healthClient ethpb.HealthClient
nodeClient iface.NodeClient
Expand Down Expand Up @@ -103,6 +105,7 @@ func NewServer(ctx context.Context, cfg *Config) *Server {
wallet: cfg.Wallet,
beaconApiTimeout: cfg.BeaconApiTimeout,
beaconApiEndpoint: cfg.BeaconApiEndpoint,
beaconApiHeaders: cfg.BeaconAPIHeaders,
beaconNodeEndpoint: cfg.BeaconNodeGRPCEndpoint,
router: cfg.Router,
}
Expand Down
Loading