@ethernity-cloud/sdk-js 1.6.2 → 1.7.1
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 +86 -48
- package/cas/config.js +61 -3
- package/info.js +14 -5
- package/init.js +24 -6
- package/network.js +51 -0
- package/nodenithy/build/securelock/Dockerfile.tmpl +34 -32
- package/nodenithy/build/securelock/scripts/binary-fs-build.sh +7 -6
- package/nodenithy/build.mjs +80 -68
- package/nodenithy/run/docker-compose-final.yml.tmpl +36 -31
- package/nodenithy/run/docker-compose.yml.tmpl +34 -45
- package/nodenithy/run/image_registry.js +17 -4
- package/nodenithy/run.js +42 -28
- package/nodenithy/src/ec_helloworld_example.js +46 -42
- package/nodenithy/src/preStart.mjs +41 -51
- package/package.json +6 -3
- package/publish.js +4 -2
- package/pynithy/build/securelock/Dockerfile.base.tpl +13 -0
- package/pynithy/build/securelock/Dockerfile.tpl +131 -0
- package/pynithy/build/securelock/scripts/binary-fs-build.sh +25 -34
- package/pynithy/build/securelock/scripts/pyfreeze.sh +32 -0
- package/pynithy/build/securelock/src/{pox.abi → abi/etnyImplementationV2.abi} +1489 -1489
- package/pynithy/build/securelock/src/abi/image_registry.abi +1 -0
- package/pynithy/build/securelock/src/ecld_state.py +906 -0
- package/pynithy/build/securelock/src/esr.abi +1 -0
- package/pynithy/build/securelock/src/esr_local.py +267 -0
- package/pynithy/build/securelock/src/esr_wallet.py +150 -0
- package/pynithy/build/securelock/src/etny_crypto.py +208 -167
- package/pynithy/build/securelock/src/etny_exec.py +385 -79
- package/pynithy/build/securelock/src/etny_exec_flask.py +535 -0
- package/pynithy/build/securelock/src/etny_exec_serv.py +353 -0
- package/pynithy/build/securelock/src/etny_session_exec.py +245 -0
- package/pynithy/build/securelock/src/get_sgx_report.so +0 -0
- package/pynithy/build/securelock/src/image_registry.abi +1 -1
- package/pynithy/build/securelock/src/key_generation.py +17 -17
- package/pynithy/build/securelock/src/local_api.py +189 -0
- package/pynithy/build/securelock/src/models.py +200 -176
- package/pynithy/build/securelock/src/securelock.py.tmpl +1037 -731
- package/pynithy/build/securelock/src/swift_stream_service.py +206 -186
- package/pynithy/build.mjs +139 -89
- package/pynithy/run/docker-compose-final.yml.tmpl +58 -51
- package/pynithy/run/docker-compose.yml.tmpl +62 -48
- package/pynithy/run/etny-securelock-test.yaml.tpl +4 -1
- package/pynithy/run/image_registry.js +17 -4
- package/pynithy/run.js +44 -28
- package/pynithy/src/ec_helloworld_example.js +46 -42
- package/pynithy/src/preStart.mjs +41 -51
- package/pynithy/src/serverless/Dockerfile.serverless +8 -0
- package/pynithy/src/serverless/requirements.txt +6 -0
- package/run.js +37 -34
- package/nodenithy/build/las/Dockerfile +0 -5
- package/nodenithy/src/preStart.js +0 -45
- package/pynithy/build/docker-compose.yml +0 -70
- package/pynithy/build/las/Dockerfile +0 -11
- package/pynithy/build/las/src/etny_entrypoint.sh +0 -6
- package/pynithy/build/las/src/las_entrypoint.sh +0 -26
- package/pynithy/build/scripts/binary-fs-build.sh +0 -28
- package/pynithy/build/securelock/Dockerfile.tmpl +0 -95
package/pynithy/build.mjs
CHANGED
|
@@ -50,7 +50,12 @@ export const ECRunner = {
|
|
|
50
50
|
'ecld-nodenithy-ethereum-sepolia': ['0x29D3eC870565B6A1510232bd950A8Bc8336f0EB2', '0x55e0ad455Be85162b71a790f00Fc305680E3CE53', 'https://ethereum-sepolia-rpc.publicnode.com', 11155111],
|
|
51
51
|
// --- litvm liteforge testnet (shares sepolia contracts; distinct chain/RPC) ---
|
|
52
52
|
'ecld-pynithy-litvm-testnet': ['0x29D3eC870565B6A1510232bd950A8Bc8336f0EB2', '0x55e0ad455Be85162b71a790f00Fc305680E3CE53', 'https://liteforge.rpc.caldera.xyz/infra-partner-http', 4441],
|
|
53
|
-
'ecld-nodenithy-litvm-testnet': ['0x29D3eC870565B6A1510232bd950A8Bc8336f0EB2', '0x55e0ad455Be85162b71a790f00Fc305680E3CE53', 'https://liteforge.rpc.caldera.xyz/infra-partner-http', 4441]
|
|
53
|
+
'ecld-nodenithy-litvm-testnet': ['0x29D3eC870565B6A1510232bd950A8Bc8336f0EB2', '0x55e0ad455Be85162b71a790f00Fc305680E3CE53', 'https://liteforge.rpc.caldera.xyz/infra-partner-http', 4441],
|
|
54
|
+
// --- the -unsafe networks: the contracts of the network each is named after ---
|
|
55
|
+
'etny-pynithy-testnet-unsafe': ['0x02882F03097fE8cD31afbdFbB5D72a498B41112c', '0x99A84C624C028bdf0a855A1E9E3f2fcf7275B3D8', 'https://bloxberg.ethernity.cloud', 8995],
|
|
56
|
+
'etny-nodenithy-testnet-unsafe': ['0x02882F03097fE8cD31afbdFbB5D72a498B41112c', '0x99A84C624C028bdf0a855A1E9E3f2fcf7275B3D8', 'https://bloxberg.ethernity.cloud', 8995],
|
|
57
|
+
'ecld-pynithy-litvm-testnet-unsafe': ['0x29D3eC870565B6A1510232bd950A8Bc8336f0EB2', '0x55e0ad455Be85162b71a790f00Fc305680E3CE53', 'https://liteforge.rpc.caldera.xyz/infra-partner-http', 4441],
|
|
58
|
+
'ecld-nodenithy-litvm-testnet-unsafe': ['0x29D3eC870565B6A1510232bd950A8Bc8336f0EB2', '0x55e0ad455Be85162b71a790f00Fc305680E3CE53', 'https://liteforge.rpc.caldera.xyz/infra-partner-http', 4441]
|
|
54
59
|
};
|
|
55
60
|
|
|
56
61
|
// ethernity-cas ValidatorRegistry per template, baked into the enclave so it
|
|
@@ -63,6 +68,28 @@ const require = createRequire(import.meta.url);
|
|
|
63
68
|
const casConfig = require('../cas/config.js');
|
|
64
69
|
export const VALIDATOR_REGISTRY = casConfig.VALIDATOR_REGISTRY;
|
|
65
70
|
|
|
71
|
+
// An -unsafe network runs an -unsafe trustedzone and nothing else, and an
|
|
72
|
+
// -unsafe trustedzone runs on no other network: the runner and the node refuse
|
|
73
|
+
// either mismatch, so the build does too.
|
|
74
|
+
const unsafe = casConfig.isUnsafeNetwork(process.env.BLOCKCHAIN_NETWORK);
|
|
75
|
+
if (unsafe !== String(process.env.TRUSTED_ZONE_IMAGE || '').endsWith('-unsafe')) {
|
|
76
|
+
console.error(`ERROR: TRUSTED_ZONE_IMAGE=${process.env.TRUSTED_ZONE_IMAGE} does not run on BLOCKCHAIN_NETWORK=${process.env.BLOCKCHAIN_NETWORK}.`);
|
|
77
|
+
console.error(unsafe
|
|
78
|
+
? ' An -unsafe network runs only a trustedzone whose name ends in -unsafe.'
|
|
79
|
+
: ' A trustedzone whose name ends in -unsafe runs only on an -unsafe network.');
|
|
80
|
+
console.error(' Run ecld-init to choose the network again.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The securelock pipeline (build/securelock: Dockerfile.base.tpl, Dockerfile.tpl,
|
|
85
|
+
// scripts/, src/) is ethernity-cloud-sdk-py's, kept identical to it, and so are
|
|
86
|
+
// the images it builds on: a static SCONE python 3.14 the code is frozen for,
|
|
87
|
+
// and the SCONE 6.0.7 LAS that runs beside it.
|
|
88
|
+
const SDK_REGISTRY = 'registry.ethernity.cloud:443/debuggingdelight/ethernity-cloud-sdk-registry';
|
|
89
|
+
const BASE_IMAGE_REPO = `${SDK_REGISTRY}/sconecuratedimages/apps`;
|
|
90
|
+
const BASE_IMAGE_TAG = 'python-3.14.6-alpine3.24-scone6.0.7';
|
|
91
|
+
const LAS_IMAGE = `${SDK_REGISTRY}/sconecuratedimages/las:scone6.0.7`;
|
|
92
|
+
|
|
66
93
|
const runCommand = (command, canPass = false) => {
|
|
67
94
|
if (shell.exec(command).code !== 0 && !canPass) {
|
|
68
95
|
console.error(`Error executing command: ${command}`);
|
|
@@ -70,25 +97,32 @@ const runCommand = (command, canPass = false) => {
|
|
|
70
97
|
}
|
|
71
98
|
};
|
|
72
99
|
|
|
73
|
-
//
|
|
74
|
-
|
|
100
|
+
// Removes the containers this build and publish create, matched by exact name:
|
|
101
|
+
// `--filter name=` matches a substring, so name=las would also match the LAS of
|
|
102
|
+
// a CAS validator set on the same host.
|
|
103
|
+
const removeContainer = (name) => {
|
|
104
|
+
const ids = shell.exec(`docker ps -a -q --filter "name=^/${name}$"`, { silent: true }).stdout.trim();
|
|
105
|
+
if (ids) {
|
|
106
|
+
runCommand(`docker rm -f ${ids.split('\n').join(' ')}`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
75
110
|
const currentDir = process.cwd();
|
|
76
|
-
// console.log(`current_dir: ${currentDir}`);
|
|
77
111
|
const buildDir = path.join(currentDir, 'node_modules/@ethernity-cloud/sdk-js/pynithy/build');
|
|
78
|
-
|
|
112
|
+
const srcDir = './src/serverless';
|
|
113
|
+
const destDir = path.join(buildDir, 'securelock/src/serverless');
|
|
79
114
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
const dockerRm = shell.exec('docker ps --filter name=registry -q', { silent: true }).stdout.trim();
|
|
89
|
-
if (dockerRm) {
|
|
90
|
-
runCommand(`docker rm ${dockerRm.split('\n').join(' ')} -f`);
|
|
115
|
+
// Fail the build NOW if the backend is missing: an enclave built without one
|
|
116
|
+
// runs every task into "name 'X' is not defined" on-chain.
|
|
117
|
+
if (!fs.existsSync(path.join(srcDir, 'backend.py'))) {
|
|
118
|
+
console.error(`ERROR: ${path.join(srcDir, 'backend.py')} not found.`);
|
|
119
|
+
console.error(' The securelock enclave loads your functions from src/serverless/backend.py.');
|
|
120
|
+
console.error(' Run ecld-init to scaffold it, or create the file before building.');
|
|
121
|
+
process.exit(1);
|
|
91
122
|
}
|
|
123
|
+
|
|
124
|
+
shell.rm('-rf', './registry');
|
|
125
|
+
['registry', 'las', 'etny-securelock', 'etny-trustedzone', 'etny-swift-stream'].forEach(removeContainer);
|
|
92
126
|
const dockerImg = shell.exec('docker images --filter reference="*etny*" -q', { silent: true }).stdout.trim();
|
|
93
127
|
if (dockerImg) {
|
|
94
128
|
runCommand(`docker rmi ${dockerImg.split('\n').join(' ')} -f`);
|
|
@@ -97,93 +131,109 @@ const dockerImgReg = shell.exec('docker images --filter reference="*registry*" -
|
|
|
97
131
|
if (dockerImgReg) {
|
|
98
132
|
runCommand(`docker rmi ${dockerImgReg.split('\n').join(' ')} -f`);
|
|
99
133
|
}
|
|
100
|
-
runCommand(`docker rm registry -f`);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const srcDir = './src/serverless';
|
|
105
|
-
const destDir = path.join(buildDir, 'securelock/src/serverless');
|
|
106
|
-
console.log(`Creating destination directory: ${destDir}`);
|
|
107
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
108
134
|
|
|
109
|
-
console.log(`Copying
|
|
110
|
-
fs.
|
|
111
|
-
|
|
112
|
-
|
|
135
|
+
console.log(`Copying ${srcDir} to ${destDir}`);
|
|
136
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
137
|
+
fs.cpSync(srcDir, destDir, { recursive: true });
|
|
138
|
+
// The securelock Dockerfile pip-installs src/serverless/requirements.txt; a
|
|
139
|
+
// project without one installs nothing extra.
|
|
140
|
+
if (!fs.existsSync(path.join(destDir, 'requirements.txt'))) {
|
|
141
|
+
fs.writeFileSync(path.join(destDir, 'requirements.txt'), '');
|
|
142
|
+
}
|
|
113
143
|
|
|
114
144
|
process.chdir(buildDir);
|
|
115
145
|
|
|
116
146
|
let templateName = process.env.TRUSTED_ZONE_IMAGE || 'etny-pynithy-testnet';
|
|
117
147
|
|
|
118
|
-
const isMainnet =
|
|
148
|
+
const isMainnet = casConfig.isMainnetNetwork(process.env.BLOCKCHAIN_NETWORK);
|
|
149
|
+
// Mainnet, or a testnet with a SessionRegistry: the securelock takes its
|
|
150
|
+
// certificate from a CAS session. Any other testnet's securelock self-signs
|
|
151
|
+
// from MR_ENCLAVE.
|
|
152
|
+
const casProvisioned = casConfig.casProvisioned(templateName, isMainnet);
|
|
119
153
|
|
|
120
|
-
|
|
154
|
+
// Enclave heap (SCONE_HEAP). Part of the measurement: publish runs the
|
|
155
|
+
// securelock with the value recorded here.
|
|
156
|
+
const MEMORY_TO_ALLOCATE = (process.env.ECLD_MEMORY_TO_ALLOCATE || process.env.MEMORY_TO_ALLOCATE || '1024M').trim();
|
|
157
|
+
writeEnv('MEMORY_TO_ALLOCATE', MEMORY_TO_ALLOCATE);
|
|
121
158
|
|
|
122
159
|
runCommand('docker pull registry:2');
|
|
123
160
|
runCommand('docker run -d --restart=always -p 5000:5000 --name registry registry:2');
|
|
124
|
-
// runCommand(`docker login ${process.env.DOCKER_REPO_URL} -u ${process.env.DOCKER_LOGIN} -p ${process.env.DOCKER_PASSWORD}`);
|
|
125
161
|
|
|
126
|
-
|
|
127
|
-
// aleXPRoj-securelock-v3-testnet-0.1.0...
|
|
128
|
-
const ENCLAVE_NAME_SECURELOCK = `${process.env.PROJECT_NAME}-SECURELOCK-V3-${process.env.BLOCKCHAIN_NETWORK.split('_')[1].toLowerCase()}-${VERSION}`.replace(/\//g, '_').replace(/-/g, '_');
|
|
162
|
+
const ENCLAVE_NAME_SECURELOCK = `${process.env.PROJECT_NAME}-SECURELOCK-V3-${casConfig.sessionTag(process.env.BLOCKCHAIN_NETWORK)}-${VERSION}`.replace(/\//g, '_').replace(/-/g, '_');
|
|
129
163
|
console.log(`ENCLAVE_NAME_SECURELOCK: ${ENCLAVE_NAME_SECURELOCK}`);
|
|
130
164
|
writeEnv('ENCLAVE_NAME_SECURELOCK', ENCLAVE_NAME_SECURELOCK);
|
|
131
165
|
|
|
132
|
-
console.log('Building etny-securelock');
|
|
133
166
|
process.chdir('securelock');
|
|
134
|
-
// runCommand(`cat Dockerfile.tmpl | sed s/"__ENCLAVE_NAME_SECURELOCK__"/"${ENCLAVE_NAME_SECURELOCK}"/g > Dockerfile`);
|
|
135
|
-
const dockerfileSecureTemplate = fs.readFileSync('Dockerfile.tmpl', 'utf8');
|
|
136
|
-
let dockerfileSecureContent = dockerfileSecureTemplate.replace(/__ENCLAVE_NAME_SECURELOCK__/g, ENCLAVE_NAME_SECURELOCK).replace(/__BUCKET_NAME__/g, templateName + "-v3").replace(/__SMART_CONTRACT_ADDRESS__/g, ECRunner[templateName][0]).replace(/__IMAGE_REGISTRY_ADDRESS__/g, ECRunner[templateName][1]).replace(/__RPC_URL__/g, ECRunner[templateName][2]).replace(/__CHAIN_ID__/g, ECRunner[templateName][3]).replace(/__TRUSTED_ZONE_IMAGE__/g, templateName).replace(/__VALIDATOR_REGISTRY_ADDRESS__/g, VALIDATOR_REGISTRY[templateName] || '');
|
|
137
|
-
|
|
138
|
-
// Amount of enclave heap to allocate (SCONE_HEAP); overridable, in sync with the
|
|
139
|
-
// run/docker-compose securelock service.
|
|
140
|
-
const MEMORY_TO_ALLOCATE = (process.env.ECLD_MEMORY_TO_ALLOCATE || '1024M').trim();
|
|
141
|
-
|
|
142
|
-
// CRITICAL (mainnet DCAP): sign /usr/local/bin/python -- the binary the enclave
|
|
143
|
-
// actually EXECUTES (ENTRYPOINT + run/publish compose command both run
|
|
144
|
-
// /usr/local/bin/python). /usr/local/bin/python3 is a SEPARATE symlink; signing
|
|
145
|
-
// it leaves the executed binary DEBUG-signed, so SCONE recomputes MRENCLAVE at
|
|
146
|
-
// load and re-signs as debug -> CAS rejects the DCAP quote on mainnet.
|
|
147
|
-
// The enclave-creation params MUST match the runtime env exactly.
|
|
148
|
-
const signFlags =
|
|
149
|
-
`--key=/enclave-key.pem --env --heap=${MEMORY_TO_ALLOCATE} ` +
|
|
150
|
-
`--stack=4M --dlopen=1 --extensions=/lib/libbinary-fs.so`;
|
|
151
167
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (casConfig.casProvisioned(templateName, isMainnet)) {
|
|
164
|
-
dockerfileSecureContent = dockerfileSecureContent
|
|
165
|
-
.replace('__SCONE_SIGN__', `RUN scone-signer sign ${signFlags} --production /usr/local/bin/python`)
|
|
166
|
-
.replace('__SIGNED_MRENCLAVE__', signedMrenclaveStep);
|
|
167
|
-
if (isMainnet) imagesTag = process.env.BLOCKCHAIN_NETWORK.split("_")[0].toLowerCase();
|
|
168
|
+
console.log('Building etny-securelock-base');
|
|
169
|
+
fs.writeFileSync('Dockerfile.base', fs.readFileSync('Dockerfile.base.tpl', 'utf8')
|
|
170
|
+
.replace(/__DOCKER_REPO_URL__/g, BASE_IMAGE_REPO)
|
|
171
|
+
.replace(/__BASE_IMAGE_TAG__/g, BASE_IMAGE_TAG));
|
|
172
|
+
runCommand('docker build -f Dockerfile.base -t etny-securelock-base:latest .');
|
|
173
|
+
|
|
174
|
+
// src/serverless/Dockerfile.serverless builds on etny-securelock-base and adds
|
|
175
|
+
// what the dApp needs at the system level.
|
|
176
|
+
if (fs.existsSync('src/serverless/Dockerfile.serverless')) {
|
|
177
|
+
console.log('Adding customizations from Dockerfile.serverless');
|
|
178
|
+
runCommand('docker build -f src/serverless/Dockerfile.serverless -t etny-securelock-serverless:latest .');
|
|
168
179
|
} else {
|
|
169
|
-
|
|
170
|
-
.replace('__SCONE_SIGN__', '# testnet: non-CAS self-sign (no --production sign at build time)')
|
|
171
|
-
.replace('__SIGNED_MRENCLAVE__', '# testnet: no signed MRENCLAVE (self-signed from MR_ENCLAVE at runtime)');
|
|
180
|
+
runCommand('docker tag etny-securelock-base:latest etny-securelock-serverless:latest');
|
|
172
181
|
}
|
|
173
182
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
// ESR fail-fast gate (RFC §5.4): never build an ESR-enabled enclave with an
|
|
184
|
+
// unresolved registry address. The enclave is SEALED -- a missing value bakes
|
|
185
|
+
// in as empty, and every task then fails at runtime after gas is spent.
|
|
186
|
+
const ESR_ENABLED = /^(1|true|yes)$/i.test(String(process.env.ESR_ENABLED || '').trim());
|
|
187
|
+
const ESR_CONTRACT_ADDRESS = String(process.env.ESR_CONTRACT_ADDRESS || '').trim();
|
|
188
|
+
const ESR_WALLET_ADDRESS = String(process.env.ESR_WALLET_ADDRESS || '').trim();
|
|
189
|
+
if (ESR_ENABLED && !/^0x[0-9a-fA-F]{40}$/.test(ESR_CONTRACT_ADDRESS)) {
|
|
190
|
+
console.error('ERROR: ESR is enabled but ESR_CONTRACT_ADDRESS is not a valid address'
|
|
191
|
+
+ ` (got ${JSON.stringify(ESR_CONTRACT_ADDRESS)}).`);
|
|
192
|
+
console.error(' Set it with ecld-init (ESR step) or ESR_CONTRACT_ADDRESS in .env.');
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
// Rendered only when enabled, so non-ESR images keep byte-identical layers.
|
|
196
|
+
let esrEnvBlock = '';
|
|
197
|
+
if (ESR_ENABLED) {
|
|
198
|
+
esrEnvBlock = `ENV ESR_CONTRACT_ADDRESS=${ESR_CONTRACT_ADDRESS}\n`;
|
|
199
|
+
if (/^0x[0-9a-fA-F]{40}$/.test(ESR_WALLET_ADDRESS)) {
|
|
200
|
+
esrEnvBlock += `ENV ESR_WALLET_ADDRESS=${ESR_WALLET_ADDRESS}\n`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
177
203
|
|
|
204
|
+
// Signs the EXECUTED binary (/usr/local/bin/python, the ENTRYPOINT and the
|
|
205
|
+
// compose command) with the enclave-creation params the runtime env repeats;
|
|
206
|
+
// scone-signer embeds SCONE defaults for anything not passed, and any drift
|
|
207
|
+
// makes SCONE recompute the measurement at load. A CAS-provisioned securelock
|
|
208
|
+
// is signed --production: the CAS session admits production enclaves only. A
|
|
209
|
+
// self-signing testnet securelock is signed debug.
|
|
210
|
+
const signFlags =
|
|
211
|
+
`--key=/enclave-key.pem --env --heap=${MEMORY_TO_ALLOCATE} ` +
|
|
212
|
+
`--stack=4M --dlopen=1 --extensions=/lib/libbinary-fs.so`;
|
|
178
213
|
|
|
179
|
-
|
|
214
|
+
console.log('Building etny-securelock');
|
|
215
|
+
fs.writeFileSync('Dockerfile', fs.readFileSync('Dockerfile.tpl', 'utf8')
|
|
216
|
+
.replace(/__SECURELOCK_SESSION__/g, ENCLAVE_NAME_SECURELOCK)
|
|
217
|
+
.replace(/__BUCKET_NAME__/g, `${templateName}-v3`)
|
|
218
|
+
.replace(/__SMART_CONTRACT_ADDRESS__/g, ECRunner[templateName][0])
|
|
219
|
+
.replace(/__IMAGE_REGISTRY_ADDRESS__/g, ECRunner[templateName][1])
|
|
220
|
+
.replace(/__RPC_URL__/g, ECRunner[templateName][2])
|
|
221
|
+
.replace(/__CHAIN_ID__/g, ECRunner[templateName][3])
|
|
222
|
+
.replace(/__TRUSTED_ZONE_IMAGE__/g, templateName)
|
|
223
|
+
.replace(/__NETWORK_TYPE__/g, isMainnet ? 'mainnet' : 'testnet')
|
|
224
|
+
.replace(/__VALIDATOR_REGISTRY_ADDRESS__/g, VALIDATOR_REGISTRY[templateName] || '')
|
|
225
|
+
.replace(/__MEMORY_TO_ALLOCATE__/g, MEMORY_TO_ALLOCATE)
|
|
226
|
+
.replace(/__ESR_ENV__\n/, esrEnvBlock)
|
|
227
|
+
.replace('__SCONE_ALLOW_DLOPEN__', 'ENV SCONE_ALLOW_DLOPEN=1')
|
|
228
|
+
.replace('__SCONE_SIGN__',
|
|
229
|
+
`RUN scone-signer sign ${signFlags}${casProvisioned ? ' --production' : ''} /usr/local/bin/python`));
|
|
230
|
+
|
|
231
|
+
runCommand(`docker build --build-arg SECURELOCK_SESSION=${ENCLAVE_NAME_SECURELOCK} -t etny-securelock:latest .`);
|
|
180
232
|
runCommand('docker tag etny-securelock localhost:5000/etny-securelock');
|
|
181
233
|
runCommand('docker push localhost:5000/etny-securelock');
|
|
182
|
-
// runCommand('docker save etny-securelock:latest -o etny-securelock.tar');
|
|
183
234
|
process.chdir('..');
|
|
184
235
|
|
|
185
|
-
|
|
186
|
-
writeEnv('ENCLAVE_NAME_TRUSTEDZONE', ENCLAVE_NAME_TRUSTEDZONE);
|
|
236
|
+
writeEnv('ENCLAVE_NAME_TRUSTEDZONE', templateName);
|
|
187
237
|
|
|
188
238
|
// The trustedzone is not built here: the etny-pynithy CI builds, measures and
|
|
189
239
|
// registers it on-chain, and publishes that exact image to the ethernity registry
|
|
@@ -199,6 +249,8 @@ const trustedZoneNetByBlockchain = {
|
|
|
199
249
|
IoTeX_Testnet: 'iotex_testnet',
|
|
200
250
|
Ethereum_Sepolia: 'ethereum_sepolia',
|
|
201
251
|
LitVM_LiteForge: 'litvm_liteforge',
|
|
252
|
+
Bloxberg_Testnet_Unsafe: 'bloxberg_testnet_unsafe',
|
|
253
|
+
LitVM_LiteForge_Unsafe: 'litvm_liteforge_unsafe',
|
|
202
254
|
};
|
|
203
255
|
const trustedZoneNet = trustedZoneNetByBlockchain[process.env.BLOCKCHAIN_NETWORK];
|
|
204
256
|
if (!trustedZoneNet) {
|
|
@@ -206,22 +258,20 @@ if (!trustedZoneNet) {
|
|
|
206
258
|
console.error(` known networks: ${Object.keys(trustedZoneNetByBlockchain).join(', ')}`);
|
|
207
259
|
process.exit(1);
|
|
208
260
|
}
|
|
209
|
-
const trustedZoneImage =
|
|
261
|
+
const trustedZoneImage = `${SDK_REGISTRY}/${templateName}/trustedzone:${trustedZoneNet}`;
|
|
210
262
|
console.log(`Pulling the published trustedzone: ${trustedZoneImage}`);
|
|
211
263
|
runCommand(`docker pull ${trustedZoneImage}`);
|
|
212
264
|
runCommand(`docker tag ${trustedZoneImage} localhost:5000/etny-trustedzone`);
|
|
213
265
|
runCommand('docker push localhost:5000/etny-trustedzone');
|
|
214
266
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
runCommand(
|
|
222
|
-
|
|
223
|
-
runCommand('docker push localhost:5000/etny-las');
|
|
224
|
-
|
|
267
|
+
// Every network but an -unsafe one runs a LAS beside the enclaves; an -unsafe
|
|
268
|
+
// network's compose carries none, and its bundle none either.
|
|
269
|
+
if (!unsafe) {
|
|
270
|
+
console.log('Pulling etny-las');
|
|
271
|
+
runCommand(`docker pull ${LAS_IMAGE}`);
|
|
272
|
+
runCommand(`docker tag ${LAS_IMAGE} localhost:5000/etny-las`);
|
|
273
|
+
runCommand('docker push localhost:5000/etny-las');
|
|
274
|
+
}
|
|
225
275
|
|
|
226
276
|
process.chdir(currentDir);
|
|
227
277
|
runCommand('docker cp registry:/var/lib/registry registry');
|
|
@@ -1,25 +1,34 @@
|
|
|
1
|
+
# The compose a node runs for this dApp; its SCONE_* values match
|
|
2
|
+
# docker-compose.yml.tmpl, which measured the securelock at publish. Tagged
|
|
3
|
+
# lines are kept, untagged, only where they apply (cas/config.js
|
|
4
|
+
# renderCompose): __CAS_ONLY__ serves CAS attestation, __NO_CAS_ONLY__ a
|
|
5
|
+
# securelock that self-signs, and __SAFE_ONLY__ is the LAS, which every
|
|
6
|
+
# network but an -unsafe one runs.
|
|
1
7
|
version: '3.2'
|
|
2
8
|
services:
|
|
3
|
-
las:
|
|
4
|
-
container_name: las
|
|
5
|
-
privileged: true
|
|
6
|
-
image: localhost:5000/etny-las
|
|
7
|
-
entrypoint: "/las_entrypoint.sh"
|
|
8
|
-
# -a 0.0.0.0: without it the LAS binds dual-stack (:::18766) and cannot
|
|
9
|
-
# allocate the socket on a host booted with ipv6.disable=1, so it
|
|
10
|
-
# crash-loops and every task on that node times out with no
|
|
11
|
-
# attestation. The enclaves reach it over IPv4 either way.
|
|
12
|
-
command: "bash -c '/las_entrypoint.sh && /usr/local/bin/las -a 0.0.0.0 | tee /var/log/las.log'"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
las: # __SAFE_ONLY__
|
|
10
|
+
container_name: las # __SAFE_ONLY__
|
|
11
|
+
privileged: true # __SAFE_ONLY__
|
|
12
|
+
image: localhost:5000/etny-las # __SAFE_ONLY__
|
|
13
|
+
entrypoint: "/las_entrypoint.sh" # __SAFE_ONLY__
|
|
14
|
+
# -a 0.0.0.0: without it the LAS binds dual-stack (:::18766) and cannot # __SAFE_ONLY__
|
|
15
|
+
# allocate the socket on a host booted with ipv6.disable=1, so it # __SAFE_ONLY__
|
|
16
|
+
# crash-loops and every task on that node times out with no # __SAFE_ONLY__
|
|
17
|
+
# attestation. The enclaves reach it over IPv4 either way. # __SAFE_ONLY__
|
|
18
|
+
command: "bash -c '/las_entrypoint.sh && /usr/local/bin/las -a 0.0.0.0 | tee /var/log/las.log'" # __SAFE_ONLY__
|
|
19
|
+
restart: unless-stopped # __SAFE_ONLY__
|
|
20
|
+
networks: # __SAFE_ONLY__
|
|
21
|
+
- ethernity # __SAFE_ONLY__
|
|
22
|
+
ports: # __SAFE_ONLY__
|
|
23
|
+
- target: 18766 # __SAFE_ONLY__
|
|
24
|
+
published: 18766 # __SAFE_ONLY__
|
|
25
|
+
protocol: tcp # __SAFE_ONLY__
|
|
26
|
+
mode: host # __SAFE_ONLY__
|
|
27
|
+
healthcheck: # __SAFE_ONLY__
|
|
28
|
+
test: ["CMD", "bash", "-c", "echo > /dev/tcp/127.0.0.1/18766"] # __SAFE_ONLY__
|
|
29
|
+
interval: 5s # __SAFE_ONLY__
|
|
30
|
+
timeout: 3s # __SAFE_ONLY__
|
|
31
|
+
retries: 5 # __SAFE_ONLY__
|
|
23
32
|
|
|
24
33
|
etny-securelock:
|
|
25
34
|
container_name: etny-securelock
|
|
@@ -28,45 +37,43 @@ services:
|
|
|
28
37
|
entrypoint: ""
|
|
29
38
|
command: [ "/usr/local/bin/python", "/etny-securelock/securelock.py" ]
|
|
30
39
|
environment:
|
|
31
|
-
- SCONE_CAS_ADDR=scone-cas.cf
|
|
32
|
-
- SCONE_LAS_ADDR=las
|
|
33
|
-
- SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application
|
|
34
|
-
- SCONE_HEAP=
|
|
35
|
-
# Must match the --stack the securelock binary was signed with (build.mjs),
|
|
36
|
-
# otherwise SCONE recomputes MRENCLAVE at load and re-signs as debug.
|
|
40
|
+
- SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
|
|
41
|
+
- SCONE_LAS_ADDR=las # __CAS_ONLY__
|
|
42
|
+
- SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application # __CAS_ONLY__
|
|
43
|
+
- SCONE_HEAP=__MEMORY_TO_ALLOCATE__
|
|
37
44
|
- SCONE_STACK=4M
|
|
38
|
-
- SCONE_LOG=
|
|
45
|
+
- SCONE_LOG=FATAL
|
|
39
46
|
- SCONE_ALLOW_DLOPEN=1
|
|
40
47
|
- SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
48
|
+
- SCONE_ALPINE=1 # __NO_CAS_ONLY__
|
|
49
|
+
- SCONE_DEBUG=0 # __NO_CAS_ONLY__
|
|
41
50
|
restart: on-failure
|
|
42
|
-
devices:
|
|
43
|
-
- "/dev/sgx_enclave:/dev/sgx_enclave"
|
|
44
51
|
networks:
|
|
45
52
|
- ethernity
|
|
46
|
-
depends_on:
|
|
47
|
-
- las
|
|
53
|
+
depends_on: # __SAFE_ONLY__
|
|
54
|
+
- las # __SAFE_ONLY__
|
|
48
55
|
|
|
49
56
|
etny-trustedzone:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
container_name: etny-trustedzone
|
|
58
|
+
privileged: true
|
|
59
|
+
image: localhost:5000/etny-trustedzone
|
|
60
|
+
entrypoint: ""
|
|
61
|
+
command: [ "/usr/local/bin/python", "/etny-trustedzone/trustedzone.py" ]
|
|
62
|
+
environment:
|
|
63
|
+
- SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
|
|
64
|
+
- SCONE_LAS_ADDR=las # __CAS_ONLY__
|
|
65
|
+
- SCONE_CONFIG_ID=__ENCLAVE_NAME_TRUSTEDZONE__/application # __CAS_ONLY__
|
|
66
|
+
- SCONE_HEAP=256M
|
|
67
|
+
- SCONE_LOG=FATAL
|
|
68
|
+
- SCONE_ALLOW_DLOPEN=1
|
|
69
|
+
- SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
70
|
+
- SCONE_ALPINE=1 # __NO_CAS_ONLY__
|
|
71
|
+
- SCONE_DEBUG=0 # __NO_CAS_ONLY__
|
|
72
|
+
restart: on-failure
|
|
73
|
+
networks:
|
|
74
|
+
- ethernity
|
|
75
|
+
depends_on: # __SAFE_ONLY__
|
|
76
|
+
- las # __SAFE_ONLY__
|
|
70
77
|
networks:
|
|
71
78
|
ethernity:
|
|
72
79
|
external: true
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
# Publish-time compose: used by ecld-publish to compute the
|
|
2
|
-
# (SCONE_HASH=1) and extract
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
1
|
+
# Publish-time compose: used by ecld-publish to compute the securelock
|
|
2
|
+
# MRENCLAVE (SCONE_HASH=1) and extract its certificate. The node runs
|
|
3
|
+
# docker-compose-final.yml. The services are the ones ethernity-cloud-sdk-py
|
|
4
|
+
# runs the same securelock with.
|
|
5
|
+
#
|
|
6
|
+
# Tagged lines are kept, untagged, only where they apply (cas/config.js
|
|
7
|
+
# renderCompose): __CAS_ONLY__ serves CAS attestation (mainnet, or a testnet
|
|
8
|
+
# with a SessionRegistry), __NO_CAS_ONLY__ a securelock that self-signs, and
|
|
9
|
+
# __SAFE_ONLY__ is the LAS, which every network but an -unsafe one runs.
|
|
8
10
|
version: '3.2'
|
|
9
11
|
services:
|
|
10
|
-
las:
|
|
11
|
-
container_name: las
|
|
12
|
-
privileged: true
|
|
13
|
-
image: localhost:5000/etny-las
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
las: # __SAFE_ONLY__
|
|
13
|
+
container_name: las # __SAFE_ONLY__
|
|
14
|
+
privileged: true # __SAFE_ONLY__
|
|
15
|
+
image: localhost:5000/etny-las # __SAFE_ONLY__
|
|
16
|
+
entrypoint: "/las_entrypoint.sh" # __SAFE_ONLY__
|
|
17
|
+
# -a 0.0.0.0: without it the LAS binds dual-stack (:::18766) and cannot # __SAFE_ONLY__
|
|
18
|
+
# allocate the socket on a host booted with ipv6.disable=1, so it # __SAFE_ONLY__
|
|
19
|
+
# crash-loops and every task on that node times out with no # __SAFE_ONLY__
|
|
20
|
+
# attestation. The enclaves reach it over IPv4 either way. # __SAFE_ONLY__
|
|
21
|
+
command: "bash -c '/las_entrypoint.sh && /usr/local/bin/las -a 0.0.0.0 | tee /var/log/las.log'" # __SAFE_ONLY__
|
|
22
|
+
restart: unless-stopped # __SAFE_ONLY__
|
|
23
|
+
ports: # __SAFE_ONLY__
|
|
24
|
+
- target: 18766 # __SAFE_ONLY__
|
|
25
|
+
published: 18766 # __SAFE_ONLY__
|
|
26
|
+
protocol: tcp # __SAFE_ONLY__
|
|
27
|
+
mode: host # __SAFE_ONLY__
|
|
28
|
+
healthcheck: # __SAFE_ONLY__
|
|
29
|
+
test: ["CMD", "bash", "-c", "echo > /dev/tcp/127.0.0.1/18766"] # __SAFE_ONLY__
|
|
30
|
+
interval: 5s # __SAFE_ONLY__
|
|
31
|
+
timeout: 3s # __SAFE_ONLY__
|
|
32
|
+
retries: 5 # __SAFE_ONLY__
|
|
25
33
|
|
|
26
34
|
etny-securelock:
|
|
27
35
|
container_name: etny-securelock
|
|
@@ -30,34 +38,40 @@ services:
|
|
|
30
38
|
entrypoint: ""
|
|
31
39
|
command: [ "/usr/local/bin/python", "/etny-securelock/securelock.py" ]
|
|
32
40
|
environment:
|
|
33
|
-
- SCONE_CAS_ADDR=scone-cas.cf
|
|
34
|
-
- SCONE_LAS_ADDR=las
|
|
35
|
-
- SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
|
|
41
|
+
- SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
|
|
42
|
+
- SCONE_LAS_ADDR=las # __CAS_ONLY__
|
|
43
|
+
- SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application # __CAS_ONLY__
|
|
44
|
+
# SCONE_HEAP and SCONE_STACK are part of MRENCLAVE: the values the
|
|
45
|
+
# securelock was signed with (build.mjs, MEMORY_TO_ALLOCATE).
|
|
46
|
+
- SCONE_HEAP=__MEMORY_TO_ALLOCATE__
|
|
39
47
|
- SCONE_STACK=4M
|
|
40
|
-
- SCONE_LOG=
|
|
48
|
+
- SCONE_LOG=FATAL
|
|
41
49
|
- SCONE_ALLOW_DLOPEN=1
|
|
42
50
|
- SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
- SCONE_ALPINE=1 # __NO_CAS_ONLY__
|
|
52
|
+
- SCONE_DEBUG=0 # __NO_CAS_ONLY__
|
|
53
|
+
restart: on-failure
|
|
54
|
+
depends_on: # __SAFE_ONLY__
|
|
55
|
+
- las # __SAFE_ONLY__
|
|
46
56
|
|
|
47
57
|
etny-trustedzone:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
container_name: etny-trustedzone
|
|
59
|
+
privileged: true
|
|
60
|
+
image: localhost:5000/etny-trustedzone
|
|
61
|
+
entrypoint: ""
|
|
62
|
+
command: [ "/usr/local/bin/python", "/etny-trustedzone/trustedzone.py" ]
|
|
63
|
+
environment:
|
|
64
|
+
- SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
|
|
65
|
+
- SCONE_LAS_ADDR=las # __CAS_ONLY__
|
|
66
|
+
- SCONE_CONFIG_ID=__ENCLAVE_NAME_TRUSTEDZONE__/application # __CAS_ONLY__
|
|
67
|
+
# The heap the trustedzone was measured and registered with
|
|
68
|
+
# (etny-pynithy v3/run/docker-compose.yml).
|
|
69
|
+
- SCONE_HEAP=256M
|
|
70
|
+
- SCONE_LOG=FATAL
|
|
71
|
+
- SCONE_ALLOW_DLOPEN=1
|
|
72
|
+
- SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
73
|
+
- SCONE_ALPINE=1 # __NO_CAS_ONLY__
|
|
74
|
+
- SCONE_DEBUG=0 # __NO_CAS_ONLY__
|
|
75
|
+
restart: on-failure
|
|
76
|
+
depends_on: # __SAFE_ONLY__
|
|
77
|
+
- las # __SAFE_ONLY__
|
|
@@ -20,7 +20,10 @@ services:
|
|
|
20
20
|
command: /usr/local/bin/python /etny-securelock/securelock.py
|
|
21
21
|
pwd: /
|
|
22
22
|
environment:
|
|
23
|
-
|
|
23
|
+
# SCONE enclaves start with no HOME; the Python runtime and several
|
|
24
|
+
# libraries call os.path.expanduser('~') at startup and abort with
|
|
25
|
+
# "$HOME is not defined" before the enclave emits its public key.
|
|
26
|
+
HOME: /tmp
|
|
24
27
|
|
|
25
28
|
images:
|
|
26
29
|
- name: application_image
|
|
@@ -35,6 +35,10 @@ function setVars(network = "") {
|
|
|
35
35
|
GAS = 20000000;
|
|
36
36
|
GAS_PRICE = 1300000010;
|
|
37
37
|
}
|
|
38
|
+
} else if (BLOCKCHAIN_NETWORK.includes("LitVM")) {
|
|
39
|
+
NETWORK_RPC = "https://liteforge.rpc.caldera.xyz/infra-partner-http";
|
|
40
|
+
IMAGE_REGISTRY_ADDRESS = "0x55e0ad455Be85162b71a790f00Fc305680E3CE53";
|
|
41
|
+
CHAIN_ID = 4441;
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -71,7 +75,8 @@ class ImageRegistry {
|
|
|
71
75
|
try {
|
|
72
76
|
this.imageRegistryAbi = this.readContractAbi('image_registry.abi');
|
|
73
77
|
this.imageRegistryAddress = IMAGE_REGISTRY_ADDRESS;
|
|
74
|
-
|
|
78
|
+
// stderr: callers read an action's result from stdout.
|
|
79
|
+
console.error("imageRegistryAddress: ", this.imageRegistryAddress);
|
|
75
80
|
this.provider = new ethers.providers.JsonRpcProvider(NETWORK_RPC);
|
|
76
81
|
|
|
77
82
|
if (PRIVATE_KEY) {
|
|
@@ -298,11 +303,19 @@ class ImageRegistry {
|
|
|
298
303
|
console.log(`${balance} gas`);
|
|
299
304
|
process.exit(0);
|
|
300
305
|
}
|
|
306
|
+
// The certificate of the trustedzone's own record. getLatestImageVersionPublicKey
|
|
307
|
+
// reads the securelock records instead: ECImageRegistryV2 reverts it for
|
|
308
|
+
// a trustedzone, and the bloxberg mainnet and LitVM registries return a
|
|
309
|
+
// different certificate for the same name.
|
|
301
310
|
if (action === 'getTrustedZoneCert') {
|
|
302
311
|
const imageRegistry = new ImageRegistry();
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
312
|
+
try {
|
|
313
|
+
const latest = await imageRegistry.imageRegistryContract.getLatestTrustedZoneImageCertPublicKey(projectName, version);
|
|
314
|
+
console.log(latest[1]);
|
|
315
|
+
} catch (e) {
|
|
316
|
+
console.error(`No trustedzone ${projectName} (${version}) in image registry ${IMAGE_REGISTRY_ADDRESS}: ${e.reason || e.message}`);
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
306
319
|
}
|
|
307
320
|
// The CAS session name recorded for the trustedzone template's latest
|
|
308
321
|
// image: what the compose's SCONE_CONFIG_ID for the trustedzone must name.
|