1- import { Blob } from '@aztec/blob-lib' ;
1+ import { Blob , EMPTY_BLOB_VERSIONED_HASH } from '@aztec/blob-lib' ;
22import type { BlobSinkClientInterface } from '@aztec/blob-sink/client' ;
33import { BlobWithIndex } from '@aztec/blob-sink/types' ;
44import { GENESIS_ARCHIVE_ROOT } from '@aztec/constants' ;
@@ -16,6 +16,7 @@ import { bufferToHex, withoutHexPrefix } from '@aztec/foundation/string';
1616import { openTmpStore } from '@aztec/kv-store/lmdb-v2' ;
1717import { type InboxAbi , RollupAbi } from '@aztec/l1-artifacts' ;
1818import {
19+ Body ,
1920 CommitteeAttestation ,
2021 CommitteeAttestationsAndSigners ,
2122 L2Block ,
@@ -28,9 +29,16 @@ import { makeAndSignCommitteeAttestationsAndSigners, makeBlockAttestationFromBlo
2829import { getTelemetryClient } from '@aztec/telemetry-client' ;
2930
3031import { jest } from '@jest/globals' ;
32+ import {
33+ type FormattedBlock ,
34+ type Log ,
35+ type Transaction ,
36+ encodeFunctionData ,
37+ multicall3Abi ,
38+ toHex ,
39+ } from '@spalladino/viem' ;
3140import assert from 'assert' ;
3241import { type MockProxy , mock } from 'jest-mock-extended' ;
33- import { type FormattedBlock , type Log , type Transaction , encodeFunctionData , multicall3Abi , toHex } from 'viem' ;
3442
3543import { Archiver } from './archiver.js' ;
3644import type { ArchiverDataStore } from './archiver_store.js' ;
@@ -919,6 +927,71 @@ describe('Archiver', () => {
919927 await retryUntil ( async ( ) => ( await archiver . getBlockNumber ( ) ) === 3 , 'resync' , 10 , 0.1 ) ;
920928 } ) ;
921929
930+ it ( 'handles empty blob hash without downloading blob' , async ( ) => {
931+ let latestBlockNum = await archiver . getBlockNumber ( ) ;
932+ expect ( latestBlockNum ) . toEqual ( 0 ) ;
933+
934+ // Create a block with an empty body
935+ const emptyBlock = blocks [ 0 ] ;
936+ emptyBlock . body = Body . empty ( ) ;
937+
938+ const emptyBlobHash = bufferToHex ( EMPTY_BLOB_VERSIONED_HASH ) ;
939+ const rollupTx = await makeRollupTx ( emptyBlock ) ;
940+
941+ mockL1BlockNumbers ( 100n ) ;
942+
943+ mockRollup . read . status . mockResolvedValue ( [ 0n , GENESIS_ROOT , 1n , emptyBlock . archive . root . toString ( ) , GENESIS_ROOT ] ) ;
944+
945+ makeL2BlockProposedEvent ( 70n , 1n , emptyBlock . archive . root . toString ( ) , [ emptyBlobHash ] ) ;
946+
947+ // Mock getBlobSidecar to return empty array (simulating blob not downloaded)
948+ blobSinkClient . getBlobSidecar . mockResolvedValueOnce ( [ ] ) ;
949+
950+ publicClient . getTransaction . mockResolvedValueOnce ( rollupTx ) ;
951+
952+ await archiver . start ( false ) ;
953+
954+ // Wait until block 1 is processed
955+ await waitUntilArchiverBlock ( 1 ) ;
956+
957+ latestBlockNum = await archiver . getBlockNumber ( ) ;
958+ expect ( latestBlockNum ) . toEqual ( 1 ) ;
959+
960+ // Verify the block was synced successfully
961+ const syncedBlock = await archiver . getBlock ( 1 ) ;
962+ expect ( syncedBlock ) . toBeDefined ( ) ;
963+ expect ( syncedBlock ! . body . txEffects . length ) . toEqual ( 0 ) ;
964+ } , 10_000 ) ;
965+
966+ it ( 'throws error when blob hashes and bodies mismatch (non-empty case)' , async ( ) => {
967+ let latestBlockNum = await archiver . getBlockNumber ( ) ;
968+ expect ( latestBlockNum ) . toEqual ( 0 ) ;
969+
970+ const block = blocks [ 0 ] ;
971+ const blobHashes = await makeVersionedBlobHashes ( block ) ;
972+ const rollupTx = await makeRollupTx ( block ) ;
973+
974+ mockL1BlockNumbers ( 100n ) ;
975+
976+ mockRollup . read . status . mockResolvedValue ( [ 0n , GENESIS_ROOT , 1n , block . archive . root . toString ( ) , GENESIS_ROOT ] ) ;
977+
978+ makeL2BlockProposedEvent ( 70n , 1n , block . archive . root . toString ( ) , blobHashes ) ;
979+
980+ // Mock getBlobSidecar to return empty array (missing blobs)
981+ blobSinkClient . getBlobSidecar . mockResolvedValueOnce ( [ ] ) ;
982+
983+ publicClient . getTransaction . mockResolvedValueOnce ( rollupTx ) ;
984+
985+ await archiver . start ( false ) ;
986+
987+ // Give it some time to attempt processing
988+ await sleep ( 1000 ) ;
989+
990+ // Should still be at block 0 since the blob fetch failed
991+ latestBlockNum = await archiver . getBlockNumber ( ) ;
992+ expect ( latestBlockNum ) . toEqual ( 0 ) ;
993+ } , 10_000 ) ;
994+
922995 // TODO(palla/reorg): Add a unit test for the archiver handleEpochPrune
923996 xit ( 'handles an upcoming L2 prune' , ( ) => { } ) ;
924997
0 commit comments