Hello everyone,
I’m currently working on a file storage caniste where files are uploaded in chunks and stored using stable memory (StableBTreeMap
).
Problem:
When trying to upload a file (chunked or otherwise), I receive the error:
Canister exceeded the limit of 40000000000 instructions for single message execution.
I have tried different methods but it’s always same at the end asked to AI for help and generated something more complex but with this code throws error even on initializeUpload
method.
Code:
export default class StorageCanister {
private fileStorage = StableBTreeMap<string, File>(0); // Metadata storage
private chunkStorage = StableBTreeMap<string, Uint8Array>(1); // Chunk storage
private serviceStorage = StableBTreeMap<Principal, Service>(2); // Service management
private uploadStates = new Map<string, UploadState>(); // Tracks upload states per file
@update([IDL.Text, IDL.Text, IDL.Nat], IDL.Bool)
async initializeUpload(
fileId: string,
fileName: string,
totalSize: bigint,
): Promise<boolean> {
try {
if (!this.serviceStorage.containsKey(caller())) {
throw { Unauthorized: "Unauthorized access!" };
}
// Uncomment this if you want to check for free space
// if (!(await this.checkCanisterFreeSpace(totalSize))) {
// throw { UploadError: "No sufficient space in the canister!" };
// }
const newFile: File = {
id: fileId,
name: fileName,
size: totalSize,
userId: caller().toText(),
createdAt: time(),
chunkCount: 0,
};
this.fileStorage.insert(fileId, newFile);
this.uploadStates.set(fileId, { NotStarted: null });
return true;
} catch (error) {
return false;
}
}
@update([IDL.Text, IDL.Nat, IDL.Vec(IDL.Nat8)], IDL.Bool)
async uploadChunk(
fileId: string,
chunkId: ChunkId,
chunkData: Uint8Array,
): Promise<boolean> {
try {
if (!this.serviceStorage.containsKey(caller())) {
throw { Unauthorized: "Unauthorized access!" };
}
const currentState = this.uploadStates.get(fileId);
if (!currentState) {
throw { UploadError: "Upload not initialized" };
}
if ("Completed" in currentState) {
throw { UploadError: "Upload already completed" };
}
if (
"InProgress" in currentState &&
currentState.InProgress.chunkId + 1n !== chunkId
) {
throw { UploadError: "Invalid chunk order" };
}
const chunkKey = `${fileId}-${chunkId}`;
this.chunkStorage.insert(chunkKey, chunkData);
const file = this.fileStorage.get(fileId);
if (!file) {
throw { UploadError: "File not found" };
}
const updatedFile = {
...file,
chunkCount: file.chunkCount + 1,
};
this.fileStorage.insert(fileId, updatedFile);
this.uploadStates.set(fileId, { InProgress: { chunkId } });
return true;
} catch (error) {
return false;
}
}
@update([IDL.Text], FileResult)
async finalizeUpload(fileId: string): Promise<FileResult> {
try {
if (!this.serviceStorage.containsKey(caller())) {
throw { Unauthorized: "Unauthorized access!" };
}
const currentState = this.uploadStates.get(fileId);
if (!currentState) {
throw { NotKnown: "Upload not initialized" };
}
if ("NotStarted" in currentState) {
throw { NotKnown: "Upload not started" };
}
if ("Completed" in currentState) {
throw { NotKnown: "Upload already completed" };
}
this.uploadStates.set(fileId, { Completed: null });
const file = this.fileStorage.get(fileId);
if (!file) {
throw { NotFound: "File not found" };
}
return {
Ok: {
id: file.id,
name: file.name,
canisterId: id().toText(),
},
};
} catch (error) {
return { Err: handleError(error) };
}
}
// Retrieve a specific file chunk
@query([IDL.Text], FileChunkResult)
getFile(fileId: string, chunkNumber: bigint): FileChunkResult {
try {
if (!this.serviceStorage.containsKey(caller())) {
throw { Unauthorized: "Unauthorized access!" };
}
const file = this.fileStorage.get(fileId);
if (!file) {
throw { NotFound: `File with ID ${fileId} not found.` };
}
const chunkKey = `${fileId}-${chunkNumber}`;
const chunk = this.chunkStorage.get(chunkKey);
if (!chunk) {
throw {
NotFound: `Chunk ${chunkNumber} not found for file ${fileId}.`,
};
}
const hasNext = chunkNumber + 1n < file.chunkCount;
return {
Ok: {
id: file.id,
name: file.name,
chunk,
hasNext,
},
};
} catch (error) {
return { Err: handleError(error) };
}
}
private async checkCanisterFreeSpace(fileSize: bigint): Promise<boolean> {
try {
const status = await await call("aaaaa-aa", "canister_status", {
paramIdlTypes: [CanisterStatusArgs],
returnIdlType: CanisterStatusResult,
args: [{ canister_id: id() }],
});
if (!status || !status.memory_size) {
throw new Error("Unable to retrieve canister status or memory size.");
}
const storageLimit = BigInt(500 * 1024 * 1024 * 1024); // Set limit to 500 GiB
const threshold = (storageLimit * BigInt(90)) / BigInt(100); // Use only up to 90% of storage
const usedStorage = BigInt(status.memory_size.toString());
// Check if there is enough space for the file
return threshold - usedStorage >= fileSize;
} catch (error) {
return false; // Default to false in case of an error
}
}
}
Dependencies:
"@dfinity/agent": "^2.1.3",
"@dfinity/candid": "^2.1.3",
"azle": "^0.24.1",
Any guidance or recommendations would be greatly appreciated! Thank you in advance!