Skip to content

Commit f85ceae

Browse files
committed
test(fisherman): fix fisherman integration tests
- batch storage requests api - retry fisherman batch deletion check api
1 parent 9c34f0a commit f85ceae

17 files changed

+2501
-1338
lines changed

test/suites/integration/fisherman/batch-file-deletion.test.ts

Lines changed: 108 additions & 357 deletions
Large diffs are not rendered by default.

test/suites/integration/fisherman/file-deletion-flow-catchup.test.ts

Lines changed: 111 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
3631
await 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

Comments
 (0)