From 3dfc5701b2825273f9c6f33678feafbf3863bafb Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 18 Apr 2023 08:51:23 +1000 Subject: [PATCH] op-program: Support running in offline mode. --- op-program/host/cmd/main.go | 90 +++++++++++++++++---------- op-program/host/cmd/main_test.go | 14 ++--- op-program/host/config/config.go | 6 ++ op-program/host/config/config_test.go | 16 ++++- op-program/host/flags/flags.go | 6 ++ 5 files changed, 88 insertions(+), 44 deletions(-) diff --git a/op-program/host/cmd/main.go b/op-program/host/cmd/main.go index 32268ad8f259..dd4da838d08b 100644 --- a/op-program/host/cmd/main.go +++ b/op-program/host/cmd/main.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum-optimism/optimism/op-program/host/version" "github.com/ethereum-optimism/optimism/op-program/preimage" oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/urfave/cli" ) @@ -106,45 +107,66 @@ type L2Source struct { // FaultProofProgram is the programmatic entry-point for the fault proof program func FaultProofProgram(logger log.Logger, cfg *config.Config) error { - cfg.Rollup.LogDescription(logger, chaincfg.L2ChainIDToNetworkName) - if !cfg.FetchingEnabled() { - return errors.New("offline mode not supported") + if err := cfg.Check(); err != nil { + return fmt.Errorf("invalid config: %w", err) } + cfg.Rollup.LogDescription(logger, chaincfg.L2ChainIDToNetworkName) ctx := context.Background() - kv := kvstore.NewMemKV() - - logger.Info("Connecting to L1 node", "l1", cfg.L1URL) - l1RPC, err := client.NewRPC(ctx, logger, cfg.L1URL) - if err != nil { - return fmt.Errorf("failed to setup L1 RPC: %w", err) + var kv kvstore.KV + if cfg.DataDir == "" { + logger.Info("Using in-memory storage") + kv = kvstore.NewMemKV() + } else { + logger.Info("Creating disk storage", "datadir", cfg.DataDir) + if err := os.MkdirAll(cfg.DataDir, 0755); err != nil { + return fmt.Errorf("creating datadir: %w", err) + } + kv = kvstore.NewDiskKV(cfg.DataDir) } - logger.Info("Connecting to L2 node", "l2", cfg.L2URL) - l2RPC, err := client.NewRPC(ctx, logger, cfg.L2URL) - if err != nil { - return fmt.Errorf("failed to setup L2 RPC: %w", err) - } + var preimageOracle preimage.OracleFn + var hinter preimage.HinterFn + if cfg.FetchingEnabled() { + logger.Info("Connecting to L1 node", "l1", cfg.L1URL) + l1RPC, err := client.NewRPC(ctx, logger, cfg.L1URL) + if err != nil { + return fmt.Errorf("failed to setup L1 RPC: %w", err) + } - l1ClCfg := sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind) - l2ClCfg := sources.L2ClientDefaultConfig(cfg.Rollup, true) - l1Cl, err := sources.NewL1Client(l1RPC, logger, nil, l1ClCfg) - if err != nil { - return fmt.Errorf("failed to create L1 client: %w", err) - } - l2Cl, err := sources.NewL2Client(l2RPC, logger, nil, l2ClCfg) - if err != nil { - return fmt.Errorf("failed to create L2 client: %w", err) - } - l2DebugCl := &L2Source{L2Client: l2Cl, DebugClient: sources.NewDebugClient(l2RPC.CallContext)} + logger.Info("Connecting to L2 node", "l2", cfg.L2URL) + l2RPC, err := client.NewRPC(ctx, logger, cfg.L2URL) + if err != nil { + return fmt.Errorf("failed to setup L2 RPC: %w", err) + } - logger.Info("Setting up pre-fetcher") - prefetch := prefetcher.NewPrefetcher(l1Cl, l2DebugCl, kv) - preimageOracle := asOracleFn(ctx, prefetch) - hinter := asHinter(prefetch) + l1ClCfg := sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind) + l2ClCfg := sources.L2ClientDefaultConfig(cfg.Rollup, true) + l1Cl, err := sources.NewL1Client(l1RPC, logger, nil, l1ClCfg) + if err != nil { + return fmt.Errorf("failed to create L1 client: %w", err) + } + l2Cl, err := sources.NewL2Client(l2RPC, logger, nil, l2ClCfg) + if err != nil { + return fmt.Errorf("failed to create L2 client: %w", err) + } + l2DebugCl := &L2Source{L2Client: l2Cl, DebugClient: sources.NewDebugClient(l2RPC.CallContext)} + + logger.Info("Setting up pre-fetcher") + prefetch := prefetcher.NewPrefetcher(l1Cl, l2DebugCl, kv) + preimageOracle = asOracleFn(func(key common.Hash) ([]byte, error) { + return prefetch.GetPreimage(ctx, key) + }) + hinter = asHinter(prefetch.Hint) + } else { + logger.Info("Using offline mode. All required pre-images must be pre-populated.") + preimageOracle = asOracleFn(kv.Get) + hinter = func(v preimage.Hint) { + logger.Debug("ignoring prefetch hint", "hint", v) + } + } l1Source := l1.NewSource(logger, preimageOracle, hinter, cfg.L1Head) - logger.Info("Connecting to L2 node", "l2", cfg.L2URL) l2Source, err := l2.NewEngine(logger, preimageOracle, hinter, cfg) if err != nil { return fmt.Errorf("connect l2 oracle: %w", err) @@ -166,9 +188,9 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error { return nil } -func asOracleFn(ctx context.Context, prefetcher *prefetcher.Prefetcher) preimage.OracleFn { +func asOracleFn(getter func(key common.Hash) ([]byte, error)) preimage.OracleFn { return func(key preimage.Key) []byte { - pre, err := prefetcher.GetPreimage(ctx, key.PreimageKey()) + pre, err := getter(key.PreimageKey()) if err != nil { panic(fmt.Errorf("preimage unavailable for key %v: %w", key, err)) } @@ -176,9 +198,9 @@ func asOracleFn(ctx context.Context, prefetcher *prefetcher.Prefetcher) preimage } } -func asHinter(prefetcher *prefetcher.Prefetcher) preimage.HinterFn { +func asHinter(hint func(hint string) error) preimage.HinterFn { return func(v preimage.Hint) { - err := prefetcher.Hint(v.Hint()) + err := hint(v.Hint()) if err != nil { panic(fmt.Errorf("hint rejected %v: %w", v, err)) } diff --git a/op-program/host/cmd/main_test.go b/op-program/host/cmd/main_test.go index da19354b39e1..d849bfcd54b7 100644 --- a/op-program/host/cmd/main_test.go +++ b/op-program/host/cmd/main_test.go @@ -79,6 +79,12 @@ func TestNetwork(t *testing.T) { } } +func TestDataDir(t *testing.T) { + expected := "/tmp/mainTestDataDir" + cfg := configForArgs(t, addRequiredArgs("--datadir", expected)) + require.Equal(t, expected, cfg.DataDir) +} + func TestL2(t *testing.T) { expected := "https://example.com:8545" cfg := configForArgs(t, addRequiredArgs("--l2", expected)) @@ -170,14 +176,6 @@ func TestL1RPCKind(t *testing.T) { }) } -// Offline support will be added later, but for now it just bails out with an error -func TestOfflineModeNotSupported(t *testing.T) { - logger := log.New() - cfg := config.NewConfig(&chaincfg.Goerli, "genesis.json", common.HexToHash(l1HeadValue), common.HexToHash(l2HeadValue), common.HexToHash(l2ClaimValue)) - err := FaultProofProgram(logger, cfg) - require.ErrorContains(t, err, "offline mode not supported") -} - func TestL2Claim(t *testing.T) { t.Run("Required", func(t *testing.T) { verifyArgsInvalid(t, "flag l2.claim is required", addRequiredArgsExcept("--l2.claim")) diff --git a/op-program/host/config/config.go b/op-program/host/config/config.go index 316cd69da9b7..f7720f101e15 100644 --- a/op-program/host/config/config.go +++ b/op-program/host/config/config.go @@ -18,10 +18,12 @@ var ( ErrInvalidL2Head = errors.New("invalid l2 head") ErrL1AndL2Inconsistent = errors.New("l1 and l2 options must be specified together or both omitted") ErrInvalidL2Claim = errors.New("invalid l2 claim") + ErrDataDirRequired = errors.New("datadir must be specified when in non-fetching mode") ) type Config struct { Rollup *rollup.Config + DataDir string L2URL string L2GenesisPath string L1Head common.Hash @@ -54,6 +56,9 @@ func (c *Config) Check() error { if (c.L1URL != "") != (c.L2URL != "") { return ErrL1AndL2Inconsistent } + if !c.FetchingEnabled() && c.DataDir == "" { + return ErrDataDirRequired + } return nil } @@ -95,6 +100,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { } return &Config{ Rollup: rollupCfg, + DataDir: ctx.GlobalString(flags.DataDir.Name), L2URL: ctx.GlobalString(flags.L2NodeAddr.Name), L2GenesisPath: ctx.GlobalString(flags.L2GenesisPath.Name), L2Head: l2Head, diff --git a/op-program/host/config/config_test.go b/op-program/host/config/config_test.go index 1caa189c9e95..6ffac5c43cca 100644 --- a/op-program/host/config/config_test.go +++ b/op-program/host/config/config_test.go @@ -15,7 +15,8 @@ var validL1Head = common.Hash{0xaa} var validL2Head = common.Hash{0xbb} var validL2Claim = common.Hash{0xcc} -func TestDefaultConfigIsValid(t *testing.T) { +// TestValidConfigIsValid checks that the config provided by validConfig is actually valid +func TestValidConfigIsValid(t *testing.T) { err := validConfig().Check() require.NoError(t, err) } @@ -121,6 +122,17 @@ func TestFetchingEnabled(t *testing.T) { }) } +func TestRequireDataDirInNonFetchingMode(t *testing.T) { + cfg := validConfig() + cfg.DataDir = "" + cfg.L1URL = "" + cfg.L2URL = "" + err := cfg.Check() + require.ErrorIs(t, err, ErrDataDirRequired) +} + func validConfig() *Config { - return NewConfig(validRollupConfig, validL2GenesisPath, validL1Head, validL2Head, validL2Claim) + cfg := NewConfig(validRollupConfig, validL2GenesisPath, validL1Head, validL2Head, validL2Claim) + cfg.DataDir = "/tmp/configTest" + return cfg } diff --git a/op-program/host/flags/flags.go b/op-program/host/flags/flags.go index 057e9fbe1de4..11c2285d25dc 100644 --- a/op-program/host/flags/flags.go +++ b/op-program/host/flags/flags.go @@ -26,6 +26,11 @@ var ( Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")), EnvVar: service.PrefixEnvVar(envVarPrefix, "NETWORK"), } + DataDir = cli.StringFlag{ + Name: "datadir", + Usage: "Directory to use for preimage data storage. Default uses in-memory storage", + EnvVar: service.PrefixEnvVar(envVarPrefix, "DATADIR"), + } L2NodeAddr = cli.StringFlag{ Name: "l2", Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)", @@ -85,6 +90,7 @@ var requiredFlags = []cli.Flag{ var programFlags = []cli.Flag{ RollupConfig, Network, + DataDir, L2NodeAddr, L1NodeAddr, L1TrustRPC,