@abtnode/core 1.8.67 → 1.8.68-beta-500af7e5
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/lib/blocklet/downloader/bundle-downloader.js +6 -1
- package/lib/blocklet/manager/disk.js +158 -64
- package/lib/blocklet/manager/helper/install-from-backup.js +4 -4
- package/lib/blocklet/storage/backup/blocklet-extras.js +15 -19
- package/lib/blocklet/storage/backup/spaces.js +39 -11
- package/lib/blocklet/storage/restore/base.js +10 -9
- package/lib/blocklet/storage/restore/blocklet-extras.js +21 -16
- package/lib/blocklet/storage/restore/blocklets.js +5 -2
- package/lib/blocklet/storage/restore/spaces.js +45 -38
- package/lib/event.js +18 -2
- package/lib/index.js +21 -3
- package/lib/router/helper.js +7 -5
- package/lib/router/index.js +31 -0
- package/lib/states/audit-log.js +5 -3
- package/lib/states/blocklet.js +21 -5
- package/lib/states/node.js +5 -2
- package/lib/util/blocklet.js +111 -19
- package/lib/util/index.js +24 -0
- package/lib/validators/node.js +1 -0
- package/lib/webhook/index.js +1 -1
- package/package.json +26 -27
- /package/lib/{queue.js → util/queue.js} +0 -0
|
@@ -2,6 +2,7 @@ const { EventEmitter } = require('events');
|
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const { fileURLToPath } = require('url');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const pick = require('lodash/pick');
|
|
5
6
|
const cloneDeep = require('lodash/cloneDeep');
|
|
6
7
|
const { toBase58 } = require('@ocap/util');
|
|
7
8
|
|
|
@@ -161,7 +162,11 @@ class BundleDownloader extends EventEmitter {
|
|
|
161
162
|
}
|
|
162
163
|
ctrlStore[rootDid].set(did, cancelCtrl);
|
|
163
164
|
|
|
164
|
-
const headers = context.headers ? cloneDeep(context.headers) : {}
|
|
165
|
+
const headers = pick(context.headers ? cloneDeep(context.headers) : {}, [
|
|
166
|
+
'x-server-did',
|
|
167
|
+
'x-server-public-key',
|
|
168
|
+
'x-server-signature',
|
|
169
|
+
]);
|
|
165
170
|
const exist = (context.downloadTokenList || []).find((x) => x.did === did);
|
|
166
171
|
if (exist) {
|
|
167
172
|
headers['x-download-token'] = exist.token;
|
|
@@ -4,6 +4,7 @@ const fs = require('fs-extra');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const flat = require('flat');
|
|
6
6
|
const get = require('lodash/get');
|
|
7
|
+
const merge = require('lodash/merge');
|
|
7
8
|
const pick = require('lodash/pick');
|
|
8
9
|
const cloneDeep = require('lodash/cloneDeep');
|
|
9
10
|
const semver = require('semver');
|
|
@@ -15,20 +16,18 @@ const { isNFTExpired, getNftExpirationDate } = require('@abtnode/util/lib/nft');
|
|
|
15
16
|
const didDocument = require('@abtnode/util/lib/did-document');
|
|
16
17
|
const { sign } = require('@arcblock/jwt');
|
|
17
18
|
const { isValid: isValidDid } = require('@arcblock/did');
|
|
18
|
-
const { verifyPresentation } = require('@arcblock/vc');
|
|
19
19
|
const { toSvg: createDidLogo } =
|
|
20
20
|
process.env.NODE_ENV !== 'test' ? require('@arcblock/did-motif') : require('@arcblock/did-motif/dist/did-motif.cjs');
|
|
21
21
|
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
22
22
|
const sleep = require('@abtnode/util/lib/sleep');
|
|
23
23
|
|
|
24
24
|
const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
|
|
25
|
-
const { getVcFromPresentation } = require('@abtnode/util/lib/vc');
|
|
26
25
|
const {
|
|
27
|
-
VC_TYPE_BLOCKLET_PURCHASE,
|
|
28
26
|
WHO_CAN_ACCESS,
|
|
29
27
|
SERVER_ROLES,
|
|
30
28
|
WHO_CAN_ACCESS_PREFIX_ROLES,
|
|
31
29
|
BLOCKLET_INSTALL_TYPE,
|
|
30
|
+
NODE_MODES,
|
|
32
31
|
} = require('@abtnode/constant');
|
|
33
32
|
|
|
34
33
|
const getBlockletEngine = require('@blocklet/meta/lib/engine');
|
|
@@ -49,7 +48,7 @@ const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-
|
|
|
49
48
|
const toBlockletDid = require('@blocklet/meta/lib/did');
|
|
50
49
|
const { validateMeta } = require('@blocklet/meta/lib/validate');
|
|
51
50
|
const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
|
|
52
|
-
const { titleSchema,
|
|
51
|
+
const { titleSchema, updateMountPointSchema, environmentNameSchema } = require('@blocklet/meta/lib/schema');
|
|
53
52
|
const hasReservedKey = require('@blocklet/meta/lib/has-reserved-key');
|
|
54
53
|
const Lock = require('@abtnode/util/lib/lock');
|
|
55
54
|
|
|
@@ -111,6 +110,10 @@ const {
|
|
|
111
110
|
validateAppConfig,
|
|
112
111
|
checkDuplicateAppSk,
|
|
113
112
|
checkDuplicateMountPoint,
|
|
113
|
+
validateStore,
|
|
114
|
+
validateInServerless,
|
|
115
|
+
isRotatingAppSk,
|
|
116
|
+
isRotatingAppDid,
|
|
114
117
|
} = require('../../util/blocklet');
|
|
115
118
|
const StoreUtil = require('../../util/store');
|
|
116
119
|
const states = require('../../states');
|
|
@@ -193,13 +196,14 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
193
196
|
/**
|
|
194
197
|
* @param {*} dataDirs generate by ../../util:getDataDirs
|
|
195
198
|
*/
|
|
196
|
-
constructor({ dataDirs, startQueue, installQueue, daemon = false, teamManager }) {
|
|
199
|
+
constructor({ dataDirs, startQueue, installQueue, backupQueue, daemon = false, teamManager }) {
|
|
197
200
|
super();
|
|
198
201
|
|
|
199
202
|
this.dataDirs = dataDirs;
|
|
200
203
|
this.installDir = dataDirs.blocklets;
|
|
201
204
|
this.startQueue = startQueue;
|
|
202
205
|
this.installQueue = installQueue;
|
|
206
|
+
this.backupQueue = backupQueue;
|
|
203
207
|
this.teamManager = teamManager;
|
|
204
208
|
|
|
205
209
|
// cached installed blocklets for performance
|
|
@@ -242,6 +246,8 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
242
246
|
* did: string;
|
|
243
247
|
* title: string;
|
|
244
248
|
* description: string;
|
|
249
|
+
* storeUrl: string;
|
|
250
|
+
* appSk: string;
|
|
245
251
|
* sync: boolean = false; // download synchronously, not use queue
|
|
246
252
|
* delay: number; // push download task to queue after a delay
|
|
247
253
|
* downloadTokenList: Array<{did: string, token: string}>;
|
|
@@ -264,6 +270,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
264
270
|
|
|
265
271
|
const info = await states.node.read();
|
|
266
272
|
|
|
273
|
+
// Note: if you added new header here, please change core/state/lib/blocklet/downloader/bundle-downloader.js to use that header
|
|
267
274
|
context.headers = Object.assign(context?.headers || {}, {
|
|
268
275
|
'x-server-did': info.did,
|
|
269
276
|
'x-server-public-key': info.pk,
|
|
@@ -278,28 +285,31 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
278
285
|
context.startImmediately = !!params.startImmediately;
|
|
279
286
|
}
|
|
280
287
|
|
|
288
|
+
const { appSk } = params;
|
|
289
|
+
|
|
281
290
|
if (type === BLOCKLET_INSTALL_TYPE.URL) {
|
|
282
291
|
const { url, controller, sync, delay } = params;
|
|
283
|
-
return this._installFromUrl({ url, controller, sync, delay }, context);
|
|
292
|
+
return this._installFromUrl({ url, controller, sync, delay, appSk }, context);
|
|
284
293
|
}
|
|
285
294
|
|
|
286
295
|
if (type === BLOCKLET_INSTALL_TYPE.UPLOAD) {
|
|
287
296
|
const { file, did, diffVersion, deleteSet } = params;
|
|
288
|
-
return this._installFromUpload({ file, did, diffVersion, deleteSet,
|
|
297
|
+
return this._installFromUpload({ file, did, diffVersion, deleteSet, appSk }, context);
|
|
289
298
|
}
|
|
290
299
|
|
|
291
300
|
if (type === BLOCKLET_INSTALL_TYPE.STORE) {
|
|
292
301
|
const { did, controller, sync, delay, storeUrl } = params;
|
|
293
|
-
return this._installFromStore({ did, controller, sync, delay, storeUrl }, context);
|
|
302
|
+
return this._installFromStore({ did, controller, sync, delay, storeUrl, appSk }, context);
|
|
294
303
|
}
|
|
295
304
|
|
|
296
305
|
if (type === BLOCKLET_INSTALL_TYPE.CREATE) {
|
|
297
|
-
|
|
306
|
+
const { title, description } = params;
|
|
307
|
+
return this._installFromCreate({ title, description, appSk }, context);
|
|
298
308
|
}
|
|
299
309
|
|
|
300
310
|
if (type === BLOCKLET_INSTALL_TYPE.RESTORE) {
|
|
301
|
-
const { url
|
|
302
|
-
return this._installFromBackup({ url,
|
|
311
|
+
const { url } = params;
|
|
312
|
+
return this._installFromBackup({ url, appSk }, context);
|
|
303
313
|
}
|
|
304
314
|
|
|
305
315
|
// should not be here
|
|
@@ -343,10 +353,16 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
343
353
|
},
|
|
344
354
|
context = {}
|
|
345
355
|
) {
|
|
346
|
-
const mountPoint = await
|
|
356
|
+
const mountPoint = await updateMountPointSchema.validateAsync(tmpMountPoint);
|
|
347
357
|
logger.debug('start install component', { rootDid, mountPoint, url });
|
|
348
358
|
|
|
349
359
|
if (file) {
|
|
360
|
+
// TODO: 如何触发这种场景?
|
|
361
|
+
const info = await states.node.read();
|
|
362
|
+
if (info.mode === NODE_MODES.SERVERLESS) {
|
|
363
|
+
throw new Error("Can't install component in serverless-mode server via upload");
|
|
364
|
+
}
|
|
365
|
+
|
|
350
366
|
return this._installComponentFromUpload({
|
|
351
367
|
rootDid,
|
|
352
368
|
mountPoint,
|
|
@@ -360,6 +376,11 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
360
376
|
}
|
|
361
377
|
|
|
362
378
|
if (url) {
|
|
379
|
+
const info = await states.node.read();
|
|
380
|
+
if (info.mode === NODE_MODES.SERVERLESS) {
|
|
381
|
+
validateStore(info, url);
|
|
382
|
+
}
|
|
383
|
+
|
|
363
384
|
return this._installComponentFromUrl({
|
|
364
385
|
rootDid,
|
|
365
386
|
mountPoint,
|
|
@@ -431,25 +452,6 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
431
452
|
return { isInstalled: !!blocklet, isRunning, blockletDid, isExternal };
|
|
432
453
|
}
|
|
433
454
|
|
|
434
|
-
async installBlockletFromVc({ vcPresentation, challenge }, context) {
|
|
435
|
-
logger.info('Install from vc');
|
|
436
|
-
const vc = getVcFromPresentation(vcPresentation);
|
|
437
|
-
|
|
438
|
-
// FIXME: 这里的 trustedIssuers 相当于相信任何 VC,需要想更安全的方法
|
|
439
|
-
verifyPresentation({ presentation: vcPresentation, trustedIssuers: [get(vc, 'issuer.id')], challenge });
|
|
440
|
-
|
|
441
|
-
if (!vc.type.includes(VC_TYPE_BLOCKLET_PURCHASE)) {
|
|
442
|
-
throw new Error(`Expect ${VC_TYPE_BLOCKLET_PURCHASE} VC type`);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const blockletUrl = get(vc, 'credentialSubject.purchased.blocklet.url');
|
|
446
|
-
const urlObject = new URL(blockletUrl);
|
|
447
|
-
const did = get(vc, 'credentialSubject.purchased.blocklet.id');
|
|
448
|
-
const registry = urlObject.origin;
|
|
449
|
-
|
|
450
|
-
return this._installFromStore({ did, registry }, context);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
455
|
async start({ did, throwOnError, checkHealthImmediately = false, e2eMode = false }, context) {
|
|
454
456
|
logger.info('start blocklet', { did });
|
|
455
457
|
// should check blocklet integrity
|
|
@@ -608,32 +610,67 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
608
610
|
}
|
|
609
611
|
|
|
610
612
|
/**
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
* @param {import('@abtnode/client').BackupToSpacesParams} input
|
|
613
|
+
* FIXME: @wangshijun create audit log for this
|
|
614
|
+
* @param {import('@abtnode/client').RequestBackupToSpacesInput} input
|
|
614
615
|
* @memberof BlockletManager
|
|
615
616
|
*/
|
|
616
|
-
|
|
617
|
-
|
|
617
|
+
// eslint-disable-next-line no-unused-vars
|
|
618
|
+
async backupToSpaces({ did }, context) {
|
|
619
|
+
// add to queue
|
|
620
|
+
// const ticket = this.backupQueue.push({
|
|
621
|
+
// entity: 'blocklet',
|
|
622
|
+
// action: 'backup-to-space',
|
|
623
|
+
// did,
|
|
624
|
+
// context,
|
|
625
|
+
// });
|
|
626
|
+
|
|
627
|
+
// ticket.on('failed', async (err) => {
|
|
628
|
+
// logger.error('backup failed', { entity: 'blocklet', did, error: err });
|
|
629
|
+
// this.emit('blocklet.backup.failed', { did, err });
|
|
630
|
+
// this._createNotification(did, {
|
|
631
|
+
// title: 'Blocklet Backup Failed',
|
|
632
|
+
// description: `Blocklet backup failed with error: ${err.message || 'queue exception'}`,
|
|
633
|
+
// entityType: 'blocklet',
|
|
634
|
+
// entityId: did,
|
|
635
|
+
// severity: 'error',
|
|
636
|
+
// });
|
|
637
|
+
// });
|
|
638
|
+
|
|
639
|
+
const userDid = context.user.did;
|
|
640
|
+
const { referrer } = context;
|
|
641
|
+
|
|
642
|
+
// FIXME: @yejianchao did should be renamed to appDid
|
|
643
|
+
const spacesBackup = new SpacesBackup({ did, event: this, userDid, referrer });
|
|
644
|
+
this.emit(BlockletEvents.backupProgress, { did, message: 'Start backup...', progress: 10 });
|
|
618
645
|
await spacesBackup.backup();
|
|
646
|
+
this.emit(BlockletEvents.backupProgress, { did, completed: true, progress: 100 });
|
|
619
647
|
}
|
|
620
648
|
|
|
621
649
|
/**
|
|
622
|
-
*
|
|
623
|
-
*
|
|
650
|
+
* FIXME: @linchen support cancel
|
|
651
|
+
* FIXME: @wangshijun create audit log for this
|
|
624
652
|
* @param {import('@abtnode/client').RequestRestoreFromSpacesInput} input
|
|
625
653
|
* @memberof BlockletManager
|
|
626
654
|
*/
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
655
|
+
// eslint-disable-next-line no-unused-vars
|
|
656
|
+
async restoreFromSpaces(input, context) {
|
|
657
|
+
// FIXME: @yejianchao did should be renamed to appDid
|
|
658
|
+
this.emit(BlockletEvents.restoreProgress, { did: input.appDid, message: 'Start restore...' });
|
|
659
|
+
|
|
660
|
+
const userDid = context.user.did;
|
|
661
|
+
const { referrer } = context;
|
|
630
662
|
|
|
631
|
-
|
|
663
|
+
const spacesRestore = new SpacesRestore({ ...input, event: this, userDid, referrer });
|
|
664
|
+
const params = await spacesRestore.restore();
|
|
665
|
+
|
|
666
|
+
this.emit(BlockletEvents.restoreProgress, { did: input.appDid, message: 'Installing blocklet...' });
|
|
632
667
|
await this._installFromBackup({
|
|
633
668
|
url: `file://${spacesRestore.blockletRestoreDir}`,
|
|
634
|
-
blockletSecretKey: spacesRestore.blockletWallet.secretKey,
|
|
635
669
|
moveDir: true,
|
|
670
|
+
...merge(...params),
|
|
636
671
|
});
|
|
672
|
+
|
|
673
|
+
this.emit(BlockletEvents.restoreProgress, { did: input.appDid, completed: true });
|
|
637
674
|
}
|
|
638
675
|
|
|
639
676
|
async restart({ did }, context) {
|
|
@@ -1003,8 +1040,9 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1003
1040
|
return blocklets;
|
|
1004
1041
|
}
|
|
1005
1042
|
|
|
1043
|
+
// CAUTION: this method currently only support config by blocklet.meta.did
|
|
1006
1044
|
// eslint-disable-next-line no-unused-vars
|
|
1007
|
-
async config({ did, configs: newConfigs, skipHook }, context) {
|
|
1045
|
+
async config({ did, configs: newConfigs, skipHook, skipDidDocument }, context) {
|
|
1008
1046
|
if (!Array.isArray(newConfigs)) {
|
|
1009
1047
|
throw new Error('configs list is not an array');
|
|
1010
1048
|
}
|
|
@@ -1034,7 +1072,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1034
1072
|
throw new Error(`Cannot set ${x.key} to child blocklet`);
|
|
1035
1073
|
}
|
|
1036
1074
|
|
|
1037
|
-
await validateAppConfig(x,
|
|
1075
|
+
await validateAppConfig(x, states);
|
|
1038
1076
|
} else if (!BLOCKLET_CONFIGURABLE_KEY[x.key] && !isPreferenceKey(x)) {
|
|
1039
1077
|
if (!(blocklet.meta.environments || []).some((y) => y.name === x.key)) {
|
|
1040
1078
|
// forbid unknown format key
|
|
@@ -1057,14 +1095,26 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1057
1095
|
});
|
|
1058
1096
|
}
|
|
1059
1097
|
|
|
1098
|
+
const willAppSkChange = isRotatingAppSk(newConfigs, blocklet.configs, blocklet.externalSk);
|
|
1099
|
+
const willAppDidChange = isRotatingAppDid(newConfigs, blocklet.configs, blocklet.externalSk);
|
|
1100
|
+
|
|
1060
1101
|
// update db
|
|
1061
1102
|
await states.blockletExtras.setConfigs(dids, newConfigs);
|
|
1062
1103
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1104
|
+
if (willAppSkChange) {
|
|
1105
|
+
const info = await states.node.read();
|
|
1106
|
+
const { wallet } = getBlockletInfo(blocklet, info.sk);
|
|
1107
|
+
const migratedFrom = blocklet.migratedFrom || [];
|
|
1108
|
+
await states.blocklet.updateBlocklet(rootDid, {
|
|
1109
|
+
migratedFrom: [
|
|
1110
|
+
...migratedFrom,
|
|
1111
|
+
{ appSk: wallet.secretKey, appDid: wallet.address, at: new Date().toISOString() },
|
|
1112
|
+
],
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1066
1115
|
|
|
1067
|
-
|
|
1116
|
+
// FIXME: @zhenqiang best way to handle the did document: allow all appDids to resolve
|
|
1117
|
+
if (willAppDidChange && !skipDidDocument) {
|
|
1068
1118
|
await this._updateDidDocument(blocklet);
|
|
1069
1119
|
}
|
|
1070
1120
|
|
|
@@ -1198,7 +1248,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1198
1248
|
}
|
|
1199
1249
|
|
|
1200
1250
|
async updateComponentMountPoint({ did, rootDid: inputRootDid, mountPoint: tmpMountPoint }, context) {
|
|
1201
|
-
const mountPoint = await
|
|
1251
|
+
const mountPoint = await updateMountPointSchema.validateAsync(tmpMountPoint);
|
|
1202
1252
|
|
|
1203
1253
|
const blocklet = await states.blocklet.getBlocklet(inputRootDid);
|
|
1204
1254
|
|
|
@@ -1679,13 +1729,13 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1679
1729
|
* /blocklet.json
|
|
1680
1730
|
* /blocklet_extras.json
|
|
1681
1731
|
* @see Blocklet 端到端备份方案的设计与实现(一期) https://team.arcblock.io/comment/discussions/e62084d5-fafa-489e-91d5-defcd6e93223
|
|
1682
|
-
* @param {{ url: string,
|
|
1732
|
+
* @param {{ url: string, appSk: string, moveDir: boolean}} [{ url }={}]
|
|
1683
1733
|
* @param {Record<string, string>} [context={}]
|
|
1684
1734
|
* @return {Promise<any>}
|
|
1685
1735
|
* @memberof BlockletManager
|
|
1686
1736
|
*/
|
|
1687
|
-
async _installFromBackup({ url,
|
|
1688
|
-
return installFromBackup({ url,
|
|
1737
|
+
async _installFromBackup({ url, appSk, moveDir } = {}, context = {}) {
|
|
1738
|
+
return installFromBackup({ url, appSk, moveDir, context, manager: this, states });
|
|
1689
1739
|
}
|
|
1690
1740
|
|
|
1691
1741
|
async ensureBlocklet(did, opts = {}) {
|
|
@@ -2067,6 +2117,14 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2067
2117
|
// put BLOCKLET_APP_ID at root level for indexing
|
|
2068
2118
|
blocklet.appDid = appSystemEnvironments.BLOCKLET_APP_ID;
|
|
2069
2119
|
|
|
2120
|
+
if (!blocklet.migratedFrom) {
|
|
2121
|
+
blocklet.migratedFrom = [];
|
|
2122
|
+
}
|
|
2123
|
+
// This can only be set once, can be used for indexing, will not change ever
|
|
2124
|
+
if (!blocklet.appPid) {
|
|
2125
|
+
blocklet.appPid = appSystemEnvironments.BLOCKLET_APP_PID;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2070
2128
|
// update state to db
|
|
2071
2129
|
return states.blocklet.updateBlocklet(did, blocklet);
|
|
2072
2130
|
}
|
|
@@ -2084,17 +2142,18 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2084
2142
|
*
|
|
2085
2143
|
* @param {{
|
|
2086
2144
|
* did: string;
|
|
2087
|
-
* registry: string;
|
|
2088
2145
|
* sync: boolean;
|
|
2089
2146
|
* delay: number;
|
|
2090
2147
|
* controller: Controller;
|
|
2148
|
+
* appSk: string;
|
|
2149
|
+
* storeUrl: string;
|
|
2091
2150
|
* }} params
|
|
2092
2151
|
* @param {*} context
|
|
2093
2152
|
* @return {*}
|
|
2094
2153
|
* @memberof BlockletManager
|
|
2095
2154
|
*/
|
|
2096
2155
|
async _installFromStore(params, context) {
|
|
2097
|
-
const { did, storeUrl, sync, delay, controller } = params;
|
|
2156
|
+
const { did, storeUrl, sync, delay, controller, appSk } = params;
|
|
2098
2157
|
|
|
2099
2158
|
logger.debug('start install blocklet', { did });
|
|
2100
2159
|
if (!isValidDid(did)) {
|
|
@@ -2138,6 +2197,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2138
2197
|
sync,
|
|
2139
2198
|
delay,
|
|
2140
2199
|
controller,
|
|
2200
|
+
appSk,
|
|
2141
2201
|
context,
|
|
2142
2202
|
});
|
|
2143
2203
|
}
|
|
@@ -2154,13 +2214,14 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2154
2214
|
* sync: boolean;
|
|
2155
2215
|
* delay: number;
|
|
2156
2216
|
* controller: Controller;
|
|
2217
|
+
* appSk: string;
|
|
2157
2218
|
* }} params
|
|
2158
2219
|
* @param {{}} context
|
|
2159
2220
|
* @return {*}
|
|
2160
2221
|
* @memberof BlockletManager
|
|
2161
2222
|
*/
|
|
2162
2223
|
async _installFromUrl(params, context) {
|
|
2163
|
-
const { url, sync, delay, controller } = params;
|
|
2224
|
+
const { url, sync, delay, controller, appSk } = params;
|
|
2164
2225
|
|
|
2165
2226
|
logger.debug('start install blocklet', { url });
|
|
2166
2227
|
|
|
@@ -2174,13 +2235,18 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2174
2235
|
|
|
2175
2236
|
// install from store if url is a store url
|
|
2176
2237
|
const { inStore, registryUrl, blockletDid: bundleDid } = await StoreUtil.parseSourceUrl(url, controller);
|
|
2238
|
+
|
|
2239
|
+
const nodeInfo = await states.node.read();
|
|
2240
|
+
|
|
2241
|
+
await validateStore(nodeInfo, registryUrl);
|
|
2242
|
+
|
|
2177
2243
|
if (inStore) {
|
|
2178
2244
|
const exist = await states.blocklet.getBlocklet(blockletDid);
|
|
2179
2245
|
if (exist) {
|
|
2180
2246
|
return this.upgrade({ did: blockletDid, storeUrl: registryUrl, sync, delay }, context);
|
|
2181
2247
|
}
|
|
2182
2248
|
|
|
2183
|
-
return this._installFromStore({ did: bundleDid, storeUrl: registryUrl, controller, sync, delay }, context);
|
|
2249
|
+
return this._installFromStore({ did: bundleDid, storeUrl: registryUrl, controller, sync, delay, appSk }, context);
|
|
2184
2250
|
}
|
|
2185
2251
|
|
|
2186
2252
|
const meta = ensureMeta(bundleMeta, { name: blockletName, did: blockletDid });
|
|
@@ -2206,6 +2272,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2206
2272
|
sync,
|
|
2207
2273
|
delay,
|
|
2208
2274
|
controller,
|
|
2275
|
+
appSk,
|
|
2209
2276
|
context,
|
|
2210
2277
|
});
|
|
2211
2278
|
}
|
|
@@ -2310,7 +2377,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2310
2377
|
);
|
|
2311
2378
|
}
|
|
2312
2379
|
|
|
2313
|
-
async _installFromCreate({ title, description }, context = {}) {
|
|
2380
|
+
async _installFromCreate({ title, description, appSk }, context = {}) {
|
|
2314
2381
|
logger.debug('create blocklet', { title, description });
|
|
2315
2382
|
|
|
2316
2383
|
await joi.string().label('title').max(20).required().validateAsync(title);
|
|
@@ -2338,11 +2405,9 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2338
2405
|
};
|
|
2339
2406
|
const meta = validateMeta(rawMeta);
|
|
2340
2407
|
|
|
2341
|
-
await states.blocklet.addBlocklet({
|
|
2342
|
-
meta,
|
|
2343
|
-
source: BlockletSource.custom,
|
|
2344
|
-
});
|
|
2408
|
+
await states.blocklet.addBlocklet({ meta, source: BlockletSource.custom, externalSk: !!appSk });
|
|
2345
2409
|
await this._setConfigsFromMeta(did);
|
|
2410
|
+
await this._setAppSk(did, appSk);
|
|
2346
2411
|
|
|
2347
2412
|
// check duplicate appSk
|
|
2348
2413
|
await checkDuplicateAppSk({ did, states });
|
|
@@ -2382,7 +2447,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2382
2447
|
return { cwd, tarFile };
|
|
2383
2448
|
}
|
|
2384
2449
|
|
|
2385
|
-
async _installFromUpload({ file, did, diffVersion, deleteSet,
|
|
2450
|
+
async _installFromUpload({ file, did, diffVersion, deleteSet, appSk }, context) {
|
|
2386
2451
|
logger.info('install blocklet', { from: 'upload file' });
|
|
2387
2452
|
const { tarFile } = await this._downloadFromUpload(file);
|
|
2388
2453
|
|
|
@@ -2474,12 +2539,14 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2474
2539
|
source: BlockletSource.upload,
|
|
2475
2540
|
deployedFrom: `Upload by ${context.user.fullName}`,
|
|
2476
2541
|
children,
|
|
2542
|
+
externalSk: !!appSk,
|
|
2477
2543
|
});
|
|
2478
2544
|
|
|
2479
2545
|
const action = 'install';
|
|
2480
2546
|
const oldState = { extraState: oldExtraState };
|
|
2481
2547
|
try {
|
|
2482
2548
|
await this._setConfigsFromMeta(meta.did);
|
|
2549
|
+
await this._setAppSk(appSk);
|
|
2483
2550
|
await validateBlocklet(blocklet);
|
|
2484
2551
|
|
|
2485
2552
|
// check duplicate appSk
|
|
@@ -2776,6 +2843,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2776
2843
|
* meta: {}; // blocklet meta
|
|
2777
2844
|
* source: number; // example: BlockletSource.registry,
|
|
2778
2845
|
* deployedFrom: string;
|
|
2846
|
+
* appSk: string;
|
|
2779
2847
|
* context: {}
|
|
2780
2848
|
* sync: boolean = false;
|
|
2781
2849
|
* delay: number;
|
|
@@ -2785,10 +2853,15 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2785
2853
|
* @memberof BlockletManager
|
|
2786
2854
|
*/
|
|
2787
2855
|
async _install(params) {
|
|
2788
|
-
const { meta, source, deployedFrom, context, sync, delay, controller } = params;
|
|
2856
|
+
const { meta, source, deployedFrom, context, sync, delay, controller, appSk } = params;
|
|
2789
2857
|
|
|
2790
2858
|
validateBlockletMeta(meta, { ensureDist: true });
|
|
2791
2859
|
|
|
2860
|
+
const info = await states.node.read();
|
|
2861
|
+
if (info.mode === NODE_MODES.SERVERLESS) {
|
|
2862
|
+
validateInServerless({ blockletMeta: meta });
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2792
2865
|
const { name, did, version } = meta;
|
|
2793
2866
|
|
|
2794
2867
|
const oldExtraState = await states.blockletExtras.findOne({ did: meta.did });
|
|
@@ -2801,6 +2874,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2801
2874
|
source,
|
|
2802
2875
|
deployedFrom,
|
|
2803
2876
|
children,
|
|
2877
|
+
externalSk: !!appSk,
|
|
2804
2878
|
});
|
|
2805
2879
|
|
|
2806
2880
|
await validateBlocklet(blocklet);
|
|
@@ -2808,6 +2882,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2808
2882
|
await states.blockletExtras.addMeta({ did, meta: { did, name }, controller });
|
|
2809
2883
|
|
|
2810
2884
|
await this._setConfigsFromMeta(did);
|
|
2885
|
+
await this._setAppSk(did, appSk);
|
|
2811
2886
|
|
|
2812
2887
|
// check duplicate appSk
|
|
2813
2888
|
await checkDuplicateAppSk({ did, states });
|
|
@@ -3405,12 +3480,31 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3405
3480
|
}
|
|
3406
3481
|
}
|
|
3407
3482
|
|
|
3483
|
+
async _setAppSk(did, appSk, context) {
|
|
3484
|
+
if (process.env.NODE_ENV === 'production' && !appSk) {
|
|
3485
|
+
throw new Error(`appSk for blocklet ${did} is required`);
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
if (appSk) {
|
|
3489
|
+
await this.config(
|
|
3490
|
+
{
|
|
3491
|
+
did,
|
|
3492
|
+
configs: [{ key: 'BLOCKLET_APP_SK', value: appSk, secure: true }],
|
|
3493
|
+
skipHook: true,
|
|
3494
|
+
skipDidDocument: true,
|
|
3495
|
+
},
|
|
3496
|
+
context
|
|
3497
|
+
);
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3408
3501
|
async _findNextCustomBlockletName(leftTimes = 10) {
|
|
3409
3502
|
if (leftTimes <= 0) {
|
|
3410
3503
|
throw new Error('Generate custom blocklet did too many times');
|
|
3411
3504
|
}
|
|
3412
3505
|
const number = await states.node.increaseCustomBlockletNumber();
|
|
3413
3506
|
const name = `custom-${number}`;
|
|
3507
|
+
// MEMO: 空壳 APP可以保留原有的 did 生成逻辑
|
|
3414
3508
|
const did = toBlockletDid(name);
|
|
3415
3509
|
const blocklet = await states.blocklet.getBlocklet(did);
|
|
3416
3510
|
if (blocklet) {
|
|
@@ -19,7 +19,7 @@ const { validateBlocklet, checkDuplicateAppSk, getAppDirs } = require('../../../
|
|
|
19
19
|
* }} param0
|
|
20
20
|
* @returns
|
|
21
21
|
*/
|
|
22
|
-
module.exports = async ({ url,
|
|
22
|
+
module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } = {}) => {
|
|
23
23
|
// TODO: support more url schema feature (http, did-spaces)
|
|
24
24
|
if (!url.startsWith('file://')) {
|
|
25
25
|
throw new Error('url must starts with file://');
|
|
@@ -71,17 +71,17 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
|
|
|
71
71
|
throw new Error('blocklet is already exist');
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
if (
|
|
74
|
+
if (appSk) {
|
|
75
75
|
extra.configs = extra.configs || [];
|
|
76
76
|
const skConfig = extra.configs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
|
|
77
77
|
if (skConfig) {
|
|
78
|
-
skConfig.value =
|
|
78
|
+
skConfig.value = appSk;
|
|
79
79
|
skConfig.secure = true;
|
|
80
80
|
skConfig.shared = false;
|
|
81
81
|
} else {
|
|
82
82
|
extra.configs.push({
|
|
83
83
|
key: BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK,
|
|
84
|
-
value:
|
|
84
|
+
value: appSk,
|
|
85
85
|
secure: true,
|
|
86
86
|
shared: false,
|
|
87
87
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const { removeSync, outputJsonSync, readFileSync } = require('fs-extra');
|
|
2
2
|
const { isEmpty, cloneDeep } = require('lodash');
|
|
3
3
|
const { join } = require('path');
|
|
4
|
+
const { Hasher } = require('@ocap/mcrypto');
|
|
5
|
+
const { toBuffer } = require('@ocap/util');
|
|
4
6
|
const security = require('@abtnode/util/lib/security');
|
|
5
|
-
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
6
7
|
const states = require('../../../states');
|
|
7
8
|
const { BaseBackup } = require('./base');
|
|
8
9
|
|
|
@@ -32,7 +33,7 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
32
33
|
throw new Error('blockletExtra cannot be empty');
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
return this.
|
|
36
|
+
return this.cleanData(blockletExtra);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -42,7 +43,7 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
42
43
|
* @return {Promise<void>}
|
|
43
44
|
* @memberof BlockletExtrasBackup
|
|
44
45
|
*/
|
|
45
|
-
async
|
|
46
|
+
async cleanData(blockletExtraInput) {
|
|
46
47
|
const blockletExtra = cloneDeep(blockletExtraInput);
|
|
47
48
|
|
|
48
49
|
const queue = [blockletExtra];
|
|
@@ -57,7 +58,7 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// 加解密
|
|
60
|
-
this.
|
|
61
|
+
this.encryptBlockletConfigs(currentBlockletExtra.configs);
|
|
61
62
|
|
|
62
63
|
if (currentBlockletExtra?.children) {
|
|
63
64
|
queue.push(...currentBlockletExtra.children);
|
|
@@ -74,33 +75,28 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
74
75
|
* @return {void}
|
|
75
76
|
* @memberof BlockletExtrasBackup
|
|
76
77
|
*/
|
|
77
|
-
|
|
78
|
+
encryptBlockletConfigs(configs) {
|
|
78
79
|
if (isEmpty(configs)) {
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
const { secretKey, address } = this.blockletWallet;
|
|
84
|
+
|
|
82
85
|
// 准备加解密所需的参数
|
|
83
86
|
// @see: https://github.com/ArcBlock/blocklet-server/blob/f561ba7290285f2e23dccb6d5323eb4d43c3cc3e/core/state/lib/index.js#L59
|
|
84
|
-
const
|
|
87
|
+
const dk = readFileSync(join(this.serverDataDir, '.sock'));
|
|
88
|
+
const ek = toBuffer(Hasher.SHA3.hash256(Buffer.concat([secretKey, address].map(toBuffer))));
|
|
85
89
|
|
|
86
90
|
for (const config of configs) {
|
|
87
|
-
|
|
88
|
-
if (config.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK) {
|
|
89
|
-
config.value = '';
|
|
90
|
-
} else if (config.secure) {
|
|
91
|
+
if (config.secure) {
|
|
91
92
|
// secure 为 true 的配置才需要被加密保存上次到 did spaces
|
|
92
|
-
|
|
93
93
|
const encryptByServer = config.value;
|
|
94
94
|
// 先从 blocklet server 解密
|
|
95
95
|
// @see https://github.com/ArcBlock/blocklet-server/blob/f40338168a66893f325464cea79ae54c43f623b1/core/state/lib/blocklet/extras.js#L139
|
|
96
|
-
const
|
|
97
|
-
// 再用 blocklet
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
this.blockletWallet.address,
|
|
101
|
-
Buffer.from(this.blockletWallet.secretKey)
|
|
102
|
-
);
|
|
103
|
-
config.value = encryptByBlocklet;
|
|
96
|
+
const decrypted = security.decrypt(encryptByServer, this.blocklet.meta.did, dk);
|
|
97
|
+
// 再用 blocklet 信息加密,然后才可以上传到 spaces
|
|
98
|
+
const encrypted = security.encrypt(decrypted, address, ek);
|
|
99
|
+
config.value = encrypted;
|
|
104
100
|
}
|
|
105
101
|
}
|
|
106
102
|
}
|