Commit b1c9be0b authored by Adrian Sutton's avatar Adrian Sutton

op-program: Wire in L1 oracle

parent bc37c15e
...@@ -6,12 +6,14 @@ import ( ...@@ -6,12 +6,14 @@ import (
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
) )
var ( var (
ErrNotFound = errors.New("not found") ErrNotFound = ethereum.NotFound
ErrUnknownLabel = errors.New("unknown label") ErrUnknownLabel = errors.New("unknown label")
) )
...@@ -20,8 +22,9 @@ type OracleL1Client struct { ...@@ -20,8 +22,9 @@ type OracleL1Client struct {
head eth.L1BlockRef head eth.L1BlockRef
} }
func NewOracleL1Client(oracle Oracle, l1Head common.Hash) *OracleL1Client { func NewOracleL1Client(logger log.Logger, oracle Oracle, l1Head common.Hash) *OracleL1Client {
head := eth.InfoToL1BlockRef(oracle.HeaderByHash(l1Head)) head := eth.InfoToL1BlockRef(oracle.HeaderByHash(l1Head))
logger.Info("L1 head loaded", "hash", head.Hash, "number", head.Number)
return &OracleL1Client{ return &OracleL1Client{
oracle: oracle, oracle: oracle,
head: head, head: head,
...@@ -29,26 +32,22 @@ func NewOracleL1Client(oracle Oracle, l1Head common.Hash) *OracleL1Client { ...@@ -29,26 +32,22 @@ func NewOracleL1Client(oracle Oracle, l1Head common.Hash) *OracleL1Client {
} }
func (o OracleL1Client) L1BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L1BlockRef, error) { func (o OracleL1Client) L1BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L1BlockRef, error) {
switch label { if label != eth.Unsafe && label != eth.Safe && label != eth.Finalized {
case eth.Unsafe: return eth.L1BlockRef{}, fmt.Errorf("%w: %s", ErrUnknownLabel, label)
return o.head, nil
case eth.Safe:
return o.head, nil
case eth.Finalized:
return o.head, nil
} }
return eth.L1BlockRef{}, fmt.Errorf("%w: %s", ErrUnknownLabel, label) // The L1 head is pre-agreed and unchanging so it can be used for all of unsafe, safe and finalized
return o.head, nil
} }
func (o OracleL1Client) L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) { func (o OracleL1Client) L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) {
if number > o.head.Number { if number > o.head.Number {
return eth.L1BlockRef{}, fmt.Errorf("%w: block number %d", ErrNotFound, number) return eth.L1BlockRef{}, fmt.Errorf("%w: block number %d", ErrNotFound, number)
} }
head := o.head block := o.head
for head.Number > number { for block.Number > number {
head = eth.InfoToL1BlockRef(o.oracle.HeaderByHash(head.ParentHash)) block = eth.InfoToL1BlockRef(o.oracle.HeaderByHash(block.ParentHash))
} }
return head, nil return block, nil
} }
func (o OracleL1Client) L1BlockRefByHash(ctx context.Context, hash common.Hash) (eth.L1BlockRef, error) { func (o OracleL1Client) L1BlockRefByHash(ctx context.Context, hash common.Hash) (eth.L1BlockRef, error) {
......
...@@ -8,9 +8,12 @@ import ( ...@@ -8,9 +8,12 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils" "github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -110,7 +113,8 @@ func TestL1BlockRefByNumber(t *testing.T) { ...@@ -110,7 +113,8 @@ func TestL1BlockRefByNumber(t *testing.T) {
t.Run("AfterHead", func(t *testing.T) { t.Run("AfterHead", func(t *testing.T) {
client, _ := newClient(t) client, _ := newClient(t)
ref, err := client.L1BlockRefByNumber(context.Background(), head.NumberU64()+1) ref, err := client.L1BlockRefByNumber(context.Background(), head.NumberU64()+1)
require.ErrorIs(t, err, ErrNotFound) // Must be ethereum.NotFound error so the derivation pipeline knows it has gone past the chain head
require.ErrorIs(t, err, ethereum.NotFound)
require.Equal(t, eth.L1BlockRef{}, ref) require.Equal(t, eth.L1BlockRef{}, ref)
}) })
t.Run("ParentOfHead", func(t *testing.T) { t.Run("ParentOfHead", func(t *testing.T) {
...@@ -148,7 +152,7 @@ func newClient(t *testing.T) (*OracleL1Client, *stubOracle) { ...@@ -148,7 +152,7 @@ func newClient(t *testing.T) (*OracleL1Client, *stubOracle) {
rcpts: make(map[common.Hash]types.Receipts), rcpts: make(map[common.Hash]types.Receipts),
} }
stub.blocks[head.Hash()] = head stub.blocks[head.Hash()] = head
client := NewOracleL1Client(stub, head.Hash()) client := NewOracleL1Client(testlog.Logger(t, log.LvlDebug), stub, head.Hash())
return client, stub return client, stub
} }
......
...@@ -39,6 +39,7 @@ func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.C ...@@ -39,6 +39,7 @@ func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.C
if err != nil { if err != nil {
return nil, fmt.Errorf("loading l2 head: %w", err) return nil, fmt.Errorf("loading l2 head: %w", err)
} }
logger.Info("Loaded L2 head", "hash", head.Hash(), "number", head.Number())
return &OracleBackedL2Chain{ return &OracleBackedL2Chain{
log: logger, log: logger,
oracle: oracle, oracle: oracle,
......
...@@ -13,7 +13,9 @@ import ( ...@@ -13,7 +13,9 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var l2HeadValue = "0x6303578b1fa9480389c51bbcef6fe045bb877da39740819e9eb5f36f94949bd0" // Use HexToHash(...).Hex() to ensure the strings are the correct length for a hash
var l1HeadValue = common.HexToHash("0x111111").Hex()
var l2HeadValue = common.HexToHash("0x222222").Hex()
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
t.Run("RejectInvalid", func(t *testing.T) { t.Run("RejectInvalid", func(t *testing.T) {
...@@ -32,7 +34,8 @@ func TestLogLevel(t *testing.T) { ...@@ -32,7 +34,8 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs()) cfg := configForArgs(t, addRequiredArgs())
require.Equal(t, config.NewConfig(&chaincfg.Goerli, "genesis.json", common.HexToHash(l2HeadValue)), cfg) defaultCfg := config.NewConfig(&chaincfg.Goerli, "genesis.json", common.HexToHash(l1HeadValue), common.HexToHash(l2HeadValue))
require.Equal(t, defaultCfg, cfg)
} }
func TestNetwork(t *testing.T) { func TestNetwork(t *testing.T) {
...@@ -102,6 +105,21 @@ func TestL2Head(t *testing.T) { ...@@ -102,6 +105,21 @@ func TestL2Head(t *testing.T) {
}) })
} }
func TestL1Head(t *testing.T) {
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l1.head is required", addRequiredArgsExcept("--l1.head"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, replaceRequiredArg("--l1.head", l1HeadValue))
require.Equal(t, common.HexToHash(l1HeadValue), cfg.L1Head)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, config.ErrInvalidL1Head.Error(), replaceRequiredArg("--l1.head", "something"))
})
}
func TestL1(t *testing.T) { func TestL1(t *testing.T) {
expected := "https://example.com:8545" expected := "https://example.com:8545"
cfg := configForArgs(t, addRequiredArgs("--l1", expected)) cfg := configForArgs(t, addRequiredArgs("--l1", expected))
...@@ -149,7 +167,8 @@ func TestL1RPCKind(t *testing.T) { ...@@ -149,7 +167,8 @@ func TestL1RPCKind(t *testing.T) {
// Offline support will be added later, but for now it just bails out with an error // Offline support will be added later, but for now it just bails out with an error
func TestOfflineModeNotSupported(t *testing.T) { func TestOfflineModeNotSupported(t *testing.T) {
logger := log.New() logger := log.New()
err := FaultProofProgram(logger, config.NewConfig(&chaincfg.Goerli, "genesis.json", common.HexToHash(l2HeadValue))) cfg := config.NewConfig(&chaincfg.Goerli, "genesis.json", common.HexToHash(l1HeadValue), common.HexToHash(l2HeadValue))
err := FaultProofProgram(logger, cfg)
require.ErrorContains(t, err, "offline mode not supported") require.ErrorContains(t, err, "offline mode not supported")
} }
...@@ -199,8 +218,9 @@ func replaceRequiredArg(name string, value string) []string { ...@@ -199,8 +218,9 @@ func replaceRequiredArg(name string, value string) []string {
func requiredArgs() map[string]string { func requiredArgs() map[string]string {
return map[string]string{ return map[string]string{
"--network": "goerli", "--network": "goerli",
"--l2.genesis": "genesis.json", "--l1.head": l1HeadValue,
"--l2.head": l2HeadValue, "--l2.head": l2HeadValue,
"--l2.genesis": "genesis.json",
} }
} }
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
var ( var (
ErrMissingRollupConfig = errors.New("missing rollup config") ErrMissingRollupConfig = errors.New("missing rollup config")
ErrMissingL2Genesis = errors.New("missing l2 genesis") ErrMissingL2Genesis = errors.New("missing l2 genesis")
ErrInvalidL1Head = errors.New("invalid l1 head")
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")
) )
...@@ -22,6 +23,7 @@ type Config struct { ...@@ -22,6 +23,7 @@ type Config struct {
Rollup *rollup.Config Rollup *rollup.Config
L2URL string L2URL string
L2GenesisPath string L2GenesisPath string
L1Head common.Hash
L2Head common.Hash L2Head common.Hash
L1URL string L1URL string
L1TrustRPC bool L1TrustRPC bool
...@@ -35,12 +37,15 @@ func (c *Config) Check() error { ...@@ -35,12 +37,15 @@ func (c *Config) Check() error {
if err := c.Rollup.Check(); err != nil { if err := c.Rollup.Check(); err != nil {
return err return err
} }
if c.L2GenesisPath == "" { if c.L1Head == (common.Hash{}) {
return ErrMissingL2Genesis return ErrInvalidL1Head
} }
if c.L2Head == (common.Hash{}) { if c.L2Head == (common.Hash{}) {
return ErrInvalidL2Head return ErrInvalidL2Head
} }
if c.L2GenesisPath == "" {
return ErrMissingL2Genesis
}
if (c.L1URL != "") != (c.L2URL != "") { if (c.L1URL != "") != (c.L2URL != "") {
return ErrL1AndL2Inconsistent return ErrL1AndL2Inconsistent
} }
...@@ -52,10 +57,11 @@ func (c *Config) FetchingEnabled() bool { ...@@ -52,10 +57,11 @@ func (c *Config) FetchingEnabled() bool {
} }
// NewConfig creates a Config with all optional values set to the CLI default value // NewConfig creates a Config with all optional values set to the CLI default value
func NewConfig(rollupCfg *rollup.Config, l2GenesisPath string, l2Head common.Hash) *Config { func NewConfig(rollupCfg *rollup.Config, l2GenesisPath string, l1Head common.Hash, l2Head common.Hash) *Config {
return &Config{ return &Config{
Rollup: rollupCfg, Rollup: rollupCfg,
L2GenesisPath: l2GenesisPath, L2GenesisPath: l2GenesisPath,
L1Head: l1Head,
L2Head: l2Head, L2Head: l2Head,
L1RPCKind: sources.RPCKindBasic, L1RPCKind: sources.RPCKindBasic,
} }
...@@ -73,11 +79,16 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { ...@@ -73,11 +79,16 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
if l2Head == (common.Hash{}) { if l2Head == (common.Hash{}) {
return nil, ErrInvalidL2Head return nil, ErrInvalidL2Head
} }
l1Head := common.HexToHash(ctx.GlobalString(flags.L1Head.Name))
if l1Head == (common.Hash{}) {
return nil, ErrInvalidL1Head
}
return &Config{ return &Config{
Rollup: rollupCfg, Rollup: rollupCfg,
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,
L1Head: l1Head,
L1URL: ctx.GlobalString(flags.L1NodeAddr.Name), L1URL: ctx.GlobalString(flags.L1NodeAddr.Name),
L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name), L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name),
L1RPCKind: sources.RPCProviderKind(ctx.GlobalString(flags.L1RPCProviderKind.Name)), L1RPCKind: sources.RPCProviderKind(ctx.GlobalString(flags.L1RPCProviderKind.Name)),
......
...@@ -11,66 +11,58 @@ import ( ...@@ -11,66 +11,58 @@ import (
var validRollupConfig = &chaincfg.Goerli var validRollupConfig = &chaincfg.Goerli
var validL2GenesisPath = "genesis.json" var validL2GenesisPath = "genesis.json"
var validL1Head = common.HexToHash("0x112233889988FF")
var validL2Head = common.HexToHash("0x6303578b1fa9480389c51bbcef6fe045bb877da39740819e9eb5f36f94949bd0") var validL2Head = common.HexToHash("0x6303578b1fa9480389c51bbcef6fe045bb877da39740819e9eb5f36f94949bd0")
func TestDefaultConfigIsValid(t *testing.T) { func TestDefaultConfigIsValid(t *testing.T) {
err := NewConfig(validRollupConfig, validL2GenesisPath, validL2Head).Check() err := NewConfig(validRollupConfig, validL2GenesisPath, validL1Head, validL2Head).Check()
require.NoError(t, err) require.NoError(t, err)
} }
func TestRollupConfig(t *testing.T) { func TestRollupConfig(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
err := NewConfig(nil, validL2GenesisPath, validL2Head).Check() err := NewConfig(nil, validL2GenesisPath, validL1Head, validL2Head).Check()
require.ErrorIs(t, err, ErrMissingRollupConfig) require.ErrorIs(t, err, ErrMissingRollupConfig)
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
err := NewConfig(&rollup.Config{}, validL2GenesisPath, validL2Head).Check() err := NewConfig(&rollup.Config{}, validL2GenesisPath, validL1Head, validL2Head).Check()
require.ErrorIs(t, err, rollup.ErrBlockTimeZero) require.ErrorIs(t, err, rollup.ErrBlockTimeZero)
}) })
} }
func TestL2Genesis(t *testing.T) { func TestL1HeadRequired(t *testing.T) {
t.Run("Required", func(t *testing.T) { err := NewConfig(validRollupConfig, validL2GenesisPath, common.Hash{}, validL2Head).Check()
err := NewConfig(validRollupConfig, "", validL2Head).Check() require.ErrorIs(t, err, ErrInvalidL1Head)
require.ErrorIs(t, err, ErrMissingL2Genesis)
})
t.Run("Valid", func(t *testing.T) {
err := NewConfig(validRollupConfig, validL2GenesisPath, validL2Head).Check()
require.NoError(t, err)
})
} }
func TestL2Head(t *testing.T) { func TestL2HeadRequired(t *testing.T) {
t.Run("Required", func(t *testing.T) { err := NewConfig(validRollupConfig, validL2GenesisPath, validL1Head, common.Hash{}).Check()
err := NewConfig(validRollupConfig, validL2GenesisPath, common.Hash{}).Check() require.ErrorIs(t, err, ErrInvalidL2Head)
require.ErrorIs(t, err, ErrInvalidL2Head) }
})
t.Run("Valid", func(t *testing.T) { func TestL2GenesisRequired(t *testing.T) {
err := NewConfig(validRollupConfig, validL2GenesisPath, validL2Head).Check() err := NewConfig(validRollupConfig, "", validL1Head, validL2Head).Check()
require.NoError(t, err) require.ErrorIs(t, err, ErrMissingL2Genesis)
})
} }
func TestFetchingArgConsistency(t *testing.T) { func TestFetchingArgConsistency(t *testing.T) {
t.Run("RequireL2WhenL1Set", func(t *testing.T) { t.Run("RequireL2WhenL1Set", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L1URL = "https://example.com:1234" cfg.L1URL = "https://example.com:1234"
require.ErrorIs(t, cfg.Check(), ErrL1AndL2Inconsistent) require.ErrorIs(t, cfg.Check(), ErrL1AndL2Inconsistent)
}) })
t.Run("RequireL1WhenL2Set", func(t *testing.T) { t.Run("RequireL1WhenL2Set", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L2URL = "https://example.com:1234" cfg.L2URL = "https://example.com:1234"
require.ErrorIs(t, cfg.Check(), ErrL1AndL2Inconsistent) require.ErrorIs(t, cfg.Check(), ErrL1AndL2Inconsistent)
}) })
t.Run("AllowNeitherSet", func(t *testing.T) { t.Run("AllowNeitherSet", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
require.NoError(t, cfg.Check()) require.NoError(t, cfg.Check())
}) })
t.Run("AllowBothSet", func(t *testing.T) { t.Run("AllowBothSet", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L1URL = "https://example.com:1234" cfg.L1URL = "https://example.com:1234"
cfg.L2URL = "https://example.com:4678" cfg.L2URL = "https://example.com:4678"
require.NoError(t, cfg.Check()) require.NoError(t, cfg.Check())
...@@ -79,30 +71,30 @@ func TestFetchingArgConsistency(t *testing.T) { ...@@ -79,30 +71,30 @@ func TestFetchingArgConsistency(t *testing.T) {
func TestFetchingEnabled(t *testing.T) { func TestFetchingEnabled(t *testing.T) {
t.Run("FetchingNotEnabledWhenNoFetcherUrlsSpecified", func(t *testing.T) { t.Run("FetchingNotEnabledWhenNoFetcherUrlsSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
require.False(t, cfg.FetchingEnabled(), "Should not enable fetching when node URL not supplied") require.False(t, cfg.FetchingEnabled(), "Should not enable fetching when node URL not supplied")
}) })
t.Run("FetchingEnabledWhenFetcherUrlsSpecified", func(t *testing.T) { t.Run("FetchingEnabledWhenFetcherUrlsSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L2URL = "https://example.com:1234" cfg.L2URL = "https://example.com:1234"
require.False(t, cfg.FetchingEnabled(), "Should not enable fetching when node URL not supplied") require.False(t, cfg.FetchingEnabled(), "Should not enable fetching when node URL not supplied")
}) })
t.Run("FetchingNotEnabledWhenNoL1UrlSpecified", func(t *testing.T) { t.Run("FetchingNotEnabledWhenNoL1UrlSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L2URL = "https://example.com:1234" cfg.L2URL = "https://example.com:1234"
require.False(t, cfg.FetchingEnabled(), "Should not enable L1 fetching when L1 node URL not supplied") require.False(t, cfg.FetchingEnabled(), "Should not enable L1 fetching when L1 node URL not supplied")
}) })
t.Run("FetchingNotEnabledWhenNoL2UrlSpecified", func(t *testing.T) { t.Run("FetchingNotEnabledWhenNoL2UrlSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L1URL = "https://example.com:1234" cfg.L1URL = "https://example.com:1234"
require.False(t, cfg.FetchingEnabled(), "Should not enable L2 fetching when L2 node URL not supplied") require.False(t, cfg.FetchingEnabled(), "Should not enable L2 fetching when L2 node URL not supplied")
}) })
t.Run("FetchingEnabledWhenBothFetcherUrlsSpecified", func(t *testing.T) { t.Run("FetchingEnabledWhenBothFetcherUrlsSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L1URL = "https://example.com:1234" cfg.L1URL = "https://example.com:1234"
cfg.L2URL = "https://example.com:5678" cfg.L2URL = "https://example.com:5678"
require.True(t, cfg.FetchingEnabled(), "Should enable fetching when node URL supplied") require.True(t, cfg.FetchingEnabled(), "Should enable fetching when node URL supplied")
......
...@@ -31,16 +31,21 @@ var ( ...@@ -31,16 +31,21 @@ var (
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)",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_RPC"), EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_RPC"),
} }
L2GenesisPath = cli.StringFlag{ L1Head = cli.StringFlag{
Name: "l2.genesis", Name: "l1.head",
Usage: "Path to the op-geth genesis file", Usage: "Hash of the L1 head block. Derivation stops after this block is processed.",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_GENESIS"), EnvVar: service.PrefixEnvVar(envVarPrefix, "L1_HEAD"),
} }
L2Head = cli.StringFlag{ L2Head = cli.StringFlag{
Name: "l2.head", Name: "l2.head",
Usage: "Hash of the agreed L2 block to start derivation from", Usage: "Hash of the agreed L2 block to start derivation from",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_HEAD"), EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_HEAD"),
} }
L2GenesisPath = cli.StringFlag{
Name: "l2.genesis",
Usage: "Path to the op-geth genesis file",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_GENESIS"),
}
L1NodeAddr = cli.StringFlag{ L1NodeAddr = cli.StringFlag{
Name: "l1", Name: "l1",
Usage: "Address of L1 JSON-RPC endpoint to use (eth namespace required)", Usage: "Address of L1 JSON-RPC endpoint to use (eth namespace required)",
...@@ -70,8 +75,9 @@ var programFlags = []cli.Flag{ ...@@ -70,8 +75,9 @@ var programFlags = []cli.Flag{
RollupConfig, RollupConfig,
Network, Network,
L2NodeAddr, L2NodeAddr,
L2GenesisPath, L1Head,
L2Head, L2Head,
L2GenesisPath,
L1NodeAddr, L1NodeAddr,
L1TrustRPC, L1TrustRPC,
L1RPCProviderKind, L1RPCProviderKind,
...@@ -97,5 +103,8 @@ func CheckRequired(ctx *cli.Context) error { ...@@ -97,5 +103,8 @@ func CheckRequired(ctx *cli.Context) error {
if ctx.GlobalString(L2Head.Name) == "" { if ctx.GlobalString(L2Head.Name) == "" {
return fmt.Errorf("flag %s is required", L2Head.Name) return fmt.Errorf("flag %s is required", L2Head.Name)
} }
if ctx.GlobalString(L1Head.Name) == "" {
return fmt.Errorf("flag %s is required", L1Head.Name)
}
return nil return nil
} }
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
) )
type Source interface { type Source interface {
...@@ -17,17 +18,20 @@ type Source interface { ...@@ -17,17 +18,20 @@ type Source interface {
type FetchingL1Oracle struct { type FetchingL1Oracle struct {
ctx context.Context ctx context.Context
logger log.Logger
source Source source Source
} }
func NewFetchingL1Oracle(ctx context.Context, source Source) *FetchingL1Oracle { func NewFetchingL1Oracle(ctx context.Context, logger log.Logger, source Source) *FetchingL1Oracle {
return &FetchingL1Oracle{ return &FetchingL1Oracle{
ctx: ctx, ctx: ctx,
logger: logger,
source: source, source: source,
} }
} }
func (o FetchingL1Oracle) HeaderByHash(blockHash common.Hash) eth.BlockInfo { func (o FetchingL1Oracle) HeaderByHash(blockHash common.Hash) eth.BlockInfo {
o.logger.Trace("HeaderByHash", "hash", blockHash)
info, err := o.source.InfoByHash(o.ctx, blockHash) info, err := o.source.InfoByHash(o.ctx, blockHash)
if err != nil { if err != nil {
panic(fmt.Errorf("retrieve block %s: %w", blockHash, err)) panic(fmt.Errorf("retrieve block %s: %w", blockHash, err))
...@@ -39,6 +43,7 @@ func (o FetchingL1Oracle) HeaderByHash(blockHash common.Hash) eth.BlockInfo { ...@@ -39,6 +43,7 @@ func (o FetchingL1Oracle) HeaderByHash(blockHash common.Hash) eth.BlockInfo {
} }
func (o FetchingL1Oracle) TransactionsByHash(blockHash common.Hash) (eth.BlockInfo, types.Transactions) { func (o FetchingL1Oracle) TransactionsByHash(blockHash common.Hash) (eth.BlockInfo, types.Transactions) {
o.logger.Trace("TransactionsByHash", "hash", blockHash)
info, txs, err := o.source.InfoAndTxsByHash(o.ctx, blockHash) info, txs, err := o.source.InfoAndTxsByHash(o.ctx, blockHash)
if err != nil { if err != nil {
panic(fmt.Errorf("retrieve transactions for block %s: %w", blockHash, err)) panic(fmt.Errorf("retrieve transactions for block %s: %w", blockHash, err))
...@@ -50,6 +55,7 @@ func (o FetchingL1Oracle) TransactionsByHash(blockHash common.Hash) (eth.BlockIn ...@@ -50,6 +55,7 @@ func (o FetchingL1Oracle) TransactionsByHash(blockHash common.Hash) (eth.BlockIn
} }
func (o FetchingL1Oracle) ReceiptsByHash(blockHash common.Hash) (eth.BlockInfo, types.Receipts) { func (o FetchingL1Oracle) ReceiptsByHash(blockHash common.Hash) (eth.BlockInfo, types.Receipts) {
o.logger.Trace("ReceiptsByHash", "hash", blockHash)
info, rcpts, err := o.source.FetchReceipts(o.ctx, blockHash) info, rcpts, err := o.source.FetchReceipts(o.ctx, blockHash)
if err != nil { if err != nil {
panic(fmt.Errorf("retrieve receipts for block %s: %w", blockHash, err)) panic(fmt.Errorf("retrieve receipts for block %s: %w", blockHash, err))
......
...@@ -8,9 +8,11 @@ import ( ...@@ -8,9 +8,11 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog"
cll1 "github.com/ethereum-optimism/optimism/op-program/client/l1" cll1 "github.com/ethereum-optimism/optimism/op-program/client/l1"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -24,14 +26,14 @@ func TestHeaderByHash(t *testing.T) { ...@@ -24,14 +26,14 @@ func TestHeaderByHash(t *testing.T) {
t.Run("Success", func(t *testing.T) { t.Run("Success", func(t *testing.T) {
expected := &sources.HeaderInfo{} expected := &sources.HeaderInfo{}
source := &stubSource{nextInfo: expected} source := &stubSource{nextInfo: expected}
oracle := newFetchingOracle(source) oracle := newFetchingOracle(t, source)
actual := oracle.HeaderByHash(expected.Hash()) actual := oracle.HeaderByHash(expected.Hash())
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
}) })
t.Run("UnknownBlock", func(t *testing.T) { t.Run("UnknownBlock", func(t *testing.T) {
oracle := newFetchingOracle(&stubSource{}) oracle := newFetchingOracle(t, &stubSource{})
hash := common.HexToHash("0x4455") hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() { require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.HeaderByHash(hash) oracle.HeaderByHash(hash)
...@@ -41,7 +43,7 @@ func TestHeaderByHash(t *testing.T) { ...@@ -41,7 +43,7 @@ func TestHeaderByHash(t *testing.T) {
t.Run("Error", func(t *testing.T) { t.Run("Error", func(t *testing.T) {
err := errors.New("kaboom") err := errors.New("kaboom")
source := &stubSource{nextErr: err} source := &stubSource{nextErr: err}
oracle := newFetchingOracle(source) oracle := newFetchingOracle(t, source)
hash := common.HexToHash("0x8888") hash := common.HexToHash("0x8888")
require.PanicsWithError(t, fmt.Errorf("retrieve block %s: %w", hash, err).Error(), func() { require.PanicsWithError(t, fmt.Errorf("retrieve block %s: %w", hash, err).Error(), func() {
...@@ -57,7 +59,7 @@ func TestTransactionsByHash(t *testing.T) { ...@@ -57,7 +59,7 @@ func TestTransactionsByHash(t *testing.T) {
&types.Transaction{}, &types.Transaction{},
} }
source := &stubSource{nextInfo: expectedInfo, nextTxs: expectedTxs} source := &stubSource{nextInfo: expectedInfo, nextTxs: expectedTxs}
oracle := newFetchingOracle(source) oracle := newFetchingOracle(t, source)
info, txs := oracle.TransactionsByHash(expectedInfo.Hash()) info, txs := oracle.TransactionsByHash(expectedInfo.Hash())
require.Equal(t, expectedInfo, info) require.Equal(t, expectedInfo, info)
...@@ -65,7 +67,7 @@ func TestTransactionsByHash(t *testing.T) { ...@@ -65,7 +67,7 @@ func TestTransactionsByHash(t *testing.T) {
}) })
t.Run("UnknownBlock_NoInfo", func(t *testing.T) { t.Run("UnknownBlock_NoInfo", func(t *testing.T) {
oracle := newFetchingOracle(&stubSource{}) oracle := newFetchingOracle(t, &stubSource{})
hash := common.HexToHash("0x4455") hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() { require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.TransactionsByHash(hash) oracle.TransactionsByHash(hash)
...@@ -73,7 +75,7 @@ func TestTransactionsByHash(t *testing.T) { ...@@ -73,7 +75,7 @@ func TestTransactionsByHash(t *testing.T) {
}) })
t.Run("UnknownBlock_NoTxs", func(t *testing.T) { t.Run("UnknownBlock_NoTxs", func(t *testing.T) {
oracle := newFetchingOracle(&stubSource{nextInfo: &sources.HeaderInfo{}}) oracle := newFetchingOracle(t, &stubSource{nextInfo: &sources.HeaderInfo{}})
hash := common.HexToHash("0x4455") hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() { require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.TransactionsByHash(hash) oracle.TransactionsByHash(hash)
...@@ -83,7 +85,7 @@ func TestTransactionsByHash(t *testing.T) { ...@@ -83,7 +85,7 @@ func TestTransactionsByHash(t *testing.T) {
t.Run("Error", func(t *testing.T) { t.Run("Error", func(t *testing.T) {
err := errors.New("kaboom") err := errors.New("kaboom")
source := &stubSource{nextErr: err} source := &stubSource{nextErr: err}
oracle := newFetchingOracle(source) oracle := newFetchingOracle(t, source)
hash := common.HexToHash("0x8888") hash := common.HexToHash("0x8888")
require.PanicsWithError(t, fmt.Errorf("retrieve transactions for block %s: %w", hash, err).Error(), func() { require.PanicsWithError(t, fmt.Errorf("retrieve transactions for block %s: %w", hash, err).Error(), func() {
...@@ -99,7 +101,7 @@ func TestReceiptsByHash(t *testing.T) { ...@@ -99,7 +101,7 @@ func TestReceiptsByHash(t *testing.T) {
&types.Receipt{}, &types.Receipt{},
} }
source := &stubSource{nextInfo: expectedInfo, nextRcpts: expectedRcpts} source := &stubSource{nextInfo: expectedInfo, nextRcpts: expectedRcpts}
oracle := newFetchingOracle(source) oracle := newFetchingOracle(t, source)
info, rcpts := oracle.ReceiptsByHash(expectedInfo.Hash()) info, rcpts := oracle.ReceiptsByHash(expectedInfo.Hash())
require.Equal(t, expectedInfo, info) require.Equal(t, expectedInfo, info)
...@@ -107,7 +109,7 @@ func TestReceiptsByHash(t *testing.T) { ...@@ -107,7 +109,7 @@ func TestReceiptsByHash(t *testing.T) {
}) })
t.Run("UnknownBlock_NoInfo", func(t *testing.T) { t.Run("UnknownBlock_NoInfo", func(t *testing.T) {
oracle := newFetchingOracle(&stubSource{}) oracle := newFetchingOracle(t, &stubSource{})
hash := common.HexToHash("0x4455") hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() { require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.ReceiptsByHash(hash) oracle.ReceiptsByHash(hash)
...@@ -115,7 +117,7 @@ func TestReceiptsByHash(t *testing.T) { ...@@ -115,7 +117,7 @@ func TestReceiptsByHash(t *testing.T) {
}) })
t.Run("UnknownBlock_NoTxs", func(t *testing.T) { t.Run("UnknownBlock_NoTxs", func(t *testing.T) {
oracle := newFetchingOracle(&stubSource{nextInfo: &sources.HeaderInfo{}}) oracle := newFetchingOracle(t, &stubSource{nextInfo: &sources.HeaderInfo{}})
hash := common.HexToHash("0x4455") hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() { require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.ReceiptsByHash(hash) oracle.ReceiptsByHash(hash)
...@@ -125,7 +127,7 @@ func TestReceiptsByHash(t *testing.T) { ...@@ -125,7 +127,7 @@ func TestReceiptsByHash(t *testing.T) {
t.Run("Error", func(t *testing.T) { t.Run("Error", func(t *testing.T) {
err := errors.New("kaboom") err := errors.New("kaboom")
source := &stubSource{nextErr: err} source := &stubSource{nextErr: err}
oracle := newFetchingOracle(source) oracle := newFetchingOracle(t, source)
hash := common.HexToHash("0x8888") hash := common.HexToHash("0x8888")
require.PanicsWithError(t, fmt.Errorf("retrieve receipts for block %s: %w", hash, err).Error(), func() { require.PanicsWithError(t, fmt.Errorf("retrieve receipts for block %s: %w", hash, err).Error(), func() {
...@@ -134,11 +136,8 @@ func TestReceiptsByHash(t *testing.T) { ...@@ -134,11 +136,8 @@ func TestReceiptsByHash(t *testing.T) {
}) })
} }
func newFetchingOracle(source Source) *FetchingL1Oracle { func newFetchingOracle(t *testing.T, source Source) *FetchingL1Oracle {
return &FetchingL1Oracle{ return NewFetchingL1Oracle(context.Background(), testlog.Logger(t, log.LvlDebug), source)
ctx: context.Background(),
source: source,
}
} }
type stubSource struct { type stubSource struct {
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/client" "github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
cll1 "github.com/ethereum-optimism/optimism/op-program/client/l1"
"github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -16,5 +17,10 @@ func NewFetchingL1(ctx context.Context, logger log.Logger, cfg *config.Config) ( ...@@ -16,5 +17,10 @@ func NewFetchingL1(ctx context.Context, logger log.Logger, cfg *config.Config) (
return nil, err return nil, err
} }
return sources.NewL1Client(rpc, logger, nil, sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind)) source, err := sources.NewL1Client(rpc, logger, nil, sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind))
if err != nil {
return nil, err
}
oracle := NewFetchingL1Oracle(ctx, logger, source)
return cll1.NewOracleL1Client(logger, oracle, cfg.L1Head), err
} }
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