@ethernity-cloud/sdk-js 1.5.8 → 1.5.9
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/info.js +306 -0
- package/package.json +5 -3
package/info.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ecld-info: everything a developer needs to read or troubleshoot an enclave.
|
|
4
|
+
*
|
|
5
|
+
* Read-only and free (eth_call + event logs -- no task, no gas, no private
|
|
6
|
+
* key). Reads your project's identity from .env (PROJECT_NAME / IPFS_HASH /
|
|
7
|
+
* BLOCKCHAIN_NETWORK), then reports:
|
|
8
|
+
*
|
|
9
|
+
* NETWORK name, type, chain id, RPC, contract addresses, live block
|
|
10
|
+
* TRUSTEDZONE the gatekeeper enclave's Image Registry record
|
|
11
|
+
* SECURELOCK the executor enclave's Image Registry record
|
|
12
|
+
* ESR registry address, total keys, and -- with --enclave <wallet>
|
|
13
|
+
* -- this enclave's recent StateCommitted events
|
|
14
|
+
*
|
|
15
|
+
* Subcommands (default: the full summary):
|
|
16
|
+
* ecld-info full summary
|
|
17
|
+
* ecld-info network just the network section
|
|
18
|
+
* ecld-info trustedzone just the trustedzone registration
|
|
19
|
+
* ecld-info securelock just the securelock registration
|
|
20
|
+
* ecld-info esr address|count|state|version|list ESR inspection
|
|
21
|
+
*
|
|
22
|
+
* Registration is keyed by the image's IPFS hash: pass --ipfs <hash> for an
|
|
23
|
+
* exact record, or --name <n> to resolve the latest registered version of a
|
|
24
|
+
* name. With neither, .env IPFS_HASH / PROJECT_NAME are used.
|
|
25
|
+
*
|
|
26
|
+
* Network: --network NAME, else ESR_NETWORK env, else .env BLOCKCHAIN_NETWORK,
|
|
27
|
+
* else BLOXBERG_TESTNET.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const { ethers } = require('ethers');
|
|
35
|
+
try { require('dotenv').config(); } catch (e) { /* dotenv optional */ }
|
|
36
|
+
|
|
37
|
+
// Authoritative per-network values (mirror of the Python SDK's network table).
|
|
38
|
+
const NETWORKS = {
|
|
39
|
+
BLOXBERG_MAINNET: {
|
|
40
|
+
type: 'mainnet', chainId: 8995, rpc: 'https://core.bloxberg.org',
|
|
41
|
+
protocol: '0x549A6E06BB2084100148D50F51CF77a3436C3Ae7',
|
|
42
|
+
imageRegistry: '0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31',
|
|
43
|
+
esr: '0xF76469A5659670B6ade366dE635e6463aaB8f3D8',
|
|
44
|
+
},
|
|
45
|
+
BLOXBERG_TESTNET: {
|
|
46
|
+
type: 'testnet', chainId: 8995, rpc: 'https://core.bloxberg.org',
|
|
47
|
+
protocol: '0x02882F03097fE8cD31afbdFbB5D72a498B41112c',
|
|
48
|
+
imageRegistry: '0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31',
|
|
49
|
+
esr: '0xda5e68Bb5e68ee14D73b8de2a4D3Ca15736fACfb',
|
|
50
|
+
},
|
|
51
|
+
LITVM_LITEFORGE: {
|
|
52
|
+
type: 'testnet', chainId: 0, rpc: 'https://liteforge.rpc.caldera.xyz/infra-partner-http',
|
|
53
|
+
protocol: '', imageRegistry: '', esr: '0xbAa7F9E3287ff95D177104eD469E6d0Fd19dBB0F',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const IMAGE_REGISTRY_ABI = [
|
|
58
|
+
'function imageDetails(string) view returns (address owner, string ipfsHash, string version, string session, uint256 fee, address rewardAddress, bool validated, bool published, string certPublicKey, string dockerComposeHash, string name)',
|
|
59
|
+
'function trustedZoneImageDetails(string) view returns (address owner, string ipfsHash, string version, string session, uint256 fee, address rewardAddress, bool validated, bool published, string certPublicKey, string dockerComposeHash, string name)',
|
|
60
|
+
'function imageVersions(string, uint256) view returns (string)',
|
|
61
|
+
'function trustedZoneImageVersions(string, uint256) view returns (string)',
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const ESR_ABI = [
|
|
65
|
+
'function getState(address enclave, bytes32 key) view returns (string cid, uint256 version, uint64 updatedAt)',
|
|
66
|
+
'function getVersion(address enclave, bytes32 key) view returns (uint256)',
|
|
67
|
+
'function exists(address enclave, bytes32 key) view returns (bool)',
|
|
68
|
+
'function entryCount() view returns (uint256)',
|
|
69
|
+
'function getEntriesFrom(uint256 start, uint256 limit) view returns (address[] enclaves, bytes32[] keys, string[] cids, uint256[] versions, uint64[] updatedAts, uint256 total)',
|
|
70
|
+
'event StateCommitted(address indexed enclave, bytes32 indexed key, string cid, uint256 version, uint256 seq)',
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
function resolveNetwork(name) {
|
|
74
|
+
name = (name || process.env.ESR_NETWORK || process.env.BLOCKCHAIN_NETWORK || 'BLOXBERG_TESTNET')
|
|
75
|
+
.toUpperCase();
|
|
76
|
+
const net = NETWORKS[name];
|
|
77
|
+
if (!net) {
|
|
78
|
+
throw new Error(`Unknown network '${name}'. Known: ${Object.keys(NETWORKS).join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
return { name, net };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function provider(rpc) {
|
|
84
|
+
return new ethers.providers.JsonRpcProvider(rpc);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function keyHash(key) {
|
|
88
|
+
return ethers.utils.id(key); // keccak256(utf8(key))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function looksLikeCID(cid) {
|
|
92
|
+
return typeof cid === 'string' && (cid.startsWith('Qm') || cid.startsWith('bafk')) && cid.length > 20;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function decodeDetails(d, fallbackName) {
|
|
96
|
+
return {
|
|
97
|
+
name: d.name || fallbackName,
|
|
98
|
+
published: Boolean(d.published),
|
|
99
|
+
validated: Boolean(d.validated),
|
|
100
|
+
image_ipfs_hash: d.ipfsHash || null,
|
|
101
|
+
image_version: d.version || null,
|
|
102
|
+
reward_address: d.rewardAddress,
|
|
103
|
+
owner: d.owner,
|
|
104
|
+
docker_compose_hash: d.dockerComposeHash || null,
|
|
105
|
+
attestation_session: Boolean(d.session),
|
|
106
|
+
cert_present: Boolean(d.certPublicKey),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function lookupRegistration(reg, kind, ipfs, name) {
|
|
111
|
+
const detailsFn = kind === 'trustedzone' ? 'trustedZoneImageDetails' : 'imageDetails';
|
|
112
|
+
const versionsFn = kind === 'trustedzone' ? 'trustedZoneImageVersions' : 'imageVersions';
|
|
113
|
+
if (ipfs) {
|
|
114
|
+
try { return decodeDetails(await reg[detailsFn](ipfs), ipfs); }
|
|
115
|
+
catch (e) { return { found: false, error: e.message }; }
|
|
116
|
+
}
|
|
117
|
+
if (name) {
|
|
118
|
+
let latest = null;
|
|
119
|
+
for (let i = 0; i < 64; i += 1) {
|
|
120
|
+
let v;
|
|
121
|
+
try { v = await reg[versionsFn](name, i); } catch (e) { break; }
|
|
122
|
+
if (!v) break;
|
|
123
|
+
latest = v;
|
|
124
|
+
}
|
|
125
|
+
if (latest) {
|
|
126
|
+
try { return decodeDetails(await reg[detailsFn](latest), name); }
|
|
127
|
+
catch (e) { return { found: false, error: e.message }; }
|
|
128
|
+
}
|
|
129
|
+
return { found: false, note: `no registered version for name '${name}' -- the Image Registry is keyed by image IPFS hash; pass --ipfs <hash>` };
|
|
130
|
+
}
|
|
131
|
+
return { found: false, note: 'no --ipfs or --name to look up' };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function sectionNetwork(prov, name, net) {
|
|
135
|
+
const out = {
|
|
136
|
+
network: name, type: net.type, chain_id: net.chainId, rpc: net.rpc,
|
|
137
|
+
protocol_contract: net.protocol || '(n/a)',
|
|
138
|
+
image_registry: net.imageRegistry || '(n/a)',
|
|
139
|
+
esr_contract: net.esr || '(not deployed)',
|
|
140
|
+
};
|
|
141
|
+
try {
|
|
142
|
+
out.latest_block = await prov.getBlockNumber();
|
|
143
|
+
out.connected = true;
|
|
144
|
+
} catch (e) {
|
|
145
|
+
out.connected = false; out.error = e.message;
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function sectionRegistration(prov, net, kind, ipfs, name) {
|
|
151
|
+
if (!net.imageRegistry) return { note: 'no image registry on this network' };
|
|
152
|
+
const reg = new ethers.Contract(net.imageRegistry, IMAGE_REGISTRY_ABI, prov);
|
|
153
|
+
return lookupRegistration(reg, kind, ipfs, name);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function sectionEsr(prov, net, enclaveWallet, eventsN) {
|
|
157
|
+
if (!net.esr) return { note: 'ESR is not deployed on this network' };
|
|
158
|
+
const esr = new ethers.Contract(net.esr, ESR_ABI, prov);
|
|
159
|
+
const out = { esr_contract: net.esr };
|
|
160
|
+
try { out.total_registry_entries = (await esr.entryCount()).toNumber(); } catch (e) { /* ignore */ }
|
|
161
|
+
if (enclaveWallet) {
|
|
162
|
+
try {
|
|
163
|
+
const latest = await prov.getBlockNumber();
|
|
164
|
+
const from = Math.max(0, latest - 500000);
|
|
165
|
+
const flt = esr.filters.StateCommitted(ethers.utils.getAddress(enclaveWallet));
|
|
166
|
+
let logs = await esr.queryFilter(flt, from, 'latest');
|
|
167
|
+
logs = logs.slice(-eventsN);
|
|
168
|
+
out.recent_commits = logs.map((l) => ({
|
|
169
|
+
key_hash: l.args.key,
|
|
170
|
+
version: l.args.version.toNumber(),
|
|
171
|
+
cid: l.args.cid,
|
|
172
|
+
seq: l.args.seq.toNumber(),
|
|
173
|
+
block: l.blockNumber,
|
|
174
|
+
}));
|
|
175
|
+
} catch (e) {
|
|
176
|
+
out.recent_commits_error = e.message;
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
out.recent_commits_note = 'pass --enclave <ESR wallet> to list this enclave\'s state commits';
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function esrQuery(prov, name, net, args) {
|
|
185
|
+
const esrAddr = args.contract || net.esr;
|
|
186
|
+
const sub = args.esrCmd || 'address';
|
|
187
|
+
if (sub === 'address') return { network: name, esr_contract: esrAddr || '(not deployed)', rpc: net.rpc };
|
|
188
|
+
if (!esrAddr) throw new Error(`ESR is not deployed on ${name}.`);
|
|
189
|
+
const c = new ethers.Contract(esrAddr, ESR_ABI, prov);
|
|
190
|
+
if (sub === 'count') return { network: name, entry_count: (await c.entryCount()).toNumber() };
|
|
191
|
+
if (sub === 'version') {
|
|
192
|
+
const v = (await c.getVersion(ethers.utils.getAddress(args.enclave), keyHash(args.key))).toNumber();
|
|
193
|
+
return { enclave: args.enclave, key: args.key, version: v };
|
|
194
|
+
}
|
|
195
|
+
if (sub === 'state') {
|
|
196
|
+
const enclave = ethers.utils.getAddress(args.enclave);
|
|
197
|
+
const kh = keyHash(args.key);
|
|
198
|
+
const [cid, version, updated] = await c.getState(enclave, kh);
|
|
199
|
+
const exists = await c.exists(enclave, kh);
|
|
200
|
+
return {
|
|
201
|
+
network: name, enclave: args.enclave, key: args.key, key_hash: kh,
|
|
202
|
+
exists, version: version.toNumber(), cid: cid || null, cid_valid: looksLikeCID(cid),
|
|
203
|
+
updated_at: updated.toNumber(),
|
|
204
|
+
note: 'cid points at ENCRYPTED state; only the enclave can decrypt it',
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (sub === 'list') {
|
|
208
|
+
const start = parseInt(args.start || 0, 10);
|
|
209
|
+
const limit = parseInt(args.limit || 50, 10);
|
|
210
|
+
const [encs, keys, cids, versions, updated, total] = await c.getEntriesFrom(start, limit);
|
|
211
|
+
const entries = encs.map((e, i) => ({
|
|
212
|
+
enclave: e, key_hash: keys[i], cid: cids[i] || null,
|
|
213
|
+
version: versions[i].toNumber(), updated_at: updated[i].toNumber(),
|
|
214
|
+
}));
|
|
215
|
+
return { network: name, total: total.toNumber(), start, returned: entries.length, entries };
|
|
216
|
+
}
|
|
217
|
+
throw new Error(`unknown esr subcommand: ${sub}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---- arg parsing (tiny, dependency-free) -----------------------------------
|
|
221
|
+
|
|
222
|
+
function parseArgs(argv) {
|
|
223
|
+
const a = { _: [], json: false };
|
|
224
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
225
|
+
const t = argv[i];
|
|
226
|
+
if (t === '--json') a.json = true;
|
|
227
|
+
else if (t.startsWith('--')) { a[t.slice(2)] = argv[i + 1]; i += 1; }
|
|
228
|
+
else a._.push(t);
|
|
229
|
+
}
|
|
230
|
+
return a;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function emit(obj, asJson) {
|
|
234
|
+
if (asJson) { console.log(JSON.stringify(obj, null, 2)); return; }
|
|
235
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
236
|
+
if (Array.isArray(v)) { console.log(`${k}:`); v.forEach((it) => console.log(' ' + JSON.stringify(it))); }
|
|
237
|
+
else console.log(`${k}: ${v}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function printFull(info) {
|
|
242
|
+
const n = info.network;
|
|
243
|
+
console.log('NETWORK');
|
|
244
|
+
console.log(` ${n.network} (${n.type}, chain ${n.chain_id}) block ${n.latest_block || '?'} ${n.connected ? 'connected' : 'NOT CONNECTED'}`);
|
|
245
|
+
console.log(` rpc: ${n.rpc}`);
|
|
246
|
+
console.log(` protocol: ${n.protocol_contract}`);
|
|
247
|
+
console.log(` image registry: ${n.image_registry}`);
|
|
248
|
+
console.log(` esr: ${n.esr_contract}`);
|
|
249
|
+
for (const [label, key] of [['TRUSTEDZONE', 'trustedzone'], ['SECURELOCK', 'securelock']]) {
|
|
250
|
+
const r = info[key] || {};
|
|
251
|
+
console.log(label);
|
|
252
|
+
if (r.published || r.validated || r.image_ipfs_hash) {
|
|
253
|
+
console.log(` published: ${r.published ? 'yes' : 'no'} validated: ${r.validated ? 'yes' : 'no'}`);
|
|
254
|
+
console.log(` image hash: ${r.image_ipfs_hash}`);
|
|
255
|
+
console.log(` version: ${r.image_version}`);
|
|
256
|
+
console.log(` reward addr: ${r.reward_address}`);
|
|
257
|
+
console.log(` attestation: ${r.attestation_session ? 'MRENCLAVE session present' : 'none'}`);
|
|
258
|
+
} else {
|
|
259
|
+
console.log(` not found (${r.error || r.note || 'not registered'})`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const e = info.esr || {};
|
|
263
|
+
console.log('ESR');
|
|
264
|
+
if (e.note) { console.log(` ${e.note}`); return; }
|
|
265
|
+
console.log(` registry: ${e.esr_contract}`);
|
|
266
|
+
if ('total_registry_entries' in e) console.log(` total keys: ${e.total_registry_entries} (all enclaves)`);
|
|
267
|
+
if (e.recent_commits) {
|
|
268
|
+
console.log(` recent state commits for this enclave: ${e.recent_commits.length}`);
|
|
269
|
+
e.recent_commits.forEach((c) => console.log(` v${c.version} seq ${c.seq} ${c.key_hash} ${c.cid} (block ${c.block})`));
|
|
270
|
+
} else if (e.recent_commits_note) console.log(` ${e.recent_commits_note}`);
|
|
271
|
+
else if (e.recent_commits_error) console.log(` (could not read commits: ${e.recent_commits_error})`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function main() {
|
|
275
|
+
const args = parseArgs(process.argv.slice(2));
|
|
276
|
+
const section = args._[0]; // network | trustedzone | securelock | esr | undefined
|
|
277
|
+
const { name: netName, net } = resolveNetwork(args.network);
|
|
278
|
+
const prov = provider(net.rpc);
|
|
279
|
+
|
|
280
|
+
const ipfs = args.ipfs || process.env.IPFS_HASH || null;
|
|
281
|
+
const projName = args.name || process.env.PROJECT_NAME || null;
|
|
282
|
+
|
|
283
|
+
if (section === 'esr') {
|
|
284
|
+
args.esrCmd = args._[1];
|
|
285
|
+
const obj = await esrQuery(prov, netName, net, args);
|
|
286
|
+
emit(obj, args.json);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (section === 'network') { emit(await sectionNetwork(prov, netName, net), args.json); return; }
|
|
290
|
+
if (section === 'trustedzone') { emit(await sectionRegistration(prov, net, 'trustedzone', ipfs, projName), args.json); return; }
|
|
291
|
+
if (section === 'securelock') { emit(await sectionRegistration(prov, net, 'securelock', ipfs, projName), args.json); return; }
|
|
292
|
+
|
|
293
|
+
const info = {
|
|
294
|
+
project: { name: projName || '(unknown)', dapp_type: process.env.SERVICE_TYPE || process.env.DAPP_TYPE, version: process.env.VERSION },
|
|
295
|
+
network: await sectionNetwork(prov, netName, net),
|
|
296
|
+
trustedzone: await sectionRegistration(prov, net, 'trustedzone', ipfs, projName),
|
|
297
|
+
securelock: await sectionRegistration(prov, net, 'securelock', ipfs, projName),
|
|
298
|
+
esr: await sectionEsr(prov, net, args.enclave || null, parseInt(args.events || 10, 10)),
|
|
299
|
+
};
|
|
300
|
+
if (args.json) { console.log(JSON.stringify(info, null, 2)); return; }
|
|
301
|
+
const pr = info.project;
|
|
302
|
+
console.log(`PROJECT ${pr.name}${pr.dapp_type ? ' type ' + pr.dapp_type : ''}${pr.version != null ? ' version ' + pr.version : ''}`);
|
|
303
|
+
printFull(info);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
main().catch((e) => { console.error(`ecld-info: ${e.message}`); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethernity-cloud/sdk-js",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.9",
|
|
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",
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
"ecld-init": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/init.js",
|
|
9
9
|
"ecld-run": "cross-env ecld-run",
|
|
10
10
|
"postinstall": "node postinstall.js",
|
|
11
|
-
"ecld-test": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/test.js"
|
|
11
|
+
"ecld-test": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/test.js",
|
|
12
|
+
"ecld-info": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/info.js"
|
|
12
13
|
},
|
|
13
14
|
"bin": {
|
|
14
15
|
"ecld-build": "./build.js",
|
|
15
16
|
"ecld-publish": "./publish.js",
|
|
16
17
|
"ecld-init": "./init.js",
|
|
17
18
|
"postinstall": "./postinstall.js",
|
|
18
|
-
"ecld-test": "./test.js"
|
|
19
|
+
"ecld-test": "./test.js",
|
|
20
|
+
"ecld-info": "./info.js"
|
|
19
21
|
},
|
|
20
22
|
"files": [
|
|
21
23
|
"nodenithy/*",
|