-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(op-dispute-mon): initial setup (#9359)
- Loading branch information
Showing
12 changed files
with
603 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,20 @@ | ||
# op-dispute-mon | ||
|
||
The `op-dispute-mon` is an off-chain service to monitor dispute games. | ||
|
||
## Quickstart | ||
|
||
Clone this repo. Then run: | ||
|
||
```shell | ||
make op-dispute-mon | ||
``` | ||
|
||
This will build the `op-dispute-mon` binary which can be run with | ||
`./op-dispute-mon/bin/op-dispute-mon`. | ||
|
||
## Usage | ||
|
||
`op-dispute-mon` is configurable via command line flags and environment variables. The help menu | ||
shows the available config options and can be accessed by running `./op-dispute-mon --help`. | ||
|
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,69 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"os" | ||
|
||
"github.com/urfave/cli/v2" | ||
|
||
"github.com/ethereum/go-ethereum/log" | ||
|
||
monitor "github.com/ethereum-optimism/optimism/op-dispute-mon" | ||
"github.com/ethereum-optimism/optimism/op-dispute-mon/config" | ||
"github.com/ethereum-optimism/optimism/op-dispute-mon/flags" | ||
"github.com/ethereum-optimism/optimism/op-dispute-mon/version" | ||
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/opio" | ||
) | ||
|
||
var ( | ||
GitCommit = "" | ||
GitDate = "" | ||
) | ||
|
||
// VersionWithMeta holds the textual version string including the metadata. | ||
var VersionWithMeta = opservice.FormatVersion(version.Version, GitCommit, GitDate, version.Meta) | ||
|
||
func main() { | ||
args := os.Args | ||
ctx := opio.WithInterruptBlocker(context.Background()) | ||
if err := run(ctx, args, monitor.Main); err != nil { | ||
log.Crit("Application failed", "err", err) | ||
} | ||
} | ||
|
||
type ConfiguredLifecycle func(ctx context.Context, log log.Logger, config *config.Config) (cliapp.Lifecycle, error) | ||
|
||
func run(ctx context.Context, args []string, action ConfiguredLifecycle) error { | ||
oplog.SetupDefaults() | ||
|
||
app := cli.NewApp() | ||
app.Version = VersionWithMeta | ||
app.Flags = cliapp.ProtectFlags(flags.Flags) | ||
app.Name = "op-dispute-mon" | ||
app.Usage = "Monitor dispute games" | ||
app.Description = "Monitors output proposals and dispute games." | ||
app.Action = cliapp.LifecycleCmd(func(ctx *cli.Context, close context.CancelCauseFunc) (cliapp.Lifecycle, error) { | ||
logger, err := setupLogging(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
logger.Info("Starting op-dispute-mon", "version", VersionWithMeta) | ||
|
||
cfg, err := flags.NewConfigFromCLI(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return action(ctx.Context, logger, cfg) | ||
}) | ||
return app.RunContext(ctx, args) | ||
} | ||
|
||
func setupLogging(ctx *cli.Context) (log.Logger, error) { | ||
logCfg := oplog.ReadCLIConfig(ctx) | ||
logger := oplog.NewLogger(oplog.AppOut(ctx), logCfg) | ||
oplog.SetGlobalLogHandler(logger.GetHandler()) | ||
return logger, 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,43 @@ | ||
package config | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" | ||
"github.com/ethereum-optimism/optimism/op-service/oppprof" | ||
) | ||
|
||
var ( | ||
ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url") | ||
) | ||
|
||
// Config is a well typed config that is parsed from the CLI params. | ||
// It also contains config options for auxiliary services. | ||
type Config struct { | ||
L1EthRpc string // L1 RPC Url | ||
|
||
MetricsConfig opmetrics.CLIConfig | ||
PprofConfig oppprof.CLIConfig | ||
} | ||
|
||
func NewConfig(l1EthRpc string) Config { | ||
return Config{ | ||
L1EthRpc: l1EthRpc, | ||
MetricsConfig: opmetrics.DefaultCLIConfig(), | ||
PprofConfig: oppprof.DefaultCLIConfig(), | ||
} | ||
} | ||
|
||
func (c Config) Check() error { | ||
if c.L1EthRpc == "" { | ||
return ErrMissingL1EthRPC | ||
} | ||
if err := c.MetricsConfig.Check(); err != nil { | ||
return fmt.Errorf("metrics config: %w", err) | ||
} | ||
if err := c.PprofConfig.Check(); err != nil { | ||
return fmt.Errorf("pprof config: %w", err) | ||
} | ||
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,21 @@ | ||
package config | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
var ( | ||
validL1EthRpc = "http://localhost:8545" | ||
) | ||
|
||
func validConfig() Config { | ||
return NewConfig(validL1EthRpc) | ||
} | ||
|
||
func TestL1EthRpcRequired(t *testing.T) { | ||
config := validConfig() | ||
config.L1EthRpc = "" | ||
require.ErrorIs(t, config.Check(), ErrMissingL1EthRPC) | ||
} |
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,75 @@ | ||
package flags | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/urfave/cli/v2" | ||
|
||
"github.com/ethereum-optimism/optimism/op-dispute-mon/config" | ||
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" | ||
) | ||
|
||
const ( | ||
envVarPrefix = "OP_DISPUTE_MON" | ||
) | ||
|
||
func prefixEnvVars(name string) []string { | ||
return opservice.PrefixEnvVar(envVarPrefix, name) | ||
} | ||
|
||
var ( | ||
// Required Flags | ||
L1EthRpcFlag = &cli.StringFlag{ | ||
Name: "l1-eth-rpc", | ||
Usage: "HTTP provider URL for L1.", | ||
EnvVars: prefixEnvVars("L1_ETH_RPC"), | ||
} | ||
// Optional Flags | ||
) | ||
|
||
// requiredFlags are checked by [CheckRequired] | ||
var requiredFlags = []cli.Flag{ | ||
L1EthRpcFlag, | ||
} | ||
|
||
// optionalFlags is a list of unchecked cli flags | ||
var optionalFlags = []cli.Flag{} | ||
|
||
func init() { | ||
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...) | ||
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...) | ||
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...) | ||
|
||
Flags = append(requiredFlags, 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 | ||
} | ||
|
||
// NewConfigFromCLI parses the Config from the provided flags or environment variables. | ||
func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { | ||
if err := CheckRequired(ctx); err != nil { | ||
return nil, err | ||
} | ||
|
||
metricsConfig := opmetrics.ReadCLIConfig(ctx) | ||
pprofConfig := oppprof.ReadCLIConfig(ctx) | ||
|
||
return &config.Config{ | ||
L1EthRpc: ctx.String(L1EthRpcFlag.Name), | ||
MetricsConfig: metricsConfig, | ||
PprofConfig: pprofConfig, | ||
}, 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,82 @@ | ||
package flags | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
"testing" | ||
|
||
opservice "github.com/ethereum-optimism/optimism/op-service" | ||
|
||
"github.com/stretchr/testify/require" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
// 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{}{} | ||
} | ||
} | ||
} | ||
|
||
// TestUniqueEnvVars asserts that all flag env vars are unique, to avoid accidental conflicts between the many flags. | ||
func TestUniqueEnvVars(t *testing.T) { | ||
seenCLI := make(map[string]struct{}) | ||
for _, flag := range Flags { | ||
envVar := envVarForFlag(flag) | ||
if _, ok := seenCLI[envVar]; envVar != "" && ok { | ||
t.Errorf("duplicate flag env var %s", envVar) | ||
continue | ||
} | ||
seenCLI[envVar] = struct{}{} | ||
} | ||
} | ||
|
||
func TestCorrectEnvVarPrefix(t *testing.T) { | ||
for _, flag := range Flags { | ||
envVar := envVarForFlag(flag) | ||
if envVar == "" { | ||
t.Errorf("Failed to find EnvVar for flag %v", flag.Names()[0]) | ||
} | ||
if !strings.HasPrefix(envVar, fmt.Sprintf("%s_", envVarPrefix)) { | ||
t.Errorf("Flag %v env var (%v) does not start with %s_", flag.Names()[0], envVar, envVarPrefix) | ||
} | ||
if strings.Contains(envVar, "__") { | ||
t.Errorf("Flag %v env var (%v) has duplicate underscores", flag.Names()[0], envVar) | ||
} | ||
} | ||
} | ||
|
||
func envVarForFlag(flag cli.Flag) string { | ||
values := reflect.ValueOf(flag) | ||
envVarValue := values.Elem().FieldByName("EnvVars") | ||
if envVarValue == (reflect.Value{}) || envVarValue.Len() == 0 { | ||
return "" | ||
} | ||
return envVarValue.Index(0).String() | ||
} | ||
|
||
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, envVarPrefix) | ||
require.Equal(t, expectedEnvVar, envFlags[0]) | ||
}) | ||
} | ||
} |
Oops, something went wrong.