cojson 0.13.5 → 0.13.7
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 +10 -0
- package/LICENSE.txt +1 -1
- package/dist/PeerState.d.ts +6 -0
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +43 -0
- package/dist/PeerState.js.map +1 -1
- package/dist/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore.js +1 -0
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValueState.d.ts +1 -0
- package/dist/coValueState.d.ts.map +1 -1
- package/dist/coValueState.js +27 -2
- package/dist/coValueState.js.map +1 -1
- package/dist/coValues/group.d.ts +1 -0
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +45 -21
- package/dist/coValues/group.js.map +1 -1
- package/dist/crypto/crypto.d.ts +2 -2
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +19 -3
- package/dist/permissions.js.map +1 -1
- package/dist/storage/FileSystem.d.ts +2 -2
- package/dist/storage/FileSystem.d.ts.map +1 -1
- package/dist/sync.d.ts +14 -4
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +146 -146
- package/dist/sync.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +51 -46
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +51 -2
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueState.test.js +31 -4
- package/dist/tests/coValueState.test.js.map +1 -1
- package/dist/tests/group.test.js +135 -2
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +13 -0
- package/dist/tests/messagesTestUtils.d.ts.map +1 -0
- package/dist/tests/messagesTestUtils.js +42 -0
- package/dist/tests/messagesTestUtils.js.map +1 -0
- package/dist/tests/sync.load.test.d.ts +2 -0
- package/dist/tests/sync.load.test.d.ts.map +1 -0
- package/dist/tests/sync.load.test.js +249 -0
- package/dist/tests/sync.load.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.d.ts +2 -0
- package/dist/tests/sync.mesh.test.d.ts.map +1 -0
- package/dist/tests/sync.mesh.test.js +157 -0
- package/dist/tests/sync.mesh.test.js.map +1 -0
- package/dist/tests/sync.peerReconciliation.test.d.ts +2 -0
- package/dist/tests/sync.peerReconciliation.test.d.ts.map +1 -0
- package/dist/tests/sync.peerReconciliation.test.js +130 -0
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -0
- package/dist/tests/sync.storage.test.d.ts +2 -0
- package/dist/tests/sync.storage.test.d.ts.map +1 -0
- package/dist/tests/sync.storage.test.js +201 -0
- package/dist/tests/sync.storage.test.js.map +1 -0
- package/dist/tests/sync.test.js +139 -1048
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.d.ts +2 -0
- package/dist/tests/sync.upload.test.d.ts.map +1 -0
- package/dist/tests/sync.upload.test.js +156 -0
- package/dist/tests/sync.upload.test.js.map +1 -0
- package/dist/tests/testUtils.d.ts +76 -33
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +153 -47
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/PeerState.ts +59 -1
- package/src/coValueCore.ts +1 -0
- package/src/coValueState.ts +34 -3
- package/src/coValues/group.ts +83 -45
- package/src/permissions.ts +31 -3
- package/src/sync.ts +169 -185
- package/src/tests/SyncStateManager.test.ts +58 -70
- package/src/tests/coValueCore.test.ts +70 -1
- package/src/tests/coValueState.test.ts +59 -5
- package/src/tests/group.test.ts +250 -2
- package/src/tests/messagesTestUtils.ts +75 -0
- package/src/tests/sync.load.test.ts +327 -0
- package/src/tests/sync.mesh.test.ts +219 -0
- package/src/tests/sync.peerReconciliation.test.ts +201 -0
- package/src/tests/sync.storage.test.ts +259 -0
- package/src/tests/sync.test.ts +170 -1245
- package/src/tests/sync.upload.test.ts +202 -0
- package/src/tests/testUtils.ts +213 -61
package/src/permissions.ts
CHANGED
|
@@ -225,6 +225,7 @@ function determineValidTransactionsForGroup(
|
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
const memberState: MemberState = {};
|
|
228
|
+
const writeOnlyKeys: Record<RawAccountID | AgentID, KeyID> = {};
|
|
228
229
|
const validTransactions: ValidTransactionsResult[] = [];
|
|
229
230
|
|
|
230
231
|
const keyRevelations = new Set<string>();
|
|
@@ -302,7 +303,8 @@ function determineValidTransactionsForGroup(
|
|
|
302
303
|
memberState[transactor] !== "adminInvite" &&
|
|
303
304
|
memberState[transactor] !== "writerInvite" &&
|
|
304
305
|
memberState[transactor] !== "readerInvite" &&
|
|
305
|
-
memberState[transactor] !== "writeOnlyInvite"
|
|
306
|
+
memberState[transactor] !== "writeOnlyInvite" &&
|
|
307
|
+
!isOwnWriteKeyRevelation(change.key, transactor, writeOnlyKeys)
|
|
306
308
|
) {
|
|
307
309
|
logPermissionError("Only admins can reveal keys");
|
|
308
310
|
continue;
|
|
@@ -370,14 +372,19 @@ function determineValidTransactionsForGroup(
|
|
|
370
372
|
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
371
373
|
continue;
|
|
372
374
|
} else if (isWriteKeyForMember(change.key)) {
|
|
375
|
+
const memberKey = getAccountOrAgentFromWriteKeyForMember(change.key);
|
|
376
|
+
|
|
373
377
|
if (
|
|
374
378
|
memberState[transactor] !== "admin" &&
|
|
375
|
-
memberState[transactor] !== "writeOnlyInvite"
|
|
379
|
+
memberState[transactor] !== "writeOnlyInvite" &&
|
|
380
|
+
memberKey !== transactor
|
|
376
381
|
) {
|
|
377
382
|
logPermissionError("Only admins can set writeKeys");
|
|
378
383
|
continue;
|
|
379
384
|
}
|
|
380
385
|
|
|
386
|
+
writeOnlyKeys[memberKey] = change.value as KeyID;
|
|
387
|
+
|
|
381
388
|
/**
|
|
382
389
|
* writeOnlyInvite need to be able to set writeKeys because every new writeOnly
|
|
383
390
|
* member comes with their own write key.
|
|
@@ -422,11 +429,12 @@ function determineValidTransactionsForGroup(
|
|
|
422
429
|
!(
|
|
423
430
|
change.value === "reader" ||
|
|
424
431
|
change.value === "writer" ||
|
|
432
|
+
change.value === "writeOnly" ||
|
|
425
433
|
change.value === "revoked"
|
|
426
434
|
)
|
|
427
435
|
) {
|
|
428
436
|
logPermissionError(
|
|
429
|
-
"Everyone can only be set to reader, writer or revoked",
|
|
437
|
+
"Everyone can only be set to reader, writer, writeOnly or revoked",
|
|
430
438
|
);
|
|
431
439
|
continue;
|
|
432
440
|
}
|
|
@@ -499,6 +507,12 @@ export function isWriteKeyForMember(
|
|
|
499
507
|
return co.startsWith("writeKeyFor_");
|
|
500
508
|
}
|
|
501
509
|
|
|
510
|
+
export function getAccountOrAgentFromWriteKeyForMember(
|
|
511
|
+
co: `writeKeyFor_${RawAccountID | AgentID}`,
|
|
512
|
+
): RawAccountID | AgentID {
|
|
513
|
+
return co.slice("writeKeyFor_".length) as RawAccountID | AgentID;
|
|
514
|
+
}
|
|
515
|
+
|
|
502
516
|
export function isKeyForKeyField(co: string): co is `${KeyID}_for_${KeyID}` {
|
|
503
517
|
return co.startsWith("key_") && co.includes("_for_key");
|
|
504
518
|
}
|
|
@@ -520,3 +534,17 @@ function isParentExtension(key: string): key is `parent_${CoID<RawGroup>}` {
|
|
|
520
534
|
function isChildExtension(key: string): key is `child_${CoID<RawGroup>}` {
|
|
521
535
|
return key.startsWith("child_");
|
|
522
536
|
}
|
|
537
|
+
|
|
538
|
+
function isOwnWriteKeyRevelation(
|
|
539
|
+
key: `${KeyID}_for_${string}`,
|
|
540
|
+
memberKey: RawAccountID | AgentID,
|
|
541
|
+
writeOnlyKeys: Record<RawAccountID | AgentID, KeyID>,
|
|
542
|
+
): key is `${KeyID}_for_${RawAccountID | AgentID}` {
|
|
543
|
+
if (Object.keys(writeOnlyKeys).length === 0) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const keyID = key.slice(0, key.indexOf("_for_"));
|
|
548
|
+
|
|
549
|
+
return writeOnlyKeys[memberKey] === keyID;
|
|
550
|
+
}
|
package/src/sync.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { ValueType, metrics } from "@opentelemetry/api";
|
|
1
|
+
import { Histogram, ValueType, metrics } from "@opentelemetry/api";
|
|
2
2
|
import { PeerState } from "./PeerState.js";
|
|
3
3
|
import { SyncStateManager } from "./SyncStateManager.js";
|
|
4
4
|
import { CoValueHeader, Transaction } from "./coValueCore.js";
|
|
5
5
|
import { CoValueCore } from "./coValueCore.js";
|
|
6
|
+
import { CoValueState } from "./coValueState.js";
|
|
6
7
|
import { Signature } from "./crypto/crypto.js";
|
|
7
8
|
import { RawCoID, SessionID } from "./ids.js";
|
|
8
9
|
import { LocalNode } from "./localNode.js";
|
|
@@ -35,8 +36,8 @@ export type LoadMessage = {
|
|
|
35
36
|
|
|
36
37
|
export type KnownStateMessage = {
|
|
37
38
|
action: "known";
|
|
38
|
-
asDependencyOf?: RawCoID;
|
|
39
39
|
isCorrection?: boolean;
|
|
40
|
+
asDependencyOf?: RawCoID;
|
|
40
41
|
} & CoValueKnownState;
|
|
41
42
|
|
|
42
43
|
export type NewContentMessage = {
|
|
@@ -122,10 +123,19 @@ export class SyncManager {
|
|
|
122
123
|
valueType: ValueType.INT,
|
|
123
124
|
unit: "peer",
|
|
124
125
|
});
|
|
126
|
+
private transactionsSizeHistogram: Histogram;
|
|
125
127
|
|
|
126
128
|
constructor(local: LocalNode) {
|
|
127
129
|
this.local = local;
|
|
128
130
|
this.syncState = new SyncStateManager(this);
|
|
131
|
+
|
|
132
|
+
this.transactionsSizeHistogram = metrics
|
|
133
|
+
.getMeter("cojson")
|
|
134
|
+
.createHistogram("jazz.transactions.size", {
|
|
135
|
+
description: "The size of transactions in a covalue",
|
|
136
|
+
unit: "bytes",
|
|
137
|
+
valueType: ValueType.INT,
|
|
138
|
+
});
|
|
129
139
|
}
|
|
130
140
|
|
|
131
141
|
syncState: SyncStateManager;
|
|
@@ -145,7 +155,10 @@ export class SyncManager {
|
|
|
145
155
|
|
|
146
156
|
getServerAndStoragePeers(excludePeerId?: PeerID): PeerState[] {
|
|
147
157
|
return this.peersInPriorityOrder().filter(
|
|
148
|
-
(peer) =>
|
|
158
|
+
(peer) =>
|
|
159
|
+
peer.isServerOrStoragePeer() &&
|
|
160
|
+
peer.id !== excludePeerId &&
|
|
161
|
+
!peer.closed,
|
|
149
162
|
);
|
|
150
163
|
}
|
|
151
164
|
|
|
@@ -189,56 +202,35 @@ export class SyncManager {
|
|
|
189
202
|
}
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
async
|
|
193
|
-
const entry = this.local.coValuesStore.get(id);
|
|
194
|
-
|
|
195
|
-
if (entry.state.type !== "available") {
|
|
196
|
-
entry.loadFromPeers([peer]).catch((e: unknown) => {
|
|
197
|
-
logger.error("Error sending load", { err: e });
|
|
198
|
-
});
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const coValue = entry.state.coValue;
|
|
203
|
-
|
|
204
|
-
for (const id of coValue.getDependedOnCoValues()) {
|
|
205
|
-
await this.subscribeToIncludingDependencies(id, peer);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (!peer.toldKnownState.has(id)) {
|
|
209
|
-
peer.toldKnownState.add(id);
|
|
210
|
-
this.trySendToPeer(peer, {
|
|
211
|
-
action: "load",
|
|
212
|
-
...coValue.knownState(),
|
|
213
|
-
}).catch((e: unknown) => {
|
|
214
|
-
logger.error("Error sending load", { err: e });
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async tellUntoldKnownStateIncludingDependencies(
|
|
220
|
-
id: RawCoID,
|
|
221
|
-
peer: PeerState,
|
|
222
|
-
asDependencyOf?: RawCoID,
|
|
223
|
-
) {
|
|
205
|
+
async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
|
|
224
206
|
const coValue = this.local.expectCoValueLoaded(id);
|
|
225
207
|
|
|
226
208
|
await Promise.all(
|
|
227
209
|
coValue
|
|
228
210
|
.getDependedOnCoValues()
|
|
229
|
-
.map((
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
),
|
|
235
|
-
),
|
|
211
|
+
.map((id) => this.sendNewContentIncludingDependencies(id, peer)),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const newContentPieces = coValue.newContentSince(
|
|
215
|
+
peer.optimisticKnownStates.get(id),
|
|
236
216
|
);
|
|
237
217
|
|
|
238
|
-
if (
|
|
218
|
+
if (newContentPieces) {
|
|
219
|
+
for (const piece of newContentPieces) {
|
|
220
|
+
this.trySendToPeer(peer, piece).catch((e: unknown) => {
|
|
221
|
+
logger.error("Error sending content piece", { err: e });
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
peer.toldKnownState.add(id);
|
|
226
|
+
peer.optimisticKnownStates.dispatch({
|
|
227
|
+
type: "COMBINE_WITH",
|
|
228
|
+
id: id,
|
|
229
|
+
value: coValue.knownState(),
|
|
230
|
+
});
|
|
231
|
+
} else if (!peer.toldKnownState.has(id)) {
|
|
239
232
|
this.trySendToPeer(peer, {
|
|
240
233
|
action: "known",
|
|
241
|
-
asDependencyOf,
|
|
242
234
|
...coValue.knownState(),
|
|
243
235
|
}).catch((e: unknown) => {
|
|
244
236
|
logger.error("Error sending known state", { err: e });
|
|
@@ -248,66 +240,101 @@ export class SyncManager {
|
|
|
248
240
|
}
|
|
249
241
|
}
|
|
250
242
|
|
|
251
|
-
async
|
|
252
|
-
const
|
|
243
|
+
async startPeerReconciliation(peer: PeerState) {
|
|
244
|
+
const coValuesOrderedByDependency: CoValueCore[] = [];
|
|
253
245
|
|
|
254
|
-
|
|
255
|
-
coValue
|
|
256
|
-
.getDependedOnCoValues()
|
|
257
|
-
.map((id) => this.sendNewContentIncludingDependencies(id, peer)),
|
|
258
|
-
);
|
|
246
|
+
const gathered = new Set<string>();
|
|
259
247
|
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
248
|
+
const buildOrderedCoValueList = (coValue: CoValueCore) => {
|
|
249
|
+
if (gathered.has(coValue.id)) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
263
252
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const sendPieces = async () => {
|
|
269
|
-
let lastYield = performance.now();
|
|
270
|
-
for (const [_i, piece] of newContentPieces.entries()) {
|
|
271
|
-
this.trySendToPeer(peer, piece).catch((e: unknown) => {
|
|
272
|
-
logger.error("Error sending content piece", { err: e });
|
|
273
|
-
});
|
|
253
|
+
gathered.add(coValue.id);
|
|
254
|
+
|
|
255
|
+
for (const id of coValue.getDependedOnCoValues()) {
|
|
256
|
+
const entry = this.local.coValuesStore.get(id);
|
|
274
257
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
258
|
+
if (entry.state.type === "available") {
|
|
259
|
+
buildOrderedCoValueList(entry.state.coValue);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
coValuesOrderedByDependency.push(coValue);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
for (const entry of this.local.coValuesStore.getValues()) {
|
|
267
|
+
switch (entry.state.type) {
|
|
268
|
+
case "unavailable":
|
|
269
|
+
// If the coValue is unavailable and we never tried this peer
|
|
270
|
+
// we try to load it from the peer
|
|
271
|
+
if (!peer.toldKnownState.has(entry.id)) {
|
|
272
|
+
await entry.loadFromPeers([peer]).catch((e: unknown) => {
|
|
273
|
+
logger.error("Error sending load", { err: e });
|
|
278
274
|
});
|
|
279
|
-
lastYield = performance.now();
|
|
280
275
|
}
|
|
281
|
-
|
|
282
|
-
|
|
276
|
+
break;
|
|
277
|
+
case "available":
|
|
278
|
+
const coValue = entry.state.coValue;
|
|
279
|
+
|
|
280
|
+
// Build the list of coValues ordered by dependency
|
|
281
|
+
// so we can send the load message in the correct order
|
|
282
|
+
buildOrderedCoValueList(coValue);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
283
285
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
+
// Fill the missing known states with empty known states
|
|
287
|
+
if (!peer.optimisticKnownStates.has(entry.id)) {
|
|
286
288
|
peer.optimisticKnownStates.dispatch({
|
|
287
|
-
type: "
|
|
288
|
-
id,
|
|
289
|
-
value: optimisticKnownStateBefore ?? emptyKnownState(id),
|
|
289
|
+
type: "SET_AS_EMPTY",
|
|
290
|
+
id: entry.id,
|
|
290
291
|
});
|
|
291
|
-
|
|
292
|
-
|
|
292
|
+
}
|
|
293
|
+
}
|
|
293
294
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
for (const coValue of coValuesOrderedByDependency) {
|
|
296
|
+
/**
|
|
297
|
+
* We send the load messages to:
|
|
298
|
+
* - Subscribe to the coValue updates
|
|
299
|
+
* - Start the sync process in case we or the other peer
|
|
300
|
+
* lacks some transactions
|
|
301
|
+
*/
|
|
302
|
+
peer.toldKnownState.add(coValue.id);
|
|
303
|
+
this.trySendToPeer(peer, {
|
|
304
|
+
action: "load",
|
|
305
|
+
...coValue.knownState(),
|
|
306
|
+
}).catch((e: unknown) => {
|
|
307
|
+
logger.error("Error sending load", { err: e });
|
|
298
308
|
});
|
|
299
309
|
}
|
|
300
310
|
}
|
|
301
311
|
|
|
302
|
-
|
|
312
|
+
nextPeer: Map<PeerID, Peer> = new Map();
|
|
313
|
+
|
|
314
|
+
async addPeer(peer: Peer) {
|
|
303
315
|
const prevPeer = this.peers[peer.id];
|
|
304
|
-
const peerState = new PeerState(peer, prevPeer?.knownStates);
|
|
305
|
-
this.peers[peer.id] = peerState;
|
|
306
316
|
|
|
307
|
-
if (prevPeer
|
|
308
|
-
|
|
317
|
+
if (prevPeer) {
|
|
318
|
+
// Assign to nextPeer to check against race conditions
|
|
319
|
+
prevPeer.nextPeer = peer;
|
|
320
|
+
|
|
321
|
+
if (!prevPeer.closed) {
|
|
322
|
+
prevPeer.gracefulShutdown();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Wait for the previous peer to finish processing the incoming messages
|
|
326
|
+
await prevPeer.incomingMessagesProcessingPromise?.catch((e) => {});
|
|
327
|
+
|
|
328
|
+
// If another peer was added in the meantime, we close this peer
|
|
329
|
+
if (prevPeer.nextPeer !== peer) {
|
|
330
|
+
peer.outgoing.close();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
309
333
|
}
|
|
310
334
|
|
|
335
|
+
const peerState = new PeerState(peer, prevPeer?.knownStates);
|
|
336
|
+
this.peers[peer.id] = peerState;
|
|
337
|
+
|
|
311
338
|
this.peersCounter.add(1, { role: peer.role });
|
|
312
339
|
|
|
313
340
|
const unsubscribeFromKnownStatesUpdates = peerState.knownStates.subscribe(
|
|
@@ -317,43 +344,13 @@ export class SyncManager {
|
|
|
317
344
|
);
|
|
318
345
|
|
|
319
346
|
if (peerState.isServerOrStoragePeer()) {
|
|
320
|
-
|
|
321
|
-
for (const entry of this.local.coValuesStore.getValues()) {
|
|
322
|
-
await this.subscribeToIncludingDependencies(entry.id, peerState);
|
|
323
|
-
|
|
324
|
-
if (entry.state.type === "available") {
|
|
325
|
-
await this.sendNewContentIncludingDependencies(entry.id, peerState);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (!peerState.optimisticKnownStates.has(entry.id)) {
|
|
329
|
-
peerState.optimisticKnownStates.dispatch({
|
|
330
|
-
type: "SET_AS_EMPTY",
|
|
331
|
-
id: entry.id,
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
void initialSync();
|
|
347
|
+
void this.startPeerReconciliation(peerState);
|
|
337
348
|
}
|
|
338
349
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (msg === "Disconnected") {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
if (msg === "PingTimeout") {
|
|
345
|
-
logger.error("Ping timeout from peer", {
|
|
346
|
-
peerId: peer.id,
|
|
347
|
-
peerRole: peer.role,
|
|
348
|
-
});
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
350
|
+
peerState
|
|
351
|
+
.processIncomingMessages(async (msg) => {
|
|
352
352
|
await this.handleSyncMessage(msg, peerState);
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
processMessages()
|
|
353
|
+
})
|
|
357
354
|
.then(() => {
|
|
358
355
|
if (peer.crashOnClose) {
|
|
359
356
|
logger.error("Unexepcted close from peer", {
|
|
@@ -370,18 +367,18 @@ export class SyncManager {
|
|
|
370
367
|
peerId: peer.id,
|
|
371
368
|
peerRole: peer.role,
|
|
372
369
|
});
|
|
370
|
+
|
|
373
371
|
if (peer.crashOnClose) {
|
|
374
372
|
this.local.crashed = e;
|
|
375
373
|
throw new Error(e);
|
|
376
374
|
}
|
|
377
375
|
})
|
|
378
376
|
.finally(() => {
|
|
379
|
-
|
|
380
|
-
state?.gracefulShutdown();
|
|
377
|
+
peerState.gracefulShutdown();
|
|
381
378
|
unsubscribeFromKnownStatesUpdates();
|
|
382
379
|
this.peersCounter.add(-1, { role: peer.role });
|
|
383
380
|
|
|
384
|
-
if (peer.deletePeerStateOnClose) {
|
|
381
|
+
if (peer.deletePeerStateOnClose && this.peers[peer.id] === peerState) {
|
|
385
382
|
delete this.peers[peer.id];
|
|
386
383
|
}
|
|
387
384
|
});
|
|
@@ -391,7 +388,21 @@ export class SyncManager {
|
|
|
391
388
|
return peer.pushOutgoingMessage(msg);
|
|
392
389
|
}
|
|
393
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Handles the load message from a peer.
|
|
393
|
+
*
|
|
394
|
+
* Differences with the known state message:
|
|
395
|
+
* - The load message triggers the CoValue loading process on the other peer
|
|
396
|
+
* - The peer known state is stored as-is instead of being merged
|
|
397
|
+
* - The load message always replies with a known state message
|
|
398
|
+
*/
|
|
394
399
|
async handleLoad(msg: LoadMessage, peer: PeerState) {
|
|
400
|
+
/**
|
|
401
|
+
* We use the msg sessions as source of truth for the known states
|
|
402
|
+
*
|
|
403
|
+
* This way we can track part of the data loss that may occur when the other peer is restarted
|
|
404
|
+
*
|
|
405
|
+
*/
|
|
395
406
|
peer.dispatchToKnownStates({
|
|
396
407
|
type: "SET",
|
|
397
408
|
id: msg.id,
|
|
@@ -403,30 +414,23 @@ export class SyncManager {
|
|
|
403
414
|
const eligiblePeers = this.getServerAndStoragePeers(peer.id);
|
|
404
415
|
|
|
405
416
|
if (eligiblePeers.length === 0) {
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
action: "known",
|
|
420
|
-
id: msg.id,
|
|
421
|
-
header: false,
|
|
422
|
-
sessions: {},
|
|
423
|
-
}).catch((e) => {
|
|
424
|
-
logger.error("Error sending known state back", { err: e });
|
|
425
|
-
});
|
|
426
|
-
}
|
|
417
|
+
// We don't have any eligible peers to load the coValue from
|
|
418
|
+
// so we send a known state back to the sender to let it know
|
|
419
|
+
// that the coValue is unavailable
|
|
420
|
+
peer.toldKnownState.add(msg.id);
|
|
421
|
+
|
|
422
|
+
this.trySendToPeer(peer, {
|
|
423
|
+
action: "known",
|
|
424
|
+
id: msg.id,
|
|
425
|
+
header: false,
|
|
426
|
+
sessions: {},
|
|
427
|
+
}).catch((e) => {
|
|
428
|
+
logger.error("Error sending known state back", { err: e });
|
|
429
|
+
});
|
|
427
430
|
|
|
428
431
|
return;
|
|
429
432
|
} else {
|
|
433
|
+
// Should move the state to loading
|
|
430
434
|
this.local.loadCoValueCore(msg.id, peer.id).catch((e) => {
|
|
431
435
|
logger.error("Error loading coValue in handleLoad", { err: e });
|
|
432
436
|
});
|
|
@@ -442,11 +446,6 @@ export class SyncManager {
|
|
|
442
446
|
.getCoValue()
|
|
443
447
|
.then(async (value) => {
|
|
444
448
|
if (value === "unavailable") {
|
|
445
|
-
peer.dispatchToKnownStates({
|
|
446
|
-
type: "SET",
|
|
447
|
-
id: msg.id,
|
|
448
|
-
value: knownStateIn(msg),
|
|
449
|
-
});
|
|
450
449
|
peer.toldKnownState.add(msg.id);
|
|
451
450
|
|
|
452
451
|
this.trySendToPeer(peer, {
|
|
@@ -461,7 +460,6 @@ export class SyncManager {
|
|
|
461
460
|
return;
|
|
462
461
|
}
|
|
463
462
|
|
|
464
|
-
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
|
|
465
463
|
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
466
464
|
})
|
|
467
465
|
.catch((e) => {
|
|
@@ -469,7 +467,9 @@ export class SyncManager {
|
|
|
469
467
|
err: e,
|
|
470
468
|
});
|
|
471
469
|
});
|
|
472
|
-
} else if (entry.state.type === "
|
|
470
|
+
} else if (entry.state.type === "available") {
|
|
471
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
472
|
+
} else {
|
|
473
473
|
this.trySendToPeer(peer, {
|
|
474
474
|
action: "known",
|
|
475
475
|
id: msg.id,
|
|
@@ -477,11 +477,6 @@ export class SyncManager {
|
|
|
477
477
|
sessions: {},
|
|
478
478
|
});
|
|
479
479
|
}
|
|
480
|
-
|
|
481
|
-
if (entry.state.type === "available") {
|
|
482
|
-
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
|
|
483
|
-
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
484
|
-
}
|
|
485
480
|
}
|
|
486
481
|
|
|
487
482
|
async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
|
|
@@ -493,31 +488,6 @@ export class SyncManager {
|
|
|
493
488
|
value: knownStateIn(msg),
|
|
494
489
|
});
|
|
495
490
|
|
|
496
|
-
if (entry.state.type === "unknown" || entry.state.type === "unavailable") {
|
|
497
|
-
if (msg.asDependencyOf) {
|
|
498
|
-
const dependencyEntry = this.local.coValuesStore.get(
|
|
499
|
-
msg.asDependencyOf,
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
if (
|
|
503
|
-
dependencyEntry.state.type === "available" ||
|
|
504
|
-
dependencyEntry.state.type === "loading"
|
|
505
|
-
) {
|
|
506
|
-
this.local
|
|
507
|
-
.loadCoValueCore(
|
|
508
|
-
msg.id,
|
|
509
|
-
peer.role === "storage" ? undefined : peer.id,
|
|
510
|
-
)
|
|
511
|
-
.catch((e) => {
|
|
512
|
-
logger.error(
|
|
513
|
-
`Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
|
|
514
|
-
{ err: e },
|
|
515
|
-
);
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
491
|
// The header is a boolean value that tells us if the other peer do have information about the header.
|
|
522
492
|
// If it's false in this point it means that the coValue is unavailable on the other peer.
|
|
523
493
|
if (entry.state.type !== "available") {
|
|
@@ -534,11 +504,23 @@ export class SyncManager {
|
|
|
534
504
|
}
|
|
535
505
|
|
|
536
506
|
if (entry.state.type === "available") {
|
|
537
|
-
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
|
|
538
507
|
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
539
508
|
}
|
|
540
509
|
}
|
|
541
510
|
|
|
511
|
+
recordTransactionsSize(newTransactions: Transaction[], source: string) {
|
|
512
|
+
for (const tx of newTransactions) {
|
|
513
|
+
const txLength =
|
|
514
|
+
tx.privacy === "private"
|
|
515
|
+
? tx.encryptedChanges.length
|
|
516
|
+
: tx.changes.length;
|
|
517
|
+
|
|
518
|
+
this.transactionsSizeHistogram.record(txLength, {
|
|
519
|
+
source,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
542
524
|
async handleNewContent(msg: NewContentMessage, peer: PeerState) {
|
|
543
525
|
const entry = this.local.coValuesStore.get(msg.id);
|
|
544
526
|
|
|
@@ -638,6 +620,8 @@ export class SyncManager {
|
|
|
638
620
|
continue;
|
|
639
621
|
}
|
|
640
622
|
|
|
623
|
+
this.recordTransactionsSize(newTransactions, peer.role);
|
|
624
|
+
|
|
641
625
|
peer.dispatchToKnownStates({
|
|
642
626
|
type: "UPDATE_SESSION_COUNTER",
|
|
643
627
|
id: msg.id,
|
|
@@ -660,6 +644,7 @@ export class SyncManager {
|
|
|
660
644
|
err: e,
|
|
661
645
|
});
|
|
662
646
|
});
|
|
647
|
+
peer.toldKnownState.add(msg.id);
|
|
663
648
|
} else {
|
|
664
649
|
/**
|
|
665
650
|
* We are sending a known state message to the peer to acknowledge the
|
|
@@ -678,6 +663,7 @@ export class SyncManager {
|
|
|
678
663
|
err: e,
|
|
679
664
|
});
|
|
680
665
|
});
|
|
666
|
+
peer.toldKnownState.add(msg.id);
|
|
681
667
|
}
|
|
682
668
|
|
|
683
669
|
/**
|
|
@@ -685,7 +671,7 @@ export class SyncManager {
|
|
|
685
671
|
* response to the peers that are waiting for confirmation that a coValue is
|
|
686
672
|
* fully synced
|
|
687
673
|
*/
|
|
688
|
-
|
|
674
|
+
this.syncCoValue(coValue);
|
|
689
675
|
}
|
|
690
676
|
|
|
691
677
|
async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
|
|
@@ -729,10 +715,8 @@ export class SyncManager {
|
|
|
729
715
|
if (peer.erroredCoValues.has(coValue.id)) continue;
|
|
730
716
|
|
|
731
717
|
if (peer.optimisticKnownStates.has(coValue.id)) {
|
|
732
|
-
await this.tellUntoldKnownStateIncludingDependencies(coValue.id, peer);
|
|
733
718
|
await this.sendNewContentIncludingDependencies(coValue.id, peer);
|
|
734
719
|
} else if (peer.isServerOrStoragePeer()) {
|
|
735
|
-
await this.subscribeToIncludingDependencies(coValue.id, peer);
|
|
736
720
|
await this.sendNewContentIncludingDependencies(coValue.id, peer);
|
|
737
721
|
}
|
|
738
722
|
}
|