Skip to content

Commit 38ec12b

Browse files
feat(iapp): add bulk processing support
1 parent b7c073a commit 38ec12b

File tree

2 files changed

+378
-46
lines changed

2 files changed

+378
-46
lines changed

dapp/src/sendEmail.js

Lines changed: 152 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -28,48 +28,36 @@ async function writeTaskOutput(path, message) {
2828
}
2929
}
3030

31-
async function start() {
32-
const { IEXEC_OUT, IEXEC_APP_DEVELOPER_SECRET, IEXEC_REQUESTER_SECRET_1 } =
33-
process.env;
34-
35-
const workerEnv = validateWorkerEnv({ IEXEC_OUT });
36-
37-
// Parse and validate app secrets
38-
let appDeveloperSecret;
39-
try {
40-
appDeveloperSecret = JSON.parse(IEXEC_APP_DEVELOPER_SECRET);
41-
appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS =
42-
appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS
43-
? JSON.parse(appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS)
44-
: undefined;
45-
} catch (e) {
46-
throw new Error('Failed to parse the developer secret');
31+
async function processProtectedData(
32+
index,
33+
{
34+
IEXEC_IN,
35+
IEXEC_OUT,
36+
appDeveloperSecret,
37+
requesterSecret,
38+
datasetFilename = null,
4739
}
48-
appDeveloperSecret = validateAppSecret(appDeveloperSecret);
49-
50-
// Parse and validate requester secrets
51-
let requesterSecret;
40+
) {
41+
// Parse the protected data
42+
let protectedData;
5243
try {
53-
requesterSecret = IEXEC_REQUESTER_SECRET_1
54-
? JSON.parse(IEXEC_REQUESTER_SECRET_1)
44+
const deserializerConfig = datasetFilename
45+
? { protectedDataPath: `${IEXEC_IN}/${datasetFilename}` }
5546
: {};
56-
} catch {
57-
throw new Error('Failed to parse requester secret');
58-
}
59-
requesterSecret = validateRequesterSecret(requesterSecret);
6047

61-
// Decrypt protected email
62-
let protectedData;
63-
try {
64-
const deserializer = new IExecDataProtectorDeserializer();
48+
const deserializer = new IExecDataProtectorDeserializer(deserializerConfig);
6549
protectedData = {
6650
email: await deserializer.getValue('email', 'string'),
6751
};
6852
} catch (e) {
69-
throw Error(`Failed to parse ProtectedData: ${e.message}`);
53+
const errorMessage =
54+
index === 0
55+
? `Failed to parse ProtectedData: ${e.message}`
56+
: `Failed to parse ProtectedData ${index}: ${e.message}`;
57+
throw Error(errorMessage);
7058
}
7159

72-
// Validate format
60+
// Validate the protected data
7361
validateProtectedData(protectedData);
7462

7563
// Step 1: Check if email was already validated
@@ -122,24 +110,142 @@ async function start() {
122110
senderName: requesterSecret.senderName,
123111
});
124112

125-
// Step 5: Encode result as ABI-style bool
126-
const bool32Bytes = Buffer.alloc(32);
127-
if (isEmailValidated) {
128-
bool32Bytes[31] = 1; // set last byte to 1 for true
113+
if (index === 0) {
114+
await writeTaskOutput(
115+
`${IEXEC_OUT}/result.txt`,
116+
JSON.stringify(response, null, 2)
117+
);
118+
}
119+
120+
return { index, response, isEmailValidated };
121+
}
122+
123+
async function start() {
124+
const {
125+
IEXEC_IN,
126+
IEXEC_OUT,
127+
IEXEC_APP_DEVELOPER_SECRET,
128+
IEXEC_REQUESTER_SECRET_1,
129+
IEXEC_BULK_SLICE_SIZE,
130+
} = process.env;
131+
132+
// Check worker env
133+
const workerEnv = validateWorkerEnv({ IEXEC_OUT });
134+
135+
// Parse the app developer secret environment variable
136+
let appDeveloperSecret;
137+
try {
138+
appDeveloperSecret = JSON.parse(IEXEC_APP_DEVELOPER_SECRET);
139+
appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS =
140+
appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS
141+
? JSON.parse(appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS)
142+
: undefined;
143+
} catch {
144+
throw Error('Failed to parse the developer secret');
145+
}
146+
appDeveloperSecret = validateAppSecret(appDeveloperSecret);
147+
148+
// Parse the requester secret environment variable
149+
let requesterSecret;
150+
try {
151+
requesterSecret = IEXEC_REQUESTER_SECRET_1
152+
? JSON.parse(IEXEC_REQUESTER_SECRET_1)
153+
: {};
154+
} catch {
155+
throw Error('Failed to parse requester secret');
156+
}
157+
requesterSecret = validateRequesterSecret(requesterSecret);
158+
159+
const bulkSize = parseInt(IEXEC_BULK_SLICE_SIZE, 10) || 0;
160+
const results = [];
161+
162+
if (bulkSize > 0) {
163+
// Process multiple protected data
164+
const promises = [];
165+
for (let i = 1; i <= bulkSize; i += 1) {
166+
const datasetFilename = process.env[`IEXEC_DATASET_${i}_FILENAME`];
167+
168+
const promise = processProtectedData(i, {
169+
IEXEC_IN,
170+
IEXEC_OUT: workerEnv.IEXEC_OUT,
171+
appDeveloperSecret,
172+
requesterSecret,
173+
datasetFilename,
174+
})
175+
.then((result) => result)
176+
.catch((error) => ({
177+
index: i,
178+
resultFileName: datasetFilename
179+
? `${datasetFilename}.txt`
180+
: `dataset-${i}.txt`,
181+
response: {
182+
status: 500,
183+
message: `Failed to process dataset ${i}: ${error.message}`,
184+
},
185+
}));
186+
187+
promises.push(promise);
188+
}
189+
190+
const bulkResults = await Promise.all(promises);
191+
results.push(...bulkResults);
192+
} else {
193+
// Process single protected data
194+
const result = await processProtectedData(0, {
195+
IEXEC_IN,
196+
IEXEC_OUT: workerEnv.IEXEC_OUT,
197+
appDeveloperSecret,
198+
requesterSecret,
199+
});
200+
201+
results.push(result);
202+
}
203+
204+
// Create result.txt for bulk processing
205+
if (bulkSize > 0) {
206+
const successCount = results.filter(
207+
(r) => r.response.status === 200
208+
).length;
209+
const errorCount = results.filter((r) => r.response.status !== 200).length;
210+
211+
const bulkResult = {
212+
message: `Bulk processing completed: ${successCount} successful, ${errorCount} failed`,
213+
status: 200,
214+
'total-processed': results.length,
215+
'success-count': successCount,
216+
'error-count': errorCount,
217+
'dataset-results': results.map((r) => ({
218+
index: r.index,
219+
dataset:
220+
process.env[`IEXEC_DATASET_${r.index}_FILENAME`] ||
221+
`dataset-${r.index}`,
222+
response: r.response,
223+
})),
224+
};
225+
226+
await writeTaskOutput(
227+
`${workerEnv.IEXEC_OUT}/result.txt`,
228+
JSON.stringify(bulkResult, null, 2)
229+
);
230+
}
231+
232+
const computedData = {
233+
'deterministic-output-path': `${workerEnv.IEXEC_OUT}/result.txt`,
234+
};
235+
236+
// Add callback data for single processing if useCallback is enabled
237+
if (bulkSize === 0 && requesterSecret.useCallback && results[0]) {
238+
const bool32Bytes = Buffer.alloc(32);
239+
if (results[0].isEmailValidated) {
240+
bool32Bytes[31] = 1; // set last byte to 1 for true
241+
}
242+
const callbackData = `0x${bool32Bytes.toString('hex')}`;
243+
computedData['callback-data'] = callbackData;
129244
}
130-
const callbackData = `0x${bool32Bytes.toString('hex')}`;
131245

132-
// Write outputs
133-
await writeTaskOutput(
134-
`${workerEnv.IEXEC_OUT}/result.txt`,
135-
JSON.stringify(response, null, 2)
136-
);
137246
await writeTaskOutput(
138247
`${workerEnv.IEXEC_OUT}/computed.json`,
139-
JSON.stringify({
140-
'deterministic-output-path': `${workerEnv.IEXEC_OUT}/result.txt`,
141-
...(requesterSecret.useCallback && { 'callback-data': callbackData }),
142-
})
248+
JSON.stringify(computedData, null, 2)
143249
);
144250
}
145251

0 commit comments

Comments
 (0)