Commit c26ab41e authored by Axel Kingsley's avatar Axel Kingsley Committed by GitHub

Initialize using Dependency Set Configuration (#12495)

* Initialize using Dependency Set Configuration

* op-supervisor: init fromda, route fromda metrics, handle cross-unsafe, improve backend resource initialization

* op-supervisor: attach RPC, create processors upfront, implement backend test

* op-supervisor: fix dependency set configuration and test setup

* Update op-supervisor/supervisor/backend/backend.go
Co-authored-by: default avatarAxel Kingsley <axel.kingsley@gmail.com>

---------
Co-authored-by: default avatarprotolambda <proto@protolambda.com>
parent 7ce91656
...@@ -419,7 +419,7 @@ func (s *interopE2ESystem) newL2(id string, l2Out *interopgen.L2Output) l2Set { ...@@ -419,7 +419,7 @@ func (s *interopE2ESystem) newL2(id string, l2Out *interopgen.L2Output) l2Set {
func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService { func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService {
// Be verbose with op-supervisor, it's in early test phase // Be verbose with op-supervisor, it's in early test phase
logger := testlog.Logger(s.t, log.LevelDebug).New("role", "supervisor") logger := testlog.Logger(s.t, log.LevelDebug).New("role", "supervisor")
cfg := supervisorConfig.Config{ cfg := &supervisorConfig.Config{
MetricsConfig: metrics.CLIConfig{ MetricsConfig: metrics.CLIConfig{
Enabled: false, Enabled: false,
}, },
...@@ -441,9 +441,9 @@ func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService { ...@@ -441,9 +441,9 @@ func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService {
depSet := &depset.StaticConfigDependencySet{ depSet := &depset.StaticConfigDependencySet{
Dependencies: make(map[supervisortypes.ChainID]*depset.StaticConfigDependency), Dependencies: make(map[supervisortypes.ChainID]*depset.StaticConfigDependency),
} }
for id := range s.l2s { // Iterate over the L2 chain configs. The L2 nodes don't exist yet.
cfg.L2RPCs = append(cfg.L2RPCs, s.l2s[id].l2Geth.UserRPC().RPC()) for _, l2Out := range s.worldOutput.L2s {
chainID := supervisortypes.ChainIDFromBig(s.l2s[id].chainID) chainID := supervisortypes.ChainIDFromBig(l2Out.Genesis.Config.ChainID)
depSet.Dependencies[chainID] = &depset.StaticConfigDependency{ depSet.Dependencies[chainID] = &depset.StaticConfigDependency{
ActivationTime: 0, ActivationTime: 0,
HistoryMinTime: 0, HistoryMinTime: 0,
...@@ -451,7 +451,7 @@ func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService { ...@@ -451,7 +451,7 @@ func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService {
} }
cfg.DependencySetSource = depSet cfg.DependencySetSource = depSet
// Create the supervisor with the configuration // Create the supervisor with the configuration
super, err := supervisor.SupervisorFromConfig(context.Background(), &cfg, logger) super, err := supervisor.SupervisorFromConfig(context.Background(), cfg, logger)
require.NoError(s.t, err) require.NoError(s.t, err)
// Start the supervisor // Start the supervisor
err = super.Start(context.Background()) err = super.Start(context.Background())
...@@ -495,7 +495,7 @@ func (s *interopE2ESystem) prepare(t *testing.T, w worldResourcePaths) { ...@@ -495,7 +495,7 @@ func (s *interopE2ESystem) prepare(t *testing.T, w worldResourcePaths) {
ctx := context.Background() ctx := context.Background()
for _, l2 := range s.l2s { for _, l2 := range s.l2s {
err := s.SupervisorClient().AddL2RPC(ctx, l2.l2Geth.UserRPC().RPC()) err := s.SupervisorClient().AddL2RPC(ctx, l2.l2Geth.UserRPC().RPC())
require.NoError(s.t, err, "failed to add L2 RPC to supervisor", "error", err) require.NoError(s.t, err, "failed to add L2 RPC to supervisor")
} }
} }
......
...@@ -29,6 +29,10 @@ type Config struct { ...@@ -29,6 +29,10 @@ type Config struct {
// MockRun runs the service with a mock backend // MockRun runs the service with a mock backend
MockRun bool MockRun bool
// SynchronousProcessors disables background-workers,
// requiring manual triggers for the backend to process anything.
SynchronousProcessors bool
L2RPCs []string L2RPCs []string
Datadir string Datadir string
} }
......
package metrics package metrics
import ( import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
) )
const Namespace = "op_supervisor" const Namespace = "op_supervisor"
...@@ -18,7 +18,7 @@ type Metricer interface { ...@@ -18,7 +18,7 @@ type Metricer interface {
CacheAdd(chainID types.ChainID, label string, cacheSize int, evicted bool) CacheAdd(chainID types.ChainID, label string, cacheSize int, evicted bool)
CacheGet(chainID types.ChainID, label string, hit bool) CacheGet(chainID types.ChainID, label string, hit bool)
RecordDBEntryCount(chainID types.ChainID, count int64) RecordDBEntryCount(chainID types.ChainID, kind string, count int64)
RecordDBSearchEntriesRead(chainID types.ChainID, count int64) RecordDBSearchEntriesRead(chainID types.ChainID, count int64)
Document() []opmetrics.DocumentedMetric Document() []opmetrics.DocumentedMetric
...@@ -106,9 +106,10 @@ func NewMetrics(procName string) *Metrics { ...@@ -106,9 +106,10 @@ func NewMetrics(procName string) *Metrics {
DBEntryCountVec: factory.NewGaugeVec(prometheus.GaugeOpts{ DBEntryCountVec: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Namespace: ns,
Name: "logdb_entries_current", Name: "logdb_entries_current",
Help: "Current number of entries in the log database by chain ID", Help: "Current number of entries in the database of specified kind and chain ID",
}, []string{ }, []string{
"chain", "chain",
"kind",
}), }),
DBSearchEntriesReadVec: factory.NewHistogramVec(prometheus.HistogramOpts{ DBSearchEntriesReadVec: factory.NewHistogramVec(prometheus.HistogramOpts{
Namespace: ns, Namespace: ns,
...@@ -159,8 +160,8 @@ func (m *Metrics) CacheGet(chainID types.ChainID, label string, hit bool) { ...@@ -159,8 +160,8 @@ func (m *Metrics) CacheGet(chainID types.ChainID, label string, hit bool) {
} }
} }
func (m *Metrics) RecordDBEntryCount(chainID types.ChainID, count int64) { func (m *Metrics) RecordDBEntryCount(chainID types.ChainID, kind string, count int64) {
m.DBEntryCountVec.WithLabelValues(chainIDLabel(chainID)).Set(float64(count)) m.DBEntryCountVec.WithLabelValues(chainIDLabel(chainID), kind).Set(float64(count))
} }
func (m *Metrics) RecordDBSearchEntriesRead(chainID types.ChainID, count int64) { func (m *Metrics) RecordDBSearchEntriesRead(chainID types.ChainID, count int64) {
......
...@@ -19,5 +19,5 @@ func (*noopMetrics) RecordUp() {} ...@@ -19,5 +19,5 @@ func (*noopMetrics) RecordUp() {}
func (m *noopMetrics) CacheAdd(_ types.ChainID, _ string, _ int, _ bool) {} func (m *noopMetrics) CacheAdd(_ types.ChainID, _ string, _ int, _ bool) {}
func (m *noopMetrics) CacheGet(_ types.ChainID, _ string, _ bool) {} func (m *noopMetrics) CacheGet(_ types.ChainID, _ string, _ bool) {}
func (m *noopMetrics) RecordDBEntryCount(_ types.ChainID, _ int64) {} func (m *noopMetrics) RecordDBEntryCount(_ types.ChainID, _ string, _ int64) {}
func (m *noopMetrics) RecordDBSearchEntriesRead(_ types.ChainID, _ int64) {} func (m *noopMetrics) RecordDBSearchEntriesRead(_ types.ChainID, _ int64) {}
This diff is collapsed.
package backend
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
types2 "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/eth"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
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-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/ethereum-optimism/optimism/op-supervisor/metrics"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
func TestBackendLifetime(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
m := metrics.NoopMetrics
dataDir := t.TempDir()
chainA := types.ChainIDFromUInt64(900)
chainB := types.ChainIDFromUInt64(901)
cfg := &config.Config{
Version: "test",
LogConfig: oplog.CLIConfig{},
MetricsConfig: opmetrics.CLIConfig{},
PprofConfig: oppprof.CLIConfig{},
RPC: oprpc.CLIConfig{},
DependencySetSource: &depset.StaticConfigDependencySet{
Dependencies: map[types.ChainID]*depset.StaticConfigDependency{
chainA: {
ActivationTime: 42,
HistoryMinTime: 100,
},
chainB: {
ActivationTime: 30,
HistoryMinTime: 20,
},
},
},
SynchronousProcessors: true,
MockRun: false,
L2RPCs: nil,
Datadir: dataDir,
}
b, err := NewSupervisorBackend(context.Background(), logger, m, cfg)
require.NoError(t, err)
t.Log("initialized!")
src := &testutils.MockL1Source{}
blockX := eth.BlockRef{
Hash: common.Hash{0xaa},
Number: 0,
ParentHash: common.Hash{}, // genesis has no parent hash
Time: 10000,
}
blockY := eth.BlockRef{
Hash: common.Hash{0xbb},
Number: blockX.Number + 1,
ParentHash: blockX.Hash,
Time: blockX.Time + 2,
}
require.NoError(t, b.AttachProcessorSource(chainA, src))
require.FileExists(t, filepath.Join(cfg.Datadir, "900", "log.db"), "must have logs DB 900")
require.FileExists(t, filepath.Join(cfg.Datadir, "901", "log.db"), "must have logs DB 901")
require.FileExists(t, filepath.Join(cfg.Datadir, "900", "local_safe.db"), "must have local safe DB 900")
require.FileExists(t, filepath.Join(cfg.Datadir, "901", "local_safe.db"), "must have local safe DB 901")
require.FileExists(t, filepath.Join(cfg.Datadir, "900", "cross_safe.db"), "must have cross safe DB 900")
require.FileExists(t, filepath.Join(cfg.Datadir, "901", "cross_safe.db"), "must have cross safe DB 901")
err = b.Start(context.Background())
require.NoError(t, err)
t.Log("started!")
_, err = b.UnsafeView(context.Background(), chainA, types.ReferenceView{})
require.ErrorIs(t, err, entrydb.ErrFuture, "no data yet, need local-unsafe")
src.ExpectL1BlockRefByNumber(0, blockX, nil)
src.ExpectFetchReceipts(blockX.Hash, &testutils.MockBlockInfo{
InfoHash: blockX.Hash,
InfoParentHash: blockX.ParentHash,
InfoNum: blockX.Number,
InfoTime: blockX.Time,
InfoReceiptRoot: types2.EmptyReceiptsHash,
}, nil, nil)
src.ExpectL1BlockRefByNumber(1, blockY, nil)
src.ExpectFetchReceipts(blockY.Hash, &testutils.MockBlockInfo{
InfoHash: blockY.Hash,
InfoParentHash: blockY.ParentHash,
InfoNum: blockY.Number,
InfoTime: blockY.Time,
InfoReceiptRoot: types2.EmptyReceiptsHash,
}, nil, nil)
src.ExpectL1BlockRefByNumber(2, eth.L1BlockRef{}, ethereum.NotFound)
err = b.UpdateLocalUnsafe(chainA, blockY)
require.NoError(t, err)
// Make the processing happen, so we can rely on the new chain information,
// and not run into errors for future data that isn't mocked at this time.
b.chainProcessors[chainA].ProcessToHead()
_, err = b.UnsafeView(context.Background(), chainA, types.ReferenceView{})
require.ErrorIs(t, err, entrydb.ErrFuture, "still no data yet, need cross-unsafe")
err = b.chainDBs.UpdateCrossUnsafe(chainA, types.BlockSeal{
Hash: blockX.Hash,
Number: blockX.Number,
Timestamp: blockX.Time,
})
require.NoError(t, err)
v, err := b.UnsafeView(context.Background(), chainA, types.ReferenceView{})
require.NoError(t, err, "have a functioning cross/local unsafe view now")
require.Equal(t, blockX.ID(), v.Cross)
require.Equal(t, blockY.ID(), v.Local)
err = b.Stop(context.Background())
require.NoError(t, err)
t.Log("stopped!")
}
...@@ -10,7 +10,7 @@ type Metrics interface { ...@@ -10,7 +10,7 @@ type Metrics interface {
CacheAdd(chainID types.ChainID, label string, cacheSize int, evicted bool) CacheAdd(chainID types.ChainID, label string, cacheSize int, evicted bool)
CacheGet(chainID types.ChainID, label string, hit bool) CacheGet(chainID types.ChainID, label string, hit bool)
RecordDBEntryCount(chainID types.ChainID, count int64) RecordDBEntryCount(chainID types.ChainID, kind string, count int64)
RecordDBSearchEntriesRead(chainID types.ChainID, count int64) RecordDBSearchEntriesRead(chainID types.ChainID, count int64)
} }
...@@ -36,8 +36,8 @@ func (c *chainMetrics) CacheGet(label string, hit bool) { ...@@ -36,8 +36,8 @@ func (c *chainMetrics) CacheGet(label string, hit bool) {
c.delegate.CacheGet(c.chainID, label, hit) c.delegate.CacheGet(c.chainID, label, hit)
} }
func (c *chainMetrics) RecordDBEntryCount(count int64) { func (c *chainMetrics) RecordDBEntryCount(kind string, count int64) {
c.delegate.RecordDBEntryCount(c.chainID, count) c.delegate.RecordDBEntryCount(c.chainID, kind, count)
} }
func (c *chainMetrics) RecordDBSearchEntriesRead(count int64) { func (c *chainMetrics) RecordDBSearchEntriesRead(count int64) {
......
...@@ -10,11 +10,14 @@ import ( ...@@ -10,11 +10,14 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/fromda"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
) )
var ErrUnknownChain = errors.New("unknown chain") var (
ErrUnknownChain = errors.New("unknown chain")
)
type LogStorage interface { type LogStorage interface {
io.Closer io.Closer
...@@ -46,12 +49,14 @@ type LogStorage interface { ...@@ -46,12 +49,14 @@ type LogStorage interface {
} }
type LocalDerivedFromStorage interface { type LocalDerivedFromStorage interface {
Last() (derivedFrom eth.BlockRef, derived eth.BlockRef, err error) Latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error)
AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error
LastDerived(derivedFrom eth.BlockID) (derived eth.BlockID, err error) LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, err error)
DerivedFrom(derived eth.BlockID) (derivedFrom eth.BlockID, err error) DerivedFrom(derived eth.BlockID) (derivedFrom types.BlockSeal, err error)
} }
var _ LocalDerivedFromStorage = (*fromda.DB)(nil)
type CrossDerivedFromStorage interface { type CrossDerivedFromStorage interface {
LocalDerivedFromStorage LocalDerivedFromStorage
// This will start to differ with reorg support // This will start to differ with reorg support
...@@ -71,6 +76,7 @@ type ChainsDB struct { ...@@ -71,6 +76,7 @@ type ChainsDB struct {
logDBs map[types.ChainID]LogStorage logDBs map[types.ChainID]LogStorage
// cross-unsafe: how far we have processed the unsafe data. // cross-unsafe: how far we have processed the unsafe data.
// If present but set to a zeroed value the cross-unsafe will fallback to cross-safe.
crossUnsafe map[types.ChainID]types.BlockSeal crossUnsafe map[types.ChainID]types.BlockSeal
// local-safe: index of what we optimistically know about L2 blocks being derived from L1 // local-safe: index of what we optimistically know about L2 blocks being derived from L1
...@@ -97,14 +103,47 @@ func NewChainsDB(l log.Logger) *ChainsDB { ...@@ -97,14 +103,47 @@ func NewChainsDB(l log.Logger) *ChainsDB {
} }
} }
func (db *ChainsDB) AddLogDB(chain types.ChainID, logDB LogStorage) { func (db *ChainsDB) AddLogDB(chainID types.ChainID, logDB LogStorage) {
db.mu.Lock()
defer db.mu.Unlock()
if _, ok := db.logDBs[chainID]; ok {
db.logger.Warn("overwriting existing log DB for chain", "chain", chainID)
}
db.logDBs[chainID] = logDB
}
func (db *ChainsDB) AddLocalDerivedFromDB(chainID types.ChainID, dfDB LocalDerivedFromStorage) {
db.mu.Lock()
defer db.mu.Unlock()
if _, ok := db.localDBs[chainID]; ok {
db.logger.Warn("overwriting existing local derived-from DB for chain", "chain", chainID)
}
db.localDBs[chainID] = dfDB
}
func (db *ChainsDB) AddCrossDerivedFromDB(chainID types.ChainID, dfDB CrossDerivedFromStorage) {
db.mu.Lock()
defer db.mu.Unlock()
if _, ok := db.crossDBs[chainID]; ok {
db.logger.Warn("overwriting existing cross derived-from DB for chain", "chain", chainID)
}
db.crossDBs[chainID] = dfDB
}
func (db *ChainsDB) AddCrossUnsafeTracker(chainID types.ChainID) {
db.mu.Lock() db.mu.Lock()
defer db.mu.Unlock() defer db.mu.Unlock()
if db.logDBs[chain] != nil { if _, ok := db.crossUnsafe[chainID]; ok {
log.Warn("overwriting existing logDB for chain", "chain", chain) db.logger.Warn("overwriting existing cross-unsafe tracker for chain", "chain", chainID)
} }
db.logDBs[chain] = logDB db.crossUnsafe[chainID] = types.BlockSeal{}
} }
// ResumeFromLastSealedBlock prepares the chains db to resume recording events after a restart. // ResumeFromLastSealedBlock prepares the chains db to resume recording events after a restart.
......
package backend package db
import ( import (
"fmt" "fmt"
...@@ -8,6 +8,22 @@ import ( ...@@ -8,6 +8,22 @@ import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
) )
func prepLocalDerivedFromDBPath(chainID types.ChainID, datadir string) (string, error) {
dir, err := prepChainDir(chainID, datadir)
if err != nil {
return "", err
}
return filepath.Join(dir, "local_safe.db"), nil
}
func prepCrossDerivedFromDBPath(chainID types.ChainID, datadir string) (string, error) {
dir, err := prepChainDir(chainID, datadir)
if err != nil {
return "", err
}
return filepath.Join(dir, "cross_safe.db"), nil
}
func prepLogDBPath(chainID types.ChainID, datadir string) (string, error) { func prepLogDBPath(chainID types.ChainID, datadir string) (string, error) {
dir, err := prepChainDir(chainID, datadir) dir, err := prepChainDir(chainID, datadir)
if err != nil { if err != nil {
...@@ -24,7 +40,7 @@ func prepChainDir(chainID types.ChainID, datadir string) (string, error) { ...@@ -24,7 +40,7 @@ func prepChainDir(chainID types.ChainID, datadir string) (string, error) {
return dir, nil return dir, nil
} }
func prepDataDir(datadir string) error { func PrepDataDir(datadir string) error {
if err := os.MkdirAll(datadir, 0755); err != nil { if err := os.MkdirAll(datadir, 0755); err != nil {
return fmt.Errorf("failed to create data directory %v: %w", datadir, err) return fmt.Errorf("failed to create data directory %v: %w", datadir, err)
} }
......
...@@ -13,10 +13,6 @@ import ( ...@@ -13,10 +13,6 @@ import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
) )
type Metrics interface {
RecordDBDerivedEntryCount(count int64)
}
type EntryStore interface { type EntryStore interface {
Size() int64 Size() int64
LastEntryIdx() entrydb.EntryIdx LastEntryIdx() entrydb.EntryIdx
......
package fromda
type Metrics interface {
RecordDBDerivedEntryCount(count int64)
}
type ChainMetrics interface {
RecordDBEntryCount(kind string, count int64)
}
type delegate struct {
inner ChainMetrics
kind string
}
func (d *delegate) RecordDBDerivedEntryCount(count int64) {
d.inner.RecordDBEntryCount(d.kind, count)
}
func AdaptMetrics(chainMetrics ChainMetrics, kind string) Metrics {
return &delegate{
kind: kind,
inner: chainMetrics,
}
}
...@@ -21,7 +21,7 @@ const ( ...@@ -21,7 +21,7 @@ const (
) )
type Metrics interface { type Metrics interface {
RecordDBEntryCount(count int64) RecordDBEntryCount(kind string, count int64)
RecordDBSearchEntriesRead(count int64) RecordDBSearchEntriesRead(count int64)
} }
...@@ -122,7 +122,7 @@ func (db *DB) trimToLastSealed() error { ...@@ -122,7 +122,7 @@ func (db *DB) trimToLastSealed() error {
} }
func (db *DB) updateEntryCountMetric() { func (db *DB) updateEntryCountMetric() {
db.m.RecordDBEntryCount(db.store.Size()) db.m.RecordDBEntryCount("log", db.store.Size())
} }
func (db *DB) IteratorStartingAt(sealedNum uint64, logsSince uint32) (Iterator, error) { func (db *DB) IteratorStartingAt(sealedNum uint64, logsSince uint32) (Iterator, error) {
...@@ -295,7 +295,7 @@ func (db *DB) newIteratorAt(blockNum uint64, logIndex uint32) (*iterator, error) ...@@ -295,7 +295,7 @@ func (db *DB) newIteratorAt(blockNum uint64, logIndex uint32) (*iterator, error)
}() }()
// First walk up to the block that we are sealed up to (incl.) // First walk up to the block that we are sealed up to (incl.)
for { for {
if _, n, _ := iter.SealedBlock(); n == blockNum { // we may already have it exactly if _, n, ok := iter.SealedBlock(); ok && n == blockNum { // we may already have it exactly
break break
} }
if err := iter.NextBlock(); errors.Is(err, entrydb.ErrFuture) { if err := iter.NextBlock(); errors.Is(err, entrydb.ErrFuture) {
......
...@@ -1133,7 +1133,7 @@ type stubMetrics struct { ...@@ -1133,7 +1133,7 @@ type stubMetrics struct {
entriesReadForSearch int64 entriesReadForSearch int64
} }
func (s *stubMetrics) RecordDBEntryCount(count int64) { func (s *stubMetrics) RecordDBEntryCount(kind string, count int64) {
s.entryCount = count s.entryCount = count
} }
......
package db
import (
"fmt"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/fromda"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
func OpenLogDB(logger log.Logger, chainID types.ChainID, dataDir string, m logs.Metrics) (*logs.DB, error) {
path, err := prepLogDBPath(chainID, dataDir)
if err != nil {
return nil, fmt.Errorf("failed to create datadir for chain %s: %w", chainID, err)
}
logDB, err := logs.NewFromFile(logger, m, path, true)
if err != nil {
return nil, fmt.Errorf("failed to create logdb for chain %s at %v: %w", chainID, path, err)
}
return logDB, nil
}
func OpenLocalDerivedFromDB(logger log.Logger, chainID types.ChainID, dataDir string, m fromda.ChainMetrics) (*fromda.DB, error) {
path, err := prepLocalDerivedFromDBPath(chainID, dataDir)
if err != nil {
return nil, fmt.Errorf("failed to prepare datadir for chain %s: %w", chainID, err)
}
db, err := fromda.NewFromFile(logger, fromda.AdaptMetrics(m, "local_derived"), path)
if err != nil {
return nil, fmt.Errorf("failed to create local-derived for chain %s at %q: %w", chainID, path, err)
}
return db, nil
}
func OpenCrossDerivedFromDB(logger log.Logger, chainID types.ChainID, dataDir string, m fromda.ChainMetrics) (*fromda.DB, error) {
path, err := prepCrossDerivedFromDBPath(chainID, dataDir)
if err != nil {
return nil, fmt.Errorf("failed to prepare datadir for chain %s: %w", chainID, err)
}
db, err := fromda.NewFromFile(logger, fromda.AdaptMetrics(m, "cross_derived"), path)
if err != nil {
return nil, fmt.Errorf("failed to create cross-derived for chain %s at %q: %w", chainID, path, err)
}
return db, nil
}
...@@ -60,61 +60,69 @@ func (db *ChainsDB) CrossUnsafe(chainID types.ChainID) (types.BlockSeal, error) ...@@ -60,61 +60,69 @@ func (db *ChainsDB) CrossUnsafe(chainID types.ChainID) (types.BlockSeal, error)
if !ok { if !ok {
return types.BlockSeal{}, ErrUnknownChain return types.BlockSeal{}, ErrUnknownChain
} }
// Fall back to cross-safe if cross-unsafe is not known yet
if result == (types.BlockSeal{}) {
_, crossSafe, err := db.CrossSafe(chainID)
if err != nil {
return types.BlockSeal{}, fmt.Errorf("no cross-unsafe known for chain %s, and failed to fall back to cross-safe value: %w", chainID, err)
}
return crossSafe, nil
}
return result, nil return result, nil
} }
func (db *ChainsDB) LocalSafe(chainID types.ChainID) (derivedFrom eth.BlockRef, derived eth.BlockRef, err error) { func (db *ChainsDB) LocalSafe(chainID types.ChainID) (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) {
db.mu.RLock() db.mu.RLock()
defer db.mu.RUnlock() defer db.mu.RUnlock()
localDB, ok := db.localDBs[chainID] localDB, ok := db.localDBs[chainID]
if !ok { if !ok {
return eth.BlockRef{}, eth.BlockRef{}, ErrUnknownChain return types.BlockSeal{}, types.BlockSeal{}, ErrUnknownChain
} }
return localDB.Last() return localDB.Latest()
} }
func (db *ChainsDB) CrossSafe(chainID types.ChainID) (derivedFrom eth.BlockRef, derived eth.BlockRef, err error) { func (db *ChainsDB) CrossSafe(chainID types.ChainID) (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) {
db.mu.RLock() db.mu.RLock()
defer db.mu.RUnlock() defer db.mu.RUnlock()
crossDB, ok := db.crossDBs[chainID] crossDB, ok := db.crossDBs[chainID]
if !ok { if !ok {
return eth.BlockRef{}, eth.BlockRef{}, ErrUnknownChain return types.BlockSeal{}, types.BlockSeal{}, ErrUnknownChain
} }
return crossDB.Last() return crossDB.Latest()
} }
func (db *ChainsDB) Finalized(chainID types.ChainID) (eth.BlockID, error) { func (db *ChainsDB) Finalized(chainID types.ChainID) (types.BlockSeal, error) {
db.mu.RLock() db.mu.RLock()
defer db.mu.RUnlock() defer db.mu.RUnlock()
finalizedL1 := db.finalizedL1 finalizedL1 := db.finalizedL1
if finalizedL1 == (eth.L1BlockRef{}) { if finalizedL1 == (eth.L1BlockRef{}) {
return eth.BlockID{}, errors.New("no finalized L1 signal, cannot determine L2 finality yet") return types.BlockSeal{}, errors.New("no finalized L1 signal, cannot determine L2 finality yet")
} }
derived, err := db.LastDerivedFrom(chainID, finalizedL1.ID()) derived, err := db.LastDerivedFrom(chainID, finalizedL1.ID())
if err != nil { if err != nil {
return eth.BlockID{}, errors.New("could not find what was last derived from the finalized L1 block") return types.BlockSeal{}, errors.New("could not find what was last derived from the finalized L1 block")
} }
return derived, nil return derived, nil
} }
func (db *ChainsDB) LastDerivedFrom(chainID types.ChainID, derivedFrom eth.BlockID) (derived eth.BlockID, err error) { func (db *ChainsDB) LastDerivedFrom(chainID types.ChainID, derivedFrom eth.BlockID) (derived types.BlockSeal, err error) {
crossDB, ok := db.crossDBs[chainID] crossDB, ok := db.crossDBs[chainID]
if !ok { if !ok {
return eth.BlockID{}, ErrUnknownChain return types.BlockSeal{}, ErrUnknownChain
} }
return crossDB.LastDerived(derivedFrom) return crossDB.LastDerivedAt(derivedFrom)
} }
func (db *ChainsDB) DerivedFrom(chainID types.ChainID, derived eth.BlockID) (derivedFrom eth.BlockID, err error) { func (db *ChainsDB) DerivedFrom(chainID types.ChainID, derived eth.BlockID) (derivedFrom types.BlockSeal, err error) {
db.mu.RLock() db.mu.RLock()
defer db.mu.RUnlock() defer db.mu.RUnlock()
localDB, ok := db.localDBs[chainID] localDB, ok := db.localDBs[chainID]
if !ok { if !ok {
return eth.BlockID{}, ErrUnknownChain return types.BlockSeal{}, ErrUnknownChain
} }
return localDB.DerivedFrom(derived) return localDB.DerivedFrom(derived)
} }
......
...@@ -25,4 +25,11 @@ type DependencySet interface { ...@@ -25,4 +25,11 @@ type DependencySet interface {
// This may return an error if the query temporarily cannot be answered. // This may return an error if the query temporarily cannot be answered.
// E.g. if the DependencySet is syncing new changes. // E.g. if the DependencySet is syncing new changes.
CanInitiateAt(chainID types.ChainID, initTimestamp uint64) (bool, error) CanInitiateAt(chainID types.ChainID, initTimestamp uint64) (bool, error)
// Chains returns the list of chains that are part of the dependency set.
Chains() []types.ChainID
// HasChain determines if a chain is being tracked for interop purposes.
// See CanExecuteAt and CanInitiateAt to check if a chain may message at a given time.
HasChain(chainID types.ChainID) bool
} }
...@@ -36,6 +36,12 @@ func TestDependencySet(t *testing.T) { ...@@ -36,6 +36,12 @@ func TestDependencySet(t *testing.T) {
result, err := loader.LoadDependencySet(context.Background()) result, err := loader.LoadDependencySet(context.Background())
require.NoError(t, err) require.NoError(t, err)
chainIDs := result.Chains()
require.Equal(t, []types.ChainID{
types.ChainIDFromUInt64(900),
types.ChainIDFromUInt64(901),
}, chainIDs)
v, err := result.CanExecuteAt(types.ChainIDFromUInt64(900), 42) v, err := result.CanExecuteAt(types.ChainIDFromUInt64(900), 42)
require.NoError(t, err) require.NoError(t, err)
require.True(t, v) require.True(t, v)
......
...@@ -2,6 +2,9 @@ package depset ...@@ -2,6 +2,9 @@ package depset
import ( import (
"context" "context"
"sort"
"golang.org/x/exp/maps"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
) )
...@@ -46,3 +49,16 @@ func (ds *StaticConfigDependencySet) CanInitiateAt(chainID types.ChainID, initTi ...@@ -46,3 +49,16 @@ func (ds *StaticConfigDependencySet) CanInitiateAt(chainID types.ChainID, initTi
} }
return initTimestamp >= dep.HistoryMinTime, nil return initTimestamp >= dep.HistoryMinTime, nil
} }
func (ds *StaticConfigDependencySet) Chains() []types.ChainID {
out := maps.Keys(ds.Dependencies)
sort.Slice(out, func(i, j int) bool {
return out[i].Cmp(out[j]) < 0
})
return out
}
func (ds *StaticConfigDependencySet) HasChain(chainID types.ChainID) bool {
_, ok := ds.Dependencies[chainID]
return ok
}
...@@ -2,11 +2,13 @@ package processors ...@@ -2,11 +2,13 @@ package processors
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
gethtypes "github.com/ethereum/go-ethereum/core/types" gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -15,6 +17,8 @@ import ( ...@@ -15,6 +17,8 @@ import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
) )
var ErrNoRPCSource = errors.New("no RPC client configured")
type Source interface { type Source interface {
L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error)
FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, gethtypes.Receipts, error) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, gethtypes.Receipts, error)
...@@ -38,8 +42,10 @@ func (fn BlockProcessorFn) ProcessBlock(ctx context.Context, block eth.BlockRef) ...@@ -38,8 +42,10 @@ func (fn BlockProcessorFn) ProcessBlock(ctx context.Context, block eth.BlockRef)
// ChainProcessor is a HeadProcessor that fills in any skipped blocks between head update events. // ChainProcessor is a HeadProcessor that fills in any skipped blocks between head update events.
// It ensures that, absent reorgs, every block in the chain is processed even if some head advancements are skipped. // It ensures that, absent reorgs, every block in the chain is processed even if some head advancements are skipped.
type ChainProcessor struct { type ChainProcessor struct {
log log.Logger log log.Logger
client Source
client Source
clientLock sync.Mutex
chain types.ChainID chain types.ChainID
...@@ -51,8 +57,6 @@ type ChainProcessor struct { ...@@ -51,8 +57,6 @@ type ChainProcessor struct {
// channel with capacity of 1, full if there is work to do // channel with capacity of 1, full if there is work to do
newHead chan struct{} newHead chan struct{}
// bool to indicate if calls are synchronous
synchronous bool
// channel with capacity of 1, to signal work complete if running in synchroneous mode // channel with capacity of 1, to signal work complete if running in synchroneous mode
out chan struct{} out chan struct{}
...@@ -62,27 +66,37 @@ type ChainProcessor struct { ...@@ -62,27 +66,37 @@ type ChainProcessor struct {
wg sync.WaitGroup wg sync.WaitGroup
} }
func NewChainProcessor(log log.Logger, client Source, chain types.ChainID, processor LogProcessor, rewinder DatabaseRewinder) *ChainProcessor { func NewChainProcessor(log log.Logger, chain types.ChainID, processor LogProcessor, rewinder DatabaseRewinder) *ChainProcessor {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
out := &ChainProcessor{ out := &ChainProcessor{
log: log, log: log.New("chain", chain),
client: client, client: nil,
chain: chain, chain: chain,
processor: processor, processor: processor,
rewinder: rewinder, rewinder: rewinder,
newHead: make(chan struct{}, 1), newHead: make(chan struct{}, 1),
// default to synchronous because we want other processors to wait for this out: make(chan struct{}, 1),
// in the future we could make this async and have a separate mechanism which forwards the work signal to other processors ctx: ctx,
synchronous: true, cancel: cancel,
out: make(chan struct{}, 1),
ctx: ctx,
cancel: cancel,
} }
out.wg.Add(1)
go out.worker()
return out return out
} }
func (s *ChainProcessor) SetSource(cl Source) {
s.clientLock.Lock()
defer s.clientLock.Unlock()
s.client = cl
}
func (s *ChainProcessor) StartBackground() {
s.wg.Add(1)
go s.worker()
}
func (s *ChainProcessor) ProcessToHead() {
s.work()
}
func (s *ChainProcessor) nextNum() uint64 { func (s *ChainProcessor) nextNum() uint64 {
headNum, ok := s.rewinder.LatestBlockNum(s.chain) headNum, ok := s.rewinder.LatestBlockNum(s.chain)
if !ok { if !ok {
...@@ -106,11 +120,6 @@ func (s *ChainProcessor) worker() { ...@@ -106,11 +120,6 @@ func (s *ChainProcessor) worker() {
case <-s.newHead: case <-s.newHead:
s.log.Debug("Responding to new head signal") s.log.Debug("Responding to new head signal")
s.work() s.work()
// if this chain processor is synchronous, signal completion
// to be picked up by the caller (ChainProcessor.OnNewHead)
if s.synchronous {
s.out <- struct{}{}
}
case <-delay.C: case <-delay.C:
s.log.Debug("Checking for updates") s.log.Debug("Checking for updates")
s.work() s.work()
...@@ -126,8 +135,14 @@ func (s *ChainProcessor) work() { ...@@ -126,8 +135,14 @@ func (s *ChainProcessor) work() {
} }
target := s.nextNum() target := s.nextNum()
if err := s.update(target); err != nil { if err := s.update(target); err != nil {
s.log.Error("Failed to process new block", "err", err) if errors.Is(err, ethereum.NotFound) {
// idle until next update trigger s.log.Info("Cannot find next block yet", "target", target)
} else if errors.Is(err, ErrNoRPCSource) {
s.log.Warn("No RPC source configured, cannot process new blocks")
} else {
s.log.Error("Failed to process new block", "err", err)
// idle until next update trigger
}
} else if x := s.lastHead.Load(); target+1 <= x { } else if x := s.lastHead.Load(); target+1 <= x {
s.log.Debug("Continuing with next block", "newTarget", target+1, "lastHead", x) s.log.Debug("Continuing with next block", "newTarget", target+1, "lastHead", x)
continue // instantly continue processing, no need to idle continue // instantly continue processing, no need to idle
...@@ -139,6 +154,13 @@ func (s *ChainProcessor) work() { ...@@ -139,6 +154,13 @@ func (s *ChainProcessor) work() {
} }
func (s *ChainProcessor) update(nextNum uint64) error { func (s *ChainProcessor) update(nextNum uint64) error {
s.clientLock.Lock()
defer s.clientLock.Unlock()
if s.client == nil {
return ErrNoRPCSource
}
ctx, cancel := context.WithTimeout(s.ctx, time.Second*10) ctx, cancel := context.WithTimeout(s.ctx, time.Second*10)
nextL1, err := s.client.L1BlockRefByNumber(ctx, nextNum) nextL1, err := s.client.L1BlockRefByNumber(ctx, nextNum)
next := eth.BlockRef{ next := eth.BlockRef{
...@@ -185,10 +207,6 @@ func (s *ChainProcessor) OnNewHead(head eth.BlockRef) error { ...@@ -185,10 +207,6 @@ func (s *ChainProcessor) OnNewHead(head eth.BlockRef) error {
default: default:
// already requested an update // already requested an update
} }
// if we are running synchronously, wait for the work to complete
if s.synchronous {
<-s.out
}
return nil return nil
} }
......
...@@ -182,6 +182,10 @@ func (id *ChainID) UnmarshalText(data []byte) error { ...@@ -182,6 +182,10 @@ func (id *ChainID) UnmarshalText(data []byte) error {
return nil return nil
} }
func (id ChainID) Cmp(other ChainID) int {
return (*uint256.Int)(&id).Cmp((*uint256.Int)(&other))
}
type ReferenceView struct { type ReferenceView struct {
Local eth.BlockID `json:"local"` Local eth.BlockID `json:"local"`
Cross eth.BlockID `json:"cross"` 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