@ethernity-cloud/sdk-js 1.7.0 → 1.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/nodenithy/run.js CHANGED
@@ -8,6 +8,7 @@ const https = require('https');
8
8
  const casConfig = require('../cas/config.js');
9
9
  const casResolver = require('../cas/resolver.js');
10
10
  const sessionRegistry = require('../cas/session_registry.js');
11
+ const { LocalKubo, ownEndpoint } = require('../localKubo.js');
11
12
 
12
13
  if (!fs.existsSync('.env')) {
13
14
  console.error("Error: .env file not found");
@@ -82,7 +83,7 @@ process.env.REGISTRY_PATH = `${currentDir}/registry`;
82
83
  const registryPath = process.env.REGISTRY_PATH;
83
84
 
84
85
  const runDockerCommand = (service) => {
85
- const command = `docker-compose run -e SCONE_LOG=INFO -e SCONE_HASH=1 ${service}`;
86
+ const command = `docker-compose run --rm -e SCONE_LOG=INFO -e SCONE_HASH=1 ${service}`;
86
87
  const output = execSync(command).toString().trim();
87
88
  console.log(`Output of ${command}: ${output}`);
88
89
  return output.split('\n').filter(line => !/Creating|Pulling|latest|Digest/.test(line)).join('');
@@ -103,7 +104,7 @@ const extractSconeHash = (service) => {
103
104
  // extract_signed_mrenclave. Returns '' if the file is absent (testnet builds).
104
105
  const extractSignedMrenclave = (service) => {
105
106
  try {
106
- const command = `docker-compose -f docker-compose.yml run --no-deps --entrypoint cat ${service} /signed_mrenclave.txt`;
107
+ const command = `docker-compose -f docker-compose.yml run --rm --no-deps --entrypoint cat ${service} /signed_mrenclave.txt`;
107
108
  const output = execSync(command, { stdio: ['pipe', 'pipe', 'pipe'] }).toString().trim();
108
109
  const m = output.match(/\b[a-fA-F0-9]{64}\b/);
109
110
  return m ? m[0] : '';
@@ -118,6 +119,79 @@ const main = async () => {
118
119
  // (etny-nodenithy v3/run/docker-compose.yml). The mainnet session named
119
120
  // below comes from the features/v3 branches, which ran it with 256M.
120
121
  const trustedZoneHeap = isMainnet ? '256M' : '1024M';
122
+
123
+ // Where the image goes. An IPFS API of the application's own
124
+ // (IPFS_ENDPOINT, when it is not the public one) takes precedence.
125
+ // Otherwise the publish runs its own Kubo: the public write API of
126
+ // ipfs.ethernity.cloud is closing, and the image is held by this Kubo,
127
+ // peered with the bootnode, until its certificate is on chain.
128
+ let localKubo = null;
129
+ let ipfsApi = process.env.IPFS_ENDPOINT || '';
130
+ if (!ownEndpoint(ipfsApi)) {
131
+ localKubo = new LocalKubo(process.env.PROJECT_NAME);
132
+ try {
133
+ await localKubo.start();
134
+ } catch (e) {
135
+ console.error(`Error: ${e.message}`);
136
+ process.exit(1);
137
+ }
138
+ ipfsApi = localKubo.apiUrl;
139
+ console.log(`\t✔ IPFS node for this publish: ${ipfsApi}`);
140
+ process.on('exit', () => localKubo.stop());
141
+ }
142
+
143
+ // The CIDs uploadToIpfs gives, from the same add calls with only-hash: the
144
+ // IPFS node stores and announces nothing. Set by hashForIpfs, after which
145
+ // uploadToIpfs refuses an upload that gives other CIDs.
146
+ let expectedCids = null;
147
+ const hashForIpfs = () => {
148
+ const hashOf = (target) => {
149
+ try {
150
+ const out = execSync(`node ../ipfs.mjs --host "${ipfsApi}" --action hash ${target}`, { stdio: ['ignore', 'pipe', 'inherit'] });
151
+ return out.toString().trim().split('\n').pop().trim();
152
+ } catch (e) {
153
+ console.error(`Error: could not hash ${target} for IPFS: ${e.message}`);
154
+ process.exit(1);
155
+ }
156
+ };
157
+ process.env.IPFS_DOCKER_COMPOSE_HASH = hashOf('--filePath docker-compose-final.yml');
158
+ process.env.IPFS_HASH = hashOf(`--folderPath ${registryPath}`);
159
+ console.log("IPFS_DOCKER_COMPOSE_HASH (before upload): ", process.env.IPFS_DOCKER_COMPOSE_HASH);
160
+ console.log("IPFS_HASH (before upload): ", process.env.IPFS_HASH);
161
+ expectedCids = { image: process.env.IPFS_HASH, compose: process.env.IPFS_DOCKER_COMPOSE_HASH };
162
+ };
163
+
164
+ // The compose and the image tree, added through ipfsApi; their CIDs land
165
+ // in IPFS_DOCKER_COMPOSE_HASH / IPFS_HASH (files and environment).
166
+ const uploadToIpfs = () => {
167
+ for (const file of ['IPFS_HASH.ipfs', 'IPFS_DOCKER_COMPOSE_HASH.ipfs']) {
168
+ if (fs.existsSync(file)) fs.unlinkSync(file);
169
+ }
170
+ console.log('Upload docker-compose-final.yml to IPFS');
171
+ execSync(`node ../ipfs.mjs --host "${ipfsApi}" --action upload --filePath docker-compose-final.yml`, { stdio: "inherit" });
172
+ if (!fs.existsSync('IPFS_DOCKER_COMPOSE_HASH.ipfs')) {
173
+ console.error("Error: Could not upload docker-compose-final.yml to IPFS, please try again!");
174
+ process.exit(1);
175
+ }
176
+ process.env.IPFS_DOCKER_COMPOSE_HASH = fs.readFileSync('IPFS_DOCKER_COMPOSE_HASH.ipfs', 'utf8').trim();
177
+ console.log("IPFS_DOCKER_COMPOSE_HASH: ", process.env.IPFS_DOCKER_COMPOSE_HASH);
178
+ writeEnv('IPFS_DOCKER_COMPOSE_HASH', process.env.IPFS_DOCKER_COMPOSE_HASH);
179
+ console.log('Upload docker registry to IPFS');
180
+ execSync(`node ../ipfs.mjs --host "${ipfsApi}" --action upload --folderPath ${registryPath}`, { stdio: "inherit" });
181
+ if (!fs.existsSync('IPFS_HASH.ipfs')) {
182
+ console.error("Error: Could not upload registry to IPFS, please try again!");
183
+ process.exit(1);
184
+ }
185
+ process.env.IPFS_HASH = fs.readFileSync('IPFS_HASH.ipfs', 'utf8').trim();
186
+ console.log("IPFS_HASH: ", process.env.IPFS_HASH);
187
+ writeEnv('IPFS_HASH', process.env.IPFS_HASH);
188
+ if (expectedCids && (process.env.IPFS_HASH !== expectedCids.image
189
+ || process.env.IPFS_DOCKER_COMPOSE_HASH !== expectedCids.compose)) {
190
+ console.error(`Error: the upload gave ${process.env.IPFS_HASH} (compose ${process.env.IPFS_DOCKER_COMPOSE_HASH}), not the registered ${expectedCids.image} (compose ${expectedCids.compose}).`);
191
+ process.exit(1);
192
+ }
193
+ };
194
+
121
195
  ['docker-compose.yml', 'docker-compose-final.yml'].forEach(file => {
122
196
  const template = `${file}.tmpl`;
123
197
  if (!fs.existsSync(template)) {
@@ -239,17 +313,19 @@ const main = async () => {
239
313
  // Where the session goes depends on who provisions the securelock. On a
240
314
  // CAS-attested testnet it is registered ON-CHAIN in the ethernity-cas
241
315
  // SessionRegistry, which the validator set reads and which accepts no
242
- // POST; the publish then waits until the validators serve it, because the
243
- // public-key harvest below provisions the enclave from them. On mainnet it
244
- // is registered with the Scontain CAS. A self-signing testnet has none: a
245
- // CAS-issued certificate would not match the key the enclave derives.
246
- if (!cas) {
247
- console.log(`\t✔ ${process.env.BLOCKCHAIN_NETWORK}: no CAS session; the securelock self-signs from MR_ENCLAVE`);
248
- } else if (casTestnet) {
316
+ // POST. That happens once the image is registered, below: the
317
+ // SessionRegistry takes a securelock session only from the wallet that
318
+ // owns the image name. The publish then waits until the validators serve
319
+ // it, because the public-key harvest provisions the enclave from them. On
320
+ // mainnet it is registered with the Scontain CAS. A self-signing testnet
321
+ // has none: a CAS-issued certificate would not match the key the enclave
322
+ // derives.
323
+ const registerSessionOnChain = async () => {
249
324
  const registered = await sessionRegistry.register(
250
325
  casChain.rpcUrl, casChain.chainId, casConfig.SESSION_REGISTRY[templateName],
251
326
  process.env.PRIVATE_KEY, fs.readFileSync('etny-securelock-test.yaml'),
252
- process.env.IPFS_ENDPOINT, '');
327
+ ipfsApi, '');
328
+ if (localKubo) await localKubo.provide(registered.cid);
253
329
  if (registered.registered) {
254
330
  console.log(`\t✔ Session ${registered.name} registered on-chain (body ${registered.cid})`);
255
331
  await sessionRegistry.waitVisible(
@@ -257,7 +333,10 @@ const main = async () => {
257
333
  } else {
258
334
  console.log(`\t✔ Session ${registered.name} already registered on-chain with this body (${registered.hash})`);
259
335
  }
260
- } else {
336
+ };
337
+ if (!cas) {
338
+ console.log(`\t✔ ${process.env.BLOCKCHAIN_NETWORK}: no CAS session; the securelock self-signs from MR_ENCLAVE`);
339
+ } else if (!casTestnet) {
261
340
  // don't generate new keys if PREDECESSOR_HASH_SECURELOCK is not empty and the key.pem and cert.pem files exist
262
341
  if (PREDECESSOR_HASH_SECURELOCK !== 'EMPTY' && fs.existsSync('key.pem') && fs.existsSync('cert.pem')) {
263
342
  console.log("Skipping keypair generation and certificate creation.");
@@ -407,15 +486,20 @@ const main = async () => {
407
486
  console.log("# Update docker-compose files");
408
487
 
409
488
  // On a CAS-attested testnet the compose names the CAS both enclaves are
410
- // provisioned from (a validator resolved from the registry, or
411
- // ECLD_CAS_ADDR; the node re-resolves it before each task) and the
412
- // trustedzone session the ImageRegistry records for this template.
489
+ // provisioned from and the trustedzone session the ImageRegistry records
490
+ // for this template. docker-compose.yml, which harvests the certificate
491
+ // here, names a validator that answered (or ECLD_CAS_ADDR); the published
492
+ // docker-compose-final.yml names the first validator in registry order,
493
+ // so that a rerun of this publish renders the same compose. The node and
494
+ // the extraction service resolve a CAS again before they run it.
413
495
  let casAddr = null;
496
+ let publishedCasAddr = null;
414
497
  let trustedZoneSession = null;
415
498
  if (casTestnet) {
416
499
  casAddr = await casResolver.casAddressFor(casChain.rpcUrl, casConfig.VALIDATOR_REGISTRY[templateName]);
417
- console.log(`\t✔ CAS for this network: ${casAddr}`);
418
- trustedZoneSession = execSync(`node ./image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" ${templateName} "v3" "" "getTrustedZoneSession"`).toString().trim();
500
+ publishedCasAddr = await casResolver.publishedCasAddress(casChain.rpcUrl, casConfig.VALIDATOR_REGISTRY[templateName]);
501
+ console.log(`\t✔ CAS for this network: ${casAddr} (the published compose names ${publishedCasAddr})`);
502
+ trustedZoneSession = execSync(`node ./image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" ${templateName} "v3" "getTrustedZoneSession"`).toString().trim();
419
503
  if (!trustedZoneSession) {
420
504
  console.error(`ERROR! The ImageRegistry records no trustedzone session for ${templateName}.`);
421
505
  process.exit(1);
@@ -455,7 +539,8 @@ const main = async () => {
455
539
  .replace(/__ENCLAVE_NAME_SECURELOCK__/g, ENCLAVE_NAME_SECURELOCK)
456
540
  .replace(/__ENCLAVE_NAME_TRUSTEDZONE__/g, ENCLAVE_NAME_TRUSTEDZONE);
457
541
  if (casTestnet) {
458
- updatedContent = updatedContent.replace(/SCONE_CAS_ADDR=scone-cas\.cf/g, `SCONE_CAS_ADDR=${casAddr}`);
542
+ const fileCasAddr = file === 'docker-compose-final.yml' ? publishedCasAddr : casAddr;
543
+ updatedContent = updatedContent.replace(/SCONE_CAS_ADDR=scone-cas\.cf/g, `SCONE_CAS_ADDR=${fileCasAddr}`);
459
544
  }
460
545
 
461
546
  fs.writeFileSync(file, updatedContent, 'utf8');
@@ -479,7 +564,30 @@ const main = async () => {
479
564
  // let PUBLIC_KEY_SECURELOCK_RES = execSync(`docker-compose run etny-securelock 2>/dev/null | grep -v Creating | grep -v Pulling | grep -v latest | grep -v Digest | sed 's/.*PUBLIC_KEY:\\s*//' | tr -d '\\r'`).toString().trim();
480
565
 
481
566
 
482
- // TODO: calculate hash of the files localy, and query the public key service, if the hash is already there it means we dont have to do the below.
567
+ // The image and its compose go out before the certificate is extracted,
568
+ // whichever way it is: the extraction service fetches them by these CIDs.
569
+ // On a two-step registry (ECImageRegistryV2 and later) the wallet records
570
+ // the image first (registerImage, with the publish's node), under the
571
+ // CIDs the upload gives, hashed before anything is stored. On
572
+ // ECImageRegistryV3 that binds the image name to this wallet before the
573
+ // upload, the session registration or anything else discloses it. The
574
+ // bootnode's mirror then pins the image and the extraction service queues
575
+ // it from the chain; the certificate is written below by the same wallet
576
+ // (setImageCert). A V1 registry takes both in one addImage call.
577
+ const registryV2 = execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "${securelock}" "v3" "isV2"`, { env: { ...process.env, PROJECT_NAME: securelock } }).toString().trim() === 'true';
578
+ let ipfsPeer = '';
579
+ if (registryV2) {
580
+ hashForIpfs();
581
+ if (localKubo) ipfsPeer = await localKubo.peerMultiaddr();
582
+ execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "${securelock}" "v3" "registerImage"`, { stdio: "inherit", env: { ...process.env, PROJECT_NAME: securelock, IPFS_PEER: ipfsPeer } });
583
+ }
584
+ uploadToIpfs();
585
+ const registryEnv = { ...process.env, PROJECT_NAME: securelock, IPFS_PEER: ipfsPeer };
586
+ if (registryV2 && localKubo) {
587
+ await localKubo.provide(process.env.IPFS_HASH);
588
+ await localKubo.provide(process.env.IPFS_DOCKER_COMPOSE_HASH);
589
+ }
590
+ if (casTestnet) await registerSessionOnChain();
483
591
 
484
592
  if (fs.existsSync('certificate.securelock.crt')) {
485
593
  // delete it
@@ -488,7 +596,7 @@ const main = async () => {
488
596
 
489
597
  let PUBLIC_KEY_SECURELOCK_RES = '';
490
598
  try {
491
- const output = execSync(`docker-compose run etny-securelock`, { cwd: runDir }).toString();
599
+ const output = execSync(`docker-compose run --rm etny-securelock`, { cwd: runDir }).toString();
492
600
  console.log("Output of docker-compose run etny-securelock:");
493
601
  // Extract the WHOLE multi-line PEM certificate block from the enclave
494
602
  // output. A previous version grabbed only the single line containing
@@ -521,43 +629,6 @@ const main = async () => {
521
629
  process.exit(0);
522
630
  } else {
523
631
  console.log("\nGenerating certificates using the Ethernity Cloud signing service...\n");
524
- console.log("**** Started ipfs initial pining ****");
525
- if (fs.existsSync('IPFS_HASH.ipfs')) {
526
- fs.unlinkSync('IPFS_HASH.ipfs');
527
- }
528
- if (fs.existsSync('IPFS_DOCKER_COMPOSE_HASH.ipfs')) {
529
- fs.unlinkSync('IPFS_DOCKER_COMPOSE_HASH.ipfs');
530
- }
531
-
532
-
533
- console.log('Upload docker-compose-final.yml to IPFS');
534
- const dockerHash = execSync(`node ../ipfs.mjs --host "${process.env.IPFS_ENDPOINT}" --action upload --filePath docker-compose-final.yml`, { stdio: "inherit" });
535
- if (!fs.existsSync('IPFS_DOCKER_COMPOSE_HASH.ipfs')) {
536
- console.error("Error: Could not upload docker-compose-final.yml to IPFS, please try again!");
537
- process.exit(1);
538
- }
539
- process.env.IPFS_DOCKER_COMPOSE_HASH = fs.readFileSync('IPFS_DOCKER_COMPOSE_HASH.ipfs', 'utf8').trim();
540
- console.log("IPFS_DOCKER_COMPOSE_HASH: ", process.env.IPFS_DOCKER_COMPOSE_HASH);
541
- writeEnv('IPFS_DOCKER_COMPOSE_HASH', process.env.IPFS_DOCKER_COMPOSE_HASH);
542
- console.log()
543
- await new Promise(resolve => setTimeout(resolve, 3000));
544
-
545
- console.log('Upload docker registry to IPFS');
546
- const repositoryHash = execSync(`node ../ipfs.mjs --host "${process.env.IPFS_ENDPOINT}" --action upload --folderPath ${registryPath}`, { stdio: "inherit" });
547
- if (!fs.existsSync(`./IPFS_HASH.ipfs`)) {
548
- console.error("Error: Could not upload registry to IPFS, please try again!");
549
- process.exit(1);
550
- }
551
- process.env.IPFS_HASH = fs.readFileSync(`./IPFS_HASH.ipfs`, 'utf8').trim();
552
- console.log("IPFS_HASH: ", process.env.IPFS_HASH);
553
- writeEnv('IPFS_HASH', process.env.IPFS_HASH);
554
-
555
- console.log()
556
-
557
- console.log("**** Finished ipfs initial pining ****");
558
- console.log()
559
- console.log()
560
-
561
632
  console.log(`ENCLAVE_NAME_SECURELOCK: ${ENCLAVE_NAME_SECURELOCK}`);
562
633
  execSync(`node ./public_key_service.js --enclave_name "${securelock}" --protocol_version "v3" --network "${process.env.BLOCKCHAIN_NETWORK}" --template_version "${process.env.VERSION}"`, { stdio: 'inherit' });
563
634
  PUBLIC_KEY_SECURELOCK_RES = fs.readFileSync('PUBLIC_KEY.txt', 'utf8').trim();
@@ -614,7 +685,7 @@ const main = async () => {
614
685
  }
615
686
 
616
687
  // const scriptPath = path.resolve(__dirname, '/image_registry.js');
617
- const trustedZoneCert = execSync(`node ./image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" ${templateName} "v3" "" "getTrustedZoneCert"`).toString().trim();
688
+ const trustedZoneCert = execSync(`node ./image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" ${templateName} "v3" "getTrustedZoneCert"`).toString().trim();
618
689
 
619
690
  console.log("trustedZoneCert: ", trustedZoneCert);
620
691
 
@@ -631,41 +702,42 @@ const main = async () => {
631
702
  console.log("Listing certificate PUBLIC_KEY_TRUSTEDZONE:");
632
703
  console.log(fs.readFileSync('certificate.trustedzone.crt', 'utf8'));
633
704
 
705
+ // image_registry.js reads the securelock certificate from registry/.
634
706
  fs.copyFileSync('certificate.securelock.crt', `${registryPath}/certificate.securelock.crt`);
635
707
  fs.copyFileSync('certificate.trustedzone.crt', `${registryPath}/certificate.trustedzone.crt`);
636
708
 
637
- if (fs.existsSync('IPFS_HASH.ipfs')) {
638
- fs.unlinkSync('IPFS_HASH.ipfs');
639
- }
640
-
641
- console.log('Upload docker registry to IPFS');
642
- execSync(`node ../ipfs.mjs --host "${process.env.IPFS_ENDPOINT}" --action upload --folderPath ${registryPath}`, { stdio: "inherit" });
643
- if (!fs.existsSync(`./IPFS_HASH.ipfs`)) {
644
- console.error("Error: Could not upload registry to IPFS, please try again!");
645
- process.exit(1);
646
- }
647
- process.env.IPFS_HASH = fs.readFileSync(`./IPFS_HASH.ipfs`, 'utf8').trim();
648
- console.log("IPFS_HASH: ", process.env.IPFS_HASH);
649
- writeEnv('IPFS_HASH', process.env.IPFS_HASH);
650
- process.chdir(currentDir);
651
- console.log(`Adding certificates for SECURELOCK ${securelock} into IMAGE REGISTRY smart contract...`);
652
- // image_registry.js registers the image under PROJECT_NAME.
653
- const registryEnv = { ...process.env, PROJECT_NAME: securelock };
654
- let existing = false;
655
- try {
656
- const existingImages = execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "${securelock}" "${process.env.VERSION}" "${process.env.PRIVATE_KEY}" "registerSecureLockImage"`, { stdio: "inherit", env: registryEnv });
657
- if (existingImages.toString().trim().replace('Image hash: ', '') === process.env.IPFS_HASH) {
658
- console.log("Certificates for SECURELOCK already added to IMAGE REGISTRY smart contract");
709
+ if (registryV2) {
710
+ // The registered hash is the tree uploaded before the extraction.
711
+ process.chdir(currentDir);
712
+ console.log(`Registering the certificate of SECURELOCK ${securelock} in the IMAGE REGISTRY smart contract...`);
713
+ execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "${securelock}" "v3" "setImageCert"`, { stdio: "inherit", env: registryEnv });
714
+ } else {
715
+ // A V1 registry gets the tree that carries the certificate files,
716
+ // uploaded again, with the certificate, in one addImage call.
717
+ if (fs.existsSync('IPFS_HASH.ipfs')) {
718
+ fs.unlinkSync('IPFS_HASH.ipfs');
719
+ }
720
+ console.log('Upload docker registry to IPFS');
721
+ execSync(`node ../ipfs.mjs --host "${ipfsApi}" --action upload --folderPath ${registryPath}`, { stdio: "inherit" });
722
+ if (!fs.existsSync(`./IPFS_HASH.ipfs`)) {
723
+ console.error("Error: Could not upload registry to IPFS, please try again!");
724
+ process.exit(1);
725
+ }
726
+ process.env.IPFS_HASH = fs.readFileSync(`./IPFS_HASH.ipfs`, 'utf8').trim();
727
+ console.log("IPFS_HASH: ", process.env.IPFS_HASH);
728
+ writeEnv('IPFS_HASH', process.env.IPFS_HASH);
729
+ if (localKubo) await localKubo.provide(process.env.IPFS_HASH);
730
+ process.chdir(currentDir);
731
+ console.log(`Adding certificates for SECURELOCK ${securelock} into IMAGE REGISTRY smart contract...`);
732
+ // image_registry.js registers the image under PROJECT_NAME and prints
733
+ // why when it cannot.
734
+ registryEnv.IPFS_HASH = process.env.IPFS_HASH;
735
+ try {
736
+ execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "${securelock}" "${process.env.VERSION}" "registerSecureLockImage"`, { stdio: "inherit", env: registryEnv });
737
+ } catch (error) {
738
+ console.error("Error: Could not add the certificates of the SECURELOCK to the IMAGE REGISTRY smart contract");
739
+ process.exit(1);
659
740
  }
660
- existing = true;
661
- } catch (error) {
662
- // console.error("Error: Could not add certificates for SECURELOCK into IMAGE REGISTRY smart contract");
663
- // console.error(error);
664
- // process.exit(1);
665
- }
666
-
667
- if (!existing) {
668
- const res = execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "" "" "" "registerSecureLockImage"`, { stdio: "inherit", env: registryEnv });
669
741
  }
670
742
  // The on-chain session points at the image it admits, so a validator can
671
743
  // pin the image beside the body it serves.
@@ -1,58 +1,62 @@
1
- const AppCss = require('./App.css');
1
+ import './App.css';
2
2
  import EthernityCloudRunner from "@ethernity-cloud/runner";
3
- import {ECEvent, ECRunner, ECStatus} from "@ethernity-cloud/runner/enums";
4
- import Web3 from 'web3';
3
+ import { ECStatus } from "@ethernity-cloud/runner/enums";
5
4
 
5
+ // Filled in from the project's .env by src/preStart.mjs on `npm start`: the
6
+ // securelock this dApp is published as, the trustedzone it was built against,
7
+ // the network's token address and chain id, the -unsafe network when the dApp
8
+ // runs without a CAS (null otherwise), and the IPFS endpoint.
6
9
  const PROJECT_NAME = "";
10
+ const TRUSTED_ZONE_IMAGE = "";
11
+ const NETWORK_ADDRESS = "";
12
+ const CHAIN_ID = null;
13
+ const UNSAFE_NETWORK = null;
7
14
  const IPFS_ENDPOINT = "";
8
15
 
16
+ // A call into src/serverless/backend.js; its return value is the task result.
9
17
  const code = `hello("World");`;
10
18
 
11
19
  function App() {
12
20
  const executeTask = async () => {
13
- const runner = new EthernityCloudRunner();
14
- // this is a server provided by Ethernity CLOUD, please bear in mind that you can use your own Decentralized Storage server
15
- const ipfsAddress = IPFS_ENDPOINT;
16
- runner.initializeStorage(ipfsAddress);
17
- console.log(PROJECT_NAME)
18
- const onTaskProgress = (e) => {
19
- if (e.detail.status === ECStatus.ERROR) {
20
- console.error(e.detail.message);
21
- } else {
22
- console.log(e.detail.message);
23
- }
24
- };
25
-
26
- const onTaskCompleted = (e) => {
27
- console.log(`Task Result: ${e.detail.message.result}`);
28
- // display the result in page below the buttons
29
- const result = document.createElement("p");
30
- result.innerHTML = `Task Result: ${e.detail.message.result}`;
31
- document.body.appendChild(result);
21
+ // The browser wallet (MetaMask) signs and pays for the order.
22
+ const runner = new EthernityCloudRunner(NETWORK_ADDRESS, {}, CHAIN_ID || undefined);
23
+ if (UNSAFE_NETWORK) {
24
+ await runner.setNetwork(...UNSAFE_NETWORK);
32
25
  }
26
+ runner.initializeStorage(IPFS_ENDPOINT);
33
27
 
34
- runner.addEventListener(ECEvent.TASK_PROGRESS, onTaskProgress);
35
- runner.addEventListener(ECEvent.TASK_COMPLETED, onTaskCompleted);
28
+ // Events are named by task status; detail is { message, status, progress }.
29
+ runner.addEventListener(ECStatus.DEFAULT, (e) => {
30
+ console.log(`[${e.detail.progress}] ${e.detail.message}`);
31
+ });
32
+ runner.addEventListener(ECStatus.ERROR, (e) => {
33
+ console.error(e.detail.message);
34
+ });
35
+ runner.addEventListener(ECStatus.SUCCESS, async () => {
36
+ const result = await runner.getResult();
37
+ console.log(`Task Result: ${result}`);
38
+ // display the result in page below the buttons
39
+ const line = document.createElement("p");
40
+ line.textContent = `Task Result: ${result}`;
41
+ document.body.appendChild(line);
42
+ });
36
43
 
37
- await runner.run(PROJECT_NAME,
38
- code,
39
- '',
40
- { taskPrice: 10, cpu: 1, memory: 1, storage: 10, bandwidth: 1, duration: 1, validators: 1 });
44
+ const resources = { taskPrice: 10, cpu: 1, memory: 1, storage: 10, bandwidth: 1, duration: 1, validators: 1 };
45
+ await runner.run(resources, PROJECT_NAME, code, '', TRUSTED_ZONE_IMAGE);
41
46
  };
47
+
42
48
  const connectWallet = async () => {
43
- if (window.ethereum) {
44
- window.web3 = new Web3(window.ethereum);
45
- try {
46
- // Request account access
47
- await window.ethereum.request({ method: 'eth_requestAccounts' });
48
- console.log("Wallet connected");
49
- } catch (error) {
50
- console.error("User denied account access");
51
- }
52
- } else {
53
- console.log('Please install MetaMask!');
54
- }
55
- };
49
+ if (!window.ethereum) {
50
+ console.log('Please install MetaMask!');
51
+ return;
52
+ }
53
+ try {
54
+ await window.ethereum.request({ method: 'eth_requestAccounts' });
55
+ console.log("Wallet connected");
56
+ } catch (error) {
57
+ console.error("User denied account access");
58
+ }
59
+ };
56
60
 
57
61
  return (
58
62
  <div className="container">
@@ -61,4 +65,4 @@ function App() {
61
65
  </div>
62
66
  );
63
67
  }
64
- export default App;
68
+ export default App;
@@ -1,57 +1,47 @@
1
+ // Runs before `react-scripts start` (the project's `npm start`): fills the
2
+ // constants at the top of src/ec_helloworld_example.js from the project's
3
+ // .env, the way ecld-run reads the same values. PROJECT_NAME and
4
+ // TRUSTED_ZONE_IMAGE take their -unsafe form on an -unsafe network; the
5
+ // network's token address, chain id and -unsafe (network, type) come from the
6
+ // runner's own enums through the SDK's network table.
1
7
  import fs from 'fs';
8
+ import { createRequire } from 'module';
2
9
  import dotenv from 'dotenv';
3
- dotenv.config();
10
+ import * as enums from '@ethernity-cloud/runner/enums.js';
4
11
 
5
- export const ECRunner = {
6
- 'etny-pynithy-testnet': ['0x02882F03097fE8cD31afbdFbB5D72a498B41112c'],
7
- 'etny-nodenithy-testnet': ['0x02882F03097fE8cD31afbdFbB5D72a498B41112c'],
8
- 'etny-pynithy': ['0x549A6E06BB2084100148D50F51CF77a3436C3Ae7'],
9
- 'etny-nodenithy': ['0x549A6E06BB2084100148D50F51CF77a3436C3Ae7'],
10
- 'ecld-nodenithy-testnet': ['0xfb450e40f590F1B5A119a4B82E6F3579D6742a00'],
11
- 'ecld-pynithy': ['0xc6920888988cAcEeA7ACCA0c96f2D65b05eE22Ba'],
12
- 'ecld-nodenithy': ['0xc6920888988cAcEeA7ACCA0c96f2D65b05eE22Ba']
12
+ dotenv.config();
13
+ const require = createRequire(import.meta.url);
14
+ const casConfig = require('@ethernity-cloud/sdk-js/cas/config.js');
15
+ const { networkFor } = require('@ethernity-cloud/sdk-js/network.js');
16
+
17
+ const { PROJECT_NAME, TRUSTED_ZONE_IMAGE, BLOCKCHAIN_NETWORK, IPFS_ENDPOINT } = process.env;
18
+ if (!PROJECT_NAME || !BLOCKCHAIN_NETWORK) {
19
+ console.error('preStart: PROJECT_NAME and BLOCKCHAIN_NETWORK are not set in .env; run `npm run ecld-init` first.');
20
+ process.exit(1);
21
+ }
22
+ const { network, known } = networkFor(enums, BLOCKCHAIN_NETWORK);
23
+ if (!network) {
24
+ console.error(`preStart: unknown BLOCKCHAIN_NETWORK '${BLOCKCHAIN_NETWORK}' in .env. Known: ${known.join(', ')}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ const constants = {
29
+ PROJECT_NAME: casConfig.nameOnNetwork(PROJECT_NAME, BLOCKCHAIN_NETWORK),
30
+ TRUSTED_ZONE_IMAGE: casConfig.nameOnNetwork(TRUSTED_ZONE_IMAGE, BLOCKCHAIN_NETWORK) || '',
31
+ NETWORK_ADDRESS: network.address,
32
+ CHAIN_ID: network.chainId || null,
33
+ UNSAFE_NETWORK: network.unsafe || null,
34
+ IPFS_ENDPOINT: IPFS_ENDPOINT || 'https://ipfs.ethernity.cloud',
13
35
  };
14
36
 
15
37
  const filePath = 'src/ec_helloworld_example.js';
16
- const fileContent = fs.readFileSync(filePath, 'utf8');
17
-
18
- const updatedContent = fileContent
19
- .replace(/const PROJECT_NAME = ".*?";/, `const PROJECT_NAME = "${process.env.PROJECT_NAME}";`)
20
- .replace(/const IPFS_ENDPOINT = ".*?";/, `const IPFS_ENDPOINT = "${process.env.IPFS_ENDPOINT}";`)
21
- .replace(/new EthernityCloudRunner\(.*?\);/, `new EthernityCloudRunner('${ECRunner[process.env.ENCLAVE_NAME_TRUSTEDZONE]}');`);
22
-
23
- fs.writeFileSync(filePath, updatedContent, 'utf8');
24
-
25
- // const code = updatedContent.match(/const code = `hello\("(.*?)"\);/)[1];
26
-
27
- const imageRegistryPath = 'node_modules/@ethernity-cloud/runner/contract/operation/imageRegistryContract.js';
28
- const imageRegistryContent = fs.readFileSync(imageRegistryPath, 'utf8');
29
-
30
- // Log the original content for debugging
31
- // console.log('Original imageRegistryContent:', imageRegistryContent);
32
-
33
- // Replace the content in imageRegistryContract.js
34
- // const updatedImageRegistryContent = imageRegistryContent.replace(/getLatestTrustedZoneImageCertPublicKey\([^)]*\);/, "getLatestTrustedZoneImageCertPublicKey('" + process.env.ENCLAVE_NAME_TRUSTEDZONE + "', 'v3')").replace(/getLatestImageVersionPublicKey\([^)]*\)/, "getLatestImageVersionPublicKey(imageName, '" + process.env.VERSION + "')");
35
- const updatedImageRegistryContent = imageRegistryContent.replace(
36
- /async getEnclaveDetailsV3\(.*?\{[\s\S]*?\}\s*\}/,
37
- `async getEnclaveDetailsV3(imageName, version) {
38
- try {
39
- const trustedZonePublicKey = (await this.contract.getLatestTrustedZoneImageCertPublicKey('${process.env.ENCLAVE_NAME_TRUSTEDZONE}', 'v3'));
40
- const imageDetails = await this.contract.getLatestImageVersionPublicKey(imageName, '${process.env.VERSION}');
41
- return [imageDetails[0], trustedZonePublicKey[1], imageDetails[2]];
42
- } catch (e) {
43
- console.log(e);
44
- return null;
45
- }
46
- }`
47
- );
48
-
49
-
50
- fs.writeFileSync(imageRegistryPath, updatedImageRegistryContent, 'utf8');
51
-
52
- const runnerPath = 'node_modules/@ethernity-cloud/runner/runner.js';
53
- const runnerContent = fs.readFileSync(runnerPath, 'utf8');
54
-
55
- const updatedRunnerContent = runnerContent.replace(/this.#enclaveImageIPFSHash}:.*?:/, "this.#enclaveImageIPFSHash}:"+process.env.ENCLAVE_NAME_TRUSTEDZONE+":").replace(/new ImageRegistryContract\([^)]*\);/, "new ImageRegistryContract(this.#networkAddress, '"+process.env.ENCLAVE_NAME_TRUSTEDZONE+"');");
56
-
57
- fs.writeFileSync(runnerPath, updatedRunnerContent, 'utf8');
38
+ let content = fs.readFileSync(filePath, 'utf8');
39
+ for (const [name, value] of Object.entries(constants)) {
40
+ const line = new RegExp(`^const ${name} = .*;$`, 'm');
41
+ if (!line.test(content)) {
42
+ console.error(`preStart: ${filePath} has no 'const ${name} = ...;' line to fill.`);
43
+ process.exit(1);
44
+ }
45
+ content = content.replace(line, `const ${name} = ${JSON.stringify(value)};`);
46
+ }
47
+ fs.writeFileSync(filePath, content, 'utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethernity-cloud/sdk-js",
3
- "version": "1.7.0",
3
+ "version": "1.7.7",
4
4
  "description": "A package to run ethernity cloud build scripts",
5
5
  "scripts": {
6
6
  "ecld-build": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/build.js",
@@ -31,11 +31,14 @@
31
31
  "test.js",
32
32
  "info.js",
33
33
  "run.js",
34
+ "network.js",
35
+ "localKubo.js",
36
+ "revert.js",
34
37
  "!**/__pycache__/**",
35
38
  "!**/*.pyc"
36
39
  ],
37
40
  "dependencies": {
38
- "@ethernity-cloud/runner": "^0.6.0",
41
+ "@ethernity-cloud/runner": "^0.6.3",
39
42
  "acorn": "^8.11.0",
40
43
  "adm-zip": "^0.5.14",
41
44
  "axios": "^1.7.2",