@twin.org/node-core 0.0.2-next.3 â 0.0.2-next.5
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/dist/cjs/index.cjs +263 -162
- package/dist/esm/index.mjs +264 -163
- package/dist/types/bootstrap.d.ts +9 -9
- package/dist/types/builders/engineServerEnvBuilder.d.ts +2 -1
- package/dist/types/models/IEngineEnvironmentVariables.d.ts +63 -9
- package/dist/types/models/INodeOptions.d.ts +4 -0
- package/dist/types/models/nodeFeatures.d.ts +4 -0
- package/docs/changelog.md +14 -0
- package/docs/reference/functions/{bootstrapAttestationMethod.md â bootstrapSynchronisedStorage.md} +3 -3
- package/docs/reference/functions/buildEngineServerConfiguration.md +7 -1
- package/docs/reference/index.md +1 -1
- package/docs/reference/interfaces/IEngineEnvironmentVariables.md +133 -15
- package/docs/reference/interfaces/INodeEnvironmentVariables.md +183 -21
- package/docs/reference/interfaces/INodeOptions.md +8 -0
- package/docs/reference/variables/NodeFeatures.md +6 -0
- package/locales/en.json +6 -4
- package/package.json +2 -2
package/dist/cjs/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var apiAuthEntityStorageService = require('@twin.org/api-auth-entity-storage-service');
|
|
4
4
|
var core = require('@twin.org/core');
|
|
5
5
|
var crypto = require('@twin.org/crypto');
|
|
6
|
+
var engineServerTypes = require('@twin.org/engine-server-types');
|
|
6
7
|
var engineTypes = require('@twin.org/engine-types');
|
|
7
8
|
var entityStorageModels = require('@twin.org/entity-storage-models');
|
|
8
9
|
var identityModels = require('@twin.org/identity-models');
|
|
@@ -11,7 +12,6 @@ var walletModels = require('@twin.org/wallet-models');
|
|
|
11
12
|
var promises = require('node:fs/promises');
|
|
12
13
|
var path = require('node:path');
|
|
13
14
|
var engineServer = require('@twin.org/engine-server');
|
|
14
|
-
var engineServerTypes = require('@twin.org/engine-server-types');
|
|
15
15
|
var dotenv = require('dotenv');
|
|
16
16
|
var engine = require('@twin.org/engine');
|
|
17
17
|
var engineCore = require('@twin.org/engine-core');
|
|
@@ -50,7 +50,11 @@ const NodeFeatures = {
|
|
|
50
50
|
/**
|
|
51
51
|
* NodeUser - generates a user for the node if not provided in config.
|
|
52
52
|
*/
|
|
53
|
-
NodeUser: "node-user"
|
|
53
|
+
NodeUser: "node-user",
|
|
54
|
+
/**
|
|
55
|
+
* NodeWallet - generates a wallet for the node and funds it when there is a faucet available.
|
|
56
|
+
*/
|
|
57
|
+
NodeWallet: "node-wallet"
|
|
54
58
|
};
|
|
55
59
|
|
|
56
60
|
// Copyright 2024 IOTA Stiftung.
|
|
@@ -137,8 +141,9 @@ async function bootstrap(engineCore, context, envVars) {
|
|
|
137
141
|
await bootstrapNodeUser(engineCore, context, envVars, features);
|
|
138
142
|
await bootstrapAuth(engineCore, context, envVars);
|
|
139
143
|
await bootstrapBlobEncryption(engineCore, context, envVars);
|
|
140
|
-
await
|
|
141
|
-
await
|
|
144
|
+
await addVerificationMethod(engineCore, context, "attestation", envVars.attestationVerificationMethodId);
|
|
145
|
+
await addVerificationMethod(engineCore, context, "immutable proof", envVars.immutableProofVerificationMethodId);
|
|
146
|
+
await bootstrapSynchronisedStorage(engineCore, context, envVars);
|
|
142
147
|
}
|
|
143
148
|
/**
|
|
144
149
|
* Bootstrap the node creating any necessary resources.
|
|
@@ -153,23 +158,21 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
|
|
|
153
158
|
// But we have a chicken and egg problem in that we can't create the identity
|
|
154
159
|
// to store the mnemonic in the vault without an identity. We use a temporary identity
|
|
155
160
|
// and then replace it with the new identity later in the process.
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}));
|
|
172
|
-
}
|
|
161
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
162
|
+
const vaultConnector = vaultModels.VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
163
|
+
const workingIdentity = envVars.identity ??
|
|
164
|
+
context.state.nodeIdentity ??
|
|
165
|
+
`bootstrap-temp-${core.Converter.bytesToHex(core.RandomHelper.generate(16))}`;
|
|
166
|
+
await bootstrapMnemonic(engineCore, envVars, features, vaultConnector, workingIdentity);
|
|
167
|
+
const addresses = await bootstrapWallet(engineCore, envVars, features, workingIdentity);
|
|
168
|
+
const finalIdentity = await bootstrapIdentity(engineCore, envVars, features, workingIdentity);
|
|
169
|
+
await finaliseWallet(engineCore, envVars, features, finalIdentity, addresses);
|
|
170
|
+
await finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity);
|
|
171
|
+
context.state.nodeIdentity = finalIdentity;
|
|
172
|
+
context.stateDirty = true;
|
|
173
|
+
engineCore.logInfo(core.I18n.formatMessage("node.nodeIdentity", {
|
|
174
|
+
identity: context.state.nodeIdentity
|
|
175
|
+
}));
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
/**
|
|
@@ -181,12 +184,13 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
|
|
|
181
184
|
* @returns The addresses for the wallet.
|
|
182
185
|
*/
|
|
183
186
|
async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
184
|
-
const
|
|
187
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
185
188
|
// Now create an identity for the node controlled by the address we just funded
|
|
186
|
-
const identityConnector = identityModels.IdentityConnectorFactory.get(
|
|
189
|
+
const identityConnector = identityModels.IdentityConnectorFactory.get(defaultIdentityConnectorType);
|
|
187
190
|
let identityDocument;
|
|
188
191
|
try {
|
|
189
|
-
const
|
|
192
|
+
const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
|
|
193
|
+
const identityResolverConnector = identityModels.IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
|
|
190
194
|
identityDocument = await identityResolverConnector.resolveDocument(nodeIdentity);
|
|
191
195
|
engineCore.logInfo(core.I18n.formatMessage("node.existingNodeIdentity", { identity: nodeIdentity }));
|
|
192
196
|
}
|
|
@@ -196,7 +200,7 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
|
196
200
|
identityDocument = await identityConnector.createDocument(nodeIdentity);
|
|
197
201
|
engineCore.logInfo(core.I18n.formatMessage("node.createdNodeIdentity", { identity: identityDocument.id }));
|
|
198
202
|
}
|
|
199
|
-
if (
|
|
203
|
+
if (defaultIdentityConnectorType.startsWith(engineTypes.IdentityConnectorType.Iota)) {
|
|
200
204
|
const didUrn = core.Urn.fromValidString(identityDocument.id);
|
|
201
205
|
const didParts = didUrn.parts();
|
|
202
206
|
const objectId = didParts[3];
|
|
@@ -215,23 +219,26 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
|
215
219
|
* @returns The addresses for the wallet.
|
|
216
220
|
*/
|
|
217
221
|
async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
if (features.includes(NodeFeatures.NodeWallet)) {
|
|
223
|
+
const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
|
|
224
|
+
const walletConnector = walletModels.WalletConnectorFactory.get(defaultWalletConnectorType);
|
|
225
|
+
const addresses = await walletConnector.getAddresses(nodeIdentity, 0, 0, 5);
|
|
226
|
+
const balance = await walletConnector.getBalance(nodeIdentity, addresses[0]);
|
|
227
|
+
if (balance === 0n) {
|
|
228
|
+
let address0 = addresses[0];
|
|
229
|
+
if (defaultWalletConnectorType.startsWith(engineTypes.WalletConnectorType.Iota)) {
|
|
230
|
+
address0 = `${envVars.iotaExplorerEndpoint}address/${address0}?network=${envVars.iotaNetwork}`;
|
|
231
|
+
}
|
|
232
|
+
engineCore.logInfo(core.I18n.formatMessage("node.fundingWallet", { address: address0 }));
|
|
233
|
+
// Add some funds to the wallet from the faucet
|
|
234
|
+
await walletConnector.ensureBalance(nodeIdentity, addresses[0], 1000000000n);
|
|
226
235
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
else {
|
|
232
|
-
engineCore.logInfo(core.I18n.formatMessage("node.fundedWallet"));
|
|
236
|
+
else {
|
|
237
|
+
engineCore.logInfo(core.I18n.formatMessage("node.fundedWallet"));
|
|
238
|
+
}
|
|
239
|
+
return addresses;
|
|
233
240
|
}
|
|
234
|
-
return
|
|
241
|
+
return [];
|
|
235
242
|
}
|
|
236
243
|
/**
|
|
237
244
|
* Bootstrap the identity for the node.
|
|
@@ -242,15 +249,17 @@ async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
|
|
|
242
249
|
* @param addresses The addresses for the wallet.
|
|
243
250
|
*/
|
|
244
251
|
async function finaliseWallet(engineCore, envVars, features, finalIdentity, addresses) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
addr
|
|
253
|
-
|
|
252
|
+
if (features.includes(NodeFeatures.NodeWallet)) {
|
|
253
|
+
const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
|
|
254
|
+
// If we are using entity storage for wallet the identity associated with the
|
|
255
|
+
// address will be wrong, so fix it
|
|
256
|
+
if (defaultWalletConnectorType.startsWith(engineTypes.WalletConnectorType.EntityStorage)) {
|
|
257
|
+
const walletAddress = entityStorageModels.EntityStorageConnectorFactory.get(core.StringHelper.kebabCase("WalletAddress"));
|
|
258
|
+
const addr = await walletAddress.get(addresses[0]);
|
|
259
|
+
if (!core.Is.empty(addr)) {
|
|
260
|
+
addr.identity = finalIdentity;
|
|
261
|
+
await walletAddress.set(addr);
|
|
262
|
+
}
|
|
254
263
|
}
|
|
255
264
|
}
|
|
256
265
|
}
|
|
@@ -312,8 +321,8 @@ async function finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity)
|
|
|
312
321
|
*/
|
|
313
322
|
async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
314
323
|
if (features.includes(NodeFeatures.NodeUser)) {
|
|
315
|
-
const
|
|
316
|
-
if (
|
|
324
|
+
const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceType("authenticationComponent");
|
|
325
|
+
if (defaultAuthenticationComponentType.startsWith(engineServerTypes.AuthenticationComponentType.EntityStorage) &&
|
|
317
326
|
core.Is.stringValue(context.state.nodeIdentity)) {
|
|
318
327
|
const authUserEntityStorage = entityStorageModels.EntityStorageConnectorFactory.get(core.StringHelper.kebabCase("AuthenticationUser"));
|
|
319
328
|
const email = envVars.username ?? DEFAULT_NODE_USERNAME;
|
|
@@ -356,7 +365,8 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
|
356
365
|
}
|
|
357
366
|
}
|
|
358
367
|
// We have create a node user, now we need to create a profile for the user
|
|
359
|
-
const
|
|
368
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
369
|
+
const identityProfileConnector = identityModels.IdentityProfileConnectorFactory.get(defaultIdentityConnectorType);
|
|
360
370
|
if (identityProfileConnector) {
|
|
361
371
|
let userProfile;
|
|
362
372
|
try {
|
|
@@ -386,43 +396,6 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
|
386
396
|
}
|
|
387
397
|
}
|
|
388
398
|
}
|
|
389
|
-
/**
|
|
390
|
-
* Bootstrap the attestation verification methods.
|
|
391
|
-
* @param engineCore The engine core for the node.
|
|
392
|
-
* @param context The context for the node.
|
|
393
|
-
* @param envVars The environment variables for the node.
|
|
394
|
-
* @param features The features that are enabled on the node.
|
|
395
|
-
*/
|
|
396
|
-
async function bootstrapAttestationMethod(engineCore, context, envVars, features) {
|
|
397
|
-
if (core.Is.stringValue(context.state.nodeIdentity) &&
|
|
398
|
-
core.Is.arrayValue(context.config.types.identityConnector) &&
|
|
399
|
-
core.Is.stringValue(envVars.attestationVerificationMethodId)) {
|
|
400
|
-
const engineDefaultTypes = engineCore.getDefaultTypes();
|
|
401
|
-
const identityConnector = identityModels.IdentityConnectorFactory.get(engineDefaultTypes.identityConnector);
|
|
402
|
-
const identityResolverConnector = identityModels.IdentityResolverConnectorFactory.get(engineDefaultTypes.identityResolverConnector);
|
|
403
|
-
const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
|
|
404
|
-
const fullMethodId = `${identityDocument.id}#${envVars.attestationVerificationMethodId}`;
|
|
405
|
-
let createVm = true;
|
|
406
|
-
try {
|
|
407
|
-
identityModels.DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
|
|
408
|
-
createVm = false;
|
|
409
|
-
}
|
|
410
|
-
catch { }
|
|
411
|
-
if (createVm) {
|
|
412
|
-
// Add attestation verification method to DID, the correct node context is now in place
|
|
413
|
-
// so the keys for the verification method will be stored correctly
|
|
414
|
-
engineCore.logInfo(core.I18n.formatMessage("node.addingAttestation", {
|
|
415
|
-
methodId: fullMethodId
|
|
416
|
-
}));
|
|
417
|
-
await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", envVars.attestationVerificationMethodId);
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
engineCore.logInfo(core.I18n.formatMessage("node.existingAttestation", {
|
|
421
|
-
methodId: fullMethodId
|
|
422
|
-
}));
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
399
|
/**
|
|
427
400
|
* Bootstrap the immutable proof verification methods.
|
|
428
401
|
* @param engineCore The engine core for the node.
|
|
@@ -430,36 +403,7 @@ async function bootstrapAttestationMethod(engineCore, context, envVars, features
|
|
|
430
403
|
* @param envVars The environment variables for the node.
|
|
431
404
|
* @param features The features that are enabled on the node.
|
|
432
405
|
*/
|
|
433
|
-
async function bootstrapImmutableProofMethod(engineCore, context, envVars, features) {
|
|
434
|
-
const engineDefaultTypes = engineCore.getDefaultTypes();
|
|
435
|
-
if (core.Is.stringValue(context.state.nodeIdentity) &&
|
|
436
|
-
core.Is.arrayValue(context.config.types.identityConnector) &&
|
|
437
|
-
core.Is.stringValue(envVars.immutableProofVerificationMethodId)) {
|
|
438
|
-
const identityConnector = identityModels.IdentityConnectorFactory.get(engineDefaultTypes.identityConnector);
|
|
439
|
-
const identityResolverConnector = identityModels.IdentityResolverConnectorFactory.get(engineDefaultTypes.identityResolverConnector);
|
|
440
|
-
const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
|
|
441
|
-
const fullMethodId = `${identityDocument.id}#${envVars.immutableProofVerificationMethodId}`;
|
|
442
|
-
let createVm = true;
|
|
443
|
-
try {
|
|
444
|
-
identityModels.DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
|
|
445
|
-
createVm = false;
|
|
446
|
-
}
|
|
447
|
-
catch { }
|
|
448
|
-
if (createVm) {
|
|
449
|
-
// Add AIG verification method to DID, the correct node context is now in place
|
|
450
|
-
// so the keys for the verification method will be stored correctly
|
|
451
|
-
engineCore.logInfo(core.I18n.formatMessage("node.addingImmutableProof", {
|
|
452
|
-
methodId: fullMethodId
|
|
453
|
-
}));
|
|
454
|
-
await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", envVars.immutableProofVerificationMethodId);
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
engineCore.logInfo(core.I18n.formatMessage("node.existingImmutableProof", {
|
|
458
|
-
methodId: fullMethodId
|
|
459
|
-
}));
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
406
|
+
async function bootstrapImmutableProofMethod(engineCore, context, envVars, features) { }
|
|
463
407
|
/**
|
|
464
408
|
* Bootstrap the keys for blob encryption.
|
|
465
409
|
* @param engineCore The engine core for the node.
|
|
@@ -470,18 +414,28 @@ async function bootstrapImmutableProofMethod(engineCore, context, envVars, featu
|
|
|
470
414
|
async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
|
|
471
415
|
if ((core.Coerce.boolean(envVars.blobStorageEnableEncryption) ?? false) &&
|
|
472
416
|
core.Is.stringValue(context.state.nodeIdentity)) {
|
|
473
|
-
const engineDefaultTypes = engineCore.getDefaultTypes();
|
|
474
417
|
// Create a new key for encrypting blobs
|
|
475
|
-
const
|
|
476
|
-
const
|
|
418
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
419
|
+
const vaultConnector = vaultModels.VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
420
|
+
const keyName = `${context.state.nodeIdentity}/${envVars.blobStorageEncryptionKeyId}`;
|
|
477
421
|
let existingKey;
|
|
478
422
|
try {
|
|
479
423
|
existingKey = await vaultConnector.getKey(keyName);
|
|
480
424
|
}
|
|
481
425
|
catch { }
|
|
482
426
|
if (core.Is.empty(existingKey)) {
|
|
483
|
-
|
|
484
|
-
|
|
427
|
+
if (core.Is.stringBase64(envVars.blobStorageSymmetricEncryptionKey)) {
|
|
428
|
+
engineCore.logInfo(core.I18n.formatMessage("node.addingBlobEncryptionKey", { keyName }));
|
|
429
|
+
await vaultConnector.addKey(keyName, vaultModels.VaultKeyType.ChaCha20Poly1305, core.Converter.base64ToBytes(envVars.blobStorageSymmetricEncryptionKey));
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
engineCore.logInfo(core.I18n.formatMessage("node.creatingBlobEncryptionKey", { keyName }));
|
|
433
|
+
const key = await vaultConnector.createKey(keyName, vaultModels.VaultKeyType.ChaCha20Poly1305);
|
|
434
|
+
engineCore.logInfo(core.I18n.formatMessage("node.createdBlobEncryptionKey", {
|
|
435
|
+
keyName,
|
|
436
|
+
keyValue: core.Converter.bytesToBase64(key)
|
|
437
|
+
}));
|
|
438
|
+
}
|
|
485
439
|
}
|
|
486
440
|
else {
|
|
487
441
|
engineCore.logInfo(core.I18n.formatMessage("node.existingBlobEncryptionKey", { keyName }));
|
|
@@ -496,11 +450,13 @@ async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
|
|
|
496
450
|
* @param features The features that are enabled on the node.
|
|
497
451
|
*/
|
|
498
452
|
async function bootstrapAuth(engineCore, context, envVars, features) {
|
|
499
|
-
const
|
|
500
|
-
if (
|
|
453
|
+
const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceTypeOptional("authenticationComponent");
|
|
454
|
+
if (core.Is.stringValue(defaultAuthenticationComponentType) &&
|
|
455
|
+
defaultAuthenticationComponentType.startsWith(engineServerTypes.AuthenticationComponentType.EntityStorage) &&
|
|
501
456
|
core.Is.stringValue(context.state.nodeIdentity)) {
|
|
502
457
|
// Create a new JWT signing key and a user login for the node
|
|
503
|
-
const
|
|
458
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
459
|
+
const vaultConnector = vaultModels.VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
504
460
|
const keyName = `${context.state.nodeIdentity}/${envVars.authSigningKeyId}`;
|
|
505
461
|
let existingKey;
|
|
506
462
|
try {
|
|
@@ -516,6 +472,76 @@ async function bootstrapAuth(engineCore, context, envVars, features) {
|
|
|
516
472
|
}
|
|
517
473
|
}
|
|
518
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* Bootstrap the synchronised storage blob encryption and verification methods.
|
|
477
|
+
* @param engineCore The engine core for the node.
|
|
478
|
+
* @param context The context for the node.
|
|
479
|
+
* @param envVars The environment variables for the node.
|
|
480
|
+
* @param features The features that are enabled on the node.
|
|
481
|
+
*/
|
|
482
|
+
async function bootstrapSynchronisedStorage(engineCore, context, envVars, features) {
|
|
483
|
+
if (core.Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false) {
|
|
484
|
+
// Add the verification method to the identity if it doesn't exist
|
|
485
|
+
await addVerificationMethod(engineCore, context, "synchronised storage", envVars.synchronisedStorageVerificationMethodId);
|
|
486
|
+
// If this is a trusted node we need to add the blob encryption key pair
|
|
487
|
+
if (core.Is.stringValue(envVars.synchronisedStorageBlobStorageEncryptionKeyId) &&
|
|
488
|
+
core.Is.stringBase64(envVars.synchronisedStorageBlobStorageKey)) {
|
|
489
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
490
|
+
const vaultConnector = vaultModels.VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
491
|
+
const keyName = envVars.synchronisedStorageBlobStorageEncryptionKeyId;
|
|
492
|
+
let existingKey;
|
|
493
|
+
try {
|
|
494
|
+
existingKey = await vaultConnector.getKey(keyName);
|
|
495
|
+
}
|
|
496
|
+
catch { }
|
|
497
|
+
if (core.Is.empty(existingKey)) {
|
|
498
|
+
engineCore.logInfo(core.I18n.formatMessage("node.addingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
499
|
+
await vaultConnector.addKey(keyName, vaultModels.VaultKeyType.ChaCha20Poly1305, core.Converter.base64ToBytes(envVars.synchronisedStorageBlobStorageKey));
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
engineCore.logInfo(core.I18n.formatMessage("node.existingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Add a verification method if it doesn't exist.
|
|
509
|
+
* @param engineCore The engine core for the node.
|
|
510
|
+
* @param context The context for the node.
|
|
511
|
+
* @param verificationMethodTitle The verification method title.
|
|
512
|
+
* @param verificationMethodId The verification method ID.
|
|
513
|
+
*/
|
|
514
|
+
async function addVerificationMethod(engineCore, context, verificationMethodTitle, verificationMethodId) {
|
|
515
|
+
if (core.Is.stringValue(context.state.nodeIdentity) &&
|
|
516
|
+
core.Is.arrayValue(context.config.types.identityConnector) &&
|
|
517
|
+
core.Is.stringValue(verificationMethodId)) {
|
|
518
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
519
|
+
const identityConnector = identityModels.IdentityConnectorFactory.get(defaultIdentityConnectorType);
|
|
520
|
+
const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
|
|
521
|
+
const identityResolverConnector = identityModels.IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
|
|
522
|
+
const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
|
|
523
|
+
const fullMethodId = `${identityDocument.id}#${verificationMethodId}`;
|
|
524
|
+
let exists = false;
|
|
525
|
+
try {
|
|
526
|
+
identityModels.DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
|
|
527
|
+
exists = true;
|
|
528
|
+
}
|
|
529
|
+
catch { }
|
|
530
|
+
if (!exists) {
|
|
531
|
+
engineCore.logInfo(core.I18n.formatMessage("node.addingVerificationMethod", {
|
|
532
|
+
title: verificationMethodTitle,
|
|
533
|
+
methodId: fullMethodId
|
|
534
|
+
}));
|
|
535
|
+
await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", verificationMethodId);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
engineCore.logInfo(core.I18n.formatMessage("node.existingVerificationMethod", {
|
|
539
|
+
title: verificationMethodTitle,
|
|
540
|
+
methodId: fullMethodId
|
|
541
|
+
}));
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
519
545
|
|
|
520
546
|
// Copyright 2024 IOTA Stiftung.
|
|
521
547
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -533,7 +559,9 @@ function buildEngineConfiguration(envVars) {
|
|
|
533
559
|
envVars.attestationVerificationMethodId ??= "attestation-assertion";
|
|
534
560
|
envVars.immutableProofVerificationMethodId ??= "immutable-proof-assertion";
|
|
535
561
|
envVars.blobStorageEnableEncryption ??= "false";
|
|
536
|
-
envVars.
|
|
562
|
+
envVars.blobStorageEncryptionKeyId ??= "blob-encryption";
|
|
563
|
+
envVars.synchronisedStorageBlobStorageEncryptionKeyId ??= "synchronised-storage-blob-encryption";
|
|
564
|
+
envVars.synchronisedStorageVerificationMethodId ??= "synchronised-storage-assertion";
|
|
537
565
|
const coreConfig = {
|
|
538
566
|
debug: core.Coerce.boolean(envVars.debug) ?? false,
|
|
539
567
|
types: {}
|
|
@@ -559,9 +587,10 @@ function buildEngineConfiguration(envVars) {
|
|
|
559
587
|
configureAuditableItemGraph(coreConfig);
|
|
560
588
|
configureAuditableItemStream(coreConfig);
|
|
561
589
|
configureDocumentManagement(coreConfig);
|
|
562
|
-
configureFederatedCatalogue(coreConfig, envVars);
|
|
563
590
|
configureRightsManagement(coreConfig, envVars);
|
|
564
591
|
configureTaskScheduler(coreConfig, envVars);
|
|
592
|
+
configureSynchronisedStorage(coreConfig, envVars);
|
|
593
|
+
configureFederatedCatalogue(coreConfig, envVars);
|
|
565
594
|
return coreConfig;
|
|
566
595
|
}
|
|
567
596
|
/**
|
|
@@ -683,7 +712,7 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
683
712
|
}
|
|
684
713
|
});
|
|
685
714
|
}
|
|
686
|
-
if (entityStorageConnectorTypes) {
|
|
715
|
+
if (entityStorageConnectorTypes.includes(engineTypes.EntityStorageConnectorType.PostgreSql)) {
|
|
687
716
|
coreConfig.types.entityStorageConnector.push({
|
|
688
717
|
type: engineTypes.EntityStorageConnectorType.PostgreSql,
|
|
689
718
|
options: {
|
|
@@ -698,10 +727,19 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
698
727
|
}
|
|
699
728
|
});
|
|
700
729
|
}
|
|
730
|
+
const defaultEntityStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
|
|
731
|
+
if (entityStorageConnectorTypes.includes(engineTypes.EntityStorageConnectorType.Synchronised)) {
|
|
732
|
+
// For synchronised storage we use the default connector as the one we wrap for real DB operations
|
|
733
|
+
coreConfig.types.entityStorageConnector.push({
|
|
734
|
+
type: engineTypes.EntityStorageConnectorType.Synchronised,
|
|
735
|
+
options: {
|
|
736
|
+
entityStorageConnectorType: defaultEntityStorageConnectorType
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
}
|
|
701
740
|
if (core.Is.arrayValue(entityStorageConnectorTypes)) {
|
|
702
|
-
const defaultStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
|
|
703
741
|
for (const config of coreConfig.types.entityStorageConnector) {
|
|
704
|
-
if (config.type ===
|
|
742
|
+
if (config.type === defaultEntityStorageConnectorType) {
|
|
705
743
|
config.isDefault = true;
|
|
706
744
|
break;
|
|
707
745
|
}
|
|
@@ -793,6 +831,13 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
793
831
|
for (const config of coreConfig.types.blobStorageConnector) {
|
|
794
832
|
if (config.type === defaultStorageConnectorType) {
|
|
795
833
|
config.isDefault = true;
|
|
834
|
+
}
|
|
835
|
+
// If this blob storage connector is the one to use for public access
|
|
836
|
+
// then add it as a feature
|
|
837
|
+
if (core.Is.stringValue(envVars.blobStorageConnectorPublic) &&
|
|
838
|
+
config.type === envVars.blobStorageConnectorPublic) {
|
|
839
|
+
config.features ??= [];
|
|
840
|
+
config.features.push("public");
|
|
796
841
|
break;
|
|
797
842
|
}
|
|
798
843
|
}
|
|
@@ -804,7 +849,7 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
804
849
|
options: {
|
|
805
850
|
config: {
|
|
806
851
|
vaultKeyId: (envVars.blobStorageEnableEncryption ?? false)
|
|
807
|
-
? envVars.
|
|
852
|
+
? envVars.blobStorageEncryptionKeyId
|
|
808
853
|
: undefined
|
|
809
854
|
}
|
|
810
855
|
}
|
|
@@ -1298,25 +1343,6 @@ function configureDocumentManagement(coreConfig, envVars) {
|
|
|
1298
1343
|
});
|
|
1299
1344
|
}
|
|
1300
1345
|
}
|
|
1301
|
-
/**
|
|
1302
|
-
* Configures the federated catalogue.
|
|
1303
|
-
* @param coreConfig The core config.
|
|
1304
|
-
* @param envVars The environment variables.
|
|
1305
|
-
*/
|
|
1306
|
-
function configureFederatedCatalogue(coreConfig, envVars) {
|
|
1307
|
-
if (core.Is.arrayValue(coreConfig.types.identityResolverComponent)) {
|
|
1308
|
-
coreConfig.types.federatedCatalogueComponent ??= [];
|
|
1309
|
-
coreConfig.types.federatedCatalogueComponent.push({
|
|
1310
|
-
type: engineTypes.FederatedCatalogueComponentType.Service,
|
|
1311
|
-
options: {
|
|
1312
|
-
config: {
|
|
1313
|
-
subResourceCacheTtlMs: core.Coerce.number(envVars.federatedCatalogueCacheTtlMs),
|
|
1314
|
-
clearingHouseApproverList: core.Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
1346
|
/**
|
|
1321
1347
|
* Configures the rights management.
|
|
1322
1348
|
* @param coreConfig The core config.
|
|
@@ -1340,13 +1366,76 @@ function configureRightsManagement(coreConfig, envVars) {
|
|
|
1340
1366
|
* @param envVars The environment variables.
|
|
1341
1367
|
*/
|
|
1342
1368
|
function configureTaskScheduler(coreConfig, envVars) {
|
|
1343
|
-
if (core.Coerce.boolean(envVars.taskSchedulerEnabled) ??
|
|
1369
|
+
if (core.Coerce.boolean(envVars.taskSchedulerEnabled) ?? false) {
|
|
1344
1370
|
coreConfig.types.taskSchedulerComponent ??= [];
|
|
1345
1371
|
coreConfig.types.taskSchedulerComponent.push({
|
|
1346
1372
|
type: engineTypes.TaskSchedulerComponentType.Service
|
|
1347
1373
|
});
|
|
1348
1374
|
}
|
|
1349
1375
|
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Configures the synchronised storage.
|
|
1378
|
+
* @param coreConfig The core config.
|
|
1379
|
+
* @param envVars The environment variables.
|
|
1380
|
+
*/
|
|
1381
|
+
function configureSynchronisedStorage(coreConfig, envVars) {
|
|
1382
|
+
if (core.Is.arrayValue(coreConfig.types.identityResolverComponent) &&
|
|
1383
|
+
(core.Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false)) {
|
|
1384
|
+
// Check if the config provides a custom verifiable storage key id
|
|
1385
|
+
let verifiableStorageKeyId = core.Coerce.string(envVars.synchronisedStorageVerifiableStorageKeyId);
|
|
1386
|
+
if (!core.Is.stringValue(verifiableStorageKeyId)) {
|
|
1387
|
+
// No custom key so default to the network setting
|
|
1388
|
+
verifiableStorageKeyId = envVars.iotaNetwork;
|
|
1389
|
+
}
|
|
1390
|
+
coreConfig.types.synchronisedStorageComponent ??= [];
|
|
1391
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1392
|
+
type: engineTypes.SynchronisedStorageComponentType.Service,
|
|
1393
|
+
options: {
|
|
1394
|
+
config: {
|
|
1395
|
+
verifiableStorageKeyId: verifiableStorageKeyId ?? "",
|
|
1396
|
+
synchronisedStorageMethodId: envVars.synchronisedStorageVerificationMethodId,
|
|
1397
|
+
blobStorageEncryptionKeyId: envVars.synchronisedStorageBlobStorageEncryptionKeyId,
|
|
1398
|
+
entityUpdateIntervalMinutes: core.Coerce.number(envVars.synchronisedStorageEntityUpdateIntervalMinutes),
|
|
1399
|
+
consolidationIntervalMinutes: core.Coerce.number(envVars.synchronisedStorageConsolidationIntervalMinutes),
|
|
1400
|
+
consolidationBatchSize: core.Coerce.number(envVars.synchronisedStorageConsolidationBatchSize),
|
|
1401
|
+
maxConsolidations: core.Coerce.number(envVars.synchronisedStorageMaxConsolidations)
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
// If there is a trusted url set, we need to add a client
|
|
1406
|
+
// and give it a feature of trusted so that when the synchronised
|
|
1407
|
+
// storage is created it can pickup the correct component
|
|
1408
|
+
if (core.Is.stringValue(envVars.synchronisedStorageTrustedUrl)) {
|
|
1409
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1410
|
+
type: engineTypes.SynchronisedStorageComponentType.RestClient,
|
|
1411
|
+
options: {
|
|
1412
|
+
endpoint: envVars.synchronisedStorageTrustedUrl
|
|
1413
|
+
},
|
|
1414
|
+
features: ["trusted"]
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Configures the federated catalogue.
|
|
1421
|
+
* @param coreConfig The core config.
|
|
1422
|
+
* @param envVars The environment variables.
|
|
1423
|
+
*/
|
|
1424
|
+
function configureFederatedCatalogue(coreConfig, envVars) {
|
|
1425
|
+
if (core.Is.arrayValue(coreConfig.types.identityResolverComponent) &&
|
|
1426
|
+
(core.Coerce.boolean(envVars.federatedCatalogueEnabled) ?? false)) {
|
|
1427
|
+
coreConfig.types.federatedCatalogueComponent ??= [];
|
|
1428
|
+
coreConfig.types.federatedCatalogueComponent.push({
|
|
1429
|
+
type: engineTypes.FederatedCatalogueComponentType.Service,
|
|
1430
|
+
options: {
|
|
1431
|
+
config: {
|
|
1432
|
+
subResourceCacheTtlMs: core.Coerce.number(envVars.federatedCatalogueCacheTtlMs),
|
|
1433
|
+
clearingHouseApproverList: core.Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1350
1439
|
/**
|
|
1351
1440
|
* Configures the DLT.
|
|
1352
1441
|
* @param coreConfig The core config.
|
|
@@ -1386,9 +1475,10 @@ function configureDlt(coreConfig, envVars) {
|
|
|
1386
1475
|
* @param coreEngineConfig The core engine config.
|
|
1387
1476
|
* @param serverInfo The server information.
|
|
1388
1477
|
* @param openApiSpecPath The path to the open api spec.
|
|
1478
|
+
* @param favIconPath The path to the favicon.
|
|
1389
1479
|
* @returns The the config for the core and the server.
|
|
1390
1480
|
*/
|
|
1391
|
-
function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, openApiSpecPath) {
|
|
1481
|
+
function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, openApiSpecPath, favIconPath) {
|
|
1392
1482
|
envVars.authSigningKeyId ??= "auth-signing";
|
|
1393
1483
|
const webServerOptions = {
|
|
1394
1484
|
port: core.Coerce.number(envVars.port),
|
|
@@ -1415,7 +1505,8 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
|
|
|
1415
1505
|
options: {
|
|
1416
1506
|
config: {
|
|
1417
1507
|
serverInfo,
|
|
1418
|
-
openApiSpecPath
|
|
1508
|
+
openApiSpecPath,
|
|
1509
|
+
favIconPath
|
|
1419
1510
|
}
|
|
1420
1511
|
}
|
|
1421
1512
|
}
|
|
@@ -1532,9 +1623,10 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
|
|
|
1532
1623
|
* @returns The engine server.
|
|
1533
1624
|
*/
|
|
1534
1625
|
async function start(nodeOptions, engineServerConfig, envVars) {
|
|
1535
|
-
envVars.storageFileRoot ??= "";
|
|
1536
1626
|
const entityStorageConnectorType = envVars.entityStorageConnectorType?.split(",") ?? [];
|
|
1537
1627
|
const blobStorageConnectorType = envVars.blobStorageConnectorType?.split(",") ?? [];
|
|
1628
|
+
// If the blob storage or entity storage is configured with file connectors
|
|
1629
|
+
// then we need to make sure the storageFileRoot is set
|
|
1538
1630
|
if ((entityStorageConnectorType.includes(engineTypes.EntityStorageConnectorType.File) ||
|
|
1539
1631
|
blobStorageConnectorType.includes(engineTypes.BlobStorageConnectorType.File) ||
|
|
1540
1632
|
core.Is.empty(nodeOptions?.stateStorage)) &&
|
|
@@ -1543,7 +1635,7 @@ async function start(nodeOptions, engineServerConfig, envVars) {
|
|
|
1543
1635
|
storageFileRoot: `${nodeOptions?.envPrefix ?? ""}_STORAGE_FILE_ROOT`
|
|
1544
1636
|
});
|
|
1545
1637
|
}
|
|
1546
|
-
// Create the engine instance using file state storage
|
|
1638
|
+
// Create the engine instance using file state storage unless one is configured in options
|
|
1547
1639
|
const engine$1 = new engine.Engine({
|
|
1548
1640
|
config: engineServerConfig,
|
|
1549
1641
|
stateStorage: nodeOptions?.stateStorage ?? new engineCore.FileStateStorage(envVars.stateFilename ?? ""),
|
|
@@ -1587,7 +1679,7 @@ async function run(nodeOptions) {
|
|
|
1587
1679
|
nodeOptions ??= {};
|
|
1588
1680
|
const serverInfo = {
|
|
1589
1681
|
name: nodeOptions?.serverName ?? "TWIN Node Server",
|
|
1590
|
-
version: nodeOptions?.serverVersion ?? "0.0.2-next.
|
|
1682
|
+
version: nodeOptions?.serverVersion ?? "0.0.2-next.5" // x-release-please-version
|
|
1591
1683
|
};
|
|
1592
1684
|
console.log(`\u001B[4mđŠī¸ ${serverInfo.name} v${serverInfo.version}\u001B[24m\n`);
|
|
1593
1685
|
if (!core.Is.stringValue(nodeOptions?.executionDirectory)) {
|
|
@@ -1600,13 +1692,21 @@ async function run(nodeOptions) {
|
|
|
1600
1692
|
console.info("Locales Directory:", nodeOptions.localesDirectory);
|
|
1601
1693
|
await initialiseLocales(nodeOptions.localesDirectory);
|
|
1602
1694
|
if (core.Is.empty(nodeOptions?.openApiSpecFile)) {
|
|
1603
|
-
const specFile = path.resolve(path.join(nodeOptions.executionDirectory, "docs", "open-api", "spec.json"));
|
|
1695
|
+
const specFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "docs", "open-api", "spec.json"));
|
|
1604
1696
|
console.info("Default OpenAPI Spec File:", specFile);
|
|
1605
1697
|
if (await fileExists(specFile)) {
|
|
1606
1698
|
nodeOptions ??= {};
|
|
1607
1699
|
nodeOptions.openApiSpecFile = specFile;
|
|
1608
1700
|
}
|
|
1609
1701
|
}
|
|
1702
|
+
if (core.Is.empty(nodeOptions?.favIconFile)) {
|
|
1703
|
+
const favIconFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "static", "favicon.png"));
|
|
1704
|
+
console.info("Default Favicon File:", favIconFile);
|
|
1705
|
+
if (await fileExists(favIconFile)) {
|
|
1706
|
+
nodeOptions ??= {};
|
|
1707
|
+
nodeOptions.favIconFile = favIconFile;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1610
1710
|
nodeOptions.envPrefix ??= "TWIN_NODE_";
|
|
1611
1711
|
console.info("Environment Prefix:", nodeOptions.envPrefix);
|
|
1612
1712
|
const { engineServerConfig, nodeEnvVars: envVars } = await buildConfiguration(process.env, nodeOptions, serverInfo);
|
|
@@ -1645,7 +1745,8 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1645
1745
|
}
|
|
1646
1746
|
if (core.Is.arrayValue(options?.envFilenames)) {
|
|
1647
1747
|
const output = dotenv__namespace.config({
|
|
1648
|
-
path: options?.envFilenames
|
|
1748
|
+
path: options?.envFilenames,
|
|
1749
|
+
quiet: true
|
|
1649
1750
|
});
|
|
1650
1751
|
// We don't want to throw an error if the default environment file is not found.
|
|
1651
1752
|
// Only if we have custom environment files.
|
|
@@ -1675,7 +1776,7 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1675
1776
|
}
|
|
1676
1777
|
// Build the engine configuration from the environment variables.
|
|
1677
1778
|
const coreConfig = buildEngineConfiguration(envVars);
|
|
1678
|
-
const engineServerConfig = buildEngineServerConfiguration(envVars, coreConfig, serverInfo, options?.openApiSpecFile);
|
|
1779
|
+
const engineServerConfig = buildEngineServerConfiguration(envVars, coreConfig, serverInfo, options?.openApiSpecFile, options?.favIconFile);
|
|
1679
1780
|
// Merge any custom configuration provided in the options.
|
|
1680
1781
|
if (core.Is.arrayValue(options?.configFilenames)) {
|
|
1681
1782
|
for (const configFile of options.configFilenames) {
|
|
@@ -1699,12 +1800,12 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1699
1800
|
|
|
1700
1801
|
exports.NodeFeatures = NodeFeatures;
|
|
1701
1802
|
exports.bootstrap = bootstrap;
|
|
1702
|
-
exports.bootstrapAttestationMethod = bootstrapAttestationMethod;
|
|
1703
1803
|
exports.bootstrapAuth = bootstrapAuth;
|
|
1704
1804
|
exports.bootstrapBlobEncryption = bootstrapBlobEncryption;
|
|
1705
1805
|
exports.bootstrapImmutableProofMethod = bootstrapImmutableProofMethod;
|
|
1706
1806
|
exports.bootstrapNodeIdentity = bootstrapNodeIdentity;
|
|
1707
1807
|
exports.bootstrapNodeUser = bootstrapNodeUser;
|
|
1808
|
+
exports.bootstrapSynchronisedStorage = bootstrapSynchronisedStorage;
|
|
1708
1809
|
exports.buildConfiguration = buildConfiguration;
|
|
1709
1810
|
exports.buildEngineConfiguration = buildEngineConfiguration;
|
|
1710
1811
|
exports.buildEngineServerConfiguration = buildEngineServerConfiguration;
|