@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.
@@ -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, mountPointSchema, environmentNameSchema } = require('@blocklet/meta/lib/schema');
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, context });
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
- return this._installFromCreate({ title: params.title, description: params.description }, context);
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, blockletSecretKey } = params;
302
- return this._installFromBackup({ url, blockletSecretKey }, context);
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 mountPointSchema.validateAsync(tmpMountPoint);
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
- async backupToSpaces(input) {
617
- const spacesBackup = new SpacesBackup(input);
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
- async restoreFromSpaces(input) {
628
- const spacesRestore = new SpacesRestore(input);
629
- await spacesRestore.restore();
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
- // FIXME: 需要改成队列执行,本次失败,下次还需要重试,页面刷新后也能知道最新的状态
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, rootDid, states);
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
- const isSkOrWalletTypeChanged = newConfigs.find((item) =>
1064
- [BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK, BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_WALLET_TYPE].includes(item.key)
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
- if (isSkOrWalletTypeChanged) {
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 mountPointSchema.validateAsync(tmpMountPoint);
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, blockletSecretKey: string, moveDir: boolean}} [{ url }={}]
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, blockletSecretKey, moveDir } = {}, context = {}) {
1688
- return installFromBackup({ url, blockletSecretKey, moveDir, context, manager: this, states });
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, context }) {
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, blockletSecretKey, moveDir, context = {}, states, manager } = {}) => {
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 (blockletSecretKey) {
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 = blockletSecretKey;
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: blockletSecretKey,
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.cleanDate(blockletExtra);
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 cleanDate(blockletExtraInput) {
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.useBlockletEncryptConfigs(currentBlockletExtra.configs);
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
- useBlockletEncryptConfigs(configs) {
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 dek = readFileSync(join(this.serverDataDir, '.sock'));
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
- // 置空 blocklet 的密钥,但是保留其他属性,这很重要
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 decryptByServer = security.decrypt(encryptByServer, this.blocklet.meta.did, dek);
97
- // 再用 blocklet secret 加密,然后才可以上传到 spaces
98
- const encryptByBlocklet = security.encrypt(
99
- decryptByServer,
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
  }