@twin.org/synchronised-storage-service 0.0.1-next.8 → 0.0.3-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/es/data/verifiableStorageKeys.json +5 -0
  2. package/dist/es/entities/syncSnapshotEntry.js +93 -0
  3. package/dist/es/entities/syncSnapshotEntry.js.map +1 -0
  4. package/dist/es/helpers/blobStorageHelper.js +185 -0
  5. package/dist/es/helpers/blobStorageHelper.js.map +1 -0
  6. package/dist/es/helpers/changeSetHelper.js +215 -0
  7. package/dist/es/helpers/changeSetHelper.js.map +1 -0
  8. package/dist/es/helpers/localSyncStateHelper.js +384 -0
  9. package/dist/es/helpers/localSyncStateHelper.js.map +1 -0
  10. package/dist/es/helpers/remoteSyncStateHelper.js +560 -0
  11. package/dist/es/helpers/remoteSyncStateHelper.js.map +1 -0
  12. package/dist/es/helpers/versions.js +6 -0
  13. package/dist/es/helpers/versions.js.map +1 -0
  14. package/dist/es/index.js +13 -0
  15. package/dist/es/index.js.map +1 -0
  16. package/dist/es/models/ISyncPointerStore.js +4 -0
  17. package/dist/es/models/ISyncPointerStore.js.map +1 -0
  18. package/dist/es/models/ISyncSnapshot.js +4 -0
  19. package/dist/es/models/ISyncSnapshot.js.map +1 -0
  20. package/dist/es/models/ISyncState.js +2 -0
  21. package/dist/es/models/ISyncState.js.map +1 -0
  22. package/dist/es/models/ISynchronisedStorageServiceConfig.js +4 -0
  23. package/dist/es/models/ISynchronisedStorageServiceConfig.js.map +1 -0
  24. package/dist/es/models/ISynchronisedStorageServiceConstructorOptions.js +2 -0
  25. package/dist/es/models/ISynchronisedStorageServiceConstructorOptions.js.map +1 -0
  26. package/dist/es/restEntryPoints.js +10 -0
  27. package/dist/es/restEntryPoints.js.map +1 -0
  28. package/dist/es/schema.js +11 -0
  29. package/dist/es/schema.js.map +1 -0
  30. package/dist/es/synchronisedStorageRoutes.js +153 -0
  31. package/dist/es/synchronisedStorageRoutes.js.map +1 -0
  32. package/dist/es/synchronisedStorageService.js +554 -0
  33. package/dist/es/synchronisedStorageService.js.map +1 -0
  34. package/dist/types/entities/syncSnapshotEntry.d.ts +3 -3
  35. package/dist/types/helpers/blobStorageHelper.d.ts +3 -3
  36. package/dist/types/helpers/changeSetHelper.d.ts +16 -32
  37. package/dist/types/helpers/localSyncStateHelper.d.ts +11 -11
  38. package/dist/types/helpers/remoteSyncStateHelper.d.ts +18 -14
  39. package/dist/types/index.d.ts +10 -10
  40. package/dist/types/models/ISyncState.d.ts +1 -1
  41. package/dist/types/models/ISynchronisedStorageServiceConfig.d.ts +3 -8
  42. package/dist/types/models/ISynchronisedStorageServiceConstructorOptions.d.ts +7 -6
  43. package/dist/types/synchronisedStorageRoutes.d.ts +1 -1
  44. package/dist/types/synchronisedStorageService.d.ts +18 -21
  45. package/docs/architecture.md +168 -12
  46. package/docs/changelog.md +149 -0
  47. package/docs/open-api/spec.json +62 -57
  48. package/docs/reference/classes/SyncSnapshotEntry.md +4 -10
  49. package/docs/reference/classes/SynchronisedStorageService.md +38 -50
  50. package/docs/reference/interfaces/ISynchronisedStorageServiceConfig.md +3 -17
  51. package/docs/reference/interfaces/ISynchronisedStorageServiceConstructorOptions.md +9 -8
  52. package/locales/en.json +11 -16
  53. package/package.json +26 -9
  54. package/dist/cjs/index.cjs +0 -2233
  55. package/dist/esm/index.mjs +0 -2225
@@ -0,0 +1,554 @@
1
+ import { BlobStorageConnectorFactory } from "@twin.org/blob-storage-models";
2
+ import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
3
+ import { BaseError, Coerce, ComponentFactory, Converter, GeneralError, Guards, Is, UnauthorizedError } from "@twin.org/core";
4
+ import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
5
+ import { IdentityAuthenticationContexts, IdentityAuthenticationTypes } from "@twin.org/identity-authentication";
6
+ import { ActionType } from "@twin.org/standards-w3c-odrl";
7
+ import { SynchronisedStorageAssetTypes, SynchronisedStorageTopics } from "@twin.org/synchronised-storage-models";
8
+ import { VaultConnectorFactory, VaultKeyType } from "@twin.org/vault-models";
9
+ import { VerifiableStorageConnectorFactory } from "@twin.org/verifiable-storage-models";
10
+ import verifiableStorageKeys from "./data/verifiableStorageKeys.json" with { type: "json" };
11
+ import { BlobStorageHelper } from "./helpers/blobStorageHelper.js";
12
+ import { ChangeSetHelper } from "./helpers/changeSetHelper.js";
13
+ import { LocalSyncStateHelper } from "./helpers/localSyncStateHelper.js";
14
+ import { RemoteSyncStateHelper } from "./helpers/remoteSyncStateHelper.js";
15
+ /**
16
+ * Class for performing synchronised storage operations.
17
+ */
18
+ export class SynchronisedStorageService {
19
+ /**
20
+ * Runtime name for the class.
21
+ */
22
+ static CLASS_NAME = "SynchronisedStorageService";
23
+ /**
24
+ * The default interval to check for entity updates.
25
+ * @internal
26
+ */
27
+ static _DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES = 5;
28
+ /**
29
+ * The default interval to perform consolidation.
30
+ * @internal
31
+ */
32
+ static _DEFAULT_CONSOLIDATION_INTERVAL_MINUTES = 60;
33
+ /**
34
+ * The default size of a consolidation batch.
35
+ * @internal
36
+ */
37
+ static _DEFAULT_CONSOLIDATION_BATCH_SIZE = 100;
38
+ /**
39
+ * The default max number of consolidations to keep in storage.
40
+ * @internal
41
+ */
42
+ static _DEFAULT_MAX_CONSOLIDATIONS = 5;
43
+ /**
44
+ * The logging component to use for logging.
45
+ * @internal
46
+ */
47
+ _logging;
48
+ /**
49
+ * The event bus component.
50
+ * @internal
51
+ */
52
+ _eventBusComponent;
53
+ /**
54
+ * The vault connector.
55
+ * @internal
56
+ */
57
+ _vaultConnector;
58
+ /**
59
+ * The storage connector for the sync snapshot entries.
60
+ * @internal
61
+ */
62
+ _localSyncSnapshotEntryEntityStorage;
63
+ /**
64
+ * The blob storage connector to use for remote sync states.
65
+ * @internal
66
+ */
67
+ _blobStorageConnector;
68
+ /**
69
+ * The verifiable storage connector to use for storing sync pointers.
70
+ * @internal
71
+ */
72
+ _verifiableSyncPointerStorageConnector;
73
+ /**
74
+ * The task scheduler component.
75
+ * @internal
76
+ */
77
+ _taskSchedulerComponent;
78
+ /**
79
+ * The synchronised storage service to use when this is not a trusted node.
80
+ * @internal
81
+ */
82
+ _trustedSynchronisedStorageComponent;
83
+ /**
84
+ * The policy enforcement point component, used by trusted nodes for incoming requests.
85
+ * @internal
86
+ */
87
+ _policyEnforcementPointComponent;
88
+ /**
89
+ * The blob storage helper.
90
+ * @internal
91
+ */
92
+ _blobStorageHelper;
93
+ /**
94
+ * The change set helper.
95
+ * @internal
96
+ */
97
+ _changeSetHelper;
98
+ /**
99
+ * The local sync state helper to use for applying changesets.
100
+ * @internal
101
+ */
102
+ _localSyncStateHelper;
103
+ /**
104
+ * The remote sync state helper to use for applying changesets.
105
+ * @internal
106
+ */
107
+ _remoteSyncStateHelper;
108
+ /**
109
+ * The options for the connector.
110
+ * @internal
111
+ */
112
+ _config;
113
+ /**
114
+ * The synchronised storage key to use for the remote synchronised storage.
115
+ * @internal
116
+ */
117
+ _synchronisedStorageKey;
118
+ /**
119
+ * The flag to determine if the service has been started.
120
+ * @internal
121
+ */
122
+ _serviceStarted;
123
+ /**
124
+ * The active storage keys for the synchronised storage service.
125
+ * @internal
126
+ */
127
+ _activeStorageKeys;
128
+ /**
129
+ * The identity of the node this connector is running on.
130
+ * @internal
131
+ */
132
+ _nodeId;
133
+ /**
134
+ * Create a new instance of SynchronisedStorageService.
135
+ * @param options The options for the service.
136
+ */
137
+ constructor(options) {
138
+ Guards.object(SynchronisedStorageService.CLASS_NAME, "options", options);
139
+ Guards.object(SynchronisedStorageService.CLASS_NAME, "options.config", options.config);
140
+ Guards.stringValue(SynchronisedStorageService.CLASS_NAME, "options.config.verifiableStorageKeyId", options.config.verifiableStorageKeyId);
141
+ this._eventBusComponent = ComponentFactory.get(options.eventBusComponentType ?? "event-bus");
142
+ this._logging = ComponentFactory.getIfExists(options.loggingComponentType ?? "logging");
143
+ this._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType ?? "vault");
144
+ this._localSyncSnapshotEntryEntityStorage = EntityStorageConnectorFactory.get(options.syncSnapshotStorageConnectorType ?? "sync-snapshot-entry");
145
+ this._verifiableSyncPointerStorageConnector = VerifiableStorageConnectorFactory.get(options.verifiableStorageConnectorType ?? "verifiable-storage");
146
+ this._blobStorageConnector = BlobStorageConnectorFactory.get(options.blobStorageConnectorType ?? "blob-storage");
147
+ this._taskSchedulerComponent = ComponentFactory.get(options.taskSchedulerComponentType ?? "task-scheduler");
148
+ // If this is empty we assume the local node has the rights to write to the verifiable storage.
149
+ let isTrustedNode = true;
150
+ if (!Is.empty(options.trustedSynchronisedStorageComponentType)) {
151
+ isTrustedNode = false;
152
+ // If it is set then we used the trusted component to send changesets to
153
+ this._trustedSynchronisedStorageComponent =
154
+ ComponentFactory.get(options.trustedSynchronisedStorageComponentType);
155
+ }
156
+ else {
157
+ // A trusted node must have a policy enforcement point component
158
+ this._policyEnforcementPointComponent = ComponentFactory.get(options?.policyEnforcementPointComponentType ?? "policy-enforcement-point");
159
+ }
160
+ this._config = {
161
+ entityUpdateIntervalMinutes: options.config.entityUpdateIntervalMinutes ??
162
+ SynchronisedStorageService._DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES,
163
+ consolidationIntervalMinutes: options.config.consolidationIntervalMinutes ??
164
+ SynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MINUTES,
165
+ consolidationBatchSize: options.config.consolidationBatchSize ??
166
+ SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE,
167
+ maxConsolidations: options.config.maxConsolidations ?? SynchronisedStorageService._DEFAULT_MAX_CONSOLIDATIONS,
168
+ blobStorageEncryptionKeyId: options.config.blobStorageEncryptionKeyId ?? "synchronised-storage-blob-encryption-key",
169
+ verifiableStorageKeyId: options.config.verifiableStorageKeyId
170
+ };
171
+ this._synchronisedStorageKey =
172
+ verifiableStorageKeys[options.config.verifiableStorageKeyId] ?? options.config.verifiableStorageKeyId;
173
+ Guards.stringValue(SynchronisedStorageService.CLASS_NAME, "synchronisedStorageKey", this._synchronisedStorageKey);
174
+ this._blobStorageHelper = new BlobStorageHelper(this._logging, this._vaultConnector, this._blobStorageConnector, this._config.blobStorageEncryptionKeyId, isTrustedNode);
175
+ this._changeSetHelper = new ChangeSetHelper(this._logging, this._eventBusComponent, this._blobStorageHelper);
176
+ this._localSyncStateHelper = new LocalSyncStateHelper(this._logging, this._localSyncSnapshotEntryEntityStorage, this._changeSetHelper);
177
+ this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._logging, this._eventBusComponent, this._verifiableSyncPointerStorageConnector, this._blobStorageHelper, this._changeSetHelper, isTrustedNode, this._config.maxConsolidations);
178
+ this._serviceStarted = false;
179
+ this._activeStorageKeys = {};
180
+ }
181
+ /**
182
+ * Returns the class name of the component.
183
+ * @returns The class name of the component.
184
+ */
185
+ className() {
186
+ return SynchronisedStorageService.CLASS_NAME;
187
+ }
188
+ /**
189
+ * The component needs to be started when the node is initialized.
190
+ * @param nodeLoggingComponentType The node logging component type.
191
+ * @returns Nothing.
192
+ */
193
+ async start(nodeLoggingComponentType) {
194
+ const contextIds = await ContextIdStore.getContextIds();
195
+ ContextIdHelper.guard(contextIds, ContextIdKeys.Node);
196
+ this._nodeId = contextIds[ContextIdKeys.Node];
197
+ this._remoteSyncStateHelper.setNodeId(this._nodeId);
198
+ this._changeSetHelper.setNodeId(this._nodeId);
199
+ this._remoteSyncStateHelper.setSynchronisedStorageKey(this._synchronisedStorageKey);
200
+ this._serviceStarted = true;
201
+ // If this is not a trusted node we need to request the decryption key from a trusted node
202
+ if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
203
+ const actionRequest = {
204
+ "@context": IdentityAuthenticationContexts.ContextRoot,
205
+ type: IdentityAuthenticationTypes.ActionRequest,
206
+ action: "get-key",
207
+ requester: contextIds[ContextIdKeys.Node]
208
+ };
209
+ const decryptionKey = await this._trustedSynchronisedStorageComponent.getDecryptionKey(actionRequest);
210
+ // If the key exists remove it and get a new one, in case the key has been rotated
211
+ const existingKey = await this._vaultConnector.getKey(this._config.blobStorageEncryptionKeyId);
212
+ if (!Is.empty(existingKey)) {
213
+ await this._vaultConnector.removeKey(this._config.blobStorageEncryptionKeyId);
214
+ }
215
+ await this._vaultConnector.addKey(this._config.blobStorageEncryptionKeyId, VaultKeyType.ChaCha20Poly1305, Converter.base64ToBytes(decryptionKey));
216
+ }
217
+ await this._eventBusComponent.subscribe(SynchronisedStorageTopics.RegisterStorageKey, async (event) => this.registerStorageKey(event.data));
218
+ await this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemChange, async (event) => {
219
+ // Make sure the change event is from this node
220
+ if (Is.stringValue(this._nodeId) && this._nodeId === event.data.nodeId) {
221
+ await this._localSyncStateHelper.addLocalChange(event.data.storageKey, event.data.operation, event.data.id);
222
+ }
223
+ });
224
+ await this._remoteSyncStateHelper.start();
225
+ // If there are already storage keys registered, we need to activate them
226
+ for (const storageKey in this._activeStorageKeys) {
227
+ await this.activateStorageKey(storageKey);
228
+ }
229
+ }
230
+ /**
231
+ * The component needs to be stopped when the node is closed.
232
+ * @param nodeLoggingComponentType The node logging component type.
233
+ * @returns Nothing.
234
+ */
235
+ async stop(nodeLoggingComponentType) {
236
+ for (const storageKey in this._activeStorageKeys) {
237
+ this._activeStorageKeys[storageKey] = false;
238
+ await this._taskSchedulerComponent.removeTask(`synchronised-storage-update-${storageKey}`);
239
+ await this._taskSchedulerComponent.removeTask(`synchronised-storage-consolidation-${storageKey}`);
240
+ }
241
+ }
242
+ /**
243
+ * Get the decryption key for the synchronised storage.
244
+ * This is used to decrypt the data stored in the synchronised storage.
245
+ * @param actionRequest The action request used in the verifiable credential.
246
+ * @returns The decryption key.
247
+ */
248
+ async getDecryptionKey(actionRequest) {
249
+ if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
250
+ throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "notTrustedNode");
251
+ }
252
+ Guards.objectValue(SynchronisedStorageService.CLASS_NAME, "actionRequest", actionRequest);
253
+ if (actionRequest.action !== "get-key") {
254
+ throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "incorrectActionType", {
255
+ action: actionRequest.action,
256
+ expecting: "get-key"
257
+ });
258
+ }
259
+ const result = await this._policyEnforcementPointComponent?.intercept({
260
+ assignee: actionRequest.requester,
261
+ assetType: SynchronisedStorageAssetTypes.DecryptionKey,
262
+ action: ActionType.Read
263
+ });
264
+ if (!(Coerce.boolean(result) ?? false)) {
265
+ throw new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, "decryptionKeyNotAllowed", {
266
+ nodeId: actionRequest.requester
267
+ });
268
+ }
269
+ await this._logging?.log({
270
+ level: "info",
271
+ source: SynchronisedStorageService.CLASS_NAME,
272
+ message: "decryptionKeyRequest",
273
+ data: {
274
+ nodeId: actionRequest.requester
275
+ }
276
+ });
277
+ const key = await this._vaultConnector.getKey(this._config.blobStorageEncryptionKeyId);
278
+ if (Is.undefined(key.privateKey)) {
279
+ throw new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, "decryptionKeyNotFound");
280
+ }
281
+ return Converter.bytesToBase64(key.privateKey);
282
+ }
283
+ /**
284
+ * Synchronise a set of changes from an untrusted node, assumes this is a trusted node.
285
+ * @param syncChangeSet The change set to synchronise.
286
+ * @param actionRequest The action request used in the verifiable credential.
287
+ * @returns Nothing.
288
+ */
289
+ async syncChangeSet(syncChangeSet, actionRequest) {
290
+ if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
291
+ throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "notTrustedNode");
292
+ }
293
+ Guards.object(SynchronisedStorageService.CLASS_NAME, "syncChangeSet", syncChangeSet);
294
+ Guards.objectValue(SynchronisedStorageService.CLASS_NAME, "actionRequest", actionRequest);
295
+ if (actionRequest.action !== "sync-changeset") {
296
+ throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "incorrectActionType", {
297
+ action: actionRequest.action,
298
+ expecting: "sync-changeset"
299
+ });
300
+ }
301
+ const result = await this._policyEnforcementPointComponent?.intercept({
302
+ assignee: actionRequest.requester,
303
+ assetType: SynchronisedStorageAssetTypes.ChangeSet,
304
+ action: ActionType.Read
305
+ });
306
+ if (!(Coerce.boolean(result) ?? false)) {
307
+ throw new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, "changeSetNotAllowed", {
308
+ nodeId: actionRequest.requester,
309
+ changeSetStorageId: syncChangeSet.id
310
+ });
311
+ }
312
+ await this._logging?.log({
313
+ level: "info",
314
+ source: SynchronisedStorageService.CLASS_NAME,
315
+ message: "syncChangeSetForRemoteNode",
316
+ data: {
317
+ changeSetStorageId: syncChangeSet.id,
318
+ nodeId: actionRequest.requester
319
+ }
320
+ });
321
+ const copy = await this._changeSetHelper.copyChangeset(syncChangeSet);
322
+ if (!Is.empty(copy)) {
323
+ // Apply the changes to this node
324
+ await this._changeSetHelper.applyChangeset(copy.syncChangeSet);
325
+ // And update the sync state with the latest changes
326
+ await this._remoteSyncStateHelper.addChangeSetToSyncState(copy.syncChangeSet.storageKey, copy.changeSetStorageId);
327
+ }
328
+ }
329
+ /**
330
+ * Start the sync with further updates after an interval.
331
+ * @param storageKey The storage key to sync.
332
+ * @returns Nothing.
333
+ * @internal
334
+ */
335
+ async startEntitySync(storageKey) {
336
+ try {
337
+ await this._logging?.log({
338
+ level: "info",
339
+ source: SynchronisedStorageService.CLASS_NAME,
340
+ message: "startEntitySync",
341
+ data: {
342
+ storageKey
343
+ }
344
+ });
345
+ // First we check for remote changes
346
+ await this.updateFromRemoteSyncState(storageKey);
347
+ // Now send any updates we have to the remote storage
348
+ await this.updateFromLocalSyncState(storageKey);
349
+ }
350
+ catch (error) {
351
+ await this._logging?.log({
352
+ level: "error",
353
+ source: SynchronisedStorageService.CLASS_NAME,
354
+ message: "entitySyncFailed",
355
+ error: BaseError.fromError(error)
356
+ });
357
+ }
358
+ }
359
+ /**
360
+ * Check for updates in the remote storage.
361
+ * @param storageKey The storage key to check for updates.
362
+ * @returns Nothing.
363
+ * @internal
364
+ */
365
+ async updateFromRemoteSyncState(storageKey) {
366
+ await this._logging?.log({
367
+ level: "info",
368
+ source: SynchronisedStorageService.CLASS_NAME,
369
+ message: "updateFromRemoteSyncState",
370
+ data: {
371
+ storageKey
372
+ }
373
+ });
374
+ // Get the verifiable sync pointer store from the verifiable storage
375
+ const verifiableSyncPointerStore = await this._remoteSyncStateHelper.getVerifiableSyncPointerStore();
376
+ if (!Is.empty(verifiableSyncPointerStore.syncPointers[storageKey])) {
377
+ // Load the sync state from the remote blob storage using the sync pointer
378
+ // to load the sync state
379
+ const remoteSyncState = await this._remoteSyncStateHelper.getSyncState(verifiableSyncPointerStore.syncPointers[storageKey]);
380
+ // If we got the sync state we can try and sync from it
381
+ if (!Is.undefined(remoteSyncState)) {
382
+ await this._localSyncStateHelper.applySyncState(storageKey, remoteSyncState);
383
+ }
384
+ }
385
+ }
386
+ /**
387
+ * Find any local updates and send them to the remote storage.
388
+ * @returns Nothing.
389
+ * @internal
390
+ */
391
+ async updateFromLocalSyncState(storageKey) {
392
+ await this._logging?.log({
393
+ level: "info",
394
+ source: SynchronisedStorageService.CLASS_NAME,
395
+ message: "updateFromLocalSyncState",
396
+ data: {
397
+ storageKey
398
+ }
399
+ });
400
+ const localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);
401
+ if (localChangeSnapshots.length > 0) {
402
+ const localChangeSnapshot = localChangeSnapshots[0];
403
+ if (Is.arrayValue(localChangeSnapshot.changes)) {
404
+ await this._remoteSyncStateHelper.buildChangeSet(storageKey, localChangeSnapshot.changes, async (syncChangeSet, changeSetStorageId) => {
405
+ if (Is.empty(syncChangeSet) && Is.empty(changeSetStorageId)) {
406
+ await this._logging?.log({
407
+ level: "info",
408
+ source: SynchronisedStorageService.CLASS_NAME,
409
+ message: "builtStorageChangeSetNone",
410
+ data: {
411
+ storageKey
412
+ }
413
+ });
414
+ }
415
+ else {
416
+ await this._logging?.log({
417
+ level: "info",
418
+ source: SynchronisedStorageService.CLASS_NAME,
419
+ message: "builtStorageChangeSet",
420
+ data: {
421
+ storageKey,
422
+ changeSetStorageId
423
+ }
424
+ });
425
+ // Send the local changes to the remote storage if we are a trusted node
426
+ if (Is.empty(this._trustedSynchronisedStorageComponent) &&
427
+ Is.stringValue(changeSetStorageId)) {
428
+ // If we are a trusted node, we can add the change set to the sync state
429
+ // and remove the local change snapshot
430
+ await this._remoteSyncStateHelper.addChangeSetToSyncState(storageKey, changeSetStorageId);
431
+ await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
432
+ }
433
+ else if (!Is.empty(this._trustedSynchronisedStorageComponent) &&
434
+ Is.object(syncChangeSet) &&
435
+ Is.stringValue(this._nodeId)) {
436
+ // If we are not a trusted node, we need to send the changes to the trusted node
437
+ // and then remove the local change snapshot
438
+ await this._logging?.log({
439
+ level: "info",
440
+ source: SynchronisedStorageService.CLASS_NAME,
441
+ message: "sendingChangeSetToTrustedNode",
442
+ data: {
443
+ storageKey,
444
+ changeSetStorageId
445
+ }
446
+ });
447
+ const actionRequest = {
448
+ "@context": IdentityAuthenticationContexts.ContextRoot,
449
+ type: IdentityAuthenticationTypes.ActionRequest,
450
+ action: "sync-changeset",
451
+ requester: this._nodeId
452
+ };
453
+ await this._trustedSynchronisedStorageComponent.syncChangeSet(syncChangeSet, actionRequest);
454
+ await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
455
+ }
456
+ }
457
+ });
458
+ }
459
+ else {
460
+ await this._logging?.log({
461
+ level: "info",
462
+ source: SynchronisedStorageService.CLASS_NAME,
463
+ message: "updateFromLocalSyncStateNoChanges",
464
+ data: {
465
+ storageKey
466
+ }
467
+ });
468
+ }
469
+ }
470
+ }
471
+ /**
472
+ * Start the consolidation sync.
473
+ * @param storageKey The storage key to consolidate.
474
+ * @returns Nothing.
475
+ * @internal
476
+ */
477
+ async startConsolidationSync(storageKey) {
478
+ try {
479
+ // If we are going to perform a consolidation first take any local updates
480
+ // we have and create a changeset from them, so that anybody applying
481
+ // just changes since a consolidation can use the changeset
482
+ // and skip the consolidation
483
+ await this.updateFromLocalSyncState(storageKey);
484
+ // Now start the consolidation
485
+ await this._remoteSyncStateHelper.consolidationStart(storageKey, this._config.consolidationBatchSize ??
486
+ SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE);
487
+ }
488
+ catch (error) {
489
+ await this._logging?.log({
490
+ level: "error",
491
+ source: SynchronisedStorageService.CLASS_NAME,
492
+ message: "consolidationSyncFailed",
493
+ error: BaseError.fromError(error)
494
+ });
495
+ }
496
+ }
497
+ /**
498
+ * Register a new sync type.
499
+ * @param syncRegisterStorageKey The sync register type to register.
500
+ * @internal
501
+ */
502
+ async registerStorageKey(syncRegisterStorageKey) {
503
+ await this._logging?.log({
504
+ level: "info",
505
+ source: SynchronisedStorageService.CLASS_NAME,
506
+ message: "registerStorageKey",
507
+ data: {
508
+ storageKey: syncRegisterStorageKey.storageKey
509
+ }
510
+ });
511
+ if (Is.empty(this._activeStorageKeys[syncRegisterStorageKey.storageKey])) {
512
+ this._activeStorageKeys[syncRegisterStorageKey.storageKey] = false;
513
+ if (this._serviceStarted) {
514
+ await this.activateStorageKey(syncRegisterStorageKey.storageKey);
515
+ }
516
+ }
517
+ }
518
+ /**
519
+ * Activate a storage key.
520
+ * @param storageKey The storage key to activate.
521
+ * @internal
522
+ */
523
+ async activateStorageKey(storageKey) {
524
+ if (!Is.empty(this._activeStorageKeys[storageKey]) && !this._activeStorageKeys[storageKey]) {
525
+ await this._logging?.log({
526
+ level: "info",
527
+ source: SynchronisedStorageService.CLASS_NAME,
528
+ message: "activateStorageKey",
529
+ data: {
530
+ storageKey
531
+ }
532
+ });
533
+ this._activeStorageKeys[storageKey] = true;
534
+ if (this._config.entityUpdateIntervalMinutes > 0) {
535
+ await this._taskSchedulerComponent.addTask(`synchronised-storage-update-${storageKey}`, [
536
+ {
537
+ nextTriggerTime: Date.now(),
538
+ intervalMinutes: this._config.entityUpdateIntervalMinutes
539
+ }
540
+ ], async () => this.startEntitySync(storageKey));
541
+ }
542
+ if (!Is.empty(this._trustedSynchronisedStorageComponent) &&
543
+ this._config.consolidationIntervalMinutes > 0) {
544
+ await this._taskSchedulerComponent.addTask(`synchronised-storage-consolidation-${storageKey}`, [
545
+ {
546
+ nextTriggerTime: Date.now(),
547
+ intervalMinutes: this._config.consolidationIntervalMinutes
548
+ }
549
+ ], async () => this.startConsolidationSync(storageKey));
550
+ }
551
+ }
552
+ }
553
+ }
554
+ //# sourceMappingURL=synchronisedStorageService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synchronisedStorageService.js","sourceRoot":"","sources":["../../src/synchronisedStorageService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ITaskSchedulerComponent } from \"@twin.org/background-task-models\";\nimport {\n\tBlobStorageConnectorFactory,\n\ttype IBlobStorageConnector\n} from \"@twin.org/blob-storage-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tBaseError,\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tUnauthorizedError\n} from \"@twin.org/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport type { IEventBusComponent } from \"@twin.org/event-bus-models\";\nimport {\n\tIdentityAuthenticationContexts,\n\tIdentityAuthenticationTypes,\n\ttype IIdentityAuthenticationActionRequest\n} from \"@twin.org/identity-authentication\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IPolicyEnforcementPointComponent } from \"@twin.org/rights-management-models\";\nimport { ActionType } from \"@twin.org/standards-w3c-odrl\";\nimport {\n\ttype ISyncChangeSet,\n\ttype ISynchronisedStorageComponent,\n\ttype ISyncItemChange,\n\ttype ISyncRegisterStorageKey,\n\tSynchronisedStorageAssetTypes,\n\tSynchronisedStorageTopics\n} from \"@twin.org/synchronised-storage-models\";\nimport { type IVaultConnector, VaultConnectorFactory, VaultKeyType } from \"@twin.org/vault-models\";\nimport {\n\ttype IVerifiableStorageConnector,\n\tVerifiableStorageConnectorFactory\n} from \"@twin.org/verifiable-storage-models\";\nimport verifiableStorageKeys from \"./data/verifiableStorageKeys.json\" with { type: \"json\" };\nimport type { SyncSnapshotEntry } from \"./entities/syncSnapshotEntry.js\";\nimport { BlobStorageHelper } from \"./helpers/blobStorageHelper.js\";\nimport { ChangeSetHelper } from \"./helpers/changeSetHelper.js\";\nimport { LocalSyncStateHelper } from \"./helpers/localSyncStateHelper.js\";\nimport { RemoteSyncStateHelper } from \"./helpers/remoteSyncStateHelper.js\";\nimport type { ISynchronisedStorageServiceConfig } from \"./models/ISynchronisedStorageServiceConfig.js\";\nimport type { ISynchronisedStorageServiceConstructorOptions } from \"./models/ISynchronisedStorageServiceConstructorOptions.js\";\n\n/**\n * Class for performing synchronised storage operations.\n */\nexport class SynchronisedStorageService implements ISynchronisedStorageComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<SynchronisedStorageService>();\n\n\t/**\n\t * The default interval to check for entity updates.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES: number = 5;\n\n\t/**\n\t * The default interval to perform consolidation.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_CONSOLIDATION_INTERVAL_MINUTES: number = 60;\n\n\t/**\n\t * The default size of a consolidation batch.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_CONSOLIDATION_BATCH_SIZE: number = 100;\n\n\t/**\n\t * The default max number of consolidations to keep in storage.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_MAX_CONSOLIDATIONS: number = 5;\n\n\t/**\n\t * The logging component to use for logging.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The event bus component.\n\t * @internal\n\t */\n\tprivate readonly _eventBusComponent: IEventBusComponent;\n\n\t/**\n\t * The vault connector.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The storage connector for the sync snapshot entries.\n\t * @internal\n\t */\n\tprivate readonly _localSyncSnapshotEntryEntityStorage: IEntityStorageConnector<SyncSnapshotEntry>;\n\n\t/**\n\t * The blob storage connector to use for remote sync states.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageConnector: IBlobStorageConnector;\n\n\t/**\n\t * The verifiable storage connector to use for storing sync pointers.\n\t * @internal\n\t */\n\tprivate readonly _verifiableSyncPointerStorageConnector: IVerifiableStorageConnector;\n\n\t/**\n\t * The task scheduler component.\n\t * @internal\n\t */\n\tprivate readonly _taskSchedulerComponent: ITaskSchedulerComponent;\n\n\t/**\n\t * The synchronised storage service to use when this is not a trusted node.\n\t * @internal\n\t */\n\tprivate readonly _trustedSynchronisedStorageComponent?: ISynchronisedStorageComponent;\n\n\t/**\n\t * The policy enforcement point component, used by trusted nodes for incoming requests.\n\t * @internal\n\t */\n\tprivate readonly _policyEnforcementPointComponent?: IPolicyEnforcementPointComponent;\n\n\t/**\n\t * The blob storage helper.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageHelper: BlobStorageHelper;\n\n\t/**\n\t * The change set helper.\n\t * @internal\n\t */\n\tprivate readonly _changeSetHelper: ChangeSetHelper;\n\n\t/**\n\t * The local sync state helper to use for applying changesets.\n\t * @internal\n\t */\n\tprivate readonly _localSyncStateHelper: LocalSyncStateHelper;\n\n\t/**\n\t * The remote sync state helper to use for applying changesets.\n\t * @internal\n\t */\n\tprivate readonly _remoteSyncStateHelper: RemoteSyncStateHelper;\n\n\t/**\n\t * The options for the connector.\n\t * @internal\n\t */\n\tprivate readonly _config: Required<ISynchronisedStorageServiceConfig>;\n\n\t/**\n\t * The synchronised storage key to use for the remote synchronised storage.\n\t * @internal\n\t */\n\tprivate readonly _synchronisedStorageKey: string;\n\n\t/**\n\t * The flag to determine if the service has been started.\n\t * @internal\n\t */\n\tprivate _serviceStarted: boolean;\n\n\t/**\n\t * The active storage keys for the synchronised storage service.\n\t * @internal\n\t */\n\tprivate readonly _activeStorageKeys: { [storageKey: string]: boolean };\n\n\t/**\n\t * The identity of the node this connector is running on.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of SynchronisedStorageService.\n\t * @param options The options for the service.\n\t */\n\tconstructor(options: ISynchronisedStorageServiceConstructorOptions) {\n\t\tGuards.object<ISynchronisedStorageServiceConstructorOptions>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(options),\n\t\t\toptions\n\t\t);\n\t\tGuards.object<ISynchronisedStorageServiceConfig>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(options.config),\n\t\t\toptions.config\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(options.config.verifiableStorageKeyId),\n\t\t\toptions.config.verifiableStorageKeyId\n\t\t);\n\n\t\tthis._eventBusComponent = ComponentFactory.get(options.eventBusComponentType ?? \"event-bus\");\n\t\tthis._logging = ComponentFactory.getIfExists(options.loggingComponentType ?? \"logging\");\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType ?? \"vault\");\n\n\t\tthis._localSyncSnapshotEntryEntityStorage = EntityStorageConnectorFactory.get<\n\t\t\tIEntityStorageConnector<SyncSnapshotEntry>\n\t\t>(options.syncSnapshotStorageConnectorType ?? \"sync-snapshot-entry\");\n\n\t\tthis._verifiableSyncPointerStorageConnector = VerifiableStorageConnectorFactory.get(\n\t\t\toptions.verifiableStorageConnectorType ?? \"verifiable-storage\"\n\t\t);\n\n\t\tthis._blobStorageConnector = BlobStorageConnectorFactory.get(\n\t\t\toptions.blobStorageConnectorType ?? \"blob-storage\"\n\t\t);\n\n\t\tthis._taskSchedulerComponent = ComponentFactory.get(\n\t\t\toptions.taskSchedulerComponentType ?? \"task-scheduler\"\n\t\t);\n\n\t\t// If this is empty we assume the local node has the rights to write to the verifiable storage.\n\t\tlet isTrustedNode = true;\n\t\tif (!Is.empty(options.trustedSynchronisedStorageComponentType)) {\n\t\t\tisTrustedNode = false;\n\n\t\t\t// If it is set then we used the trusted component to send changesets to\n\t\t\tthis._trustedSynchronisedStorageComponent =\n\t\t\t\tComponentFactory.get<ISynchronisedStorageComponent>(\n\t\t\t\t\toptions.trustedSynchronisedStorageComponentType\n\t\t\t\t);\n\t\t} else {\n\t\t\t// A trusted node must have a policy enforcement point component\n\t\t\tthis._policyEnforcementPointComponent = ComponentFactory.get(\n\t\t\t\toptions?.policyEnforcementPointComponentType ?? \"policy-enforcement-point\"\n\t\t\t);\n\t\t}\n\n\t\tthis._config = {\n\t\t\tentityUpdateIntervalMinutes:\n\t\t\t\toptions.config.entityUpdateIntervalMinutes ??\n\t\t\t\tSynchronisedStorageService._DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES,\n\t\t\tconsolidationIntervalMinutes:\n\t\t\t\toptions.config.consolidationIntervalMinutes ??\n\t\t\t\tSynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MINUTES,\n\t\t\tconsolidationBatchSize:\n\t\t\t\toptions.config.consolidationBatchSize ??\n\t\t\t\tSynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE,\n\t\t\tmaxConsolidations:\n\t\t\t\toptions.config.maxConsolidations ?? SynchronisedStorageService._DEFAULT_MAX_CONSOLIDATIONS,\n\t\t\tblobStorageEncryptionKeyId:\n\t\t\t\toptions.config.blobStorageEncryptionKeyId ?? \"synchronised-storage-blob-encryption-key\",\n\t\t\tverifiableStorageKeyId: options.config.verifiableStorageKeyId\n\t\t};\n\n\t\tthis._synchronisedStorageKey =\n\t\t\tverifiableStorageKeys[\n\t\t\t\toptions.config.verifiableStorageKeyId as keyof typeof verifiableStorageKeys\n\t\t\t] ?? options.config.verifiableStorageKeyId;\n\n\t\tGuards.stringValue(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\t\"synchronisedStorageKey\",\n\t\t\tthis._synchronisedStorageKey\n\t\t);\n\n\t\tthis._blobStorageHelper = new BlobStorageHelper(\n\t\t\tthis._logging,\n\t\t\tthis._vaultConnector,\n\t\t\tthis._blobStorageConnector,\n\t\t\tthis._config.blobStorageEncryptionKeyId,\n\t\t\tisTrustedNode\n\t\t);\n\n\t\tthis._changeSetHelper = new ChangeSetHelper(\n\t\t\tthis._logging,\n\t\t\tthis._eventBusComponent,\n\t\t\tthis._blobStorageHelper\n\t\t);\n\n\t\tthis._localSyncStateHelper = new LocalSyncStateHelper(\n\t\t\tthis._logging,\n\t\t\tthis._localSyncSnapshotEntryEntityStorage,\n\t\t\tthis._changeSetHelper\n\t\t);\n\n\t\tthis._remoteSyncStateHelper = new RemoteSyncStateHelper(\n\t\t\tthis._logging,\n\t\t\tthis._eventBusComponent,\n\t\t\tthis._verifiableSyncPointerStorageConnector,\n\t\t\tthis._blobStorageHelper,\n\t\t\tthis._changeSetHelper,\n\t\t\tisTrustedNode,\n\t\t\tthis._config.maxConsolidations\n\t\t);\n\n\t\tthis._serviceStarted = false;\n\t\tthis._activeStorageKeys = {};\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn SynchronisedStorageService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The component needs to be started when the node is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.Node);\n\t\tthis._nodeId = contextIds[ContextIdKeys.Node];\n\n\t\tthis._remoteSyncStateHelper.setNodeId(this._nodeId);\n\t\tthis._changeSetHelper.setNodeId(this._nodeId);\n\n\t\tthis._remoteSyncStateHelper.setSynchronisedStorageKey(this._synchronisedStorageKey);\n\t\tthis._serviceStarted = true;\n\n\t\t// If this is not a trusted node we need to request the decryption key from a trusted node\n\t\tif (!Is.empty(this._trustedSynchronisedStorageComponent)) {\n\t\t\tconst actionRequest: IIdentityAuthenticationActionRequest = {\n\t\t\t\t\"@context\": IdentityAuthenticationContexts.ContextRoot,\n\t\t\t\ttype: IdentityAuthenticationTypes.ActionRequest,\n\t\t\t\taction: \"get-key\",\n\t\t\t\trequester: contextIds[ContextIdKeys.Node]\n\t\t\t};\n\n\t\t\tconst decryptionKey =\n\t\t\t\tawait this._trustedSynchronisedStorageComponent.getDecryptionKey(actionRequest);\n\n\t\t\t// If the key exists remove it and get a new one, in case the key has been rotated\n\t\t\tconst existingKey = await this._vaultConnector.getKey(\n\t\t\t\tthis._config.blobStorageEncryptionKeyId\n\t\t\t);\n\n\t\t\tif (!Is.empty(existingKey)) {\n\t\t\t\tawait this._vaultConnector.removeKey(this._config.blobStorageEncryptionKeyId);\n\t\t\t}\n\n\t\t\tawait this._vaultConnector.addKey(\n\t\t\t\tthis._config.blobStorageEncryptionKeyId,\n\t\t\t\tVaultKeyType.ChaCha20Poly1305,\n\t\t\t\tConverter.base64ToBytes(decryptionKey)\n\t\t\t);\n\t\t}\n\n\t\tawait this._eventBusComponent.subscribe<ISyncRegisterStorageKey>(\n\t\t\tSynchronisedStorageTopics.RegisterStorageKey,\n\t\t\tasync event => this.registerStorageKey(event.data)\n\t\t);\n\n\t\tawait this._eventBusComponent.subscribe<ISyncItemChange>(\n\t\t\tSynchronisedStorageTopics.LocalItemChange,\n\t\t\tasync event => {\n\t\t\t\t// Make sure the change event is from this node\n\t\t\t\tif (Is.stringValue(this._nodeId) && this._nodeId === event.data.nodeId) {\n\t\t\t\t\tawait this._localSyncStateHelper.addLocalChange(\n\t\t\t\t\t\tevent.data.storageKey,\n\t\t\t\t\t\tevent.data.operation,\n\t\t\t\t\t\tevent.data.id\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\n\t\tawait this._remoteSyncStateHelper.start();\n\n\t\t// If there are already storage keys registered, we need to activate them\n\t\tfor (const storageKey in this._activeStorageKeys) {\n\t\t\tawait this.activateStorageKey(storageKey);\n\t\t}\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tfor (const storageKey in this._activeStorageKeys) {\n\t\t\tthis._activeStorageKeys[storageKey] = false;\n\t\t\tawait this._taskSchedulerComponent.removeTask(`synchronised-storage-update-${storageKey}`);\n\t\t\tawait this._taskSchedulerComponent.removeTask(\n\t\t\t\t`synchronised-storage-consolidation-${storageKey}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get the decryption key for the synchronised storage.\n\t * This is used to decrypt the data stored in the synchronised storage.\n\t * @param actionRequest The action request used in the verifiable credential.\n\t * @returns The decryption key.\n\t */\n\tpublic async getDecryptionKey(\n\t\tactionRequest: IIdentityAuthenticationActionRequest\n\t): Promise<string> {\n\t\tif (!Is.empty(this._trustedSynchronisedStorageComponent)) {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"notTrustedNode\");\n\t\t}\n\n\t\tGuards.objectValue<IIdentityAuthenticationActionRequest>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(actionRequest),\n\t\t\tactionRequest\n\t\t);\n\n\t\tif (actionRequest.action !== \"get-key\") {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"incorrectActionType\", {\n\t\t\t\taction: actionRequest.action,\n\t\t\t\texpecting: \"get-key\"\n\t\t\t});\n\t\t}\n\n\t\tconst result = await this._policyEnforcementPointComponent?.intercept({\n\t\t\tassignee: actionRequest.requester,\n\t\t\tassetType: SynchronisedStorageAssetTypes.DecryptionKey,\n\t\t\taction: ActionType.Read\n\t\t});\n\n\t\tif (!(Coerce.boolean(result) ?? false)) {\n\t\t\tthrow new UnauthorizedError(\n\t\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\"decryptionKeyNotAllowed\",\n\t\t\t\t{\n\t\t\t\t\tnodeId: actionRequest.requester\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"decryptionKeyRequest\",\n\t\t\tdata: {\n\t\t\t\tnodeId: actionRequest.requester\n\t\t\t}\n\t\t});\n\n\t\tconst key = await this._vaultConnector.getKey(this._config.blobStorageEncryptionKeyId);\n\t\tif (Is.undefined(key.privateKey)) {\n\t\t\tthrow new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, \"decryptionKeyNotFound\");\n\t\t}\n\n\t\treturn Converter.bytesToBase64(key.privateKey);\n\t}\n\n\t/**\n\t * Synchronise a set of changes from an untrusted node, assumes this is a trusted node.\n\t * @param syncChangeSet The change set to synchronise.\n\t * @param actionRequest The action request used in the verifiable credential.\n\t * @returns Nothing.\n\t */\n\tpublic async syncChangeSet(\n\t\tsyncChangeSet: ISyncChangeSet,\n\t\tactionRequest: IIdentityAuthenticationActionRequest\n\t): Promise<void> {\n\t\tif (!Is.empty(this._trustedSynchronisedStorageComponent)) {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"notTrustedNode\");\n\t\t}\n\n\t\tGuards.object<ISyncChangeSet>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(syncChangeSet),\n\t\t\tsyncChangeSet\n\t\t);\n\t\tGuards.objectValue<IIdentityAuthenticationActionRequest>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(actionRequest),\n\t\t\tactionRequest\n\t\t);\n\n\t\tif (actionRequest.action !== \"sync-changeset\") {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"incorrectActionType\", {\n\t\t\t\taction: actionRequest.action,\n\t\t\t\texpecting: \"sync-changeset\"\n\t\t\t});\n\t\t}\n\n\t\tconst result = await this._policyEnforcementPointComponent?.intercept({\n\t\t\tassignee: actionRequest.requester,\n\t\t\tassetType: SynchronisedStorageAssetTypes.ChangeSet,\n\t\t\taction: ActionType.Read\n\t\t});\n\n\t\tif (!(Coerce.boolean(result) ?? false)) {\n\t\t\tthrow new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, \"changeSetNotAllowed\", {\n\t\t\t\tnodeId: actionRequest.requester,\n\t\t\t\tchangeSetStorageId: syncChangeSet.id\n\t\t\t});\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"syncChangeSetForRemoteNode\",\n\t\t\tdata: {\n\t\t\t\tchangeSetStorageId: syncChangeSet.id,\n\t\t\t\tnodeId: actionRequest.requester\n\t\t\t}\n\t\t});\n\n\t\tconst copy = await this._changeSetHelper.copyChangeset(syncChangeSet);\n\n\t\tif (!Is.empty(copy)) {\n\t\t\t// Apply the changes to this node\n\t\t\tawait this._changeSetHelper.applyChangeset(copy.syncChangeSet);\n\n\t\t\t// And update the sync state with the latest changes\n\t\t\tawait this._remoteSyncStateHelper.addChangeSetToSyncState(\n\t\t\t\tcopy.syncChangeSet.storageKey,\n\t\t\t\tcopy.changeSetStorageId\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Start the sync with further updates after an interval.\n\t * @param storageKey The storage key to sync.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async startEntitySync(storageKey: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"startEntitySync\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// First we check for remote changes\n\t\t\tawait this.updateFromRemoteSyncState(storageKey);\n\n\t\t\t// Now send any updates we have to the remote storage\n\t\t\tawait this.updateFromLocalSyncState(storageKey);\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"entitySyncFailed\",\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Check for updates in the remote storage.\n\t * @param storageKey The storage key to check for updates.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async updateFromRemoteSyncState(storageKey: string): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"updateFromRemoteSyncState\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\n\t\t// Get the verifiable sync pointer store from the verifiable storage\n\t\tconst verifiableSyncPointerStore =\n\t\t\tawait this._remoteSyncStateHelper.getVerifiableSyncPointerStore();\n\n\t\tif (!Is.empty(verifiableSyncPointerStore.syncPointers[storageKey])) {\n\t\t\t// Load the sync state from the remote blob storage using the sync pointer\n\t\t\t// to load the sync state\n\t\t\tconst remoteSyncState = await this._remoteSyncStateHelper.getSyncState(\n\t\t\t\tverifiableSyncPointerStore.syncPointers[storageKey]\n\t\t\t);\n\n\t\t\t// If we got the sync state we can try and sync from it\n\t\t\tif (!Is.undefined(remoteSyncState)) {\n\t\t\t\tawait this._localSyncStateHelper.applySyncState(storageKey, remoteSyncState);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Find any local updates and send them to the remote storage.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async updateFromLocalSyncState(storageKey: string): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"updateFromLocalSyncState\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\n\t\tconst localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);\n\n\t\tif (localChangeSnapshots.length > 0) {\n\t\t\tconst localChangeSnapshot = localChangeSnapshots[0];\n\n\t\t\tif (Is.arrayValue(localChangeSnapshot.changes)) {\n\t\t\t\tawait this._remoteSyncStateHelper.buildChangeSet(\n\t\t\t\t\tstorageKey,\n\t\t\t\t\tlocalChangeSnapshot.changes,\n\t\t\t\t\tasync (syncChangeSet, changeSetStorageId) => {\n\t\t\t\t\t\tif (Is.empty(syncChangeSet) && Is.empty(changeSetStorageId)) {\n\t\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\t\t\t\tmessage: \"builtStorageChangeSetNone\",\n\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\tstorageKey\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\t\t\t\tmessage: \"builtStorageChangeSet\",\n\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\t\t\tchangeSetStorageId\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Send the local changes to the remote storage if we are a trusted node\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tIs.empty(this._trustedSynchronisedStorageComponent) &&\n\t\t\t\t\t\t\t\tIs.stringValue(changeSetStorageId)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t// If we are a trusted node, we can add the change set to the sync state\n\t\t\t\t\t\t\t\t// and remove the local change snapshot\n\t\t\t\t\t\t\t\tawait this._remoteSyncStateHelper.addChangeSetToSyncState(\n\t\t\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\t\t\tchangeSetStorageId\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tawait this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);\n\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\t!Is.empty(this._trustedSynchronisedStorageComponent) &&\n\t\t\t\t\t\t\t\tIs.object(syncChangeSet) &&\n\t\t\t\t\t\t\t\tIs.stringValue(this._nodeId)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t// If we are not a trusted node, we need to send the changes to the trusted node\n\t\t\t\t\t\t\t\t// and then remove the local change snapshot\n\t\t\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\t\t\t\t\tmessage: \"sendingChangeSetToTrustedNode\",\n\t\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\t\t\t\tchangeSetStorageId\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst actionRequest: IIdentityAuthenticationActionRequest = {\n\t\t\t\t\t\t\t\t\t\"@context\": IdentityAuthenticationContexts.ContextRoot,\n\t\t\t\t\t\t\t\t\ttype: IdentityAuthenticationTypes.ActionRequest,\n\t\t\t\t\t\t\t\t\taction: \"sync-changeset\",\n\t\t\t\t\t\t\t\t\trequester: this._nodeId\n\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\tawait this._trustedSynchronisedStorageComponent.syncChangeSet(\n\t\t\t\t\t\t\t\t\tsyncChangeSet,\n\t\t\t\t\t\t\t\t\tactionRequest\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tawait this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\tmessage: \"updateFromLocalSyncStateNoChanges\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tstorageKey\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Start the consolidation sync.\n\t * @param storageKey The storage key to consolidate.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async startConsolidationSync(storageKey: string): Promise<void> {\n\t\ttry {\n\t\t\t// If we are going to perform a consolidation first take any local updates\n\t\t\t// we have and create a changeset from them, so that anybody applying\n\t\t\t// just changes since a consolidation can use the changeset\n\t\t\t// and skip the consolidation\n\t\t\tawait this.updateFromLocalSyncState(storageKey);\n\n\t\t\t// Now start the consolidation\n\t\t\tawait this._remoteSyncStateHelper.consolidationStart(\n\t\t\t\tstorageKey,\n\t\t\t\tthis._config.consolidationBatchSize ??\n\t\t\t\t\tSynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"consolidationSyncFailed\",\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Register a new sync type.\n\t * @param syncRegisterStorageKey The sync register type to register.\n\t * @internal\n\t */\n\tprivate async registerStorageKey(syncRegisterStorageKey: ISyncRegisterStorageKey): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"registerStorageKey\",\n\t\t\tdata: {\n\t\t\t\tstorageKey: syncRegisterStorageKey.storageKey\n\t\t\t}\n\t\t});\n\n\t\tif (Is.empty(this._activeStorageKeys[syncRegisterStorageKey.storageKey])) {\n\t\t\tthis._activeStorageKeys[syncRegisterStorageKey.storageKey] = false;\n\n\t\t\tif (this._serviceStarted) {\n\t\t\t\tawait this.activateStorageKey(syncRegisterStorageKey.storageKey);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Activate a storage key.\n\t * @param storageKey The storage key to activate.\n\t * @internal\n\t */\n\tprivate async activateStorageKey(storageKey: string): Promise<void> {\n\t\tif (!Is.empty(this._activeStorageKeys[storageKey]) && !this._activeStorageKeys[storageKey]) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"activateStorageKey\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis._activeStorageKeys[storageKey] = true;\n\n\t\t\tif (this._config.entityUpdateIntervalMinutes > 0) {\n\t\t\t\tawait this._taskSchedulerComponent.addTask(\n\t\t\t\t\t`synchronised-storage-update-${storageKey}`,\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnextTriggerTime: Date.now(),\n\t\t\t\t\t\t\tintervalMinutes: this._config.entityUpdateIntervalMinutes\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\tasync () => this.startEntitySync(storageKey)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\t!Is.empty(this._trustedSynchronisedStorageComponent) &&\n\t\t\t\tthis._config.consolidationIntervalMinutes > 0\n\t\t\t) {\n\t\t\t\tawait this._taskSchedulerComponent.addTask(\n\t\t\t\t\t`synchronised-storage-consolidation-${storageKey}`,\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnextTriggerTime: Date.now(),\n\t\t\t\t\t\t\tintervalMinutes: this._config.consolidationIntervalMinutes\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\tasync () => this.startConsolidationSync(storageKey)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}