Skip to content

Commit c2ce92d

Browse files
fix!: ensure iapp exit 0 and update outputs
- App always create result files - upon success `result.json` contains `"success": true` (rather than `"status": 200`) - upon failure `result.json` contains `"success": false` and the error message in `"error"` (rather than `"status": 4xx | 5xx`)
1 parent bbf7b05 commit c2ce92d

File tree

9 files changed

+453
-436
lines changed

9 files changed

+453
-436
lines changed

dapp/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ fill in the environment variables:
1212

1313
- **IEXEC_IN**: The path to the input directory on your local machine where the unencrypted data .zip file will be stored. This file contains the telegram chat ID address to which the message will be sent.
1414
- **IEXEC_OUT**: The path on your local machine where the result of the Dapp execution will be written.
15-
- **IEXEC_DATASET_FILENAME**: The name of the data file that you place in the **IEXEC_IN** directory.
15+
- data to process
16+
- either **IEXEC_DATASET_FILENAME**: The name of the data file that you place in the **IEXEC_IN** directory.
17+
- or multiple datasets using:
18+
- **IEXEC_BULK_SLICE_SIZE**
19+
- **IEXEC_DATASET\_\<index\>\_FILENAME**: The name of the data file for dataset at index <index> (starting from 1) that you place in the **IEXEC_IN** directory.
1620
- **IEXEC_APP_DEVELOPER_SECRET**: A JSON string with the following keys:
1721
- **TELEGRAM_BOT_TOKEN**: The API key of the telegram bot used to send the message.
1822
- **IEXEC_REQUESTER_SECRET_1**: A JSON string with the following keys:

dapp/src/decryptContent.js

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,43 @@ export const downloadEncryptedContent = async (
1616
}
1717
const arrayBuffer = await res.arrayBuffer();
1818
return new Uint8Array(arrayBuffer);
19-
} catch (error) {
20-
console.error('Error downloading encrypted content:', error);
21-
throw error;
19+
} catch {
20+
throw new Error('Failed to download encrypted content');
2221
}
2322
};
2423

2524
export const decryptContent = (encryptedContent, encryptionKey) => {
26-
const ivBytes = encryptedContent.slice(0, 16);
27-
let ciphertextBytes = encryptedContent.slice(16);
28-
const key = forge.util.createBuffer(Buffer.from(encryptionKey, 'base64'));
29-
const decipher = forge.cipher.createDecipher('AES-CBC', key);
30-
31-
decipher.start({ iv: forge.util.createBuffer(ivBytes) });
25+
try {
26+
const ivBytes = encryptedContent.slice(0, 16);
27+
let ciphertextBytes = encryptedContent.slice(16);
28+
const key = forge.util.createBuffer(Buffer.from(encryptionKey, 'base64'));
29+
const decipher = forge.cipher.createDecipher('AES-CBC', key);
30+
31+
decipher.start({ iv: forge.util.createBuffer(ivBytes) });
32+
33+
const CHUNK_SIZE = 10 * 1000 * 1000;
34+
let decryptedBuffer = Buffer.from([]);
35+
36+
while (ciphertextBytes.length > 0) {
37+
// flush the decipher buffer
38+
decryptedBuffer = Buffer.concat([
39+
decryptedBuffer,
40+
Buffer.from(decipher.output.getBytes(), 'binary'),
41+
]);
42+
const chunk = ciphertextBytes.slice(0, CHUNK_SIZE);
43+
ciphertextBytes = ciphertextBytes.slice(CHUNK_SIZE);
44+
decipher.update(forge.util.createBuffer(chunk));
45+
}
3246

33-
const CHUNK_SIZE = 10 * 1000 * 1000;
34-
let decryptedBuffer = Buffer.from([]);
47+
decipher.finish();
3548

36-
while (ciphertextBytes.length > 0) {
37-
// flush the decipher buffer
3849
decryptedBuffer = Buffer.concat([
3950
decryptedBuffer,
4051
Buffer.from(decipher.output.getBytes(), 'binary'),
4152
]);
42-
const chunk = ciphertextBytes.slice(0, CHUNK_SIZE);
43-
ciphertextBytes = ciphertextBytes.slice(CHUNK_SIZE);
44-
decipher.update(forge.util.createBuffer(chunk));
45-
}
4653

47-
decipher.finish();
48-
49-
decryptedBuffer = Buffer.concat([
50-
decryptedBuffer,
51-
Buffer.from(decipher.output.getBytes(), 'binary'),
52-
]);
53-
54-
return decryptedBuffer.toString();
54+
return decryptedBuffer.toString();
55+
} catch {
56+
throw new Error('Failed to decrypt content');
57+
}
5558
};

dapp/src/executeTask.js

Lines changed: 95 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { IExecDataProtectorDeserializer } from '@iexec/dataprotector-deserializer';
21
import { promises as fs } from 'fs';
2+
import { IExecDataProtectorDeserializer } from '@iexec/dataprotector-deserializer';
33
import { decryptContent, downloadEncryptedContent } from './decryptContent.js';
44
import sendTelegram from './telegramService.js';
55
import {
@@ -9,58 +9,57 @@ import {
99
validateWorkerEnv,
1010
} from './validation.js';
1111

12-
async function writeTaskOutput(path, message) {
13-
try {
14-
await fs.writeFile(path, message);
15-
console.log(`File successfully written at path: ${path}`);
16-
} catch {
17-
console.error('Failed to write Task Output');
18-
process.exit(1);
19-
}
20-
}
21-
2212
async function processProtectedData({
2313
index,
2414
IEXEC_IN,
2515
appDeveloperSecret,
2616
requesterSecret,
2717
}) {
2818
const datasetFilename =
29-
index > 0 ? process.env[`IEXEC_DATASET_${index}_FILENAME`] : null;
30-
31-
let protectedData;
19+
index > 0
20+
? process.env[`IEXEC_DATASET_${index}_FILENAME`]
21+
: process.env.IEXEC_DATASET_FILENAME;
22+
const result = { index, protectedData: datasetFilename };
3223
try {
33-
const deserializerConfig = datasetFilename
34-
? { protectedDataPath: `${IEXEC_IN}/${datasetFilename}` }
35-
: {};
36-
37-
const deserializer = new IExecDataProtectorDeserializer(deserializerConfig);
38-
protectedData = {
39-
chatId: await deserializer.getValue('telegram_chatId', 'string'),
40-
};
41-
} catch (e) {
42-
throw Error(`Failed to parse ProtectedData ${index}: ${e.message}`);
43-
}
24+
let protectedData;
25+
try {
26+
const deserializerConfig = datasetFilename
27+
? { protectedDataPath: `${IEXEC_IN}/${datasetFilename}` }
28+
: {};
29+
30+
const deserializer = new IExecDataProtectorDeserializer(
31+
deserializerConfig
32+
);
33+
protectedData = {
34+
chatId: await deserializer.getValue('telegram_chatId', 'string'),
35+
};
36+
} catch (e) {
37+
throw Error(`Failed to parse ProtectedData ${index}: ${e.message}`);
38+
}
4439

45-
validateProtectedData(protectedData);
40+
validateProtectedData(protectedData);
4641

47-
const encryptedTelegramContent = await downloadEncryptedContent(
48-
requesterSecret.telegramContentMultiAddr
49-
);
50-
51-
const telegramContent = decryptContent(
52-
encryptedTelegramContent,
53-
requesterSecret.telegramContentEncryptionKey
54-
);
42+
const encryptedTelegramContent = await downloadEncryptedContent(
43+
requesterSecret.telegramContentMultiAddr
44+
);
5545

56-
const response = await sendTelegram({
57-
chatId: protectedData.chatId,
58-
message: telegramContent,
59-
botToken: appDeveloperSecret.TELEGRAM_BOT_TOKEN,
60-
senderName: requesterSecret.senderName,
61-
});
46+
const telegramContent = decryptContent(
47+
encryptedTelegramContent,
48+
requesterSecret.telegramContentEncryptionKey
49+
);
6250

63-
return { index, response };
51+
await sendTelegram({
52+
chatId: protectedData.chatId,
53+
message: telegramContent,
54+
botToken: appDeveloperSecret.TELEGRAM_BOT_TOKEN,
55+
senderName: requesterSecret.senderName,
56+
});
57+
result.success = true;
58+
} catch (e) {
59+
result.success = false;
60+
result.error = e.message;
61+
}
62+
return result;
6463
}
6564

6665
async function start() {
@@ -74,95 +73,71 @@ async function start() {
7473

7574
const workerEnv = validateWorkerEnv({ IEXEC_OUT });
7675

77-
let appDeveloperSecret;
76+
let result; // { success: boolean, error?: string , protectedData?: string, results?: { index: number, protectedData: string, success: boolean, error?: string }[] }
7877
try {
79-
appDeveloperSecret = JSON.parse(IEXEC_APP_DEVELOPER_SECRET);
80-
} catch {
81-
throw Error('Failed to parse the developer secret');
82-
}
83-
appDeveloperSecret = validateAppSecret(appDeveloperSecret);
84-
85-
let requesterSecret;
86-
try {
87-
requesterSecret = IEXEC_REQUESTER_SECRET_1
88-
? JSON.parse(IEXEC_REQUESTER_SECRET_1)
89-
: {};
90-
} catch {
91-
throw Error('Failed to parse requester secret');
92-
}
93-
requesterSecret = validateRequesterSecret(requesterSecret);
78+
let appDeveloperSecret;
79+
try {
80+
appDeveloperSecret = JSON.parse(IEXEC_APP_DEVELOPER_SECRET);
81+
} catch {
82+
throw Error('Failed to parse the developer secret');
83+
}
84+
appDeveloperSecret = validateAppSecret(appDeveloperSecret);
9485

95-
const bulkSize = parseInt(IEXEC_BULK_SLICE_SIZE, 10) || 0;
86+
let requesterSecret;
87+
try {
88+
requesterSecret = JSON.parse(IEXEC_REQUESTER_SECRET_1);
89+
} catch {
90+
throw Error('Failed to parse requester secret');
91+
}
9692

97-
// Process multiple protected data
98-
if (bulkSize > 0) {
99-
const promises = [];
100-
for (let index = 1; index <= bulkSize; index += 1) {
101-
const promise = processProtectedData({
102-
index,
93+
requesterSecret = validateRequesterSecret(requesterSecret);
94+
95+
const bulkSize = parseInt(IEXEC_BULK_SLICE_SIZE, 10) || 0;
96+
97+
// Process multiple protected data
98+
if (bulkSize > 0) {
99+
const processPromises = new Array(bulkSize).fill(null).map((_, index) =>
100+
processProtectedData({
101+
index: index + 1,
102+
IEXEC_IN,
103+
appDeveloperSecret,
104+
requesterSecret,
105+
})
106+
);
107+
const results = await Promise.all(processPromises);
108+
const successCount = results.filter((r) => r.success === true).length;
109+
const errorCount = results.filter((r) => r.success !== true).length;
110+
result = {
111+
success: errorCount === 0,
112+
error: errorCount > 0 ? 'Partial failure' : undefined,
113+
totalCount: results.length,
114+
successCount,
115+
errorCount,
116+
results: results.map((r) => ({
117+
index: r.index,
118+
protectedData: r.protectedData,
119+
success: r.success,
120+
error: r.error,
121+
})),
122+
};
123+
} else {
124+
const { protectedData, success, error } = await processProtectedData({
125+
index: 0,
103126
IEXEC_IN,
104127
appDeveloperSecret,
105128
requesterSecret,
106-
}).catch((error) => {
107-
const datasetFilename = process.env[`IEXEC_DATASET_${index}_FILENAME`];
108-
return {
109-
index,
110-
resultFileName: datasetFilename
111-
? `${datasetFilename}.txt`
112-
: `dataset-${index}.txt`,
113-
response: {
114-
status: 500,
115-
message: `Failed to process protected-data ${index}. Cause: ${error.message}`,
116-
},
117-
};
118129
});
119-
120-
promises.push(promise);
130+
result = { protectedData, success, error };
121131
}
122-
123-
const results = await Promise.all(promises);
124-
125-
// Write result.json for bulk processing
126-
const successCount = results.filter(
127-
(r) => r.response.status === 200
128-
).length;
129-
const errorCount = results.filter((r) => r.response.status !== 200).length;
130-
131-
const bulkResult = {
132-
message: `Bulk processing completed: ${successCount} successful, ${errorCount} failed`,
133-
'total-count': results.length,
134-
'success-count': successCount,
135-
'error-count': errorCount,
136-
results: results.map((r) => ({
137-
index: r.index,
138-
'protected-data':
139-
process.env[`IEXEC_DATASET_${r.index}_FILENAME`] ||
140-
`dataset-${r.index}`,
141-
response: r.response,
142-
})),
143-
};
144-
145-
await writeTaskOutput(
146-
`${workerEnv.IEXEC_OUT}/result.json`,
147-
JSON.stringify(bulkResult, null, 2)
148-
);
149-
} else {
150-
// Process single protected data
151-
const result = await processProtectedData({
152-
index: 0,
153-
IEXEC_IN,
154-
appDeveloperSecret,
155-
requesterSecret,
156-
});
157-
158-
await writeTaskOutput(
159-
`${workerEnv.IEXEC_OUT}/result.json`,
160-
JSON.stringify(result.response, null, 2)
161-
);
132+
} catch (e) {
133+
result = { success: false, error: e.message };
162134
}
163135

164-
// Generate computed.json - same format for both single and bulk
165-
await writeTaskOutput(
136+
await fs.writeFile(
137+
`${workerEnv.IEXEC_OUT}/result.json`,
138+
JSON.stringify(result, null, 2)
139+
);
140+
await fs.writeFile(
166141
`${workerEnv.IEXEC_OUT}/computed.json`,
167142
JSON.stringify(
168143
{

0 commit comments

Comments
 (0)