Commit b40933ec authored by Sebastian Stammler's avatar Sebastian Stammler Committed by GitHub

op-node: Support multiple beacon archiver/fallbacks (#10655)

* op-node: Support multiple beacon archiver/fallbacks

* op-node: rename flag to l1.beacon-fallbacks

- retain old flag name and env var
- fix some tests

* Update op-node/node/client_test.go
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>

---------
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>
parent 5c187bc7
...@@ -34,8 +34,12 @@ func init() { ...@@ -34,8 +34,12 @@ func init() {
cli.VersionFlag.(*cli.BoolFlag).Category = MiscCategory cli.VersionFlag.(*cli.BoolFlag).Category = MiscCategory
} }
func prefixEnvVars(name string) []string { func prefixEnvVars(names ...string) []string {
return []string{EnvVarPrefix + "_" + name} envs := make([]string, 0, len(names))
for _, name := range names {
envs = append(envs, EnvVarPrefix+"_"+name)
}
return envs
} }
var ( var (
...@@ -76,11 +80,11 @@ var ( ...@@ -76,11 +80,11 @@ var (
EnvVars: prefixEnvVars("L1_BEACON_HEADER"), EnvVars: prefixEnvVars("L1_BEACON_HEADER"),
Category: L1RPCCategory, Category: L1RPCCategory,
} }
BeaconArchiverAddr = &cli.StringFlag{ BeaconFallbackAddrs = &cli.StringSliceFlag{
Name: "l1.beacon-archiver", Name: "l1.beacon-fallbacks",
Usage: "Address of L1 Beacon-node compatible HTTP endpoint to use. This is used to fetch blobs that the --l1.beacon does not have (i.e expired blobs).", Aliases: []string{"l1.beacon-archiver"},
Required: false, Usage: "Addresses of L1 Beacon-API compatible HTTP fallback endpoints. Used to fetch blob sidecars not availalbe at the l1.beacon (e.g. expired blobs).",
EnvVars: prefixEnvVars("L1_BEACON_ARCHIVER"), EnvVars: prefixEnvVars("L1_BEACON_FALLBACKS", "L1_BEACON_ARCHIVER"),
Category: L1RPCCategory, Category: L1RPCCategory,
} }
BeaconCheckIgnore = &cli.BoolFlag{ BeaconCheckIgnore = &cli.BoolFlag{
...@@ -364,7 +368,7 @@ var requiredFlags = []cli.Flag{ ...@@ -364,7 +368,7 @@ var requiredFlags = []cli.Flag{
var optionalFlags = []cli.Flag{ var optionalFlags = []cli.Flag{
BeaconAddr, BeaconAddr,
BeaconHeader, BeaconHeader,
BeaconArchiverAddr, BeaconFallbackAddrs,
BeaconCheckIgnore, BeaconCheckIgnore,
BeaconFetchAllSidecars, BeaconFetchAllSidecars,
SyncModeFlag, SyncModeFlag,
......
...@@ -61,7 +61,6 @@ func TestDeprecatedFlagsAreHidden(t *testing.T) { ...@@ -61,7 +61,6 @@ func TestDeprecatedFlagsAreHidden(t *testing.T) {
flagName := flag.Names()[0] flagName := flag.Names()[0]
t.Run(flagName, func(t *testing.T) { t.Run(flagName, func(t *testing.T) {
visibleFlag, ok := flag.(interface { visibleFlag, ok := flag.(interface {
IsVisible() bool IsVisible() bool
}) })
...@@ -72,6 +71,11 @@ func TestDeprecatedFlagsAreHidden(t *testing.T) { ...@@ -72,6 +71,11 @@ func TestDeprecatedFlagsAreHidden(t *testing.T) {
} }
func TestHasEnvVar(t *testing.T) { func TestHasEnvVar(t *testing.T) {
// known exceptions to the number of env vars
expEnvVars := map[string]int{
BeaconFallbackAddrs.Name: 2,
}
for _, flag := range Flags { for _, flag := range Flags {
flag := flag flag := flag
flagName := flag.Names()[0] flagName := flag.Names()[0]
...@@ -83,39 +87,43 @@ func TestHasEnvVar(t *testing.T) { ...@@ -83,39 +87,43 @@ func TestHasEnvVar(t *testing.T) {
envFlagGetter, ok := flag.(interface { envFlagGetter, ok := flag.(interface {
GetEnvVars() []string GetEnvVars() []string
}) })
envFlags := envFlagGetter.GetEnvVars()
require.True(t, ok, "must be able to cast the flag to an EnvVar interface") require.True(t, ok, "must be able to cast the flag to an EnvVar interface")
require.Equal(t, 1, len(envFlags), "flags should have exactly one env var") envFlags := envFlagGetter.GetEnvVars()
if numEnvVars, ok := expEnvVars[flagName]; ok {
require.Equalf(t, numEnvVars, len(envFlags), "flags should have %d env vars", numEnvVars)
} else {
require.Equal(t, 1, len(envFlags), "flags should have exactly one env var")
}
}) })
} }
} }
func TestEnvVarFormat(t *testing.T) { func TestEnvVarFormat(t *testing.T) {
skippedFlags := []string{
L1NodeAddr.Name,
L2EngineAddr.Name,
L2EngineJWTSecret.Name,
L1TrustRPC.Name,
L1RPCProviderKind.Name,
SnapshotLog.Name,
BackupL2UnsafeSyncRPC.Name,
BackupL2UnsafeSyncRPCTrustRPC.Name,
"p2p.scoring",
"p2p.ban.peers",
"p2p.ban.threshold",
"p2p.ban.duration",
"p2p.listen.tcp",
"p2p.listen.udp",
"p2p.useragent",
"p2p.gossip.mesh.lo",
"p2p.gossip.mesh.floodpublish",
"l2.engine-sync",
}
for _, flag := range Flags { for _, flag := range Flags {
flag := flag flag := flag
flagName := flag.Names()[0] flagName := flag.Names()[0]
skippedFlags := []string{
L1NodeAddr.Name,
L2EngineAddr.Name,
L2EngineJWTSecret.Name,
L1TrustRPC.Name,
L1RPCProviderKind.Name,
SnapshotLog.Name,
BackupL2UnsafeSyncRPC.Name,
BackupL2UnsafeSyncRPCTrustRPC.Name,
"p2p.scoring",
"p2p.ban.peers",
"p2p.ban.threshold",
"p2p.ban.duration",
"p2p.listen.tcp",
"p2p.listen.udp",
"p2p.useragent",
"p2p.gossip.mesh.lo",
"p2p.gossip.mesh.floodpublish",
"l2.engine-sync",
}
t.Run(flagName, func(t *testing.T) { t.Run(flagName, func(t *testing.T) {
if slices.Contains(skippedFlags, flagName) { if slices.Contains(skippedFlags, flagName) {
t.Skipf("Skipping flag %v which is known to not have a standard flag name <-> env var conversion", flagName) t.Skipf("Skipping flag %v which is known to not have a standard flag name <-> env var conversion", flagName)
...@@ -126,9 +134,8 @@ func TestEnvVarFormat(t *testing.T) { ...@@ -126,9 +134,8 @@ func TestEnvVarFormat(t *testing.T) {
envFlagGetter, ok := flag.(interface { envFlagGetter, ok := flag.(interface {
GetEnvVars() []string GetEnvVars() []string
}) })
envFlags := envFlagGetter.GetEnvVars()
require.True(t, ok, "must be able to cast the flag to an EnvVar interface") require.True(t, ok, "must be able to cast the flag to an EnvVar interface")
require.Equal(t, 1, len(envFlags), "flags should have exactly one env var") envFlags := envFlagGetter.GetEnvVars()
expectedEnvVar := opservice.FlagNameToEnvVarName(flagName, "OP_NODE") expectedEnvVar := opservice.FlagNameToEnvVarName(flagName, "OP_NODE")
require.Equal(t, expectedEnvVar, envFlags[0]) require.Equal(t, expectedEnvVar, envFlags[0])
}) })
......
...@@ -178,11 +178,11 @@ func (cfg *PreparedL1Endpoint) Check() error { ...@@ -178,11 +178,11 @@ func (cfg *PreparedL1Endpoint) Check() error {
} }
type L1BeaconEndpointConfig struct { type L1BeaconEndpointConfig struct {
BeaconAddr string // Address of L1 User Beacon-API endpoint to use (beacon namespace required) BeaconAddr string // Address of L1 User Beacon-API endpoint to use (beacon namespace required)
BeaconHeader string // Optional HTTP header for all requests to L1 Beacon BeaconHeader string // Optional HTTP header for all requests to L1 Beacon
BeaconArchiverAddr string // Address of L1 User Beacon-API Archive endpoint to use for expired blobs (beacon namespace required) BeaconFallbackAddrs []string // Addresses of L1 Beacon-API fallback endpoints (only for blob sidecars retrieval)
BeaconCheckIgnore bool // When false, halt startup if the beacon version endpoint fails BeaconCheckIgnore bool // When false, halt startup if the beacon version endpoint fails
BeaconFetchAllSidecars bool // Whether to fetch all blob sidecars and filter locally BeaconFetchAllSidecars bool // Whether to fetch all blob sidecars and filter locally
} }
var _ L1BeaconEndpointSetup = (*L1BeaconEndpointConfig)(nil) var _ L1BeaconEndpointSetup = (*L1BeaconEndpointConfig)(nil)
...@@ -197,11 +197,12 @@ func (cfg *L1BeaconEndpointConfig) Setup(ctx context.Context, log log.Logger) (c ...@@ -197,11 +197,12 @@ func (cfg *L1BeaconEndpointConfig) Setup(ctx context.Context, log log.Logger) (c
opts = append(opts, client.WithHeader(hdr)) opts = append(opts, client.WithHeader(hdr))
} }
a := client.NewBasicHTTPClient(cfg.BeaconAddr, log, opts...) for _, addr := range cfg.BeaconFallbackAddrs {
if cfg.BeaconArchiverAddr != "" { b := client.NewBasicHTTPClient(addr, log)
b := client.NewBasicHTTPClient(cfg.BeaconArchiverAddr, log)
fb = append(fb, sources.NewBeaconHTTPClient(b)) fb = append(fb, sources.NewBeaconHTTPClient(b))
} }
a := client.NewBasicHTTPClient(cfg.BeaconAddr, log, opts...)
return sources.NewBeaconHTTPClient(a), fb, nil return sources.NewBeaconHTTPClient(a), fb, nil
} }
......
package node package node
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
...@@ -60,3 +61,32 @@ func TestParseHTTPHeader(t *testing.T) { ...@@ -60,3 +61,32 @@ func TestParseHTTPHeader(t *testing.T) {
}) })
} }
} }
func TestL1BeaconEndpointConfig_Setup(t *testing.T) {
for _, test := range []struct {
desc string
baa []string
len int
}{
{
desc: "empty",
},
{
desc: "one",
baa: []string{"http://foo.bar"},
len: 1,
},
{
desc: "three",
baa: []string{"http://foo.bar", "http://op.ti", "http://ba.se"},
len: 3,
},
} {
t.Run(test.desc, func(t *testing.T) {
cfg := L1BeaconEndpointConfig{BeaconFallbackAddrs: test.baa}
_, fb, err := cfg.Setup(context.Background(), nil)
require.NoError(t, err)
require.Len(t, fb, test.len)
})
}
}
...@@ -132,7 +132,7 @@ func NewBeaconEndpointConfig(ctx *cli.Context) node.L1BeaconEndpointSetup { ...@@ -132,7 +132,7 @@ func NewBeaconEndpointConfig(ctx *cli.Context) node.L1BeaconEndpointSetup {
return &node.L1BeaconEndpointConfig{ return &node.L1BeaconEndpointConfig{
BeaconAddr: ctx.String(flags.BeaconAddr.Name), BeaconAddr: ctx.String(flags.BeaconAddr.Name),
BeaconHeader: ctx.String(flags.BeaconHeader.Name), BeaconHeader: ctx.String(flags.BeaconHeader.Name),
BeaconArchiverAddr: ctx.String(flags.BeaconArchiverAddr.Name), BeaconFallbackAddrs: ctx.StringSlice(flags.BeaconFallbackAddrs.Name),
BeaconCheckIgnore: ctx.Bool(flags.BeaconCheckIgnore.Name), BeaconCheckIgnore: ctx.Bool(flags.BeaconCheckIgnore.Name),
BeaconFetchAllSidecars: ctx.Bool(flags.BeaconFetchAllSidecars.Name), BeaconFetchAllSidecars: ctx.Bool(flags.BeaconFetchAllSidecars.Name),
} }
......
...@@ -179,8 +179,9 @@ func NewL1BeaconClient(cl BeaconClient, cfg L1BeaconClientConfig, fallbacks ...B ...@@ -179,8 +179,9 @@ func NewL1BeaconClient(cl BeaconClient, cfg L1BeaconClientConfig, fallbacks ...B
cs := append([]BlobSideCarsFetcher{cl}, fallbacks...) cs := append([]BlobSideCarsFetcher{cl}, fallbacks...)
return &L1BeaconClient{ return &L1BeaconClient{
cl: cl, cl: cl,
pool: NewClientPool[BlobSideCarsFetcher](cs...), pool: NewClientPool(cs...),
cfg: cfg} cfg: cfg,
}
} }
type TimeToSlotFn func(timestamp uint64) (uint64, error) type TimeToSlotFn func(timestamp uint64) (uint64, error)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment