diff --git a/op-e2e/actions/helpers/l2_verifier.go b/op-e2e/actions/helpers/l2_verifier.go index c27583963346a..f5eb7872adb6e 100644 --- a/op-e2e/actions/helpers/l2_verifier.go +++ b/op-e2e/actions/helpers/l2_verifier.go @@ -173,6 +173,9 @@ func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, syncStatusTracker := status.NewStatusTracker(log, metrics) sys.Register("status", syncStatusTracker, opts) + // TODO(#17115): Refactor dependency cycles + ec.SetCrossUpdateHandler(syncStatusTracker) + stepDeriver := NewTestingStepSchedulingDeriver() stepDeriver.AttachEmitter(testActionEmitter) diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index 5f921dc6328f3..a3aeb9bc350df 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -191,6 +191,8 @@ func NewDriver( verifConfDepth := confdepth.NewConfDepth(driverCfg.VerifierConfDepth, statusTracker.L1Head, l1) ec := engine.NewEngineController(driverCtx, l2, log, metrics, cfg, syncCfg, sys.Register("engine-controller", nil)) + // TODO(#17115): Refactor dependency cycles + ec.SetCrossUpdateHandler(statusTracker) sys.Register("engine-reset", engine.NewEngineResetDeriver(driverCtx, log, cfg, l1, l2, syncCfg)) diff --git a/op-node/rollup/driver/state.go b/op-node/rollup/driver/state.go index bf2cd65eb141f..8bd4d9d6fdaa3 100644 --- a/op-node/rollup/driver/state.go +++ b/op-node/rollup/driver/state.go @@ -427,11 +427,6 @@ func (s *SyncDeriver) SyncStep() { // to generate new attributes, if no attributes are known already. s.Emitter.Emit(s.Ctx, engine.PendingSafeRequestEvent{}) - // If interop is configured, we have to run the engine events, - // to ensure cross-L2 safety is continuously verified against the interop-backend. - if s.Config.InteropTime != nil && !s.ManagedBySupervisor { - s.Emitter.Emit(s.Ctx, engine.CrossUpdateRequestEvent{}) - } } // ResetDerivationPipeline forces a reset of the derivation pipeline. diff --git a/op-node/rollup/engine/engine_controller.go b/op-node/rollup/engine/engine_controller.go index 078294e2d1118..bcfa8f3d696b1 100644 --- a/op-node/rollup/engine/engine_controller.go +++ b/op-node/rollup/engine/engine_controller.go @@ -50,6 +50,13 @@ type SyncDeriver interface { OnELSyncStarted() } +// CrossUpdateHandler handles both cross-unsafe and cross-safe L2 head changes. +// Nil check required because op-program omits this handler. +type CrossUpdateHandler interface { + OnCrossUnsafeUpdate(ctx context.Context, crossUnsafe eth.L2BlockRef, localUnsafe eth.L2BlockRef) + OnCrossSafeUpdate(ctx context.Context, crossSafe eth.L2BlockRef, localSafe eth.L2BlockRef) +} + type EngineController struct { engine ExecEngine // Underlying execution engine RPC log log.Logger @@ -102,6 +109,9 @@ type EngineController struct { // EngineController is first initialized and used to initialize SyncDeriver. // Embed SyncDeriver into EngineController after initializing SyncDeriver SyncDeriver SyncDeriver + + // Handler for cross-unsafe and cross-safe updates + crossUpdateHandler CrossUpdateHandler } func NewEngineController(ctx context.Context, engine ExecEngine, log log.Logger, m opmetrics.Metricer, @@ -224,6 +234,24 @@ func (e *EngineController) SetBackupUnsafeL2Head(r eth.L2BlockRef, triggerReorg e.needFCUCallForBackupUnsafeReorg = triggerReorg } +func (e *EngineController) SetCrossUpdateHandler(handler CrossUpdateHandler) { + e.crossUpdateHandler = handler +} + +func (e *EngineController) onUnsafeUpdate(ctx context.Context, crossUnsafe, localUnsafe eth.L2BlockRef) { + // Nil check required because op-program omits this handler. + if e.crossUpdateHandler != nil { + e.crossUpdateHandler.OnCrossUnsafeUpdate(ctx, crossUnsafe, localUnsafe) + } +} + +func (e *EngineController) onSafeUpdate(ctx context.Context, crossSafe, localSafe eth.L2BlockRef) { + // Nil check required because op-program omits this handler. + if e.crossUpdateHandler != nil { + e.crossUpdateHandler.OnCrossSafeUpdate(ctx, crossSafe, localSafe) + } +} + // logSyncProgressMaybe helps log forkchoice state-changes when applicable. // First, the pre-state is registered. // A callback is returned to then log the changes to the pre-state, if any. @@ -449,7 +477,7 @@ func (e *EngineController) InsertUnsafePayload(ctx context.Context, envelope *et e.emitter.Emit(ctx, UnsafeUpdateEvent{Ref: ref}) e.SetLocalSafeHead(ref) e.SetSafeHead(ref) - e.emitter.Emit(ctx, CrossSafeUpdateEvent{LocalSafe: ref, CrossSafe: ref}) + e.onSafeUpdate(ctx, ref, ref) e.SetFinalizedHead(ref) } logFn := e.logSyncProgressMaybe() @@ -671,10 +699,7 @@ func (d *EngineController) OnEvent(ctx context.Context, ev event.Event) bool { d.TryUpdateEngine(ctx) case PromoteCrossUnsafeEvent: d.SetCrossUnsafeHead(x.Ref) - d.emitter.Emit(ctx, CrossUnsafeUpdateEvent{ - CrossUnsafe: x.Ref, - LocalUnsafe: d.UnsafeL2Head(), - }) + d.onUnsafeUpdate(ctx, x.Ref, d.UnsafeL2Head()) case PendingSafeRequestEvent: d.emitter.Emit(ctx, PendingSafeUpdateEvent{ PendingSafe: d.PendingSafeL2Head(), @@ -691,17 +716,11 @@ func (d *EngineController) OnEvent(ctx context.Context, ev event.Event) bool { d.SetSafeHead(x.Ref) // Finalizer can pick up this safe cross-block now d.emitter.Emit(ctx, SafeDerivedEvent{Safe: x.Ref, Source: x.Source}) - d.emitter.Emit(ctx, CrossSafeUpdateEvent{ - CrossSafe: d.SafeL2Head(), - LocalSafe: d.LocalSafeL2Head(), - }) + d.onSafeUpdate(ctx, d.SafeL2Head(), d.LocalSafeL2Head()) if x.Ref.Number > d.crossUnsafeHead.Number { d.log.Debug("Cross Unsafe Head is stale, updating to match cross safe", "cross_unsafe", d.crossUnsafeHead, "cross_safe", x.Ref) d.SetCrossUnsafeHead(x.Ref) - d.emitter.Emit(ctx, CrossUnsafeUpdateEvent{ - CrossUnsafe: x.Ref, - LocalUnsafe: d.UnsafeL2Head(), - }) + d.onUnsafeUpdate(ctx, x.Ref, d.UnsafeL2Head()) } // Try to apply the forkchoice changes d.TryUpdateEngine(ctx) @@ -718,19 +737,6 @@ func (d *EngineController) OnEvent(ctx context.Context, ev event.Event) bool { d.emitter.Emit(ctx, FinalizedUpdateEvent(x)) // Try to apply the forkchoice changes d.TryUpdateEngine(ctx) - case CrossUpdateRequestEvent: - if x.CrossUnsafe { - d.emitter.Emit(ctx, CrossUnsafeUpdateEvent{ - CrossUnsafe: d.CrossUnsafeL2Head(), - LocalUnsafe: d.UnsafeL2Head(), - }) - } - if x.CrossSafe { - d.emitter.Emit(ctx, CrossSafeUpdateEvent{ - CrossSafe: d.SafeL2Head(), - LocalSafe: d.LocalSafeL2Head(), - }) - } case InteropInvalidateBlockEvent: d.emitter.Emit(ctx, BuildStartEvent{Attributes: x.Attributes}) case BuildStartEvent: diff --git a/op-node/rollup/engine/events.go b/op-node/rollup/engine/events.go index f84c53c55803c..1bac262ce70b1 100644 --- a/op-node/rollup/engine/events.go +++ b/op-node/rollup/engine/events.go @@ -47,16 +47,6 @@ func (ev PromoteCrossUnsafeEvent) String() string { return "promote-cross-unsafe" } -// CrossUnsafeUpdateEvent signals that the given block is now considered cross-unsafe. -type CrossUnsafeUpdateEvent struct { - CrossUnsafe eth.L2BlockRef - LocalUnsafe eth.L2BlockRef -} - -func (ev CrossUnsafeUpdateEvent) String() string { - return "cross-unsafe-update" -} - type PendingSafeUpdateEvent struct { PendingSafe eth.L2BlockRef Unsafe eth.L2BlockRef // tip, added to the signal, to determine if there are existing blocks to consolidate @@ -66,15 +56,6 @@ func (ev PendingSafeUpdateEvent) String() string { return "pending-safe-update" } -type CrossSafeUpdateEvent struct { - CrossSafe eth.L2BlockRef - LocalSafe eth.L2BlockRef -} - -func (ev CrossSafeUpdateEvent) String() string { - return "cross-safe-update" -} - // LocalSafeUpdateEvent signals that a block is now considered to be local-safe. type LocalSafeUpdateEvent struct { Ref eth.L2BlockRef @@ -151,16 +132,6 @@ func (ev FinalizedUpdateEvent) String() string { return "finalized-update" } -// CrossUpdateRequestEvent triggers update events to be emitted, repeating the current state. -type CrossUpdateRequestEvent struct { - CrossUnsafe bool - CrossSafe bool -} - -func (ev CrossUpdateRequestEvent) String() string { - return "cross-update-request" -} - // InteropInvalidateBlockEvent is emitted when a block needs to be invalidated, and a replacement is needed. type InteropInvalidateBlockEvent struct { Invalidated eth.BlockRef diff --git a/op-node/rollup/status/status.go b/op-node/rollup/status/status.go index f0f95f98e9398..b6419f26f2c6b 100644 --- a/op-node/rollup/status/status.go +++ b/op-node/rollup/status/status.go @@ -14,6 +14,9 @@ import ( "github.com/ethereum-optimism/optimism/op-service/event" ) +// Compile-time interface compliance check +var _ engine.CrossUpdateHandler = (*StatusTracker)(nil) + type Metrics interface { RecordL1ReorgDepth(d uint64) RecordL1Ref(name string, ref eth.L1BlockRef) @@ -60,17 +63,9 @@ func (st *StatusTracker) OnEvent(ctx context.Context, ev event.Event) bool { case engine.PendingSafeUpdateEvent: st.data.UnsafeL2 = x.Unsafe st.data.PendingSafeL2 = x.PendingSafe - case engine.CrossUnsafeUpdateEvent: - st.log.Debug("Cross unsafe head updated", "cross_unsafe", x.CrossUnsafe, "local_unsafe", x.LocalUnsafe) - st.data.CrossUnsafeL2 = x.CrossUnsafe - st.data.UnsafeL2 = x.LocalUnsafe case engine.LocalSafeUpdateEvent: st.log.Debug("Local safe head updated", "local_safe", x.Ref) st.data.LocalSafeL2 = x.Ref - case engine.CrossSafeUpdateEvent: - st.log.Debug("Cross safe head updated", "cross_safe", x.CrossSafe, "local_safe", x.LocalSafe) - st.data.SafeL2 = x.CrossSafe - st.data.LocalSafeL2 = x.LocalSafe case derive.DeriverL1StatusEvent: st.data.CurrentL1 = x.Origin case rollup.ResetEvent: @@ -151,3 +146,25 @@ func (st *StatusTracker) SyncStatus() *eth.SyncStatus { func (st *StatusTracker) L1Head() eth.L1BlockRef { return st.SyncStatus().HeadL1 } + +func (st *StatusTracker) OnCrossUnsafeUpdate(ctx context.Context, crossUnsafe eth.L2BlockRef, localUnsafe eth.L2BlockRef) { + st.mu.Lock() + defer st.mu.Unlock() + + st.log.Debug("Cross unsafe head updated", "cross_unsafe", crossUnsafe, "local_unsafe", localUnsafe) + st.data.CrossUnsafeL2 = crossUnsafe + st.data.UnsafeL2 = localUnsafe + + st.UpdateSyncStatus() +} + +func (st *StatusTracker) OnCrossSafeUpdate(ctx context.Context, crossSafe eth.L2BlockRef, localSafe eth.L2BlockRef) { + st.mu.Lock() + defer st.mu.Unlock() + + st.log.Debug("Cross safe head updated", "cross_safe", crossSafe, "local_safe", localSafe) + st.data.SafeL2 = crossSafe + st.data.LocalSafeL2 = localSafe + + st.UpdateSyncStatus() +}