Commit 4460800e authored by Adrian Sutton's avatar Adrian Sutton

op-program: Load rollup config and log network info at startup

parent f9f26e33
package main package main
import ( import (
"fmt"
"os" "os"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-program/config" "github.com/ethereum-optimism/optimism/op-program/config"
"github.com/ethereum-optimism/optimism/op-program/flags" "github.com/ethereum-optimism/optimism/op-program/flags"
"github.com/ethereum-optimism/optimism/op-program/version" "github.com/ethereum-optimism/optimism/op-program/version"
...@@ -33,14 +35,17 @@ var VersionWithMeta = func() string { ...@@ -33,14 +35,17 @@ var VersionWithMeta = func() string {
func main() { func main() {
args := os.Args args := os.Args
err := run(args, FaultProofProgramMain) err := run(args, FaultProofProgram)
if err != nil { if err != nil {
log.Crit("Application failed", "message", err) log.Crit("Application failed", "message", err)
} }
} }
type ConfigAction func(log log.Logger, config config.Config) error type ConfigAction func(log log.Logger, config *config.Config) error
// run parses the supplied args to create a config.Config instance, sets up logging
// then calls the supplied ConfigAction.
// This allows testing the translation from CLI arguments to Config
func run(args []string, action ConfigAction) error { func run(args []string, action ConfigAction) error {
// Set up logger with a default INFO level in case we fail to parse flags, // Set up logger with a default INFO level in case we fail to parse flags,
// otherwise the final critical log won't show what the parsing error was. // otherwise the final critical log won't show what the parsing error was.
...@@ -53,12 +58,10 @@ func run(args []string, action ConfigAction) error { ...@@ -53,12 +58,10 @@ func run(args []string, action ConfigAction) error {
app.Usage = "Optimism Fault Proof Program" app.Usage = "Optimism Fault Proof Program"
app.Description = "The Optimism Fault Proof Program fault proof program that runs through the rollup state-transition to verify an L2 output from L1 inputs." app.Description = "The Optimism Fault Proof Program fault proof program that runs through the rollup state-transition to verify an L2 output from L1 inputs."
app.Action = func(ctx *cli.Context) error { app.Action = func(ctx *cli.Context) error {
logCfg := oplog.ReadCLIConfig(ctx) logger, err := setupLogging(ctx)
if err := logCfg.Check(); err != nil { if err != nil {
log.Error("Unable to create the log config", "error", err)
return err return err
} }
logger := oplog.NewLogger(logCfg)
logger.Info("Starting fault proof program", "version", VersionWithMeta) logger.Info("Starting fault proof program", "version", VersionWithMeta)
cfg, err := config.NewConfigFromCLI(ctx) cfg, err := config.NewConfigFromCLI(ctx)
...@@ -71,6 +74,17 @@ func run(args []string, action ConfigAction) error { ...@@ -71,6 +74,17 @@ func run(args []string, action ConfigAction) error {
return app.Run(args) return app.Run(args)
} }
func FaultProofProgramMain(log log.Logger, cfg config.Config) error { func setupLogging(ctx *cli.Context) (log.Logger, error) {
logCfg := oplog.ReadCLIConfig(ctx)
if err := logCfg.Check(); err != nil {
return nil, fmt.Errorf("log config error: %w", err)
}
logger := oplog.NewLogger(logCfg)
return logger, nil
}
// FaultProofProgram is the programmatic entry-point for the fault proof program
func FaultProofProgram(log log.Logger, cfg *config.Config) error {
cfg.Rollup.LogDescription(log, chaincfg.L2ChainIDToNetworkName)
return nil return nil
} }
package main package main
import ( import (
"encoding/json"
"os"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-program/config" "github.com/ethereum-optimism/optimism/op-program/config"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -10,14 +13,13 @@ import ( ...@@ -10,14 +13,13 @@ import (
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
t.Run("RejectInvalid", func(t *testing.T) { t.Run("RejectInvalid", func(t *testing.T) {
_, _, err := runWithArgs("--log.level=foo") verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs("--log.level=foo"))
require.ErrorContains(t, err, "unknown level: foo")
}) })
for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} { for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} {
lvl := lvl lvl := lvl
t.Run("AcceptValid_"+lvl, func(t *testing.T) { t.Run("AcceptValid_"+lvl, func(t *testing.T) {
logger, _, err := runWithArgs("--log.level", lvl) logger, _, err := runWithArgs(addRequiredArgs("--log.level", lvl))
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, logger) require.NotNil(t, logger)
}) })
...@@ -25,25 +27,61 @@ func TestLogLevel(t *testing.T) { ...@@ -25,25 +27,61 @@ func TestLogLevel(t *testing.T) {
} }
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t) cfg := configForArgs(t, addRequiredArgs())
require.Equal(t, config.DefaultConfig(), cfg) require.Equal(t, config.NewConfig(&chaincfg.Goerli), cfg)
} }
func TestDefaultConfigIsValid(t *testing.T) { func TestNetwork(t *testing.T) {
err := config.DefaultConfig().Check() t.Run("Unknown", func(t *testing.T) {
require.NoError(t, err) verifyArgsInvalid(t, "invalid network bar", replaceRequiredArg("--network", "bar"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag rollup.config or network is required", addRequiredArgsExcept("--network"))
})
t.Run("DisallowNetworkAndRollupConfig", func(t *testing.T) {
verifyArgsInvalid(t, "cannot specify both rollup.config and network", addRequiredArgs("--rollup.config=foo"))
})
t.Run("RollupConfig", func(t *testing.T) {
dir := t.TempDir()
configJson, err := json.Marshal(chaincfg.Goerli)
require.NoError(t, err)
configFile := dir + "/config.json"
err = os.WriteFile(configFile, configJson, os.ModePerm)
require.NoError(t, err)
cfg := configForArgs(t, addRequiredArgsExcept("--network", "--rollup.config", configFile))
require.Equal(t, chaincfg.Goerli, *cfg.Rollup)
})
for name, cfg := range chaincfg.NetworksByName {
name := name
expected := cfg
t.Run("Network_"+name, func(t *testing.T) {
cfg := configForArgs(t, replaceRequiredArg("--network", name))
require.Equal(t, expected, *cfg.Rollup)
})
}
} }
func configForArgs(t *testing.T, cliArgs ...string) config.Config { func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, cfg, err := runWithArgs(cliArgs...) _, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains)
}
func configForArgs(t *testing.T, cliArgs []string) *config.Config {
_, cfg, err := runWithArgs(cliArgs)
require.NoError(t, err) require.NoError(t, err)
return cfg return cfg
} }
func runWithArgs(cliArgs ...string) (log.Logger, config.Config, error) { func runWithArgs(cliArgs []string) (log.Logger, *config.Config, error) {
var cfg config.Config var cfg *config.Config
var logger log.Logger var logger log.Logger
err := run(args(cliArgs...), func(log log.Logger, config config.Config) error { fullArgs := append([]string{"op-program"}, cliArgs...)
err := run(fullArgs, func(log log.Logger, config *config.Config) error {
logger = log logger = log
cfg = config cfg = config
return nil return nil
...@@ -51,6 +89,37 @@ func runWithArgs(cliArgs ...string) (log.Logger, config.Config, error) { ...@@ -51,6 +89,37 @@ func runWithArgs(cliArgs ...string) (log.Logger, config.Config, error) {
return logger, cfg, err return logger, cfg, err
} }
func args(args ...string) []string { func addRequiredArgs(args ...string) []string {
return append([]string{"op-program"}, args...) 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 replaceRequiredArg(name string, value string) []string {
req := requiredArgs()
req[name] = value
return toArgList(req)
}
// requiredArgs returns map of argument names to values which are the minimal arguments required
// to create a valid Config
func requiredArgs() map[string]string {
return map[string]string{
"--network": "goerli",
}
}
func toArgList(req map[string]string) []string {
var combined []string
for name, value := range req {
combined = append(combined, name)
combined = append(combined, value)
}
return combined
} }
package config package config
import "github.com/urfave/cli" import (
"errors"
opnode "github.com/ethereum-optimism/optimism/op-node"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/flags"
"github.com/urfave/cli"
)
var (
ErrMissingRollupConfig = errors.New("missing rollup config")
)
type Config struct { type Config struct {
Rollup *rollup.Config
} }
func (c Config) Check() error { func (c *Config) Check() error {
if c.Rollup == nil {
return ErrMissingRollupConfig
}
if err := c.Rollup.Check(); err != nil {
return err
}
return nil return nil
} }
func DefaultConfig() Config { // NewConfig creates a Config with all optional values set to the CLI default value
return Config{} func NewConfig(rollupCfg *rollup.Config) *Config {
return &Config{
Rollup: rollupCfg,
}
} }
func NewConfigFromCLI(ctx *cli.Context) (Config, error) { func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
return Config{}, nil if err := flags.CheckRequired(ctx); err != nil {
return nil, err
}
rollupCfg, err := opnode.NewRollupConfig(ctx)
if err != nil {
return nil, err
}
return &Config{
Rollup: rollupCfg,
}, nil
} }
package config
import (
"testing"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/stretchr/testify/require"
)
func TestDefaultConfigIsValid(t *testing.T) {
err := NewConfig(&chaincfg.Goerli).Check()
require.NoError(t, err)
}
func TestRollupConfig(t *testing.T) {
t.Run("Required", func(t *testing.T) {
err := NewConfig(nil).Check()
require.ErrorIs(t, err, ErrMissingRollupConfig)
})
t.Run("Valid", func(t *testing.T) {
err := NewConfig(&rollup.Config{}).Check()
require.ErrorIs(t, err, rollup.ErrBlockTimeZero)
})
}
package flags package flags
import ( import (
"fmt"
"strings"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
service "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
const envVarPrefix = "OP_PROGRAM" const envVarPrefix = "OP_PROGRAM"
var (
RollupConfig = cli.StringFlag{
Name: "rollup.config",
Usage: "Rollup chain parameters",
EnvVar: service.PrefixEnvVar(envVarPrefix, "ROLLUP_CONFIG"),
}
Network = cli.StringFlag{
Name: "network",
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVar: service.PrefixEnvVar(envVarPrefix, "NETWORK"),
}
)
// Flags contains the list of configuration options available to the binary. // Flags contains the list of configuration options available to the binary.
var Flags []cli.Flag var Flags []cli.Flag
var programFlags = []cli.Flag{
RollupConfig,
Network,
}
func init() { func init() {
Flags = oplog.CLIFlags(envVarPrefix) Flags = append(Flags, oplog.CLIFlags(envVarPrefix)...)
Flags = append(Flags, programFlags...)
}
func CheckRequired(ctx *cli.Context) error {
rollupConfig := ctx.GlobalString(RollupConfig.Name)
network := ctx.GlobalString(Network.Name)
if rollupConfig == "" && network == "" {
return fmt.Errorf("flag %s or %s is required", RollupConfig.Name, Network.Name)
}
if rollupConfig != "" && network != "" {
return fmt.Errorf("cannot specify both %s and %s", RollupConfig.Name, Network.Name)
}
return nil
} }
package version package version
var ( var (
Version = "v0.1.0" Version = "v0.10.14"
Meta = "dev" Meta = "dev"
) )
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