Commit a01d02c3 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

Merge pull request #5466 from ethereum-optimism/aj/fpp-disk-store

op-program: Support running in offline mode.
parents 1ce4fdf3 3dfc5701
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"github.com/ethereum-optimism/optimism/op-program/host/version" "github.com/ethereum-optimism/optimism/op-program/host/version"
"github.com/ethereum-optimism/optimism/op-program/preimage" "github.com/ethereum-optimism/optimism/op-program/preimage"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
...@@ -106,45 +107,66 @@ type L2Source struct { ...@@ -106,45 +107,66 @@ type L2Source struct {
// FaultProofProgram is the programmatic entry-point for the fault proof program // FaultProofProgram is the programmatic entry-point for the fault proof program
func FaultProofProgram(logger log.Logger, cfg *config.Config) error { func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
cfg.Rollup.LogDescription(logger, chaincfg.L2ChainIDToNetworkName) if err := cfg.Check(); err != nil {
if !cfg.FetchingEnabled() { return fmt.Errorf("invalid config: %w", err)
return errors.New("offline mode not supported")
} }
cfg.Rollup.LogDescription(logger, chaincfg.L2ChainIDToNetworkName)
ctx := context.Background() ctx := context.Background()
kv := kvstore.NewMemKV() var kv kvstore.KV
if cfg.DataDir == "" {
logger.Info("Connecting to L1 node", "l1", cfg.L1URL) logger.Info("Using in-memory storage")
l1RPC, err := client.NewRPC(ctx, logger, cfg.L1URL) kv = kvstore.NewMemKV()
if err != nil { } else {
return fmt.Errorf("failed to setup L1 RPC: %w", err) 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) var preimageOracle preimage.OracleFn
l2RPC, err := client.NewRPC(ctx, logger, cfg.L2URL) var hinter preimage.HinterFn
if err != nil { if cfg.FetchingEnabled() {
return fmt.Errorf("failed to setup L2 RPC: %w", err) 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) logger.Info("Connecting to L2 node", "l2", cfg.L2URL)
l2ClCfg := sources.L2ClientDefaultConfig(cfg.Rollup, true) l2RPC, err := client.NewRPC(ctx, logger, cfg.L2URL)
l1Cl, err := sources.NewL1Client(l1RPC, logger, nil, l1ClCfg) if err != nil {
if err != nil { return fmt.Errorf("failed to setup L2 RPC: %w", err)
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") l1ClCfg := sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind)
prefetch := prefetcher.NewPrefetcher(l1Cl, l2DebugCl, kv) l2ClCfg := sources.L2ClientDefaultConfig(cfg.Rollup, true)
preimageOracle := asOracleFn(ctx, prefetch) l1Cl, err := sources.NewL1Client(l1RPC, logger, nil, l1ClCfg)
hinter := asHinter(prefetch) 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) 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) l2Source, err := l2.NewEngine(logger, preimageOracle, hinter, cfg)
if err != nil { if err != nil {
return fmt.Errorf("connect l2 oracle: %w", err) return fmt.Errorf("connect l2 oracle: %w", err)
...@@ -166,9 +188,9 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error { ...@@ -166,9 +188,9 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
return nil 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 { return func(key preimage.Key) []byte {
pre, err := prefetcher.GetPreimage(ctx, key.PreimageKey()) pre, err := getter(key.PreimageKey())
if err != nil { if err != nil {
panic(fmt.Errorf("preimage unavailable for key %v: %w", key, err)) panic(fmt.Errorf("preimage unavailable for key %v: %w", key, err))
} }
...@@ -176,9 +198,9 @@ func asOracleFn(ctx context.Context, prefetcher *prefetcher.Prefetcher) preimage ...@@ -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) { return func(v preimage.Hint) {
err := prefetcher.Hint(v.Hint()) err := hint(v.Hint())
if err != nil { if err != nil {
panic(fmt.Errorf("hint rejected %v: %w", v, err)) panic(fmt.Errorf("hint rejected %v: %w", v, err))
} }
......
...@@ -79,6 +79,12 @@ func TestNetwork(t *testing.T) { ...@@ -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) { func TestL2(t *testing.T) {
expected := "https://example.com:8545" expected := "https://example.com:8545"
cfg := configForArgs(t, addRequiredArgs("--l2", expected)) cfg := configForArgs(t, addRequiredArgs("--l2", expected))
...@@ -170,14 +176,6 @@ func TestL1RPCKind(t *testing.T) { ...@@ -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) { func TestL2Claim(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.claim is required", addRequiredArgsExcept("--l2.claim")) verifyArgsInvalid(t, "flag l2.claim is required", addRequiredArgsExcept("--l2.claim"))
......
...@@ -18,10 +18,12 @@ var ( ...@@ -18,10 +18,12 @@ var (
ErrInvalidL2Head = errors.New("invalid l2 head") ErrInvalidL2Head = errors.New("invalid l2 head")
ErrL1AndL2Inconsistent = errors.New("l1 and l2 options must be specified together or both omitted") ErrL1AndL2Inconsistent = errors.New("l1 and l2 options must be specified together or both omitted")
ErrInvalidL2Claim = errors.New("invalid l2 claim") ErrInvalidL2Claim = errors.New("invalid l2 claim")
ErrDataDirRequired = errors.New("datadir must be specified when in non-fetching mode")
) )
type Config struct { type Config struct {
Rollup *rollup.Config Rollup *rollup.Config
DataDir string
L2URL string L2URL string
L2GenesisPath string L2GenesisPath string
L1Head common.Hash L1Head common.Hash
...@@ -54,6 +56,9 @@ func (c *Config) Check() error { ...@@ -54,6 +56,9 @@ func (c *Config) Check() error {
if (c.L1URL != "") != (c.L2URL != "") { if (c.L1URL != "") != (c.L2URL != "") {
return ErrL1AndL2Inconsistent return ErrL1AndL2Inconsistent
} }
if !c.FetchingEnabled() && c.DataDir == "" {
return ErrDataDirRequired
}
return nil return nil
} }
...@@ -95,6 +100,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { ...@@ -95,6 +100,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
} }
return &Config{ return &Config{
Rollup: rollupCfg, Rollup: rollupCfg,
DataDir: ctx.GlobalString(flags.DataDir.Name),
L2URL: ctx.GlobalString(flags.L2NodeAddr.Name), L2URL: ctx.GlobalString(flags.L2NodeAddr.Name),
L2GenesisPath: ctx.GlobalString(flags.L2GenesisPath.Name), L2GenesisPath: ctx.GlobalString(flags.L2GenesisPath.Name),
L2Head: l2Head, L2Head: l2Head,
......
...@@ -15,7 +15,8 @@ var validL1Head = common.Hash{0xaa} ...@@ -15,7 +15,8 @@ var validL1Head = common.Hash{0xaa}
var validL2Head = common.Hash{0xbb} var validL2Head = common.Hash{0xbb}
var validL2Claim = common.Hash{0xcc} 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() err := validConfig().Check()
require.NoError(t, err) require.NoError(t, err)
} }
...@@ -121,6 +122,17 @@ func TestFetchingEnabled(t *testing.T) { ...@@ -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 { func validConfig() *Config {
return NewConfig(validRollupConfig, validL2GenesisPath, validL1Head, validL2Head, validL2Claim) cfg := NewConfig(validRollupConfig, validL2GenesisPath, validL1Head, validL2Head, validL2Claim)
cfg.DataDir = "/tmp/configTest"
return cfg
} }
...@@ -26,6 +26,11 @@ var ( ...@@ -26,6 +26,11 @@ var (
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")), Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVar: service.PrefixEnvVar(envVarPrefix, "NETWORK"), 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{ L2NodeAddr = cli.StringFlag{
Name: "l2", Name: "l2",
Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)", Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)",
...@@ -85,6 +90,7 @@ var requiredFlags = []cli.Flag{ ...@@ -85,6 +90,7 @@ var requiredFlags = []cli.Flag{
var programFlags = []cli.Flag{ var programFlags = []cli.Flag{
RollupConfig, RollupConfig,
Network, Network,
DataDir,
L2NodeAddr, L2NodeAddr,
L1NodeAddr, L1NodeAddr,
L1TrustRPC, L1TrustRPC,
......
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