@@ -219,20 +219,15 @@ func TestLeaseFailoverE2E(t *testing.T) {
219219 return err == nil
220220 }, time .Second , 100 * time .Millisecond )
221221
222- lastDABlockNewLeader = queryLastDAHeight (t , env .SequencerJWT , env .Endpoints .GetDAAddress ())
223-
224222 genesisHeight := state .InitialHeight
225223 verifyNoDoubleSigning (t , clusterNodes , genesisHeight , state .LastBlockHeight )
226224
227- // wait for the next DA block to ensure all blocks are propagated
228- require .Eventually (t , func () bool {
229- before := lastDABlockNewLeader
230- lastDABlockNewLeader = queryLastDAHeight (t , env .SequencerJWT , env .Endpoints .GetDAAddress ())
231- return before < lastDABlockNewLeader
232- }, 2 * must (time .ParseDuration (DefaultDABlockTime )), 100 * time .Millisecond )
233-
225+ // wait for the DA submitter to catch up — poll until all blocks are on DA
234226 t .Log ("+++ Verifying no DA gaps..." )
235- verifyDABlocks (t , 1 , lastDABlockNewLeader , env .SequencerJWT , env .Endpoints .GetDAAddress (), genesisHeight , state .LastBlockHeight )
227+ require .EventuallyWithT (t , func (collect * assert.CollectT ) {
228+ lastDA := queryLastDAHeight (t , env .SequencerJWT , env .Endpoints .GetDAAddress ())
229+ verifyDABlocksCollect (collect , 1 , lastDA , env .SequencerJWT , env .Endpoints .GetDAAddress (), genesisHeight , state .LastBlockHeight )
230+ }, 3 * must (time .ParseDuration (DefaultDABlockTime )), 500 * time .Millisecond )
236231
237232 // Cleanup processes
238233 clusterNodes .killAll ()
@@ -508,22 +503,16 @@ func TestHASequencerRollingRestartE2E(t *testing.T) {
508503 return err == nil
509504 }, time .Second , 100 * time .Millisecond )
510505
511- lastDABlock := queryLastDAHeight (t , env .SequencerJWT , env .Endpoints .GetDAAddress ())
512-
513506 genesisHeight := state .InitialHeight
514507 verifyNoDoubleSigning (t , clusterNodes , genesisHeight , state .LastBlockHeight )
515508 t .Log ("+++ No double-signing detected ✓" )
516509
517- // Wait for the next DA block to ensure all blocks are propagated
518- require .Eventually (t , func () bool {
519- before := lastDABlock
520- lastDABlock = queryLastDAHeight (t , env .SequencerJWT , env .Endpoints .GetDAAddress ())
521- return before < lastDABlock
522- }, 2 * must (time .ParseDuration (DefaultDABlockTime )), 100 * time .Millisecond )
523-
524- // Verify no DA gaps
510+ // Wait for the DA submitter to catch up — poll until all blocks are on DA
525511 t .Log ("+++ Verifying no DA gaps..." )
526- verifyDABlocks (t , 1 , lastDABlock , env .SequencerJWT , env .Endpoints .GetDAAddress (), genesisHeight , state .LastBlockHeight )
512+ require .EventuallyWithT (t , func (collect * assert.CollectT ) {
513+ lastDA := queryLastDAHeight (t , env .SequencerJWT , env .Endpoints .GetDAAddress ())
514+ verifyDABlocksCollect (collect , 1 , lastDA , env .SequencerJWT , env .Endpoints .GetDAAddress (), genesisHeight , state .LastBlockHeight )
515+ }, 3 * must (time .ParseDuration (DefaultDABlockTime )), 500 * time .Millisecond )
527516 t .Log ("+++ No DA gaps detected ✓" )
528517
529518 // Cleanup processes
@@ -611,6 +600,80 @@ func verifyDABlocks(t *testing.T, daStartHeight, lastDABlock uint64, jwtSecret s
611600 }
612601}
613602
603+ // verifyDABlocksCollect is like verifyDABlocks but uses assert.CollectT so it can be retried
604+ // inside require.EventuallyWithT.
605+ func verifyDABlocksCollect (collect * assert.CollectT , daStartHeight , lastDABlock uint64 , jwtSecret string , daAddress string , genesisHeight , lastEVBlock uint64 ) {
606+ ctx := context .Background ()
607+ blobClient , err := blobrpc .NewClient (ctx , daAddress , jwtSecret , "" )
608+ if ! assert .NoError (collect , err ) {
609+ return
610+ }
611+ defer blobClient .Close ()
612+
613+ ns , err := libshare .NewNamespaceFromBytes (coreda .NamespaceFromString (DefaultDANamespace ).Bytes ())
614+ if ! assert .NoError (collect , err ) {
615+ return
616+ }
617+ evHeightsToEvBlockParts := make (map [uint64 ]int )
618+ deduplicationCache := make (map [string ]uint64 )
619+
620+ for daHeight := daStartHeight ; daHeight <= lastDABlock ; daHeight ++ {
621+ blobs , err := blobClient .Blob .GetAll (ctx , daHeight , []libshare.Namespace {ns })
622+ if err != nil {
623+ if strings .Contains (err .Error (), "blob: not found" ) {
624+ continue
625+ }
626+ assert .NoError (collect , err , "height %d/%d" , daHeight , lastDABlock )
627+ return
628+ }
629+ if len (blobs ) == 0 {
630+ continue
631+ }
632+
633+ for _ , blob := range blobs {
634+ if evHeight , hash , blobType := extractBlockHeightRaw (blob .Data ()); evHeight != 0 {
635+ _ = blobType
636+ if height , ok := deduplicationCache [hash .String ()]; ok {
637+ assert .Equal (collect , evHeight , height )
638+ continue
639+ }
640+ assert .GreaterOrEqual (collect , evHeight , genesisHeight )
641+ deduplicationCache [hash .String ()] = evHeight
642+ evHeightsToEvBlockParts [evHeight ]++
643+ }
644+ }
645+ }
646+
647+ for h := genesisHeight ; h <= lastEVBlock ; h ++ {
648+ assert .NotEmpty (collect , evHeightsToEvBlockParts [h ], "missing block on DA for height %d/%d" , h , lastEVBlock )
649+ assert .Less (collect , evHeightsToEvBlockParts [h ], 3 , "duplicate block on DA for height %d/%d" , h , lastEVBlock )
650+ }
651+ }
652+
653+ // extractBlockHeightRaw is like extractBlockHeight but doesn't require *testing.T.
654+ func extractBlockHeightRaw (blob []byte ) (uint64 , types.Hash , string ) {
655+ if len (blob ) == 0 {
656+ return 0 , nil , ""
657+ }
658+ var headerPb pb.SignedHeader
659+ if err := proto .Unmarshal (blob , & headerPb ); err == nil {
660+ var signedHeader types.SignedHeader
661+ if err := signedHeader .FromProto (& headerPb ); err == nil {
662+ if err := signedHeader .Header .ValidateBasic (); err == nil {
663+ return signedHeader .Height (), signedHeader .Hash (), "header"
664+ }
665+ }
666+ }
667+
668+ var signedData types.SignedData
669+ if err := signedData .UnmarshalBinary (blob ); err == nil {
670+ if signedData .Metadata != nil {
671+ return signedData .Height (), signedData .Hash (), "data"
672+ }
673+ }
674+ return 0 , nil , ""
675+ }
676+
614677// extractBlockHeight attempts to decode a blob as SignedHeader or SignedData and extract the block height
615678func extractBlockHeight (t * testing.T , blob []byte ) (uint64 , types.Hash , string ) {
616679 t .Helper ()
0 commit comments