cojson 0.13.25 → 0.13.28
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/coValueCore/coValueCore.d.ts +4 -3
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +82 -66
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +3 -1
- package/dist/coValues/group.js.map +1 -1
- package/dist/localNode.d.ts +1 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +5 -2
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -1
- package/dist/permissions.js.map +1 -1
- package/dist/sync.d.ts +6 -6
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +100 -66
- package/dist/sync.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +1 -1
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/group.removeMember.test.d.ts +2 -0
- package/dist/tests/group.removeMember.test.d.ts.map +1 -0
- package/dist/tests/group.removeMember.test.js +133 -0
- package/dist/tests/group.removeMember.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.js +47 -3
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +71 -2
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +1 -0
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/coValueCore/coValueCore.ts +111 -80
- package/src/coValues/group.ts +4 -1
- package/src/localNode.ts +6 -2
- package/src/permissions.ts +6 -1
- package/src/sync.ts +117 -71
- package/src/tests/SyncStateManager.test.ts +1 -1
- package/src/tests/group.removeMember.test.ts +255 -0
- package/src/tests/sync.mesh.test.ts +60 -2
- package/src/tests/sync.peerReconciliation.test.ts +90 -2
- package/src/tests/testUtils.ts +1 -0
|
@@ -109,7 +109,7 @@ export class CoValueCore {
|
|
|
109
109
|
private readonly _decryptionCache: {
|
|
110
110
|
[key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
|
|
111
111
|
} = {};
|
|
112
|
-
private _cachedDependentOn?: RawCoID
|
|
112
|
+
private _cachedDependentOn?: Set<RawCoID>;
|
|
113
113
|
private counter: UpDownCounter;
|
|
114
114
|
|
|
115
115
|
private constructor(
|
|
@@ -897,39 +897,57 @@ export class CoValueCore {
|
|
|
897
897
|
];
|
|
898
898
|
}
|
|
899
899
|
|
|
900
|
-
getDependedOnCoValues(): RawCoID
|
|
900
|
+
getDependedOnCoValues(): Set<RawCoID> {
|
|
901
901
|
if (this._cachedDependentOn) {
|
|
902
902
|
return this._cachedDependentOn;
|
|
903
903
|
} else {
|
|
904
|
-
|
|
904
|
+
if (!this.verified) {
|
|
905
|
+
return new Set();
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const dependentOn = this.getDependedOnCoValuesFromHeaderAndSessions(
|
|
909
|
+
this.verified.header,
|
|
910
|
+
this.verified.sessions.keys(),
|
|
911
|
+
);
|
|
905
912
|
this._cachedDependentOn = dependentOn;
|
|
906
913
|
return dependentOn;
|
|
907
914
|
}
|
|
908
915
|
}
|
|
909
916
|
|
|
910
917
|
/** @internal */
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
918
|
+
getDependedOnCoValuesFromHeaderAndSessions(
|
|
919
|
+
header: CoValueHeader,
|
|
920
|
+
sessions: Iterable<SessionID>,
|
|
921
|
+
): Set<RawCoID> {
|
|
922
|
+
const deps = new Set<RawCoID>();
|
|
923
|
+
|
|
924
|
+
for (const session of sessions) {
|
|
925
|
+
const accountId = accountOrAgentIDfromSessionID(session);
|
|
926
|
+
|
|
927
|
+
if (isAccountID(accountId) && accountId !== this.id) {
|
|
928
|
+
deps.add(accountId);
|
|
929
|
+
}
|
|
914
930
|
}
|
|
915
931
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
932
|
+
if (header.ruleset.type === "group") {
|
|
933
|
+
if (isAccountID(header.ruleset.initialAdmin)) {
|
|
934
|
+
deps.add(header.ruleset.initialAdmin);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (this.verified) {
|
|
938
|
+
for (const id of getGroupDependentKeyList(
|
|
939
|
+
expectGroup(this.getCurrentContent()).keys(),
|
|
940
|
+
)) {
|
|
941
|
+
deps.add(id);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (header.ruleset.type === "ownedByGroup") {
|
|
947
|
+
deps.add(header.ruleset.group);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return deps;
|
|
933
951
|
}
|
|
934
952
|
|
|
935
953
|
waitForSync(options?: {
|
|
@@ -943,7 +961,11 @@ export class CoValueCore {
|
|
|
943
961
|
return;
|
|
944
962
|
}
|
|
945
963
|
|
|
946
|
-
const peersToActuallyLoadFrom =
|
|
964
|
+
const peersToActuallyLoadFrom = {
|
|
965
|
+
storage: [] as PeerState[],
|
|
966
|
+
server: [] as PeerState[],
|
|
967
|
+
};
|
|
968
|
+
|
|
947
969
|
for (const peer of peers) {
|
|
948
970
|
const currentState = this.peers.get(peer.id);
|
|
949
971
|
|
|
@@ -959,78 +981,87 @@ export class CoValueCore {
|
|
|
959
981
|
}
|
|
960
982
|
|
|
961
983
|
if (currentState?.type === "unavailable") {
|
|
962
|
-
if (peer.
|
|
984
|
+
if (peer.role === "server") {
|
|
985
|
+
peersToActuallyLoadFrom.server.push(peer);
|
|
963
986
|
this.markPending(peer.id);
|
|
964
|
-
peersToActuallyLoadFrom.push(peer);
|
|
965
987
|
}
|
|
966
988
|
|
|
967
989
|
continue;
|
|
968
990
|
}
|
|
969
991
|
|
|
970
992
|
if (!currentState || currentState?.type === "unknown") {
|
|
993
|
+
if (peer.role === "storage") {
|
|
994
|
+
peersToActuallyLoadFrom.storage.push(peer);
|
|
995
|
+
} else {
|
|
996
|
+
peersToActuallyLoadFrom.server.push(peer);
|
|
997
|
+
}
|
|
998
|
+
|
|
971
999
|
this.markPending(peer.id);
|
|
972
|
-
peersToActuallyLoadFrom.push(peer);
|
|
973
1000
|
}
|
|
974
1001
|
}
|
|
975
1002
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1003
|
+
// Load from storage peers first, then from server peers
|
|
1004
|
+
if (peersToActuallyLoadFrom.storage.length > 0) {
|
|
1005
|
+
await Promise.all(
|
|
1006
|
+
peersToActuallyLoadFrom.storage.map((peer) =>
|
|
1007
|
+
this.internalLoadFromPeer(peer),
|
|
1008
|
+
),
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
981
1011
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
* they may take a long time to consume the messages queue
|
|
991
|
-
*
|
|
992
|
-
* TODO: Track errors on storage and do not rely on timeout
|
|
993
|
-
*/
|
|
994
|
-
const timeoutDuration =
|
|
995
|
-
peer.role === "storage"
|
|
996
|
-
? CO_VALUE_LOADING_CONFIG.TIMEOUT * 10
|
|
997
|
-
: CO_VALUE_LOADING_CONFIG.TIMEOUT;
|
|
998
|
-
|
|
999
|
-
const waitingForPeer = new Promise<void>((resolve) => {
|
|
1000
|
-
const markNotFound = () => {
|
|
1001
|
-
if (this.peers.get(peer.id)?.type === "pending") {
|
|
1002
|
-
logger.warn("Timeout waiting for peer to load coValue", {
|
|
1003
|
-
id: this.id,
|
|
1004
|
-
peerID: peer.id,
|
|
1005
|
-
});
|
|
1006
|
-
this.markNotFoundInPeer(peer.id);
|
|
1007
|
-
}
|
|
1008
|
-
};
|
|
1012
|
+
if (peersToActuallyLoadFrom.server.length > 0) {
|
|
1013
|
+
await Promise.all(
|
|
1014
|
+
peersToActuallyLoadFrom.server.map((peer) =>
|
|
1015
|
+
this.internalLoadFromPeer(peer),
|
|
1016
|
+
),
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1009
1020
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1021
|
+
internalLoadFromPeer(peer: PeerState) {
|
|
1022
|
+
if (peer.closed) {
|
|
1023
|
+
this.markNotFoundInPeer(peer.id);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1012
1026
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
peerState?.type === "errored" ||
|
|
1019
|
-
peerState?.type === "unavailable"
|
|
1020
|
-
) {
|
|
1021
|
-
this.listeners.delete(listener);
|
|
1022
|
-
removeCloseListener();
|
|
1023
|
-
clearTimeout(timeout);
|
|
1024
|
-
resolve();
|
|
1025
|
-
}
|
|
1026
|
-
};
|
|
1027
|
+
peer.pushOutgoingMessage({
|
|
1028
|
+
action: "load",
|
|
1029
|
+
...this.knownState(),
|
|
1030
|
+
});
|
|
1031
|
+
peer.trackLoadRequestSent(this.id);
|
|
1027
1032
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1033
|
+
return new Promise<void>((resolve) => {
|
|
1034
|
+
const markNotFound = () => {
|
|
1035
|
+
if (this.peers.get(peer.id)?.type === "pending") {
|
|
1036
|
+
logger.warn("Timeout waiting for peer to load coValue", {
|
|
1037
|
+
id: this.id,
|
|
1038
|
+
peerID: peer.id,
|
|
1039
|
+
});
|
|
1040
|
+
this.markNotFoundInPeer(peer.id);
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1031
1043
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1044
|
+
const timeout = setTimeout(markNotFound, CO_VALUE_LOADING_CONFIG.TIMEOUT);
|
|
1045
|
+
const removeCloseListener = peer.addCloseListener(markNotFound);
|
|
1046
|
+
|
|
1047
|
+
const listener = (state: CoValueCore) => {
|
|
1048
|
+
const peerState = state.peers.get(peer.id);
|
|
1049
|
+
if (
|
|
1050
|
+
state.isAvailable() || // might have become available from another peer e.g. through handleNewContent
|
|
1051
|
+
peerState?.type === "available" ||
|
|
1052
|
+
peerState?.type === "errored" ||
|
|
1053
|
+
peerState?.type === "unavailable"
|
|
1054
|
+
) {
|
|
1055
|
+
this.listeners.delete(listener);
|
|
1056
|
+
removeCloseListener();
|
|
1057
|
+
clearTimeout(timeout);
|
|
1058
|
+
resolve();
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
this.listeners.add(listener);
|
|
1063
|
+
listener(this);
|
|
1064
|
+
});
|
|
1034
1065
|
}
|
|
1035
1066
|
}
|
|
1036
1067
|
|
package/src/coValues/group.ts
CHANGED
|
@@ -773,7 +773,10 @@ export class RawGroup<
|
|
|
773
773
|
) {
|
|
774
774
|
const memberKey = typeof account === "string" ? account : account.id;
|
|
775
775
|
|
|
776
|
-
this.
|
|
776
|
+
if (this.myRole() === "admin") {
|
|
777
|
+
this.rotateReadKey(memberKey);
|
|
778
|
+
}
|
|
779
|
+
|
|
777
780
|
this.set(memberKey, "revoked", "trusting");
|
|
778
781
|
}
|
|
779
782
|
|
package/src/localNode.ts
CHANGED
|
@@ -104,8 +104,12 @@ export class LocalNode {
|
|
|
104
104
|
this.coValues.delete(id);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
getCurrentAccountOrAgentID(): RawAccountID | AgentID {
|
|
108
|
+
return accountOrAgentIDfromSessionID(this.currentSessionID);
|
|
109
|
+
}
|
|
110
|
+
|
|
107
111
|
getCurrentAgent(): ControlledAccountOrAgent {
|
|
108
|
-
const accountOrAgent =
|
|
112
|
+
const accountOrAgent = this.getCurrentAccountOrAgentID();
|
|
109
113
|
if (isAgentID(accountOrAgent)) {
|
|
110
114
|
return new ControlledAgent(this.agentSecret, this.crypto);
|
|
111
115
|
}
|
|
@@ -118,7 +122,7 @@ export class LocalNode {
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
expectCurrentAccountID(reason: string): RawAccountID {
|
|
121
|
-
const accountOrAgent =
|
|
125
|
+
const accountOrAgent = this.getCurrentAccountOrAgentID();
|
|
122
126
|
if (isAgentID(accountOrAgent)) {
|
|
123
127
|
throw new Error(
|
|
124
128
|
"Current account is an agent, but expected an account: " + reason,
|
package/src/permissions.ts
CHANGED
|
@@ -452,7 +452,12 @@ function determineValidTransactionsForGroup(
|
|
|
452
452
|
change.key === transactor &&
|
|
453
453
|
change.value === "admin";
|
|
454
454
|
|
|
455
|
-
|
|
455
|
+
const currentAccountId = coValue.node.getCurrentAccountOrAgentID();
|
|
456
|
+
|
|
457
|
+
const isSelfRevoke =
|
|
458
|
+
currentAccountId === change.key && change.value === "revoked";
|
|
459
|
+
|
|
460
|
+
if (!isFirstSelfAppointment && !isSelfRevoke) {
|
|
456
461
|
if (memberState[transactor] === "admin") {
|
|
457
462
|
if (
|
|
458
463
|
memberState[affectedMember] === "admin" &&
|
package/src/sync.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { RawCoID, SessionID } from "./ids.js";
|
|
|
11
11
|
import { LocalNode } from "./localNode.js";
|
|
12
12
|
import { logger } from "./logger.js";
|
|
13
13
|
import { CoValuePriority } from "./priority.js";
|
|
14
|
+
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
|
15
|
+
import { isAccountID } from "./typeUtils/isAccountID.js";
|
|
14
16
|
|
|
15
17
|
export type CoValueKnownState = {
|
|
16
18
|
id: RawCoID;
|
|
@@ -211,9 +213,9 @@ export class SyncManager {
|
|
|
211
213
|
return;
|
|
212
214
|
}
|
|
213
215
|
|
|
214
|
-
coValue
|
|
215
|
-
.
|
|
216
|
-
|
|
216
|
+
for (const dependency of coValue.getDependedOnCoValues()) {
|
|
217
|
+
this.sendNewContentIncludingDependencies(dependency, peer);
|
|
218
|
+
}
|
|
217
219
|
|
|
218
220
|
const newContentPieces = coValue.verified.newContentSince(
|
|
219
221
|
peer.optimisticKnownStates.get(id),
|
|
@@ -298,7 +300,7 @@ export class SyncManager {
|
|
|
298
300
|
}
|
|
299
301
|
}
|
|
300
302
|
|
|
301
|
-
|
|
303
|
+
addPeer(peer: Peer) {
|
|
302
304
|
const prevPeer = this.peers[peer.id];
|
|
303
305
|
|
|
304
306
|
if (prevPeer && !prevPeer.closed) {
|
|
@@ -379,65 +381,17 @@ export class SyncManager {
|
|
|
379
381
|
peer.setKnownState(msg.id, knownStateIn(msg));
|
|
380
382
|
const coValue = this.local.getCoValue(msg.id);
|
|
381
383
|
|
|
382
|
-
if (
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
) {
|
|
386
|
-
const eligiblePeers = this.getServerAndStoragePeers(peer.id);
|
|
387
|
-
|
|
388
|
-
if (eligiblePeers.length === 0) {
|
|
389
|
-
// We don't have any eligible peers to load the coValue from
|
|
390
|
-
// so we send a known state back to the sender to let it know
|
|
391
|
-
// that the coValue is unavailable
|
|
392
|
-
peer.trackToldKnownState(msg.id);
|
|
393
|
-
this.trySendToPeer(peer, {
|
|
394
|
-
action: "known",
|
|
395
|
-
id: msg.id,
|
|
396
|
-
header: false,
|
|
397
|
-
sessions: {},
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
return;
|
|
401
|
-
} else {
|
|
402
|
-
// Syncronously updates the state loading is possible
|
|
403
|
-
coValue
|
|
404
|
-
.loadFromPeers(this.getServerAndStoragePeers(peer.id))
|
|
405
|
-
.catch((e) => {
|
|
406
|
-
logger.error("Error loading coValue in handleLoad", { err: e });
|
|
407
|
-
});
|
|
408
|
-
}
|
|
384
|
+
if (coValue.isAvailable()) {
|
|
385
|
+
this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
386
|
+
return;
|
|
409
387
|
}
|
|
410
388
|
|
|
411
|
-
|
|
412
|
-
// We need to return from handleLoad immediately and wait for the CoValue to be loaded
|
|
413
|
-
// in a new task, otherwise we might block further incoming content messages that would
|
|
414
|
-
// resolve the CoValue as available. This can happen when we receive fresh
|
|
415
|
-
// content from a client, but we are a server with our own upstream server(s)
|
|
416
|
-
coValue
|
|
417
|
-
.waitForAvailableOrUnavailable()
|
|
418
|
-
.then(async (value) => {
|
|
419
|
-
if (!value.isAvailable()) {
|
|
420
|
-
peer.trackToldKnownState(msg.id);
|
|
421
|
-
this.trySendToPeer(peer, {
|
|
422
|
-
action: "known",
|
|
423
|
-
id: msg.id,
|
|
424
|
-
header: false,
|
|
425
|
-
sessions: {},
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
389
|
+
const eligiblePeers = this.getServerAndStoragePeers(peer.id);
|
|
430
390
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
err: e,
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
} else if (coValue.isAvailable()) {
|
|
439
|
-
this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
440
|
-
} else {
|
|
391
|
+
if (eligiblePeers.length === 0) {
|
|
392
|
+
// We don't have any eligible peers to load the coValue from
|
|
393
|
+
// so we send a known state back to the sender to let it know
|
|
394
|
+
// that the coValue is unavailable
|
|
441
395
|
peer.trackToldKnownState(msg.id);
|
|
442
396
|
this.trySendToPeer(peer, {
|
|
443
397
|
action: "known",
|
|
@@ -445,9 +399,41 @@ export class SyncManager {
|
|
|
445
399
|
header: false,
|
|
446
400
|
sessions: {},
|
|
447
401
|
});
|
|
402
|
+
|
|
403
|
+
return;
|
|
448
404
|
}
|
|
449
|
-
}
|
|
450
405
|
|
|
406
|
+
coValue.loadFromPeers(eligiblePeers).catch((e) => {
|
|
407
|
+
logger.error("Error loading coValue in handleLoad", { err: e });
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// We need to return from handleLoad immediately and wait for the CoValue to be loaded
|
|
411
|
+
// in a new task, otherwise we might block further incoming content messages that would
|
|
412
|
+
// resolve the CoValue as available. This can happen when we receive fresh
|
|
413
|
+
// content from a client, but we are a server with our own upstream server(s)
|
|
414
|
+
coValue
|
|
415
|
+
.waitForAvailableOrUnavailable()
|
|
416
|
+
.then((value) => {
|
|
417
|
+
if (!value.isAvailable()) {
|
|
418
|
+
peer.trackToldKnownState(msg.id);
|
|
419
|
+
this.trySendToPeer(peer, {
|
|
420
|
+
action: "known",
|
|
421
|
+
id: msg.id,
|
|
422
|
+
header: false,
|
|
423
|
+
sessions: {},
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
430
|
+
})
|
|
431
|
+
.catch((e) => {
|
|
432
|
+
logger.error("Error loading coValue in handleLoad loading state", {
|
|
433
|
+
err: e,
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
}
|
|
451
437
|
handleKnownState(msg: KnownStateMessage, peer: PeerState) {
|
|
452
438
|
const coValue = this.local.getCoValue(msg.id);
|
|
453
439
|
|
|
@@ -494,6 +480,52 @@ export class SyncManager {
|
|
|
494
480
|
return;
|
|
495
481
|
}
|
|
496
482
|
|
|
483
|
+
let dependencyMissing = false;
|
|
484
|
+
const sessionIDs = Object.keys(msg.new) as SessionID[];
|
|
485
|
+
for (const dependency of coValue.getDependedOnCoValuesFromHeaderAndSessions(
|
|
486
|
+
msg.header,
|
|
487
|
+
sessionIDs,
|
|
488
|
+
)) {
|
|
489
|
+
const dependencyCoValue = this.local.getCoValue(dependency);
|
|
490
|
+
|
|
491
|
+
if (!dependencyCoValue.isAvailable()) {
|
|
492
|
+
if (peer.role !== "storage") {
|
|
493
|
+
this.trySendToPeer(peer, {
|
|
494
|
+
action: "load",
|
|
495
|
+
id: dependency,
|
|
496
|
+
header: false,
|
|
497
|
+
sessions: {},
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
dependencyMissing = true;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (dependencyMissing) {
|
|
506
|
+
if (peer.role !== "storage") {
|
|
507
|
+
/**
|
|
508
|
+
* If we have missing dependencies, we send a known state message to the peer
|
|
509
|
+
* to let it know that we need a correction update.
|
|
510
|
+
*
|
|
511
|
+
* Sync-wise is sub-optimal, but it gives us correctness until
|
|
512
|
+
* https://github.com/garden-co/jazz/issues/1917 is implemented.
|
|
513
|
+
*/
|
|
514
|
+
this.trySendToPeer(peer, {
|
|
515
|
+
action: "known",
|
|
516
|
+
isCorrection: true,
|
|
517
|
+
id: msg.id,
|
|
518
|
+
header: false,
|
|
519
|
+
sessions: {},
|
|
520
|
+
});
|
|
521
|
+
} else {
|
|
522
|
+
/** Cases of broken deps from storage are recovered by falling back to the server peers */
|
|
523
|
+
coValue.loadFromPeers(this.getServerAndStoragePeers(peer.id));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
497
529
|
peer.updateHeader(msg.id, true);
|
|
498
530
|
coValue.markAvailable(msg.header, peer.id);
|
|
499
531
|
}
|
|
@@ -528,6 +560,18 @@ export class SyncManager {
|
|
|
528
560
|
continue;
|
|
529
561
|
}
|
|
530
562
|
|
|
563
|
+
const accountId = accountOrAgentIDfromSessionID(sessionID);
|
|
564
|
+
|
|
565
|
+
if (isAccountID(accountId)) {
|
|
566
|
+
const account = this.local.getCoValue(accountId);
|
|
567
|
+
|
|
568
|
+
if (!account.isAvailable()) {
|
|
569
|
+
account.loadFromPeers([peer]);
|
|
570
|
+
invalidStateAssumed = true;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
531
575
|
const result = coValue.tryAddTransactions(
|
|
532
576
|
sessionID,
|
|
533
577
|
newTransactions,
|
|
@@ -607,6 +651,8 @@ export class SyncManager {
|
|
|
607
651
|
// Check if there is a inflight load operation and we
|
|
608
652
|
// are waiting for other peers to send the load request
|
|
609
653
|
if (state === "unknown" || state === undefined) {
|
|
654
|
+
// Sending a load message to the peer to get to know how much content is missing
|
|
655
|
+
// before sending the new content
|
|
610
656
|
this.trySendToPeer(peer, {
|
|
611
657
|
action: "load",
|
|
612
658
|
...coValue.knownState(),
|
|
@@ -645,7 +691,7 @@ export class SyncManager {
|
|
|
645
691
|
this.requestedSyncs.add(coValue.id);
|
|
646
692
|
}
|
|
647
693
|
|
|
648
|
-
|
|
694
|
+
syncCoValue(coValue: CoValueCore) {
|
|
649
695
|
this.requestedSyncs.delete(coValue.id);
|
|
650
696
|
|
|
651
697
|
for (const peer of this.peersInPriorityOrder()) {
|
|
@@ -668,21 +714,21 @@ export class SyncManager {
|
|
|
668
714
|
}
|
|
669
715
|
}
|
|
670
716
|
|
|
671
|
-
|
|
717
|
+
waitForSyncWithPeer(peerId: PeerID, id: RawCoID, timeout: number) {
|
|
672
718
|
const { syncState } = this;
|
|
673
719
|
const currentSyncState = syncState.getCurrentSyncState(peerId, id);
|
|
674
720
|
|
|
675
721
|
const isTheConditionAlreadyMet = currentSyncState.uploaded;
|
|
676
722
|
|
|
677
723
|
if (isTheConditionAlreadyMet) {
|
|
678
|
-
return
|
|
724
|
+
return;
|
|
679
725
|
}
|
|
680
726
|
|
|
681
727
|
const peerState = this.peers[peerId];
|
|
682
728
|
|
|
683
729
|
// The peer has been closed, so it isn't possible to sync
|
|
684
730
|
if (!peerState || peerState.closed) {
|
|
685
|
-
return
|
|
731
|
+
return;
|
|
686
732
|
}
|
|
687
733
|
|
|
688
734
|
// The client isn't subscribed to the coValue, so we won't sync it
|
|
@@ -690,7 +736,7 @@ export class SyncManager {
|
|
|
690
736
|
peerState.role === "client" &&
|
|
691
737
|
!peerState.optimisticKnownStates.has(id)
|
|
692
738
|
) {
|
|
693
|
-
return
|
|
739
|
+
return;
|
|
694
740
|
}
|
|
695
741
|
|
|
696
742
|
return new Promise((resolve, reject) => {
|
|
@@ -712,25 +758,25 @@ export class SyncManager {
|
|
|
712
758
|
});
|
|
713
759
|
}
|
|
714
760
|
|
|
715
|
-
|
|
761
|
+
waitForStorageSync(id: RawCoID, timeout = 30_000) {
|
|
716
762
|
const peers = this.getPeers();
|
|
717
763
|
|
|
718
|
-
|
|
764
|
+
return Promise.all(
|
|
719
765
|
peers
|
|
720
766
|
.filter((peer) => peer.role === "storage")
|
|
721
767
|
.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
|
|
722
768
|
);
|
|
723
769
|
}
|
|
724
770
|
|
|
725
|
-
|
|
771
|
+
waitForSync(id: RawCoID, timeout = 30_000) {
|
|
726
772
|
const peers = this.getPeers();
|
|
727
773
|
|
|
728
|
-
|
|
774
|
+
return Promise.all(
|
|
729
775
|
peers.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
|
|
730
776
|
);
|
|
731
777
|
}
|
|
732
778
|
|
|
733
|
-
|
|
779
|
+
waitForAllCoValuesSync(timeout = 60_000) {
|
|
734
780
|
const coValues = this.local.allCoValues();
|
|
735
781
|
const validCoValues = Array.from(coValues).filter(
|
|
736
782
|
(coValue) =>
|
|
@@ -257,7 +257,7 @@ describe("SyncStateManager", () => {
|
|
|
257
257
|
const group = client.node.createGroup();
|
|
258
258
|
const map = group.createMap();
|
|
259
259
|
|
|
260
|
-
await
|
|
260
|
+
await map.core.waitForSync();
|
|
261
261
|
});
|
|
262
262
|
|
|
263
263
|
test("should skip client peers that are not subscribed to the coValue", async () => {
|