11package altda
22
33import (
4+ "log/slog"
45 "math/big"
56 "math/rand"
67 "testing"
@@ -49,6 +50,12 @@ type L2AltDA struct {
4950
5051type AltDAParam func (p * e2eutils.TestParams )
5152
53+ func WithLogLevel (level slog.Level ) AltDAParam {
54+ return func (p * e2eutils.TestParams ) {
55+ p .LogLevel = level
56+ }
57+ }
58+
5259func NewL2AltDA (t helpers.Testing , params ... AltDAParam ) * L2AltDA {
5360 p := & e2eutils.TestParams {
5461 MaxSequencerDrift : 40 ,
@@ -57,11 +64,12 @@ func NewL2AltDA(t helpers.Testing, params ...AltDAParam) *L2AltDA {
5764 L1BlockTime : 12 ,
5865 UseAltDA : true ,
5966 AllocType : config .AllocTypeAltDA ,
67+ LogLevel : log .LevelDebug ,
6068 }
6169 for _ , apply := range params {
6270 apply (p )
6371 }
64- log := testlog .Logger (t , log . LvlDebug )
72+ log := testlog .Logger (t , p . LogLevel )
6573
6674 dp := e2eutils .MakeDeployParams (t , p )
6775 sd := e2eutils .Setup (t , dp , helpers .DefaultAlloc )
@@ -75,14 +83,13 @@ func NewL2AltDA(t helpers.Testing, params ...AltDAParam) *L2AltDA {
7583 engine := helpers .NewL2Engine (t , log , sd .L2Cfg , jwtPath )
7684 engCl := engine .EngineClient (t , sd .RollupCfg )
7785
78- storage := & altda.DAErrFaker {Client : altda .NewMockDAClient (log )}
79-
8086 l1F , err := sources .NewL1Client (miner .RPCClient (), log , nil , sources .L1ClientDefaultConfig (sd .RollupCfg , false , sources .RPCKindBasic ))
8187 require .NoError (t , err )
8288
8389 altDACfg , err := sd .RollupCfg .GetOPAltDAConfig ()
8490 require .NoError (t , err )
8591
92+ storage := & altda.DAErrFaker {Client : altda .NewMockDAClient (log )}
8693 daMgr := altda .NewAltDAWithStorage (log , altDACfg , storage , & altda.NoopMetrics {})
8794
8895 sequencer := helpers .NewL2Sequencer (t , log , l1F , miner .BlobStore (), daMgr , engCl , sd .RollupCfg , 0 )
@@ -177,6 +184,34 @@ func (a *L2AltDA) ActNewL2Tx(t helpers.Testing) {
177184 a .lastCommBn = a .miner .L1Chain ().CurrentBlock ().Number .Uint64 ()
178185}
179186
187+ // ActNewL2TxFinalized sends a new L2 transaction, submits a batch containing it to L1
188+ // and finalizes the L1 and L2 chains (including advancing enough to clear the altda challenge window).
189+ //
190+ // TODO: understand why (notation is l1unsafe/l1safe/l1finalized-l2unsafe/l2safe/l2finalized):
191+ // - the first call advances heads by (0/0/17-71/71/1)
192+ // - second call advances by 0/0/17-204/204/82,
193+ // - but all subsequent calls advance status by exactly 0/0/17-204/204/204.
194+ //
195+ // 17 makes sense because challengeWindow=16 and we create 1 extra block before that,
196+ // and 204 L2blocks = 17 L1blocks * 12 L2blocks/L1block (L1blocktime=12s, L2blocktime=1s)
197+ func (a * L2AltDA ) ActNewL2TxFinalized (t helpers.Testing ) {
198+ // Include a new l2 batcher transaction, submitting an input commitment to the l1.
199+ a .ActNewL2Tx (t )
200+ // Create ChallengeWindow empty blocks so the above batcher blocks can finalize (can't be challenged anymore)
201+ a .ActL1Blocks (t , a .altDACfg .ChallengeWindow )
202+ // Finalize the L1 chain and the L2 chain (by draining all events and running through derivation pipeline)
203+ // TODO: understand why we need to drain the pipeline before AND after actL1Finalized
204+ a .sequencer .ActL2PipelineFull (t )
205+ a .ActL1Finalized (t )
206+ a .sequencer .ActL2PipelineFull (t )
207+
208+ // Uncomment the below code to observe the behavior described in the TODO above
209+ // syncStatus := a.sequencer.SyncStatus()
210+ // a.log.Info("Sync status after ActNewL2TxFinalized",
211+ // "unsafeL1", syncStatus.HeadL1.Number, "safeL1", syncStatus.SafeL1.Number, "finalizedL1", syncStatus.FinalizedL1.Number,
212+ // "unsafeL2", syncStatus.UnsafeL2.Number, "safeL2", syncStatus.SafeL2.Number, "finalizedL2", syncStatus.FinalizedL2.Number)
213+ }
214+
180215func (a * L2AltDA ) ActDeleteLastInput (t helpers.Testing ) {
181216 require .NoError (t , a .storage .Client .DeleteData (a .lastComm ))
182217}
@@ -363,7 +398,7 @@ func TestAltDA_ChallengeResolved(gt *testing.T) {
363398}
364399
365400// DA storage service goes offline while sequencer keeps making blocks. When storage comes back online, it should be able to catch up.
366- func TestAltDA_StorageError (gt * testing.T ) {
401+ func TestAltDA_StorageGetError (gt * testing.T ) {
367402 t := helpers .NewDefaultTesting (gt )
368403 harness := NewL2AltDA (t )
369404
@@ -528,19 +563,20 @@ func TestAltDA_Finalization(gt *testing.T) {
528563 t := helpers .NewDefaultTesting (gt )
529564 a := NewL2AltDA (t )
530565
531- // build L1 block #1
566+ // Notation everywhere below is l1unsafe/l1safe/l1finalized-l2unsafe/l2safe/l2finalized
567+ // build L1 block #1: 0/0/0-0/0/0 -> 1/1/0-0/0/0
532568 a .ActL1Blocks (t , 1 )
533569 a .miner .ActL1SafeNext (t )
534570
535- // Fill with l2 blocks up to the L1 head
571+ // Fill with l2 blocks up to the L1 head: 1/1/0:0/0/0 -> 1/1/0:1/1/0
536572 a .sequencer .ActL1HeadSignal (t )
537573 a .sequencer .ActBuildToL1Head (t )
538574
539575 a .sequencer .ActL2PipelineFull (t )
540576 a .sequencer .ActL1SafeSignal (t )
541577 require .Equal (t , uint64 (1 ), a .sequencer .SyncStatus ().SafeL1 .Number )
542578
543- // add L1 block #2
579+ // add L1 block #2: 1/1/0:1/1/0 -> 2/2/1:2/1/0
544580 a .ActL1Blocks (t , 1 )
545581 a .miner .ActL1SafeNext (t )
546582 a .miner .ActL1FinalizeNext (t )
@@ -552,7 +588,7 @@ func TestAltDA_Finalization(gt *testing.T) {
552588 a .sequencer .ActL1FinalizedSignal (t )
553589 a .sequencer .ActL1SafeSignal (t )
554590
555- // commit all the l2 blocks to L1
591+ // commit all the l2 blocks to L1: 2/2/1:2/1/0 -> 3/2/1:2/1/0
556592 a .batcher .ActSubmitAll (t )
557593 a .miner .ActL1StartBlock (12 )(t )
558594 a .miner .ActL1IncludeTx (a .dp .Addresses .Batcher )(t )
@@ -561,31 +597,31 @@ func TestAltDA_Finalization(gt *testing.T) {
561597 // verify
562598 a .sequencer .ActL2PipelineFull (t )
563599
564- // fill with more unsafe L2 blocks
600+ // fill with more unsafe L2 blocks: 3/2/1:2/1/0 -> 3/2/1:3/1/0
565601 a .sequencer .ActL1HeadSignal (t )
566602 a .sequencer .ActBuildToL1Head (t )
567603
568- // submit those blocks too, block #4
604+ // submit those blocks too, block #4: 3/2/1:3/1/0 -> 4/2/1:3/1/0
569605 a .batcher .ActSubmitAll (t )
570606 a .miner .ActL1StartBlock (12 )(t )
571607 a .miner .ActL1IncludeTx (a .dp .Addresses .Batcher )(t )
572608 a .miner .ActL1EndBlock (t )
573609
574- // add some more L1 blocks #5, #6
610+ // add some more L1 blocks #5, #6: 4/2/1:3/1/0 -> 6/2/1:3/1/0
575611 a .miner .ActEmptyBlock (t )
576612 a .miner .ActEmptyBlock (t )
577613
578- // and more unsafe L2 blocks
614+ // and more unsafe L2 blocks: 6/2/1:3/1/0 -> 6/2/1:6/1/0
579615 a .sequencer .ActL1HeadSignal (t )
580616 a .sequencer .ActBuildToL1Head (t )
581617
582- // move safe/finalize markers: finalize the L1 chain block with the first batch, but not the second
618+ // move safe/finalize markers: 6/2/1:6/1/0 -> 6/4/3:6/1/0
583619 a .miner .ActL1SafeNext (t ) // #2 -> #3
584620 a .miner .ActL1SafeNext (t ) // #3 -> #4
585621 a .miner .ActL1FinalizeNext (t ) // #1 -> #2
586622 a .miner .ActL1FinalizeNext (t ) // #2 -> #3
587623
588- // L1 safe and finalized as expected
624+ // L1 safe and finalized as expected:
589625 a .sequencer .ActL2PipelineFull (t )
590626 a .sequencer .ActL1FinalizedSignal (t )
591627 a .sequencer .ActL1SafeSignal (t )
@@ -607,3 +643,64 @@ func TestAltDA_Finalization(gt *testing.T) {
607643 // given 12s l1 time and 1s l2 time, l2 should be 12 * 3 = 36 blocks finalized
608644 require .Equal (t , uint64 (36 ), a .sequencer .SyncStatus ().FinalizedL2 .Number )
609645}
646+
647+ // This test tests altDA -> ethDA -> altDA finalization behavior, simulating a temp altDA failure.
648+ func TestAltDA_FinalizationAfterEthDAFailover (gt * testing.T ) {
649+ t := helpers .NewDefaultTesting (gt )
650+ // we only print critical logs to be able to see the statusLogs
651+ harness := NewL2AltDA (t , WithLogLevel (log .LevelDebug ))
652+
653+ // We first call this twice because the first 2 times are irregular.
654+ // See ActNewL2TxFinalized's TODO comment.
655+ harness .ActNewL2TxFinalized (t )
656+ harness .ActNewL2TxFinalized (t )
657+
658+ // ActNewL2TxFinalized advances L1 by (1+ChallengeWindow)L1 blocks, and there are 12 L2 blocks per L1 block.
659+ diffL2Blocks := (1 + harness .altDACfg .ChallengeWindow ) * 12
660+
661+ for i := 0 ; i < 5 ; i ++ {
662+ ssBefore := harness .sequencer .SyncStatus ()
663+ harness .ActNewL2TxFinalized (t )
664+ ssAfter := harness .sequencer .SyncStatus ()
665+ // Finalized head should advance normally in altda mode
666+ require .Equal (t , ssBefore .FinalizedL2 .Number + diffL2Blocks , ssAfter .FinalizedL2 .Number )
667+ }
668+
669+ // We swap out altda batcher for ethda batcher
670+ harness .batcher .ActAltDAFailoverToEthDA (t )
671+
672+ for i := 0 ; i < 3 ; i ++ {
673+ ssBefore := harness .sequencer .SyncStatus ()
674+ harness .ActNewL2TxFinalized (t )
675+ if i == 0 {
676+ // TODO: figure out why we need to act twice for the first time after failover.
677+ // I think it's because the L1 driven finalizedHead is set to L1FinalizedHead-ChallengeWindow (see damgr.go updateFinalizedFromL1),
678+ // so it trails behind by an extra challenge_window when we switch over to ethDA.
679+ harness .ActNewL2TxFinalized (t )
680+ }
681+ ssAfter := harness .sequencer .SyncStatus ()
682+ // Even after failover, the finalized head should continue advancing normally
683+ require .Equal (t , ssBefore .FinalizedL2 .Number + diffL2Blocks , ssAfter .FinalizedL2 .Number )
684+ }
685+
686+ // Revert back to altda batcher (simulating that altda's temporary outage is resolved)
687+ harness .batcher .ActAltDAFallbackToAltDA (t )
688+
689+ for i := 0 ; i < 3 ; i ++ {
690+ ssBefore := harness .sequencer .SyncStatus ()
691+ harness .ActNewL2TxFinalized (t )
692+ ssAfter := harness .sequencer .SyncStatus ()
693+
694+ // Even after fallback to altda, the finalized head should continue advancing normally
695+ if i == 0 {
696+ // This is the opposite as the altda->ethda direction. In this case, the first time we fallback to altda,
697+ // the finalized head will advance by 2*diffL2Blocks: in ethda mode when driven by L1 finalization,
698+ // the head is set to L1FinalizedHead-ChallengeWindow. After sending an altda commitment, the finalized head
699+ // is now driven by the finalization of the altda commitment.
700+ require .Equal (t , ssBefore .FinalizedL2 .Number + 2 * diffL2Blocks , ssAfter .FinalizedL2 .Number )
701+ } else {
702+ require .Equal (t , ssBefore .FinalizedL2 .Number + diffL2Blocks , ssAfter .FinalizedL2 .Number )
703+ }
704+
705+ }
706+ }
0 commit comments