Skip to content

Commit 002dc4a

Browse files
op-supervisor,op-node: Handle non-genesis Interop (#16008)
* op-supervisor,op-node: Handle non-interop genesis activation * op-supervisor: fix OpenBlock error case * op-e2e: Skip tests that depend on multi-node setup Multi-node will be completely removed via #16174. Other tests will be fixed by switching to multi-supervisor setup (#16187). --------- Co-authored-by: protolambda <[email protected]>
1 parent 41036a4 commit 002dc4a

File tree

30 files changed

+886
-480
lines changed

30 files changed

+886
-480
lines changed

op-acceptance-tests/tests/interop/sync/redundant_interop/interop_sync_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
// TestUnsafeChainKnownToL2CL tests the below scenario:
1414
// supervisor cross-safe ahead of L2CL cross-safe, aka L2CL can "skip" forward to match safety of supervisor.
1515
func TestUnsafeChainKnownToL2CL(gt *testing.T) {
16+
gt.Skip() // TODO(#16187): Fix to use multi-supervisor (not multi-node) setup.
1617
t := devtest.SerialT(gt)
1718

1819
// Sequencer and verifier are connected via P2P, which makes their unsafe heads in sync.
@@ -84,6 +85,7 @@ func TestUnsafeChainKnownToL2CL(gt *testing.T) {
8485
// TestUnsafeChainUnknownToL2CL tests the below scenario:
8586
// supervisor unsafe ahead of L2CL unsafe, aka L2CL processes new blocks first.
8687
func TestUnsafeChainUnknownToL2CL(gt *testing.T) {
88+
gt.Skip() // TODO(#16187): Fix to use multi-supervisor (not multi-node) setup.
8789
t := devtest.SerialT(gt)
8890

8991
// Sequencer and verifier are connected via P2P, which makes their unsafe heads in sync.

op-e2e/actions/interop/interop_fork_test.go

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,27 +77,19 @@ func TestInteropUpgrade(gt *testing.T) {
7777
c.Sequencer.ActL2PipelineFull(t)
7878
}, dsl.WithFinalizedAdvancesTo(1)) // assert unsafe head does not move, but update finalized head
7979

80-
// Interop
80+
////////////////////////////
81+
// Unsafe Interop Upgrade (for the current chain in iteration)
82+
////////////////////////////
83+
8184
syncAsserter.RequireSeqSyncStatus(func() {
8285
// Build another L2 block so that Interop activates
8386
system.AddL2Block(c, dsl.WithL2BlocksUntilTimestamp(*c.Sequencer.RollupCfg.InteropTime))
8487
}, dsl.WithUnsafeAdvancesTo(2), dsl.WithCrossUnsafeAdvancesTo(2)) // assert unsafe and crossUnsafe advance by one
8588

86-
////////////////////////////
87-
// After Interop Upgrade (for the current chain in iteration)
88-
////////////////////////////
89-
9089
dsl.RequireUnsafeTimeOffset(t, c, 4) // interop should be enabled
91-
// Assert for the first time on supervisor heads
92-
dsl.RequireSupervisorChainHeads(t, svr, c, syncAsserter.PrevStatus.UnsafeL2.ID(), syncAsserter.PrevStatus.CrossUnsafeL2.ID(), syncAsserter.PrevStatus.LocalSafeL2.ID(), syncAsserter.PrevStatus.SafeL2.ID(), eth.BlockID{Hash: common.BytesToHash([]byte{}), Number: 0})
93-
94-
// TODO(15863): The initial state of the finalized head differs between supervisor
95-
// and sequencer. In an action test setup, the sequencer considers
96-
// genesis head as finalized from the start. The supervisor however sets
97-
// the finalized head to a nil value until it it set. Seems worth fixing.
98-
// The following will fail.
99-
// system.ActSyncSupernode(t, dsl.WithChains(c), dsl.WithFinalizedSignal(), dsl.WithLatestSignal(), dsl.WithRequireFinalizedAdvances())
100-
// syncAsserter.RequireSupChainHeadsBySyncStatus()
90+
91+
// Safe head hasn't moved yet.
92+
dsl.RequireSupervisorChainHeads(t, svr, c, syncAsserter.PrevStatus.UnsafeL2.ID(), syncAsserter.PrevStatus.CrossUnsafeL2.ID(), eth.BlockID{}, eth.BlockID{}, eth.BlockID{})
10193
}
10294

10395
// Settle both chains to DA again and have supervisor ingest
@@ -107,6 +99,9 @@ func TestInteropUpgrade(gt *testing.T) {
10799
dsl.RequireL1Heads(t, system, 3, 1)
108100

109101
// // TODO: Why doesn't the cross head promotion happen here?
102+
// Seb: I think because there's always one round of back and forth between supervisor and node required
103+
// per L1 block. The node processed until an ExhaustL1Event, then needs the supervisor
104+
// to give it the next L1 block.
110105
superchainSyncAsserter.RequireAllSeqSyncStatuses(func() {
111106
system.ActSyncSupernode(t, dsl.WithLatestSignal())
112107
}, dsl.WithMapChainAssertions(dsl.WithLocalSafeAdvancesTo(2)))
@@ -115,14 +110,21 @@ func TestInteropUpgrade(gt *testing.T) {
115110
system.ActSyncSupernode(t)
116111
}, dsl.WithMapChainAssertions(dsl.WithSafeAdvancesTo(2)))
117112

118-
// Note that finalized remains nil
113+
// TODO(15863): The initial state of the finalized head differs between supervisor
114+
// and sequencer. In an action test setup, the sequencer considers
115+
// genesis head as finalized from the start. The supervisor however sets
116+
// the finalized head to a nil value until it it set. Seems worth fixing.
117+
// The following will fail.
118+
// system.ActSyncSupernode(t, dsl.WithChains(c), dsl.WithFinalizedSignal(), dsl.WithLatestSignal(), dsl.WithRequireFinalizedAdvances())
119+
// syncAsserter.RequireSupChainHeadsBySyncStatus()
119120
for _, c := range chains {
120121
syncAsserter := superchainSyncAsserter.ChainAsserters[c.ChainID]
121-
dsl.RequireSupervisorChainHeads(t, svr, c, syncAsserter.PrevStatus.UnsafeL2.ID(), syncAsserter.PrevStatus.CrossUnsafeL2.ID(), syncAsserter.PrevStatus.LocalSafeL2.ID(), syncAsserter.PrevStatus.SafeL2.ID(), eth.BlockID{Hash: common.BytesToHash([]byte{}), Number: 0})
122+
dsl.RequireSupervisorChainHeads(t, svr, c, syncAsserter.PrevStatus.UnsafeL2.ID(), syncAsserter.PrevStatus.CrossUnsafeL2.ID(), syncAsserter.PrevStatus.LocalSafeL2.ID(), syncAsserter.PrevStatus.SafeL2.ID(), eth.BlockID{})
122123
}
123124

124125
// Verify proofs agree
125-
assertProgramOutputMatchesDerivationForBlockTimestamp(gt, system, system.Actors.ChainA.Sequencer.L2Unsafe().Time)
126+
// TODO(#16166): Fix non-genesis Interop activation proofs
127+
// assertProgramOutputMatchesDerivationForBlockTimestamp(gt, system, system.Actors.ChainA.Sequencer.L2Safe().Time)
126128

127129
superchainSyncAsserter.RequireAllSeqSyncStatuses(func() {
128130
// Advance L1 safe head and finalized head
@@ -140,9 +142,6 @@ func TestInteropUpgrade(gt *testing.T) {
140142
syncAsserter.RequireSupChainHeadsBySyncStatus()
141143
}
142144

143-
// Verify proofs agree on prev blocks
144-
assertProgramOutputMatchesDerivationForBlockTimestamp(gt, system, system.Actors.ChainA.Sequencer.L2Safe().Time)
145-
146145
for _, c := range chains {
147146
// Advance unsafe head again
148147
syncAsserter := superchainSyncAsserter.ChainAsserters[c.ChainID]

op-e2e/actions/interop/proofs_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ func TestInteropFaultProofs_ConsolidateValidCrossChainMessage(gt *testing.T) {
176176
}
177177

178178
func TestInteropFaultProofs_PreForkActivation(gt *testing.T) {
179+
// TODO(#16166): Fix non-genesis Interop activation proofs
180+
gt.Skip()
179181
t := helpers.NewDefaultTesting(gt)
180182
system := dsl.NewInteropDSL(t, dsl.SetInteropForkScheduledButInactive())
181183

op-e2e/interop/interop_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ func TestInteropBlockBuilding(t *testing.T) {
364364
}
365365

366366
func TestMultiNode(t *testing.T) {
367+
t.Skip() // TODO(#16174): Decide on future of multi-node support
367368
t.Parallel()
368369
test := func(t *testing.T, s2 SuperSystem) {
369370
supervisor := s2.SupervisorClient()

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func (ib *InteropAPI) InvalidateBlock(ctx context.Context, seal supervisortypes.
3939
return ib.backend.InvalidateBlock(ctx, seal)
4040
}
4141

42+
// TODO(#16140): remove
4243
func (ib *InteropAPI) AnchorPoint(ctx context.Context) (supervisortypes.DerivedBlockRefPair, error) {
4344
return ib.backend.AnchorPoint(ctx)
4445
}
@@ -47,6 +48,10 @@ func (ib *InteropAPI) Reset(ctx context.Context, lUnsafe, xUnsafe, lSafe, xSafe,
4748
return ib.backend.Reset(ctx, lUnsafe, xUnsafe, lSafe, xSafe, finalized)
4849
}
4950

51+
func (ib *InteropAPI) ResetPreInterop(ctx context.Context) error {
52+
return ib.backend.ResetPreInterop(ctx)
53+
}
54+
5055
func (ib *InteropAPI) FetchReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) {
5156
return ib.backend.FetchReceipts(ctx, blockHash)
5257
}

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(#16141): 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: 45 additions & 23 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,6 +179,17 @@ 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.IsInterop(x.ChainID, x.NewLocalUnsafe.Time) {
183+
su.logger.Warn("ignoring local unsafe received event for pre-interop block", "chainID", x.ChainID, "unsafe", x.NewLocalUnsafe)
184+
return false
185+
} else if su.cfgSet.IsInteropActivationBlock(x.ChainID, x.NewLocalUnsafe.Time) {
186+
su.emitter.Emit(superevents.UnsafeActivationBlockEvent{
187+
ChainID: x.ChainID,
188+
Unsafe: x.NewLocalUnsafe,
189+
})
190+
// don't process events of the activation block
191+
return true
192+
}
179193
su.emitter.Emit(superevents.ChainProcessEvent{
180194
ChainID: x.ChainID,
181195
Target: x.NewLocalUnsafe.Number,
@@ -188,6 +202,16 @@ func (su *SupervisorBackend) OnEvent(ev event.Event) bool {
188202
su.emitter.Emit(superevents.UpdateCrossUnsafeRequestEvent{
189203
ChainID: x.ChainID,
190204
})
205+
case superevents.LocalDerivedEvent:
206+
if !su.cfgSet.IsInterop(x.ChainID, x.Derived.Derived.Time) {
207+
su.logger.Warn("ignoring local derived event for pre-interop block", "chainID", x.ChainID, "derived", x.Derived.Derived)
208+
return false
209+
} else if su.cfgSet.IsInteropActivationBlock(x.ChainID, x.Derived.Derived.Time) {
210+
su.emitter.Emit(superevents.SafeActivationBlockEvent{
211+
ChainID: x.ChainID,
212+
Safe: x.Derived,
213+
})
214+
}
191215
case superevents.LocalSafeUpdateEvent:
192216
su.emitter.Emit(superevents.ChainProcessEvent{
193217
ChainID: x.ChainID,
@@ -299,6 +323,20 @@ func (su *SupervisorBackend) openChainDBs(chainID eth.ChainID) error {
299323

300324
su.chainDBs.AddCrossUnsafeTracker(chainID)
301325

326+
// If Interop is active at genesis, emit SafeActivationBlockEvent so that the DB
327+
// can initialize, if needed.
328+
genesis := su.cfgSet.Genesis(chainID)
329+
if su.cfgSet.IsInterop(chainID, genesis.L2.Timestamp) {
330+
su.emitter.Emit(superevents.SafeActivationBlockEvent{
331+
ChainID: chainID,
332+
Safe: types.DerivedBlockRefPair{
333+
// Initialization skips parent checks, so zero parents are ok.
334+
Source: genesis.L1.WithZeroParent(),
335+
Derived: genesis.L2.WithZeroParent(),
336+
},
337+
})
338+
}
339+
302340
return nil
303341
}
304342

@@ -314,11 +352,6 @@ func (su *SupervisorBackend) AttachSyncNode(ctx context.Context, src syncnode.Sy
314352
if !su.cfgSet.HasChain(chainID) {
315353
return nil, fmt.Errorf("chain %s is not part of the interop dependency set: %w", chainID, types.ErrUnknownChain)
316354
}
317-
// before attaching the sync source to the backend at all,
318-
// query the anchor point to initialize the database
319-
if err := su.QueryAnchorpoint(chainID, src); err != nil {
320-
return nil, fmt.Errorf("failed to query anchor point: %w", err)
321-
}
322355
err = su.AttachProcessorSource(chainID, src)
323356
if err != nil {
324357
return nil, fmt.Errorf("failed to attach sync source to processor: %w", err)
@@ -330,18 +363,6 @@ func (su *SupervisorBackend) AttachSyncNode(ctx context.Context, src syncnode.Sy
330363
return su.syncNodesController.AttachNodeController(chainID, src, noSubscribe)
331364
}
332365

333-
func (su *SupervisorBackend) QueryAnchorpoint(chainID eth.ChainID, src syncnode.SyncNode) error {
334-
anchor, err := src.AnchorPoint(context.Background())
335-
if err != nil {
336-
return fmt.Errorf("failed to get anchor point: %w", err)
337-
}
338-
su.emitter.Emit(superevents.AnchorEvent{
339-
ChainID: chainID,
340-
Anchor: anchor,
341-
})
342-
return nil
343-
}
344-
345366
func (su *SupervisorBackend) AttachProcessorSource(chainID eth.ChainID, src processors.Source) error {
346367
proc, ok := su.chainProcessors.Get(chainID)
347368
if !ok {
@@ -364,7 +385,7 @@ func (su *SupervisorBackend) attachL1RPC(ctx context.Context, l1RPCAddr string)
364385
su.logger.Info("attaching L1 RPC to L1 processor", "rpc", l1RPCAddr)
365386

366387
logger := su.logger.New("l1-rpc", l1RPCAddr)
367-
l1RPC, err := client.NewRPC(ctx, logger, l1RPCAddr)
388+
l1RPC, err := client.NewRPC(ctx, logger, l1RPCAddr, client.WithLazyDial())
368389
if err != nil {
369390
return fmt.Errorf("failed to setup L1 RPC: %w", err)
370391
}
@@ -523,7 +544,8 @@ func (su *SupervisorBackend) checkSafety(chainID eth.ChainID, blockID eth.BlockI
523544
}
524545

525546
func (su *SupervisorBackend) CheckAccessList(ctx context.Context, inboxEntries []common.Hash,
526-
minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error {
547+
minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor,
548+
) error {
527549
switch minSafety {
528550
case types.LocalUnsafe, types.CrossUnsafe, types.LocalSafe, types.CrossSafe, types.Finalized:
529551
// valid safety level
@@ -662,7 +684,7 @@ func (su *SupervisorBackend) FinalizedL1(ctx context.Context) (eth.BlockRef, err
662684
return v, nil
663685
}
664686

665-
func (su *SupervisorBackend) AnchorPoint(ctx context.Context, chainID eth.ChainID) (types.DerivedBlockSealPair, error) {
687+
func (su *SupervisorBackend) ActivationBlock(ctx context.Context, chainID eth.ChainID) (types.DerivedBlockSealPair, error) {
666688
return su.chainDBs.AnchorPoint(chainID)
667689
}
668690

0 commit comments

Comments
 (0)