Commit 6818a0c6 authored by Adrian Sutton's avatar Adrian Sutton

op-program: Specify l2 block number to stop derivation at

parent d7a4b366
......@@ -24,18 +24,20 @@ type L2Source interface {
}
type Driver struct {
logger log.Logger
pipeline Derivation
l2OutputRoot func() (eth.Bytes32, error)
logger log.Logger
pipeline Derivation
l2OutputRoot func() (eth.Bytes32, error)
targetBlockNum uint64
}
func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher, l2Source L2Source) *Driver {
func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher, l2Source L2Source, targetBlockNum uint64) *Driver {
pipeline := derive.NewDerivationPipeline(logger, cfg, l1Source, l2Source, metrics.NoopMetrics)
pipeline.Reset()
return &Driver{
logger: logger,
pipeline: pipeline,
l2OutputRoot: l2Source.L2OutputRoot,
logger: logger,
pipeline: pipeline,
l2OutputRoot: l2Source.L2OutputRoot,
targetBlockNum: targetBlockNum,
}
}
......@@ -47,6 +49,11 @@ func (d *Driver) Step(ctx context.Context) error {
if err := d.pipeline.Step(ctx); errors.Is(err, io.EOF) {
return io.EOF
} else if errors.Is(err, derive.NotEnoughData) {
head := d.pipeline.SafeL2Head()
if head.Number >= d.targetBlockNum {
d.logger.Info("Target L2 block reached", "head", head)
return io.EOF
}
d.logger.Debug("Data is lacking")
return nil
} else if err != nil {
......
......@@ -39,6 +39,30 @@ func TestGenericError(t *testing.T) {
require.ErrorIs(t, err, expected)
}
func TestTargetBlock(t *testing.T) {
t.Run("Reached", func(t *testing.T) {
driver := createDriverWithNextBlock(t, derive.NotEnoughData, 1000)
driver.targetBlockNum = 1000
err := driver.Step(context.Background())
require.ErrorIs(t, err, io.EOF)
})
t.Run("Exceeded", func(t *testing.T) {
driver := createDriverWithNextBlock(t, derive.NotEnoughData, 1000)
driver.targetBlockNum = 500
err := driver.Step(context.Background())
require.ErrorIs(t, err, io.EOF)
})
t.Run("NotYetReached", func(t *testing.T) {
driver := createDriverWithNextBlock(t, derive.NotEnoughData, 1000)
driver.targetBlockNum = 1001
err := driver.Step(context.Background())
// No error to indicate derivation should continue
require.NoError(t, err)
})
}
func TestNoError(t *testing.T) {
driver := createDriver(t, nil)
err := driver.Step(context.Background())
......@@ -76,15 +100,21 @@ func TestValidateClaim(t *testing.T) {
}
func createDriver(t *testing.T, derivationResult error) *Driver {
derivation := &stubDerivation{nextErr: derivationResult}
return createDriverWithNextBlock(t, derivationResult, 0)
}
func createDriverWithNextBlock(t *testing.T, derivationResult error, nextBlockNum uint64) *Driver {
derivation := &stubDerivation{nextErr: derivationResult, nextBlockNum: nextBlockNum}
return &Driver{
logger: testlog.Logger(t, log.LvlDebug),
pipeline: derivation,
logger: testlog.Logger(t, log.LvlDebug),
pipeline: derivation,
targetBlockNum: 1_000_000,
}
}
type stubDerivation struct {
nextErr error
nextErr error
nextBlockNum uint64
}
func (s stubDerivation) Step(ctx context.Context) error {
......@@ -92,5 +122,7 @@ func (s stubDerivation) Step(ctx context.Context) error {
}
func (s stubDerivation) SafeL2Head() eth.L2BlockRef {
return eth.L2BlockRef{}
return eth.L2BlockRef{
Number: s.nextBlockNum,
}
}
......@@ -173,7 +173,7 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
}
logger.Info("Starting derivation")
d := cldr.NewDriver(logger, cfg.Rollup, l1Source, l2Source)
d := cldr.NewDriver(logger, cfg.Rollup, l1Source, l2Source, cfg.L2ClaimBlockNumber)
for {
if err = d.Step(ctx); errors.Is(err, io.EOF) {
break
......
......@@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"os"
"strconv"
"testing"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
......@@ -14,15 +15,17 @@ import (
"github.com/stretchr/testify/require"
)
// 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()
var l2ClaimValue = common.HexToHash("0x333333").Hex()
var l2Genesis = core.DefaultGoerliGenesisBlock()
var l2GenesisConfig = l2Genesis.Config
var (
// Use HexToHash(...).Hex() to ensure the strings are the correct length for a hash
l1HeadValue = common.HexToHash("0x111111").Hex()
l2HeadValue = common.HexToHash("0x222222").Hex()
l2ClaimValue = common.HexToHash("0x333333").Hex()
l2ClaimBlockNumber = uint64(1203)
l2Genesis = core.DefaultGoerliGenesisBlock()
l2GenesisConfig = l2Genesis.Config
)
func TestLogLevel(t *testing.T) {
t.Parallel()
t.Run("RejectInvalid", func(t *testing.T) {
verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs(t, "--log.level=foo"))
})
......@@ -38,19 +41,18 @@ func TestLogLevel(t *testing.T) {
}
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
t.Parallel()
cfg := configForArgs(t, addRequiredArgs(t))
defaultCfg := config.NewConfig(
&chaincfg.Goerli,
l2GenesisConfig,
common.HexToHash(l1HeadValue),
common.HexToHash(l2HeadValue),
common.HexToHash(l2ClaimValue))
common.HexToHash(l2ClaimValue),
l2ClaimBlockNumber)
require.Equal(t, defaultCfg, cfg)
}
func TestNetwork(t *testing.T) {
t.Parallel()
t.Run("Unknown", func(t *testing.T) {
verifyArgsInvalid(t, "invalid network bar", replaceRequiredArg(t, "--network", "bar"))
})
......@@ -86,21 +88,18 @@ func TestNetwork(t *testing.T) {
}
func TestDataDir(t *testing.T) {
t.Parallel()
expected := "/tmp/mainTestDataDir"
cfg := configForArgs(t, addRequiredArgs(t, "--datadir", expected))
require.Equal(t, expected, cfg.DataDir)
}
func TestL2(t *testing.T) {
t.Parallel()
expected := "https://example.com:8545"
cfg := configForArgs(t, addRequiredArgs(t, "--l2", expected))
require.Equal(t, expected, cfg.L2URL)
}
func TestL2Genesis(t *testing.T) {
t.Parallel()
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.genesis is required", addRequiredArgsExcept(t, "--l2.genesis"))
})
......@@ -112,7 +111,6 @@ func TestL2Genesis(t *testing.T) {
}
func TestL2Head(t *testing.T) {
t.Parallel()
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.head is required", addRequiredArgsExcept(t, "--l2.head"))
})
......@@ -128,7 +126,6 @@ func TestL2Head(t *testing.T) {
}
func TestL1Head(t *testing.T) {
t.Parallel()
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l1.head is required", addRequiredArgsExcept(t, "--l1.head"))
})
......@@ -144,14 +141,12 @@ func TestL1Head(t *testing.T) {
}
func TestL1(t *testing.T) {
t.Parallel()
expected := "https://example.com:8545"
cfg := configForArgs(t, addRequiredArgs(t, "--l1", expected))
require.Equal(t, expected, cfg.L1URL)
}
func TestL1TrustRPC(t *testing.T) {
t.Parallel()
t.Run("DefaultFalse", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(t))
require.False(t, cfg.L1TrustRPC)
......@@ -171,7 +166,6 @@ func TestL1TrustRPC(t *testing.T) {
}
func TestL1RPCKind(t *testing.T) {
t.Parallel()
t.Run("DefaultBasic", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(t))
require.Equal(t, sources.RPCKindBasic, cfg.L1RPCKind)
......@@ -191,7 +185,6 @@ func TestL1RPCKind(t *testing.T) {
}
func TestL2Claim(t *testing.T) {
t.Parallel()
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.claim is required", addRequiredArgsExcept(t, "--l2.claim"))
})
......@@ -206,6 +199,21 @@ func TestL2Claim(t *testing.T) {
})
}
func TestL2BlockNumber(t *testing.T) {
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.blocknumber is required", addRequiredArgsExcept(t, "--l2.blocknumber"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, replaceRequiredArg(t, "--l2.blocknumber", strconv.FormatUint(l2ClaimBlockNumber, 10)))
require.EqualValues(t, l2ClaimBlockNumber, cfg.L2ClaimBlockNumber)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, "invalid value \"something\" for flag -l2.blocknumber", replaceRequiredArg(t, "--l2.blocknumber", "something"))
})
}
func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains)
......@@ -218,7 +226,7 @@ func configForArgs(t *testing.T, cliArgs []string) *config.Config {
}
func runWithArgs(cliArgs []string) (log.Logger, *config.Config, error) {
var cfg *config.Config
cfg := new(config.Config)
var logger log.Logger
fullArgs := append([]string{"op-program"}, cliArgs...)
err := run(fullArgs, func(log log.Logger, config *config.Config) error {
......@@ -252,11 +260,12 @@ func replaceRequiredArg(t *testing.T, name string, value string) []string {
func requiredArgs(t *testing.T) map[string]string {
genesisFile := writeValidGenesis(t)
return map[string]string{
"--network": "goerli",
"--l1.head": l1HeadValue,
"--l2.head": l2HeadValue,
"--l2.claim": l2ClaimValue,
"--l2.genesis": genesisFile,
"--network": "goerli",
"--l1.head": l1HeadValue,
"--l2.head": l2HeadValue,
"--l2.claim": l2ClaimValue,
"--l2.blocknumber": strconv.FormatUint(l2ClaimBlockNumber, 10),
"--l2.genesis": genesisFile,
}
}
......
......@@ -23,6 +23,7 @@ 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")
ErrInvalidL2ClaimBlock = errors.New("invalid l2 claim block number")
ErrDataDirRequired = errors.New("datadir must be specified when in non-fetching mode")
)
......@@ -43,6 +44,9 @@ type Config struct {
L2URL string
// L2Claim is the claimed L2 output root to verify
L2Claim common.Hash
// L2ClaimBlockNumber is the block number the claimed L2 output root is from
// Must be above 0 and to be a valid claim needs to be above the L2Head block.
L2ClaimBlockNumber uint64
// L2ChainConfig is the op-geth chain config for the L2 execution engine
L2ChainConfig *params.ChainConfig
}
......@@ -63,6 +67,9 @@ func (c *Config) Check() error {
if c.L2Claim == (common.Hash{}) {
return ErrInvalidL2Claim
}
if c.L2ClaimBlockNumber == 0 {
return ErrInvalidL2ClaimBlock
}
if c.L2ChainConfig == nil {
return ErrMissingL2Genesis
}
......@@ -80,14 +87,15 @@ func (c *Config) FetchingEnabled() bool {
}
// NewConfig creates a Config with all optional values set to the CLI default value
func NewConfig(rollupCfg *rollup.Config, l2Genesis *params.ChainConfig, l1Head common.Hash, l2Head common.Hash, l2Claim common.Hash) *Config {
func NewConfig(rollupCfg *rollup.Config, l2Genesis *params.ChainConfig, l1Head common.Hash, l2Head common.Hash, l2Claim common.Hash, l2ClaimBlockNum uint64) *Config {
return &Config{
Rollup: rollupCfg,
L2ChainConfig: l2Genesis,
L1Head: l1Head,
L2Head: l2Head,
L2Claim: l2Claim,
L1RPCKind: sources.RPCKindBasic,
Rollup: rollupCfg,
L2ChainConfig: l2Genesis,
L1Head: l1Head,
L2Head: l2Head,
L2Claim: l2Claim,
L2ClaimBlockNumber: l2ClaimBlockNum,
L1RPCKind: sources.RPCKindBasic,
}
}
......@@ -107,6 +115,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
if l2Claim == (common.Hash{}) {
return nil, ErrInvalidL2Claim
}
l2ClaimBlockNum := ctx.GlobalUint64(flags.L2BlockNumber.Name)
l1Head := common.HexToHash(ctx.GlobalString(flags.L1Head.Name))
if l1Head == (common.Hash{}) {
return nil, ErrInvalidL1Head
......@@ -117,16 +126,17 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
return nil, fmt.Errorf("invalid genesis: %w", err)
}
return &Config{
Rollup: rollupCfg,
DataDir: ctx.GlobalString(flags.DataDir.Name),
L2URL: ctx.GlobalString(flags.L2NodeAddr.Name),
L2ChainConfig: l2ChainConfig,
L2Head: l2Head,
L2Claim: l2Claim,
L1Head: l1Head,
L1URL: ctx.GlobalString(flags.L1NodeAddr.Name),
L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name),
L1RPCKind: sources.RPCProviderKind(ctx.GlobalString(flags.L1RPCProviderKind.Name)),
Rollup: rollupCfg,
DataDir: ctx.GlobalString(flags.DataDir.Name),
L2URL: ctx.GlobalString(flags.L2NodeAddr.Name),
L2ChainConfig: l2ChainConfig,
L2Head: l2Head,
L2Claim: l2Claim,
L2ClaimBlockNumber: l2ClaimBlockNum,
L1Head: l1Head,
L1URL: ctx.GlobalString(flags.L1NodeAddr.Name),
L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name),
L1RPCKind: sources.RPCProviderKind(ctx.GlobalString(flags.L1RPCProviderKind.Name)),
}, nil
}
......
......@@ -10,11 +10,14 @@ import (
"github.com/stretchr/testify/require"
)
var validRollupConfig = &chaincfg.Goerli
var validL2Genesis = params.GoerliChainConfig
var validL1Head = common.Hash{0xaa}
var validL2Head = common.Hash{0xbb}
var validL2Claim = common.Hash{0xcc}
var (
validRollupConfig = &chaincfg.Goerli
validL2Genesis = params.GoerliChainConfig
validL1Head = common.Hash{0xaa}
validL2Head = common.Hash{0xbb}
validL2Claim = common.Hash{0xcc}
validL2ClaimBlockNum = uint64(15)
)
// TestValidConfigIsValid checks that the config provided by validConfig is actually valid
func TestValidConfigIsValid(t *testing.T) {
......@@ -59,6 +62,13 @@ func TestL2ClaimRequired(t *testing.T) {
require.ErrorIs(t, err, ErrInvalidL2Claim)
}
func TestL2ClaimBlockNumberRequired(t *testing.T) {
config := validConfig()
config.L2ClaimBlockNumber = 0
err := config.Check()
require.ErrorIs(t, err, ErrInvalidL2ClaimBlock)
}
func TestL2GenesisRequired(t *testing.T) {
config := validConfig()
config.L2ChainConfig = nil
......@@ -133,7 +143,7 @@ func TestRequireDataDirInNonFetchingMode(t *testing.T) {
}
func validConfig() *Config {
cfg := NewConfig(validRollupConfig, validL2Genesis, validL1Head, validL2Head, validL2Claim)
cfg := NewConfig(validRollupConfig, validL2Genesis, validL1Head, validL2Head, validL2Claim, validL2ClaimBlockNum)
cfg.DataDir = "/tmp/configTest"
return cfg
}
......@@ -51,6 +51,11 @@ var (
Usage: "Claimed L2 output root to validate",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_CLAIM"),
}
L2BlockNumber = cli.Uint64Flag{
Name: "l2.blocknumber",
Usage: "Number of the L2 block that the claim is from",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_BLOCK_NUM"),
}
L2GenesisPath = cli.StringFlag{
Name: "l2.genesis",
Usage: "Path to the op-geth genesis file",
......@@ -85,6 +90,7 @@ var requiredFlags = []cli.Flag{
L1Head,
L2Head,
L2Claim,
L2BlockNumber,
L2GenesisPath,
}
var programFlags = []cli.Flag{
......@@ -113,7 +119,7 @@ func CheckRequired(ctx *cli.Context) error {
return fmt.Errorf("cannot specify both %s and %s", RollupConfig.Name, Network.Name)
}
for _, flag := range requiredFlags {
if ctx.GlobalString(flag.GetName()) == "" {
if !ctx.IsSet(flag.GetName()) {
return fmt.Errorf("flag %s is required", flag.GetName())
}
}
......
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