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 (
"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))
}
......
......@@ -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"))
......
......@@ -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,
......
......@@ -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
}
......@@ -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,
......
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