diff --git a/packages/sdk/package-lock.json b/packages/sdk/package-lock.json index 4cb626f80..2914b8a9d 100644 --- a/packages/sdk/package-lock.json +++ b/packages/sdk/package-lock.json @@ -18,9 +18,9 @@ "debug": "^4.3.4", "ethers": "^6.13.2", "graphql-request": "^6.0.0", - "iexec": "^8.20.0", + "iexec": "^8.22.0", "jszip": "^3.7.1", - "kubo-rpc-client": "^4.1.1", + "kubo-rpc-client": "^5.4.1", "magic-bytes.js": "^1.0.15", "typechain": "^8.3.2", "yup": "^1.0.2" @@ -176,6 +176,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -1895,60 +1896,178 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@libp2p/crypto": { + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.13.tgz", + "integrity": "sha512-8NN9cQP3jDn+p9+QE9ByiEoZ2lemDFf/unTgiKmS3JF93ph240EUVdbCyyEgOMfykzb0okTM4gzvwfx9osJebQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface": "^3.1.0", + "@noble/curves": "^2.0.1", + "@noble/hashes": "^2.0.1", + "multiformats": "^13.4.0", + "protons-runtime": "^5.6.0", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.1.0" + } + }, + "node_modules/@libp2p/crypto/node_modules/@noble/curves": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.1" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@libp2p/crypto/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@libp2p/crypto/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/crypto/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, "node_modules/@libp2p/interface": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-1.6.3.tgz", - "integrity": "sha512-Tm8W5Q2FsjcSdeA5BvP/GTUq/lp3SjeW6GPmWbbIasBJdv67UGHahu8YDFTME90IxTijnikkfGNkOPsd/4UuvA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.0.tgz", + "integrity": "sha512-RE7/XyvC47fQBe1cHxhMvepYKa5bFCUyFrrpj8PuM0E7JtzxU7F+Du5j4VXbg2yLDcToe0+j8mB7jvwE2AThYw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^13.0.1", + "main-event": "^1.0.1", + "multiformats": "^13.4.0", + "progress-events": "^1.0.1", "uint8arraylist": "^2.4.8" } }, + "node_modules/@libp2p/interface/node_modules/@multiformats/multiaddr": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.1.tgz", + "integrity": "sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, "node_modules/@libp2p/interface/node_modules/multiformats": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.2.2.tgz", - "integrity": "sha512-RWI+nyf0q64vyOxL8LbKtjJMki0sogRL/8axvklNtiTM0iFCVtHwME9w6+0P1/v4dQvsIg8A45oT3ka1t/M/+A==" + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } }, "node_modules/@libp2p/logger": { - "version": "4.0.19", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-4.0.19.tgz", - "integrity": "sha512-VKpIMbjzs60AaTezh55iEDPJ0W2icbkJkBXSlAMycCT4C+RYxOTRgevasw3mDB6+Lj9etM0nfa4vutoG4fsYCw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-5.2.0.tgz", + "integrity": "sha512-OEFS529CnIKfbWEHmuCNESw9q0D0hL8cQ8klQfjIVPur15RcgAEgc1buQ7Y6l0B6tCYg120bp55+e9tGvn8c0g==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", - "@multiformats/multiaddr": "^12.2.3", - "interface-datastore": "^8.2.11", - "multiformats": "^13.1.0", - "weald": "^1.0.2" + "@libp2p/interface": "^2.11.0", + "@multiformats/multiaddr": "^12.4.4", + "interface-datastore": "^8.3.1", + "multiformats": "^13.3.6", + "weald": "^1.0.4" + } + }, + "node_modules/@libp2p/logger/node_modules/@libp2p/interface": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.11.0.tgz", + "integrity": "sha512-0MUFKoXWHTQW3oWIgSHApmYMUKWO/Y02+7Hpyp+n3z+geD4Xo2Rku2gYWmxcq+Pyjkz6Q9YjDWz3Yb2SoV2E8Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^12.4.4", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.2", + "main-event": "^1.0.1", + "multiformats": "^13.3.6", + "progress-events": "^1.0.1", + "uint8arraylist": "^2.4.8" } }, "node_modules/@libp2p/logger/node_modules/multiformats": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.2.2.tgz", - "integrity": "sha512-RWI+nyf0q64vyOxL8LbKtjJMki0sogRL/8axvklNtiTM0iFCVtHwME9w6+0P1/v4dQvsIg8A45oT3ka1t/M/+A==" + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" }, "node_modules/@libp2p/peer-id": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-4.2.3.tgz", - "integrity": "sha512-hRqPzcYOz/5q6QvHYdmPMGeFZCjC/9qxQ/+jstSDMnY1DuKEXCre2+tCpG9OeRAFyPBbs5isfaqbY3zNZV2pqA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-5.1.9.tgz", + "integrity": "sha512-cVDp7lX187Epmi/zr0Qq2RsEMmueswP9eIxYSFoMcHL/qcvRFhsxOfUGB8361E26s2WJvC9sXZ0oJS9XVueJhQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^1.6.3", - "multiformats": "^13.1.0", + "@libp2p/crypto": "^5.1.8", + "@libp2p/interface": "^2.11.0", + "multiformats": "^13.3.6", "uint8arrays": "^5.1.0" } }, + "node_modules/@libp2p/peer-id/node_modules/@libp2p/interface": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.11.0.tgz", + "integrity": "sha512-0MUFKoXWHTQW3oWIgSHApmYMUKWO/Y02+7Hpyp+n3z+geD4Xo2Rku2gYWmxcq+Pyjkz6Q9YjDWz3Yb2SoV2E8Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^12.4.4", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.2", + "main-event": "^1.0.1", + "multiformats": "^13.3.6", + "progress-events": "^1.0.1", + "uint8arraylist": "^2.4.8" + } + }, "node_modules/@libp2p/peer-id/node_modules/multiformats": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.2.2.tgz", - "integrity": "sha512-RWI+nyf0q64vyOxL8LbKtjJMki0sogRL/8axvklNtiTM0iFCVtHwME9w6+0P1/v4dQvsIg8A45oT3ka1t/M/+A==" + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" }, "node_modules/@libp2p/peer-id/node_modules/uint8arrays": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", "dependencies": { "multiformats": "^13.0.0" } @@ -1981,23 +2100,25 @@ } }, "node_modules/@multiformats/multiaddr": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.4.0.tgz", - "integrity": "sha512-FL7yBTLijJ5JkO044BGb2msf+uJLrwpD6jD6TkXlbjA9N12+18HT40jvd4o5vL4LOJMc86dPX6tGtk/uI9kYKg==", + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", "license": "Apache-2.0 OR MIT", "dependencies": { "@chainsafe/is-ip": "^2.0.1", "@chainsafe/netmask": "^2.0.0", "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", "multiformats": "^13.0.0", "uint8-varint": "^2.0.1", "uint8arrays": "^5.0.0" } }, "node_modules/@multiformats/multiaddr-to-uri": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-10.1.0.tgz", - "integrity": "sha512-ZNwSAx3ssBWwd4y0LKrOsq9xG7LBHboQxnUdSduNc2fTh/NS1UjA2slgUy6KHxH5k9S2DSus0iU2CoyJyN0/pg==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-11.0.2.tgz", + "integrity": "sha512-SiLFD54zeOJ0qMgo9xv1Tl9O5YktDKAVDP4q4hL16mSq4O4sfFNagNADz8eAofxd6TfQUzGQ3TkRRG9IY2uHRg==", + "license": "Apache-2.0 OR MIT", "dependencies": { "@multiformats/multiaddr": "^12.3.0" } @@ -2041,7 +2162,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2054,7 +2174,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -2063,7 +2182,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -2125,6 +2243,7 @@ "integrity": "sha512-HHAlbXjWI6Kl9JmmUW1LSygT1YbblXgj2UvvDzMkTBPRzYMhW6xchxdO8HbtMPtFYRt/EQq9u1z7j4ttRSrFsA==", "dev": true, "hasInstallScript": true, + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.12" @@ -2640,6 +2759,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz", "integrity": "sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.10.0", @@ -2675,6 +2795,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.10.0", "@typescript-eslint/types": "6.10.0", @@ -2861,6 +2982,12 @@ "node": ">=10" } }, + "node_modules/abort-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.1.tgz", + "integrity": "sha512-fxqCblJiIPdSXIUrxI0PL+eJG49QdP9SQ70qtB65MVAoMr2rASlOyAbJFOylfB467F/f+5BCLJJq58RYi7mGfg==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -2880,6 +3007,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3332,12 +3460,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3367,6 +3495,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -4216,6 +4345,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -4417,6 +4547,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4598,6 +4729,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -4998,6 +5130,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", @@ -5133,16 +5266,16 @@ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -5152,7 +5285,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -5176,7 +5308,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -5203,10 +5334,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -5845,9 +5976,9 @@ ] }, "node_modules/iexec": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/iexec/-/iexec-8.20.0.tgz", - "integrity": "sha512-ce0l1eJWqOj85kWXbUJ7yF6IhbHiHz1hRqLdDEAJhCvlIAArdyrwJcWtXz6gmuYoSQ0F8gkKvEWC/FHg4Gr/8w==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/iexec/-/iexec-8.22.0.tgz", + "integrity": "sha512-6mqR0Vp6eHAcEelAjeXuGn8EzmsbPdHsSdBfSXlrM8hHMKOCVFPbgkRind1VFlDxhvKvHaY8szk0xUELa5YCnA==", "license": "Apache-2.0", "dependencies": { "@ensdomains/ens-contracts": "^1.2.5", @@ -5865,6 +5996,8 @@ "inquirer": "^12.5.0", "is-docker": "^3.0.0", "jszip": "^3.10.1", + "kubo-rpc-client": "^5.3.0", + "multiformats": "^13.4.1", "node-forge": "^1.3.1", "ora": "^8.2.0", "prettyjson": "^1.2.5", @@ -5890,6 +6023,12 @@ "graphql": "14 - 16" } }, + "node_modules/iexec/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -6003,31 +6142,35 @@ } }, "node_modules/interface-datastore": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.0.tgz", - "integrity": "sha512-RM/rTSmRcnoCwGZIHrPm+nlGYVoT4R0lcFvNnDyhdFT4R6BuHHhfFP47UldVEjs98SfxLuMhaNMsyjI918saHw==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.2.tgz", + "integrity": "sha512-R3NLts7pRbJKc3qFdQf+u40hK8XWc0w4Qkx3OFEstC80VoaDUABY/dXA2EJPhtNC+bsrf41Ehvqb6+pnIclyRA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "interface-store": "6.0.0", - "uint8arrays": "^5.0.2" + "interface-store": "^6.0.0", + "uint8arrays": "^5.1.0" } }, "node_modules/interface-datastore/node_modules/multiformats": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.2.2.tgz", - "integrity": "sha512-RWI+nyf0q64vyOxL8LbKtjJMki0sogRL/8axvklNtiTM0iFCVtHwME9w6+0P1/v4dQvsIg8A45oT3ka1t/M/+A==" + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" }, "node_modules/interface-datastore/node_modules/uint8arrays": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", "dependencies": { "multiformats": "^13.0.0" } }, "node_modules/interface-store": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.0.tgz", - "integrity": "sha512-HkjsDPsjA7SKkCr+TH1elUQApAAM3X3JPwrz3vFzaf614wI+ZD6GVvwKGZCHYcbSRqeZP/uzVPqezzeISeo5kA==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.3.tgz", + "integrity": "sha512-+WvfEZnFUhRwFxgz+QCQi7UC6o9AM0EHM9bpIe2Nhqb100NHCsTvNAn4eJgvgV2/tmLo1MP9nGxQKEcZTAueLA==", + "license": "Apache-2.0 OR MIT" }, "node_modules/internal-slot": { "version": "1.0.6", @@ -6188,7 +6331,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6214,7 +6356,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -6250,7 +6391,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -6499,33 +6640,12 @@ "integrity": "sha512-ExIewyK9kXKNAplg2GMeWfgjUcfC1FnUXz/RPfAvIXby+w7U4b3//5Lic0NV03gXT8O/isj5Nmp6KiY0d45pIQ==" }, "node_modules/it-glob": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-2.0.7.tgz", - "integrity": "sha512-FjL2rAsGu566sMVKexFG5ciDoKoi1lgwXlSE6DW0yVbGRyvrlzi0OQ3fbfrw89dpIAzJFHnLNQwZS4yRkt3d/Q==", - "dependencies": { - "minimatch": "^9.0.4" - } - }, - "node_modules/it-glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/it-glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-3.0.4.tgz", + "integrity": "sha512-73PbGBTK/dHp5PX4l8pkQH1ozCONP0U+PB3qMqltxPonRJQNomINE3Hn9p02m2GOu95VoeVvSZdHI2N+qub0pw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "fast-glob": "^3.3.3" } }, "node_modules/it-last": { @@ -6550,18 +6670,16 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.3.tgz", "integrity": "sha512-gzYnXYK8Y5t5b/BnJUr7glfQLO4U5vyb05gPx/TyTw+4Bv1zM9gFk4YsOrnulWefMewlphCjKkakFvj1y99Tcg==", + "license": "Apache-2.0 OR MIT", "dependencies": { "p-defer": "^4.0.0" } }, "node_modules/it-stream-types": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.1.tgz", - "integrity": "sha512-6DmOs5r7ERDbvS4q8yLKENcj6Yecr7QQTqWApbZdfAUTEC947d+PEha7PCqhm//9oxaLYL7TWRekwhoXl2s6fg==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.2.tgz", + "integrity": "sha512-Rz/DEZ6Byn/r9+/SBCuJhpPATDF9D+dz5pbgSUyBsCDtza6wtNATrz/jz1gDyNanC3XdLboriHnOC925bZRBww==", + "license": "Apache-2.0 OR MIT" }, "node_modules/it-to-stream": { "version": "1.0.0", @@ -6620,6 +6738,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -7289,18 +7408,20 @@ } }, "node_modules/kubo-rpc-client": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/kubo-rpc-client/-/kubo-rpc-client-4.1.1.tgz", - "integrity": "sha512-vxIkTQLvDqd2DEcBfz7DWegAidYtopmWUR/UhWyuuss9+JNFzV5neBn8BKxSDHoLJPvkxJpqXDaJhsb7ZuPouQ==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/kubo-rpc-client/-/kubo-rpc-client-5.4.1.tgz", + "integrity": "sha512-v86bQWtyA//pXTrt9y4iEwjW6pt1gA18Z1famWXIR/HN5TFdYwQ3yHOlRE6JSWBDQ0rR6FOMyrrGy8To78mXow==", + "license": "Apache-2.0 OR MIT", "dependencies": { "@ipld/dag-cbor": "^9.0.0", "@ipld/dag-json": "^10.0.0", "@ipld/dag-pb": "^4.0.0", - "@libp2p/interface": "^1.2.0", - "@libp2p/logger": "^4.0.10", - "@libp2p/peer-id": "^4.0.10", + "@libp2p/crypto": "^5.0.0", + "@libp2p/interface": "^2.0.0", + "@libp2p/logger": "^5.0.0", + "@libp2p/peer-id": "^5.0.0", "@multiformats/multiaddr": "^12.2.1", - "@multiformats/multiaddr-to-uri": "^10.0.1", + "@multiformats/multiaddr-to-uri": "^11.0.0", "any-signal": "^4.1.1", "blob-to-it": "^2.0.5", "browser-readablestream-to-it": "^2.0.5", @@ -7311,7 +7432,7 @@ "iso-url": "^1.2.1", "it-all": "^3.0.4", "it-first": "^3.0.4", - "it-glob": "^2.0.6", + "it-glob": "^3.0.1", "it-last": "^3.0.4", "it-map": "^3.0.5", "it-peekable": "^3.0.3", @@ -7320,22 +7441,40 @@ "multiformats": "^13.1.0", "nanoid": "^5.0.7", "native-fetch": "^4.0.2", - "parse-duration": "^1.0.2", + "parse-duration": "^2.1.2", "react-native-fetch-api": "^3.0.0", "stream-to-it": "^1.0.1", "uint8arrays": "^5.0.3", "wherearewe": "^2.0.1" } }, + "node_modules/kubo-rpc-client/node_modules/@libp2p/interface": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.11.0.tgz", + "integrity": "sha512-0MUFKoXWHTQW3oWIgSHApmYMUKWO/Y02+7Hpyp+n3z+geD4Xo2Rku2gYWmxcq+Pyjkz6Q9YjDWz3Yb2SoV2E8Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^12.4.4", + "it-pushable": "^3.2.3", + "it-stream-types": "^2.0.2", + "main-event": "^1.0.1", + "multiformats": "^13.3.6", + "progress-events": "^1.0.1", + "uint8arraylist": "^2.4.8" + } + }, "node_modules/kubo-rpc-client/node_modules/multiformats": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.2.2.tgz", - "integrity": "sha512-RWI+nyf0q64vyOxL8LbKtjJMki0sogRL/8axvklNtiTM0iFCVtHwME9w6+0P1/v4dQvsIg8A45oT3ka1t/M/+A==" + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" }, "node_modules/kubo-rpc-client/node_modules/uint8arrays": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", "dependencies": { "multiformats": "^13.0.0" } @@ -7461,6 +7600,12 @@ "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.5.0.tgz", "integrity": "sha512-wJkXvutRbNWcc37tt5j1HyOK1nosspdh3dj6LUYYAvF6JYNqs53IfRvK9oEpcwiDA1NdoIi64yAMfdivPeVAyw==" }, + "node_modules/main-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.1.tgz", + "integrity": "sha512-NWtdGrAca/69fm6DIVd8T9rtfDII4Q8NQbIbsKQq2VzS9eqOGYs8uaNQjcuaCq/d9H/o625aOTJX2Qoxzqw0Pw==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -7539,18 +7684,17 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -8046,6 +8190,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", "integrity": "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -8153,9 +8298,10 @@ } }, "node_modules/parse-duration": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.0.tgz", - "integrity": "sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ==" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-2.1.4.tgz", + "integrity": "sha512-b98m6MsCh+akxfyoz9w9dt0AlH2dfYLOBss5SdDsr9pkhKNvkWBXU/r8A4ahmIGByBOLV2+4YwfCuFxbDDaGyg==", + "license": "MIT" }, "node_modules/parse-json": { "version": "5.2.0", @@ -8271,7 +8417,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -8451,9 +8596,10 @@ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" }, "node_modules/protons-runtime": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.5.0.tgz", - "integrity": "sha512-EsALjF9QsrEk6gbCx3lmfHxVN0ah7nG3cY7GySD4xf4g8cr7g543zB88Foh897Sr1RQJ9yDCUsoT1i1H/cVUFA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.6.0.tgz", + "integrity": "sha512-/Kde+sB9DsMFrddJT/UZWe6XqvL7SL5dbag/DBCElFKhkwDj7XKt53S+mzLyaDP5OqS0wXjV5SA572uWDaT0Hg==", + "license": "Apache-2.0 OR MIT", "dependencies": { "uint8-varint": "^2.0.2", "uint8arraylist": "^2.4.3", @@ -8549,7 +8695,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -8960,7 +9105,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -9065,7 +9209,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -9760,7 +9903,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -9958,6 +10101,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.3.2.tgz", "integrity": "sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==", + "peer": true, "dependencies": { "@types/prettier": "^2.1.1", "debug": "^4.3.1", @@ -10094,6 +10238,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10294,28 +10439,31 @@ } }, "node_modules/weald": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/weald/-/weald-1.0.2.tgz", - "integrity": "sha512-iG5cIuBwsPe1ZcoGGd4X6QYlepU1vLr4l4oWpzQWqeJPSo9B8bxxyE6xlnj3TCmThtha7gyVL+uuZgUFkPyfDg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/weald/-/weald-1.1.1.tgz", + "integrity": "sha512-PaEQShzMCz8J/AD2N3dJMc1hTZWkJeLKS2NMeiVkV5KDHwgZe7qXLEzyodsT/SODxWDdXJJqocuwf3kHzcXhSQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { "ms": "^3.0.0-canary.1", - "supports-color": "^9.4.0" + "supports-color": "^10.0.0" } }, "node_modules/weald/node_modules/ms": { - "version": "3.0.0-canary.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", - "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "version": "3.0.0-canary.202508261828", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.202508261828.tgz", + "integrity": "sha512-NotsCoUCIUkojWCzQff4ttdCfIPoA1UGZsyQbi7KmqkNRfKCrvga8JJi2PknHymHOuor0cJSn/ylj52Cbt2IrQ==", + "license": "MIT", "engines": { - "node": ">=12.13" + "node": ">=18" } }, "node_modules/weald/node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" diff --git a/packages/sdk/package.json b/packages/sdk/package.json index cb651615b..4acc1a131 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -61,9 +61,9 @@ "debug": "^4.3.4", "ethers": "^6.13.2", "graphql-request": "^6.0.0", - "iexec": "^8.20.0", + "iexec": "^8.22.0", "jszip": "^3.7.1", - "kubo-rpc-client": "^4.1.1", + "kubo-rpc-client": "^5.4.1", "magic-bytes.js": "^1.0.15", "typechain": "^8.3.2", "yup": "^1.0.2" diff --git a/packages/sdk/src/lib/IExecDataProtectorModule.ts b/packages/sdk/src/lib/IExecDataProtectorModule.ts index e05fdda87..0c799f5f3 100644 --- a/packages/sdk/src/lib/IExecDataProtectorModule.ts +++ b/packages/sdk/src/lib/IExecDataProtectorModule.ts @@ -118,6 +118,7 @@ abstract class IExecDataProtectorModule { { ethProvider: this.ethProvider }, { ipfsGatewayURL: ipfsGateway, + ipfsNodeURL: ipfsNode, ...this.options?.iexecOptions, allowExperimentalNetworks: this.options.allowExperimentalNetworks, } diff --git a/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts b/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts index 4a61cd4a9..071a61ad7 100644 --- a/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts +++ b/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts @@ -8,6 +8,8 @@ import { GrantedAccessResponse, ProcessProtectedDataParams, ProcessProtectedDataResponse, + ProcessBulkRequestParams, + ProcessBulkRequestResponse, ProtectDataParams, ProtectedData, GetResultFromCompletedTaskParams, @@ -19,11 +21,15 @@ import { TransferResponse, WaitForTaskCompletionResponse, WaitForTaskCompletionParams, + PrepareBulkRequestParams, + PrepareBulkRequestResponse, } 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 { prepareBulkRequest } from './prepareBulkRequest.js'; +import { processBulkRequest } from './processBulkRequest.js'; import { processProtectedData } from './processProtectedData.js'; import { protectData } from './protectData.js'; import { revokeAllAccess } from './revokeAllAccess.js'; @@ -83,6 +89,29 @@ class IExecDataProtectorCore extends IExecDataProtectorModule { }); } + async prepareBulkRequest( + args: PrepareBulkRequestParams + ): Promise { + await this.init(); + await isValidProvider(this.iexec); + return prepareBulkRequest({ + ...args, + iexec: this.iexec, + }); + } + + async processBulkRequest( + args: Params + ): Promise> { + await this.init(); + await isValidProvider(this.iexec); + return processBulkRequest({ + ...args, + iexec: this.iexec, + defaultWorkerpool: this.defaultWorkerpool, + }); + } + // ----- READ METHODS ----- async getProtectedData( args?: GetProtectedDataParams diff --git a/packages/sdk/src/lib/dataProtectorCore/getGrantedAccess.ts b/packages/sdk/src/lib/dataProtectorCore/getGrantedAccess.ts index 5f9c74312..25bcc9537 100644 --- a/packages/sdk/src/lib/dataProtectorCore/getGrantedAccess.ts +++ b/packages/sdk/src/lib/dataProtectorCore/getGrantedAccess.ts @@ -21,6 +21,7 @@ export const getGrantedAccess = async ({ isUserStrict = false, page, pageSize, + bulkOnly = false, }: IExecConsumer & GetGrantedAccessParams): Promise => { const vProtectedData = addressOrEnsSchema() .label('protectedData') @@ -38,6 +39,7 @@ export const getGrantedAccess = async ({ const vPageSize = numberBetweenSchema(10, 1000) .label('pageSize') .validateSync(pageSize); + const vBulkOnly = booleanSchema().label('bulkOnly').validateSync(bulkOnly); try { const { count, orders } = await iexec.orderbook.fetchDatasetOrderbook({ @@ -48,6 +50,7 @@ export const getGrantedAccess = async ({ isAppStrict: true, page: vPage, pageSize: vPageSize, + bulkOnly: vBulkOnly, }); const grantedAccess = orders?.map((order) => formatGrantedAccess(order.order, order.remaining) diff --git a/packages/sdk/src/lib/dataProtectorCore/grantAccess.ts b/packages/sdk/src/lib/dataProtectorCore/grantAccess.ts index 95708cf08..dfbeb3961 100644 --- a/packages/sdk/src/lib/dataProtectorCore/grantAccess.ts +++ b/packages/sdk/src/lib/dataProtectorCore/grantAccess.ts @@ -1,5 +1,5 @@ import { ZeroAddress } from 'ethers'; -import { NULL_ADDRESS } from 'iexec/utils'; +import { DATASET_INFINITE_VOLUME, NULL_ADDRESS } from 'iexec/utils'; import { ValidationError, WorkflowError, @@ -9,6 +9,7 @@ import { import { formatGrantedAccess } from '../../utils/formatGrantedAccess.js'; import { addressOrEnsSchema, + booleanSchema, isEnsTest, positiveIntegerStringSchema, positiveStrictIntegerStringSchema, @@ -47,8 +48,9 @@ export const grantAccess = async ({ protectedData, authorizedApp, authorizedUser, - pricePerAccess, + pricePerAccess = 0, numberOfAccess, + allowBulk = false, onStatusUpdate = () => {}, }: IExecConsumer & GrantAccessParams): Promise => { const vProtectedData = addressOrEnsSchema() @@ -62,17 +64,37 @@ export const grantAccess = async ({ const vAuthorizedUser = addressOrEnsSchema() .label('authorizedUser') .validateSync(authorizedUser); - const vPricePerAccess = positiveIntegerStringSchema() + let vPricePerAccess = positiveIntegerStringSchema() .label('pricePerAccess') .validateSync(pricePerAccess); - const vNumberOfAccess = positiveStrictIntegerStringSchema() + let vNumberOfAccess = positiveStrictIntegerStringSchema() .label('numberOfAccess') .validateSync(numberOfAccess); + const vAllowBulk = booleanSchema().label('allowBulk').validateSync(allowBulk); const vOnStatusUpdate = validateOnStatusUpdateCallback>( onStatusUpdate ); + // Validate consistency between allowBulk, pricePerAccess and numberOfAccess + if (vAllowBulk) { + if (vPricePerAccess && vPricePerAccess !== '0') { + throw new ValidationError( + 'allowBulk requires pricePerAccess to be 0 or undefined' + ); + } + vPricePerAccess = '0'; + if ( + vNumberOfAccess && + vNumberOfAccess !== DATASET_INFINITE_VOLUME.toString() + ) { + throw new ValidationError( + `allowBulk requires numberOfAccess to be ${DATASET_INFINITE_VOLUME.toString()} or undefined` + ); + } + vNumberOfAccess = DATASET_INFINITE_VOLUME.toString(); + } + if (vAuthorizedApp && isEnsTest(vAuthorizedApp)) { const resolved = await iexec.ens.resolveName(vAuthorizedApp); if (!resolved) { @@ -127,6 +149,7 @@ export const grantAccess = async ({ title: 'CREATE_DATASET_ORDER', isDone: false, }); + const datasetorder = await iexec.order .createDatasetorder({ dataset: vProtectedData, @@ -145,6 +168,7 @@ export const grantAccess = async ({ errorCause: e, }); }); + vOnStatusUpdate({ title: 'CREATE_DATASET_ORDER', isDone: true, @@ -154,6 +178,7 @@ export const grantAccess = async ({ title: 'PUBLISH_DATASET_ORDER', isDone: false, }); + await iexec.order.publishDatasetorder(datasetorder).catch((e) => { handleIfProtocolError(e); throw new WorkflowError({ @@ -161,6 +186,7 @@ export const grantAccess = async ({ errorCause: e, }); }); + vOnStatusUpdate({ title: 'PUBLISH_DATASET_ORDER', isDone: true, diff --git a/packages/sdk/src/lib/dataProtectorCore/prepareBulkRequest.ts b/packages/sdk/src/lib/dataProtectorCore/prepareBulkRequest.ts new file mode 100644 index 000000000..baef2a4a7 --- /dev/null +++ b/packages/sdk/src/lib/dataProtectorCore/prepareBulkRequest.ts @@ -0,0 +1,226 @@ +import { NULL_ADDRESS } from 'iexec/utils'; +import { + MAX_DESIRED_APP_ORDER_PRICE, + MAX_DESIRED_WORKERPOOL_ORDER_PRICE, + SCONE_TAG, +} from '../../config/config.js'; +import { + ValidationError, + WorkflowError, + handleIfProtocolError, + prepareBulkRequestErrorMessage, +} from '../../utils/errors.js'; +import { pushRequesterSecret } from '../../utils/pushRequesterSecret.js'; +import { + getPemFormattedKeyPair, + formatPemPublicKeyForSMS, +} from '../../utils/rsa.js'; +import { + addressOrEnsSchema, + booleanSchema, + positiveNumberSchema, + secretsSchema, + stringSchema, + throwIfMissing, + urlArraySchema, + validateOnStatusUpdateCallback, +} from '../../utils/validators.js'; +import { + OnStatusUpdateFn, + PrepareBulkRequestParams, + PrepareBulkRequestResponse, + PrepareBulkRequestStatuses, +} from '../types/index.js'; +import { IExecConsumer } from '../types/internalTypes.js'; + +export type PrepareBulkRequest = typeof prepareBulkRequest; + +export const prepareBulkRequest = async ({ + iexec = throwIfMissing(), + bulkAccesses, + app, + maxProtectedDataPerTask = 100, + appMaxPrice = MAX_DESIRED_APP_ORDER_PRICE, + workerpoolMaxPrice = MAX_DESIRED_WORKERPOOL_ORDER_PRICE, + args, + inputFiles, + secrets, + workerpool, + encryptResult = false, + pemPrivateKey, + onStatusUpdate = () => {}, +}: IExecConsumer & + PrepareBulkRequestParams): Promise => { + const vApp = addressOrEnsSchema().required().label('app').validateSync(app); + const vMaxProtectedDataPerTask = positiveNumberSchema() + .label('maxProtectedDataPerTask') + .validateSync(maxProtectedDataPerTask); + const vAppMaxPrice = positiveNumberSchema() + .label('appMaxPrice') + .validateSync(appMaxPrice); + const vWorkerpoolMaxPrice = positiveNumberSchema() + .label('workerpoolMaxPrice') + .validateSync(workerpoolMaxPrice); + const vInputFiles = urlArraySchema() + .label('inputFiles') + .validateSync(inputFiles); + const vArgs = stringSchema().label('args').validateSync(args); + const vSecrets = secretsSchema().label('secrets').validateSync(secrets); + const vWorkerpool = addressOrEnsSchema() + .default(NULL_ADDRESS) + .label('workerpool') + .validateSync(workerpool); + const vEncryptResult = booleanSchema() + .label('encryptResult') + .validateSync(encryptResult); + const vPemPrivateKey = stringSchema() + .label('pemPrivateKey') + .validateSync(pemPrivateKey); + const vOnStatusUpdate = + validateOnStatusUpdateCallback< + OnStatusUpdateFn + >(onStatusUpdate); + + // Validate that if pemPrivateKey is provided, encryptResult must be true + if (vPemPrivateKey && !vEncryptResult) { + throw new Error( + 'pemPrivateKey can only be provided when encryptResult is true' + ); + } + + if (!bulkAccesses || bulkAccesses.length === 0) { + throw new ValidationError('bulkAccesses is required and must not be empty'); + } + // TODO validate bulkOrders? + // price, volume, app, workerpool, requester, signature (including whitelists) + const vBulkAccesses = bulkAccesses; + + try { + vOnStatusUpdate({ + title: 'PUSH_REQUESTER_SECRET', + isDone: false, + }); + const secretsId = await pushRequesterSecret(iexec, vSecrets); + vOnStatusUpdate({ + title: 'PUSH_REQUESTER_SECRET', + isDone: true, + }); + + vOnStatusUpdate({ + title: 'GENERATE_ENCRYPTION_KEY', + isDone: false, + }); + + // Handle result encryption + let privateKey: string | undefined; + if (vEncryptResult) { + if (!vPemPrivateKey) { + vOnStatusUpdate({ + title: 'GENERATE_ENCRYPTION_KEY', + isDone: false, + }); + } + const pemKeyPair = await getPemFormattedKeyPair({ + pemPrivateKey: vPemPrivateKey, + }); + privateKey = pemKeyPair.pemPrivateKey; + // Notify user if a new key was generated + if (!vPemPrivateKey) { + vOnStatusUpdate({ + title: 'GENERATE_ENCRYPTION_KEY', + isDone: true, + payload: { + pemPrivateKey: pemKeyPair.pemPrivateKey, + }, + }); + } + + vOnStatusUpdate({ + title: 'PUSH_ENCRYPTION_KEY', + isDone: false, + payload: { + pemPublicKey: pemKeyPair.pemPublicKey, + }, + }); + + await iexec.result.pushResultEncryptionKey( + formatPemPublicKeyForSMS(pemKeyPair.pemPublicKey), + { + forceUpdate: true, + } + ); + + vOnStatusUpdate({ + title: 'PUSH_ENCRYPTION_KEY', + isDone: true, + payload: { + pemPublicKey: pemKeyPair.pemPublicKey, + }, + }); + } + + // Prepare dataset bulk + vOnStatusUpdate({ + title: 'PREPARE_PROTECTED_DATA_BULK', + isDone: false, + }); + + const { cid, volume } = await iexec.order.prepareDatasetBulk( + vBulkAccesses, + { + maxDatasetPerTask: parseInt(vMaxProtectedDataPerTask.toString()), + } + ); + + vOnStatusUpdate({ + title: 'PREPARE_PROTECTED_DATA_BULK', + isDone: true, + }); + + // Create request order for the whole bulk (only once) + vOnStatusUpdate({ + title: 'CREATE_BULK_REQUEST', + isDone: false, + }); + + const requestorderToSign = await iexec.order.createRequestorder({ + app: vApp, + appmaxprice: vAppMaxPrice, + workerpool: vWorkerpool, + workerpoolmaxprice: vWorkerpoolMaxPrice, + volume, + category: 0, + tag: SCONE_TAG, + params: { + bulk_cid: cid, + iexec_input_files: vInputFiles, + iexec_secrets: secretsId, + iexec_args: vArgs, + ...(vEncryptResult ? { iexec_result_encryption: true } : {}), + }, + trust: 0, + // bulk order + datasetmaxprice: 0, + dataset: NULL_ADDRESS, + }); + + const requestorder = await iexec.order.signRequestorder(requestorderToSign); + + vOnStatusUpdate({ + title: 'CREATE_BULK_REQUEST', + isDone: true, + }); + + return { + bulkRequest: requestorder, + ...(privateKey ? { pemPrivateKey: privateKey } : {}), + }; + } catch (error) { + console.error('[prepareBulkRequest] ERROR', error); + handleIfProtocolError(error); + throw new WorkflowError({ + message: prepareBulkRequestErrorMessage, + errorCause: error, + }); + } +}; diff --git a/packages/sdk/src/lib/dataProtectorCore/processBulkRequest.ts b/packages/sdk/src/lib/dataProtectorCore/processBulkRequest.ts new file mode 100644 index 000000000..9c0a73068 --- /dev/null +++ b/packages/sdk/src/lib/dataProtectorCore/processBulkRequest.ts @@ -0,0 +1,364 @@ +import { sumTags } from 'iexec/utils'; +import { SCONE_TAG } from '../../config/config.js'; +import { + WorkflowError, + processProtectedDataErrorMessage, + handleIfProtocolError, + ValidationError, +} from '../../utils/errors.js'; +import { + checkUserVoucher, + filterWorkerpoolOrders, +} from '../../utils/processProtectedData.models.js'; +import { + addressOrEnsSchema, + booleanSchema, + bulkRequestSchema, + stringSchema, + throwIfMissing, + validateOnStatusUpdateCallback, +} from '../../utils/validators.js'; +import { + BulkRequest, + DefaultWorkerpoolConsumer, + MatchOptions, + OnStatusUpdateFn, + ProcessBulkRequestParams, + ProcessBulkRequestResponse, + ProcessBulkRequestResponseBase, + ProcessBulkRequestResponseWithResult, + ProcessBulkRequestStatuses, +} from '../types/index.js'; +import { IExecConsumer, VoucherInfo } from '../types/internalTypes.js'; +import { getResultFromCompletedTask } from './getResultFromCompletedTask.js'; +import { waitForTaskCompletion } from './waitForTaskCompletion.js'; + +export type ProcessBulkRequest = typeof processBulkRequest; + +// Helper function to avoid unsafe setTimeout reference in loop +const waitForRetry = (ms: number): Promise => { + return new Promise((resolve) => { + setTimeout(() => resolve(), ms); + }); +}; + +export const processBulkRequest = async < + Params extends ProcessBulkRequestParams +>({ + iexec = throwIfMissing(), + defaultWorkerpool, + bulkRequest, + workerpool, + useVoucher = false, + voucherOwner, + path, + pemPrivateKey, + waitForResult = false, + onStatusUpdate = () => {}, +}: IExecConsumer & DefaultWorkerpoolConsumer & Params): Promise< + ProcessBulkRequestResponse +> => { + const vRequestorder = bulkRequestSchema() + .label('bulkRequest') + .required() + .validateSync(bulkRequest) as BulkRequest; // Type assertion after validation + const vWorkerpool = addressOrEnsSchema() + .default(defaultWorkerpool) // Default workerpool if none is specified + .label('workerpool') + .validateSync(workerpool); + const vUseVoucher = booleanSchema() + .label('useVoucher') + .validateSync(useVoucher); + const vVoucherOwner = addressOrEnsSchema() + .label('voucherOwner') + .validateSync(voucherOwner); + const vWaitForResult = booleanSchema() + .label('waitForResult') + .validateSync(waitForResult); + const vPath = stringSchema().label('path').validateSync(path); + const vPemPrivateKey = stringSchema() + .label('pemPrivateKey') + .validateSync(pemPrivateKey); + const vOnStatusUpdate = + validateOnStatusUpdateCallback< + OnStatusUpdateFn + >(onStatusUpdate); + + 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 (vWaitForResult && iexecResultEncryption && !vPemPrivateKey) { + throw new ValidationError( + 'Missing pemPrivateKey required for result decryption' + ); + } + + if (vWaitForResult && !iexecResultEncryption && vPemPrivateKey) { + throw new ValidationError( + 'pemPrivateKey is passed but result encryption is not enabled in bulkRequest this is likely an error when preparing the bulk request' + ); + } + + try { + let userVoucher: VoucherInfo | undefined; + if (vUseVoucher) { + try { + userVoucher = await iexec.voucher.showUserVoucher( + vVoucherOwner || vRequestorder.requester + ); + checkUserVoucher({ userVoucher }); + } catch (err) { + if (err?.message?.startsWith('No Voucher found for address')) { + throw new Error( + 'Oops, it seems your wallet is not associated with any voucher. Check on https://builder.iex.ec/' + ); + } + throw err; + } + } + + vOnStatusUpdate({ + title: 'FETCH_ORDERS', + isDone: false, + }); + + // Fetch app order + const apporder = await iexec.orderbook + .fetchAppOrderbook({ + app: vRequestorder.app, + minTag: SCONE_TAG, + maxTag: SCONE_TAG, + workerpool: vWorkerpool, + }) + .then((appOrderbook) => { + const desiredPriceAppOrderbook = appOrderbook.orders.filter( + (order) => order.order.appprice <= Number(vRequestorder.appmaxprice) + ); + const desiredPriceAppOrder = desiredPriceAppOrderbook[0]?.order; + if (!desiredPriceAppOrder) { + throw new Error('No App order found for the desired price'); + } + return desiredPriceAppOrder; + }); + + vOnStatusUpdate({ + title: 'FETCH_ORDERS', + isDone: true, + }); + + const volume = 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 volume - Number(consumed); + }; + + vOnStatusUpdate({ + title: 'CREATE_BULK_TASKS', + isDone: false, + payload: { + remainingVolume: await getRemainingVolume(), + totalVolume: volume, + }, + }); + + // While loop to match orders until volume is reached + let remainingVolume: number; + while ((remainingVolume = await getRemainingVolume()) > 0) { + // Fetch workerpool order + const workerpoolorder = await iexec.orderbook + .fetchWorkerpoolOrderbook({ + workerpool: vWorkerpool, + app: vRequestorder.app, + dataset: vRequestorder.dataset, // For bulk requests, we don't specify a specific dataset + requester: vRequestorder.requester, + isRequesterStrict: useVoucher, + minTag: sumTags([apporder.tag, vRequestorder.tag]), + category: 0, + }) + .then((workerpoolOrderbook) => { + const desiredPriceWorkerpoolOrder = filterWorkerpoolOrders({ + workerpoolOrders: workerpoolOrderbook.orders, + workerpoolMaxPrice: Number(vRequestorder.workerpoolmaxprice), + useVoucher: vUseVoucher, + userVoucher, + }); + return desiredPriceWorkerpoolOrder; + }); + + // If no workerpool order is available, wait and retry + if (!workerpoolorder) { + vOnStatusUpdate({ + title: 'WAIT_FOR_WORKERPOOL_AVAILABILITY', + isDone: false, + payload: { + remainingVolume, + totalVolume: volume, + }, + }); + + // Wait 5 seconds before retrying + await waitForRetry(5000); + continue; + } + + const orders = { + requestorder: vRequestorder, + workerpoolorder: workerpoolorder, + apporder: apporder, + }; + + const matchOptions: MatchOptions = { + useVoucher: vUseVoucher, + ...(vVoucherOwner ? { voucherAddress: userVoucher?.address } : {}), + }; + + const { + volume: matchableVolume, + total, + sponsored, + } = await iexec.order.estimateMatchOrders(orders, matchOptions); + + vOnStatusUpdate({ + title: 'REQUEST_TO_PROCESS_BULK_DATA', + isDone: false, + payload: { + cost: total.toString(), + sponsoredCost: vUseVoucher ? sponsored.toString() : undefined, + remainingVolume, + matchVolume: matchableVolume.toNumber(), + totalVolume: volume, + }, + }); + + const { + dealid, + txHash, + volume: matchedVolume, + } = await iexec.order.matchOrders(orders, matchOptions); + + vOnStatusUpdate({ + title: 'REQUEST_TO_PROCESS_BULK_DATA', + isDone: true, + payload: { + txHash: txHash, + dealId: dealid, + matchVolume: matchedVolume.toNumber(), + totalVolume: volume, + }, + }); + } + + const { deals } = await iexec.deal.fetchDealsByRequestorder( + requestorderHash, + { pageSize: Math.max(volume, 10) } // Fetch all deals (min page size 10) + ); + + const tasks: ProcessBulkRequestResponseBase['tasks'] = []; + + for (const deal of deals) { + const dealTasks = await Promise.all( + new Array(deal.botSize) + .fill(deal.botFirst) + .map(async (botFirst, i) => ({ + taskId: await iexec.deal.computeTaskId(deal.dealid, botFirst + i), + dealId: deal.dealid, + bulkIndex: botFirst + i, + })) + ); + tasks.push(...dealTasks); + } + tasks.sort((a, b) => a.bulkIndex - b.bulkIndex); + + vOnStatusUpdate({ + title: 'CREATE_BULK_TASKS', + isDone: true, + payload: { + totalMatches: deals.length, + totalVolume: volume, + tasks, + }, + }); + + if (!vWaitForResult) { + return { + tasks, + } as ProcessBulkRequestResponse; + } + + const tasksWithResults = + tasks as ProcessBulkRequestResponseWithResult['tasks']; + + await Promise.all( + tasksWithResults.map(async (task) => { + try { + vOnStatusUpdate({ + title: 'PROCESS_BULK_SLICE', + isDone: false, + payload: task, + }); + vOnStatusUpdate({ + title: 'TASK_EXECUTION', + isDone: false, + payload: task, + }); + const { status, success } = await waitForTaskCompletion({ + iexec, + taskId: task.taskId, + dealId: task.dealId, + }); + task.status = status; + task.success = success; + vOnStatusUpdate({ + title: 'TASK_EXECUTION', + isDone: true, + payload: task, + }); + if (!success) { + throw new Error(`Task ended with status: ${status}`); + } + const { result } = await getResultFromCompletedTask({ + iexec, + taskId: task.taskId, + path: vPath, + pemPrivateKey: vPemPrivateKey, + onStatusUpdate: (update) => { + vOnStatusUpdate({ + ...update, + payload: { ...update.payload, task }, + }); + }, + }); + task.result = result; + vOnStatusUpdate({ + title: 'PROCESS_BULK_SLICE', + isDone: true, + payload: task, + }); + } catch (error) { + task.error = error as Error; + vOnStatusUpdate({ + title: 'PROCESS_BULK_SLICE', + isDone: true, + payload: task, + }); + } + }) + ); + + return { + tasks: tasksWithResults, + }; + } catch (error) { + console.error('[processBulkRequest] ERROR', error); + handleIfProtocolError(error); + throw new WorkflowError({ + message: processProtectedDataErrorMessage, + errorCause: error, + }); + } +}; diff --git a/packages/sdk/src/lib/dataProtectorCore/processProtectedData.ts b/packages/sdk/src/lib/dataProtectorCore/processProtectedData.ts index be3fbe74e..3ca626a81 100644 --- a/packages/sdk/src/lib/dataProtectorCore/processProtectedData.ts +++ b/packages/sdk/src/lib/dataProtectorCore/processProtectedData.ts @@ -9,6 +9,7 @@ import { WorkflowError, processProtectedDataErrorMessage, handleIfProtocolError, + ValidationError, } from '../../utils/errors.js'; import { checkUserVoucher, @@ -117,19 +118,19 @@ export const processProtectedData = async < const vWaitForResult = booleanSchema() .label('waitForResult') .validateSync(waitForResult); + const vOnStatusUpdate = + validateOnStatusUpdateCallback< + OnStatusUpdateFn + >(onStatusUpdate); // Validate that if pemPrivateKey is provided, encryptResult must be true if (vPemPrivateKey && !vEncryptResult) { - throw new Error( + throw new ValidationError( 'pemPrivateKey can only be provided when encryptResult is true' ); } - try { - const vOnStatusUpdate = - validateOnStatusUpdateCallback< - OnStatusUpdateFn - >(onStatusUpdate); + try { let requester = await iexec.wallet.getAddress(); if (vUserWhitelist) { const isValidWhitelist = await isERC734(iexec, vUserWhitelist); @@ -213,8 +214,8 @@ export const processProtectedData = async < iexec.orderbook .fetchAppOrderbook({ app: vApp, - minTag: ['tee', 'scone'], - maxTag: ['tee', 'scone'], + minTag: SCONE_TAG, + maxTag: SCONE_TAG, workerpool: vWorkerpool, }) .then((appOrderbook) => { @@ -236,8 +237,7 @@ export const processProtectedData = async < dataset: vProtectedData, requester: requester, // public orders + user specific orders isRequesterStrict: useVoucher, // If voucher, we only want user specific orders - minTag: ['tee', 'scone'], - maxTag: ['tee', 'scone'], + minTag: SCONE_TAG, category: 0, }), // for app whitelist @@ -247,8 +247,7 @@ export const processProtectedData = async < dataset: vProtectedData, requester: requester, // public orders + user specific orders isRequesterStrict: useVoucher, // If voucher, we only want user specific orders - minTag: ['tee', 'scone'], - maxTag: ['tee', 'scone'], + minTag: SCONE_TAG, category: 0, }), ]).then( diff --git a/packages/sdk/src/lib/types/coreTypes.ts b/packages/sdk/src/lib/types/coreTypes.ts index 34386d473..ee6470434 100644 --- a/packages/sdk/src/lib/types/coreTypes.ts +++ b/packages/sdk/src/lib/types/coreTypes.ts @@ -141,6 +141,11 @@ export type GetGrantedAccessParams = { * Size of the page to fetch */ pageSize?: number; + + /** + * Filter for bulk orders only + */ + bulkOnly?: boolean; }; export type GetProtectedDataParams = { @@ -154,7 +159,9 @@ export type GetProtectedDataParams = { export type GrantAccessStatuses = | 'CREATE_DATASET_ORDER' - | 'PUBLISH_DATASET_ORDER'; + | 'PUBLISH_DATASET_ORDER' + | 'CREATE_BULK_ORDER' + | 'PUBLISH_BULK_ORDER'; export type GrantAccessParams = { /** @@ -184,6 +191,14 @@ export type GrantAccessParams = { */ numberOfAccess?: number; + /** + * Enable bulk processing for the granted access + * + * Bulk processing allows multiple protected data to be processed in a single task without paying per access. + * `pricePerAccess` and `numberOfAccess` should be left undefined when `allowBulk` is true. + */ + allowBulk?: boolean; + /** * Callback function that will get called at each step of the process */ @@ -218,8 +233,10 @@ export type WaitForTaskCompletionParams = { onStatusUpdate?: OnStatusUpdateFn; }; +export type TaskStatus = 'COMPLETED' | 'FAILED' | 'TIMEOUT'; + export type WaitForTaskCompletionResponse = { - status: 'COMPLETED' | 'FAILED' | 'TIMEOUT'; + status: TaskStatus; success: boolean; }; @@ -292,8 +309,6 @@ export type TransferResponse = { // ---------------------ProcessProtectedData Types------------------------------------ export type ProcessProtectedDataStatuses = | 'FETCH_ORDERS' - | 'FETCH_PROTECTED_DATA_ORDERBOOK' - | 'FETCH_APP_ORDERBOOK' | 'FETCH_WORKERPOOL_ORDERBOOK' | 'PUSH_REQUESTER_SECRET' | 'GENERATE_ENCRYPTION_KEY' @@ -321,24 +336,26 @@ export type ProcessProtectedDataParams = { /** * The maximum price of dataset per task for processing the protected data. - @default = 0 - */ + * @default 0 + */ dataMaxPrice?: number; /** * The maximum price of application per task for processing the protected data. - @default = 0 - */ + * @default 0 + */ appMaxPrice?: number; /** * The maximum price of workerpool per task for processing the protected data. - @default = 0 - */ + * @default 0 + */ workerpoolMaxPrice?: number; /** * The file name of the desired file in the returned ZIP file. + * + * Ignored if `waitForResult` is `false` */ path?: string; @@ -375,7 +392,7 @@ export type ProcessProtectedDataParams = { /** * Enable result encryption for the processed data. - * @default = false + * @default false */ encryptResult?: boolean; @@ -387,7 +404,7 @@ export type ProcessProtectedDataParams = { /** * Whether to wait for the result of the processing or not. - * @default = true + * @default true */ waitForResult?: boolean; @@ -412,3 +429,188 @@ export type ProcessProtectedDataResponseWithResult = export type ProcessProtectedDataResponse = T extends { waitForResult: false } ? ProcessProtectedDataResponseBase : ProcessProtectedDataResponseWithResult; + +// --------------------- PrepareBulkRequest Types------------------------------------ + +export type PrepareBulkRequestStatuses = + | 'PUSH_REQUESTER_SECRET' + | 'GENERATE_ENCRYPTION_KEY' + | 'PUSH_ENCRYPTION_KEY' + | 'PREPARE_PROTECTED_DATA_BULK' + | 'CREATE_BULK_REQUEST'; + +export type PrepareBulkRequestParams = { + /** + * Array of accesses allowing protected data processing in bulk + * + * use `bulkOnly: true` option in `getGrantedAccess()` to obtain bulk accesses + */ + bulkAccesses: GrantedAccess[]; + + /** + * Address or ENS of the app to use for processing the protected data + */ + app: AddressOrENS; + + /** + * Maximum number of protected data to process per task (any protected data exceeding this number will be processed in another task) + * + * @default 100 + */ + maxProtectedDataPerTask?: number; + + /** + * Maximum price willing to pay for the app order (in nRLC) + */ + appMaxPrice?: number; + + /** + * Maximum price willing to pay for the workerpool order (in nRLC) + */ + workerpoolMaxPrice?: number; + + /** + * Arguments to pass to the application + */ + args?: string; + + /** + * URLs of input files to be used by the application + */ + inputFiles?: string[]; + + /** + * Requester secrets necessary for the application's execution. + * It is represented as a mapping of numerical identifiers to corresponding secrets. + */ + secrets?: Record; + + /** + * The workerpool to use for the application's execution. (default any workerpool) + */ + workerpool?: AddressOrENS; + + /** + * Enable result encryption for the processed data. + * @default false + */ + encryptResult?: boolean; + + /** + * Private key in PEM format for result encryption/decryption. + * If not provided and encryptResult is true, a new key pair will be generated. + */ + pemPrivateKey?: string; + + /** + * Callback function that will get called at each step of the process + */ + onStatusUpdate?: OnStatusUpdateFn; +}; + +export type BulkRequest = { + app: string; + appmaxprice: string; // string notation allowed for big integers + workerpool: string; + workerpoolmaxprice: string; // string notation allowed for big integers + dataset: string; // "0x0000000000000000000000000000000000000000" + datasetmaxprice: string; // "0" + params: string; // contains bulkCid + requester: string; + beneficiary: string; + callback: string; + category: string; // string notation allowed for big integers + volume: string; // string notation allowed for big integers + tag: string; + trust: string; + salt: string; + sign: string; +}; + +export type PrepareBulkRequestResponse = { + bulkRequest: BulkRequest; + pemPrivateKey?: string; +}; + +// ---------------------ProcessBulkRequest Types------------------------------------ + +export type ProcessBulkRequestStatuses = + | 'FETCH_ORDERS' + | 'CREATE_BULK_TASKS' + | 'WAIT_FOR_WORKERPOOL_AVAILABILITY' + | 'REQUEST_TO_PROCESS_BULK_DATA' + | 'PROCESS_BULK_SLICE' + | 'TASK_EXECUTION' + | 'TASK_RESULT_DOWNLOAD' + | 'TASK_RESULT_DECRYPT'; + +export type ProcessBulkRequestParams = { + /** + * bulk request to process + */ + bulkRequest: BulkRequest; + + /** + * Path to the result file in the app's output + * + * Ignored if `waitForResult` is `false` + */ + path?: string; + + /** + * The workerpool to use for the application's execution. (default iExec production workerpool) + */ + workerpool?: AddressOrENS; + + /** + * A boolean that indicates whether to use a voucher or no. + */ + useVoucher?: boolean; + + /** + * Override the voucher contract to use, must be combined with useVoucher: true the user must be authorized by the voucher's owner to use it. + */ + voucherOwner?: AddressOrENS; + + /** + * Private key in PEM format for result decryption. + * + * Required if `bulkRequest` use results encryption and `waitForResult` is `true`. + */ + pemPrivateKey?: string; + + /** + * Whether to wait for the result of the bulk request. + * @default false + */ + waitForResult?: boolean; + + /** + * Callback function that will get called at each step of the process + */ + onStatusUpdate?: OnStatusUpdateFn; +}; + +export type ProcessBulkRequestResponse = T extends { waitForResult: true } + ? ProcessBulkRequestResponseWithResult + : ProcessBulkRequestResponseBase; + +export type ProcessBulkRequestResponseBase = { + tasks: Array<{ + taskId: string; + dealId: string; + bulkIndex: number; + }>; +}; + +export type ProcessBulkRequestResponseWithResult = { + tasks: Array<{ + taskId: string; + dealId: string; + bulkIndex: number; + success: boolean; + status: TaskStatus; + result?: ArrayBuffer; + error?: Error; + }>; +}; diff --git a/packages/sdk/src/utils/errors.ts b/packages/sdk/src/utils/errors.ts index 3b616b9f3..78ccb4f55 100644 --- a/packages/sdk/src/utils/errors.ts +++ b/packages/sdk/src/utils/errors.ts @@ -1,11 +1,12 @@ -import { ValidationError } from 'yup'; import { ApiCallError } from 'iexec/errors'; +import { ValidationError } from 'yup'; export const grantAccessErrorMessage = 'Failed to grant access'; export const consumeProtectedDataErrorMessage = 'Failed to consume protected data'; export const processProtectedDataErrorMessage = 'Failed to process protected data'; +export const prepareBulkRequestErrorMessage = 'Failed to prepare bulk request'; export class WorkflowError extends Error { isProtocolError: boolean; diff --git a/packages/sdk/src/utils/validators.ts b/packages/sdk/src/utils/validators.ts index 00e1b6794..e60958ccc 100644 --- a/packages/sdk/src/utils/validators.ts +++ b/packages/sdk/src/utils/validators.ts @@ -1,5 +1,6 @@ import { isAddress } from 'ethers'; 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) => { @@ -98,6 +99,43 @@ export const grantedAccessSchema = () => .noUnknown() .default(undefined); +export const bulkRequestSchema = () => + object({ + app: addressSchema().required(), + appmaxprice: positiveIntegerStringSchema().required(), + workerpool: addressSchema().required(), + workerpoolmaxprice: positiveIntegerStringSchema().required(), + dataset: addressSchema().oneOf([NULL_ADDRESS]).required(), + datasetmaxprice: positiveIntegerStringSchema().oneOf(['0']).required(), + params: stringSchema() + .test( + 'is-valid-bulk-params', + '${path} should be a valid JSON string with bulk_cid field', + (value) => { + try { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { bulk_cid } = JSON.parse(value); + if (typeof bulk_cid === 'string') { + return true; + } + } catch {} + return false; + } + ) + .required(), + requester: addressSchema().required(), + beneficiary: addressSchema().required(), + callback: addressSchema().required(), + category: positiveIntegerStringSchema().required(), + volume: positiveStrictIntegerStringSchema().required(), + tag: stringSchema().required(), + trust: positiveIntegerStringSchema().required(), + salt: stringSchema().required(), + sign: stringSchema().required(), + }) + .noUnknown() + .default(undefined); + export const urlArraySchema = () => array().of(string().matches(/^http[s]?:\/\//, '${value} should be a url')); diff --git a/packages/sdk/tests/docker-compose.yml b/packages/sdk/tests/docker-compose.yml index ee9962a1b..47b282387 100644 --- a/packages/sdk/tests/docker-compose.yml +++ b/packages/sdk/tests/docker-compose.yml @@ -111,7 +111,7 @@ services: condition: service_started market-api: - image: iexechub/iexec-market-api:6.4 + image: iexechub/iexec-market-api:7.1.0 restart: unless-stopped ports: - 3000:3000 diff --git a/packages/sdk/tests/e2e/dataProtectorCore/getGrantedAccess.test.ts b/packages/sdk/tests/e2e/dataProtectorCore/getGrantedAccess.test.ts index 05e1b8df4..a4fe23f12 100644 --- a/packages/sdk/tests/e2e/dataProtectorCore/getGrantedAccess.test.ts +++ b/packages/sdk/tests/e2e/dataProtectorCore/getGrantedAccess.test.ts @@ -676,4 +676,61 @@ describe('dataProtectorCore.getGrantedAccess()', () => { }, MAX_EXPECTED_WEB2_SERVICES_TIME ); + + it( + 'filters for bulk orders only when bulkOnly is true', + async () => { + // Create a protected data + const protectedData = await dataProtectorCore.protectData({ + data: { email: 'bulk-test@example.com' }, + name: 'test protected data for bulk filtering', + }); + + // Deploy a SCONE app + const sconeAppAddress = await deployRandomApp({ + ethProvider: getTestConfig(Wallet.createRandom().privateKey)[0], + teeFramework: 'scone', + }); + + const regularUserAddress = Wallet.createRandom().address; + const bulkUserAddress = Wallet.createRandom().address; + + // Grant regular access (non-bulk) + await dataProtectorCore.grantAccess({ + protectedData: protectedData.address, + authorizedApp: sconeAppAddress, + authorizedUser: regularUserAddress, + numberOfAccess: 5, + allowBulk: false, + }); + + // Grant bulk access + await dataProtectorCore.grantAccess({ + protectedData: protectedData.address, + authorizedApp: sconeAppAddress, + authorizedUser: bulkUserAddress, + allowBulk: true, + }); + + // Test without bulkOnly filter - should return both orders + const allAccess = await dataProtectorCore.getGrantedAccess({ + protectedData: protectedData.address, + authorizedApp: sconeAppAddress, + }); + expect(allAccess.grantedAccess.length).toBe(2); + + // Test with bulkOnly filter - should return only bulk orders + const bulkOnlyAccess = await dataProtectorCore.getGrantedAccess({ + protectedData: protectedData.address, + authorizedApp: sconeAppAddress, + bulkOnly: true, + }); + expect(bulkOnlyAccess.grantedAccess.length).toBe(1); + expect(bulkOnlyAccess.grantedAccess[0].volume).toBe('9007199254740991'); // Number.MAX_SAFE_INTEGER + expect( + bulkOnlyAccess.grantedAccess[0].requesterrestrict.toLowerCase() + ).toBe(bulkUserAddress.toLowerCase()); + }, + 4 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + ); }); diff --git a/packages/sdk/tests/e2e/dataProtectorCore/grantAccess.test.ts b/packages/sdk/tests/e2e/dataProtectorCore/grantAccess.test.ts index a3f748cef..e51464073 100644 --- a/packages/sdk/tests/e2e/dataProtectorCore/grantAccess.test.ts +++ b/packages/sdk/tests/e2e/dataProtectorCore/grantAccess.test.ts @@ -263,4 +263,71 @@ describe('dataProtectorCore.grantAccess()', () => { }, MAX_EXPECTED_WEB2_SERVICES_TIME ); + + it( + 'pass with bulk processing enabled', + async () => { + const onStatusUpdateMock = jest.fn(); + const grantedAccess = await dataProtectorCore.grantAccess({ + ...input, + authorizedApp: sconeAppAddress, + allowBulk: true, + pricePerAccess: 0, + onStatusUpdate: onStatusUpdateMock, + }); + expect(grantedAccess).toBeDefined(); + expect(grantedAccess.volume).toBe('9007199254740991'); // Number.MAX_SAFE_INTEGER + expect(grantedAccess.datasetprice).toBe('0'); // Price should be 0 for bulk orders + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(1, { + title: 'CREATE_DATASET_ORDER', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(2, { + title: 'CREATE_DATASET_ORDER', + isDone: true, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(3, { + title: 'PUBLISH_DATASET_ORDER', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(4, { + title: 'PUBLISH_DATASET_ORDER', + isDone: true, + }); + }, + MAX_EXPECTED_WEB2_SERVICES_TIME + ); + + it( + 'pass with bulk processing disabled (default behavior)', + async () => { + const onStatusUpdateMock = jest.fn(); + const grantedAccess = await dataProtectorCore.grantAccess({ + ...input, + authorizedApp: sconeAppAddress, + allowBulk: false, + onStatusUpdate: onStatusUpdateMock, + }); + expect(grantedAccess).toBeDefined(); + expect(grantedAccess.datasetprice).toBe('0'); // Default price + + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(1, { + title: 'CREATE_DATASET_ORDER', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(2, { + title: 'CREATE_DATASET_ORDER', + isDone: true, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(3, { + title: 'PUBLISH_DATASET_ORDER', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(4, { + title: 'PUBLISH_DATASET_ORDER', + isDone: true, + }); + }, + MAX_EXPECTED_WEB2_SERVICES_TIME + ); }); diff --git a/packages/sdk/tests/unit/dataProtectorCore/getGrantedAccess.test.ts b/packages/sdk/tests/unit/dataProtectorCore/getGrantedAccess.test.ts index de6ffb21c..0b0438144 100644 --- a/packages/sdk/tests/unit/dataProtectorCore/getGrantedAccess.test.ts +++ b/packages/sdk/tests/unit/dataProtectorCore/getGrantedAccess.test.ts @@ -125,6 +125,7 @@ describe('getGrantedAccess', () => { pageSize: 10, isRequesterStrict: true, isAppStrict: true, + bulkOnly: false, }); }); }); @@ -242,6 +243,7 @@ describe('getGrantedAccess', () => { pageSize: undefined, isRequesterStrict: false, isAppStrict: true, + bulkOnly: false, }); expect(grantedAccessResult.count).toBe(1); diff --git a/packages/sdk/tests/unit/dataProtectorCore/prepareBulkRequest.test.ts b/packages/sdk/tests/unit/dataProtectorCore/prepareBulkRequest.test.ts new file mode 100644 index 000000000..05d7d64be --- /dev/null +++ b/packages/sdk/tests/unit/dataProtectorCore/prepareBulkRequest.test.ts @@ -0,0 +1,401 @@ +import { describe, expect, it, jest, beforeAll } from '@jest/globals'; +import { DATASET_INFINITE_VOLUME } from 'iexec/utils'; +import { ValidationError } from 'yup'; +import { PrepareBulkRequest } from '../../../src/lib/dataProtectorCore/prepareBulkRequest.js'; +import { + WorkflowError, + processProtectedDataErrorMessage, +} from '../../../src/utils/errors.js'; +import { + getRandomAddress, + getRequiredFieldMessage, + mockWorkerpoolOrderbook, +} from '../../test-utils.js'; + +// Mock bulk orders for testing +const mockBulkAccesses = [ + { + dataset: getRandomAddress(), + datasetprice: '0', // 1 RLC in wei + volume: DATASET_INFINITE_VOLUME.toString(), + tag: '0x0000000000000000000000000000000000000000000000000000000000000000', + apprestrict: '0x0000000000000000000000000000000000000000', + workerpoolrestrict: '0x0000000000000000000000000000000000000000', + requesterrestrict: '0x0000000000000000000000000000000000000000', + salt: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + sign: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890', + remainingAccess: 10, + }, + { + dataset: getRandomAddress(), + datasetprice: '0', // 2 RLC in wei + volume: DATASET_INFINITE_VOLUME.toString(), + tag: '0x0000000000000000000000000000000000000000000000000000000000000000', + apprestrict: '0x0000000000000000000000000000000000000000', + workerpoolrestrict: '0x0000000000000000000000000000000000000000', + requesterrestrict: '0x0000000000000000000000000000000000000000', + salt: '0x2345678901bcdef12345678901bcdef12345678901bcdef12345678901bcdef1', + sign: '0xbcdef12345678901bcdef12345678901bcdef12345678901bcdef12345678901', + remainingAccess: 5, + }, +]; + +jest.unstable_mockModule('../../../src/utils/whitelist.js', () => ({ + isERC734: jest.fn(), +})); + +jest.unstable_mockModule( + '../../../src/lib/dataProtectorCore/smartContract/getWhitelistContract.js', + () => ({ + getWhitelistContract: jest.fn(), + }) +); + +jest.unstable_mockModule( + '../../../src/lib/dataProtectorCore/smartContract/whitelistContract.read.js', + () => ({ + isAddressInWhitelist: jest.fn(), + }) +); + +jest.unstable_mockModule( + '../../../src/utils/processProtectedData.models.js', + () => ({ + filterWorkerpoolOrders: jest.fn( + () => mockWorkerpoolOrderbook.orders[0].order + ), + checkUserVoucher: jest.fn(), + }) +); + +jest.unstable_mockModule('../../../src/utils/pushRequesterSecret.js', () => ({ + pushRequesterSecret: jest + .fn<() => Promise>>() + .mockResolvedValue({ 1: 'secrets-id-123' }), +})); + +describe('prepareBulkRequest', () => { + let testedModule: any; + let prepareBulkRequest: PrepareBulkRequest; + + beforeAll(async () => { + // import tested module after all mocked modules + testedModule = await import( + '../../../src/lib/dataProtectorCore/prepareBulkRequest.js' + ); + prepareBulkRequest = testedModule.prepareBulkRequest; + }); + + describe('Check validation for input parameters', () => { + describe('When bulkAccesses is NOT given', () => { + it('should throw an error with the correct message', async () => { + // --- GIVEN + const missingBulkAccesses = undefined; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: missingBulkAccesses, + app: getRandomAddress(), + }) + // --- THEN + ).rejects.toThrow('bulkAccesses is required and must not be empty'); + }); + }); + + describe('When bulkAccesses is empty', () => { + it('should throw an error with the correct message', async () => { + // --- GIVEN + const emptyBulkAccesses: any[] = []; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: emptyBulkAccesses, + app: getRandomAddress(), + }) + // --- THEN + ).rejects.toThrow('bulkAccesses is required and must not be empty'); + }); + }); + + describe('When app address is NOT given', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const missingAppAddress = undefined; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: missingAppAddress, + }) + // --- THEN + ).rejects.toThrow(new ValidationError(getRequiredFieldMessage('app'))); + }); + }); + + describe('When given app address is NOT valid', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidAppAddress = '0x123456...'; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: invalidAppAddress, + }) + // --- THEN + ).rejects.toThrow( + new ValidationError('app should be an ethereum address or a ENS name') + ); + }); + }); + + describe('When maxProtectedDataPerTask is not a positive number', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidMaxProtectedDataPerTask = -1; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: getRandomAddress(), + maxProtectedDataPerTask: invalidMaxProtectedDataPerTask, + }) + // --- THEN + ).rejects.toThrow( + new ValidationError( + 'maxProtectedDataPerTask must be greater than or equal to 0' + ) + ); + }); + }); + + describe('When appMaxPrice is not a positive number', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidAppMaxPrice = -1; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: getRandomAddress(), + appMaxPrice: invalidAppMaxPrice, + }) + // --- THEN + ).rejects.toThrow( + new ValidationError('appMaxPrice must be greater than or equal to 0') + ); + }); + }); + + describe('When workerpoolMaxPrice is not a positive number', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidWorkerpoolMaxPrice = -1; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: getRandomAddress(), + workerpoolMaxPrice: invalidWorkerpoolMaxPrice, + }) + // --- THEN + ).rejects.toThrow( + new ValidationError( + 'workerpoolMaxPrice must be greater than or equal to 0' + ) + ); + }); + }); + + describe('When given args is NOT valid', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidArgs = { test: '123' }; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: getRandomAddress(), + // @ts-expect-error explicitly invalid args + args: invalidArgs, + }) + // --- THEN + ).rejects.toThrow(new ValidationError('args should be a string')); + }); + }); + + describe('When given inputFiles is not a valid URL array', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidInputFiles = ['notAUrl']; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: getRandomAddress(), + inputFiles: invalidInputFiles, + }) + // --- THEN + ).rejects.toThrow(new ValidationError('notAUrl should be a url')); + }); + }); + + describe('When given secrets is NOT valid', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidSecrets = { 0: 123, 1: 'validSecret' }; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: getRandomAddress(), + // @ts-expect-error explicitly invalid secrets + secrets: invalidSecrets, + }) + // --- THEN + ).rejects.toThrow( + new ValidationError( + 'secrets must be an object with numeric keys and string values' + ) + ); + }); + }); + + describe('When given workerpool is NOT valid', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidWorkerpool = '0x123456...'; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: getRandomAddress(), + workerpool: invalidWorkerpool, + }) + // --- THEN + ).rejects.toThrow( + new ValidationError( + 'workerpool should be an ethereum address or a ENS name' + ) + ); + }); + }); + + describe('When pemPrivateKey is provided without encryptResult', () => { + it('should throw an error with the correct message', async () => { + // --- GIVEN + const encryptResult = false; + const pemPrivateKey = 'test-key'; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + bulkAccesses: mockBulkAccesses, + app: getRandomAddress(), + encryptResult, + pemPrivateKey, + }) + // --- THEN + ).rejects.toThrow( + 'pemPrivateKey can only be provided when encryptResult is true' + ); + }); + }); + }); + + // TODO implement whitelist check + describe.skip('When userWhitelist is given but user is NOT in this whitelist', () => { + it('should throw a WorkflowError with the correct message', async () => { + // --- GIVEN + const validWhitelistAddress = getRandomAddress(); + const bulkAccessesWithWhitelist = [ + { + ...mockBulkAccesses[0], + requesterrestrict: validWhitelistAddress, + }, + ]; + + // Say that given whitelist is valid + const { isERC734 } = (await import( + '../../../src/utils/whitelist.js' + )) as { isERC734: jest.Mock<() => Promise> }; + isERC734.mockResolvedValue(true); + + // Say that current user (requester) is NOT in the whitelist + const { isAddressInWhitelist } = (await import( + '../../../src/lib/dataProtectorCore/smartContract/whitelistContract.read.js' + )) as { isAddressInWhitelist: jest.Mock<() => Promise> }; + isAddressInWhitelist.mockResolvedValue(false); + + const iexec = { + wallet: { + getAddress: jest + .fn<() => Promise>() + .mockResolvedValue(getRandomAddress()), + }, + order: { + prepareDatasetBulk: jest + .fn<() => Promise<{ cid: string; volume: number }>>() + .mockResolvedValue({ cid: 'test-cid', volume: 1 }), + createRequestorder: jest + .fn<() => Promise>() + .mockResolvedValue({}), + signRequestorder: jest.fn<() => Promise>().mockResolvedValue({}), + }, + }; + + await expect( + // --- WHEN + prepareBulkRequest({ + // @ts-expect-error Minimal iexec implementation with only what's necessary for this test + iexec, + bulkAccesses: bulkAccessesWithWhitelist, + app: getRandomAddress(), + }) + // --- THEN + ).rejects.toThrow( + new WorkflowError({ + message: processProtectedDataErrorMessage, + errorCause: Error( + "As a user, you are not in the whitelist. You can't access the protectedData so you can't process it." + ), + }) + ); + }); + }); +}); diff --git a/packages/sdk/tests/unit/dataProtectorCore/processBulkRequest.test.ts b/packages/sdk/tests/unit/dataProtectorCore/processBulkRequest.test.ts new file mode 100644 index 000000000..d8ce3439a --- /dev/null +++ b/packages/sdk/tests/unit/dataProtectorCore/processBulkRequest.test.ts @@ -0,0 +1,246 @@ +import { describe, expect, it, jest, beforeAll } from '@jest/globals'; +import { ValidationError } from 'yup'; +import { type ProcessBulkRequest } from '../../../src/lib/dataProtectorCore/processBulkRequest.js'; +import { + WorkflowError, + processProtectedDataErrorMessage, +} from '../../../src/utils/errors.js'; +import { + getRandomAddress, + getRequiredFieldMessage, + mockWorkerpoolOrderbook, +} from '../../test-utils.js'; +import { resolveWithOneAppOrder } from '../../utils/appOrders.js'; +import { resolveWithOneWorkerpoolOrder } from '../../utils/workerpoolOrders.js'; + +// Mock bulk request for testing +const mockBulkRequest = { + params: + '{"iexec_secrets":{},"iexec_result_storage_provider":"ipfs","bulk_cid":"QmQpQDkEJ6yAgyXPqxwn9Kv3BUTR1pf4mHKW2jeAiE6N34"}', + callback: '0x0000000000000000000000000000000000000000', + beneficiary: '0xc063e7ed698DEf5Ab483299a4867b516a00135Be', + trust: '0', + category: '0', + tag: '0x0000000000000000000000000000000000000000000000000000000000000003', + volume: '4', + requester: '0xc063e7ed698DEf5Ab483299a4867b516a00135Be', + workerpoolmaxprice: '100000000', + workerpool: '0x0000000000000000000000000000000000000000', + datasetmaxprice: '0', + dataset: '0x0000000000000000000000000000000000000000', + appmaxprice: '0', + app: '0x94b1D1f9065fD2a2971Cdb6F59e9E8d3517563E9', + salt: '0x402e71646bae6dc6a9e6069bafe5e0c675b28ad93038775ae715a4ca846bfc4a', + sign: '0xf21c6770574d91aee77df960517a7d22464ef39e9549ebe28ecdd59d5de6a65c34eb27b412abec85482d4e855858caecaf2d93c36c9253604368d7a67258786e1b', +}; + +jest.unstable_mockModule('../../../src/utils/whitelist.js', () => ({ + isERC734: jest.fn(), +})); + +jest.unstable_mockModule( + '../../../src/lib/dataProtectorCore/smartContract/getWhitelistContract.js', + () => ({ + getWhitelistContract: jest.fn(), + }) +); + +jest.unstable_mockModule( + '../../../src/lib/dataProtectorCore/smartContract/whitelistContract.read.js', + () => ({ + isAddressInWhitelist: jest.fn(), + }) +); + +jest.unstable_mockModule( + '../../../src/utils/processProtectedData.models.js', + () => ({ + filterWorkerpoolOrders: jest.fn( + () => mockWorkerpoolOrderbook.orders[0].order + ), + checkUserVoucher: jest.fn(), + }) +); + +jest.unstable_mockModule('../../../src/utils/pushRequesterSecret.js', () => ({ + pushRequesterSecret: jest + .fn<() => Promise>>() + .mockResolvedValue({ 1: 'secrets-id-123' }), +})); + +describe('processBulkRequest', () => { + let testedModule: any; + let processBulkRequest: ProcessBulkRequest; + + beforeAll(async () => { + // import tested module after all mocked modules + testedModule = await import( + '../../../src/lib/dataProtectorCore/processBulkRequest.js' + ); + processBulkRequest = testedModule.processBulkRequest; + }); + + describe('Check validation for input parameters', () => { + describe('When bulkRequest is NOT given', () => { + it('should throw an error with the correct message', async () => { + // --- GIVEN + const missingBulkOrders = undefined; + + await expect( + // --- WHEN + processBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + defaultWorkerpool: getRandomAddress(), + bulkRequest: missingBulkOrders, + }) + // --- THEN + ).rejects.toThrow(getRequiredFieldMessage('bulkRequest')); + }); + }); + + describe('When given workerpool is NOT valid', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidWorkerpool = '0x123456...'; + + await expect( + // --- WHEN + processBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + defaultWorkerpool: getRandomAddress(), + bulkRequest: mockBulkRequest, + workerpool: invalidWorkerpool, + }) + // --- THEN + ).rejects.toThrow( + new ValidationError( + 'workerpool should be an ethereum address or a ENS name' + ) + ); + }); + }); + + describe('When given voucherOwner is NOT valid', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidVoucherOwner = '0x123456...'; + + await expect( + // --- WHEN + processBulkRequest({ + // @ts-expect-error No need for iexec here + iexec: {}, + defaultWorkerpool: getRandomAddress(), + bulkRequest: mockBulkRequest, + voucherOwner: invalidVoucherOwner, + }) + // --- THEN + ).rejects.toThrow( + new ValidationError( + 'voucherOwner should be an ethereum address or a ENS name' + ) + ); + }); + }); + }); + + describe('When there is NO app orders', () => { + it('should throw a WorkflowError with the correct message', async () => { + // --- GIVEN + const iexec = { + wallet: { + getAddress: jest + .fn<() => Promise>() + .mockResolvedValue(getRandomAddress()), + }, + orderbook: { + fetchAppOrderbook: jest + .fn<() => Promise<{ orders: []; count: 0 }>>() + .mockResolvedValue({ orders: [], count: 0 }), // <-- NO app order + fetchWorkerpoolOrderbook: jest + .fn<() => Promise>() + .mockResolvedValue(mockWorkerpoolOrderbook), + }, + order: { + prepareDatasetBulk: jest + .fn<() => Promise<{ cid: string; volume: number }>>() + .mockResolvedValue({ cid: 'test-cid', volume: 1 }), + createRequestorder: jest + .fn<() => Promise>() + .mockResolvedValue({}), + signRequestorder: jest.fn<() => Promise>().mockResolvedValue({}), + }, + }; + + await expect( + // --- WHEN + processBulkRequest({ + // @ts-expect-error Minimal iexec implementation with only what's necessary for this test + iexec, + defaultWorkerpool: getRandomAddress(), + bulkRequest: mockBulkRequest, + }) + // --- THEN + ).rejects.toThrow( + new WorkflowError({ + message: processProtectedDataErrorMessage, + errorCause: Error('No App order found for the desired price'), + }) + ); + }); + }); + + describe('When voucher is used but user has no voucher', () => { + it('should throw a WorkflowError with the correct message', async () => { + // --- GIVEN + const iexec = { + wallet: { + getAddress: jest + .fn<() => Promise>() + .mockResolvedValue(getRandomAddress()), + }, + voucher: { + showUserVoucher: jest + .fn<() => Promise>() + .mockRejectedValue( + new Error('No Voucher found for address test-address') + ), + }, + orderbook: { + fetchAppOrderbook: resolveWithOneAppOrder(), + fetchWorkerpoolOrderbook: resolveWithOneWorkerpoolOrder(), + }, + order: { + prepareDatasetBulk: jest + .fn<() => Promise<{ cid: string; volume: number }>>() + .mockResolvedValue({ cid: 'test-cid', volume: 1 }), + createRequestorder: jest + .fn<() => Promise>() + .mockResolvedValue({}), + signRequestorder: jest.fn<() => Promise>().mockResolvedValue({}), + }, + }; + + await expect( + // --- WHEN + processBulkRequest({ + // @ts-expect-error Minimal iexec implementation with only what's necessary for this test + iexec, + bulkRequest: mockBulkRequest, + defaultWorkerpool: getRandomAddress(), + useVoucher: true, + }) + // --- THEN + ).rejects.toThrow( + new WorkflowError({ + message: processProtectedDataErrorMessage, + errorCause: Error( + 'Oops, it seems your wallet is not associated with any voucher. Check on https://builder.iex.ec/' + ), + }) + ); + }); + }); +}); diff --git a/packages/sdk/tests/unit/dataProtectorCore/processProtectedData/processProtectedData.test.ts b/packages/sdk/tests/unit/dataProtectorCore/processProtectedData/processProtectedData.test.ts index 2d419b9c4..55ec4de36 100644 --- a/packages/sdk/tests/unit/dataProtectorCore/processProtectedData/processProtectedData.test.ts +++ b/packages/sdk/tests/unit/dataProtectorCore/processProtectedData/processProtectedData.test.ts @@ -71,6 +71,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: missingProtectedDataAddress, app: '', }) @@ -91,6 +92,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: invalidProtectedDataAddress, app: getRandomAddress(), }) @@ -113,6 +115,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: missingAppAddress, }) @@ -133,6 +136,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: invalidAppAddress, }) @@ -155,6 +159,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), userWhitelist: invalidUserWhitelist, @@ -176,6 +181,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), // @ts-expect-error Type 'number' is not assignable to type 'string' @@ -196,6 +202,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), dataMaxPrice: invalidDataMaxPrice, @@ -217,6 +224,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), workerpoolMaxPrice: invalidWorkerpoolMaxPrice, @@ -240,6 +248,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), appMaxPrice: invalidAppMaxPrice, @@ -261,6 +270,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), // @ts-expect-error This is intended to actually test yup runtime validation @@ -281,6 +291,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), inputFiles: invalidInputFiles, @@ -300,6 +311,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), // @ts-expect-error This is intended to actually test yup runtime validation @@ -324,6 +336,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error No need for iexec here iexec: {}, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), workerpool: invalidWorkerpool, @@ -361,6 +374,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error Minimal iexec implementation with only what's necessary for this test iexec, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), userWhitelist: invalidUserWhitelist, @@ -404,6 +418,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error Minimal iexec implementation with only what's necessary for this test iexec, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), }) @@ -442,6 +457,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error Minimal iexec implementation with only what's necessary for this test iexec, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), }) @@ -467,6 +483,7 @@ describe('processProtectedData', () => { await processProtectedData({ // @ts-expect-error Minimal iexec implementation with only what's necessary for this test iexec, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), workerpool: ZeroAddress, // <-- ZeroAddress explicitly set here @@ -480,7 +497,6 @@ describe('processProtectedData', () => { requester: expect.any(String), category: 0, minTag: SCONE_TAG, - maxTag: SCONE_TAG, isRequesterStrict: expect.any(Boolean), }) ); @@ -513,6 +529,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error Minimal iexec implementation with only what's necessary for this test iexec, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), userWhitelist: validWhitelistAddress, @@ -554,6 +571,7 @@ describe('processProtectedData', () => { await processProtectedData({ // @ts-expect-error Minimal iexec implementation with only what's necessary for this test iexec, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), userWhitelist: validWhitelistAddress, @@ -604,6 +622,7 @@ describe('processProtectedData', () => { processProtectedData({ // @ts-expect-error Minimal iexec implementation with only what's necessary for this test iexec, + defaultWorkerpool: getRandomAddress(), protectedData: getRandomAddress(), app: getRandomAddress(), })