From 68ff04e8ddae910040e7ec8b2fca3ea9e6a4175b Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Mon, 22 Dec 2025 11:26:16 +0100 Subject: [PATCH 1/8] feat: add inspectBulkRequest method --- packages/sdk/package.json | 3 +- .../sdk/src/lib/IExecDataProtectorModule.ts | 16 +- .../IExecDataProtectorCore.ts | 35 +- .../getProtectedDataInBulk.ts | 41 ++ .../dataProtectorCore/inspectBulkRequest.ts | 194 ++++++++ ...getProtectedDataInBulkByBulkRequestHash.ts | 47 ++ packages/sdk/src/lib/types/coreTypes.ts | 121 ++++- packages/sdk/src/lib/types/internalTypes.ts | 4 + packages/sdk/src/utils/getChainId.ts | 33 +- packages/sdk/src/utils/validators.ts | 2 +- packages/sdk/tests/docker-compose.yml | 17 + .../inspectBulkRequest.test.ts | 448 ++++++++++++++++++ packages/sdk/tests/prepare-iexec.js | 29 ++ packages/sdk/tests/test-utils.ts | 2 + 14 files changed, 949 insertions(+), 43 deletions(-) create mode 100644 packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts create mode 100644 packages/sdk/src/lib/dataProtectorCore/inspectBulkRequest.ts create mode 100644 packages/sdk/src/lib/dataProtectorCore/subgraph/getProtectedDataInBulkByBulkRequestHash.ts create mode 100644 packages/sdk/tests/e2e/dataProtectorCore/inspectBulkRequest.test.ts create mode 100644 packages/sdk/tests/prepare-iexec.js diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 8c5b0f2a4..f21b5cdb8 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -17,6 +17,7 @@ "build": "rimraf dist && tsc --project tsconfig.build.json", "build:watch": "npm run build -- --watch", "check-types": "tsc --noEmit", + "test:prepare": "node tests/prepare-iexec.js", "test": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/**/*.test.ts\"", "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/**/*.test.ts\" --coverage", "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/unit/**/*.test.ts\"", @@ -32,7 +33,7 @@ "generate:abi": "rimraf generated/abis && node tools/generateAbiModules.mjs", "refresh-abis": "mkdir -p .tmp && cp -r abis/core/registry .tmp/ 2>/dev/null || true && rm -rf abis && mkdir -p abis/core && cp -r ../smart-contract/abis/. ./abis/core/ && if [ -d .tmp/registry ]; then mkdir -p abis/core/registry && cp -r .tmp/registry/. ./abis/core/registry/ && rm -rf .tmp; fi", "stop-test-stack": "cd tests && docker compose --project-name dataprotector-sdk down --volumes --remove-orphans", - "start-test-stack": "cd tests && npm run stop-test-stack && node prepare-test-env.js && docker compose --project-name dataprotector-sdk build && docker compose --project-name dataprotector-sdk up -d" + "start-test-stack": "cd tests && npm run stop-test-stack && node prepare-test-env.js && docker compose --project-name dataprotector-sdk build && docker compose --project-name dataprotector-sdk up -d && npm run test:prepare" }, "repository": { "type": "git", diff --git a/packages/sdk/src/lib/IExecDataProtectorModule.ts b/packages/sdk/src/lib/IExecDataProtectorModule.ts index 235a94c71..343aa4c4e 100644 --- a/packages/sdk/src/lib/IExecDataProtectorModule.ts +++ b/packages/sdk/src/lib/IExecDataProtectorModule.ts @@ -22,6 +22,7 @@ type EthersCompatibleProvider = interface IExecDataProtectorResolvedConfig { dataprotectorContractAddress: AddressOrENS; graphQLClient: GraphQLClient; + pocoSubgraphClient: GraphQLClient; ipfsNode: string; ipfsGateway: string; defaultWorkerpool: string; @@ -33,6 +34,8 @@ abstract class IExecDataProtectorModule { protected graphQLClient!: GraphQLClient; + protected pocoSubgraphClient!: GraphQLClient; + protected ipfsNode!: string; protected ipfsGateway!: string; @@ -62,6 +65,7 @@ abstract class IExecDataProtectorModule { this.initPromise = this.resolveConfig().then((config) => { this.dataprotectorContractAddress = config.dataprotectorContractAddress; this.graphQLClient = config.graphQLClient; + this.pocoSubgraphClient = config.pocoSubgraphClient; this.ipfsNode = config.ipfsNode; this.ipfsGateway = config.ipfsGateway; this.defaultWorkerpool = config.defaultWorkerpool; @@ -103,7 +107,9 @@ abstract class IExecDataProtectorModule { ); } - let iexec: IExec, graphQLClient: GraphQLClient; + let iexec: IExec, + graphQLClient: GraphQLClient, + pocoSubgraphClient: GraphQLClient; try { iexec = new IExec( @@ -125,6 +131,13 @@ abstract class IExecDataProtectorModule { throw new Error(`Failed to create GraphQLClient: ${error.message}`); } + try { + const pocoSubgraphURL = await iexec.config.resolvePocoSubgraphURL(); + pocoSubgraphClient = new GraphQLClient(pocoSubgraphURL); + } catch (error: any) { + throw new Error(`Failed to create PoCo GraphQLClient: ${error.message}`); + } + return { dataprotectorContractAddress: dataprotectorContractAddress.toLowerCase(), defaultWorkerpool, @@ -132,6 +145,7 @@ abstract class IExecDataProtectorModule { ipfsNode, ipfsGateway, iexec, + pocoSubgraphClient, }; } } diff --git a/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts b/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts index 071a61ad7..20208430b 100644 --- a/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts +++ b/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts @@ -1,4 +1,4 @@ -import { isValidProvider } from '../../utils/validators.js'; +import { isValidSigner } from '../../utils/validators.js'; import { IExecDataProtectorModule } from '../IExecDataProtectorModule.js'; import { GetGrantedAccessParams, @@ -23,11 +23,14 @@ import { WaitForTaskCompletionParams, PrepareBulkRequestParams, PrepareBulkRequestResponse, + InspectBulkRequestResponse, + InspectBulkRequestParams, } from '../types/index.js'; import { getGrantedAccess } from './getGrantedAccess.js'; import { getProtectedData } from './getProtectedData.js'; import { getResultFromCompletedTask } from './getResultFromCompletedTask.js'; import { grantAccess } from './grantAccess.js'; +import { inspectBulkRequest } from './inspectBulkRequest.js'; import { prepareBulkRequest } from './prepareBulkRequest.js'; import { processBulkRequest } from './processBulkRequest.js'; import { processProtectedData } from './processProtectedData.js'; @@ -42,7 +45,7 @@ class IExecDataProtectorCore extends IExecDataProtectorModule { args: ProtectDataParams ): Promise { await this.init(); - await isValidProvider(this.iexec); + await isValidSigner(this.iexec); return protectData({ ...args, dataprotectorContractAddress: this.dataprotectorContractAddress, @@ -55,25 +58,25 @@ class IExecDataProtectorCore extends IExecDataProtectorModule { async grantAccess(args: GrantAccessParams): Promise { await this.init(); - await isValidProvider(this.iexec); + await isValidSigner(this.iexec); return grantAccess({ ...args, iexec: this.iexec }); } async revokeOneAccess(args: GrantedAccess): Promise { await this.init(); - await isValidProvider(this.iexec); + await isValidSigner(this.iexec); return revokeOneAccess({ ...args, iexec: this.iexec }); } async revokeAllAccess(args: RevokeAllAccessParams): Promise { await this.init(); - await isValidProvider(this.iexec); + await isValidSigner(this.iexec); return revokeAllAccess({ ...args, iexec: this.iexec }); } async transferOwnership(args: TransferParams): Promise { await this.init(); - await isValidProvider(this.iexec); + await isValidSigner(this.iexec); return transferOwnership({ ...args, iexec: this.iexec }); } @@ -81,7 +84,7 @@ class IExecDataProtectorCore extends IExecDataProtectorModule { args: Params ): Promise> { await this.init(); - await isValidProvider(this.iexec); + await isValidSigner(this.iexec); return processProtectedData({ ...args, iexec: this.iexec, @@ -93,7 +96,7 @@ class IExecDataProtectorCore extends IExecDataProtectorModule { args: PrepareBulkRequestParams ): Promise { await this.init(); - await isValidProvider(this.iexec); + await isValidSigner(this.iexec); return prepareBulkRequest({ ...args, iexec: this.iexec, @@ -104,7 +107,7 @@ class IExecDataProtectorCore extends IExecDataProtectorModule { args: Params ): Promise> { await this.init(); - await isValidProvider(this.iexec); + await isValidSigner(this.iexec); return processBulkRequest({ ...args, iexec: this.iexec, @@ -131,11 +134,22 @@ class IExecDataProtectorCore extends IExecDataProtectorModule { return getGrantedAccess({ ...args, iexec: this.iexec }); } + async inspectBulkRequest( + args: Params + ): Promise> { + await this.init(); + return inspectBulkRequest({ + ...args, + iexec: this.iexec, + pocoSubgraphClient: this.pocoSubgraphClient, + defaultWorkerpool: this.defaultWorkerpool, + }); + } + async waitForTaskCompletion( args: WaitForTaskCompletionParams ): Promise { await this.init(); - await isValidProvider(this.iexec); return waitForTaskCompletion({ ...args, iexec: this.iexec, @@ -146,7 +160,6 @@ class IExecDataProtectorCore extends IExecDataProtectorModule { args: GetResultFromCompletedTaskParams ): Promise { await this.init(); - await isValidProvider(this.iexec); return getResultFromCompletedTask({ ...args, iexec: this.iexec, diff --git a/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts b/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts new file mode 100644 index 000000000..207bb767e --- /dev/null +++ b/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts @@ -0,0 +1,41 @@ +import { WorkflowError } from '../../utils/errors.js'; +import { throwIfMissing } from '../../utils/validators.js'; +import { PocoSubgraphConsumer } from '../types/internalTypes.js'; +import { getProtectedDataInBulkByBulkRequestHash } from './subgraph/getProtectedDataInBulkByBulkRequestHash.js'; + +export async function getProtectedDataInBulk({ + pocoSubgraphClient = throwIfMissing(), + bulkRequestHash = throwIfMissing(), +}: PocoSubgraphConsumer & { + bulkRequestHash: string; +}): Promise< + Record +> { + try { + const result = await getProtectedDataInBulkByBulkRequestHash({ + pocoSubgraphClient, + bulkRequestHash, + }); + + const tasks: Record< + string, + { dealId: string; protectedDataAddresses: string[] } + > = {}; + result.deals.forEach((deal) => { + deal.tasks.forEach((task) => { + tasks[task.taskId] = { + dealId: deal.dealId, + protectedDataAddresses: + task.bulkSlice?.datasets.map((dataset) => dataset.id) ?? undefined, + }; + }); + }); + return tasks; + } catch (e) { + console.log('[getProtectedDataInBulk] ERROR', e); + throw new WorkflowError({ + message: 'Failed to get protected data in bulk', + errorCause: e, + }); + } +} diff --git a/packages/sdk/src/lib/dataProtectorCore/inspectBulkRequest.ts b/packages/sdk/src/lib/dataProtectorCore/inspectBulkRequest.ts new file mode 100644 index 000000000..8c84d8a15 --- /dev/null +++ b/packages/sdk/src/lib/dataProtectorCore/inspectBulkRequest.ts @@ -0,0 +1,194 @@ +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> => { + 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 => { + 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['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['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['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 && { + protectedDataAddresses: + taskProtectedDataAddressesMap[taskId]?.protectedDataAddresses, + }), + ...(vWithResult && { result }), + error, + } as InspectBulkRequestResponse['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; + } +}; diff --git a/packages/sdk/src/lib/dataProtectorCore/subgraph/getProtectedDataInBulkByBulkRequestHash.ts b/packages/sdk/src/lib/dataProtectorCore/subgraph/getProtectedDataInBulkByBulkRequestHash.ts new file mode 100644 index 000000000..b33b85b32 --- /dev/null +++ b/packages/sdk/src/lib/dataProtectorCore/subgraph/getProtectedDataInBulkByBulkRequestHash.ts @@ -0,0 +1,47 @@ +import { gql } from 'graphql-request'; +import { throwIfMissing } from '../../../utils/validators.js'; +import { Address } from '../../types/commonTypes.js'; +import { PocoSubgraphConsumer } from '../../types/internalTypes.js'; + +export async function getProtectedDataInBulkByBulkRequestHash({ + pocoSubgraphClient = throwIfMissing(), + bulkRequestHash = throwIfMissing(), +}: PocoSubgraphConsumer & { + bulkRequestHash: string; +}) { + const dealsQuery = gql` + query ($where: Deal_filter) { + deals(where: $where) { + dealId: id + tasks { + taskId: id + bulkSlice { + datasets { + id + } + } + } + } + } + `; + const variables = { + where: { + requestorder: bulkRequestHash, + }, + }; + const res = await pocoSubgraphClient.request<{ + deals: Array<{ + dealId: string; + tasks: Array<{ + taskId: string; + bulkSlice?: { + datasets: Array<{ + id: Address; + }>; + }; + }>; + }>; + }>(dealsQuery, variables); + + return res; +} diff --git a/packages/sdk/src/lib/types/coreTypes.ts b/packages/sdk/src/lib/types/coreTypes.ts index ee6470434..77ab41baf 100644 --- a/packages/sdk/src/lib/types/coreTypes.ts +++ b/packages/sdk/src/lib/types/coreTypes.ts @@ -233,10 +233,11 @@ export type WaitForTaskCompletionParams = { onStatusUpdate?: OnStatusUpdateFn; }; -export type TaskStatus = 'COMPLETED' | 'FAILED' | 'TIMEOUT'; +export type TaskStatusFinal = 'COMPLETED' | 'FAILED' | 'TIMEOUT'; +export type TaskStatus = 'UNSET' | 'ACTIVE' | 'REVEALING' | TaskStatusFinal; export type WaitForTaskCompletionResponse = { - status: TaskStatus; + status: TaskStatusFinal; success: boolean; }; @@ -609,8 +610,122 @@ export type ProcessBulkRequestResponseWithResult = { dealId: string; bulkIndex: number; success: boolean; - status: TaskStatus; + status: TaskStatusFinal; result?: ArrayBuffer; error?: Error; }>; }; + +export type InspectBulkRequestParams = { + /** + * bulk request to inspect + */ + bulkRequest: BulkRequest; + + /** + * Whether to download results of completed tasks. + * @default false + */ + withResult?: boolean; + + /** + * Whether to include detailed information such as addresses of protectedData included in each tasks. + * @default false + */ + detailed?: boolean; + + /** + * Path to the result file in the app's output + * + * Ignored if `withResult` is `false` + */ + path?: string; + + /** + * Private key in PEM format for result decryption. + * + * Required if `bulkRequest` use results encryption and `withResult` is `true`. + */ + pemPrivateKey?: string; +}; + +export type InspectBulkRequestResponse = { + /** + * Status of the bulk request + * - "INITIALIZING": some tasks needs to be created + * - "IN_PROGRESS": all tasks have been created but some tasks execution are still pending + * - "FINISHED": all tasks have reached a final execution status (COMPLETED, FAILED or TIMEOUT) + */ + bulkStatus: 'INITIALIZING' | 'IN_PROGRESS' | 'FINISHED'; + /** + * Number of tasks remaining to create + */ + tasksToCreateCount: number; + /** + * Number of tasks being processed (created but not yet completed) + */ + tasksProcessingCount: number; + /** + * Number of tasks completed + */ + tasksCompletedCount: number; + /** + * Number of tasks in the bulk request + */ + tasksTotalCount: number; + /** + * tasks details + */ + tasks: Array<{ + /** + * id of the task + */ + taskId: string; + /** + * id of the deal containing the task + */ + dealId: string; + /** + * index of the task in the bulk request + */ + bulkIndex: number; + /** + * addresses of the protected data processed by the task + * + * NB: present only if `detailed` is true when task status is already initialized (ie: task status is not 'UNSET') + */ + protectedDataAddresses: T extends { detailed: true } + ? Address[] | undefined + : never; + /** + * status of the task + * - "UNSET": task is not yet initialized + * - "ACTIVE": task is being processed + * - "REVEALING": task has been processed, waiting for result reveal + * - "COMPLETED": task has been completed, result is available + * - "TIMEOUT": task execution has timed out and can be claimed for refund + * - "FAILED": task execution has failed and execution has been refunded + */ + status: TaskStatus; + /** + * indicates if the task has been successfully completed or not + * - undefined: task did not yet reach a final status + * - true: task completed successfully + * - false: task failed or timed out + */ + success?: boolean; + /** + * error encountered during task execution, result download or decryption + */ + error?: Error; + /** + * result file content + * + * NB: + * - present only if `withResult` is true and task status is "COMPLETED" + * - requires `pemPrivateKey` if the bulk request was created with `encryptResult: true` + * - returns the content of the root ZIP file unless `path` is specified in the params + */ + result: T extends { withResult: true } ? ArrayBuffer : never; + }>; +}; diff --git a/packages/sdk/src/lib/types/internalTypes.ts b/packages/sdk/src/lib/types/internalTypes.ts index 56393435f..dd249371e 100644 --- a/packages/sdk/src/lib/types/internalTypes.ts +++ b/packages/sdk/src/lib/types/internalTypes.ts @@ -18,6 +18,10 @@ export type SubgraphConsumer = { graphQLClient: GraphQLClient; }; +export type PocoSubgraphConsumer = { + pocoSubgraphClient: GraphQLClient; +}; + export type VoucherInfo = { owner: Address; address: Address; diff --git a/packages/sdk/src/utils/getChainId.ts b/packages/sdk/src/utils/getChainId.ts index 02338c184..62779d36a 100644 --- a/packages/sdk/src/utils/getChainId.ts +++ b/packages/sdk/src/utils/getChainId.ts @@ -1,10 +1,5 @@ -import { - JsonRpcProvider, - BrowserProvider, - AbstractProvider, - AbstractSigner, - Eip1193Provider, -} from 'ethers'; +import { AbstractProvider, AbstractSigner, Eip1193Provider } from 'ethers'; +import { IExecNetworkModule } from 'iexec'; import { DEFAULT_CHAIN_ID } from '../config/config.js'; type EthersCompatibleProvider = @@ -17,25 +12,11 @@ export async function getChainIdFromProvider( ethProvider: EthersCompatibleProvider ): Promise { try { - if (typeof ethProvider === 'string') { - const provider = new JsonRpcProvider(ethProvider); - const network = await provider.getNetwork(); - return Number(network.chainId); - } else if (ethProvider instanceof AbstractProvider) { - const network = await ethProvider.getNetwork(); - return Number(network.chainId); - } else if (ethProvider instanceof AbstractSigner) { - const { provider } = ethProvider; - if (!provider) { - throw Error('Signer is not connected to a provider'); - } - const network = await provider.getNetwork(); - return Number(network.chainId); - } else if ('request' in ethProvider) { - const provider = new BrowserProvider(ethProvider as Eip1193Provider); - const network = await provider.getNetwork(); - return Number(network.chainId); - } + const networkModule = new IExecNetworkModule({ + ethProvider, + }); + const { chainId } = await networkModule.getNetwork(); + return Number(chainId); } catch (e) { console.warn('Failed to detect chainId:', e); } diff --git a/packages/sdk/src/utils/validators.ts b/packages/sdk/src/utils/validators.ts index e60958ccc..db244cd00 100644 --- a/packages/sdk/src/utils/validators.ts +++ b/packages/sdk/src/utils/validators.ts @@ -3,7 +3,7 @@ import { IExec } from 'iexec'; import { NULL_ADDRESS } from 'iexec/utils'; import { ValidationError, array, boolean, number, object, string } from 'yup'; -export const isValidProvider = async (iexec: IExec) => { +export const isValidSigner = async (iexec: IExec) => { const client = await iexec.config.resolveContractsClient(); if (!client.signer) { throw new Error( diff --git a/packages/sdk/tests/docker-compose.yml b/packages/sdk/tests/docker-compose.yml index 47b282387..c959f66a8 100644 --- a/packages/sdk/tests/docker-compose.yml +++ b/packages/sdk/tests/docker-compose.yml @@ -213,6 +213,21 @@ services: IPFS_URL: http://ipfs:5001 ENV: prod + poco-subgraph-deployer: + image: iexechub/poco-subgraph-deployer:v2.1.2 + restart: 'on-failure' + depends_on: + graphnode: + condition: service_healthy + ipfs: + condition: service_started + environment: + START_BLOCK: $BELLECOUR_FORK_BLOCK + NETWORK_NAME: bellecour + GRAPHNODE_URL: http://graphnode:8020 + IPFS_URL: http://ipfs:5001 + ENV: prod + stack-ready: image: bash command: @@ -232,3 +247,5 @@ services: condition: service_started dataprotector-subgraph-deployer: condition: service_completed_successfully + poco-subgraph-deployer: + condition: service_completed_successfully diff --git a/packages/sdk/tests/e2e/dataProtectorCore/inspectBulkRequest.test.ts b/packages/sdk/tests/e2e/dataProtectorCore/inspectBulkRequest.test.ts new file mode 100644 index 000000000..649c96eac --- /dev/null +++ b/packages/sdk/tests/e2e/dataProtectorCore/inspectBulkRequest.test.ts @@ -0,0 +1,448 @@ +import { beforeAll, describe, expect, it } from '@jest/globals'; +import { HDNodeWallet, Wallet } from 'ethers'; +import { IExec } from 'iexec'; +import { + BulkRequest, + GrantedAccess, + IExecDataProtectorCore, +} from '../../../src/index.js'; +import { + MAX_EXPECTED_BLOCKTIME, + MAX_EXPECTED_WEB2_SERVICES_TIME, + deployRandomApp, + getTestConfig, +} from '../../test-utils.js'; + +describe('dataProtectorCore.inspectBulkRequest()', () => { + let iexec: IExec; + let dataProtectorCore: IExecDataProtectorCore; + let wallet: HDNodeWallet; + + const grantedAccesses: GrantedAccess[] = []; + + let appAddress: string; + let workerpoolAddress: string; + + beforeAll(async () => { + wallet = Wallet.createRandom(); + dataProtectorCore = new IExecDataProtectorCore( + ...getTestConfig(wallet.privateKey) + ); + // create app & workerpool + const [ethProvider, options] = getTestConfig(wallet.privateKey); + appAddress = await deployRandomApp({ + ethProvider, + teeFramework: 'scone', + }); + iexec = new IExec({ ethProvider }, options.iexecOptions); + await iexec.order + .createApporder({ app: appAddress, volume: 1000, tag: ['tee', 'scone'] }) + .then(iexec.order.signApporder) + .then(iexec.order.publishApporder); + const { address: workerpool } = await iexec.workerpool.deployWorkerpool({ + description: 'test pool', + owner: await iexec.wallet.getAddress(), + }); + workerpoolAddress = workerpool; + await iexec.order + .createWorkerpoolorder({ + workerpool: workerpoolAddress, + category: 0, + volume: 1000, + tag: ['tee', 'scone'], + }) + .then(iexec.order.signWorkerpoolorder) + .then(iexec.order.publishWorkerpoolorder); + + const createTestData = async () => { + const protectedData = await dataProtectorCore.protectData({ + data: { email: 'example@example.com' }, + name: 'test do not use', + }); + const grantedAccess = await dataProtectorCore.grantAccess({ + authorizedApp: appAddress, + protectedData: protectedData.address, + authorizedUser: wallet.address, + allowBulk: true, + }); + return { + address: protectedData.address, + grantedAccess, + }; + }; + + for (let i = 0; i < 10; i++) { + const { grantedAccess } = await createTestData(); + grantedAccesses.push(grantedAccess); + } + }, 10 * 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME); + + describe('when bulk request is not yet processed', () => { + it('should inspect INITIALIZING bulk request successfully', async () => { + // --- GIVEN + const { bulkRequest } = await dataProtectorCore.prepareBulkRequest({ + app: appAddress, + bulkAccesses: grantedAccesses, + maxProtectedDataPerTask: 3, + }); + // --- WHEN + const inspectResponse = await dataProtectorCore.inspectBulkRequest({ + bulkRequest, + withResult: false, + detailed: true, + }); + + // --- THEN + expect(inspectResponse).toBeDefined(); + expect(inspectResponse.bulkStatus).toBe('INITIALIZING'); + expect(inspectResponse.tasksTotalCount).toBe(4); + expect(inspectResponse.tasksToCreateCount).toBe(4); + expect(inspectResponse.tasksProcessingCount).toBe(0); + expect(inspectResponse.tasksCompletedCount).toBe(0); + expect(inspectResponse.tasks).toHaveLength(0); + }, 60000); + }); + + describe('when bulk request is in progress', () => { + it( + 'should inspect IN_PROGRESS bulk request successfully', + async () => { + // --- GIVEN + const { bulkRequest } = await dataProtectorCore.prepareBulkRequest({ + app: appAddress, + bulkAccesses: grantedAccesses, + maxProtectedDataPerTask: 5, + }); + const {} = await dataProtectorCore.processBulkRequest({ + bulkRequest, + workerpool: workerpoolAddress, + checkInterval: 1_000, + timeout: 10_000, + }); + // --- WHEN + const inspectResponse = await dataProtectorCore.inspectBulkRequest({ + bulkRequest, + withResult: false, + detailed: true, + }); + + // --- THEN + expect(inspectResponse).toBeDefined(); + expect(inspectResponse.bulkStatus).toBe('IN_PROGRESS'); + expect(inspectResponse.tasksTotalCount).toBe(2); + expect(inspectResponse.tasksToCreateCount).toBe(0); + expect(inspectResponse.tasksProcessingCount).toBe(2); + expect(inspectResponse.tasksCompletedCount).toBe(0); + expect(inspectResponse.tasks).toHaveLength(2); + for (const task of inspectResponse.tasks) { + expect(task.status).toBe('UNSET'); + expect(task.protectedDataAddresses).toBeUndefined(); + expect(task.success).toBeUndefined(); + } + }, + MAX_EXPECTED_BLOCKTIME * 8 + MAX_EXPECTED_WEB2_SERVICES_TIME + ); + }); + + describe('when bulk request is finished', () => { + const dataProtectorCoreArbSepolia = new IExecDataProtectorCore( + 'arbitrum-sepolia-testnet' + ); + const bulkRequestArbSepolia: BulkRequest = { + app: '0x650386bA74015Cd0a67034D7f9035CD5e23b46BC', + appmaxprice: '0', + dataset: '0x0000000000000000000000000000000000000000', + datasetmaxprice: '0', + workerpool: '0xB967057a21dc6A66A29721d96b8Aa7454B7c383F', + workerpoolmaxprice: '100000000', + requester: '0xB0b552107C14A5e4BCf20f359DeF23Ac6003Abde', + volume: '2', + tag: '0x0000000000000000000000000000000000000000000000000000000000000003', + category: '0', + trust: '0', + beneficiary: '0xB0b552107C14A5e4BCf20f359DeF23Ac6003Abde', + callback: '0x0000000000000000000000000000000000000000', + params: + '{"iexec_secrets":{"1":"0xf5452d11237fab687de562d6905d1d1f"},"iexec_result_storage_provider":"ipfs","bulk_cid":"QmXoH2m21wvNckTAFBYQ3arVpuPCrXggZdLGTrc5GaBM5M","iexec_input_files":[],"iexec_args":"EXISTING"}', + salt: '0x40aac92300aacfc33d0664ac8f45465527d28aba0c91f799597828032490cb60', + sign: '0x7a469e82458e6a3ac728dd432321f8385f38c46f5f8513abe127f288b5c1296247dcd2c60704a483ee9b196d162d2d6f8f1fd8e10aa70ac3c65b039c25e7baa91b', + }; + + it('should inspect FINISHED bulk request successfully', async () => { + // --- GIVEN + // --- WHEN + const inspectResponse = + await dataProtectorCoreArbSepolia.inspectBulkRequest({ + bulkRequest: bulkRequestArbSepolia, + }); + + // --- THEN + expect(inspectResponse).toBeDefined(); + expect(inspectResponse.bulkStatus).toBe('FINISHED'); + expect(inspectResponse.tasksTotalCount).toBe(2); + expect(inspectResponse.tasksToCreateCount).toBe(0); + expect(inspectResponse.tasksProcessingCount).toBe(0); + expect(inspectResponse.tasksCompletedCount).toBe(2); + expect(inspectResponse.tasks).toHaveLength(2); + + const task0 = inspectResponse.tasks[0]; + expect(task0.taskId).toBe( + '0x333c91c3e90597c24ff69fd5fe3cec50c3d87a78ff4d3df7c0895b30777d0951' + ); + expect(task0.dealId).toBe( + '0xb76713c910641d7000e05ece0c7b77a2a6fef4f43b94811e2322e6f5ffefa773' + ); + expect(task0.bulkIndex).toBe(0); + expect(task0.status).toBe('COMPLETED'); + expect(task0.protectedDataAddresses).toBeUndefined(); + expect(task0.success).toBe(true); + + const task1 = inspectResponse.tasks[1]; + expect(task1.taskId).toBe( + '0xafef56f10938b865377002eaaa75a90545153f0aa5f0ba29101e10c3b1956675' + ); + expect(task1.dealId).toBe( + '0xb76713c910641d7000e05ece0c7b77a2a6fef4f43b94811e2322e6f5ffefa773' + ); + expect(task1.bulkIndex).toBe(1); + expect(task1.status).toBe('COMPLETED'); + expect(task1.protectedDataAddresses).toBeUndefined(); + expect(task1.success).toBe(true); + }); + + describe('when detailed is true', () => { + it( + 'should include protected data addresses', + async () => { + // --- GIVEN + // --- WHEN + const inspectResponse = + await dataProtectorCoreArbSepolia.inspectBulkRequest({ + bulkRequest: bulkRequestArbSepolia, + detailed: true, + }); + + // --- THEN + expect(inspectResponse.tasks).toHaveLength(2); + expect(inspectResponse.tasks[0].protectedDataAddresses).toStrictEqual( + [ + '0x017f354098459b4c440b6e404030dbde75c837a6', + '0x030e6a91df6909de2d34abbc13563bead82a63a2', + '0x085cdffabeaff870f0233d6e11a2bae07b1a92aa', + '0x15de1cba5154a9d82e4bf3292c3e5031c0f11091', + '0x16333cfbf307e36551511050489f310cf51e2999', + '0x18ac83aa5a211317a872bb72cbbba296aae26c2c', + '0x19c04c5ba2d408465e3a297baf93f269a9f00ef2', + '0x1cd41098e3901d1543710aa93b306a08b90b7c2b', + '0x1e76fd850074ab9a7885c8f4b010db66c40a8bc7', + '0x206cdf322e224b98fda1f6089a7d64eda0446da7', + '0x221f87649d4ec3e95f500b9ab3d4c36ca1ce7ec0', + '0x23d7cee8d89e4c3cebf9d1012a64650a6d433450', + '0x23f489b2af8c1ffcd5db86ec224787119269c01b', + '0x2699c8472f05d89796a5a197207aa3fd78ca78d3', + '0x26c64687982b8ae74e9a0d490d3cdccb9d7cc7ef', + '0x2a09d32a1491bc32f0466ac969477e379414127b', + '0x2d00d107f2effc1fc7c07fb500d3dc208a69de6c', + '0x2f33aa58a0ad731a3fa0dafa9841261dabfedf7a', + '0x371365165ba6c8757ba84938d58f2e0bdbabccf5', + '0x38459ef2da1365fce481ce779a1a236d4710c2c7', + '0x397a1b86192f0aeb8e0ff167f9bebd83210f4772', + '0x3cbfd616bfcbb9c5740483535ed492625466051c', + '0x40e32f2ca294eb2c799fbfd55a7566e90e9d15bb', + '0x43b9f7c776ed025afaeae0bc7d9fb9864eb302c2', + '0x45dcd4511fafa41f6f6bbe7535559e4ba93aa7a9', + '0x4872ccc304de6f3f0ef42c5362ec489a83f423e3', + '0x4ac074d06b3a00599f3931e1ec436077fd961d1e', + '0x4f1bbc9d791dc9abdaa4b6d54b513354a9910a59', + '0x5218f845f79e0947d0a498a0648fc6bcd18769dc', + '0x5391aa9b2e973dd2a137850c13dc75353fd529d9', + '0x56811938fddcec68ef83903a1a9b473eddabe160', + '0x600c3a2e46f8f6a8e38ebce4c7649f3a05388140', + '0x60b5f6e779065eab67b02e9e982a784016094353', + '0x619c8a00ee131d52e0605e95d0c39d466fa24bea', + '0x6523b65f8c9e48c9220f59e176ff22abc72f4dc7', + '0x6558559f98f0b4cc79b660e6f7d73e68d842f4ff', + '0x66cb1edec3385605bb4a3aaf0ff0d75f4c9ed0b7', + '0x6c907502e6f2f61b062f24461630261942989212', + '0x6d1ade11ea005b32ecc90e68df86c0a6d0710dab', + '0x6e01ecacb7f0a04df3353150e1a30f0f14bdf550', + '0x75ae7c6c0ffa7ff2b1b7420b747731e92c5b991b', + '0x78f8e262a603f5cfe3bb2ad30ad9052f8fce6630', + '0x7b9645a59d1883db6922c94e8071626c7d564095', + '0x82d71ed9d14e0ed7605b686f37651b7365a4ed6a', + '0x82efae19aec7e1c1333501b03fb5b987f26a3744', + '0x836553507fe02ef81224485274a586ac284f7c31', + '0x8610aab99e294a966c1cee4a6e59925974d04905', + '0x89363ba962041232fb0d03ca8833d15e8a33d98f', + '0x8bf71fad528ef3b085e0d2e5cd34f159bfcf2c13', + '0x8d3352ae80f0ad63e9f7fead2a21a212ff674ac6', + '0x8da3990e153dc2f829781ac71e0f1850beefdc29', + '0x8e5088a991814d9de31dd209ff7572d49e68d541', + '0x905369a2f4e4b84c29e3389f3237e7a77656e80c', + '0x9232cbf4fc50a36aac9bb4bb5c23c2d66993a8a4', + '0x95b11e00c69f5d298d986321c7c556cd1032e86c', + '0x98257afee41068fff012a3fa853be32c5b6b8c91', + '0x9f8854e4ee9d93491092645002f9c8e091f262d5', + '0xa1c59808b422c970cab32eecca67257242c7aca8', + '0xa2a997b196ec1d45fac584a3a44a30d310781a10', + '0xa91e8be343b25084efdc60951b82cbe64649731e', + '0xab6bc91d650f4468d8a4f181f8db9a4bbd7da931', + '0xac1f6e47d6ca4f0948774fa92f1d7dfe2e82b963', + '0xac30b2621aa8df33effd957bc3c4aabd80aef02a', + '0xacbd220d422cb1ac63afbfdd05bac426b643c4e3', + '0xad1550347be905fe846389ca20c40535573bb9bc', + '0xae3b1c7a68a9d073940a89ea6beb4a229c556299', + '0xafe44084479964f80d32aeadb369c721a40bcdb8', + '0xb3afb6f81791604324409435ab4ebdb45d5905c8', + '0xb59ec5a53c97efe846dd7cd66fdf835e57341c18', + '0xb611340106517c9a48dd91e5e15b2e324f22ca10', + '0xb7f83baf0c28bf43a8e3aea18721755bebeb8013', + '0xbc5882142c328d0cc30f0abe461c441f6a7ca91b', + '0xbde7166515ed6d47b6fcaa820841572d113eb1a4', + '0xbfd9200ae8bb1f7453fc554d4fb1dbce7e5b3b8b', + '0xc1fc82a8b4cd2ecc8f4770c07f0d92b37ff9f804', + '0xc4b18db491c222bb83263e14aad3c3e43b01e25b', + '0xc8c2ad61e09610716a301c3e1bf1b77888c4ad00', + '0xca7415b0be0edbb9a50633d386e1d64089c70682', + '0xca7bedd67808de0288c8c56d8df964435a9f8cc9', + '0xd0f7094b084d728441e6f4fec534945c8a978b4f', + '0xd2468ad03256c65facc18d938b3fcbee9eecfec4', + '0xd3809d64a90a5d2867997d01c10a323bfab5bba7', + '0xde768628f787ea19adb933f37518c10628503c8f', + '0xe06d76693117070b4adb9def80692f224d444256', + '0xe147db10c54c18593f49f9654c01138071923b3e', + '0xe1a777b9f1933b250cef02e292a092a256495c4b', + '0xe3a016d1360a946c8d1c1d9dfc025697c436e1d4', + '0xe3fa895e42f1e7bd874751b0f8f5889737c166c0', + '0xe4173610be5e66941a2d8d47bd6fd906e9161ab5', + '0xe53b0833f871d76bc3cf803d41e10e10a1dddc79', + '0xe53f37e597a4d66801486f8ea53a0b0895535643', + '0xea52a6686987c1689512c4bfb816356b2259242e', + '0xec0e9be51285a4fdf17db5c2ae9dbc6032276a65', + '0xeca3f6b883bd7c4c789f6e091a3459e1b0814dfb', + '0xee6c677be075fbf3f246f1005315e2d7f1aea8cf', + '0xf09e1cd068c45d200b26e1cf8fa89bf419f362fa', + '0xf4b5473b8d0dfa8dc64b1e82bc91b06cc6c3a6be', + '0xf55074873f183d6593600e14684f120c361b59ea', + '0xf6fc9c6dcef021aad8c549a1977deadb47dcd050', + '0xfaebbc4d93df155fabc7391b401479f58cfaf21d', + ] + ); + expect(inspectResponse.tasks[1].protectedDataAddresses).toStrictEqual( + [ + '0x046aa3521a570016f8c55870c82fc49dcfa51c8d', + '0x0752314d058e3c80f82952cadd66fddcf83ac9e4', + '0x0df222bc8e6a3685bea4cfa9998fbb3675b12fa9', + '0x11e85d053f733930d0234687d27bc78687ff40f5', + '0x1535708698ac554e455c7a4b07018b0531f961dc', + '0x2346ecefe1009d735898247f6e3ea58d65726aeb', + '0x32cad04924838f64e27cad93bf40772583f24520', + '0x332935525167f518dcda26dedb3f20fdc356e843', + '0x39a2a91f07cda61415d0cfd8012034560295c3f5', + '0x3ac290236621f6b58508c6e1feafd9158e671dfb', + '0x3fe78730dffc2297bc61c4dc8f900d5e19cc975c', + '0x4169e2e4d0dfa7b094cb33819bfa5b3a93f28b0e', + '0x4eb39b3df1e7a83247f549307aa1a07d1cfe1137', + '0x51e8378ef9ef8db76663133cdb1a4e9b685266d1', + '0x582685da6bf656e67147fc617e7953fd22dd446a', + '0x5b20fbf28787ccc22e887e1a8a7fc5b6298a0019', + '0x5c0f62600c02c55ba75fe79520e2f8b2dabc4c91', + '0x5df438d28dcf12e342b9f1a769cd65b0b898a109', + '0x5e0c9d8094cc609abaf57882644bf78096627f95', + '0x60ebadc23e384835243adcaa33afad77d2c683f7', + '0x683873397fba6db5f47ab4b0edb4f8857fb0d5ca', + '0x75c2f99c0b49596efe7edd6401c7d3025cf0b2c2', + '0x792cd604053a1997c0e1c3f057f25ae8ea227f5e', + '0x7b8ff0ef9eb7948a90b266d34f910f7eb6e48f37', + '0x7c7a9ef6daa32810f555dba64d5472f1649677a0', + '0x823ab3ca1883e48e6fa8184370f0e46350b4c9f5', + '0x832d92f3eea36d219b8c409fe0e4da8e20ab17d3', + '0x846a42375d29ac9f54c04cbe6916c84fecb76212', + '0x8630ae5ce15deb727952f22040dac42505e179c6', + '0x8982a0d493397ef928eb58f95bbb84aad660c7c9', + '0xb15e8eb120c139de26804aa978407bacc0da2ab6', + '0xb16ac4e77fb21f11e07e7bf3cac2f1cb6225b9e6', + '0xb8ecd307b9f3643a99b16ddafeb463985649f363', + '0xbb6bfc355a12498044780c5e4003b7f2b0f021c8', + '0xbc0475c978f11f02bf529e16e5e2f218606829e8', + '0xc3dc62ba0b2487775249363f672eba729edb7985', + '0xc46d86bc5c6e5ec86c80f2d9285d6cfbe89aef30', + '0xcd1deb62243b5ea1d57c7f9006e2c30d9d78750d', + '0xd11999d1cda0acf8fdd124050064a8431d1897a5', + '0xd735679f9a4619681b119faebde2e48f6115c109', + '0xd83423e7819dda53f0889b17d5e12d4656d5610e', + '0xe49184fa101d7cd28f9bfc45c9541d27c86af4ec', + '0xfb3f3fa87fc6e5d2e8dcf2e2126cea0f333a2fd4', + '0xffc758fd24418b859b00f2d95ea48205d76c781e', + '0xffd0e1a8bb9545ac370ea205c5e469656b97546c', + ] + ); + }, + MAX_EXPECTED_BLOCKTIME * 8 + MAX_EXPECTED_WEB2_SERVICES_TIME + ); + }); + + describe('when withResult is true', () => { + it('should include tasks results', async () => { + // --- GIVEN + // --- WHEN + const inspectResponse = + await dataProtectorCoreArbSepolia.inspectBulkRequest({ + bulkRequest: bulkRequestArbSepolia, + withResult: true, + }); + + // --- THEN + expect(inspectResponse.tasks).toHaveLength(2); + expect(inspectResponse.tasks[0].result.byteLength).toBe(3625); + expect(inspectResponse.tasks[1].result.byteLength).toBe(1908); + }); + describe('when path is specified', () => { + it('should include tasks results at specified path', async () => { + // --- GIVEN + // --- WHEN + const inspectResponse = + await dataProtectorCoreArbSepolia.inspectBulkRequest({ + bulkRequest: bulkRequestArbSepolia, + withResult: true, + path: 'computed.json', + }); + const decoder = new TextDecoder(); + // --- THEN + const expectedResultPathContent = `{ + "deterministic-output-path": "/iexec_out/result.json" +}`; + expect(inspectResponse.tasks).toHaveLength(2); + expect(decoder.decode(inspectResponse.tasks[0].result)).toBe( + expectedResultPathContent + ); + expect(decoder.decode(inspectResponse.tasks[1].result)).toBe( + expectedResultPathContent + ); + }); + describe('when result path does not exist', () => { + it('should report the error', async () => { + // --- GIVEN + // --- WHEN + const inspectResponse = + await dataProtectorCoreArbSepolia.inspectBulkRequest({ + bulkRequest: bulkRequestArbSepolia, + withResult: true, + path: 'does/not/exist', + }); + // --- THEN + expect(inspectResponse.tasks).toHaveLength(2); + expect(inspectResponse.tasks[0].result).toBeUndefined(); + expect(inspectResponse.tasks[0].error).toEqual( + new Error('Failed to process task result') + ); + expect(inspectResponse.tasks[1].result).toBeUndefined(); + expect(inspectResponse.tasks[1].error).toEqual( + new Error('Failed to process task result') + ); + }); + }); + }); + }); + }); +}); diff --git a/packages/sdk/tests/prepare-iexec.js b/packages/sdk/tests/prepare-iexec.js new file mode 100644 index 000000000..30c8b0415 --- /dev/null +++ b/packages/sdk/tests/prepare-iexec.js @@ -0,0 +1,29 @@ +import { readFile, writeFile } from 'fs/promises'; +import { resolve } from 'path'; + +const disableCheckImplementedOnChain = async () => { + const configModulePath = resolve( + 'node_modules/iexec/dist/esm/common/utils/config.js' + ); + + const configModule = await readFile(configModulePath, 'utf8'); + + const OG_CODE_SNIPPET = + 'export const checkImplementedOnChain = (chainId, featureName) => {'; + + const REPLACEMENT_CODE_SNIPPET = + 'export const checkImplementedOnChain = (chainId, featureName) => { return;'; + + if (!configModule.includes(REPLACEMENT_CODE_SNIPPET)) { + console.log('disabling checkImplementedOnChain implementation...'); + const patchedConfigModule = configModule.replace( + OG_CODE_SNIPPET, + REPLACEMENT_CODE_SNIPPET + ); + + await writeFile(configModulePath, patchedConfigModule, 'utf8'); + } +}; + +disableCheckImplementedOnChain(); + diff --git a/packages/sdk/tests/test-utils.ts b/packages/sdk/tests/test-utils.ts index 6bc3e45ce..e675e576e 100644 --- a/packages/sdk/tests/test-utils.ts +++ b/packages/sdk/tests/test-utils.ts @@ -14,6 +14,7 @@ const TEST_CHAIN = { smsURL: 'http://127.0.0.1:13300', resultProxyURL: 'http://127.0.0.1:13200', // TODO remove iexecGatewayURL: 'http://127.0.0.1:3000', + pocoSubgraphURL: 'http://127.0.0.1:8000/subgraphs/name/bellecour/poco-v5', provider: new JsonRpcProvider('http://localhost:8545'), }; @@ -28,6 +29,7 @@ export const getTestIExecOption = () => ({ smsURL: TEST_CHAIN.smsURL, resultProxyURL: TEST_CHAIN.resultProxyURL, iexecGatewayURL: TEST_CHAIN.iexecGatewayURL, + pocoSubgraphURL: TEST_CHAIN.pocoSubgraphURL, }); export const getTestConfig = ( From a16b951467456aeb51214b666ac257df77080c33 Mon Sep 17 00:00:00 2001 From: pjt <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:24:52 +0100 Subject: [PATCH 2/8] fix: remove protectedDataAddresses when task status is UNSET Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../sdk/src/lib/dataProtectorCore/inspectBulkRequest.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/sdk/src/lib/dataProtectorCore/inspectBulkRequest.ts b/packages/sdk/src/lib/dataProtectorCore/inspectBulkRequest.ts index 8c84d8a15..acb73f45d 100644 --- a/packages/sdk/src/lib/dataProtectorCore/inspectBulkRequest.ts +++ b/packages/sdk/src/lib/dataProtectorCore/inspectBulkRequest.ts @@ -144,10 +144,11 @@ export const inspectBulkRequest = async < bulkIndex: botFirst + i, status: statusName as TaskStatus, success, - ...(vDetailed && { - protectedDataAddresses: - taskProtectedDataAddressesMap[taskId]?.protectedDataAddresses, - }), + ...(vDetailed && + statusName !== 'UNSET' && { + protectedDataAddresses: + taskProtectedDataAddressesMap[taskId]?.protectedDataAddresses, + }), ...(vWithResult && { result }), error, } as InspectBulkRequestResponse['tasks'][number]; From 78b0f0b8163b55f42c4e5bd4982a7a48543edb4e Mon Sep 17 00:00:00 2001 From: pjt <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:33:28 +0100 Subject: [PATCH 3/8] fix: typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/sdk/src/lib/types/coreTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/lib/types/coreTypes.ts b/packages/sdk/src/lib/types/coreTypes.ts index 77ab41baf..314b18c0a 100644 --- a/packages/sdk/src/lib/types/coreTypes.ts +++ b/packages/sdk/src/lib/types/coreTypes.ts @@ -629,7 +629,7 @@ export type InspectBulkRequestParams = { withResult?: boolean; /** - * Whether to include detailed information such as addresses of protectedData included in each tasks. + * Whether to include detailed information such as addresses of protectedData included in each task. * @default false */ detailed?: boolean; From 5b9daad934ffc9a4f49a09758261c560aa631a4f Mon Sep 17 00:00:00 2001 From: pjt <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:35:12 +0100 Subject: [PATCH 4/8] refactor: cleanup Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../sdk/tests/e2e/dataProtectorCore/inspectBulkRequest.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/tests/e2e/dataProtectorCore/inspectBulkRequest.test.ts b/packages/sdk/tests/e2e/dataProtectorCore/inspectBulkRequest.test.ts index 649c96eac..a081485ca 100644 --- a/packages/sdk/tests/e2e/dataProtectorCore/inspectBulkRequest.test.ts +++ b/packages/sdk/tests/e2e/dataProtectorCore/inspectBulkRequest.test.ts @@ -113,7 +113,7 @@ describe('dataProtectorCore.inspectBulkRequest()', () => { bulkAccesses: grantedAccesses, maxProtectedDataPerTask: 5, }); - const {} = await dataProtectorCore.processBulkRequest({ + await dataProtectorCore.processBulkRequest({ bulkRequest, workerpool: workerpoolAddress, checkInterval: 1_000, From 7f3f3bdbc3b49b81efc79c447cef85990d999dba Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:41:19 +0100 Subject: [PATCH 5/8] fix: change error log level --- .../sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts b/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts index 207bb767e..bb52c8f07 100644 --- a/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts +++ b/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts @@ -32,7 +32,7 @@ export async function getProtectedDataInBulk({ }); return tasks; } catch (e) { - console.log('[getProtectedDataInBulk] ERROR', e); + console.error('[getProtectedDataInBulk] ERROR', e); throw new WorkflowError({ message: 'Failed to get protected data in bulk', errorCause: e, From ae4f092e09fcfed0ae62cfce8360decbe5396540 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:43:46 +0100 Subject: [PATCH 6/8] refactor: cleanup --- .../sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts b/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts index bb52c8f07..b1e8f8492 100644 --- a/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts +++ b/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts @@ -25,8 +25,9 @@ export async function getProtectedDataInBulk({ deal.tasks.forEach((task) => { tasks[task.taskId] = { dealId: deal.dealId, - protectedDataAddresses: - task.bulkSlice?.datasets.map((dataset) => dataset.id) ?? undefined, + protectedDataAddresses: task.bulkSlice?.datasets.map( + (dataset) => dataset.id + ), }; }); }); From 190cea06c839a84b4afb89c8748bfab90b5b0388 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:59:24 +0100 Subject: [PATCH 7/8] refactor: cleanup --- .../src/lib/dataProtectorCore/getProtectedDataInBulk.ts | 9 ++++----- .../subgraph/getProtectedDataInBulkByBulkRequestHash.ts | 2 -- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts b/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts index b1e8f8492..01453f9ad 100644 --- a/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts +++ b/packages/sdk/src/lib/dataProtectorCore/getProtectedDataInBulk.ts @@ -8,9 +8,7 @@ export async function getProtectedDataInBulk({ bulkRequestHash = throwIfMissing(), }: PocoSubgraphConsumer & { bulkRequestHash: string; -}): Promise< - Record -> { +}): Promise> { try { const result = await getProtectedDataInBulkByBulkRequestHash({ pocoSubgraphClient, @@ -19,12 +17,13 @@ export async function getProtectedDataInBulk({ const tasks: Record< string, - { dealId: string; protectedDataAddresses: string[] } + { + protectedDataAddresses: string[]; + } > = {}; result.deals.forEach((deal) => { deal.tasks.forEach((task) => { tasks[task.taskId] = { - dealId: deal.dealId, protectedDataAddresses: task.bulkSlice?.datasets.map( (dataset) => dataset.id ), diff --git a/packages/sdk/src/lib/dataProtectorCore/subgraph/getProtectedDataInBulkByBulkRequestHash.ts b/packages/sdk/src/lib/dataProtectorCore/subgraph/getProtectedDataInBulkByBulkRequestHash.ts index b33b85b32..153985159 100644 --- a/packages/sdk/src/lib/dataProtectorCore/subgraph/getProtectedDataInBulkByBulkRequestHash.ts +++ b/packages/sdk/src/lib/dataProtectorCore/subgraph/getProtectedDataInBulkByBulkRequestHash.ts @@ -12,7 +12,6 @@ export async function getProtectedDataInBulkByBulkRequestHash({ const dealsQuery = gql` query ($where: Deal_filter) { deals(where: $where) { - dealId: id tasks { taskId: id bulkSlice { @@ -31,7 +30,6 @@ export async function getProtectedDataInBulkByBulkRequestHash({ }; const res = await pocoSubgraphClient.request<{ deals: Array<{ - dealId: string; tasks: Array<{ taskId: string; bulkSlice?: { From 3cbf7c0f604874928931e16e1f313339c67235ac Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:04:01 +0100 Subject: [PATCH 8/8] fix: make sdk patch exit 1 on error --- packages/sdk/tests/prepare-iexec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/sdk/tests/prepare-iexec.js b/packages/sdk/tests/prepare-iexec.js index 30c8b0415..f181eac09 100644 --- a/packages/sdk/tests/prepare-iexec.js +++ b/packages/sdk/tests/prepare-iexec.js @@ -25,5 +25,7 @@ const disableCheckImplementedOnChain = async () => { } }; -disableCheckImplementedOnChain(); - +disableCheckImplementedOnChain().catch((e) => { + console.error(`Failed to disable checkImplementedOnChain: ${e}`); + process.exit(1); +});