-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
op-supervisor: initial service draft (#10703)
* op-supervisor: initial service draft * supervisor: Fix log capitalization * op-supervisor: fix mockrun flag * op-supervisor: CLI tests --------- Co-authored-by: Adrian Sutton <[email protected]>
- Loading branch information
1 parent
19d7b72
commit 55b67e0
Showing
16 changed files
with
1,017 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"os" | ||
|
||
"github.com/urfave/cli/v2" | ||
|
||
"github.com/ethereum/go-ethereum/log" | ||
|
||
opservice "github.com/ethereum-optimism/optimism/op-service" | ||
"github.com/ethereum-optimism/optimism/op-service/cliapp" | ||
oplog "github.com/ethereum-optimism/optimism/op-service/log" | ||
"github.com/ethereum-optimism/optimism/op-service/metrics/doc" | ||
"github.com/ethereum-optimism/optimism/op-service/opio" | ||
"github.com/ethereum-optimism/optimism/op-supervisor/flags" | ||
"github.com/ethereum-optimism/optimism/op-supervisor/metrics" | ||
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor" | ||
) | ||
|
||
var ( | ||
Version = "v0.0.1" | ||
GitCommit = "" | ||
GitDate = "" | ||
) | ||
|
||
func main() { | ||
ctx := opio.WithInterruptBlocker(context.Background()) | ||
err := run(ctx, os.Args, fromConfig) | ||
if err != nil { | ||
log.Crit("Application failed", "message", err) | ||
} | ||
} | ||
|
||
func run(ctx context.Context, args []string, fn supervisor.MainFn) error { | ||
oplog.SetupDefaults() | ||
|
||
app := cli.NewApp() | ||
app.Flags = cliapp.ProtectFlags(flags.Flags) | ||
app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "") | ||
app.Name = "op-supervisor" | ||
app.Usage = "op-supervisor monitors cross-L2 interop messaging" | ||
app.Description = "The op-supervisor monitors cross-L2 interop messaging by pre-fetching events and then resolving the cross-L2 dependencies to answer safety queries." | ||
app.Action = cliapp.LifecycleCmd(supervisor.Main(Version, fn)) | ||
app.Commands = []*cli.Command{ | ||
{ | ||
Name: "doc", | ||
Subcommands: doc.NewSubcommands(metrics.NewMetrics("default")), | ||
}, | ||
} | ||
return app.RunContext(ctx, args) | ||
} | ||
|
||
func fromConfig(ctx context.Context, cfg *supervisor.CLIConfig, logger log.Logger) (cliapp.Lifecycle, error) { | ||
return supervisor.SupervisorFromCLIConfig(ctx, cfg, logger) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ethereum/go-ethereum/log" | ||
|
||
"github.com/ethereum-optimism/optimism/op-service/cliapp" | ||
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor" | ||
) | ||
|
||
func TestLogLevel(t *testing.T) { | ||
t.Run("RejectInvalid", func(t *testing.T) { | ||
verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs("--log.level=foo")) | ||
}) | ||
|
||
for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} { | ||
lvl := lvl | ||
t.Run("AcceptValid_"+lvl, func(t *testing.T) { | ||
logger, _, err := dryRunWithArgs(addRequiredArgs("--log.level", lvl)) | ||
require.NoError(t, err) | ||
require.NotNil(t, logger) | ||
}) | ||
} | ||
} | ||
|
||
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { | ||
cfg := configForArgs(t, addRequiredArgs()) | ||
defaultCfgTempl := supervisor.DefaultCLIConfig() | ||
defaultCfg := *defaultCfgTempl | ||
defaultCfg.Version = Version | ||
require.Equal(t, defaultCfg, *cfg) | ||
} | ||
|
||
func TestL2RPCs(t *testing.T) { | ||
t.Run("Required", func(t *testing.T) { | ||
verifyArgsInvalid(t, "flag l2-rpcs is required", addRequiredArgsExcept("--l2-rpcs")) | ||
}) | ||
|
||
t.Run("Valid", func(t *testing.T) { | ||
url1 := "http://example.com:1234" | ||
url2 := "http://foobar.com:1234" | ||
cfg := configForArgs(t, addRequiredArgsExcept("--l2-rpcs", "--l2-rpcs="+url1+","+url2)) | ||
require.Equal(t, []string{url1, url2}, cfg.L2RPCs) | ||
}) | ||
} | ||
|
||
func TestMockRun(t *testing.T) { | ||
t.Run("Valid", func(t *testing.T) { | ||
cfg := configForArgs(t, addRequiredArgs("--mock-run")) | ||
require.Equal(t, true, cfg.MockRun) | ||
}) | ||
} | ||
|
||
func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) { | ||
_, _, err := dryRunWithArgs(cliArgs) | ||
require.ErrorContains(t, err, messageContains) | ||
} | ||
|
||
func configForArgs(t *testing.T, cliArgs []string) *supervisor.CLIConfig { | ||
_, cfg, err := dryRunWithArgs(cliArgs) | ||
require.NoError(t, err) | ||
return cfg | ||
} | ||
|
||
func dryRunWithArgs(cliArgs []string) (log.Logger, *supervisor.CLIConfig, error) { | ||
cfg := new(supervisor.CLIConfig) | ||
var logger log.Logger | ||
fullArgs := append([]string{"op-supervisor"}, cliArgs...) | ||
testErr := errors.New("dry-run") | ||
err := run(context.Background(), fullArgs, func(ctx context.Context, config *supervisor.CLIConfig, log log.Logger) (cliapp.Lifecycle, error) { | ||
logger = log | ||
cfg = config | ||
return nil, testErr | ||
}) | ||
if errors.Is(err, testErr) { // expected error | ||
err = nil | ||
} | ||
return logger, cfg, err | ||
} | ||
|
||
func addRequiredArgs(args ...string) []string { | ||
req := requiredArgs() | ||
combined := toArgList(req) | ||
return append(combined, args...) | ||
} | ||
|
||
func addRequiredArgsExcept(name string, optionalArgs ...string) []string { | ||
req := requiredArgs() | ||
delete(req, name) | ||
return append(toArgList(req), optionalArgs...) | ||
} | ||
|
||
func toArgList(req map[string]string) []string { | ||
var combined []string | ||
for name, value := range req { | ||
combined = append(combined, fmt.Sprintf("%s=%s", name, value)) | ||
} | ||
return combined | ||
} | ||
|
||
func requiredArgs() map[string]string { | ||
args := map[string]string{ | ||
"--l2-rpcs": "http://localhost:8545", | ||
} | ||
return args | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package flags | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/urfave/cli/v2" | ||
|
||
opservice "github.com/ethereum-optimism/optimism/op-service" | ||
oplog "github.com/ethereum-optimism/optimism/op-service/log" | ||
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" | ||
"github.com/ethereum-optimism/optimism/op-service/oppprof" | ||
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" | ||
) | ||
|
||
const EnvVarPrefix = "OP_SUPERVISOR" | ||
|
||
func prefixEnvVars(name string) []string { | ||
return opservice.PrefixEnvVar(EnvVarPrefix, name) | ||
} | ||
|
||
var ( | ||
L2RPCsFlag = &cli.StringSliceFlag{ | ||
Name: "l2-rpcs", | ||
Usage: "L2 RPC sources.", | ||
EnvVars: prefixEnvVars("L2_RPCS"), | ||
Value: cli.NewStringSlice("http://localhost:8545"), | ||
} | ||
MockRunFlag = &cli.BoolFlag{ | ||
Name: "mock-run", | ||
Usage: "Mock run, no actual backend used, just presenting the service", | ||
EnvVars: prefixEnvVars("MOCK_RUN"), | ||
Hidden: true, // this is for testing only | ||
} | ||
) | ||
|
||
var requiredFlags = []cli.Flag{ | ||
L2RPCsFlag, | ||
} | ||
|
||
var optionalFlags = []cli.Flag{ | ||
MockRunFlag, | ||
} | ||
|
||
func init() { | ||
optionalFlags = append(optionalFlags, oprpc.CLIFlags(EnvVarPrefix)...) | ||
optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...) | ||
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...) | ||
optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...) | ||
|
||
Flags = append(Flags, requiredFlags...) | ||
Flags = append(Flags, optionalFlags...) | ||
} | ||
|
||
// Flags contains the list of configuration options available to the binary. | ||
var Flags []cli.Flag | ||
|
||
func CheckRequired(ctx *cli.Context) error { | ||
for _, f := range requiredFlags { | ||
if !ctx.IsSet(f.Names()[0]) { | ||
return fmt.Errorf("flag %s is required", f.Names()[0]) | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package flags | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"github.com/urfave/cli/v2" | ||
|
||
opservice "github.com/ethereum-optimism/optimism/op-service" | ||
) | ||
|
||
// TestOptionalFlagsDontSetRequired asserts that all flags deemed optional set | ||
// the Required field to false. | ||
func TestOptionalFlagsDontSetRequired(t *testing.T) { | ||
for _, flag := range optionalFlags { | ||
reqFlag, ok := flag.(cli.RequiredFlag) | ||
require.True(t, ok) | ||
require.False(t, reqFlag.IsRequired()) | ||
} | ||
} | ||
|
||
// TestUniqueFlags asserts that all flag names are unique, to avoid accidental conflicts between the many flags. | ||
func TestUniqueFlags(t *testing.T) { | ||
seenCLI := make(map[string]struct{}) | ||
for _, flag := range Flags { | ||
for _, name := range flag.Names() { | ||
if _, ok := seenCLI[name]; ok { | ||
t.Errorf("duplicate flag %s", name) | ||
continue | ||
} | ||
seenCLI[name] = struct{}{} | ||
} | ||
} | ||
} | ||
|
||
// TestBetaFlags test that all flags starting with "beta." have "BETA_" in the env var, and vice versa. | ||
func TestBetaFlags(t *testing.T) { | ||
for _, flag := range Flags { | ||
envFlag, ok := flag.(interface { | ||
GetEnvVars() []string | ||
}) | ||
if !ok || len(envFlag.GetEnvVars()) == 0 { // skip flags without env-var support | ||
continue | ||
} | ||
name := flag.Names()[0] | ||
envName := envFlag.GetEnvVars()[0] | ||
if strings.HasPrefix(name, "beta.") { | ||
require.Contains(t, envName, "BETA_", "%q flag must contain BETA in env var to match \"beta.\" flag name", name) | ||
} | ||
if strings.Contains(envName, "BETA_") { | ||
require.True(t, strings.HasPrefix(name, "beta."), "%q flag must start with \"beta.\" in flag name to match \"BETA_\" env var", name) | ||
} | ||
} | ||
} | ||
|
||
func TestHasEnvVar(t *testing.T) { | ||
for _, flag := range Flags { | ||
flag := flag | ||
flagName := flag.Names()[0] | ||
|
||
t.Run(flagName, func(t *testing.T) { | ||
envFlagGetter, ok := flag.(interface { | ||
GetEnvVars() []string | ||
}) | ||
envFlags := envFlagGetter.GetEnvVars() | ||
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") | ||
}) | ||
} | ||
} | ||
|
||
func TestEnvVarFormat(t *testing.T) { | ||
for _, flag := range Flags { | ||
flag := flag | ||
flagName := flag.Names()[0] | ||
|
||
t.Run(flagName, func(t *testing.T) { | ||
envFlagGetter, ok := flag.(interface { | ||
GetEnvVars() []string | ||
}) | ||
envFlags := envFlagGetter.GetEnvVars() | ||
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") | ||
expectedEnvVar := opservice.FlagNameToEnvVarName(flagName, "OP_SUPERVISOR") | ||
require.Equal(t, expectedEnvVar, envFlags[0]) | ||
}) | ||
} | ||
} |
Oops, something went wrong.