From 672bcf9f292e306146bb52c2c98b0138e131741d Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 28 Feb 2024 12:12:04 +1000 Subject: [PATCH] op-node: Record genesis as being safe from L1 genesis --- op-e2e/actions/safedb_test.go | 14 +++++++++----- op-e2e/faultproofs/output_cannon_test.go | 17 ++--------------- op-node/rollup/derive/engine_queue.go | 14 ++++++++++++++ op-node/rollup/derive/engine_queue_test.go | 3 +++ 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/op-e2e/actions/safedb_test.go b/op-e2e/actions/safedb_test.go index 4948f3390544d..9fd570fa79f5b 100644 --- a/op-e2e/actions/safedb_test.go +++ b/op-e2e/actions/safedb_test.go @@ -59,9 +59,11 @@ func TestRecordSafeHeadUpdates(gt *testing.T) { require.Equal(t, eth.HeaderBlockID(l1Head), response.L1Block) require.Equal(t, verifier.L2Unsafe().ID(), response.SafeHead) - // Should get not found error before the L1 block because we have no earlier safe head recorded - _, err = verifier.RollupClient().SafeHeadAtL1Block(context.Background(), firstSafeHeadUpdateL1Block-1) - require.ErrorContains(t, err, safedb.ErrNotFound.Error()) + // Only genesis is safe at this point + response, err = verifier.RollupClient().SafeHeadAtL1Block(context.Background(), firstSafeHeadUpdateL1Block-1) + require.NoError(t, err) + require.Equal(t, eth.HeaderBlockID(miner.l1Chain.Genesis().Header()), response.L1Block) + require.Equal(t, sd.RollupCfg.Genesis.L2, response.SafeHead) // orphan the L1 block that included the batch tx, and build a new different L1 block miner.ActL1RewindToParent(t) @@ -78,9 +80,11 @@ func TestRecordSafeHeadUpdates(gt *testing.T) { require.NoError(t, err) require.Equal(t, verifier.L2Safe(), ref, "verifier engine matches rollup client") - // The safe head has been reorged so the record should have been deleted + // The safe head has been reorged so the record should have been deleted, leaving us back with just genesis safe response, err = verifier.RollupClient().SafeHeadAtL1Block(context.Background(), firstSafeHeadUpdateL1Block) - require.ErrorContainsf(t, err, safedb.ErrNotFound.Error(), "Expected error but got %v", response) + require.NoError(t, err) + require.Equal(t, eth.HeaderBlockID(miner.l1Chain.Genesis().Header()), response.L1Block) + require.Equal(t, sd.RollupCfg.Genesis.L2, response.SafeHead) // Now replay the batch tx in a new L1 block miner.ActL1StartBlock(12)(t) diff --git a/op-e2e/faultproofs/output_cannon_test.go b/op-e2e/faultproofs/output_cannon_test.go index b8f0d7ddec568..fe4917633e9a5 100644 --- a/op-e2e/faultproofs/output_cannon_test.go +++ b/op-e2e/faultproofs/output_cannon_test.go @@ -693,20 +693,10 @@ func TestInvalidateUnsafeProposal(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { op_e2e.InitParallel(t, op_e2e.UsesCannon) - sys, l1Client := startFaultDisputeSystem(t, withSequencerWindowSize(100000)) + sys, l1Client := startFaultDisputeSystem(t, withSequencerWindowSize(100000), withBatcherStopped()) t.Cleanup(sys.Close) - // Wait for the safe head to advance at least one block to init the safe head database - require.NoError(t, wait.ForSafeBlock(ctx, sys.RollupClient("sequencer"), 1)) - - // Now stop the batcher so the safe head doesn't advance any further - require.NoError(t, sys.BatchSubmitter.Stop(ctx)) - - // Wait for the unsafe head to advance to be sure it is beyond the safe head - require.NoError(t, wait.ForNextBlock(ctx, sys.NodeClient("sequencer"))) - blockNum, err := sys.NodeClient("sequencer").BlockNumber(ctx) - require.NoError(t, err) - + blockNum := uint64(1) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) // Root claim is _dishonest_ because the required data is not available on L1 game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", blockNum, disputegame.WithUnsafeProposal()) @@ -768,9 +758,6 @@ func TestInvalidateProposalForFutureBlock(t *testing.T) { sys, l1Client := startFaultDisputeSystem(t, withSequencerWindowSize(100000)) t.Cleanup(sys.Close) - // Wait for the safe head to advance at least one block to init the safe head database - require.NoError(t, wait.ForSafeBlock(ctx, sys.RollupClient("sequencer"), 1)) - farFutureBlockNum := uint64(10_000_000) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) // Root claim is _dishonest_ because the required data is not available on L1 diff --git a/op-node/rollup/derive/engine_queue.go b/op-node/rollup/derive/engine_queue.go index bf1d6a75e2a53..5518f1b78d121 100644 --- a/op-node/rollup/derive/engine_queue.go +++ b/op-node/rollup/derive/engine_queue.go @@ -705,6 +705,20 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System if err := eq.safeHeadNotifs.SafeHeadReset(safe); err != nil { return err } + if safe.Number == eq.cfg.Genesis.L2.Number && safe.Hash == eq.cfg.Genesis.L2.Hash { + // The rollup genesis block is always safe by definition. So if the pipeline resets this far back we know + // we will process all safe head updates and can record genesis as always safe from L1 genesis. + // Note that it is not safe to use cfg.Genesis.L1 here as it is the block immediately before the L2 genesis + // but the contracts may have been deployed earlier than that, allowing creating a dispute game + // with a L1 head prior to cfg.Genesis.L1 + l1Genesis, err := eq.l1Fetcher.L1BlockRefByNumber(ctx, 0) + if err != nil { + return fmt.Errorf("failed to retrieve L1 genesis: %w", err) + } + if err := eq.safeHeadNotifs.SafeHeadUpdated(safe, l1Genesis.ID()); err != nil { + return err + } + } eq.logSyncProgress("reset derivation work") return io.EOF } diff --git a/op-node/rollup/derive/engine_queue_test.go b/op-node/rollup/derive/engine_queue_test.go index 45bcc8ef558b5..aa4504eddc977 100644 --- a/op-node/rollup/derive/engine_queue_test.go +++ b/op-node/rollup/derive/engine_queue_test.go @@ -898,6 +898,7 @@ func TestBlockBuildingRace(t *testing.T) { l1F.ExpectL1BlockRefByNumber(refA.Number, refA, nil) l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil) l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil) + l1F.ExpectL1BlockRefByNumber(0, refA, nil) eng.ExpectSystemConfigByL2Hash(refA0.Hash, cfg.Genesis.SystemConfig, nil) @@ -1023,6 +1024,7 @@ func TestResetLoop(t *testing.T) { rng := rand.New(rand.NewSource(1234)) + l1Genesis := eth.L1BlockRef{Number: 0} refA := testutils.RandomBlockRef(rng) refA0 := eth.L2BlockRef{ Hash: testutils.RandomHash(rng), @@ -1080,6 +1082,7 @@ func TestResetLoop(t *testing.T) { eng.ExpectL2BlockRefByHash(refA1.Hash, refA1, nil) eng.ExpectL2BlockRefByHash(refA0.Hash, refA0, nil) eng.ExpectSystemConfigByL2Hash(refA0.Hash, cfg.Genesis.SystemConfig, nil) + l1F.ExpectL1BlockRefByNumber(0, l1Genesis, nil) l1F.ExpectL1BlockRefByNumber(refA.Number, refA, nil) l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil) l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil)