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
10 changes: 2 additions & 8 deletions op-e2e/actions/supernode/rewind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ func RewindSafeHeadBackward(gt *testing.T) {
}

// RewindFinalizedHeadBackward tests rewinding to a target behind the current finalized head.
// All heads (unsafe, safe, finalized) should move backward.
func RewindFinalizedHeadBackward(gt *testing.T) {
env := setupRewindTest(gt)

Expand Down Expand Up @@ -265,12 +264,7 @@ func RewindFinalizedHeadBackward(gt *testing.T) {
rewindTarget := finalizedBefore.Number / 2
require.Greater(gt, rewindTarget, uint64(0), "rewind target should be past genesis")
require.Less(gt, rewindTarget, finalizedBefore.Number, "rewind target should be behind finalized")
unsafeAfter, safeAfter, finalizedAfter := env.rewindToBlock(rewindTarget)

// Verify: all heads moved backward to target
require.Equal(gt, rewindTarget, unsafeAfter.Number, "unsafe should be at rewind target")
require.Equal(gt, rewindTarget, safeAfter.Number, "safe should have moved backward to target")
require.Equal(gt, rewindTarget, finalizedAfter.Number, "finalized should have moved backward to target")
require.Less(gt, safeAfter.Number, safeBefore.Number, "safe should have decreased")
require.Less(gt, finalizedAfter.Number, finalizedBefore.Number, "finalized should have decreased")
err := env.ec.RewindToTimestamp(context.Background(), env.timestampForBlock(rewindTarget))
require.ErrorIs(gt, err, engine_controller.ErrRewindOverFinalizedHead)
}
3 changes: 2 additions & 1 deletion op-supernode/supernode/chain_container/chain_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,8 @@ func isCriticalRewindError(err error) bool {
return errors.Is(err, engine_controller.ErrNoEngineClient) ||
errors.Is(err, engine_controller.ErrNoRollupConfig) ||
errors.Is(err, engine_controller.ErrRewindComputeTargetsFailed) ||
errors.Is(err, engine_controller.ErrRewindTimestampToBlockConversion)
errors.Is(err, engine_controller.ErrRewindTimestampToBlockConversion) ||
errors.Is(err, engine_controller.ErrRewindOverFinalizedHead)
}

func (c *simpleChainContainer) RewindEngine(ctx context.Context, timestamp uint64) error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var (
ErrRewindFCURejected = errors.New("forkchoice update rejected by engine")
ErrRewindTimestampToBlockConversion = errors.New("failed to convert timestamp to block number")
ErrRewindPayloadNotFound = errors.New("failed to get payload for block")
ErrRewindOverFinalizedHead = errors.New("cannot rewind over finalized head")
)

// RewindToTimestamp rewinds the L2 execution layer to the block at or before the given timestamp.
Expand Down Expand Up @@ -101,6 +102,10 @@ func (e *simpleEngineController) computeRewindTargets(ctx context.Context, targe
return eth.L2BlockRef{}, eth.L2BlockRef{}, fmt.Errorf("failed to get current finalized block: %w", err)
}

if targetBlock.Number < currentFinalized.Number {
return eth.L2BlockRef{}, eth.L2BlockRef{}, ErrRewindOverFinalizedHead
}

return earliest(currentSafe, targetBlock), earliest(currentFinalized, targetBlock), nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ func TestEngineController_RewindToTimestamp(t *testing.T) {

incorrectUnsafe, incorrectSafe, incorrectFinalized bool

targetBeforeGenesis bool
targetBeforeGenesis bool
targetBeforeFinalized bool
}

testCases := []testCase{
Expand Down Expand Up @@ -107,6 +108,11 @@ func TestEngineController_RewindToTimestamp(t *testing.T) {
targetBeforeGenesis: true,
expectedError: ErrRewindTimestampToBlockConversion,
},
{
name: "target before finalized",
targetBeforeFinalized: true,
expectedError: ErrRewindOverFinalizedHead,
},
}

// Setup: chain is at block 10, we want to rewind to block 5
Expand Down Expand Up @@ -137,28 +143,28 @@ func TestEngineController_RewindToTimestamp(t *testing.T) {
// Initial state before rewind
refsByLabel: map[eth.BlockLabel]eth.L2BlockRef{
eth.Safe: {Number: 10, Hash: common.Hash{0x0a}},
eth.Finalized: {Number: 8, Hash: common.Hash{0x08}},
eth.Finalized: {Number: 2, Hash: common.Hash{0x08}},
},
// State after FCU completes - verification reads these values
refsByLabelAfterFCU: map[eth.BlockLabel]eth.L2BlockRef{
eth.Unsafe: targetRef,
eth.Safe: targetRef, // clamped to target (min of 10 and 5)
eth.Finalized: targetRef, // clamped to target (min of 8 and 5)
eth.Safe: targetRef, // clamped to target (min of 10 and 5)
eth.Finalized: {Number: 2, Hash: common.Hash{0x08}}, // clamped to finalized head (min of 2 and 5)
},
payloadsByNumber: map[uint64]*eth.ExecutionPayloadEnvelope{
targetBlockNum: &payloadEnvelope,
},
}
}
rollupConfig := rollup.Config{
Genesis: rollup.Genesis{L2: eth.BlockID{Number: 0}, L2Time: genesisTime},
BlockTime: 2,
L2ChainID: big.NewInt(420),
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// First, get a "good mock" which would pass the test with no error:
rollupConfig := rollup.Config{
Genesis: rollup.Genesis{L2: eth.BlockID{Number: 0}, L2Time: genesisTime},
BlockTime: 2,
L2ChainID: big.NewInt(420),
}
l2 := createMockL2()

// Next, apply the sabotage(s):
Expand Down Expand Up @@ -189,6 +195,9 @@ func TestEngineController_RewindToTimestamp(t *testing.T) {
if tc.targetBeforeGenesis {
rollupConfig.Genesis = rollup.Genesis{L2Time: 2000}
}
if tc.targetBeforeFinalized {
l2.refsByLabel[eth.Finalized] = eth.L2BlockRef{Number: targetBlockNum + 1, Hash: common.Hash{0xff}}
}

// Make a "good" engine controller, using a potentially sabotaged mock L2
ec := &simpleEngineController{l2: &l2, rollup: &rollupConfig, log: testlog.Logger(t, log.LvlDebug)}
Expand Down