Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

52 changes: 0 additions & 52 deletions op-acceptance-tests/tests/supernode/interop/backfill/testutil.go

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Package startup_resync contains acceptance tests for the op-supernode
// interop startup rework's cold-start resync path: stopping the supernode,
// deleting its on-disk data dir, and starting a fresh supernode against the
// same chain containers and virtual nodes.
package startup_resync

import (
"testing"
"time"

"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/dsl"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

const (
l2BlockTime = uint64(1)
backfillDepth = 3 * time.Second
preRestartFinalized = uint64(5)
)

// TestSupernodeResyncResumesAtActivation_PostActivation drives a full
// supernode data-dir wipe after the chain has crossed activation, and
// asserts that cross-safe keeps advancing post-restart and that the
// cold-start backfill restored history into the logs DB.
func TestSupernodeResyncResumesAtActivation_PostActivation(gt *testing.T) {
t := devtest.SerialT(gt)
sys := presets.NewTwoL2SupernodeInterop(t, 0,
presets.WithUniformL2BlockTimes(l2BlockTime),
presets.WithInteropLogBackfillDepth(backfillDepth),
)

sys.Supernode.AwaitBackfillCompleted()

// Setup: let L2 finalized advance several blocks on both chains. On
// restart, op-node may drop back as part of its safe start process,
// but won't go past the finalized head. With finalized well past
// genesis the post-restart cold-start backfill has a real window to
// populate, instead of collapsing to empty against a re-recorded
// genesis SafeDB entry.
dsl.CheckAll(t,
sys.L2ACL.AdvancedFn(types.Finalized, preRestartFinalized, 180),
sys.L2BCL.AdvancedFn(types.Finalized, preRestartFinalized, 180),
)

sys.Supernode.RestartWithFreshDataDir()
sys.Supernode.AwaitBackfillCompleted()

dsl.CheckAll(t,
sys.L2ACL.AdvancedFn(types.CrossSafe, 1, 60),
sys.L2BCL.AdvancedFn(types.CrossSafe, 1, 60),
)

// Verify the cold-start backfill repopulated the logs DB.
sys.Supernode.AssertBackfillCovers(backfillDepth, l2BlockTime,
sys.L2A.ChainID(), sys.L2B.ChainID())
}

// TestSupernodeResyncSchedulesAtActivation_PreActivation drives a full
// supernode data-dir wipe while interop is scheduled but not yet active,
// and asserts that cold-start init parks the verifier at the (future)
// activation timestamp while cross-safe keeps advancing on both chains.
func TestSupernodeResyncSchedulesAtActivation_PreActivation(gt *testing.T) {
t := devtest.SerialT(gt)
// 60-minute delay: ensures the chain never approaches activation during
// the test, so we always exercise the genuine pre-activation cold-start
// path regardless of CI scheduling variance.
sys := presets.NewTwoL2SupernodeInterop(t, 60*60,
presets.WithUniformL2BlockTimes(l2BlockTime),
presets.WithInteropLogBackfillDepth(backfillDepth),
)

sys.Supernode.AwaitBackfillCompleted()
activation := sys.Supernode.ActivationTimestamp()

// Setup: let local-safe accumulate enough that op-node's SafeDB has
// entries to serve to the post-restart cold-start init.
dsl.CheckAll(t,
sys.L2ACL.AdvancedFn(types.LocalSafe, 2, 30),
sys.L2BCL.AdvancedFn(types.LocalSafe, 2, 30),
)

sys.Supernode.RestartWithFreshDataDir()
sys.Supernode.AwaitVerificationStartsAt(activation)

dsl.CheckAll(t,
sys.L2ACL.AdvancedFn(types.CrossSafe, 1, 60),
sys.L2BCL.AdvancedFn(types.CrossSafe, 1, 60),
)
}
58 changes: 43 additions & 15 deletions op-devstack/dsl/supernode.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
type Supernode struct {
commonImpl
inner stack.Supernode
testControl stack.InteropTestControl
testControl stack.SupernodeTestControl
}

// NewSupernode creates a new Supernode DSL wrapper
Expand All @@ -29,7 +29,7 @@ func NewSupernode(inner stack.Supernode) *Supernode {

// NewSupernodeWithTestControl creates a new Supernode DSL wrapper with test control support.
// The testControl parameter can be nil if no test control is needed.
func NewSupernodeWithTestControl(inner stack.Supernode, testControl stack.InteropTestControl) *Supernode {
func NewSupernodeWithTestControl(inner stack.Supernode, testControl stack.SupernodeTestControl) *Supernode {
return &Supernode{
commonImpl: commonFromT(inner.T()),
inner: inner,
Expand Down Expand Up @@ -129,19 +129,16 @@ func (s *Supernode) ResumeInterop() {
s.interopActivity().Resume()
}

// RestartInterop stops the running interop activity, optionally wipes its
// on-disk logs DBs, and launches a fresh instance against the still-running
// supernode. The HTTP server, chain containers, virtual nodes, and all other
// activities keep running across the restart. Setting wipeLogsDBs=true forces
// the fresh activity to reconstruct its database via log backfill from the
// virtual nodes, making this the primary primitive for exercising backfill
// in tests.
// Requires the Supernode to be created with NewSupernodeWithTestControl.
func (s *Supernode) RestartInterop(wipeLogsDBs bool) {
s.require.NotNil(s.testControl, "RestartInterop requires test control; use NewSupernodeWithTestControl")
s.log.Info("restarting interop activity", "wipeLogsDBs", wipeLogsDBs)
err := s.testControl.RestartInteropActivity(wipeLogsDBs)
s.require.NoError(err, "failed to restart interop activity")
// RestartWithFreshDataDir stops the supernode, deletes its on-disk data
// directory in full, and starts a fresh supernode against the same chain
// containers, virtual nodes, and externally-visible RPC address.
// Requires NewSupernodeWithTestControl.
func (s *Supernode) RestartWithFreshDataDir() {
s.require.NotNil(s.testControl,
"RestartWithFreshDataDir requires test control; use NewSupernodeWithTestControl")
s.log.Info("restarting supernode with fresh data dir")
err := s.testControl.RestartWithFreshDataDir()
s.require.NoError(err, "failed to restart supernode with fresh data dir")
}

// BackfillAttempts returns the number of log-backfill attempts since the
Expand Down Expand Up @@ -178,6 +175,37 @@ func (s *Supernode) AwaitBackfillCompleted() {
s.require.NoError(err, "backfill did not complete in time")
}

// ActivationTimestamp returns the configured interop activation timestamp.
// Requires NewSupernodeWithTestControl.
func (s *Supernode) ActivationTimestamp() uint64 {
return s.interopActivity().ActivationTimestamp()
}

// VerificationStartTimestamp returns the L2 timestamp the current interop
// activity began verifying at. Returns 0 before cold-start init completes.
// Requires NewSupernodeWithTestControl.
func (s *Supernode) VerificationStartTimestamp() uint64 {
return s.interopActivity().VerificationStartTimestamp()
}

// AwaitVerificationStartsAt blocks until cold-start init completes, then
// asserts VerificationStartTimestamp equals expected.
// Requires NewSupernodeWithTestControl.
func (s *Supernode) AwaitVerificationStartsAt(expected uint64) {
ia := s.interopActivity()
ctx, cancel := context.WithTimeout(s.ctx, 3*DefaultTimeout)
defer cancel()
err := wait.For(ctx, 500*time.Millisecond, func() (bool, error) {
return ia.BackfillCompleted(), nil
})
s.require.NoError(err, "cold-start initialization did not complete in time")
actual := ia.VerificationStartTimestamp()
s.require.Equalf(expected, actual,
"verificationStartTimestamp mismatch after cold-start init: expected %d, got %d",
expected, actual)
s.log.Info("verification start timestamp confirmed", "expected", expected, "actual", actual)
}

// AssertBackfillCovers verifies, for each supplied chain, that the interop
// logs DB contains blocks spanning from a first-seal at or near the expected
// T_lo all the way to a latest-seal at or near the safe tip. Specifically it
Expand Down
6 changes: 6 additions & 0 deletions op-devstack/presets/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ func WithL2BlockTimes(blockTimes map[eth.ChainID]uint64) Option {
return WithDeployerOptions(sysgo.WithL2BlockTimes(blockTimes))
}

// WithUniformL2BlockTimes configures the same L2 block time (in seconds) on
// every configured L2 chain via the deployer.
func WithUniformL2BlockTimes(seconds uint64) Option {
return WithDeployerOptions(sysgo.WithUniformL2BlockTimes(seconds))
}

// WithInteropLogBackfillDepth configures the supernode to pre-ingest
// initiating-message logs backward from the tip by the given duration at
// startup. Zero disables backfill (the default).
Expand Down
26 changes: 10 additions & 16 deletions op-devstack/stack/supernode.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,17 @@ type Supernode interface {
QueryAPI() apis.SupernodeQueryAPI
}

// InteropTestControl is the narrow integration-test surface on a running
// supernode. Tests get direct access to the interop activity via
// InteropActivity; see op-supernode/supernode/activity/interop for the
// methods available on the returned pointer (PauseAt, Resume,
// BackfillAttempts, BackfillCompleted, ActivationTimestamp,
// VerificationStartTimestamp, FirstVerifiableTimestamp, FirstSealedBlock,
// LatestSealedBlock, ...).
type InteropTestControl interface {
// SupernodeTestControl is the integration-test surface on a running
// supernode. See op-supernode/supernode/activity/interop for the methods
// available on the InteropActivity pointer.
type SupernodeTestControl interface {
// InteropActivity returns the current interop activity, or nil if the
// supernode is not running or interop is not configured. Callers must
// not cache the pointer across RestartInteropActivity, which swaps the
// activity for a fresh instance.
// supernode is stopped or interop is not configured. Do not cache the
// pointer across RestartWithFreshDataDir.
InteropActivity() *interop.Interop

// RestartInteropActivity stops the running interop activity, optionally
// wipes its on-disk logs DBs, and launches a fresh instance against the
// still-running supernode (HTTP server, chain containers, and all other
// activities remain up).
RestartInteropActivity(wipeLogsDBs bool) error
// RestartWithFreshDataDir stops the supernode, deletes its on-disk
// data directory, and starts a fresh supernode against the same chain
// containers, virtual nodes, and externally-visible RPC address.
RestartWithFreshDataDir() error
}
10 changes: 10 additions & 0 deletions op-devstack/sysgo/deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,16 @@ func WithL2BlockTimes(blockTimes map[eth.ChainID]uint64) DeployerOption {
}
}

// WithUniformL2BlockTimes sets the same L2 block time (in seconds) on every
// configured L2 chain.
func WithUniformL2BlockTimes(seconds uint64) DeployerOption {
return func(_ devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) {
for _, l2Cfg := range builder.L2s() {
l2Cfg.WithBlockTime(seconds)
}
}
}

// WithFinalizationPeriodSeconds overrides the number of L1 blocks in a sequencing window, applied to all L2s.
func WithFinalizationPeriodSeconds(n uint64) DeployerOption {
return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) {
Expand Down
Loading