Skip to content

Commit 75af5b2

Browse files
committed
wip: enforce msp acceptance failure integration test
1 parent 93fc9b6 commit 75af5b2

File tree

1 file changed

+213
-0
lines changed

1 file changed

+213
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import assert, { strictEqual } from "node:assert";
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
import * as $ from "scale-codec";
5+
import type { H256 } from "@polkadot/types/interfaces";
6+
import { describeMspNet, type EnrichedBspApi, waitFor } from "../../../util";
7+
import { fetchJwtToken } from "../../../util/backend/jwt";
8+
import { SH_EVM_SOLOCHAIN_CHAIN_ID } from "../../../util/evmNet/consts";
9+
import { ETH_SH_USER_ADDRESS, ETH_SH_USER_PRIVATE_KEY, ethShUser } from "../../../util/evmNet/keyring";
10+
11+
await describeMspNet(
12+
"Backend cleanup after MSP rejection (drop acceptance, expect 404)",
13+
{
14+
initialised: false,
15+
runtimeType: "solochain",
16+
indexer: true,
17+
backend: true,
18+
only: true
19+
},
20+
({ before, it, createUserApi, createMsp1Api }) => {
21+
let userApi: EnrichedBspApi;
22+
let msp1Api: EnrichedBspApi;
23+
24+
// Shared across tests
25+
let bucketId: string;
26+
let fileKey: H256;
27+
let form: FormData;
28+
let userJWT: string;
29+
let originalFileBuffer: Buffer;
30+
let uploadedFileKeyHex: string;
31+
32+
const TEST_FILE_NAME = "whatsup.jpg";
33+
const fileLocation = `test/${TEST_FILE_NAME}`;
34+
const source = `res/${TEST_FILE_NAME}`;
35+
36+
before(async () => {
37+
userApi = await createUserApi();
38+
const maybeMsp1Api = await createMsp1Api();
39+
if (maybeMsp1Api) {
40+
msp1Api = maybeMsp1Api;
41+
} else {
42+
throw new Error("MSP API for first MSP not available");
43+
}
44+
});
45+
46+
it("Postgres DB is ready", async () => {
47+
await userApi.docker.waitForLog({
48+
containerName: "storage-hub-sh-postgres-1",
49+
searchString: "database system is ready to accept connections",
50+
timeout: 10000
51+
});
52+
});
53+
54+
it("Backend service is ready", async () => {
55+
await userApi.docker.waitForLog({
56+
containerName: "storage-hub-sh-backend-1",
57+
searchString: "Server listening",
58+
timeout: 15000
59+
});
60+
});
61+
62+
it("Network launches and can be queried", async () => {
63+
const userNodePeerId = await userApi.rpc.system.localPeerId();
64+
strictEqual(userNodePeerId.toString(), userApi.shConsts.NODE_INFOS.user.expectedPeerId);
65+
66+
const mspNodePeerId = await msp1Api.rpc.system.localPeerId();
67+
strictEqual(mspNodePeerId.toString(), userApi.shConsts.NODE_INFOS.msp1.expectedPeerId);
68+
});
69+
70+
it("Create storage request and upload to MSP (no sealing acceptance)", async () => {
71+
// Make TTL short so rejection happens quickly
72+
const tickRangeToMaximumThreshold = (
73+
await userApi.query.parameters.parameters({
74+
RuntimeConfig: { TickRangeToMaximumThreshold: null }
75+
})
76+
)
77+
.unwrap()
78+
.asRuntimeConfig.asTickRangeToMaximumThreshold.toNumber();
79+
const storageRequestTtlRuntimeParameter = {
80+
RuntimeConfig: { StorageRequestTtl: [null, tickRangeToMaximumThreshold] }
81+
} as const;
82+
await userApi.block.seal({
83+
calls: [userApi.tx.sudo.sudo(userApi.tx.parameters.setParameter(storageRequestTtlRuntimeParameter))]
84+
});
85+
86+
// Create bucket
87+
const valueProps = await userApi.call.storageProvidersApi.queryValuePropositionsForMsp(
88+
userApi.shConsts.DUMMY_MSP_ID
89+
);
90+
const valuePropId = valueProps[0].id;
91+
const newBucketEvent = await userApi.createBucket("backend-rejection-bucket", valuePropId);
92+
const newBucketEventDataBlob =
93+
userApi.events.fileSystem.NewBucket.is(newBucketEvent) && newBucketEvent.data;
94+
if (!newBucketEventDataBlob) throw new Error("NewBucket event data not found");
95+
bucketId = newBucketEventDataBlob.bucketId.toString();
96+
97+
// Load file into user's local storage to get metadata and then remove so it doesn't auto-send
98+
const userAddress = ETH_SH_USER_ADDRESS.slice(2);
99+
const file = await userApi.rpc.storagehubclient.loadFileInStorage(
100+
source,
101+
fileLocation,
102+
userAddress,
103+
bucketId
104+
);
105+
fileKey = file.file_key;
106+
const fileMetadata = file.file_metadata;
107+
await userApi.rpc.storagehubclient.removeFilesFromFileStorage([fileKey]);
108+
109+
// Issue storage request
110+
await userApi.block.seal({
111+
calls: [
112+
userApi.tx.fileSystem.issueStorageRequest(
113+
bucketId,
114+
fileMetadata.location,
115+
fileMetadata.fingerprint,
116+
fileMetadata.file_size,
117+
userApi.shConsts.DUMMY_MSP_ID,
118+
[userApi.shConsts.NODE_INFOS.msp1.expectedPeerId],
119+
{ Basic: null }
120+
)
121+
],
122+
signer: ethShUser
123+
});
124+
125+
// MSP expects the file
126+
await waitFor({
127+
lambda: async () => (await msp1Api.rpc.storagehubclient.isFileKeyExpected(fileKey)).isTrue
128+
});
129+
130+
// Prepare upload form
131+
const localSource = "docker/resource/" + TEST_FILE_NAME;
132+
const fileBuffer = fs.readFileSync(path.join("..", localSource));
133+
originalFileBuffer = fileBuffer;
134+
form = new FormData();
135+
136+
const FileMetadataCodec = $.object(
137+
$.field("owner", $.uint8Array),
138+
$.field("bucket_id", $.uint8Array),
139+
$.field("location", $.uint8Array),
140+
$.field("file_size", $.compact($.u64)),
141+
$.field("fingerprint", $.sizedArray($.u8, 32))
142+
);
143+
const encoded_file_metadata = FileMetadataCodec.encode(fileMetadata);
144+
const fileMetadataBlob = new Blob([Buffer.from(encoded_file_metadata)], {
145+
type: "application/octet-stream"
146+
});
147+
form.append("file_metadata", fileMetadataBlob, "file_metadata");
148+
149+
const fileBlob = new Blob([fileBuffer], { type: "image/jpeg" });
150+
form.append("file", fileBlob, path.basename(fileLocation));
151+
152+
// Auth token
153+
userJWT = await fetchJwtToken(ETH_SH_USER_PRIVATE_KEY, SH_EVM_SOLOCHAIN_CHAIN_ID);
154+
155+
// Upload to backend
156+
const uploadResponse = await fetch(
157+
`http://localhost:8080/buckets/${bucketId}/upload/${fileKey}`,
158+
{
159+
method: "PUT",
160+
body: form,
161+
headers: { Authorization: `Bearer ${userJWT}` }
162+
}
163+
);
164+
strictEqual(uploadResponse.status, 201, "Upload should return CREATED status");
165+
166+
// File stored locally in MSP file storage
167+
await msp1Api.wait.fileStorageComplete(fileKey);
168+
169+
// MSP acceptance extrinsic in tx pool (do not seal)
170+
await userApi.wait.mspResponseInTxPool(1);
171+
172+
// Verify we can download before sealing acceptance
173+
uploadedFileKeyHex = fileKey.toHex();
174+
const preRejectDownload = await fetch(`http://localhost:8080/download/${uploadedFileKeyHex}`, {
175+
headers: { Authorization: `Bearer ${userJWT}` }
176+
});
177+
strictEqual(preRejectDownload.status, 200, "Download should succeed before rejection");
178+
const preArrayBuffer = await preRejectDownload.arrayBuffer();
179+
strictEqual(Buffer.from(preArrayBuffer).length, originalFileBuffer.length);
180+
});
181+
182+
it("Drop MSP acceptance, advance to rejection, assert and expect download to fail", async () => {
183+
assert(fileKey, "File key should be available from previous step");
184+
185+
// Remove MSP response extrinsic from tx pool
186+
await userApi.node.dropTxn({
187+
module: "fileSystem",
188+
method: "mspRespondStorageRequestsMultipleBuckets"
189+
});
190+
191+
// Find expiration and advance to it
192+
const storageRequest = await userApi.query.fileSystem.storageRequests(fileKey);
193+
assert(storageRequest.isSome, "Storage request should exist");
194+
const expiresAt = storageRequest.unwrap().expiresAt.toNumber();
195+
const result = await userApi.block.skipTo(expiresAt);
196+
197+
// Expect StorageRequestRejected event
198+
await userApi.assert.eventPresent("fileSystem", "StorageRequestRejected", result.events);
199+
200+
// Download should now fail (backend should no longer serve the file)
201+
await waitFor({
202+
lambda: async () => {
203+
const resp = await fetch(`http://localhost:8080/download/${uploadedFileKeyHex}`, {
204+
headers: { Authorization: `Bearer ${userJWT}` }
205+
});
206+
return resp.status === 404;
207+
},
208+
iterations: 60,
209+
delay: 250
210+
});
211+
});
212+
}
213+
);

0 commit comments

Comments
 (0)