Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

op-program: Support running in offline mode. #5466

Merged
merged 1 commit into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 56 additions & 34 deletions op-program/host/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
Expand All @@ -166,19 +188,19 @@ 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))
}
return pre
}
}

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))
}
Expand Down
14 changes: 6 additions & 8 deletions op-program/host/cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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"))
Expand Down
6 changes: 6 additions & 0 deletions op-program/host/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
Expand Down
16 changes: 14 additions & 2 deletions op-program/host/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
6 changes: 6 additions & 0 deletions op-program/host/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down Expand Up @@ -85,6 +90,7 @@ var requiredFlags = []cli.Flag{
var programFlags = []cli.Flag{
RollupConfig,
Network,
DataDir,
L2NodeAddr,
L1NodeAddr,
L1TrustRPC,
Expand Down