@ethernity-cloud/sdk-js 1.7.17 → 1.7.20
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/build/securelock/Dockerfile.tmpl +3 -2
- package/nodenithy/build.mjs +6 -0
- package/nodenithy/run/docker-compose-final.yml.tmpl +6 -5
- package/nodenithy/run/docker-compose.yml.tmpl +8 -7
- package/nodenithy/run/public_key_service.js +33 -12
- package/nodenithy/run.js +22 -5
- package/package.json +1 -1
- package/pynithy/build.mjs +1 -1
- package/pynithy/run/public_key_service.js +33 -12
|
@@ -63,8 +63,9 @@ RUN openssl genrsa -3 -out /enclave-key.pem 3072
|
|
|
63
63
|
# scone-signer below (SCONE embeds defaults for anything not signed explicitly;
|
|
64
64
|
# any drift makes SCONE recompute + debug re-sign the enclave at load, which CAS
|
|
65
65
|
# rejects on mainnet). Values mirror the publish/run docker-compose securelock
|
|
66
|
-
# service (SCONE_HEAP
|
|
67
|
-
|
|
66
|
+
# service (SCONE_HEAP as build.mjs's MEMORY_TO_ALLOCATE, SCONE_STACK=4M,
|
|
67
|
+
# dlopen=1, extensions=libbinary-fs).
|
|
68
|
+
ENV SCONE_HEAP=__MEMORY_TO_ALLOCATE__
|
|
68
69
|
ENV SCONE_STACK=4M
|
|
69
70
|
ENV SCONE_ALLOW_DLOPEN=1
|
|
70
71
|
ENV SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
package/nodenithy/build.mjs
CHANGED
|
@@ -265,7 +265,12 @@ let dockerfileSecureContent = dockerfileSecureTemplate.replace(/__ENCLAVE_NAME_S
|
|
|
265
265
|
|
|
266
266
|
// Amount of enclave heap to allocate (SCONE_HEAP). Kept in sync with the
|
|
267
267
|
// run/docker-compose securelock service; overridable via ECLD_MEMORY_TO_ALLOCATE.
|
|
268
|
+
// 1024M: with 256M or 512M the node runtime exhausts the protected heap
|
|
269
|
+
// reserving its V8 code range at start, before the securelock runs.
|
|
268
270
|
const MEMORY_TO_ALLOCATE = (process.env.ECLD_MEMORY_TO_ALLOCATE || '1024M').trim();
|
|
271
|
+
// Read back by ecld-publish (run.js), which renders it into the composes'
|
|
272
|
+
// securelock service.
|
|
273
|
+
writeEnv('MEMORY_TO_ALLOCATE', MEMORY_TO_ALLOCATE);
|
|
269
274
|
|
|
270
275
|
// CRITICAL (mainnet DCAP): sign /usr/local/bin/node -- the binary the enclave
|
|
271
276
|
// actually EXECUTES (ENTRYPOINT + the run/publish compose command both run
|
|
@@ -297,6 +302,7 @@ const signedMrenclaveStep =
|
|
|
297
302
|
// securelock is signed debug with the same explicit params.
|
|
298
303
|
const cas = casConfig.casProvisioned(templateName, isMainnet);
|
|
299
304
|
dockerfileSecureContent = dockerfileSecureContent
|
|
305
|
+
.replace(/__MEMORY_TO_ALLOCATE__/g, MEMORY_TO_ALLOCATE)
|
|
300
306
|
.replace('__SCONE_SIGN__', `RUN scone-signer sign ${signFlags}${cas ? ' --production' : ''} /usr/local/bin/node`)
|
|
301
307
|
.replace('__SIGNED_MRENCLAVE__', signedMrenclaveStep);
|
|
302
308
|
|
|
@@ -36,9 +36,10 @@ services:
|
|
|
36
36
|
- SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
|
|
37
37
|
- SCONE_LAS_ADDR=las # __CAS_ONLY__
|
|
38
38
|
- SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application # __CAS_ONLY__
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
#
|
|
39
|
+
# Must match the --heap and --stack the securelock binary was signed with
|
|
40
|
+
# (build.mjs), otherwise SCONE recomputes MRENCLAVE at load and re-signs
|
|
41
|
+
# as debug. The heap is the build's MEMORY_TO_ALLOCATE (.env).
|
|
42
|
+
- SCONE_HEAP=__MEMORY_TO_ALLOCATE__
|
|
42
43
|
- SCONE_STACK=4M
|
|
43
44
|
- SCONE_LOG=DEBUG
|
|
44
45
|
- SCONE_ALLOW_DLOPEN=1
|
|
@@ -55,8 +56,8 @@ services:
|
|
|
55
56
|
container_name: etny-trustedzone
|
|
56
57
|
privileged: true
|
|
57
58
|
image: localhost:5000/etny-trustedzone
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
# No command: the image's ENTRYPOINT runs node with the flags the
|
|
60
|
+
# trustedzone was built and measured with (etny-nodenithy Dockerfile.tmpl).
|
|
60
61
|
environment:
|
|
61
62
|
- SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
|
|
62
63
|
- SCONE_LAS_ADDR=las # __CAS_ONLY__
|
|
@@ -39,9 +39,10 @@ services:
|
|
|
39
39
|
- SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
|
|
40
40
|
- SCONE_LAS_ADDR=las # __CAS_ONLY__
|
|
41
41
|
- SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application # __CAS_ONLY__
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
#
|
|
42
|
+
# Must match the --heap and --stack the securelock binary was signed with
|
|
43
|
+
# (build.mjs), otherwise SCONE recomputes MRENCLAVE at load and re-signs
|
|
44
|
+
# as debug. The heap is the build's MEMORY_TO_ALLOCATE (.env).
|
|
45
|
+
- SCONE_HEAP=__MEMORY_TO_ALLOCATE__
|
|
45
46
|
- SCONE_STACK=4M
|
|
46
47
|
- SCONE_LOG=DEBUG
|
|
47
48
|
- SCONE_ALLOW_DLOPEN=1
|
|
@@ -54,14 +55,14 @@ services:
|
|
|
54
55
|
container_name: etny-trustedzone
|
|
55
56
|
privileged: true
|
|
56
57
|
image: localhost:5000/etny-trustedzone
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
# No command: the image's ENTRYPOINT runs node with the flags the
|
|
59
|
+
# trustedzone was built and measured with (etny-nodenithy Dockerfile.tmpl).
|
|
59
60
|
environment:
|
|
60
61
|
- SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
|
|
61
62
|
- SCONE_LAS_ADDR=las # __CAS_ONLY__
|
|
62
63
|
- SCONE_CONFIG_ID=__ENCLAVE_NAME_TRUSTEDZONE__/application # __CAS_ONLY__
|
|
63
|
-
# The heap the trustedzone was
|
|
64
|
-
#
|
|
64
|
+
# The heap the trustedzone was signed with: the SCONE_HEAP of the
|
|
65
|
+
# bundled trustedzone image (run.js reads it from the image).
|
|
65
66
|
- SCONE_HEAP=__TRUSTEDZONE_HEAP__
|
|
66
67
|
- SCONE_LOG=DEBUG
|
|
67
68
|
- SCONE_ALLOW_DLOPEN=1
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// registries) is queued by the service's registry watcher from the
|
|
6
6
|
// ImageRegistered event, with this publish's IPFS peer; the hash is posted to
|
|
7
7
|
// /api/addHash only when that row has not appeared within
|
|
8
|
-
// CHAIN_ROW_GRACE_SECONDS
|
|
8
|
+
// CHAIN_ROW_GRACE_SECONDS, or when the service holds a failed extraction of
|
|
9
|
+
// the image. Other images are posted at once.
|
|
9
10
|
const axios = require('axios');
|
|
10
11
|
const fs = require('fs');
|
|
11
12
|
const { Command } = require('commander');
|
|
@@ -14,6 +15,14 @@ const BASE_URL = "https://publickey.ethernity.cloud";
|
|
|
14
15
|
// The watcher polls the registries every 30 s.
|
|
15
16
|
const CHAIN_ROW_GRACE_SECONDS = 180;
|
|
16
17
|
const POLL_SECONDS = 10;
|
|
18
|
+
// A failed extraction is recorded against the hash with publicKey -1 and a
|
|
19
|
+
// reason, and a new submission of the hash runs it again. The service answers
|
|
20
|
+
// the old record for a moment after a submission, so a failed answer is taken
|
|
21
|
+
// as the outcome only once this long has passed since this publish submitted.
|
|
22
|
+
const RESUBMIT_GRACE_SECONDS = 60;
|
|
23
|
+
|
|
24
|
+
// The service's record of an extraction that failed.
|
|
25
|
+
const failed = (answer) => answer !== null && answer.publicKey === -1;
|
|
17
26
|
|
|
18
27
|
async function submitIpfsHash(hash, enclaveName, protocolVersion, network, templateVersion, dockerComposerHash) {
|
|
19
28
|
const url = `${BASE_URL}/api/addHash`;
|
|
@@ -48,9 +57,10 @@ async function checkIpfsHashStatus(hash) {
|
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
|
|
51
|
-
// Queues the image as described above and returns the service's first
|
|
52
|
-
//
|
|
53
|
-
//
|
|
60
|
+
// Queues the image as described above and returns the service's first answer
|
|
61
|
+
// for it (`answer`, null right after a submission) and whether this publish
|
|
62
|
+
// posted the hash (`submitted`). `submit`, `check`, `sleep` and `now` are the
|
|
63
|
+
// functions above and the clock, replaceable for tests.
|
|
54
64
|
async function queueImage({ hash, chainRegistered, submitArgs, submit = submitIpfsHash, check = checkIpfsHashStatus,
|
|
55
65
|
sleep = (s) => new Promise((resolve) => setTimeout(resolve, s * 1000)), now = () => Date.now() / 1000,
|
|
56
66
|
log = console.log, graceSeconds = CHAIN_ROW_GRACE_SECONDS, pollSeconds = POLL_SECONDS }) {
|
|
@@ -58,7 +68,11 @@ async function queueImage({ hash, chainRegistered, submitArgs, submit = submitIp
|
|
|
58
68
|
const deadline = now() + graceSeconds;
|
|
59
69
|
while (true) {
|
|
60
70
|
const answer = await check(hash);
|
|
61
|
-
if (answer
|
|
71
|
+
if (failed(answer)) {
|
|
72
|
+
log(`The service's previous extraction of this image failed (${answer.reason || "no reason reported"}); submitting the hash again`);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if (answer !== null) return { answer, submitted: false };
|
|
62
76
|
if (now() >= deadline) {
|
|
63
77
|
log(`The certificate service has not picked the registration up from the chain in ${graceSeconds} s; submitting the hash`);
|
|
64
78
|
break;
|
|
@@ -69,7 +83,7 @@ async function queueImage({ hash, chainRegistered, submitArgs, submit = submitIp
|
|
|
69
83
|
}
|
|
70
84
|
const submitResponse = await submit(hash, ...submitArgs);
|
|
71
85
|
log("Submit IPFS Hash Response:", submitResponse);
|
|
72
|
-
return null;
|
|
86
|
+
return { answer: null, submitted: true };
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
async function main() {
|
|
@@ -98,10 +112,12 @@ async function main() {
|
|
|
98
112
|
console.log("Template Version:", templateVersion);
|
|
99
113
|
console.log("Docker Composer Hash:", dockerComposerHash);
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
const queued = await queueImage({
|
|
102
116
|
hash: hhash, chainRegistered: Boolean(options.chainRegistered),
|
|
103
117
|
submitArgs: [enclaveName, protocolVersion, network, templateVersion, dockerComposerHash],
|
|
104
118
|
});
|
|
119
|
+
let checkResponse = queued.answer;
|
|
120
|
+
const submittedAt = queued.submitted ? Date.now() / 1000 : null;
|
|
105
121
|
|
|
106
122
|
// The service answers publicKey 0 while the image is queued or running
|
|
107
123
|
// (with status, percent and queuePosition), -1 when the extraction failed
|
|
@@ -119,10 +135,15 @@ async function main() {
|
|
|
119
135
|
} else {
|
|
120
136
|
console.log(`Public key not available yet. Queue position: ${checkResponse.queuePosition ?? 'Unknown'}`);
|
|
121
137
|
}
|
|
122
|
-
} else if (checkResponse
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
138
|
+
} else if (failed(checkResponse)) {
|
|
139
|
+
if (submittedAt !== null && Date.now() / 1000 - submittedAt < RESUBMIT_GRACE_SECONDS) {
|
|
140
|
+
// The record of the run before this publish's submission.
|
|
141
|
+
console.log("The service still answers its previous record; waiting for the new run");
|
|
142
|
+
} else {
|
|
143
|
+
console.log(`Public key extraction failed: ${checkResponse.reason || "reason not reported by the service"}`);
|
|
144
|
+
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.");
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
126
147
|
} else {
|
|
127
148
|
console.log("Public Key:", checkResponse.publicKey);
|
|
128
149
|
// Save public key to file
|
|
@@ -152,4 +173,4 @@ async function main() {
|
|
|
152
173
|
|
|
153
174
|
if (require.main === module) main();
|
|
154
175
|
|
|
155
|
-
module.exports = { queueImage, CHAIN_ROW_GRACE_SECONDS, POLL_SECONDS };
|
|
176
|
+
module.exports = { queueImage, CHAIN_ROW_GRACE_SECONDS, POLL_SECONDS, RESUBMIT_GRACE_SECONDS };
|
package/nodenithy/run.js
CHANGED
|
@@ -113,12 +113,28 @@ const extractSignedMrenclave = (service) => {
|
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
+
// The heap the trustedzone was signed with: the SCONE_HEAP of the image the
|
|
117
|
+
// build bundled (etny-nodenithy's Dockerfile sets it to the --heap it signs
|
|
118
|
+
// with). The compose must name the same value, or SCONE recomputes the
|
|
119
|
+
// measurement at load and the CAS refuses the enclave.
|
|
120
|
+
const trustedZoneHeapOfImage = (image) => {
|
|
121
|
+
const env = execSync(`docker image inspect ${image} --format "{{range .Config.Env}}{{println .}}{{end}}"`,
|
|
122
|
+
{ stdio: ['pipe', 'pipe', 'pipe'] }).toString();
|
|
123
|
+
const m = env.match(/^SCONE_HEAP=(\S+)$/m);
|
|
124
|
+
if (!m) throw new Error(`${image} carries no SCONE_HEAP in its environment`);
|
|
125
|
+
return m[1];
|
|
126
|
+
};
|
|
127
|
+
|
|
116
128
|
const main = async () => {
|
|
117
129
|
process.env.NODE_NO_WARNINGS = 1
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
const
|
|
130
|
+
const trustedZoneHeap = trustedZoneHeapOfImage('localhost:5000/etny-trustedzone');
|
|
131
|
+
console.log(`Trustedzone heap: ${trustedZoneHeap}`);
|
|
132
|
+
// The securelock heap ecld-build signed it with (part of MRENCLAVE).
|
|
133
|
+
const memory = (process.env.MEMORY_TO_ALLOCATE || '').trim();
|
|
134
|
+
if (!memory) {
|
|
135
|
+
console.error("Error: MEMORY_TO_ALLOCATE is not set in .env; run ecld-build first.");
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
122
138
|
|
|
123
139
|
// Where the image goes. An IPFS API of the application's own
|
|
124
140
|
// (IPFS_ENDPOINT, with IPFS_TOKEN as its Authorization header when set)
|
|
@@ -208,7 +224,8 @@ const main = async () => {
|
|
|
208
224
|
return;
|
|
209
225
|
}
|
|
210
226
|
const content = casConfig.renderCompose(fs.readFileSync(template, 'utf8'), cas, unsafe)
|
|
211
|
-
.replace(/__TRUSTEDZONE_HEAP__/g, trustedZoneHeap)
|
|
227
|
+
.replace(/__TRUSTEDZONE_HEAP__/g, trustedZoneHeap)
|
|
228
|
+
.replace(/__MEMORY_TO_ALLOCATE__/g, memory);
|
|
212
229
|
fs.writeFileSync(file, content, 'utf8');
|
|
213
230
|
});
|
|
214
231
|
|
package/package.json
CHANGED
package/pynithy/build.mjs
CHANGED
|
@@ -156,7 +156,7 @@ const casProvisioned = casConfig.casProvisioned(templateName, isMainnet);
|
|
|
156
156
|
|
|
157
157
|
// Enclave heap (SCONE_HEAP). Part of the measurement: publish runs the
|
|
158
158
|
// securelock with the value recorded here.
|
|
159
|
-
const MEMORY_TO_ALLOCATE = (process.env.ECLD_MEMORY_TO_ALLOCATE || process.env.MEMORY_TO_ALLOCATE || '
|
|
159
|
+
const MEMORY_TO_ALLOCATE = (process.env.ECLD_MEMORY_TO_ALLOCATE || process.env.MEMORY_TO_ALLOCATE || '256M').trim();
|
|
160
160
|
writeEnv('MEMORY_TO_ALLOCATE', MEMORY_TO_ALLOCATE);
|
|
161
161
|
|
|
162
162
|
runCommand('docker pull registry:2');
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// registries) is queued by the service's registry watcher from the
|
|
6
6
|
// ImageRegistered event, with this publish's IPFS peer; the hash is posted to
|
|
7
7
|
// /api/addHash only when that row has not appeared within
|
|
8
|
-
// CHAIN_ROW_GRACE_SECONDS
|
|
8
|
+
// CHAIN_ROW_GRACE_SECONDS, or when the service holds a failed extraction of
|
|
9
|
+
// the image. Other images are posted at once.
|
|
9
10
|
const axios = require('axios');
|
|
10
11
|
const fs = require('fs');
|
|
11
12
|
const { Command } = require('commander');
|
|
@@ -14,6 +15,14 @@ const BASE_URL = "https://publickey.ethernity.cloud";
|
|
|
14
15
|
// The watcher polls the registries every 30 s.
|
|
15
16
|
const CHAIN_ROW_GRACE_SECONDS = 180;
|
|
16
17
|
const POLL_SECONDS = 10;
|
|
18
|
+
// A failed extraction is recorded against the hash with publicKey -1 and a
|
|
19
|
+
// reason, and a new submission of the hash runs it again. The service answers
|
|
20
|
+
// the old record for a moment after a submission, so a failed answer is taken
|
|
21
|
+
// as the outcome only once this long has passed since this publish submitted.
|
|
22
|
+
const RESUBMIT_GRACE_SECONDS = 60;
|
|
23
|
+
|
|
24
|
+
// The service's record of an extraction that failed.
|
|
25
|
+
const failed = (answer) => answer !== null && answer.publicKey === -1;
|
|
17
26
|
|
|
18
27
|
async function submitIpfsHash(hash, enclaveName, protocolVersion, network, templateVersion, dockerComposerHash) {
|
|
19
28
|
const url = `${BASE_URL}/api/addHash`;
|
|
@@ -48,9 +57,10 @@ async function checkIpfsHashStatus(hash) {
|
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
|
|
51
|
-
// Queues the image as described above and returns the service's first
|
|
52
|
-
//
|
|
53
|
-
//
|
|
60
|
+
// Queues the image as described above and returns the service's first answer
|
|
61
|
+
// for it (`answer`, null right after a submission) and whether this publish
|
|
62
|
+
// posted the hash (`submitted`). `submit`, `check`, `sleep` and `now` are the
|
|
63
|
+
// functions above and the clock, replaceable for tests.
|
|
54
64
|
async function queueImage({ hash, chainRegistered, submitArgs, submit = submitIpfsHash, check = checkIpfsHashStatus,
|
|
55
65
|
sleep = (s) => new Promise((resolve) => setTimeout(resolve, s * 1000)), now = () => Date.now() / 1000,
|
|
56
66
|
log = console.log, graceSeconds = CHAIN_ROW_GRACE_SECONDS, pollSeconds = POLL_SECONDS }) {
|
|
@@ -58,7 +68,11 @@ async function queueImage({ hash, chainRegistered, submitArgs, submit = submitIp
|
|
|
58
68
|
const deadline = now() + graceSeconds;
|
|
59
69
|
while (true) {
|
|
60
70
|
const answer = await check(hash);
|
|
61
|
-
if (answer
|
|
71
|
+
if (failed(answer)) {
|
|
72
|
+
log(`The service's previous extraction of this image failed (${answer.reason || "no reason reported"}); submitting the hash again`);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if (answer !== null) return { answer, submitted: false };
|
|
62
76
|
if (now() >= deadline) {
|
|
63
77
|
log(`The certificate service has not picked the registration up from the chain in ${graceSeconds} s; submitting the hash`);
|
|
64
78
|
break;
|
|
@@ -69,7 +83,7 @@ async function queueImage({ hash, chainRegistered, submitArgs, submit = submitIp
|
|
|
69
83
|
}
|
|
70
84
|
const submitResponse = await submit(hash, ...submitArgs);
|
|
71
85
|
log("Submit IPFS Hash Response:", submitResponse);
|
|
72
|
-
return null;
|
|
86
|
+
return { answer: null, submitted: true };
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
async function main() {
|
|
@@ -98,10 +112,12 @@ async function main() {
|
|
|
98
112
|
console.log("Template Version:", templateVersion);
|
|
99
113
|
console.log("Docker Composer Hash:", dockerComposerHash);
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
const queued = await queueImage({
|
|
102
116
|
hash: hhash, chainRegistered: Boolean(options.chainRegistered),
|
|
103
117
|
submitArgs: [enclaveName, protocolVersion, network, templateVersion, dockerComposerHash],
|
|
104
118
|
});
|
|
119
|
+
let checkResponse = queued.answer;
|
|
120
|
+
const submittedAt = queued.submitted ? Date.now() / 1000 : null;
|
|
105
121
|
|
|
106
122
|
// The service answers publicKey 0 while the image is queued or running
|
|
107
123
|
// (with status, percent and queuePosition), -1 when the extraction failed
|
|
@@ -119,10 +135,15 @@ async function main() {
|
|
|
119
135
|
} else {
|
|
120
136
|
console.log(`Public key not available yet. Queue position: ${checkResponse.queuePosition ?? 'Unknown'}`);
|
|
121
137
|
}
|
|
122
|
-
} else if (checkResponse
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
138
|
+
} else if (failed(checkResponse)) {
|
|
139
|
+
if (submittedAt !== null && Date.now() / 1000 - submittedAt < RESUBMIT_GRACE_SECONDS) {
|
|
140
|
+
// The record of the run before this publish's submission.
|
|
141
|
+
console.log("The service still answers its previous record; waiting for the new run");
|
|
142
|
+
} else {
|
|
143
|
+
console.log(`Public key extraction failed: ${checkResponse.reason || "reason not reported by the service"}`);
|
|
144
|
+
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.");
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
126
147
|
} else {
|
|
127
148
|
console.log("Public Key:", checkResponse.publicKey);
|
|
128
149
|
// Save public key to file
|
|
@@ -152,4 +173,4 @@ async function main() {
|
|
|
152
173
|
|
|
153
174
|
if (require.main === module) main();
|
|
154
175
|
|
|
155
|
-
module.exports = { queueImage, CHAIN_ROW_GRACE_SECONDS, POLL_SECONDS };
|
|
176
|
+
module.exports = { queueImage, CHAIN_ROW_GRACE_SECONDS, POLL_SECONDS, RESUBMIT_GRACE_SECONDS };
|