Commit 59ba5f37 authored by protolambda's avatar protolambda Committed by GitHub

op-supervisor: dependency-set config (#12450)

* op-supervisor: cleanup, refactor to take local-safe info from op-node

* op-supervisor: dependency-set config

---------
Co-authored-by: default avataraxelKingsley <axel.kingsley@gmail.com>
parent 745b251d
......@@ -10,10 +10,6 @@ import (
"testing"
"time"
emit "github.com/ethereum-optimism/optimism/op-e2e/interop/contracts"
"github.com/ethereum-optimism/optimism/op-e2e/system/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
......@@ -30,11 +26,14 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/devkeys"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/interopgen"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/fakebeacon"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/opnode"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/services"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/setuputils"
emit "github.com/ethereum-optimism/optimism/op-e2e/interop/contracts"
"github.com/ethereum-optimism/optimism/op-e2e/system/helpers"
"github.com/ethereum-optimism/optimism/op-node/node"
"github.com/ethereum-optimism/optimism/op-node/p2p"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
......@@ -53,6 +52,8 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog"
supervisorConfig "github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
supervisortypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
// SuperSystem is an interface for the system (collection of connected resources)
......@@ -437,9 +438,18 @@ func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService {
L2RPCs: []string{},
Datadir: path.Join(s.t.TempDir(), "supervisor"),
}
depSet := &depset.StaticConfigDependencySet{
Dependencies: make(map[supervisortypes.ChainID]*depset.StaticConfigDependency),
}
for id := range s.l2s {
cfg.L2RPCs = append(cfg.L2RPCs, s.l2s[id].l2Geth.UserRPC().RPC())
chainID := supervisortypes.ChainIDFromBig(s.l2s[id].chainID)
depSet.Dependencies[chainID] = &depset.StaticConfigDependency{
ActivationTime: 0,
HistoryMinTime: 0,
}
}
cfg.DependencySetSource = depSet
// Create the supervisor with the configuration
super, err := supervisor.SupervisorFromConfig(context.Background(), &cfg, logger)
require.NoError(s.t, err)
......
......@@ -6,12 +6,13 @@ import (
"fmt"
"testing"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
)
var (
......@@ -36,7 +37,8 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
defaultCfgTempl := config.NewConfig(ValidL2RPCs, ValidDatadir)
depSet := &depset.JsonDependencySetLoader{Path: "test"}
defaultCfgTempl := config.NewConfig(ValidL2RPCs, depSet, ValidDatadir)
defaultCfg := *defaultCfgTempl
defaultCfg.Version = Version
require.Equal(t, defaultCfg, *cfg)
......@@ -123,8 +125,9 @@ func toArgList(req map[string]string) []string {
func requiredArgs() map[string]string {
args := map[string]string{
"--l2-rpcs": ValidL2RPCs[0],
"--datadir": ValidDatadir,
"--l2-rpcs": ValidL2RPCs[0],
"--dependency-set": "test",
"--datadir": ValidDatadir,
}
return args
}
......@@ -7,11 +7,13 @@ import (
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
)
var (
ErrMissingL2RPC = errors.New("must specify at least one L2 RPC")
ErrMissingDatadir = errors.New("must specify datadir")
ErrMissingL2RPC = errors.New("must specify at least one L2 RPC")
ErrMissingDependencySet = errors.New("must specify a dependency set source")
ErrMissingDatadir = errors.New("must specify datadir")
)
type Config struct {
......@@ -22,6 +24,8 @@ type Config struct {
PprofConfig oppprof.CLIConfig
RPC oprpc.CLIConfig
DependencySetSource depset.DependencySetSource
// MockRun runs the service with a mock backend
MockRun bool
......@@ -37,6 +41,9 @@ func (c *Config) Check() error {
if len(c.L2RPCs) == 0 {
result = errors.Join(result, ErrMissingL2RPC)
}
if c.DependencySetSource == nil {
result = errors.Join(result, ErrMissingDependencySet)
}
if c.Datadir == "" {
result = errors.Join(result, ErrMissingDatadir)
}
......@@ -45,14 +52,15 @@ func (c *Config) Check() error {
// NewConfig creates a new config using default values whenever possible.
// Required options with no suitable default are passed as parameters.
func NewConfig(l2RPCs []string, datadir string) *Config {
func NewConfig(l2RPCs []string, depSet depset.DependencySetSource, datadir string) *Config {
return &Config{
LogConfig: oplog.DefaultCLIConfig(),
MetricsConfig: opmetrics.DefaultCLIConfig(),
PprofConfig: oppprof.DefaultCLIConfig(),
RPC: oprpc.DefaultCLIConfig(),
MockRun: false,
L2RPCs: l2RPCs,
Datadir: datadir,
LogConfig: oplog.DefaultCLIConfig(),
MetricsConfig: opmetrics.DefaultCLIConfig(),
PprofConfig: oppprof.DefaultCLIConfig(),
RPC: oprpc.DefaultCLIConfig(),
DependencySetSource: depSet,
MockRun: false,
L2RPCs: l2RPCs,
Datadir: datadir,
}
}
......@@ -3,10 +3,13 @@ package config
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
func TestDefaultConfigIsValid(t *testing.T) {
......@@ -20,6 +23,12 @@ func TestRequireL2RPC(t *testing.T) {
require.ErrorIs(t, cfg.Check(), ErrMissingL2RPC)
}
func TestRequireDependencySet(t *testing.T) {
cfg := validConfig()
cfg.DependencySetSource = nil
require.ErrorIs(t, cfg.Check(), ErrMissingDependencySet)
}
func TestRequireDatadir(t *testing.T) {
cfg := validConfig()
cfg.Datadir = ""
......@@ -47,6 +56,14 @@ func TestValidateRPCConfig(t *testing.T) {
}
func validConfig() *Config {
depSet := &depset.StaticConfigDependencySet{
Dependencies: map[types.ChainID]*depset.StaticConfigDependency{
types.ChainIDFromUInt64(900): &depset.StaticConfigDependency{
ActivationTime: 0,
HistoryMinTime: 0,
},
},
}
// Should be valid using only the required arguments passed in via the constructor.
return NewConfig([]string{"http://localhost:8545"}, "./supervisor_config_testdir")
return NewConfig([]string{"http://localhost:8545"}, depSet, "./supervisor_testdir")
}
......@@ -3,7 +3,6 @@ package flags
import (
"fmt"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/urfave/cli/v2"
opservice "github.com/ethereum-optimism/optimism/op-service"
......@@ -11,6 +10,8 @@ import (
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
)
const EnvVarPrefix = "OP_SUPERVISOR"
......@@ -30,6 +31,12 @@ var (
Usage: "Directory to store data generated as part of responding to games",
EnvVars: prefixEnvVars("DATADIR"),
}
DependencySetFlag = &cli.PathFlag{
Name: "dependency-set",
Usage: "Dependency-set configuration, point at JSON file.",
EnvVars: prefixEnvVars("DEPENDENCY_SET"),
TakesFile: true,
}
MockRunFlag = &cli.BoolFlag{
Name: "mock-run",
Usage: "Mock run, no actual backend used, just presenting the service",
......@@ -41,6 +48,7 @@ var (
var requiredFlags = []cli.Flag{
L2RPCsFlag,
DataDirFlag,
DependencySetFlag,
}
var optionalFlags = []cli.Flag{
......@@ -71,13 +79,14 @@ func CheckRequired(ctx *cli.Context) error {
func ConfigFromCLI(ctx *cli.Context, version string) *config.Config {
return &config.Config{
Version: version,
LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx),
RPC: oprpc.ReadCLIConfig(ctx),
MockRun: ctx.Bool(MockRunFlag.Name),
L2RPCs: ctx.StringSlice(L2RPCsFlag.Name),
Datadir: ctx.Path(DataDirFlag.Name),
Version: version,
LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx),
RPC: oprpc.ReadCLIConfig(ctx),
DependencySetSource: &depset.JsonDependencySetLoader{Path: ctx.Path(DependencySetFlag.Name)},
MockRun: ctx.Bool(MockRunFlag.Name),
L2RPCs: ctx.StringSlice(L2RPCsFlag.Name),
Datadir: ctx.Path(DataDirFlag.Name),
}
}
......@@ -19,6 +19,7 @@ import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/processors"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
......@@ -35,6 +36,8 @@ type SupervisorBackend struct {
// Write = set of chains is changing.
mu sync.RWMutex
depSet depset.DependencySet
// db holds on to the DB indices for each chain
db *db.ChainsDB
......@@ -52,6 +55,12 @@ func NewSupervisorBackend(ctx context.Context, logger log.Logger, m Metrics, cfg
return nil, err
}
// Load the dependency set
depSet, err := cfg.DependencySetSource.LoadDependencySet(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load dependency set: %w", err)
}
// create the chains db
chainsDB := db.NewChainsDB(logger)
......@@ -63,6 +72,7 @@ func NewSupervisorBackend(ctx context.Context, logger log.Logger, m Metrics, cfg
logger: logger,
m: m,
dataDir: cfg.Datadir,
depSet: depSet,
chainProcessors: chainProcessors,
db: chainsDB,
}
......
package depset
import (
"context"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
type DependencySetSource interface {
LoadDependencySet(ctx context.Context) (DependencySet, error)
}
// DependencySet is an initialized dependency set, ready to answer queries
// of what is and what is not part of the dependency set.
type DependencySet interface {
// CanExecuteAt determines if an executing message is valid at all.
// I.e. if the chain may be executing messages at the given timestamp.
// This may return an error if the query temporarily cannot be answered.
// E.g. if the DependencySet is syncing new changes.
CanExecuteAt(chainID types.ChainID, execTimestamp uint64) (bool, error)
// CanInitiateAt determines if an initiating message is valid to pull in.
// I.e. if the message of the given chain is readable or not.
// This may return an error if the query temporarily cannot be answered.
// E.g. if the DependencySet is syncing new changes.
CanInitiateAt(chainID types.ChainID, initTimestamp uint64) (bool, error)
}
package depset
import (
"context"
"encoding/json"
"os"
"path"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
func TestDependencySet(t *testing.T) {
d := path.Join(t.TempDir(), "tmp_dep_set.json")
depSet := &StaticConfigDependencySet{
Dependencies: map[types.ChainID]*StaticConfigDependency{
types.ChainIDFromUInt64(900): {
ActivationTime: 42,
HistoryMinTime: 100,
},
types.ChainIDFromUInt64(901): {
ActivationTime: 30,
HistoryMinTime: 20,
},
},
}
data, err := json.Marshal(depSet)
require.NoError(t, err)
require.NoError(t, os.WriteFile(d, data, 0644))
loader := &JsonDependencySetLoader{Path: d}
result, err := loader.LoadDependencySet(context.Background())
require.NoError(t, err)
v, err := result.CanExecuteAt(types.ChainIDFromUInt64(900), 42)
require.NoError(t, err)
require.True(t, v)
v, err = result.CanExecuteAt(types.ChainIDFromUInt64(900), 41)
require.NoError(t, err)
require.False(t, v)
v, err = result.CanInitiateAt(types.ChainIDFromUInt64(900), 100)
require.NoError(t, err)
require.True(t, v)
v, err = result.CanInitiateAt(types.ChainIDFromUInt64(900), 99)
require.NoError(t, err)
require.False(t, v)
v, err = result.CanExecuteAt(types.ChainIDFromUInt64(901), 30)
require.NoError(t, err)
require.True(t, v)
v, err = result.CanExecuteAt(types.ChainIDFromUInt64(901), 29)
require.NoError(t, err)
require.False(t, v)
v, err = result.CanInitiateAt(types.ChainIDFromUInt64(901), 20)
require.NoError(t, err)
require.True(t, v)
v, err = result.CanInitiateAt(types.ChainIDFromUInt64(901), 19)
require.NoError(t, err)
require.False(t, v)
v, err = result.CanExecuteAt(types.ChainIDFromUInt64(902), 100000)
require.NoError(t, err)
require.False(t, v, "902 not a dependency")
v, err = result.CanInitiateAt(types.ChainIDFromUInt64(902), 100000)
require.NoError(t, err)
require.False(t, v, "902 not a dependency")
}
package depset
import (
"context"
"encoding/json"
"fmt"
"os"
)
// JsonDependencySetLoader loads a dependency set from a file-path.
type JsonDependencySetLoader struct {
Path string
}
func (j *JsonDependencySetLoader) LoadDependencySet(ctx context.Context) (DependencySet, error) {
f, err := os.Open(j.Path)
if err != nil {
return nil, fmt.Errorf("failed to open dependency set: %w", err)
}
defer f.Close()
dec := json.NewDecoder(f)
var out StaticConfigDependencySet
if err := dec.Decode(&out); err != nil {
return nil, fmt.Errorf("failed to decode dependency set: %w", err)
}
return &out, nil
}
var _ DependencySetSource = (*JsonDependencySetLoader)(nil)
package depset
import (
"context"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
type StaticConfigDependency struct {
// ActivationTime is when the chain becomes part of the dependency set.
// This is the minimum timestamp of the inclusion of an executing message.
ActivationTime uint64 `json:"activationTime"`
// HistoryMinTime is what the lower bound of data is to store.
// This is the minimum timestamp of an initiating message to be accessible to others.
// This is set to 0 when all data since genesis is executable.
HistoryMinTime uint64 `json:"historyMinTime"`
}
// StaticConfigDependencySet statically declares a DependencySet.
// It can be used as a DependencySetSource itself, by simply returning the itself when loading the set.
type StaticConfigDependencySet struct {
Dependencies map[types.ChainID]*StaticConfigDependency `json:"dependencies"`
}
var _ DependencySetSource = (*StaticConfigDependencySet)(nil)
var _ DependencySet = (*StaticConfigDependencySet)(nil)
func (ds *StaticConfigDependencySet) LoadDependencySet(ctx context.Context) (DependencySet, error) {
return ds, nil
}
func (ds *StaticConfigDependencySet) CanExecuteAt(chainID types.ChainID, execTimestamp uint64) (bool, error) {
dep, ok := ds.Dependencies[chainID]
if !ok {
return false, nil
}
return execTimestamp >= dep.ActivationTime, nil
}
func (ds *StaticConfigDependencySet) CanInitiateAt(chainID types.ChainID, initTimestamp uint64) (bool, error) {
dep, ok := ds.Dependencies[chainID]
if !ok {
return false, nil
}
return initTimestamp >= dep.HistoryMinTime, nil
}
......@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/log"
......@@ -12,6 +11,7 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/ethereum-optimism/optimism/op-supervisor/flags"
)
......
......@@ -17,6 +17,7 @@ import (
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
......@@ -46,6 +47,9 @@ func TestSupervisorService(t *testing.T) {
ListenPort: 0, // pick a port automatically
EnableAdmin: true,
},
DependencySetSource: &depset.StaticConfigDependencySet{
Dependencies: make(map[types.ChainID]*depset.StaticConfigDependency),
},
MockRun: true,
}
logger := testlog.Logger(t, log.LevelError)
......
......@@ -168,6 +168,20 @@ func (id ChainID) ToUInt32() (uint32, error) {
return uint32(v64), nil
}
func (id ChainID) MarshalText() ([]byte, error) {
return []byte(id.String()), nil
}
func (id *ChainID) UnmarshalText(data []byte) error {
var x uint256.Int
err := x.UnmarshalText(data)
if err != nil {
return err
}
*id = ChainID(x)
return nil
}
type ReferenceView struct {
Local eth.BlockID `json:"local"`
Cross eth.BlockID `json:"cross"`
......
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