@iexec/dataprotector
Version:
This product enables users to confidentially store data–such as mail address, documents, personal information ...
196 lines (178 loc) • 6.22 kB
text/typescript
import { ObjectNotFoundError } from 'iexec/errors';
import { handleIfProtocolError, ValidationError } from '../../utils/errors.js';
import {
booleanSchema,
bulkRequestSchema,
stringSchema,
throwIfMissing,
} from '../../utils/validators.js';
import {
BulkRequest,
DefaultWorkerpoolConsumer,
InspectBulkRequestParams,
InspectBulkRequestResponse,
TaskStatus,
} from '../types/index.js';
import { IExecConsumer, PocoSubgraphConsumer } from '../types/internalTypes.js';
import { getProtectedDataInBulk } from './getProtectedDataInBulk.js';
import { getResultFromCompletedTask } from './getResultFromCompletedTask.js';
export type InspectBulkRequest = typeof inspectBulkRequest;
export const inspectBulkRequest = async <
Params extends InspectBulkRequestParams
>({
iexec = throwIfMissing(),
pocoSubgraphClient = throwIfMissing(),
bulkRequest,
withResult = false,
path,
detailed = false,
pemPrivateKey,
}: IExecConsumer &
PocoSubgraphConsumer &
DefaultWorkerpoolConsumer &
Params): Promise<InspectBulkRequestResponse<Params>> => {
const vRequestorder = bulkRequestSchema()
.label('bulkRequest')
.required()
.validateSync(bulkRequest) as BulkRequest; // Type assertion after validation
const vWithResult = booleanSchema()
.label('withResult')
.validateSync(withResult);
const vDetailed = booleanSchema().label('detailed').validateSync(detailed);
const vPath = stringSchema().label('path').validateSync(path);
const vPemPrivateKey = stringSchema()
.label('pemPrivateKey')
.validateSync(pemPrivateKey);
if (vPath && !vWithResult) {
throw new ValidationError(
'path parameter is only allowed when withResult is true'
);
}
if (vWithResult) {
const iexecResultEncryption =
// JSON parse safe thanks to bulkRequestSchema validation
JSON.parse(vRequestorder.params)?.iexec_result_encryption === true;
// Validate that pemPrivateKey is provided if iexec_result_encryption is true
if (iexecResultEncryption && !vPemPrivateKey) {
throw new ValidationError(
'Missing pemPrivateKey required for result decryption'
);
}
}
try {
const tasksTotalCount = Number(vRequestorder.volume);
const requestorderHash = await iexec.order.hashRequestorder(vRequestorder);
const getRemainingVolume = async (): Promise<number> => {
const contractsClient = await iexec.config.resolveContractsClient();
const poco = contractsClient.getIExecContract();
const consumed = (await poco.viewConsumed(requestorderHash)) as bigint;
return tasksTotalCount - Number(consumed);
};
const [{ deals }, tasksToCreateCount] = await Promise.all([
iexec.deal.fetchDealsByRequestorder(
requestorderHash,
{ pageSize: Math.max(tasksTotalCount, 10) } // Fetch all deals (min page size 10)
),
getRemainingVolume(),
]);
const tasks: InspectBulkRequestResponse<Params>['tasks'] = [];
let taskProtectedDataAddressesMap: Record<
string,
{ protectedDataAddresses: string[] }
> = {};
if (vDetailed) {
taskProtectedDataAddressesMap = await getProtectedDataInBulk({
pocoSubgraphClient,
bulkRequestHash: requestorderHash,
});
}
for (const deal of deals) {
const dealTasks = await Promise.all(
new Array(deal.botSize).fill(deal.botFirst).map(async (botFirst, i) => {
const taskId = await iexec.deal.computeTaskId(
deal.dealid,
botFirst + i
);
let error: Error | undefined;
let success: boolean | undefined;
const { statusName } = await iexec.task.show(taskId).catch((e) => {
if (e instanceof ObjectNotFoundError) {
// task is not yet initialized
return { statusName: 'UNSET' };
}
throw e;
});
let result: InspectBulkRequestResponse<Params>['tasks'][number]['result'];
if (statusName === 'FAILED' || statusName === 'TIMEOUT') {
success = false;
error = new Error(
`Task ${taskId} has failed with status ${statusName}`
);
} else if (statusName === 'COMPLETED') {
success = true;
if (vWithResult) {
try {
const taskResult = await getResultFromCompletedTask({
iexec,
taskId,
path: vPath,
pemPrivateKey: vPemPrivateKey,
});
result =
taskResult.result as InspectBulkRequestResponse<Params>['tasks'][number]['result'];
} catch (e) {
error = e as Error;
}
}
}
const task = {
taskId: taskId,
dealId: deal.dealid,
bulkIndex: botFirst + i,
status: statusName as TaskStatus,
success,
...(vDetailed &&
statusName !== 'UNSET' && {
protectedDataAddresses:
taskProtectedDataAddressesMap[taskId]?.protectedDataAddresses,
}),
...(vWithResult && { result }),
error,
} as InspectBulkRequestResponse<Params>['tasks'][number];
return task;
})
);
tasks.push(...dealTasks);
}
tasks.sort((a, b) => a.bulkIndex - b.bulkIndex);
const tasksProcessingCount = tasks.filter(
(task) =>
task.status === 'UNSET' ||
task.status === 'ACTIVE' ||
task.status === 'REVEALING'
).length;
const tasksCompletedCount = tasks.filter(
(task) =>
task.status === 'COMPLETED' ||
task.status === 'FAILED' ||
task.status === 'TIMEOUT'
).length;
const bulkStatus =
tasksCompletedCount === tasksTotalCount
? 'FINISHED'
: tasksToCreateCount > 0
? 'INITIALIZING'
: 'IN_PROGRESS';
return {
bulkStatus,
tasksProcessingCount,
tasksToCreateCount,
tasksCompletedCount,
tasksTotalCount,
tasks,
};
} catch (error) {
handleIfProtocolError(error);
throw error;
}
};