@@ -5,57 +5,61 @@ import {
55 type SqlClient ,
66 shUser ,
77 bspKey ,
8- sleep ,
98 waitFor ,
109 ShConsts
1110} from "../../../util" ;
12- import {
13- waitForFileIndexed ,
14- waitForMspFileAssociation ,
15- waitForBspFileAssociation
16- } from "../../../util/indexerHelpers" ;
1711
1812/**
1913 * FISHERMAN FILE DELETION FLOW WITH CATCHUP
2014 *
21- * Purpose: Tests the fisherman's ability to process file deletion events from UNFINALIZED blocks
22- * during blockchain catchup scenarios .
15+ * Purpose: Tests the fisherman's ability to build forest proofs that include files from
16+ * unfinalized blocks when processing deletion requests .
2317 *
2418 * What makes this test unique:
25- * - Creates unfinalized blocks with blockchain activity (transfers)
26- * - Sends file deletion requests in unfinalized blocks (finaliseBlock: false)
27- * - Tests fisherman indexer's catchup mechanism when processing events from non-finalized portions
28- * - Verifies the gap between finalized head and current head during processing
19+ * - Pauses fisherman to accumulate events
20+ * - Creates finalized deletion request for an existing file
21+ * - Adds NEW files in unfinalized blocks (different from the file being deleted)
22+ * - Tests fisherman's ability to build proofs with unfinalized forest state
2923 *
3024 * Test Scenario:
31- * 1. Sets up file storage with both BSP and MSP confirming storage
32- * 2. Creates 3 unfinalized blocks with transfer activity
33- * 3. Sends file deletion request in an unfinalized block
34- * 4. Verifies fisherman can index and process events from unfinalized blocks
25+ * 1. Creates and finalizes a file with BSP and MSP storage (this file will be deleted)
26+ * 2. Pauses fisherman service
27+ * 3. Finalizes a file deletion request for the file from step 1
28+ * 4. Creates 3 NEW files in unfinalized blocks (adds to BSP/MSP forests)
29+ * 5. Resumes fisherman - it should build proofs including the new unfinalized files
3530 */
3631await describeMspNet (
3732 "Fisherman File Deletion Flow with Catchup" ,
3833 {
3934 initialised : false ,
4035 indexer : true ,
4136 fisherman : true ,
42- indexerMode : "fishing"
37+ indexerMode : "fishing" ,
38+ standaloneIndexer : true
4339 } ,
44- ( { before, it, createUserApi, createBspApi, createMsp1Api, createSqlClient } ) => {
40+ ( {
41+ before,
42+ it,
43+ createUserApi,
44+ createBspApi,
45+ createMsp1Api,
46+ createSqlClient,
47+ createIndexerApi
48+ } ) => {
4549 let userApi : EnrichedBspApi ;
4650 let bspApi : EnrichedBspApi ;
4751 let msp1Api : EnrichedBspApi ;
52+ let indexerApi : EnrichedBspApi ;
4853 let sql : SqlClient ;
49- let fileKey : string ;
5054
51- // Track files created in unfinalized blocks
52- const unfinalizedFiles : Array < {
55+ // File to be deleted ( created in finalized state)
56+ let fileToDelete : {
5357 fileKey : string ;
5458 bucketId : string ;
5559 location : string ;
5660 fingerprint : string ;
5761 fileSize : number ;
58- } > = [ ] ;
62+ } ;
5963
6064 before ( async ( ) => {
6165 userApi = await createUserApi ( ) ;
@@ -66,18 +70,24 @@ await describeMspNet(
6670 msp1Api = maybeMsp1Api ;
6771 sql = createSqlClient ( ) ;
6872
73+ // Connect to standalone indexer node
74+ assert (
75+ createIndexerApi ,
76+ "Indexer API not available. Ensure `standaloneIndexer` is set to `true` in the network configuration."
77+ ) ;
78+ indexerApi = await createIndexerApi ( ) ;
79+
6980 await userApi . docker . waitForLog ( {
7081 searchString : "💤 Idle" ,
7182 containerName : userApi . shConsts . NODE_INFOS . user . containerName ,
7283 timeout : 10000
7384 } ) ;
7485
75- await userApi . block . seal ( { finaliseBlock : true } ) ;
76-
77- await userApi . indexer . waitForIndexing ( { } ) ;
86+ // Wait for indexer to process the finalized block (producerApi will seal a finalized block by default)
87+ await indexerApi . indexer . waitForIndexing ( { producerApi : userApi } ) ;
7888 } ) ;
7989
80- it ( "creates finalized block with storage request and BSP & MSP confirming " , async ( ) => {
90+ it ( "Step 1: Create and finalize file that will be deleted later " , async ( ) => {
8191 const bucketName = "test-deletion-catchup-bucket" ;
8292 const source = "res/whatsup.jpg" ;
8393 const destination = "test/file-to-delete-catchup.txt" ;
@@ -86,23 +96,21 @@ await describeMspNet(
8696 const valueProps = await userApi . call . storageProvidersApi . queryValuePropositionsForMsp ( mspId ) ;
8797 const valuePropId = valueProps [ 0 ] . id ;
8898
89- const fileMetadata = await userApi . file . createBucketAndSendNewStorageRequest (
99+ fileToDelete = await userApi . file . createBucketAndSendNewStorageRequest (
90100 source ,
91101 destination ,
92102 bucketName ,
93103 valuePropId ,
94104 ShConsts . DUMMY_MSP_ID ,
95105 shUser ,
96106 1 ,
97- true
107+ true // Finalize this file
98108 ) ;
99109
100- fileKey = fileMetadata . fileKey ;
101-
102110 // Wait for MSP to store the file
103111 await waitFor ( {
104112 lambda : async ( ) =>
105- ( await msp1Api . rpc . storagehubclient . isFileInFileStorage ( fileKey ) ) . isFileFound
113+ ( await msp1Api . rpc . storagehubclient . isFileInFileStorage ( fileToDelete . fileKey ) ) . isFileFound
106114 } ) ;
107115
108116 await userApi . wait . mspResponseInTxPool ( ) ;
@@ -111,7 +119,7 @@ await describeMspNet(
111119 await userApi . wait . bspVolunteer ( ) ;
112120 await waitFor ( {
113121 lambda : async ( ) =>
114- ( await bspApi . rpc . storagehubclient . isFileInFileStorage ( fileKey ) ) . isFileFound
122+ ( await bspApi . rpc . storagehubclient . isFileInFileStorage ( fileToDelete . fileKey ) ) . isFileFound
115123 } ) ;
116124
117125 const bspAddress = userApi . createType ( "Address" , bspKey . address ) ;
@@ -121,98 +129,23 @@ await describeMspNet(
121129 bspAccount : bspAddress
122130 } ) ;
123131
124- await userApi . indexer . waitForIndexing ( { } ) ;
125- await waitForFileIndexed ( sql , fileKey ) ;
126- await waitForMspFileAssociation ( sql , fileKey ) ;
127- await waitForBspFileAssociation ( sql , fileKey ) ;
132+ // Wait for indexer to process the finalized file
133+ await indexerApi . indexer . waitForIndexing ( { producerApi : userApi } ) ;
134+ await indexerApi . indexer . waitForFileIndexed ( { sql, fileKey : fileToDelete . fileKey } ) ;
135+ await indexerApi . indexer . waitForMspFileAssociation ( { sql, fileKey : fileToDelete . fileKey } ) ;
136+ await indexerApi . indexer . waitForBspFileAssociation ( { sql, fileKey : fileToDelete . fileKey } ) ;
128137 } ) ;
129138
130- it ( "creates 3 unfinalized blocks with storage requests and MSP & BSP confirmations " , async ( ) => {
139+ it ( "Step 2-5: Pause fisherman, finalize deletion, add unfinalized files, resume fisherman " , async ( ) => {
131140 const mspId = userApi . shConsts . DUMMY_MSP_ID ;
132141 const valueProps = await userApi . call . storageProvidersApi . queryValuePropositionsForMsp ( mspId ) ;
133142 const valuePropId = valueProps [ 0 ] . id ;
134143
135- // Create 3 unfinalized blocks, each with a storage request
136- for ( let i = 0 ; i < 3 ; i ++ ) {
137- const bucketName = `test-deletion-catchup-bucket-${ i } ` ;
138- const source = "res/whatsup.jpg" ;
139- const destination = `test/file-to-delete-catchup-${ i } .txt` ;
140-
141- const fileMetadata = await userApi . file . createBucketAndSendNewStorageRequest (
142- source ,
143- destination ,
144- bucketName ,
145- valuePropId ,
146- ShConsts . DUMMY_MSP_ID ,
147- shUser ,
148- 1 ,
149- false
150- ) ;
151-
152- // Store file metadata for later use
153- unfinalizedFiles . push ( fileMetadata ) ;
154-
155- // Wait for MSP to store the file
156- await waitFor ( {
157- lambda : async ( ) =>
158- ( await msp1Api . rpc . storagehubclient . isFileInFileStorage ( fileMetadata . fileKey ) )
159- . isFileFound
160- } ) ;
161-
162- await userApi . wait . mspResponseInTxPool ( ) ;
163-
164- // Wait for BSP to volunteer and store
165- await userApi . wait . bspVolunteer ( ) ;
166- await waitFor ( {
167- lambda : async ( ) =>
168- ( await bspApi . rpc . storagehubclient . isFileInFileStorage ( fileMetadata . fileKey ) )
169- . isFileFound
170- } ) ;
171-
172- const bspAddress = userApi . createType ( "Address" , bspKey . address ) ;
173- await userApi . wait . bspStored ( {
174- expectedExts : 1 ,
175- sealBlock : false , // Don't seal/finalize the block
176- bspAccount : bspAddress
177- } ) ;
178-
179- await userApi . block . seal ( {
180- finaliseBlock : false
181- } ) ;
182-
183- // Add a small delay between blocks
184- await sleep ( 500 ) ;
185- }
186-
187- // Verify blocks were created but not finalized
188- const finalizedHead = await userApi . rpc . chain . getFinalizedHead ( ) ;
189- const currentHead = await userApi . rpc . chain . getHeader ( ) ;
190-
191- // There should be a gap between finalized and current head
192- assert (
193- currentHead . number . toNumber ( ) >
194- ( await userApi . rpc . chain . getHeader ( finalizedHead ) ) . number . toNumber ( ) ,
195- "Current head should be ahead of finalized head"
196- ) ;
197-
198- // Verify we created 3 files
199- assert . equal ( unfinalizedFiles . length , 3 , "Should have created 3 files in unfinalized blocks" ) ;
200- } ) ;
144+ // Step 2: Pause fisherman service
145+ await userApi . docker . pauseContainer ( userApi . shConsts . NODE_INFOS . fisherman . containerName ) ;
201146
202- it ( "sends file deletion request in unfinalized block and verifies fisherman processes with ephemeral trie" , async ( ) => {
203- // Use the first file created in unfinalized blocks for deletion
204- assert ( unfinalizedFiles . length > 0 , "Should have files created in unfinalized blocks" ) ;
205- const fileToDelete = unfinalizedFiles [ 0 ] ;
206-
207- // NOTE: We don't wait for indexing here because the files are in unfinalized blocks
208- // The fisherman indexer won't have these files in its database, but it will:
209- // 1. Build an ephemeral trie from indexed (finalized) data
210- // 2. Query unfinalized blocks for additional files
211- // 3. Add unfinalized files to the ephemeral trie
212- // 4. Create proof of inclusion for the deletion
213-
214- // Ensure file is in MSP's forest storage before deletion attempt
215- // The providers store files regardless of block finalization
147+ // Step 3: Finalize file deletion request for the file created in step 1
148+ // Ensure file is in MSP's forest storage before deletion
216149 await waitFor ( {
217150 lambda : async ( ) => {
218151 const isFileInForest = await msp1Api . rpc . storagehubclient . isFileInForest (
@@ -238,8 +171,8 @@ await describeMspNet(
238171 const rawSignature = shUser . sign ( intentionPayload ) ;
239172 const userSignature = userApi . createType ( "MultiSignature" , { Sr25519 : rawSignature } ) ;
240173
241- // Submit file deletion request in an unfinalized block
242- await userApi . block . seal ( {
174+ // Submit file deletion request in a FINALIZED block (fisherman is paused)
175+ const deletionRequestResult = await userApi . block . seal ( {
243176 calls : [
244177 userApi . tx . fileSystem . requestDeleteFile (
245178 fileOperationIntention ,
@@ -251,45 +184,79 @@ await describeMspNet(
251184 )
252185 ] ,
253186 signer : shUser ,
254- finaliseBlock : false
187+ finaliseBlock : true
255188 } ) ;
256189
257190 // Verify FileDeletionRequested event
258- const { event : deletionEvent } = await userApi . assert . eventPresent (
191+ await userApi . assert . eventPresent (
259192 "fileSystem" ,
260- "FileDeletionRequested"
193+ "FileDeletionRequested" ,
194+ deletionRequestResult . events
261195 ) ;
262196
263- const deletionEventData =
264- userApi . events . fileSystem . FileDeletionRequested . is ( deletionEvent ) && deletionEvent . data ;
197+ // Wait for indexer to process the finalized deletion request
198+ await indexerApi . indexer . waitForIndexing ( { producerApi : userApi } ) ;
199+
200+ // Step 4: Create 3 NEW files in UNFINALIZED blocks (these will be added to forests)
201+ await userApi . file . batchStorageRequests ( {
202+ files : [
203+ {
204+ source : "res/smile.jpg" ,
205+ destination : "test/catchup-new-0.txt" ,
206+ bucketIdOrName : "test-catchup-new-file-0" ,
207+ replicationTarget : 1
208+ } ,
209+ {
210+ source : "res/smile.jpg" ,
211+ destination : "test/catchup-new-1.txt" ,
212+ bucketIdOrName : "test-catchup-new-file-1" ,
213+ replicationTarget : 1
214+ } ,
215+ {
216+ source : "res/smile.jpg" ,
217+ destination : "test/catchup-new-2.txt" ,
218+ bucketIdOrName : "test-catchup-new-file-2" ,
219+ replicationTarget : 1
220+ }
221+ ] ,
222+ mspId : userApi . shConsts . DUMMY_MSP_ID ,
223+ valuePropId,
224+ owner : shUser ,
225+ bspApi,
226+ mspApi : msp1Api ,
227+ finaliseBlock : false
228+ } ) ;
265229
266- assert ( deletionEventData , "FileDeletionRequested event data not found" ) ;
267- const eventFileKey = deletionEventData . signedDeleteIntention . fileKey ;
268- assert . equal ( eventFileKey . toString ( ) , fileToDelete . fileKey . toString ( ) ) ;
230+ // Verify there's a gap between finalized and current head
231+ const finalizedHead = await userApi . rpc . chain . getFinalizedHead ( ) ;
232+ const currentHead = await userApi . rpc . chain . getHeader ( ) ;
233+ assert (
234+ currentHead . number . toNumber ( ) >
235+ ( await userApi . rpc . chain . getHeader ( finalizedHead ) ) . number . toNumber ( ) ,
236+ "Current head should be ahead of finalized head (unfinalized files added)"
237+ ) ;
269238
270- // Wait for fisherman to process user deletions and verify extrinsics are in tx pool
271- const deletionResult = await userApi . fisherman . waitForBatchDeletions ( {
272- deletionType : "User" ,
273- expectExt : 2 ,
274- sealBlock : true // Seal and return events for verification
239+ // Step 5: Resume fisherman - it should process deletion with updated forest state
240+ await userApi . docker . resumeContainer ( {
241+ containerName : userApi . shConsts . NODE_INFOS . fisherman . containerName
275242 } ) ;
276243
277- assert ( deletionResult , "Deletion result should be defined when sealBlock is true" ) ;
278-
279- // Verify BSP deletions
280- await userApi . fisherman . verifyBspDeletionResults ( {
281- userApi,
282- bspApi,
283- events : deletionResult . events ,
284- expectedCount : 1
244+ await userApi . docker . waitForLog ( {
245+ searchString : "💤 Idle" ,
246+ containerName : userApi . shConsts . NODE_INFOS . fisherman . containerName ,
247+ tail : 10
285248 } ) ;
286249
287- // Verify bucket deletions
288- await userApi . fisherman . verifyBucketDeletionResults ( {
250+ await userApi . fisherman . retryableWaitAndVerifyBatchDeletions ( {
251+ blockProducerApi : userApi ,
252+ deletionType : "User" ,
253+ expectExt : 2 , // 1 BSP + 1 Bucket
289254 userApi,
255+ bspApi,
256+ expectedBspCount : 1 ,
290257 mspApi : msp1Api ,
291- events : deletionResult . events ,
292- expectedCount : 1
258+ expectedBucketCount : 1 ,
259+ maxRetries : 3
293260 } ) ;
294261 } ) ;
295262 }
0 commit comments