@ethernity-cloud/sdk-js 1.7.1 → 1.7.8
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/README.md +30 -5
- package/cas/config.js +2 -2
- package/cas/resolver.js +49 -18
- package/cas/session_registry.js +72 -24
- package/info.js +1 -1
- package/init.js +13 -0
- package/localKubo.js +205 -0
- package/nodenithy/build/securelock/src/etny_crypto.js +20 -2
- package/nodenithy/build/securelock/src/models.js +3 -5
- package/nodenithy/build/securelock/src/securelock.js.tmpl +136 -70
- package/nodenithy/build.mjs +8 -7
- package/nodenithy/ipfs.mjs +37 -13
- package/nodenithy/run/image_registry.abi +1 -1
- package/nodenithy/run/image_registry.js +258 -68
- package/nodenithy/run/public_key_service.js +10 -3
- package/nodenithy/run/public_key_service.py +12 -6
- package/nodenithy/run.js +164 -88
- package/package.json +5 -2
- package/publish.js +20 -8
- package/pynithy/build/securelock/src/securelock.py.tmpl +21 -9
- package/pynithy/build.mjs +8 -7
- package/pynithy/ipfs.mjs +37 -13
- package/pynithy/run/image_registry.abi +1 -1
- package/pynithy/run/image_registry.js +258 -68
- package/pynithy/run/public_key_service.js +10 -3
- package/pynithy/run.js +164 -88
- package/revert.js +76 -0
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
const Web3 = require('web3');
|
|
2
2
|
const { ethers } = require('ethers');
|
|
3
|
-
const { Account } = require('eth-lib/lib/account');
|
|
4
3
|
const { config } = require('dotenv');
|
|
5
4
|
const fs = require('fs');
|
|
6
5
|
const path = require('path');
|
|
6
|
+
const { isRevert, revertReason } = require('../../revert.js');
|
|
7
7
|
|
|
8
8
|
config();
|
|
9
9
|
|
|
10
|
+
// The publishing wallet's key comes from the environment only: a command line
|
|
11
|
+
// is visible to other processes and is printed when the command fails.
|
|
10
12
|
const PRIVATE_KEY = process.env.PRIVATE_KEY || "";
|
|
11
13
|
let BLOCKCHAIN_NETWORK = process.env.BLOCKCHAIN_NETWORK || "Bloxberg_Testnet";
|
|
12
14
|
let NETWORK_RPC = "https://bloxberg.ethernity.cloud";
|
|
13
|
-
let IMAGE_REGISTRY_ADDRESS = "
|
|
15
|
+
let IMAGE_REGISTRY_ADDRESS = "0xa372a6e1Eb7Fcf343AF6b91E809b900C55001CD1"; // bloxberg testnet (ECImageRegistryV3)
|
|
14
16
|
let CHAIN_ID = 8995;
|
|
15
17
|
let GAS = 9000000;
|
|
16
18
|
let GAS_PRICE = 1;
|
|
17
19
|
|
|
18
20
|
function setVars(network = "") {
|
|
19
21
|
if (BLOCKCHAIN_NETWORK.includes("Bloxberg")) {
|
|
20
|
-
// The bloxberg testnet reads
|
|
22
|
+
// The bloxberg testnet reads ECImageRegistryV3; mainnet the original registry.
|
|
21
23
|
IMAGE_REGISTRY_ADDRESS = BLOCKCHAIN_NETWORK.includes("Testnet")
|
|
22
|
-
? "
|
|
24
|
+
? "0xa372a6e1Eb7Fcf343AF6b91E809b900C55001CD1"
|
|
23
25
|
: "0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31";
|
|
24
26
|
} else if (BLOCKCHAIN_NETWORK.includes("Polygon")) {
|
|
25
27
|
if (BLOCKCHAIN_NETWORK.includes("Mainnet")) {
|
|
@@ -58,6 +60,26 @@ function isStringPrivateKey(privateKey) {
|
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
// The address of a private key given with or without its 0x prefix.
|
|
64
|
+
function walletAddress(privateKey) {
|
|
65
|
+
return new ethers.Wallet(privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`).address;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// REWARD_ADDRESS from the environment: where the image's developer fee is
|
|
69
|
+
// paid when not the publishing wallet, or "" when unset. ECImageRegistryV3
|
|
70
|
+
// refuses the zero address, so it is refused here before any transaction.
|
|
71
|
+
function rewardAddressFromEnv() {
|
|
72
|
+
const rewardAddress = (process.env.REWARD_ADDRESS || "").trim();
|
|
73
|
+
if (!rewardAddress) return "";
|
|
74
|
+
if (!ethers.utils.isAddress(rewardAddress)) {
|
|
75
|
+
throw new Error(`REWARD_ADDRESS ${rewardAddress} is not an address`);
|
|
76
|
+
}
|
|
77
|
+
if (rewardAddress.toLowerCase() === ethers.constants.AddressZero) {
|
|
78
|
+
throw new Error("REWARD_ADDRESS is the zero address; leave it empty to be paid at the publishing wallet");
|
|
79
|
+
}
|
|
80
|
+
return rewardAddress;
|
|
81
|
+
}
|
|
82
|
+
|
|
61
83
|
async function checkAccountBalance() {
|
|
62
84
|
try {
|
|
63
85
|
const web3 = new ethers.providers.JsonRpcProvider(NETWORK_RPC);
|
|
@@ -124,61 +146,12 @@ class ImageRegistry {
|
|
|
124
146
|
const receipt = await this.provider.sendTransaction(signedTxn.rawTransaction);
|
|
125
147
|
}
|
|
126
148
|
|
|
149
|
+
// Record the securelock with its certificate on a V1 registry, in one
|
|
150
|
+
// addImage call. A failure, including the registry's refusal, throws.
|
|
127
151
|
async addSecureLockImageCert(certContent, ipfsHash, imageName, version, dockerComposeHash, enclaveNameSecureLock, fee) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (BLOCKCHAIN_NETWORK.includes("Polygon")) {
|
|
132
|
-
console.log("Polygon Mainnet");
|
|
133
|
-
let nonce = await this.provider.getTransactionCount(this.acct.address, 'pending');
|
|
134
|
-
// Fetch current gas price and increase it
|
|
135
|
-
let gasPrice = await this.provider.getGasPrice();
|
|
136
|
-
gasPrice = gasPrice.mul(ethers.BigNumber.from(110)).div(ethers.BigNumber.from(100));
|
|
137
|
-
const unicornTxn = await this.imageRegistryContract.addImage(
|
|
138
|
-
ipfsHash, certContent, version, imageName, dockerComposeHash, enclaveNameSecureLock, fee,
|
|
139
|
-
{
|
|
140
|
-
nonce: nonce,
|
|
141
|
-
gasPrice: gasPrice,
|
|
142
|
-
});
|
|
143
|
-
// console.log("Transaction: ", unicornTxn);
|
|
144
|
-
const receipt = await this.imageRegistryContract.provider.waitForTransaction(unicornTxn.hash);
|
|
145
|
-
|
|
146
|
-
// console.log("transaction status: ", receipt.status);
|
|
147
|
-
console.log("transaction receipt: ", unicornTxn.hash);
|
|
148
|
-
if (receipt.status === 1) {
|
|
149
|
-
console.log("Adding secure lock image cert transaction was successful!");
|
|
150
|
-
} else {
|
|
151
|
-
// console.log("receipt.status", receipt.status)
|
|
152
|
-
console.log("Image certificates already exist for this image!");
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
const unicornTxn = await this.imageRegistryContract.addImage(
|
|
156
|
-
ipfsHash, certContent, version, imageName, dockerComposeHash, enclaveNameSecureLock, fee);
|
|
157
|
-
// console.log("Transaction: ", unicornTxn);
|
|
158
|
-
const receipt = await this.imageRegistryContract.provider.waitForTransaction(unicornTxn.hash);
|
|
159
|
-
|
|
160
|
-
// console.log("transaction status: ", receipt.status);
|
|
161
|
-
console.log("transaction receipt: ", unicornTxn.hash);
|
|
162
|
-
if (receipt.status === 1) {
|
|
163
|
-
console.log("Adding secure lock image cert transaction was successful!");
|
|
164
|
-
} else {
|
|
165
|
-
// console.log("receipt.status", receipt.status)
|
|
166
|
-
console.log("Image certificates already exist for this image!");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// console.log("this.acct.address: ", this.acct.address);
|
|
171
|
-
// console.log("private key", PRIVATE_KEY);
|
|
172
|
-
// console.log('Getting nonce');
|
|
173
|
-
// const nonce = await this.imageRegistryContract.provider.getTransactionCount(this.acct.address);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// const signedTxn = await this.acct.signTransaction(unicornTxn);
|
|
177
|
-
// const receipt = await this.provider.sendTransaction(signedTxn.rawTransaction);
|
|
178
|
-
} catch (e) {
|
|
179
|
-
// console.error(e);
|
|
180
|
-
console.log("Image certificates already exist for this image!");
|
|
181
|
-
}
|
|
152
|
+
console.log("Adding secure lock image cert to image registry");
|
|
153
|
+
await this.write('addImage', ipfsHash, certContent, version, imageName, dockerComposeHash, enclaveNameSecureLock, fee);
|
|
154
|
+
console.log("Adding secure lock image cert transaction was successful!");
|
|
182
155
|
}
|
|
183
156
|
|
|
184
157
|
async getImagePublicKeyCert(ipfsHash) {
|
|
@@ -253,6 +226,18 @@ class ImageRegistry {
|
|
|
253
226
|
const receipt = await this.provider.sendTransaction(signedTxn.rawTransaction);
|
|
254
227
|
}
|
|
255
228
|
|
|
229
|
+
// Where the image's developer fee is paid. The registry records the
|
|
230
|
+
// publishing wallet at registration; only the image's owner changes it.
|
|
231
|
+
async setRewardAddress(ipfsHash, rewardAddress) {
|
|
232
|
+
const current = await this.imageRegistryContract.getRewardAddress(ipfsHash);
|
|
233
|
+
if (current.toLowerCase() === rewardAddress.toLowerCase()) {
|
|
234
|
+
console.log(`Reward address is already ${rewardAddress}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
console.log(`Setting the reward address to ${rewardAddress}`);
|
|
238
|
+
await this.write('changeImageRewardAddress', ipfsHash, rewardAddress);
|
|
239
|
+
}
|
|
240
|
+
|
|
256
241
|
async getTrustedZoneCert(ipfsHash) {
|
|
257
242
|
const cert = await this.imageRegistryContract.getTrustedZoneCert(ipfsHash);
|
|
258
243
|
return cert;
|
|
@@ -283,14 +268,142 @@ class ImageRegistry {
|
|
|
283
268
|
return ['', '', ''];
|
|
284
269
|
}
|
|
285
270
|
}
|
|
271
|
+
|
|
272
|
+
// Whether the registry is an ECImageRegistryV2 or later, which records an
|
|
273
|
+
// image before its certificate exists (registerImage, then setImageCert).
|
|
274
|
+
// A V1 registry has no pendingImages() and reverts; a node that does not
|
|
275
|
+
// answer is an error, not a V1 registry.
|
|
276
|
+
async isV2() {
|
|
277
|
+
try {
|
|
278
|
+
await this.imageRegistryContract.pendingImages();
|
|
279
|
+
return true;
|
|
280
|
+
} catch (e) {
|
|
281
|
+
if (isRevert(e)) return false;
|
|
282
|
+
throw e;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// The wallet that owns the securelock name on an ECImageRegistryV3 or
|
|
287
|
+
// later (the zero address while nobody does), or null on a registry
|
|
288
|
+
// without name owners. Only the owner publishes under the name, and a
|
|
289
|
+
// name and its -unsafe twin have one owner.
|
|
290
|
+
async imageNameOwner(imageName) {
|
|
291
|
+
try {
|
|
292
|
+
return await this.imageRegistryContract.imageNameOwner(imageName);
|
|
293
|
+
} catch (e) {
|
|
294
|
+
if (isRevert(e)) return null;
|
|
295
|
+
throw e;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Send `method(...args)` from the publishing wallet. It is simulated
|
|
300
|
+
// first, so a call the registry refuses stops here with the registry's
|
|
301
|
+
// reason, then sent with its gas estimate plus 30%: what a registry write
|
|
302
|
+
// costs depends on the pending list, which other publishers change
|
|
303
|
+
// between the estimate and the block.
|
|
304
|
+
async write(method, ...args) {
|
|
305
|
+
try {
|
|
306
|
+
await this.imageRegistryContract.callStatic[method](...args);
|
|
307
|
+
} catch (e) {
|
|
308
|
+
throw new Error(`the image registry refuses ${method}: ${revertReason(e, this.imageRegistryContract.interface)}`);
|
|
309
|
+
}
|
|
310
|
+
const gas = await this.imageRegistryContract.estimateGas[method](...args);
|
|
311
|
+
const overrides = { gasLimit: gas.mul(13).div(10) };
|
|
312
|
+
if (BLOCKCHAIN_NETWORK.includes("Polygon")) {
|
|
313
|
+
overrides.nonce = await this.provider.getTransactionCount(this.acct.address, 'pending');
|
|
314
|
+
overrides.gasPrice = (await this.provider.getGasPrice()).mul(110).div(100);
|
|
315
|
+
}
|
|
316
|
+
const tx = await this.imageRegistryContract[method](...args, overrides);
|
|
317
|
+
const receipt = await this.imageRegistryContract.provider.waitForTransaction(tx.hash);
|
|
318
|
+
console.log("transaction receipt: ", tx.hash);
|
|
319
|
+
if (receipt.status !== 1) {
|
|
320
|
+
throw new Error(`${method} reverted (${tx.hash})`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Record the securelock on a V2 registry before its certificate exists:
|
|
325
|
+
// name, protocol version v3, compose, session, the publisher's fee and the
|
|
326
|
+
// IPFS node that holds the image (a multiaddr, or ""). A hash this wallet
|
|
327
|
+
// already registered with the same name, compose and session is left as
|
|
328
|
+
// it is; a hash another wallet registered is refused, and so is a name
|
|
329
|
+
// another wallet owns. The record cannot be changed, so a hash registered
|
|
330
|
+
// with another compose or session is refused too: the nodes would fetch
|
|
331
|
+
// the compose the record names.
|
|
332
|
+
async registerImage(ipfsHash, imageName, dockerComposeHash, session, fee, ipfsPeer) {
|
|
333
|
+
const details = await this.imageRegistryContract.imageDetails(ipfsHash);
|
|
334
|
+
if (details.owner !== ethers.constants.AddressZero) {
|
|
335
|
+
if (details.owner.toLowerCase() !== this.acct.address.toLowerCase()) {
|
|
336
|
+
throw new Error(`${ipfsHash} is registered by ${details.owner}, not by this wallet`);
|
|
337
|
+
}
|
|
338
|
+
const differing = [
|
|
339
|
+
['name', details.name, imageName],
|
|
340
|
+
['compose', details.dockerComposeHash, dockerComposeHash],
|
|
341
|
+
['session', details.session, session],
|
|
342
|
+
].filter(([, recorded, wanted]) => recorded !== wanted);
|
|
343
|
+
if (differing.length) {
|
|
344
|
+
const recorded = differing.map(([field, value]) => `${field} ${value}`).join(', ');
|
|
345
|
+
const wanted = differing.map(([field, , value]) => `${field} ${value}`).join(', ');
|
|
346
|
+
throw new Error(`${ipfsHash} is registered with ${recorded}, and this publish has ${wanted}; run ecld-build to publish a new version`);
|
|
347
|
+
}
|
|
348
|
+
console.log(`${ipfsHash} is already registered`);
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
const nameOwner = await this.imageNameOwner(imageName);
|
|
352
|
+
if (nameOwner && nameOwner !== ethers.constants.AddressZero
|
|
353
|
+
&& nameOwner.toLowerCase() !== this.acct.address.toLowerCase()) {
|
|
354
|
+
throw new Error(`the image name ${imageName} belongs to ${nameOwner}; publish under another PROJECT_NAME`);
|
|
355
|
+
}
|
|
356
|
+
console.log(`Registering ${imageName} v3 as ${ipfsHash} (compose ${dockerComposeHash}, peer ${ipfsPeer || 'none'})`);
|
|
357
|
+
await this.write('registerImage',
|
|
358
|
+
ipfsHash, "v3", imageName, dockerComposeHash, session, Number(fee), ipfsPeer || "");
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Write the certificate of a registered image, once, from the wallet that
|
|
363
|
+
// registered it. A certificate already there is left as it is. The RPC
|
|
364
|
+
// endpoint balances requests over nodes that can lag the chain by several
|
|
365
|
+
// blocks, and a read can fail outright, so an image this publish
|
|
366
|
+
// registered minutes earlier can read as missing, or be refused as "Image
|
|
367
|
+
// not found" by the simulation: both are tried again every 15 seconds for
|
|
368
|
+
// `waitMs` before the image is called missing.
|
|
369
|
+
async setImageCert(ipfsHash, cert, waitMs = 600000) {
|
|
370
|
+
const deadline = Date.now() + waitMs;
|
|
371
|
+
for (;;) {
|
|
372
|
+
let details = null;
|
|
373
|
+
try {
|
|
374
|
+
details = await this.imageRegistryContract.imageDetails(ipfsHash);
|
|
375
|
+
} catch (e) {
|
|
376
|
+
if (Date.now() >= deadline) throw e;
|
|
377
|
+
}
|
|
378
|
+
if (details && details.owner !== ethers.constants.AddressZero) {
|
|
379
|
+
if (details.certPublicKey) {
|
|
380
|
+
if (details.certPublicKey.trim() === cert.trim()) {
|
|
381
|
+
console.log(`The certificate of ${ipfsHash} is already registered`);
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
throw new Error(`${ipfsHash} already has a certificate, and it differs from the extracted one`);
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
console.log(`Registering the certificate of ${ipfsHash}`);
|
|
388
|
+
await this.write('setImageCert', ipfsHash, cert);
|
|
389
|
+
return true;
|
|
390
|
+
} catch (e) {
|
|
391
|
+
if (!(e.message || '').includes('Image not found') || Date.now() >= deadline) throw e;
|
|
392
|
+
}
|
|
393
|
+
} else if (details && Date.now() >= deadline) {
|
|
394
|
+
throw new Error(`${ipfsHash} is not registered`);
|
|
395
|
+
}
|
|
396
|
+
await new Promise((resolve) => setTimeout(resolve, 15000));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
286
399
|
}
|
|
287
400
|
|
|
288
401
|
(async () => {
|
|
289
402
|
try {
|
|
290
|
-
const [networkName, projectName, version,
|
|
403
|
+
const [networkName, projectName, version, action] = process.argv.slice(2);
|
|
291
404
|
if (action === "validateAddress") {
|
|
292
|
-
if (
|
|
293
|
-
console.log(isStringPrivateKey(
|
|
405
|
+
if (PRIVATE_KEY) {
|
|
406
|
+
console.log(isStringPrivateKey(PRIVATE_KEY));
|
|
294
407
|
}
|
|
295
408
|
process.exit(0);
|
|
296
409
|
}
|
|
@@ -304,8 +417,8 @@ class ImageRegistry {
|
|
|
304
417
|
process.exit(0);
|
|
305
418
|
}
|
|
306
419
|
// The certificate of the trustedzone's own record. getLatestImageVersionPublicKey
|
|
307
|
-
// reads the securelock records instead: ECImageRegistryV2
|
|
308
|
-
// a trustedzone, and the bloxberg mainnet and LitVM registries return a
|
|
420
|
+
// reads the securelock records instead: ECImageRegistryV2 and V3 revert it
|
|
421
|
+
// for a trustedzone, and the bloxberg mainnet and LitVM registries return a
|
|
309
422
|
// different certificate for the same name.
|
|
310
423
|
if (action === 'getTrustedZoneCert') {
|
|
311
424
|
const imageRegistry = new ImageRegistry();
|
|
@@ -328,6 +441,47 @@ class ImageRegistry {
|
|
|
328
441
|
}
|
|
329
442
|
|
|
330
443
|
const imageRegistry = new ImageRegistry();
|
|
444
|
+
// Whether the registry records images before their certificate.
|
|
445
|
+
if (action === 'isV2') {
|
|
446
|
+
try {
|
|
447
|
+
console.log((await imageRegistry.isV2()) ? 'true' : 'false');
|
|
448
|
+
} catch (e) {
|
|
449
|
+
console.error(`Could not read the image registry ${IMAGE_REGISTRY_ADDRESS}: ${e.reason || e.message}`);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
process.exit(0);
|
|
453
|
+
}
|
|
454
|
+
// Record the image before its certificate (V2): IPFS_HASH,
|
|
455
|
+
// IPFS_DOCKER_COMPOSE_HASH, PROJECT_NAME (the name registered),
|
|
456
|
+
// ENCLAVE_NAME_SECURELOCK (the session), DEVELOPER_FEE and IPFS_PEER
|
|
457
|
+
// (the publish's node) come from the environment.
|
|
458
|
+
if (action === 'registerImage') {
|
|
459
|
+
try {
|
|
460
|
+
const rewardAddress = rewardAddressFromEnv();
|
|
461
|
+
await imageRegistry.registerImage(
|
|
462
|
+
process.env.IPFS_HASH || "", process.env.PROJECT_NAME || "",
|
|
463
|
+
process.env.IPFS_DOCKER_COMPOSE_HASH || "", process.env.ENCLAVE_NAME_SECURELOCK || "",
|
|
464
|
+
process.env.DEVELOPER_FEE || "0", process.env.IPFS_PEER || "");
|
|
465
|
+
if (rewardAddress) {
|
|
466
|
+
await imageRegistry.setRewardAddress(process.env.IPFS_HASH || "", rewardAddress);
|
|
467
|
+
}
|
|
468
|
+
} catch (e) {
|
|
469
|
+
console.error(`Could not register the image: ${e.reason || e.message}`);
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
process.exit(0);
|
|
473
|
+
}
|
|
474
|
+
// Write the extracted certificate of a registered image (V2).
|
|
475
|
+
if (action === 'setImageCert') {
|
|
476
|
+
try {
|
|
477
|
+
const cert = fs.readFileSync("./registry/certificate.securelock.crt", 'utf8');
|
|
478
|
+
await imageRegistry.setImageCert(process.env.IPFS_HASH || "", cert);
|
|
479
|
+
} catch (e) {
|
|
480
|
+
console.error(`Could not register the certificate: ${e.reason || e.message}`);
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
process.exit(0);
|
|
484
|
+
}
|
|
331
485
|
if (action === 'registerSecureLockImage') {
|
|
332
486
|
const secureLock = fs.readFileSync("./registry/certificate.securelock.crt", 'utf8');
|
|
333
487
|
// console.log("SECURELOCK:", secureLock);
|
|
@@ -357,7 +511,28 @@ class ImageRegistry {
|
|
|
357
511
|
if (enclaveVersion && enclaveVersion !== "v3") versionKeys.push(enclaveVersion);
|
|
358
512
|
for (const versionKey of versionKeys) {
|
|
359
513
|
console.log(`Registering securelock under version '${versionKey}'`);
|
|
360
|
-
|
|
514
|
+
try {
|
|
515
|
+
await imageRegistry.addSecureLockImageCert(secureLock, ipfsHash, imageName, versionKey, ipfsDockerComposeHash, enclaveNameSecureLock, fee);
|
|
516
|
+
} catch (e) {
|
|
517
|
+
// The registry refuses a hash the (name, version) channel already holds.
|
|
518
|
+
if ((e.message || '').includes('Image hash is already in registry')) {
|
|
519
|
+
console.log(`The securelock is already registered under version '${versionKey}'`);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
console.error(`Could not register the securelock under version '${versionKey}': ${e.reason || e.message}`);
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// REWARD_ADDRESS: where the image's developer fee is paid, when not
|
|
527
|
+
// the publishing wallet.
|
|
528
|
+
try {
|
|
529
|
+
const rewardAddress = rewardAddressFromEnv();
|
|
530
|
+
if (rewardAddress) {
|
|
531
|
+
await imageRegistry.setRewardAddress(ipfsHash, rewardAddress);
|
|
532
|
+
}
|
|
533
|
+
} catch (e) {
|
|
534
|
+
console.error(`Could not set the reward address: ${e.reason || e.message}`);
|
|
535
|
+
process.exit(1);
|
|
361
536
|
}
|
|
362
537
|
process.exit(0);
|
|
363
538
|
}
|
|
@@ -367,6 +542,22 @@ class ImageRegistry {
|
|
|
367
542
|
process.exit(0);
|
|
368
543
|
}
|
|
369
544
|
console.log(`Checking image: '${projectName}' on the ${networkName} blockchain...`);
|
|
545
|
+
// On a registry with name owners the name's owner answers, whether or
|
|
546
|
+
// not an image is certified under the name yet.
|
|
547
|
+
const nameOwner = await imageRegistry.imageNameOwner(projectName);
|
|
548
|
+
if (nameOwner !== null) {
|
|
549
|
+
if (nameOwner === ethers.constants.AddressZero) {
|
|
550
|
+
console.log(`Image: '${projectName}' is available on the ${networkName} blockchain.`);
|
|
551
|
+
process.exit(0);
|
|
552
|
+
}
|
|
553
|
+
if (PRIVATE_KEY && isStringPrivateKey(PRIVATE_KEY) === "OK"
|
|
554
|
+
&& walletAddress(PRIVATE_KEY).toLowerCase() !== nameOwner.toLowerCase()) {
|
|
555
|
+
console.log(`!!! Image: '${projectName}' is owned by '${nameOwner}'.\nYou are not the account holder of the image.\nPlease change the project name and try again.\n`);
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
console.log(`Image: '${projectName}' is owned by '${nameOwner}'.\nIf you are not the account holder, you will not be able to publish your project with the current name. Please change the project name and try again.\n`);
|
|
559
|
+
process.exit(0);
|
|
560
|
+
}
|
|
370
561
|
const imageHash = (await imageRegistry._getLatestImageVersionPublicKey(projectName, version))[0];
|
|
371
562
|
console.log(`Image hash: ${imageHash}`);
|
|
372
563
|
if (!imageHash) {
|
|
@@ -375,10 +566,9 @@ class ImageRegistry {
|
|
|
375
566
|
}
|
|
376
567
|
const imageOwner = (await imageRegistry.getImageDetails(imageHash))[0];
|
|
377
568
|
|
|
378
|
-
if (
|
|
379
|
-
if (isStringPrivateKey(
|
|
380
|
-
|
|
381
|
-
if (imageOwner !== account.address) {
|
|
569
|
+
if (PRIVATE_KEY) {
|
|
570
|
+
if (isStringPrivateKey(PRIVATE_KEY) === "OK") {
|
|
571
|
+
if (imageOwner.toLowerCase() !== walletAddress(PRIVATE_KEY).toLowerCase()) {
|
|
382
572
|
console.log(`!!! Image: '${projectName}' is owned by '${imageOwner}'.\nYou are not the account holder of the image.\nPlease change the project name and try again.\n`);
|
|
383
573
|
process.exit(1);
|
|
384
574
|
}
|
|
@@ -65,14 +65,21 @@ console.log("Docker Composer Hash:", dockerComposerHash);
|
|
|
65
65
|
const submitResponse = await submitIpfsHash(hhash, enclaveName, protocolVersion, network, templateVersion, dockerComposerHash);
|
|
66
66
|
console.log("Submit IPFS Hash Response:", submitResponse);
|
|
67
67
|
|
|
68
|
-
//
|
|
68
|
+
// The service answers publicKey 0 while the image is queued or running
|
|
69
|
+
// (with status, percent and queuePosition), -1 when the extraction failed
|
|
70
|
+
// (with reason) and the certificate when done.
|
|
69
71
|
while (true) {
|
|
70
72
|
const checkResponse = await checkIpfsHashStatus(hhash);
|
|
71
73
|
if ("publicKey" in checkResponse) {
|
|
72
74
|
if (checkResponse.publicKey === 0) {
|
|
73
|
-
|
|
75
|
+
if (checkResponse.status === "running") {
|
|
76
|
+
console.log(`Public key extraction running, ${checkResponse.percent || 0}% done`);
|
|
77
|
+
} else {
|
|
78
|
+
console.log(`Public key not available yet. Queue position: ${checkResponse.queuePosition ?? 'Unknown'}`);
|
|
79
|
+
}
|
|
74
80
|
} else if (checkResponse.publicKey === -1) {
|
|
75
|
-
console.log("
|
|
81
|
+
console.log(`Public key extraction failed: ${checkResponse.reason || "reason not reported by the service"}`);
|
|
82
|
+
console.log("Fix the cause (keep the image pinned, build with the current SDK) and publish again: a new submission of the same hash runs the extraction again.");
|
|
76
83
|
process.exit(1);
|
|
77
84
|
} else {
|
|
78
85
|
console.log("Public Key:", checkResponse.publicKey);
|
|
@@ -83,16 +83,22 @@ if __name__ == "__main__":
|
|
|
83
83
|
)
|
|
84
84
|
print("Submit IPFS Hash Response:", submit_response)
|
|
85
85
|
|
|
86
|
-
#
|
|
86
|
+
# The service answers publicKey 0 while the image is queued or running
|
|
87
|
+
# (with status, percent and queuePosition), -1 when the extraction failed
|
|
88
|
+
# (with reason) and the certificate when done.
|
|
87
89
|
while True:
|
|
88
90
|
check_response = check_ipfs_hash_status(hhash)
|
|
89
91
|
if "publicKey" in check_response:
|
|
90
92
|
if check_response["publicKey"] == 0:
|
|
91
|
-
|
|
92
|
-
f"Public key
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
if check_response.get("status") == "running":
|
|
94
|
+
print(f"Public key extraction running, {check_response.get('percent', 0)}% done")
|
|
95
|
+
else:
|
|
96
|
+
print(
|
|
97
|
+
f"Public key not available yet. Queue position: {check_response.get('queuePosition', 'Unknown')}"
|
|
98
|
+
)
|
|
99
|
+
elif check_response["publicKey"] in (-1, "-1"):
|
|
100
|
+
print(f"Public key extraction failed: {check_response.get('reason', 'reason not reported by the service')}")
|
|
101
|
+
print("Fix the cause (keep the image pinned, build with the current SDK) and publish again: a new submission of the same hash runs the extraction again.")
|
|
96
102
|
exit(1)
|
|
97
103
|
else:
|
|
98
104
|
print("Public Key:", check_response["publicKey"])
|