Skip to content

Commit c0efb56

Browse files
committed
op-supervisor,op-node: Handle non-interop genesis activation
1 parent 9b6edc5 commit c0efb56

File tree

17 files changed

+250
-39
lines changed

17 files changed

+250
-39
lines changed

op-node/rollup/interop/managed/system.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type ManagedMode struct {
5454
}
5555

5656
func NewManagedMode(log log.Logger, cfg *rollup.Config, addr string, port int, jwtSecret eth.Bytes32, l1 L1Source, l2 L2Source, m opmetrics.RPCMetricer) *ManagedMode {
57+
log = log.With("mode", "managed", "chainId", cfg.L2ChainID)
5758
out := &ManagedMode{
5859
log: log,
5960
cfg: cfg,
@@ -113,21 +114,34 @@ func (m *ManagedMode) AttachEmitter(em event.Emitter) {
113114
m.emitter = em
114115
}
115116

117+
// Outgoing events to supervisor
116118
func (m *ManagedMode) OnEvent(ev event.Event) bool {
117119
switch x := ev.(type) {
118120
case rollup.ResetEvent:
119121
msg := x.Err.Error()
120122
m.events.Send(&supervisortypes.ManagedEvent{Reset: &msg})
121123
case engine.UnsafeUpdateEvent:
124+
if !m.cfg.IsInterop(x.Ref.Time) {
125+
m.log.Debug("Ignoring non-Interop local unsafe update", "unsafe", x.Ref)
126+
return false
127+
}
122128
ref := x.Ref.BlockRef()
123129
m.events.Send(&supervisortypes.ManagedEvent{UnsafeBlock: &ref})
124130
case engine.LocalSafeUpdateEvent:
131+
if !m.cfg.IsInterop(x.Ref.Time) {
132+
m.log.Debug("Ignoring non-Interop local safe update", "derivedFrom", x.Source, "derived", x.Ref)
133+
return false
134+
}
125135
m.log.Info("Emitting local safe update because of L2 block", "derivedFrom", x.Source, "derived", x.Ref)
126136
m.events.Send(&supervisortypes.ManagedEvent{DerivationUpdate: &supervisortypes.DerivedBlockRefPair{
127137
Source: x.Source,
128138
Derived: x.Ref.BlockRef(),
129139
}})
130140
case derive.DeriverL1StatusEvent:
141+
if !m.cfg.IsInterop(x.LastL2.Time) {
142+
m.log.Debug("Ignoring non-Interop L1 traversal", "origin", x.Origin, "lastL2", x.LastL2)
143+
return false
144+
}
131145
m.log.Info("Emitting local safe update because of L1 traversal", "derivedFrom", x.Origin, "derived", x.LastL2)
132146
m.events.Send(&supervisortypes.ManagedEvent{
133147
DerivationUpdate: &supervisortypes.DerivedBlockRefPair{
@@ -240,6 +254,15 @@ func (m *ManagedMode) InvalidateBlock(ctx context.Context, seal supervisortypes.
240254
}
241255

242256
func (m *ManagedMode) AnchorPoint(ctx context.Context) (supervisortypes.DerivedBlockRefPair, error) {
257+
// TODO: maybe cache non-genesis anchor point when seeing safe Interop activation block?
258+
// Only needed if we don't test for activation block in the supervisor.
259+
if !m.cfg.IsInterop(m.cfg.Genesis.L2Time) {
260+
return supervisortypes.DerivedBlockRefPair{}, &gethrpc.JsonError{
261+
Code: InteropInactiveRPCErrCode,
262+
Message: "Interop inactive at genesis",
263+
}
264+
}
265+
243266
l1Ref, err := m.l1.L1BlockRefByHash(ctx, m.cfg.Genesis.L1.Hash)
244267
if err != nil {
245268
return supervisortypes.DerivedBlockRefPair{}, fmt.Errorf("failed to fetch L1 block ref: %w", err)
@@ -258,8 +281,16 @@ const (
258281
InternalErrorRPCErrcode = -32603
259282
BlockNotFoundRPCErrCode = -39001
260283
ConflictingBlockRPCErrCode = -39002
284+
InteropInactiveRPCErrCode = -39003
261285
)
262286

287+
// TODO: add ResetPreInterop, called by supervisor if bisection went pre-Interop. Emit ResetEngineRequestEvent.
288+
func (m *ManagedMode) ResetPreInterop(ctx context.Context) error {
289+
m.log.Info("Received pre-interop reset request")
290+
m.emitter.Emit(engine.ResetEngineRequestEvent{})
291+
return nil
292+
}
293+
263294
func (m *ManagedMode) Reset(ctx context.Context, lUnsafe, xUnsafe, lSafe, xSafe, finalized eth.BlockID) error {
264295
logger := m.log.New(
265296
"localUnsafe", lUnsafe,

op-node/rollup/sync/start.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ type L2Chain interface {
4848
L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error)
4949
}
5050

51-
var ReorgFinalizedErr = errors.New("cannot reorg finalized block")
52-
var WrongChainErr = errors.New("wrong chain")
53-
var TooDeepReorgErr = errors.New("reorg is too deep")
51+
var (
52+
ReorgFinalizedErr = errors.New("cannot reorg finalized block")
53+
WrongChainErr = errors.New("wrong chain")
54+
TooDeepReorgErr = errors.New("reorg is too deep")
55+
)
5456

5557
const MaxReorgSeqWindows = 5
5658

@@ -156,6 +158,8 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain
156158
// Once we pass the previous safe head and we have seen enough canonical L1 origins to fill a sequence window worth of data,
157159
// then we return the last L2 block of the epoch before that as safe head.
158160
// Each loop iteration we traverse a single L2 block, and we check if the L1 origins are consistent.
161+
// TODO: maybe check inside here not to go over Interop activation, this should be handled by supervisor
162+
// Fine to keep unsafe chain. Safe chain should be capped to stay pre-Interop.
159163
for {
160164
// Fetch L1 information if we never had it, or if we do not have it for the current origin.
161165
// Optimization: as soon as we have a previous L1 block, try to traverse L1 by hash instead of by number, to fill the cache.

op-supervisor/supervisor/backend/backend.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ type SupervisorBackend struct {
8181
rpcVerificationWarnings bool
8282
}
8383

84-
var _ event.AttachEmitter = (*SupervisorBackend)(nil)
85-
var _ frontend.Backend = (*SupervisorBackend)(nil)
84+
var (
85+
_ event.AttachEmitter = (*SupervisorBackend)(nil)
86+
_ frontend.Backend = (*SupervisorBackend)(nil)
87+
)
8688

8789
var (
8890
errAlreadyStopped = errors.New("already stopped")
@@ -97,7 +99,8 @@ var (
9799
var verifyAccessWithRPCTimeout = 10 * time.Second
98100

99101
func NewSupervisorBackend(ctx context.Context, logger log.Logger,
100-
m Metrics, cfg *config.Config, eventExec event.Executor) (*SupervisorBackend, error) {
102+
m Metrics, cfg *config.Config, eventExec event.Executor,
103+
) (*SupervisorBackend, error) {
101104
// attempt to prepare the data directory
102105
if err := db.PrepDataDir(cfg.Datadir); err != nil {
103106
return nil, err
@@ -176,18 +179,37 @@ func NewSupervisorBackend(ctx context.Context, logger log.Logger,
176179
func (su *SupervisorBackend) OnEvent(ev event.Event) bool {
177180
switch x := ev.(type) {
178181
case superevents.LocalUnsafeReceivedEvent:
182+
if su.cfgSet.IsInteropActivationBlock(x.ChainID, x.NewLocalUnsafe.Time) {
183+
su.emitter.Emit(db.UnsafeActivationBlockReceivedEvent{
184+
ChainID: x.ChainID,
185+
Unsafe: x.NewLocalUnsafe,
186+
})
187+
// don't process events of the activation block
188+
return true
189+
}
179190
su.emitter.Emit(superevents.ChainProcessEvent{
180191
ChainID: x.ChainID,
181192
Target: x.NewLocalUnsafe.Number,
182193
})
183194
case superevents.LocalUnsafeUpdateEvent:
195+
// TODO: activation block should be directly promoted to cross-safe
196+
// because exec msgs aren't allowed in it. Where to best handle this?
184197
su.emitter.Emit(superevents.UpdateCrossUnsafeRequestEvent{
185198
ChainID: x.ChainID,
186199
})
187200
case superevents.CrossUnsafeUpdateEvent:
188201
su.emitter.Emit(superevents.UpdateCrossUnsafeRequestEvent{
189202
ChainID: x.ChainID,
190203
})
204+
case superevents.LocalDerivedEvent:
205+
if su.cfgSet.IsInteropActivationBlock(x.ChainID, x.NewLocalSafe.Derived.Timestamp) {
206+
su.emitter.Emit(db.SafeActivationBlockReceivedEvent{
207+
ChainID: x.ChainID,
208+
Safe: x.Derived,
209+
})
210+
}
211+
// TODO: activation block should be directly promoted to cross-safe
212+
// because exec msgs aren't allowed in it. Where to best handle this?
191213
case superevents.LocalSafeUpdateEvent:
192214
su.emitter.Emit(superevents.ChainProcessEvent{
193215
ChainID: x.ChainID,
@@ -314,7 +336,9 @@ func (su *SupervisorBackend) AttachSyncNode(ctx context.Context, src syncnode.Sy
314336
return nil, fmt.Errorf("chain %s is not part of the interop dependency set: %w", chainID, types.ErrUnknownChain)
315337
}
316338
// before attaching the sync source to the backend at all,
317-
// query the anchor point to initialize the database
339+
// query the anchor point to initialize the database, if the node has activated Interop already.
340+
// TODO: maybe move to end, and just emit QueryAnchorpointEvent instead, removing
341+
// [SupervisorBackend.QueryAnchorpoint].
318342
if err := su.QueryAnchorpoint(chainID, src); err != nil {
319343
return nil, fmt.Errorf("failed to query anchor point: %w", err)
320344
}
@@ -330,8 +354,11 @@ func (su *SupervisorBackend) AttachSyncNode(ctx context.Context, src syncnode.Sy
330354
}
331355

332356
func (su *SupervisorBackend) QueryAnchorpoint(chainID eth.ChainID, src syncnode.SyncNode) error {
333-
anchor, err := src.AnchorPoint(context.Background())
334-
if err != nil {
357+
anchor, err := src.AnchorPoint(su.sysContext)
358+
if errors.Is(err, types.ErrFuture) {
359+
su.logger.Info("Interop not activated yet while querying anchor point", "chainID", chainID)
360+
return nil
361+
} else if err != nil {
335362
return fmt.Errorf("failed to get anchor point: %w", err)
336363
}
337364
su.emitter.Emit(superevents.AnchorEvent{
@@ -522,7 +549,8 @@ func (su *SupervisorBackend) checkSafety(chainID eth.ChainID, blockID eth.BlockI
522549
}
523550

524551
func (su *SupervisorBackend) CheckAccessList(ctx context.Context, inboxEntries []common.Hash,
525-
minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error {
552+
minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor,
553+
) error {
526554
switch minSafety {
527555
case types.LocalUnsafe, types.CrossUnsafe, types.LocalSafe, types.CrossSafe, types.Finalized:
528556
// valid safety level

op-supervisor/supervisor/backend/cross/safe_frontier_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ func (m mockDependencySet) ChainIndexFromID(chain eth.ChainID) (types.ChainIndex
204204
return types.ChainIndex(v.Uint64() + 1000), nil
205205
}
206206

207+
func (m mockDependencySet) IsInterop(chainID eth.ChainID, timestamp uint64) bool{
208+
return true
209+
}
210+
207211
func (m mockDependencySet) Chains() []eth.ChainID {
208212
return nil
209213
}

op-supervisor/supervisor/backend/db/anchor.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ func (db *ChainsDB) initFromAnchor(id eth.ChainID, anchor types.DerivedBlockRefP
2727
}
2828
db.logger.Debug("initializing chain database from anchor point")
2929

30-
// Initialize the local and cross safe databases
31-
if err := db.maybeInitSafeDB(id, anchor); err != nil {
32-
db.logger.Warn("failed to initialize local and cross safe databases", "err", err)
30+
// Initialize the events database
31+
if err := db.maybeInitEventsDB(id, anchor.Derived); err != nil {
32+
db.logger.Warn("failed to initialize events database", "err", err)
3333
return
3434
}
3535

36-
// Initialize the events database
37-
if err := db.maybeInitEventsDB(id, anchor); err != nil {
38-
db.logger.Warn("failed to initialize events database", "err", err)
36+
// Initialize the local and cross safe databases
37+
if err := db.maybeInitSafeDB(id, anchor); err != nil {
38+
db.logger.Warn("failed to initialize local and cross safe databases", "err", err)
3939
return
4040
}
4141

@@ -75,12 +75,12 @@ func (db *ChainsDB) maybeInitSafeDB(id eth.ChainID, anchor types.DerivedBlockRef
7575
return nil
7676
}
7777

78-
func (db *ChainsDB) maybeInitEventsDB(id eth.ChainID, anchor types.DerivedBlockRefPair) error {
79-
logger := db.logger.New("chain", id, "derived", anchor.Derived, "source", anchor.Source)
80-
seal, _, _, err := db.OpenBlock(id, 0)
78+
func (db *ChainsDB) maybeInitEventsDB(id eth.ChainID, anchor eth.BlockRef) error {
79+
logger := db.logger.New("chain", id, "anchor", anchor)
80+
seal, err := db.FindSealedBlock(id, anchor.Number)
8181
if errors.Is(err, types.ErrFuture) {
8282
logger.Debug("initializing events database")
83-
err := db.initializedSealBlock(id, anchor.Derived)
83+
err := db.initializedSealBlock(id, anchor)
8484
if err != nil {
8585
return err
8686
}
@@ -89,7 +89,8 @@ func (db *ChainsDB) maybeInitEventsDB(id eth.ChainID, anchor types.DerivedBlockR
8989
return fmt.Errorf("failed to check if logDB is initialized: %w", err)
9090
} else {
9191
logger.Debug("Events database already initialized")
92-
if seal.Hash != anchor.Derived.Hash {
92+
// TODO: to allow for reorgs, we may want to handle this differently
93+
if seal.Hash != anchor.Hash {
9394
return fmt.Errorf("events database (%s) does not match anchor point (%s): %w",
9495
seal,
9596
anchor,

op-supervisor/supervisor/backend/db/db.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,42 @@ func (db *ChainsDB) AttachEmitter(em event.Emitter) {
163163

164164
func (db *ChainsDB) OnEvent(ev event.Event) bool {
165165
switch x := ev.(type) {
166+
case UnsafeActivationBlockReceivedEvent:
167+
if !db.isInitialized(x.ChainID) {
168+
db.logger.Info("Initializing logs DB from unsafe activation block",
169+
"chain", x.ChainID, "block", x.Unsafe)
170+
// Note that isInitialized is only true after full initialization,
171+
// not only the logs db.
172+
db.maybeInitEventsDB(x.ChainID, x.Unsafe)
173+
} else {
174+
db.logger.Warn("Received unsafe activation block on initialized DB",
175+
"chain", x.ChainID, "block", x.Unsafe)
176+
// TODO: handle reorg?
177+
}
178+
return false
179+
case SafeActivationBlockReceivedEvent:
180+
if !db.isInitialized(x.ChainID) {
181+
db.logger.Info("Initializing full DB from safe activation block",
182+
"chain", x.ChainID, "block", x.Safe)
183+
// Note that isInitialized is only true after full initialization,
184+
// not only the logs db.
185+
db.initFromAnchor(x.ChainID, x.Safe)
186+
} else {
187+
db.logger.Warn("Received unsafe activation block on initialized DB",
188+
"chain", x.ChainID, "block", x.Safe)
189+
// TODO: handle reorg?
190+
}
191+
return false
166192
case superevents.AnchorEvent:
193+
// TODO: Anchor should only be used if Interop is active at genesis.
167194
db.logger.Info("Received chain anchor information",
168195
"chain", x.ChainID, "derived", x.Anchor.Derived, "source", x.Anchor.Source)
169196
db.initFromAnchor(x.ChainID, x.Anchor)
170197
case superevents.LocalDerivedEvent:
198+
if !db.isInitialized(x.ChainID) {
199+
// this case is handled by SafeActivationBlockReceivedEvent
200+
return false
201+
}
171202
db.UpdateLocalSafe(x.ChainID, x.Derived.Source, x.Derived.Derived, x.NodeID)
172203
case superevents.FinalizedL1RequestEvent:
173204
db.onFinalizedL1(x.FinalizedL1)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package db
2+
3+
import (
4+
"github.com/ethereum-optimism/optimism/op-service/eth"
5+
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
6+
)
7+
8+
type UnsafeActivationBlockReceivedEvent struct {
9+
Unsafe eth.BlockRef
10+
ChainID eth.ChainID
11+
}
12+
13+
func (ev UnsafeActivationBlockReceivedEvent) String() string {
14+
return "unsafe-activation-block-received"
15+
}
16+
17+
type SafeActivationBlockReceivedEvent struct {
18+
Safe types.DerivedBlockRefPair
19+
ChainID eth.ChainID
20+
}
21+
22+
func (ev SafeActivationBlockReceivedEvent) String() string {
23+
return "safe-activation-block-received"
24+
}

op-supervisor/supervisor/backend/db/update.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ func (db *ChainsDB) UpdateLocalSafe(chain eth.ChainID, source eth.BlockRef, last
101101
}
102102

103103
func (db *ChainsDB) initializedUpdateLocalSafe(chain eth.ChainID, source eth.BlockRef, lastDerived eth.BlockRef, nodeId string) {
104+
// TODO: remove notion of anchor block
105+
// if first interop activation block is seen, it's set as cross-(un)safe block
106+
// pre-interop, don't emit UpdateLocalSafeFailedEvent
104107
logger := db.logger.New("chain", chain, "source", source, "lastDerived", lastDerived)
105108
localDB, ok := db.localDBs.Get(chain)
106109
if !ok {
@@ -114,7 +117,7 @@ func (db *ChainsDB) initializedUpdateLocalSafe(chain eth.ChainID, source eth.Blo
114117
return
115118
}
116119
if errors.Is(err, types.ErrDataCorruption) {
117-
logger.Warn("TODO", "err", err)
120+
logger.Error("DB coruption occurred", "err", err)
118121
return
119122
}
120123
logger.Warn("Failed to update local safe", "err", err)
@@ -226,11 +229,16 @@ func (db *ChainsDB) onFinalizedL1(finalized eth.BlockRef) {
226229
db.logger.Info("Updated finalized L1", "finalizedL1", finalized)
227230
db.finalizedL1.Unlock()
228231

232+
// TODO: There seems to be no consumer of this event?
229233
db.emitter.Emit(superevents.FinalizedL1UpdateEvent{
230234
FinalizedL1: finalized,
231235
})
232236
// whenever the L1 Finalized changes, the L2 Finalized may change, notify subscribers
233237
for _, chain := range db.depSet.Chains() {
238+
if !db.isInitialized(chain) {
239+
continue
240+
}
241+
234242
fin, err := db.Finalized(chain)
235243
if err != nil {
236244
db.logger.Warn("Unable to determine finalized L2 block", "chain", chain, "l1Finalized", finalized)

op-supervisor/supervisor/backend/depset/depset.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ type DependencySetSource interface {
1414
// DependencySet is an initialized dependency set, ready to answer queries
1515
// of what is and what is not part of the dependency set.
1616
type DependencySet interface {
17-
1817
// CanExecuteAt determines if an executing message is valid at all.
1918
// I.e. if the chain may be executing messages at the given timestamp.
2019
// This may return an error if the query temporarily cannot be answered.

op-supervisor/supervisor/backend/depset/depset_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ func testDependencySetSerialization(
7979
})
8080
require.NoError(t, err)
8181

82+
id900 := eth.ChainIDFromUInt64(900)
83+
8284
t.Run("DefaultExpiryWindow", func(t *testing.T) {
8385
data, err := marshal(depSet)
8486
require.NoError(t, err)
@@ -138,7 +140,6 @@ func testDependencySetSerialization(
138140
})
139141

140142
t.Run("chain index round trip", func(t *testing.T) {
141-
id900 := eth.ChainIDFromUInt64(900)
142143
idx, _ := depSet.ChainIndexFromID(id900)
143144
idBack, _ := depSet.ChainIDFromIndex(idx)
144145
require.Equal(t, id900, idBack)
@@ -152,6 +153,12 @@ func testDependencySetSerialization(
152153
require.False(t, depSet.HasChain(eth.ChainIDFromUInt64(902)))
153154
})
154155

156+
t.Run("IsInterop", func(t *testing.T) {
157+
require.True(t, depSet.IsInterop(id900, 42))
158+
require.False(t, depSet.IsInterop(id900, 41))
159+
require.False(t, depSet.IsInterop(eth.ChainIDFromUInt64(999), 1000))
160+
})
161+
155162
}
156163

157164
func testChainCapabilities(t *testing.T, result DependencySet) {

0 commit comments

Comments
 (0)