@twin.org/synchronised-storage-service 0.0.1-next.4 → 0.0.1-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +422 -160
- package/dist/esm/index.mjs +423 -161
- package/dist/types/entities/syncSnapshotEntry.d.ts +15 -3
- package/dist/types/helpers/blobStorageHelper.d.ts +7 -1
- package/dist/types/helpers/changeSetHelper.d.ts +8 -1
- package/dist/types/helpers/localSyncStateHelper.d.ts +4 -3
- package/dist/types/helpers/remoteSyncStateHelper.d.ts +3 -2
- package/dist/types/models/ISyncSnapshot.d.ts +8 -0
- package/dist/types/models/ISyncState.d.ts +4 -0
- package/dist/types/models/ISynchronisedStorageServiceConfig.d.ts +5 -0
- package/docs/architecture.md +4 -1
- package/docs/changelog.md +28 -0
- package/docs/open-api/spec.json +2 -0
- package/docs/reference/classes/SyncSnapshotEntry.md +29 -5
- package/docs/reference/interfaces/ISyncSnapshot.md +16 -0
- package/docs/reference/interfaces/ISyncState.md +8 -0
- package/docs/reference/interfaces/ISynchronisedStorageServiceConfig.md +14 -0
- package/locales/en.json +16 -9
- package/package.json +2 -2
package/dist/cjs/index.cjs
CHANGED
|
@@ -23,6 +23,10 @@ exports.SyncSnapshotEntry = class SyncSnapshotEntry {
|
|
|
23
23
|
* The id for the snapshot.
|
|
24
24
|
*/
|
|
25
25
|
id;
|
|
26
|
+
/**
|
|
27
|
+
* The version for the snapshot.
|
|
28
|
+
*/
|
|
29
|
+
version;
|
|
26
30
|
/**
|
|
27
31
|
* The storage key for the snapshot i.e. which entity is being synchronized.
|
|
28
32
|
*/
|
|
@@ -36,9 +40,17 @@ exports.SyncSnapshotEntry = class SyncSnapshotEntry {
|
|
|
36
40
|
*/
|
|
37
41
|
dateModified;
|
|
38
42
|
/**
|
|
39
|
-
* The flag to determine if this is the
|
|
43
|
+
* The flag to determine if this is the snapshot is the local one containing changes for this node.
|
|
44
|
+
*/
|
|
45
|
+
isLocal;
|
|
46
|
+
/**
|
|
47
|
+
* The flag to determine if this is a consolidated snapshot.
|
|
40
48
|
*/
|
|
41
|
-
|
|
49
|
+
isConsolidated;
|
|
50
|
+
/**
|
|
51
|
+
* The epoch for the changeset.
|
|
52
|
+
*/
|
|
53
|
+
epoch;
|
|
42
54
|
/**
|
|
43
55
|
* The ids of the storage for the change sets in the snapshot, if this is not a local snapshot.
|
|
44
56
|
*/
|
|
@@ -52,6 +64,10 @@ __decorate([
|
|
|
52
64
|
entity.property({ type: "string", isPrimary: true }),
|
|
53
65
|
__metadata("design:type", String)
|
|
54
66
|
], exports.SyncSnapshotEntry.prototype, "id", void 0);
|
|
67
|
+
__decorate([
|
|
68
|
+
entity.property({ type: "string" }),
|
|
69
|
+
__metadata("design:type", String)
|
|
70
|
+
], exports.SyncSnapshotEntry.prototype, "version", void 0);
|
|
55
71
|
__decorate([
|
|
56
72
|
entity.property({ type: "string", isSecondary: true }),
|
|
57
73
|
__metadata("design:type", String)
|
|
@@ -61,13 +77,21 @@ __decorate([
|
|
|
61
77
|
__metadata("design:type", String)
|
|
62
78
|
], exports.SyncSnapshotEntry.prototype, "dateCreated", void 0);
|
|
63
79
|
__decorate([
|
|
64
|
-
entity.property({ type: "string"
|
|
80
|
+
entity.property({ type: "string" }),
|
|
65
81
|
__metadata("design:type", String)
|
|
66
82
|
], exports.SyncSnapshotEntry.prototype, "dateModified", void 0);
|
|
67
83
|
__decorate([
|
|
68
|
-
entity.property({ type: "boolean"
|
|
84
|
+
entity.property({ type: "boolean" }),
|
|
69
85
|
__metadata("design:type", Boolean)
|
|
70
|
-
], exports.SyncSnapshotEntry.prototype, "
|
|
86
|
+
], exports.SyncSnapshotEntry.prototype, "isLocal", void 0);
|
|
87
|
+
__decorate([
|
|
88
|
+
entity.property({ type: "boolean" }),
|
|
89
|
+
__metadata("design:type", Boolean)
|
|
90
|
+
], exports.SyncSnapshotEntry.prototype, "isConsolidated", void 0);
|
|
91
|
+
__decorate([
|
|
92
|
+
entity.property({ type: "number" }),
|
|
93
|
+
__metadata("design:type", Number)
|
|
94
|
+
], exports.SyncSnapshotEntry.prototype, "epoch", void 0);
|
|
71
95
|
__decorate([
|
|
72
96
|
entity.property({ type: "array", itemType: "string", optional: true }),
|
|
73
97
|
__metadata("design:type", Array)
|
|
@@ -116,6 +140,7 @@ function generateRestRoutesSynchronisedStorage(baseRouteName, componentName) {
|
|
|
116
140
|
body: {
|
|
117
141
|
id: "0909090909090909090909090909090909090909090909090909090909090909",
|
|
118
142
|
dateCreated: "2025-05-29T01:00:00.000Z",
|
|
143
|
+
dateModified: "2025-05-29T01:00:00.000Z",
|
|
119
144
|
nodeIdentity: "did:entity-storage:0xd2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2",
|
|
120
145
|
changes: [
|
|
121
146
|
{
|
|
@@ -316,7 +341,7 @@ class BlobStorageHelper {
|
|
|
316
341
|
* @param blobId The id of the blob to apply.
|
|
317
342
|
* @returns The blob.
|
|
318
343
|
*/
|
|
319
|
-
async
|
|
344
|
+
async loadBlob(blobId) {
|
|
320
345
|
await this._logging?.log({
|
|
321
346
|
level: "info",
|
|
322
347
|
source: this.CLASS_NAME,
|
|
@@ -409,6 +434,51 @@ class BlobStorageHelper {
|
|
|
409
434
|
throw error;
|
|
410
435
|
}
|
|
411
436
|
}
|
|
437
|
+
/**
|
|
438
|
+
* Remove a blob from storage.
|
|
439
|
+
* @param blobId The id of the blob to remove.
|
|
440
|
+
* @returns Nothing.
|
|
441
|
+
*/
|
|
442
|
+
async removeBlob(blobId) {
|
|
443
|
+
await this._logging?.log({
|
|
444
|
+
level: "info",
|
|
445
|
+
source: this.CLASS_NAME,
|
|
446
|
+
message: "removeBlob",
|
|
447
|
+
data: {
|
|
448
|
+
blobId
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
try {
|
|
452
|
+
await this._blobStorageConnector.remove(blobId);
|
|
453
|
+
await this._logging?.log({
|
|
454
|
+
level: "info",
|
|
455
|
+
source: this.CLASS_NAME,
|
|
456
|
+
message: "removedBlob",
|
|
457
|
+
data: {
|
|
458
|
+
blobId
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
await this._logging?.log({
|
|
464
|
+
level: "error",
|
|
465
|
+
source: this.CLASS_NAME,
|
|
466
|
+
message: "removeBlobFailed",
|
|
467
|
+
data: {
|
|
468
|
+
blobId
|
|
469
|
+
},
|
|
470
|
+
error: core.BaseError.fromError(error)
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
await this._logging?.log({
|
|
474
|
+
level: "info",
|
|
475
|
+
source: this.CLASS_NAME,
|
|
476
|
+
message: "removeBlobEmpty",
|
|
477
|
+
data: {
|
|
478
|
+
blobId
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}
|
|
412
482
|
}
|
|
413
483
|
|
|
414
484
|
// Copyright 2024 IOTA Stiftung.
|
|
@@ -488,7 +558,7 @@ class ChangeSetHelper {
|
|
|
488
558
|
}
|
|
489
559
|
});
|
|
490
560
|
try {
|
|
491
|
-
const syncChangeSet = await this._blobStorageHelper.
|
|
561
|
+
const syncChangeSet = await this._blobStorageHelper.loadBlob(changeSetStorageId);
|
|
492
562
|
if (core.Is.object(syncChangeSet)) {
|
|
493
563
|
const verified = await this.verifyChangesetProof(syncChangeSet);
|
|
494
564
|
return verified ? syncChangeSet : undefined;
|
|
@@ -521,7 +591,9 @@ class ChangeSetHelper {
|
|
|
521
591
|
*/
|
|
522
592
|
async getAndApplyChangeset(changeSetStorageId) {
|
|
523
593
|
const syncChangeset = await this.getAndVerifyChangeset(changeSetStorageId);
|
|
524
|
-
|
|
594
|
+
// Only apply changesets from other nodes, we don't want to overwrite
|
|
595
|
+
// any changes we have made to local entity storage
|
|
596
|
+
if (!core.Is.empty(syncChangeset) && syncChangeset.nodeIdentity !== this._nodeIdentity) {
|
|
525
597
|
await this.applyChangeset(syncChangeset);
|
|
526
598
|
}
|
|
527
599
|
return syncChangeset;
|
|
@@ -565,7 +637,8 @@ class ChangeSetHelper {
|
|
|
565
637
|
if (!core.Is.empty(change.id)) {
|
|
566
638
|
await this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.RemoteItemRemove, {
|
|
567
639
|
storageKey: syncChangeset.storageKey,
|
|
568
|
-
id: change.id
|
|
640
|
+
id: change.id,
|
|
641
|
+
nodeIdentity: syncChangeset.nodeIdentity
|
|
569
642
|
});
|
|
570
643
|
}
|
|
571
644
|
break;
|
|
@@ -707,8 +780,36 @@ class ChangeSetHelper {
|
|
|
707
780
|
}
|
|
708
781
|
}
|
|
709
782
|
}
|
|
783
|
+
/**
|
|
784
|
+
* Reset the storage for a given storage key.
|
|
785
|
+
* @param storageKey The key of the storage to reset.
|
|
786
|
+
* @param resetMode The reset mode, this will use the nodeIdentity in the entities to determine which are local/remote.
|
|
787
|
+
* @returns Nothing.
|
|
788
|
+
*/
|
|
789
|
+
async reset(storageKey, resetMode) {
|
|
790
|
+
// If we are applying a consolidation we need to reset the local db
|
|
791
|
+
// but keep any entries from the local node, as they might have been updated
|
|
792
|
+
await this._logging?.log({
|
|
793
|
+
level: "info",
|
|
794
|
+
source: this.CLASS_NAME,
|
|
795
|
+
message: "storageReset",
|
|
796
|
+
data: {
|
|
797
|
+
storageKey
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
await this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.Reset, {
|
|
801
|
+
storageKey,
|
|
802
|
+
resetMode
|
|
803
|
+
});
|
|
804
|
+
}
|
|
710
805
|
}
|
|
711
806
|
|
|
807
|
+
// Copyright 2024 IOTA Stiftung.
|
|
808
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
809
|
+
const SYNC_STATE_VERSION = "1";
|
|
810
|
+
const SYNC_POINTER_STORE_VERSION = "1";
|
|
811
|
+
const SYNC_SNAPSHOT_VERSION = "1";
|
|
812
|
+
|
|
712
813
|
// Copyright 2024 IOTA Stiftung.
|
|
713
814
|
// SPDX-License-Identifier: Apache-2.0.
|
|
714
815
|
/**
|
|
@@ -763,31 +864,38 @@ class LocalSyncStateHelper {
|
|
|
763
864
|
id
|
|
764
865
|
}
|
|
765
866
|
});
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
localChangeSnapshot.changes.
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
867
|
+
const localChangeSnapshots = await this.getSnapshots(storageKey, true);
|
|
868
|
+
if (localChangeSnapshots.length > 0) {
|
|
869
|
+
const localChangeSnapshot = localChangeSnapshots[0];
|
|
870
|
+
localChangeSnapshot.changes ??= [];
|
|
871
|
+
// If we already have a change for this id we are
|
|
872
|
+
// about to supersede it, we remove the previous change
|
|
873
|
+
// to avoid having multiple changes for the same id
|
|
874
|
+
const previousChangeIndex = localChangeSnapshot.changes.findIndex(change => change.id === id);
|
|
875
|
+
if (previousChangeIndex !== -1) {
|
|
876
|
+
localChangeSnapshot.changes.splice(previousChangeIndex, 1);
|
|
877
|
+
}
|
|
878
|
+
// If we already have changes from previous updates
|
|
879
|
+
// then make sure we update the dateModified, otherwise
|
|
880
|
+
// we assume this is the first change and setting modified is not necessary
|
|
881
|
+
if (localChangeSnapshot.changes.length > 0) {
|
|
882
|
+
localChangeSnapshot.dateModified = new Date(Date.now()).toISOString();
|
|
883
|
+
}
|
|
884
|
+
localChangeSnapshot.changes.push({ operation, id });
|
|
885
|
+
await this.setLocalChangeSnapshot(localChangeSnapshot);
|
|
777
886
|
}
|
|
778
|
-
localChangeSnapshot.changes.push({ operation, id });
|
|
779
|
-
await this.setLocalChangeSnapshot(localChangeSnapshot);
|
|
780
887
|
}
|
|
781
888
|
/**
|
|
782
|
-
* Get the
|
|
889
|
+
* Get the snapshot which contains just the changes for this node.
|
|
783
890
|
* @param storageKey The storage key of the snapshot to get.
|
|
891
|
+
* @param isLocal Whether to get the local snapshot or not.
|
|
784
892
|
* @returns The local snapshot entry.
|
|
785
893
|
*/
|
|
786
|
-
async
|
|
894
|
+
async getSnapshots(storageKey, isLocal) {
|
|
787
895
|
await this._logging?.log({
|
|
788
896
|
level: "info",
|
|
789
897
|
source: this.CLASS_NAME,
|
|
790
|
-
message: "
|
|
898
|
+
message: "getSnapshots",
|
|
791
899
|
data: {
|
|
792
900
|
storageKey
|
|
793
901
|
}
|
|
@@ -795,8 +903,8 @@ class LocalSyncStateHelper {
|
|
|
795
903
|
const queryResult = await this._snapshotEntryEntityStorage.query({
|
|
796
904
|
conditions: [
|
|
797
905
|
{
|
|
798
|
-
property: "
|
|
799
|
-
value:
|
|
906
|
+
property: "isLocal",
|
|
907
|
+
value: isLocal,
|
|
800
908
|
comparison: entity.ComparisonOperator.Equals
|
|
801
909
|
},
|
|
802
910
|
{
|
|
@@ -810,28 +918,35 @@ class LocalSyncStateHelper {
|
|
|
810
918
|
await this._logging?.log({
|
|
811
919
|
level: "info",
|
|
812
920
|
source: this.CLASS_NAME,
|
|
813
|
-
message: "
|
|
921
|
+
message: "getSnapshotsExists",
|
|
814
922
|
data: {
|
|
815
923
|
storageKey
|
|
816
924
|
}
|
|
817
925
|
});
|
|
818
|
-
return queryResult.entities
|
|
926
|
+
return queryResult.entities;
|
|
819
927
|
}
|
|
820
928
|
await this._logging?.log({
|
|
821
929
|
level: "info",
|
|
822
930
|
source: this.CLASS_NAME,
|
|
823
|
-
message: "
|
|
931
|
+
message: "getSnapshotsDoesNotExist",
|
|
824
932
|
data: {
|
|
825
933
|
storageKey
|
|
826
934
|
}
|
|
827
935
|
});
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
936
|
+
const now = new Date(Date.now()).toISOString();
|
|
937
|
+
return [
|
|
938
|
+
{
|
|
939
|
+
version: SYNC_SNAPSHOT_VERSION,
|
|
940
|
+
id: core.Converter.bytesToHex(core.RandomHelper.generate(32)),
|
|
941
|
+
storageKey,
|
|
942
|
+
dateCreated: now,
|
|
943
|
+
dateModified: now,
|
|
944
|
+
changeSetStorageIds: [],
|
|
945
|
+
isLocal,
|
|
946
|
+
isConsolidated: false,
|
|
947
|
+
epoch: 0
|
|
948
|
+
}
|
|
949
|
+
];
|
|
835
950
|
}
|
|
836
951
|
/**
|
|
837
952
|
* Set the current local snapshot with changes for this node.
|
|
@@ -880,46 +995,139 @@ class LocalSyncStateHelper {
|
|
|
880
995
|
snapshotCount: syncState.snapshots.length
|
|
881
996
|
}
|
|
882
997
|
});
|
|
998
|
+
// Get all the existing snapshots that we have processed previously
|
|
999
|
+
let existingSnapshots = await this.getSnapshots(storageKey, false);
|
|
883
1000
|
// Sort from newest to oldest
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
const
|
|
887
|
-
|
|
1001
|
+
existingSnapshots = existingSnapshots.sort((a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime());
|
|
1002
|
+
// Sort from newest to oldest
|
|
1003
|
+
const syncStateSnapshots = syncState.snapshots.sort((a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime());
|
|
1004
|
+
// Get the newest epoch from the local storage
|
|
1005
|
+
const newestExistingEpoch = existingSnapshots[0]?.epoch ?? 0;
|
|
1006
|
+
// Get the oldest epoch from the remote storage
|
|
1007
|
+
const oldestSyncStateEpoch = syncStateSnapshots[syncStateSnapshots.length - 1]?.epoch ?? 0;
|
|
1008
|
+
// If there is a gap between the largest epoch we have locally
|
|
1009
|
+
// and the smallest epoch we have remotely then we have missed
|
|
1010
|
+
// data so we need to perform a full sync
|
|
1011
|
+
const hasEpochGap = newestExistingEpoch + 1 < oldestSyncStateEpoch;
|
|
1012
|
+
// If we have an epoch gap or no existing snapshots then we need to apply
|
|
1013
|
+
// a full sync from a consolidation
|
|
1014
|
+
if (!existingSnapshots.some(s => s.isConsolidated) || hasEpochGap) {
|
|
888
1015
|
await this._logging?.log({
|
|
889
1016
|
level: "info",
|
|
890
1017
|
source: this.CLASS_NAME,
|
|
891
|
-
message: "
|
|
1018
|
+
message: "applySnapshotNoExisting",
|
|
892
1019
|
data: {
|
|
893
|
-
|
|
894
|
-
dateCreated: new Date(snapshot.dateCreated).toISOString()
|
|
1020
|
+
storageKey
|
|
895
1021
|
}
|
|
896
1022
|
});
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
modifiedSnapshots.push({
|
|
909
|
-
localSnapshot,
|
|
910
|
-
remoteSnapshot: remoteSnapshotWithContext
|
|
1023
|
+
const mostRecentConsolidation = syncStateSnapshots.findIndex(snapshot => snapshot.isConsolidated);
|
|
1024
|
+
if (mostRecentConsolidation !== -1) {
|
|
1025
|
+
// We found the most recent consolidated snapshot, we can use it
|
|
1026
|
+
await this._logging?.log({
|
|
1027
|
+
level: "info",
|
|
1028
|
+
source: this.CLASS_NAME,
|
|
1029
|
+
message: "applySnapshotFoundConsolidated",
|
|
1030
|
+
data: {
|
|
1031
|
+
storageKey,
|
|
1032
|
+
snapshotId: syncStateSnapshots[mostRecentConsolidation].id
|
|
1033
|
+
}
|
|
911
1034
|
});
|
|
1035
|
+
// We need to reset the entity storage and remove all the remote items
|
|
1036
|
+
// so that we use just the ones from the consolidation, since
|
|
1037
|
+
// we don't have any existing there shouldn't be any remote entries
|
|
1038
|
+
// but we reset nonetheless
|
|
1039
|
+
await this._changeSetHelper.reset(storageKey, synchronisedStorageModels.SyncNodeIdentityMode.Remote);
|
|
1040
|
+
// We need to process the most recent consolidation and all changes
|
|
1041
|
+
// that were made since then, from newest to oldest (so newer changes override older ones)
|
|
1042
|
+
// Process snapshots from the consolidation point (most recent) back to the newest
|
|
1043
|
+
for (let i = mostRecentConsolidation; i >= 0; i--) {
|
|
1044
|
+
await this.processNewSnapshots([
|
|
1045
|
+
{
|
|
1046
|
+
...syncStateSnapshots[i],
|
|
1047
|
+
storageKey,
|
|
1048
|
+
isLocal: false
|
|
1049
|
+
}
|
|
1050
|
+
]);
|
|
1051
|
+
}
|
|
912
1052
|
}
|
|
913
1053
|
else {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1054
|
+
await this._logging?.log({
|
|
1055
|
+
level: "info",
|
|
1056
|
+
source: this.CLASS_NAME,
|
|
1057
|
+
message: "applySnapshotNoConsolidated",
|
|
1058
|
+
data: {
|
|
1059
|
+
storageKey
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
// We have existing consolidated remote snapshots, so we can assume that we have
|
|
1066
|
+
// applied at least one consolidation snapshot, in this case we need to look at the changes since
|
|
1067
|
+
// then and apply them if we haven't already
|
|
1068
|
+
// We don't need to apply any additional consolidated snapshots, just the changesets
|
|
1069
|
+
// Create a lookup map for the existing snapshots
|
|
1070
|
+
const existingSnapshotsMap = {};
|
|
1071
|
+
for (const snapshot of existingSnapshots) {
|
|
1072
|
+
existingSnapshotsMap[snapshot.id] = snapshot;
|
|
1073
|
+
}
|
|
1074
|
+
const newSnapshots = [];
|
|
1075
|
+
const modifiedSnapshots = [];
|
|
1076
|
+
const referencedExistingSnapshots = Object.keys(existingSnapshotsMap);
|
|
1077
|
+
let completedProcessing = false;
|
|
1078
|
+
for (const snapshot of syncStateSnapshots) {
|
|
1079
|
+
await this._logging?.log({
|
|
1080
|
+
level: "info",
|
|
1081
|
+
source: this.CLASS_NAME,
|
|
1082
|
+
message: "applySnapshot",
|
|
1083
|
+
data: {
|
|
1084
|
+
snapshotId: snapshot.id,
|
|
1085
|
+
dateCreated: new Date(snapshot.dateCreated).toISOString()
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
// See if we have the snapshot stored locally
|
|
1089
|
+
const currentSnapshot = existingSnapshotsMap[snapshot.id];
|
|
1090
|
+
// As we are referencing an existing snapshot, we need to remove it from the list
|
|
1091
|
+
// to allow us to cleanup any unreferenced snapshots later
|
|
1092
|
+
const idx = referencedExistingSnapshots.indexOf(snapshot.id);
|
|
1093
|
+
if (idx !== -1) {
|
|
1094
|
+
referencedExistingSnapshots.splice(idx, 1);
|
|
1095
|
+
}
|
|
1096
|
+
// No need to apply consolidated snapshots
|
|
1097
|
+
if (!snapshot.isConsolidated && !completedProcessing) {
|
|
1098
|
+
const updatedSnapshot = {
|
|
1099
|
+
...snapshot,
|
|
1100
|
+
storageKey,
|
|
1101
|
+
isLocal: false
|
|
1102
|
+
};
|
|
1103
|
+
if (core.Is.empty(currentSnapshot)) {
|
|
1104
|
+
// We don't have the snapshot locally, so we need to process all of it
|
|
1105
|
+
newSnapshots.push(updatedSnapshot);
|
|
1106
|
+
}
|
|
1107
|
+
else if (currentSnapshot.dateModified !== snapshot.dateModified) {
|
|
1108
|
+
// If the local snapshot has a different dateModified, we need to update it
|
|
1109
|
+
modifiedSnapshots.push({
|
|
1110
|
+
currentSnapshot,
|
|
1111
|
+
updatedSnapshot
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
// we sorted the snapshots from newest to oldest, so if we found a local snapshot
|
|
1116
|
+
// with the same dateModified as the remote snapshot, we can stop processing further
|
|
1117
|
+
completedProcessing = true;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
// We reverse the order of the snapshots to process them from oldest to newest
|
|
1122
|
+
// because we want to apply the changes in the order they were created
|
|
1123
|
+
await this.processModifiedSnapshots(modifiedSnapshots.reverse());
|
|
1124
|
+
await this.processNewSnapshots(newSnapshots.reverse());
|
|
1125
|
+
// Any ids remaining in this list are no longer referenced in the global state
|
|
1126
|
+
// so we should remove them from the local storage as they will never be updated again
|
|
1127
|
+
for (const referencedSnapshotId of referencedExistingSnapshots) {
|
|
1128
|
+
await this._snapshotEntryEntityStorage.remove(referencedSnapshotId);
|
|
917
1129
|
}
|
|
918
1130
|
}
|
|
919
|
-
// We reverse the order of the snapshots to process them from oldest to newest
|
|
920
|
-
// because we want to apply the changes in the order they were created
|
|
921
|
-
await this.processModifiedSnapshots(modifiedSnapshots.reverse());
|
|
922
|
-
await this.processNewSnapshots(newSnapshots.reverse());
|
|
923
1131
|
}
|
|
924
1132
|
/**
|
|
925
1133
|
* Process the modified snapshots and store them in the local storage.
|
|
@@ -934,15 +1142,15 @@ class LocalSyncStateHelper {
|
|
|
934
1142
|
source: this.CLASS_NAME,
|
|
935
1143
|
message: "processModifiedSnapshot",
|
|
936
1144
|
data: {
|
|
937
|
-
snapshotId: modifiedSnapshot.
|
|
938
|
-
localModified: new Date(modifiedSnapshot.
|
|
939
|
-
modifiedSnapshot.
|
|
940
|
-
remoteModified: new Date(modifiedSnapshot.
|
|
941
|
-
modifiedSnapshot.
|
|
1145
|
+
snapshotId: modifiedSnapshot.updatedSnapshot.id,
|
|
1146
|
+
localModified: new Date(modifiedSnapshot.currentSnapshot.dateModified ??
|
|
1147
|
+
modifiedSnapshot.currentSnapshot.dateCreated).toISOString(),
|
|
1148
|
+
remoteModified: new Date(modifiedSnapshot.updatedSnapshot.dateModified ??
|
|
1149
|
+
modifiedSnapshot.updatedSnapshot.dateCreated).toISOString()
|
|
942
1150
|
}
|
|
943
1151
|
});
|
|
944
|
-
const remoteChangeSetStorageIds = modifiedSnapshot.
|
|
945
|
-
const localChangeSetStorageIds = modifiedSnapshot.
|
|
1152
|
+
const remoteChangeSetStorageIds = modifiedSnapshot.updatedSnapshot.changeSetStorageIds;
|
|
1153
|
+
const localChangeSetStorageIds = modifiedSnapshot.currentSnapshot.changeSetStorageIds ?? [];
|
|
946
1154
|
if (core.Is.arrayValue(remoteChangeSetStorageIds)) {
|
|
947
1155
|
for (const storageId of remoteChangeSetStorageIds) {
|
|
948
1156
|
// Check if the local snapshot does not have the storageId
|
|
@@ -951,7 +1159,7 @@ class LocalSyncStateHelper {
|
|
|
951
1159
|
}
|
|
952
1160
|
}
|
|
953
1161
|
}
|
|
954
|
-
await this._snapshotEntryEntityStorage.set(modifiedSnapshot.
|
|
1162
|
+
await this._snapshotEntryEntityStorage.set(modifiedSnapshot.updatedSnapshot);
|
|
955
1163
|
}
|
|
956
1164
|
}
|
|
957
1165
|
/**
|
|
@@ -968,7 +1176,7 @@ class LocalSyncStateHelper {
|
|
|
968
1176
|
message: "processNewSnapshot",
|
|
969
1177
|
data: {
|
|
970
1178
|
snapshotId: newSnapshot.id,
|
|
971
|
-
|
|
1179
|
+
dateCreated: newSnapshot.dateCreated
|
|
972
1180
|
}
|
|
973
1181
|
});
|
|
974
1182
|
const newSnapshotChangeSetStorageIds = newSnapshot.changeSetStorageIds ?? [];
|
|
@@ -982,12 +1190,6 @@ class LocalSyncStateHelper {
|
|
|
982
1190
|
}
|
|
983
1191
|
}
|
|
984
1192
|
|
|
985
|
-
// Copyright 2024 IOTA Stiftung.
|
|
986
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
987
|
-
const SYNC_STATE_VERSION = "1";
|
|
988
|
-
const SYNC_POINTER_STORE_VERSION = "1";
|
|
989
|
-
const SYNC_SNAPSHOT_VERSION = "1";
|
|
990
|
-
|
|
991
1193
|
// Copyright 2024 IOTA Stiftung.
|
|
992
1194
|
// SPDX-License-Identifier: Apache-2.0.
|
|
993
1195
|
/**
|
|
@@ -1048,6 +1250,11 @@ class RemoteSyncStateHelper {
|
|
|
1048
1250
|
* @internal
|
|
1049
1251
|
*/
|
|
1050
1252
|
_isTrustedNode;
|
|
1253
|
+
/**
|
|
1254
|
+
* Maximum number of consolidations to keep in storage.
|
|
1255
|
+
* @internal
|
|
1256
|
+
*/
|
|
1257
|
+
_maxConsolidations;
|
|
1051
1258
|
/**
|
|
1052
1259
|
* Create a new instance of DecentralisedEntityStorageConnector.
|
|
1053
1260
|
* @param logging The logging connector to use for logging.
|
|
@@ -1056,14 +1263,16 @@ class RemoteSyncStateHelper {
|
|
|
1056
1263
|
* @param blobStorageHelper The blob storage helper to use for remote sync states.
|
|
1057
1264
|
* @param changeSetHelper The change set helper to use for managing changesets.
|
|
1058
1265
|
* @param isTrustedNode Whether the node is trusted or not.
|
|
1266
|
+
* @param maxConsolidations The maximum number of consolidations to keep in storage.
|
|
1059
1267
|
*/
|
|
1060
|
-
constructor(logging, eventBusComponent, verifiableSyncPointerStorageConnector, blobStorageHelper, changeSetHelper, isTrustedNode) {
|
|
1268
|
+
constructor(logging, eventBusComponent, verifiableSyncPointerStorageConnector, blobStorageHelper, changeSetHelper, isTrustedNode, maxConsolidations) {
|
|
1061
1269
|
this._logging = logging;
|
|
1062
1270
|
this._eventBusComponent = eventBusComponent;
|
|
1063
1271
|
this._verifiableSyncPointerStorageConnector = verifiableSyncPointerStorageConnector;
|
|
1064
1272
|
this._changeSetHelper = changeSetHelper;
|
|
1065
1273
|
this._blobStorageHelper = blobStorageHelper;
|
|
1066
1274
|
this._isTrustedNode = isTrustedNode;
|
|
1275
|
+
this._maxConsolidations = maxConsolidations;
|
|
1067
1276
|
this._batchResponseStorageIds = {};
|
|
1068
1277
|
this._populateFullChanges = {};
|
|
1069
1278
|
this._eventBusComponent.subscribe(synchronisedStorageModels.SynchronisedStorageTopics.BatchResponse, async (response) => {
|
|
@@ -1165,9 +1374,11 @@ class RemoteSyncStateHelper {
|
|
|
1165
1374
|
core.ObjectHelper.propertyDelete(change.entity, "nodeIdentity");
|
|
1166
1375
|
}
|
|
1167
1376
|
}
|
|
1377
|
+
const now = new Date(Date.now()).toISOString();
|
|
1168
1378
|
const syncChangeSet = {
|
|
1169
1379
|
id: core.Converter.bytesToHex(core.RandomHelper.generate(32)),
|
|
1170
|
-
dateCreated:
|
|
1380
|
+
dateCreated: now,
|
|
1381
|
+
dateModified: now,
|
|
1171
1382
|
storageKey,
|
|
1172
1383
|
changes,
|
|
1173
1384
|
nodeIdentity: this._nodeIdentity
|
|
@@ -1219,23 +1430,28 @@ class RemoteSyncStateHelper {
|
|
|
1219
1430
|
const syncPointerStore = await this.getVerifiableSyncPointerStore();
|
|
1220
1431
|
let syncState;
|
|
1221
1432
|
if (!core.Is.empty(syncPointerStore.syncPointers[storageKey])) {
|
|
1222
|
-
syncState = await this.
|
|
1433
|
+
syncState = await this.getSyncState(syncPointerStore.syncPointers[storageKey]);
|
|
1223
1434
|
}
|
|
1224
1435
|
// No current sync state, so we create a new one
|
|
1225
1436
|
if (core.Is.empty(syncState)) {
|
|
1226
|
-
syncState = { version: SYNC_STATE_VERSION, snapshots: [] };
|
|
1437
|
+
syncState = { version: SYNC_STATE_VERSION, storageKey, snapshots: [] };
|
|
1227
1438
|
}
|
|
1228
1439
|
// Sort the snapshots so the newest snapshot is last in the array
|
|
1229
1440
|
const sortedSnapshots = syncState.snapshots.sort((a, b) => a.dateCreated.localeCompare(b.dateCreated));
|
|
1230
1441
|
// Get the current snapshot, if it does not exist we create a new one
|
|
1231
1442
|
let currentSnapshot = sortedSnapshots[sortedSnapshots.length - 1];
|
|
1443
|
+
const currentEpoch = currentSnapshot?.epoch ?? 0;
|
|
1232
1444
|
const now = new Date(Date.now()).toISOString();
|
|
1233
|
-
|
|
1445
|
+
// If there is no snapshot or the current one is a consolidation
|
|
1446
|
+
// we start a new snapshot
|
|
1447
|
+
if (core.Is.empty(currentSnapshot) || currentSnapshot.isConsolidated) {
|
|
1234
1448
|
currentSnapshot = {
|
|
1235
1449
|
version: SYNC_SNAPSHOT_VERSION,
|
|
1236
1450
|
id: core.Converter.bytesToHex(core.RandomHelper.generate(32)),
|
|
1237
1451
|
dateCreated: now,
|
|
1238
1452
|
dateModified: now,
|
|
1453
|
+
isConsolidated: false,
|
|
1454
|
+
epoch: currentEpoch + 1,
|
|
1239
1455
|
changeSetStorageIds: []
|
|
1240
1456
|
};
|
|
1241
1457
|
syncState.snapshots.push(currentSnapshot);
|
|
@@ -1264,7 +1480,7 @@ class RemoteSyncStateHelper {
|
|
|
1264
1480
|
message: "consolidationStarting"
|
|
1265
1481
|
});
|
|
1266
1482
|
// Perform a batch request to start the consolidation
|
|
1267
|
-
await this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.BatchRequest, { storageKey, batchSize });
|
|
1483
|
+
await this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.BatchRequest, { storageKey, batchSize, requestMode: synchronisedStorageModels.SyncNodeIdentityMode.All });
|
|
1268
1484
|
}
|
|
1269
1485
|
/**
|
|
1270
1486
|
* Get the sync pointer store.
|
|
@@ -1343,11 +1559,37 @@ class RemoteSyncStateHelper {
|
|
|
1343
1559
|
await this._logging?.log({
|
|
1344
1560
|
level: "info",
|
|
1345
1561
|
source: this.CLASS_NAME,
|
|
1346
|
-
message: "
|
|
1562
|
+
message: "syncStateStoring",
|
|
1347
1563
|
data: {
|
|
1348
1564
|
snapshotCount: syncState.snapshots.length
|
|
1349
1565
|
}
|
|
1350
1566
|
});
|
|
1567
|
+
// Limits the number of consolidations in the list so that we can shrink decentralised
|
|
1568
|
+
// storage requirements, sort from newest to oldest so that we can easily find the
|
|
1569
|
+
// oldest snapshots to remove.
|
|
1570
|
+
const snapshots = syncState.snapshots.sort((a, b) => new Date(a.dateCreated).getTime() - new Date(b.dateCreated).getTime());
|
|
1571
|
+
// Find all the consolidation indexes
|
|
1572
|
+
const consolidationIndexes = [];
|
|
1573
|
+
for (let i = 0; i < snapshots.length; i++) {
|
|
1574
|
+
const snapshot = snapshots[i];
|
|
1575
|
+
if (snapshot.isConsolidated) {
|
|
1576
|
+
consolidationIndexes.push(i);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
if (consolidationIndexes.length > this._maxConsolidations) {
|
|
1580
|
+
// Once we have reached the max for consolidations we need to remove
|
|
1581
|
+
// all the snapshots, including non consolidated ones, beyond this point
|
|
1582
|
+
const toRemove = snapshots.slice(consolidationIndexes[this._maxConsolidations - 1] + 1);
|
|
1583
|
+
syncState.snapshots = snapshots.slice(0, consolidationIndexes[this._maxConsolidations - 1] + 1);
|
|
1584
|
+
for (const snapshot of toRemove) {
|
|
1585
|
+
// We need to remove all the storage ids associated with the snapshot
|
|
1586
|
+
if (core.Is.arrayValue(snapshot.changeSetStorageIds)) {
|
|
1587
|
+
for (const storageId of snapshot.changeSetStorageIds) {
|
|
1588
|
+
await this._blobStorageHelper.removeBlob(storageId);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1351
1593
|
return this._blobStorageHelper.saveBlob(syncState);
|
|
1352
1594
|
}
|
|
1353
1595
|
/**
|
|
@@ -1355,22 +1597,22 @@ class RemoteSyncStateHelper {
|
|
|
1355
1597
|
* @param syncPointerId The id of the sync pointer to retrieve the state for.
|
|
1356
1598
|
* @returns The remote sync state.
|
|
1357
1599
|
*/
|
|
1358
|
-
async
|
|
1600
|
+
async getSyncState(syncPointerId) {
|
|
1359
1601
|
try {
|
|
1360
1602
|
await this._logging?.log({
|
|
1361
1603
|
level: "info",
|
|
1362
1604
|
source: this.CLASS_NAME,
|
|
1363
|
-
message: "
|
|
1605
|
+
message: "syncStateRetrieving",
|
|
1364
1606
|
data: {
|
|
1365
1607
|
syncPointerId
|
|
1366
1608
|
}
|
|
1367
1609
|
});
|
|
1368
|
-
const syncState = await this._blobStorageHelper.
|
|
1610
|
+
const syncState = await this._blobStorageHelper.loadBlob(syncPointerId);
|
|
1369
1611
|
if (core.Is.object(syncState)) {
|
|
1370
1612
|
await this._logging?.log({
|
|
1371
1613
|
level: "info",
|
|
1372
1614
|
source: this.CLASS_NAME,
|
|
1373
|
-
message: "
|
|
1615
|
+
message: "syncStateRetrieved",
|
|
1374
1616
|
data: {
|
|
1375
1617
|
syncPointerId,
|
|
1376
1618
|
snapshotCount: syncState.snapshots.length
|
|
@@ -1393,7 +1635,7 @@ class RemoteSyncStateHelper {
|
|
|
1393
1635
|
await this._logging?.log({
|
|
1394
1636
|
level: "info",
|
|
1395
1637
|
source: this.CLASS_NAME,
|
|
1396
|
-
message: "
|
|
1638
|
+
message: "syncStateNotFound",
|
|
1397
1639
|
data: {
|
|
1398
1640
|
syncPointerId
|
|
1399
1641
|
}
|
|
@@ -1432,19 +1674,29 @@ class RemoteSyncStateHelper {
|
|
|
1432
1674
|
let syncState;
|
|
1433
1675
|
if (core.Is.stringValue(syncPointerStore.syncPointers[response.storageKey])) {
|
|
1434
1676
|
// If the sync pointer exists, we load the current sync state
|
|
1435
|
-
syncState = await this.
|
|
1677
|
+
syncState = await this.getSyncState(syncPointerStore.syncPointers[response.storageKey]);
|
|
1436
1678
|
}
|
|
1437
1679
|
// If the sync state does not exist, we create a new one
|
|
1438
|
-
syncState ??= {
|
|
1680
|
+
syncState ??= {
|
|
1681
|
+
version: SYNC_STATE_VERSION,
|
|
1682
|
+
storageKey: response.storageKey,
|
|
1683
|
+
snapshots: []
|
|
1684
|
+
};
|
|
1685
|
+
// Sort the snapshots so the newest snapshot is last in the array
|
|
1686
|
+
const sortedSnapshots = syncState.snapshots.sort((a, b) => a.dateCreated.localeCompare(b.dateCreated));
|
|
1687
|
+
const currentSnapshot = sortedSnapshots[sortedSnapshots.length - 1];
|
|
1688
|
+
const currentEpoch = currentSnapshot?.epoch ?? 0;
|
|
1439
1689
|
const batchSnapshot = {
|
|
1440
1690
|
version: SYNC_SNAPSHOT_VERSION,
|
|
1441
1691
|
id: core.Converter.bytesToHex(core.RandomHelper.generate(32)),
|
|
1442
1692
|
dateCreated: now,
|
|
1443
1693
|
dateModified: now,
|
|
1694
|
+
isConsolidated: true,
|
|
1695
|
+
epoch: currentEpoch + 1,
|
|
1444
1696
|
changeSetStorageIds: this._batchResponseStorageIds[response.storageKey]
|
|
1445
1697
|
};
|
|
1446
1698
|
syncState.snapshots.push(batchSnapshot);
|
|
1447
|
-
// Store the sync state
|
|
1699
|
+
// Store the updated sync state
|
|
1448
1700
|
const syncStateId = await this.storeRemoteSyncState(syncState);
|
|
1449
1701
|
syncPointerStore.syncPointers[response.storageKey] = syncStateId;
|
|
1450
1702
|
// Store the verifiable sync pointer in the verifiable storage
|
|
@@ -1474,11 +1726,14 @@ class RemoteSyncStateHelper {
|
|
|
1474
1726
|
id: response.id
|
|
1475
1727
|
}
|
|
1476
1728
|
});
|
|
1729
|
+
// We have received a response to an item request, find the right storage
|
|
1730
|
+
// for the request id
|
|
1477
1731
|
if (!core.Is.empty(this._populateFullChanges[response.storageKey])) {
|
|
1478
1732
|
const idx = this._populateFullChanges[response.storageKey].requestIds.indexOf(response.id);
|
|
1479
1733
|
if (idx !== -1) {
|
|
1480
1734
|
this._populateFullChanges[response.storageKey].requestIds.splice(idx, 1);
|
|
1481
1735
|
this._populateFullChanges[response.storageKey].entities[response.id] = response.entity;
|
|
1736
|
+
// If there are no request ids remaining we can complete the population
|
|
1482
1737
|
if (this._populateFullChanges[response.storageKey].requestIds.length === 0) {
|
|
1483
1738
|
await this._populateFullChanges[response.storageKey].completeCallback();
|
|
1484
1739
|
}
|
|
@@ -1506,6 +1761,11 @@ class SynchronisedStorageService {
|
|
|
1506
1761
|
* @internal
|
|
1507
1762
|
*/
|
|
1508
1763
|
static _DEFAULT_CONSOLIDATION_BATCH_SIZE = 100;
|
|
1764
|
+
/**
|
|
1765
|
+
* The default max number of consolidations to keep in storage.
|
|
1766
|
+
* @internal
|
|
1767
|
+
*/
|
|
1768
|
+
static _DEFAULT_MAX_CONSOLIDATIONS = 5;
|
|
1509
1769
|
/**
|
|
1510
1770
|
* Runtime name for the class.
|
|
1511
1771
|
*/
|
|
@@ -1624,6 +1884,7 @@ class SynchronisedStorageService {
|
|
|
1624
1884
|
SynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MINUTES,
|
|
1625
1885
|
consolidationBatchSize: options.config.consolidationBatchSize ??
|
|
1626
1886
|
SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE,
|
|
1887
|
+
maxConsolidations: options.config.maxConsolidations ?? SynchronisedStorageService._DEFAULT_MAX_CONSOLIDATIONS,
|
|
1627
1888
|
blobStorageEncryptionKeyId: options.config.blobStorageEncryptionKeyId ?? "synchronised-storage-blob-encryption-key",
|
|
1628
1889
|
verifiableStorageKeyId: options.config.verifiableStorageKeyId
|
|
1629
1890
|
};
|
|
@@ -1639,11 +1900,16 @@ class SynchronisedStorageService {
|
|
|
1639
1900
|
this._blobStorageHelper = new BlobStorageHelper(this._logging, this._vaultConnector, this._blobStorageConnector, this._config.blobStorageEncryptionKeyId, this._config.isTrustedNode);
|
|
1640
1901
|
this._changeSetHelper = new ChangeSetHelper(this._logging, this._eventBusComponent, this._identityConnector, this._blobStorageHelper, this._config.synchronisedStorageMethodId);
|
|
1641
1902
|
this._localSyncStateHelper = new LocalSyncStateHelper(this._logging, this._localSyncSnapshotEntryEntityStorage, this._changeSetHelper);
|
|
1642
|
-
this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._logging, this._eventBusComponent, this._verifiableSyncPointerStorageConnector, this._blobStorageHelper, this._changeSetHelper, this._config.isTrustedNode);
|
|
1903
|
+
this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._logging, this._eventBusComponent, this._verifiableSyncPointerStorageConnector, this._blobStorageHelper, this._changeSetHelper, this._config.isTrustedNode, this._config.maxConsolidations);
|
|
1643
1904
|
this._serviceStarted = false;
|
|
1644
1905
|
this._activeStorageKeys = {};
|
|
1645
1906
|
this._eventBusComponent.subscribe(synchronisedStorageModels.SynchronisedStorageTopics.RegisterStorageKey, async (event) => this.registerStorageKey(event.data));
|
|
1646
|
-
this._eventBusComponent.subscribe(synchronisedStorageModels.SynchronisedStorageTopics.LocalItemChange, async (event) =>
|
|
1907
|
+
this._eventBusComponent.subscribe(synchronisedStorageModels.SynchronisedStorageTopics.LocalItemChange, async (event) => {
|
|
1908
|
+
// Make sure the change event is from this node
|
|
1909
|
+
if (core.Is.stringValue(this._nodeIdentity) && this._nodeIdentity === event.data.nodeIdentity) {
|
|
1910
|
+
await this._localSyncStateHelper.addLocalChange(event.data.storageKey, event.data.operation, event.data.id);
|
|
1911
|
+
}
|
|
1912
|
+
});
|
|
1647
1913
|
}
|
|
1648
1914
|
/**
|
|
1649
1915
|
* The component needs to be started when the node is initialized.
|
|
@@ -1733,7 +1999,7 @@ class SynchronisedStorageService {
|
|
|
1733
1999
|
// to store the change set in the synchronised storage.
|
|
1734
2000
|
// This will be performed using rights-management
|
|
1735
2001
|
const copy = await this._changeSetHelper.copyChangeset(syncChangeSet);
|
|
1736
|
-
if (!core.Is.empty(copy)
|
|
2002
|
+
if (!core.Is.empty(copy)) {
|
|
1737
2003
|
// Apply the changes to this node
|
|
1738
2004
|
await this._changeSetHelper.applyChangeset(copy.syncChangeSet);
|
|
1739
2005
|
// And update the sync state with the latest changes
|
|
@@ -1790,7 +2056,7 @@ class SynchronisedStorageService {
|
|
|
1790
2056
|
if (!core.Is.empty(verifiableSyncPointerStore.syncPointers[storageKey])) {
|
|
1791
2057
|
// Load the sync state from the remote blob storage using the sync pointer
|
|
1792
2058
|
// to load the sync state
|
|
1793
|
-
const remoteSyncState = await this._remoteSyncStateHelper.
|
|
2059
|
+
const remoteSyncState = await this._remoteSyncStateHelper.getSyncState(verifiableSyncPointerStore.syncPointers[storageKey]);
|
|
1794
2060
|
// If we got the sync state we can try and sync from it
|
|
1795
2061
|
if (!core.Is.undefined(remoteSyncState)) {
|
|
1796
2062
|
await this._localSyncStateHelper.applySyncState(storageKey, remoteSyncState);
|
|
@@ -1811,64 +2077,67 @@ class SynchronisedStorageService {
|
|
|
1811
2077
|
storageKey
|
|
1812
2078
|
}
|
|
1813
2079
|
});
|
|
1814
|
-
const
|
|
1815
|
-
if (
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
await this._logging?.log({
|
|
1829
|
-
level: "info",
|
|
1830
|
-
source: this.CLASS_NAME,
|
|
1831
|
-
message: "builtStorageChangeSet",
|
|
1832
|
-
data: {
|
|
1833
|
-
storageKey,
|
|
1834
|
-
changeSetStorageId
|
|
1835
|
-
}
|
|
1836
|
-
});
|
|
1837
|
-
// Send the local changes to the remote storage if we are a trusted node
|
|
1838
|
-
if (this._config.isTrustedNode && core.Is.stringValue(changeSetStorageId)) {
|
|
1839
|
-
// If we are a trusted node, we can add the change set to the sync state
|
|
1840
|
-
// and remove the local change snapshot
|
|
1841
|
-
await this._remoteSyncStateHelper.addChangeSetToSyncState(storageKey, changeSetStorageId);
|
|
1842
|
-
await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
|
|
2080
|
+
const localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);
|
|
2081
|
+
if (localChangeSnapshots.length > 0) {
|
|
2082
|
+
const localChangeSnapshot = localChangeSnapshots[0];
|
|
2083
|
+
if (core.Is.arrayValue(localChangeSnapshot.changes)) {
|
|
2084
|
+
await this._remoteSyncStateHelper.buildChangeSet(storageKey, localChangeSnapshot.changes, async (syncChangeSet, changeSetStorageId) => {
|
|
2085
|
+
if (core.Is.empty(syncChangeSet) && core.Is.empty(changeSetStorageId)) {
|
|
2086
|
+
await this._logging?.log({
|
|
2087
|
+
level: "info",
|
|
2088
|
+
source: this.CLASS_NAME,
|
|
2089
|
+
message: "builtStorageChangeSetNone",
|
|
2090
|
+
data: {
|
|
2091
|
+
storageKey
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
1843
2094
|
}
|
|
1844
|
-
else
|
|
1845
|
-
core.Is.object(syncChangeSet)) {
|
|
1846
|
-
// If we are not a trusted node, we need to send the changes to the trusted node
|
|
1847
|
-
// and then remove the local change snapshot
|
|
2095
|
+
else {
|
|
1848
2096
|
await this._logging?.log({
|
|
1849
2097
|
level: "info",
|
|
1850
2098
|
source: this.CLASS_NAME,
|
|
1851
|
-
message: "
|
|
2099
|
+
message: "builtStorageChangeSet",
|
|
1852
2100
|
data: {
|
|
1853
2101
|
storageKey,
|
|
1854
2102
|
changeSetStorageId
|
|
1855
2103
|
}
|
|
1856
2104
|
});
|
|
1857
|
-
|
|
1858
|
-
|
|
2105
|
+
// Send the local changes to the remote storage if we are a trusted node
|
|
2106
|
+
if (this._config.isTrustedNode && core.Is.stringValue(changeSetStorageId)) {
|
|
2107
|
+
// If we are a trusted node, we can add the change set to the sync state
|
|
2108
|
+
// and remove the local change snapshot
|
|
2109
|
+
await this._remoteSyncStateHelper.addChangeSetToSyncState(storageKey, changeSetStorageId);
|
|
2110
|
+
await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
|
|
2111
|
+
}
|
|
2112
|
+
else if (!core.Is.empty(this._trustedSynchronisedStorageComponent) &&
|
|
2113
|
+
core.Is.object(syncChangeSet)) {
|
|
2114
|
+
// If we are not a trusted node, we need to send the changes to the trusted node
|
|
2115
|
+
// and then remove the local change snapshot
|
|
2116
|
+
await this._logging?.log({
|
|
2117
|
+
level: "info",
|
|
2118
|
+
source: this.CLASS_NAME,
|
|
2119
|
+
message: "sendingChangeSetToTrustedNode",
|
|
2120
|
+
data: {
|
|
2121
|
+
storageKey,
|
|
2122
|
+
changeSetStorageId
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
await this._trustedSynchronisedStorageComponent.syncChangeSet(syncChangeSet);
|
|
2126
|
+
await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
|
|
2127
|
+
}
|
|
1859
2128
|
}
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
else {
|
|
2132
|
+
await this._logging?.log({
|
|
2133
|
+
level: "info",
|
|
2134
|
+
source: this.CLASS_NAME,
|
|
2135
|
+
message: "updateFromLocalSyncStateNoChanges",
|
|
2136
|
+
data: {
|
|
2137
|
+
storageKey
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
1872
2141
|
}
|
|
1873
2142
|
}
|
|
1874
2143
|
/**
|
|
@@ -1878,24 +2147,17 @@ class SynchronisedStorageService {
|
|
|
1878
2147
|
* @internal
|
|
1879
2148
|
*/
|
|
1880
2149
|
async startConsolidationSync(storageKey) {
|
|
1881
|
-
let localChangeSnapshot;
|
|
1882
2150
|
try {
|
|
1883
|
-
// If we are
|
|
1884
|
-
//
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2151
|
+
// If we are going to perform a consolidation first take any local updates
|
|
2152
|
+
// we have and create a changeset from them, so that anybody applying
|
|
2153
|
+
// just changes since a consolidation can use the changeset
|
|
2154
|
+
// and skip the consolidation
|
|
2155
|
+
await this.updateFromLocalSyncState(storageKey);
|
|
2156
|
+
// Now start the consolidation
|
|
1889
2157
|
await this._remoteSyncStateHelper.consolidationStart(storageKey, this._config.consolidationBatchSize ??
|
|
1890
2158
|
SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE);
|
|
1891
|
-
// The consolidation was successful, so we can remove the local change snapshot permanently
|
|
1892
|
-
localChangeSnapshot = undefined;
|
|
1893
2159
|
}
|
|
1894
2160
|
catch (error) {
|
|
1895
|
-
if (localChangeSnapshot) {
|
|
1896
|
-
// If the consolidation failed, we can keep the local change snapshot
|
|
1897
|
-
await this._localSyncStateHelper.setLocalChangeSnapshot(localChangeSnapshot);
|
|
1898
|
-
}
|
|
1899
2161
|
await this._logging?.log({
|
|
1900
2162
|
level: "error",
|
|
1901
2163
|
source: this.CLASS_NAME,
|