From 92d442d051ea143b5f2bd52c8bf60f31d575ba8a Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:50:52 +0200 Subject: [PATCH 1/2] fix!: remove bellecour chain BREAKING CHANGE: `bellecour` chain is removed; users using `"defaultChain": "bellecour"` must edit their `iapp.config.json` to set `defaultChain` to a supported chain. --- cli/README.md | 38 +++++++++++++++++++++---- cli/src/cli-helpers/warnBeforeTxFees.ts | 14 ++++----- cli/src/cmd/deploy.ts | 2 +- cli/src/cmd/run.ts | 2 +- cli/src/config/config.ts | 8 +----- cli/test/iapp.test.ts | 18 +----------- 6 files changed, 42 insertions(+), 40 deletions(-) diff --git a/cli/README.md b/cli/README.md index 02e21a51..0bc9ecda 100644 --- a/cli/README.md +++ b/cli/README.md @@ -110,8 +110,9 @@ Description: Deploy your iApp on the iExec protocol in debug mode. Options: - use `--chain` Specify the blockchain on which the iApp will be deployed - (overrides defaultChain configuration which is `bellecour`). Possible values - are `bellecour|arbitrum-sepolia-testnet|arbitrum-mainnet` + (overrides `defaultChain` configuration which is declared in your + `iapp.config.json`). Possible values are + `arbitrum-sepolia-testnet|arbitrum-mainnet` --- @@ -142,8 +143,9 @@ Options: include the `--protectedData` option followed by the address of the protected data. - use `--chain` Specify the blockchain on which the iApp will be deployed - (overrides defaultChain configuration which is `bellecour`). Possible values - are `bellecour|arbitrum-sepolia-testnet|arbitrum-mainnet` + (overrides `defaultChain` configuration which is declared in your + `iapp.config.json`). Possible values are + `arbitrum-sepolia-testnet|arbitrum-mainnet` --- @@ -185,4 +187,30 @@ Description: Manage wallet. Options for `` : - `import` import a new wallet by providing a private key. -- `select` select a wallet from your personnal keystore. +- `select` select a wallet from your personal keystore. + +--- + +### `chain select` + +Command: + +```sh +iapp chain select +``` + +Description: set the default chain to use for your iApp. This will update the +`defaultChain` field in your `iapp.config.json` file. + +Options for `` : + +- `arbitrum-sepolia-testnet` +- `arbitrum-mainnet` + +## Migrate from v1 + +### `bellecour` chain removed + +The `bellecour` chain is no longer supported. If you were using `bellecour` as +the default chain, you will need to update your `iapp.config.json` file to set +`defaultChain` to a supported chain (e.g. `arbitrum-sepolia-testnet`). diff --git a/cli/src/cli-helpers/warnBeforeTxFees.ts b/cli/src/cli-helpers/warnBeforeTxFees.ts index 0e1e5c8e..72d37639 100644 --- a/cli/src/cli-helpers/warnBeforeTxFees.ts +++ b/cli/src/cli-helpers/warnBeforeTxFees.ts @@ -3,16 +3,12 @@ import { askForAcknowledgment } from './askForAcknowledgment.js'; export async function warnBeforeTxFees({ spinner, - chain, }: { spinner: Spinner; - chain: string; }): Promise { - if (chain !== 'bellecour') { - await askForAcknowledgment({ - spinner, - message: - 'This method requires sending blockchain transactions, transaction fees will be applied. Would you like to continue?', - }); - } + await askForAcknowledgment({ + spinner, + message: + 'This method requires sending blockchain transactions, transaction fees will be applied. Would you like to continue?', + }); } diff --git a/cli/src/cmd/deploy.ts b/cli/src/cmd/deploy.ts index c299e9bd..4b3c3a48 100644 --- a/cli/src/cmd/deploy.ts +++ b/cli/src/cmd/deploy.ts @@ -69,7 +69,7 @@ run ${color.command('EXPERIMENTAL_TDX_APP=1 iapp deploy')} to deploy your app wi } } - await warnBeforeTxFees({ spinner, chain: chainConfig.name }); + await warnBeforeTxFees({ spinner }); const signer = await askForWallet({ spinner }); const userAddress = await signer.getAddress(); diff --git a/cli/src/cmd/run.ts b/cli/src/cmd/run.ts index 4c0965e6..38ea9286 100644 --- a/cli/src/cmd/run.ts +++ b/cli/src/cmd/run.ts @@ -110,7 +110,7 @@ run ${color.command('EXPERIMENTAL_TDX_APP=1 iapp deploy')} to redeploy your app ); } - await warnBeforeTxFees({ spinner, chain: chainConfig.name }); + await warnBeforeTxFees({ spinner }); // Get wallet from privateKey const signer = await askForWallet({ spinner }); diff --git a/cli/src/config/config.ts b/cli/src/config/config.ts index ea7e8093..225eb2e1 100644 --- a/cli/src/config/config.ts +++ b/cli/src/config/config.ts @@ -76,15 +76,9 @@ type ChainConfig = { export const DEFAULT_CHAIN = 'arbitrum-sepolia-testnet'; -export const DEPRECATED_CHAINS = ['bellecour']; +export const DEPRECATED_CHAINS: string[] = []; export const CHAINS_CONFIGURATIONS: Record = { - bellecour: { - rpcHostUrl: 'https://bellecour.iex.ec', - ipfsGatewayUrl: 'https://ipfs-gateway.v8-bellecour.iex.ec', - iexecExplorerUrl: 'https://explorer.iex.ec/bellecour', - sconeWorkerpool: 'prod-v8-learn.main.pools.iexec.eth', - }, 'arbitrum-mainnet': { rpcHostUrl: 'https://arb1.arbitrum.io/rpc', ipfsGatewayUrl: 'https://ipfs-gateway.arbitrum-mainnet.iex.ec', diff --git a/cli/test/iapp.test.ts b/cli/test/iapp.test.ts index 90782091..67d1aabc 100644 --- a/cli/test/iapp.test.ts +++ b/cli/test/iapp.test.ts @@ -78,7 +78,7 @@ test('iapp init command works', async () => { clear(); const config = await readIAppConfig(join(testDir, 'hello-world')); - // default chain is bellecour + // default chain is arbitrum-sepolia-testnet assert.strictEqual( config.defaultChain, 'arbitrum-sepolia-testnet', @@ -113,22 +113,6 @@ describe('iapp chain select', () => { }); }); - test('select bellecour works', async () => { - await render(IAPP_COMMAND, ['chain select bellecour'], { - cwd: join(testDir, projectName), - }); - await retry( - async () => { - const config = await readIAppConfig(join(testDir, projectName)); - assert.strictEqual(config.defaultChain, 'bellecour'); - }, - { - retries: 10, - delay: 100, - } - ); - }); - test('select arbitrum-sepolia-testnet works', async () => { await render(IAPP_COMMAND, ['chain select arbitrum-sepolia-testnet'], { cwd: join(testDir, projectName), From 2c65c11d40132357b08c5593257e7e5d2a97a4d0 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:27:16 +0200 Subject: [PATCH 2/2] fix!: remove SCONE TEE framework BREAKING CHANGE: iApps deployed with SCONE will no longer be executable and must be redeployed with TDX by running `iapp deploy` --- cli/README.md | 16 ++- cli/npm-shrinkwrap.json | 157 -------------------------- cli/package.json | 2 - cli/src/cmd/deploy.ts | 151 ++++++------------------- cli/src/cmd/run.ts | 31 +---- cli/src/cmd/test.ts | 6 +- cli/src/config/config.ts | 17 +-- cli/src/utils/featureFlags.ts | 1 - cli/src/utils/sconify.ts | 188 ------------------------------- cli/src/utils/websocket.ts | 205 ---------------------------------- 10 files changed, 53 insertions(+), 721 deletions(-) delete mode 100644 cli/src/utils/sconify.ts delete mode 100644 cli/src/utils/websocket.ts diff --git a/cli/README.md b/cli/README.md index 0bc9ecda..f4247347 100644 --- a/cli/README.md +++ b/cli/README.md @@ -211,6 +211,16 @@ Options for `` : ### `bellecour` chain removed -The `bellecour` chain is no longer supported. If you were using `bellecour` as -the default chain, you will need to update your `iapp.config.json` file to set -`defaultChain` to a supported chain (e.g. `arbitrum-sepolia-testnet`). +The `bellecour` chain is no longer supported. + +If you were using `bellecour` as the default chain, you will need to update your +`iapp.config.json` file to set `defaultChain` to a supported chain (e.g. +`arbitrum-sepolia-testnet`). + +### SCONE TEE framework removed + +The SCONE TEE framework has been removed in favor of TDX. iApps deployed with +SCONE will no longer be executable and must be redeployed with TDX. + +If your iApp was deployed using SCONE, redeploy it with TDX by running +`iapp deploy` with the latest version of the `iapp` CLI. diff --git a/cli/npm-shrinkwrap.json b/cli/npm-shrinkwrap.json index 5300af05..b2404abf 100644 --- a/cli/npm-shrinkwrap.json +++ b/cli/npm-shrinkwrap.json @@ -19,12 +19,10 @@ "iexec": "^8.24.0", "jszip": "^3.10.1", "magic-bytes.js": "^1.10.0", - "msgpackr": "^1.11.2", "ora": "^8.2.0", "prompts": "^2.4.2", "update-check": "^1.5.4", "uuid": "^11.1.0", - "ws": "^8.18.1", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "zod": "^3.24.2", @@ -1033,84 +1031,6 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@multiformats/dns": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.9.tgz", @@ -2426,16 +2346,6 @@ "dev": true, "license": "MIT" }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -4230,37 +4140,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/msgpackr": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", - "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", - "license": "MIT", - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } - }, - "node_modules/msgpackr-extract": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", - "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" - } - }, "node_modules/multiformats": { "version": "13.4.2", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", @@ -4326,21 +4205,6 @@ "node": ">= 6.13.0" } }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -5923,27 +5787,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/cli/package.json b/cli/package.json index c6545d1a..dd3bbf25 100644 --- a/cli/package.json +++ b/cli/package.json @@ -46,12 +46,10 @@ "iexec": "^8.24.0", "jszip": "^3.10.1", "magic-bytes.js": "^1.10.0", - "msgpackr": "^1.11.2", "ora": "^8.2.0", "prompts": "^2.4.2", "update-check": "^1.5.4", "uuid": "^11.1.0", - "ws": "^8.18.1", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "zod": "^3.24.2", diff --git a/cli/src/cmd/deploy.ts b/cli/src/cmd/deploy.ts index 4b3c3a48..d397397c 100644 --- a/cli/src/cmd/deploy.ts +++ b/cli/src/cmd/deploy.ts @@ -6,7 +6,6 @@ import { inspectImage, tagDockerImage, } from '../execDocker/docker.js'; -import { sconify } from '../utils/sconify.js'; import { askForDockerhubUsername } from '../cli-helpers/askForDockerhubUsername.js'; import { projectNameToImageName, @@ -20,9 +19,8 @@ import { askForWallet } from '../cli-helpers/askForWallet.js'; import { getIExec } from '../utils/iexec.js'; import { goToProjectRoot } from '../cli-helpers/goToProjectRoot.js'; import * as color from '../cli-helpers/color.js'; -import { hintBox, warnBox } from '../cli-helpers/box.js'; +import { hintBox } from '../cli-helpers/box.js'; import { addDeploymentData } from '../utils/cacheExecutions.js'; -import { useTdx } from '../utils/featureFlags.js'; import { ensureBalances } from '../cli-helpers/ensureBalances.js'; import { warnBeforeTxFees } from '../cli-helpers/warnBeforeTxFees.js'; import { resolveChainConfig } from '../cli-helpers/resolveChainConfig.js'; @@ -31,44 +29,13 @@ export async function deploy({ chain }: { chain?: string }) { const spinner = getSpinner(); try { await goToProjectRoot({ spinner }); - const { projectName, template, defaultChain } = await readIAppConfig(); + const { projectName, defaultChain } = await readIAppConfig(); const chainConfig = resolveChainConfig({ chain, defaultChain, spinner, }); - // initialize iExec - const iexecReadonly = getIExec({ ...chainConfig }); - // determine TEE framework based on feature flag - const teeFramework = useTdx ? 'tdx' : 'scone'; - // check TEE framework compatibility with selected chain - try { - await iexecReadonly.config.resolveSmsURL({ teeFramework }); - } catch { - throw new Error( - `TEE framework ${teeFramework.toUpperCase()} is not supported on the selected chain` - ); - } - - if (teeFramework === 'scone') { - try { - await iexecReadonly.config.resolveSmsURL({ teeFramework: 'tdx' }); - spinner.log( - warnBox( - `SGX SCONE TEE framework is deprecated in favor of TDX and will be removed in future versions. -Please consider redeploying your app with TDX instead. - -run ${color.command('EXPERIMENTAL_TDX_APP=1 iapp deploy')} to deploy your app with TDX now.` - ) - ); - } catch { - spinner.warn( - 'SGX SCONE TEE framework is deprecated and will be removed in a future version, please contact iExec support to know more about TDX and how to migrate your app.' - ); - } - } - await warnBeforeTxFees({ spinner }); const signer = await askForWallet({ spinner }); @@ -115,89 +82,39 @@ run ${color.command('EXPERIMENTAL_TDX_APP=1 iapp deploy')} to deploy your app wi }); spinner.succeed(`Docker image built (${imageId})`); - let appDockerImage: string; - let appContractAddress: string; + spinner.start('Pushing docker image...\n'); + const { + dockerUserName, + imageName, + imageTag: originalImageTag, + } = parseImagePath(nonTeeImage); + const repo = `${dockerUserName}/${imageName}`; + const { Id } = await inspectImage(nonTeeImage); + const tdxImageShortId = Id.split('sha256:')[1].substring(0, 12); // extract 12 first chars after the leading "sha256:" + const tdxImageTag = `${originalImageTag}-tdx-${tdxImageShortId}`; // add short ID in tag to avoid replacing previous build + const tdxImage = await tagDockerImage({ + image: nonTeeImage, + repo, + tag: tdxImageTag, + }); + await pushDockerImage({ + tag: tdxImage, + dockerhubUsername, + dockerhubAccessToken, + }); + const appDockerImage = tdxImage; + spinner.succeed(`Pushed image ${tdxImage} on dockerhub`); + spinner.start('Deploying your TDX TEE app on iExec...'); + const { RepoDigests } = await inspectImage(tdxImage); + const { address } = await iexec.app.deployApp({ + owner: await iexec.wallet.getAddress(), + name: `${imageName}-${originalImageTag}`, + type: 'DOCKER', + multiaddr: tdxImage, + checksum: `0x${RepoDigests[0].split('@sha256:')[1]}`, + }); + const appContractAddress = address; - if (useTdx) { - spinner.start('Pushing docker image...\n'); - const { - dockerUserName, - imageName, - imageTag: originalImageTag, - } = parseImagePath(nonTeeImage); - const repo = `${dockerUserName}/${imageName}`; - const { Id } = await inspectImage(nonTeeImage); - const tdxImageShortId = Id.split('sha256:')[1].substring(0, 12); // extract 12 first chars after the leading "sha256:" - const tdxImageTag = `${originalImageTag}-tdx-${tdxImageShortId}`; // add short ID in tag to avoid replacing previous build - const tdxImage = await tagDockerImage({ - image: nonTeeImage, - repo, - tag: tdxImageTag, - }); - await pushDockerImage({ - tag: tdxImage, - dockerhubUsername, - dockerhubAccessToken, - }); - appDockerImage = tdxImage; - spinner.succeed(`Pushed image ${tdxImage} on dockerhub`); - spinner.start('Deploying your TDX TEE app on iExec...'); - const { RepoDigests } = await inspectImage(tdxImage); - const { address } = await iexec.app.deployApp({ - owner: await iexec.wallet.getAddress(), - name: `${imageName}-${originalImageTag}`, - type: 'DOCKER', - multiaddr: tdxImage, - checksum: `0x${RepoDigests[0].split('@sha256:')[1]}`, - }); - appContractAddress = address; - } else { - spinner.start('Pushing docker image...\n'); - await pushDockerImage({ - tag: nonTeeImage, - dockerhubAccessToken, - dockerhubUsername, - progressCallback: (msg) => { - spinner.text = spinner.text + color.comment(msg); - }, - }); - spinner.succeed(`Pushed image ${nonTeeImage} on dockerhub`); - spinner.start( - 'Transforming your image into a TEE image, this may take a few minutes...' - ); - const { - dockerImage, - dockerImageDigest, - sconeVersion, - fingerprint, - entrypoint, - } = await sconify({ - iAppNameToSconify: nonTeeImage, - template, - walletAddress: userAddress, - dockerhubAccessToken, - dockerhubUsername, - }); - appDockerImage = dockerImage; - spinner.succeed(`Pushed TEE image ${appDockerImage} on dockerhub`); - spinner.start('Deploying your TEE app on iExec...'); - const { address } = await iexec.app.deployApp({ - owner: userAddress, - name: `${projectNameToImageName(projectName)}-${iAppVersion}`, - type: 'DOCKER', - multiaddr: dockerImage, - checksum: `0x${dockerImageDigest}`, - // Some code sample here: https://github.com/iExecBlockchainComputing/dataprotector-sdk/blob/v2/packages/protected-data-delivery-dapp/deployment/src/singleFunction/deployApp.ts - mrenclave: { - framework: 'SCONE', - version: sconeVersion, - entrypoint: entrypoint, - heapSize: 1073741824, - fingerprint, - }, - }); - appContractAddress = address; - } // Add deployment data to deployments.json await addDeploymentData({ image: appDockerImage, diff --git a/cli/src/cmd/run.ts b/cli/src/cmd/run.ts index 38ea9286..a1cff54d 100644 --- a/cli/src/cmd/run.ts +++ b/cli/src/cmd/run.ts @@ -19,7 +19,6 @@ import { ensureBalances } from '../cli-helpers/ensureBalances.js'; import { askForAcknowledgment } from '../cli-helpers/askForAcknowledgment.js'; import { warnBeforeTxFees } from '../cli-helpers/warnBeforeTxFees.js'; import { resolveChainConfig } from '../cli-helpers/resolveChainConfig.js'; -import { warnBox } from '../cli-helpers/box.js'; export async function run({ iAppAddress, @@ -61,31 +60,11 @@ export async function run({ // determine TEE framework based on app properties (SCONE apps define `appMREnclave`, TDX apps do NOT define `appMREnclave`) const isTdxApp = !app.appMREnclave; const teeFramework = isTdxApp ? 'tdx' : 'scone'; - // check TEE framework compatibility with selected chain - try { - await readOnlyIexec.config.resolveSmsURL({ teeFramework }); - } catch { - throw new Error( - `TEE framework ${teeFramework.toUpperCase()} is not supported on the selected chain` - ); - } if (teeFramework === 'scone') { - try { - await readOnlyIexec.config.resolveSmsURL({ teeFramework: 'tdx' }); - spinner.log( - warnBox( - `SGX SCONE TEE framework is deprecated in favor of TDX and will be removed in future versions. -Please consider redeploying your app with TDX instead. - -run ${color.command('EXPERIMENTAL_TDX_APP=1 iapp deploy')} to redeploy your app with TDX.` - ) - ); - } catch { - spinner.warn( - 'SGX SCONE TEE framework is deprecated and will be removed in a future version, please contact iExec support to know more about TDX and how to migrate your app.' - ); - } + throw new Error( + 'The selected iApp is using the SGX SCONE TEE framework which is no longer supported. The iApp must be redeployed with TDX.' + ); } if (protectedData.length > 0) { @@ -186,9 +165,7 @@ run ${color.command('EXPERIMENTAL_TDX_APP=1 iapp deploy')} to redeploy your app // Workerpool Order spinner.start('Fetching workerpool order...'); const workerpoolOrderbook = await iexec.orderbook.fetchWorkerpoolOrderbook({ - workerpool: isTdxApp - ? chainConfig.tdxWorkerpool - : chainConfig.sconeWorkerpool, + workerpool: chainConfig.tdxWorkerpool, app: iAppAddress, minTag: apporder.tag, minVolume: volume, // TODO handle multiple matches if not enough volume diff --git a/cli/src/cmd/test.ts b/cli/src/cmd/test.ts index 78cdaa8b..b5f7b017 100644 --- a/cli/src/cmd/test.ts +++ b/cli/src/cmd/test.ts @@ -9,7 +9,6 @@ import { } from '../execDocker/docker.js'; import { checkDeterministicOutputExists } from '../utils/deterministicOutput.js'; import { - IEXEC_SCONE_WORKER_HEAP_SIZE, IEXEC_RESULT_UPLOAD_MAX_SIZE, PROTECTED_DATA_MOCK_DIR, TASK_OBSERVATION_TIMEOUT, @@ -26,7 +25,6 @@ import { copy, fileExists } from '../utils/fs.utils.js'; import { goToProjectRoot } from '../cli-helpers/goToProjectRoot.js'; import * as color from '../cli-helpers/color.js'; import { hintBox } from '../cli-helpers/box.js'; -import { useTdx } from '../utils/featureFlags.js'; export async function test({ args, @@ -180,9 +178,7 @@ export async function testApp({ spinner.warn('Task is taking longer than expected...'); spinner.start(spinnerText); // restart spinning }, TASK_OBSERVATION_TIMEOUT); - const memoryLimit = useTdx - ? IEXEC_TDX_WORKER_HEAP_SIZE - : IEXEC_SCONE_WORKER_HEAP_SIZE; + const memoryLimit = IEXEC_TDX_WORKER_HEAP_SIZE; const appLogs: string[] = []; const { exitCode, outOfMemory } = await runDockerContainer({ image: imageId, diff --git a/cli/src/config/config.ts b/cli/src/config/config.ts index 225eb2e1..88ebcd50 100644 --- a/cli/src/config/config.ts +++ b/cli/src/config/config.ts @@ -1,7 +1,3 @@ -export const DEFAULT_SCONE_VERSION = 'v5.9'; - -export const SCONIFY_API_WS_URL = 'wss://iapp-api.iex.ec'; - export const CONFIG_FILE = 'iapp.config.json'; export const TEST_INPUT_DIR = 'input'; export const TEST_OUTPUT_DIR = 'output'; @@ -12,7 +8,6 @@ export const PROTECTED_DATA_MOCK_DIR = 'mock/protectedData'; export const IEXEC_OUT = '/iexec_out'; export const IEXEC_COMPUTED_JSON = 'computed.json'; export const IEXEC_DETERMINISTIC_OUTPUT_PATH_KEY = 'deterministic-output-path'; -export const IEXEC_SCONE_WORKER_HEAP_SIZE = 1024 * 1024 * 1024; // iExec SCONE worker memory limit (1 GiB) export const IEXEC_TDX_WORKER_HEAP_SIZE = 6 * 1024 * 1024 * 1024; // iExec TDX worker memory limit (6 GiB) export const IEXEC_RESULT_UPLOAD_MAX_SIZE = 50 * 1024 * 1024; // Maximum allowed size for the result output (50 MiB) @@ -59,19 +54,11 @@ export const TEMPLATES: Record< }, }; -const WS_SERVER_SESSION_TIMEOUT = 60_000; // session retention after websocket close proposed by the API -export const WS_SERVER_HEARTBEAT_INTERVAL = 15_000; // heartbeat proposed by the API -export const WS_RECONNECTION_DELAY = 6_000; -export const WS_RECONNECTION_MAX_ATTEMPTS = Math.floor( - WS_SERVER_SESSION_TIMEOUT / WS_RECONNECTION_DELAY -); - type ChainConfig = { rpcHostUrl: string; ipfsGatewayUrl: string; iexecExplorerUrl: string; - sconeWorkerpool?: string; - tdxWorkerpool?: string; + tdxWorkerpool: string; }; export const DEFAULT_CHAIN = 'arbitrum-sepolia-testnet'; @@ -83,14 +70,12 @@ export const CHAINS_CONFIGURATIONS: Record = { rpcHostUrl: 'https://arb1.arbitrum.io/rpc', ipfsGatewayUrl: 'https://ipfs-gateway.arbitrum-mainnet.iex.ec', iexecExplorerUrl: 'https://explorer.iex.ec/arbitrum-mainnet', - sconeWorkerpool: '0x2c06263943180cc024daffeee15612db6e5fd248', tdxWorkerpool: '0x8ef2ec3ef9535d4b4349bfec7d8b31a580e60244', }, 'arbitrum-sepolia-testnet': { rpcHostUrl: 'https://sepolia-rollup.arbitrum.io/rpc', ipfsGatewayUrl: 'https://ipfs-gateway.arbitrum-sepolia-testnet.iex.ec', iexecExplorerUrl: 'https://explorer.iex.ec/arbitrum-sepolia-testnet', - sconeWorkerpool: '0xB967057a21dc6A66A29721d96b8Aa7454B7c383F', tdxWorkerpool: '0x2956f0cb779904795a5f30d3b3ea88b714c3123f', }, }; diff --git a/cli/src/utils/featureFlags.ts b/cli/src/utils/featureFlags.ts index 8d4da2e7..5ceed0bc 100644 --- a/cli/src/utils/featureFlags.ts +++ b/cli/src/utils/featureFlags.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -export const useTdx = checkFlag('EXPERIMENTAL_TDX_APP'); export const useExperimentalNetworks = checkFlag('EXPERIMENTAL_NETWORKS'); function checkFlag(envName: string) { diff --git a/cli/src/utils/sconify.ts b/cli/src/utils/sconify.ts deleted file mode 100644 index 83b663b6..00000000 --- a/cli/src/utils/sconify.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { DEFAULT_SCONE_VERSION, SCONIFY_API_WS_URL } from '../config/config.js'; -import { getAuthToken } from './dockerhub.js'; -import { sleep } from './sleep.js'; -import { - createReconnectingWs, - deserializeData, - serializeData, -} from './websocket.js'; -import { debug } from './debug.js'; - -const INITIAL_RETRY_PERIOD = 20 * 1000; // 20s - -class TooManyRequestsError extends Error {} - -export async function sconify({ - iAppNameToSconify, - template, - walletAddress, - dockerhubAccessToken, - dockerhubUsername, - tryCount = 0, -}: { - iAppNameToSconify: string; - template?: string; - walletAddress: string; - dockerhubAccessToken: string; - dockerhubUsername: string; - tryCount?: number; -}): Promise<{ - dockerImage: string; - dockerImageDigest: string; - sconeVersion: string; - fingerprint: string; - entrypoint: string; -}> { - let dockerImage; - let dockerImageDigest; - let sconeVersion; - let entrypoint; - let fingerprint; - try { - const [dockerRepository] = iAppNameToSconify.split(':'); - - const getPushToken = async () => - getAuthToken({ - repository: dockerRepository, - action: 'pull,push', - dockerhubAccessToken, - dockerhubUsername, - }); - - const pushToken = await getPushToken(); - - const sconifyResult: { - dockerImage?: string; - dockerImageDigest?: string; - entrypoint?: string; - fingerprint?: string; - sconeVersion?: string; - } = await new Promise((resolve, reject) => { - createReconnectingWs(SCONIFY_API_WS_URL, { - headers: { - 'x-wallet': walletAddress, - }, - connectCallback: (ws) => { - const handleError = (e: unknown) => { - ws.close(1000); // normal ws close - reject(e); - }; - - ws.on('message', (data) => { - let message; - // handle communication errors - try { - message = deserializeData(data); - debug(`ws message: ${JSON.stringify(message, undefined, 2)}`); - } catch (e) { - handleError(e); - } - - // handle server responses - if (message?.type === 'RESPONSE') { - if (message?.target === 'SCONIFY_BUILD') { - ws.close(1000); // normal ws close - if (message?.success === true && message.result) { - resolve(message.result); - } else { - reject(Error(message.error || 'Server unknown error')); - } - } - } - - // handle server requests - if (message?.type === 'REQUEST') { - if (message?.target === 'RENEW_PUSH_TOKEN') { - getPushToken() - .then((renewedPushToken) => { - ws.send( - serializeData({ - type: 'RESPONSE', - target: 'RENEW_PUSH_TOKEN', - result: { - dockerhubPushToken: renewedPushToken, - }, - }) - ); - }) - .catch(handleError); - } - } - - // handle server info - if (message?.type === 'INFO') { - // TODO server feedback - } - }); - }, - initCallback: (ws) => { - ws.send( - serializeData({ - type: 'REQUEST', - target: 'SCONIFY_BUILD', // call sconify handler - template, - dockerhubImageToSconify: iAppNameToSconify, - dockerhubPushToken: pushToken, - yourWalletPublicAddress: walletAddress, - sconeVersion: DEFAULT_SCONE_VERSION, - sconeProd: true, - }) - ); - }, - errorCallback: reject, - }); - }); - - // Extract necessary information - if (!sconifyResult.dockerImage) { - throw Error('Unexpected server response: missing dockerImage'); - } - if (!sconifyResult.dockerImageDigest) { - throw Error('Unexpected server response: missing dockerImageDigest'); - } - if (!sconifyResult.sconeVersion) { - throw Error('Unexpected server response: missing sconeVersion'); - } - if (!sconifyResult.entrypoint) { - throw Error('Unexpected server response: missing entrypoint'); - } - if (!sconifyResult.fingerprint) { - throw Error('Unexpected server response: missing fingerprint'); - } - - dockerImage = sconifyResult.dockerImage; - dockerImageDigest = sconifyResult.dockerImageDigest; - sconeVersion = sconifyResult.sconeVersion; - entrypoint = sconifyResult.entrypoint; - fingerprint = sconifyResult.fingerprint; - } catch (err) { - debug(`sconify error: ${err}`); - // retry with exponential backoff - if (err instanceof TooManyRequestsError && tryCount < 3) { - const retryAfter = INITIAL_RETRY_PERIOD * Math.pow(2, tryCount); - debug( - `server is busy retrying in after ${retryAfter} ms (count: ${tryCount})` - ); - await sleep(retryAfter); - return sconify({ - iAppNameToSconify, - template, - walletAddress, - dockerhubAccessToken, - dockerhubUsername, - tryCount: tryCount + 1, - }); - } - throw Error( - `Failed to transform your app into a TEE app: ${(err as Error)?.message}` - ); - } - - return { - dockerImage, - dockerImageDigest, - sconeVersion, - entrypoint, - fingerprint, - }; -} diff --git a/cli/src/utils/websocket.ts b/cli/src/utils/websocket.ts deleted file mode 100644 index d530038e..00000000 --- a/cli/src/utils/websocket.ts +++ /dev/null @@ -1,205 +0,0 @@ -import WebSocket, { RawData } from 'ws'; -import { pack, unpack } from 'msgpackr'; -import { debug } from './debug.js'; -import { sleep } from './sleep.js'; -import { - WS_RECONNECTION_DELAY, - WS_RECONNECTION_MAX_ATTEMPTS, - WS_SERVER_HEARTBEAT_INTERVAL, -} from '../config/config.js'; - -export type WsMessage = { - type: - | 'NEW_SESSION' - | 'RECOVERED_SESSION' - | 'REQUEST' - | 'RESPONSE' - | 'INFO' - | 'ACK'; - /** - * message reception acknowledge code - */ - ack?: number; - /** - * sent with type REQUEST and RESPONSE - */ - target?: string; - /** - * sent with type RESPONSE - */ - success?: boolean; - /** - * sent with type RESPONSE when success: false - */ - error?: string; - /** - * sent with type RESPONSE when success: false - */ - code?: number; - /** - * sent with type RESPONSE when success: true - */ - result?: object; -}; - -/** - * serialize data to send through a websocket - */ -export function serializeData(data: T) { - try { - return pack(data); - } catch { - throw Error('Failed to serialize WS data'); - } -} - -/** - * deserialize data received through a websocket - */ -export function deserializeData(data: RawData): T { - try { - return unpack(data as Buffer); - } catch { - throw Error('Failed to deserialize WS data'); - } -} - -export function createReconnectingWs( - host: string, - options: { - /** - * use to register event listeners - */ - connectCallback?: (ws: WebSocket) => void; - /** - * use to perform operation that must be done only once when the session is established - */ - initCallback?: (ws: WebSocket) => void; - /** - * called when session with server is definitely broken - */ - errorCallback?: (err: Error) => void; - /** - * connection headers - */ - headers?: Record; - } = {} -) { - const createWs = (sid?: string, failedReconnectCount: number = 0) => { - const { - connectCallback = () => {}, - initCallback = () => {}, - errorCallback = () => {}, - } = options; - - const ws: WebSocket & { pingTimeout?: NodeJS.Timeout } = new WebSocket( - host, - sid, - { handshakeTimeout: 10_000, headers: options.headers } - ); - - /** - * clean close ws procedure - */ - const teardown = () => { - debug('ws teardown'); - ws.removeAllListeners(); - ws.terminate(); - if (ws.pingTimeout) { - clearTimeout(ws.pingTimeout); - } - }; - - // setup acknowledge messages mechanism - ws.on('message', (data) => { - try { - const { ack, type } = deserializeData( - data - ); - if (ack !== undefined && type !== 'ACK') { - ws.send(serializeData({ type: 'ACK', ack })); - debug(`acknowledge ws message ${ack}`); - } - } catch { - // noop - } - }); - - // create or recover WS session - ws.once('message', (data) => { - const message = deserializeData(data); - debug(`ws message once: ${JSON.stringify(message, undefined, 2)}`); - - if (message.type === 'RECOVERED_SESSION') { - connectCallback(ws); - } else if (message.type === 'NEW_SESSION' && message.sid) { - if (sid) { - teardown(); - return errorCallback(Error('Failed to recover session with server')); - } - sid = message.sid; - connectCallback(ws); - initCallback(ws); - } else { - teardown(); - return errorCallback(Error('Failed to establish session with server')); - } - }); - - // ensure ws liveness (reset heartbeat on ping) - const heartbeat = () => { - clearTimeout(ws.pingTimeout); - ws.pingTimeout = setTimeout(() => { - debug('ws heartbeat fail'); - teardown(); - reconnect(); - }, 1.5 * WS_SERVER_HEARTBEAT_INTERVAL); - }; - const ping = () => { - debug('ws ping'); - heartbeat(); - }; - ws.on('ping', ping) - .on('open', ping) - .on('close', () => { - clearTimeout(ws.pingTimeout); - }); - - /** - * try reconnecting to ws session - */ - const reconnect = async () => { - debug(`ws try reconnect (count: ${failedReconnectCount})`); - if (failedReconnectCount >= WS_RECONNECTION_MAX_ATTEMPTS) { - return errorCallback(Error('Reconnection to server failed')); - } - // first reconnect attempt occurs immediately - if (failedReconnectCount > 0) { - await sleep(WS_RECONNECTION_DELAY); - } - createWs(sid, failedReconnectCount + 1); - }; - - // reset retry count when connection is established properly - ws.on('open', () => { - failedReconnectCount = 0; - }); - - // clean ws and recreate connection on error - ws.on('error', (err) => { - debug(`ws error: ${err}`); - teardown(); - reconnect(); - }); - - // clean ws and recreate connection on unexpected close - ws.on('close', (code) => { - debug(`ws close: ${code}`); - if (code !== 1000) { - reconnect(); - } - }); - }; - - createWs(); -}