cojson 0.4.8 → 0.5.0
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/CHANGELOG.md +7 -0
- package/dist/coValueCore.js +112 -45
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValues/coList.js +22 -6
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.js +1 -2
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coStream.js +12 -8
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/crypto.js +57 -31
- package/dist/crypto.js.map +1 -1
- package/dist/localNode.js +59 -16
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.js +5 -3
- package/dist/permissions.js.map +1 -1
- package/dist/streamUtils.js +34 -8
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.js +180 -46
- package/dist/sync.js.map +1 -1
- package/dist/typeUtils/accountOrAgentIDfromSessionID.js +2 -1
- package/dist/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
- package/package.json +4 -3
- package/src/coValueCore.ts +144 -66
- package/src/coValues/coList.ts +34 -12
- package/src/coValues/coMap.ts +2 -5
- package/src/coValues/coStream.ts +36 -11
- package/src/crypto.ts +83 -41
- package/src/localNode.ts +128 -33
- package/src/permissions.ts +19 -24
- package/src/streamUtils.ts +41 -8
- package/src/sync.ts +221 -60
- package/src/tests/coValue.test.ts +3 -3
- package/src/tests/permissions.test.ts +124 -83
- package/src/tests/sync.test.ts +29 -20
- package/src/typeUtils/accountOrAgentIDfromSessionID.ts +2 -1
package/src/streamUtils.ts
CHANGED
|
@@ -18,11 +18,11 @@ export function connectedPeers(
|
|
|
18
18
|
peer2role?: Peer["role"];
|
|
19
19
|
} = {}
|
|
20
20
|
): [Peer, Peer] {
|
|
21
|
-
const [inRx1, inTx1] = newStreamPair<SyncMessage>();
|
|
22
|
-
const [outRx1, outTx1] = newStreamPair<SyncMessage>();
|
|
21
|
+
const [inRx1, inTx1] = newStreamPair<SyncMessage>(peer1id + "_in");
|
|
22
|
+
const [outRx1, outTx1] = newStreamPair<SyncMessage>(peer1id + "_out");
|
|
23
23
|
|
|
24
|
-
const [inRx2, inTx2] = newStreamPair<SyncMessage>();
|
|
25
|
-
const [outRx2, outTx2] = newStreamPair<SyncMessage>();
|
|
24
|
+
const [inRx2, inTx2] = newStreamPair<SyncMessage>(peer2id + "_in");
|
|
25
|
+
const [outRx2, outTx2] = newStreamPair<SyncMessage>(peer2id + "_out");
|
|
26
26
|
|
|
27
27
|
void outRx2
|
|
28
28
|
.pipeThrough(
|
|
@@ -37,7 +37,7 @@ export function connectedPeers(
|
|
|
37
37
|
JSON.stringify(
|
|
38
38
|
chunk,
|
|
39
39
|
(k, v) =>
|
|
40
|
-
|
|
40
|
+
k === "changes" || k === "encryptedChanges"
|
|
41
41
|
? v.slice(0, 20) + "..."
|
|
42
42
|
: v,
|
|
43
43
|
2
|
|
@@ -62,7 +62,7 @@ export function connectedPeers(
|
|
|
62
62
|
JSON.stringify(
|
|
63
63
|
chunk,
|
|
64
64
|
(k, v) =>
|
|
65
|
-
|
|
65
|
+
k === "changes" || k === "encryptedChanges"
|
|
66
66
|
? v.slice(0, 20) + "..."
|
|
67
67
|
: v,
|
|
68
68
|
2
|
|
@@ -91,7 +91,10 @@ export function connectedPeers(
|
|
|
91
91
|
return [peer1AsPeer, peer2AsPeer];
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export function newStreamPair<T>(
|
|
94
|
+
export function newStreamPair<T>(
|
|
95
|
+
pairName?: string
|
|
96
|
+
): [ReadableStream<T>, WritableStream<T>] {
|
|
97
|
+
let queueLength = 0;
|
|
95
98
|
let readerClosed = false;
|
|
96
99
|
|
|
97
100
|
let resolveEnqueue: (enqueue: (item: T) => void) => void;
|
|
@@ -104,6 +107,22 @@ export function newStreamPair<T>(): [ReadableStream<T>, WritableStream<T>] {
|
|
|
104
107
|
resolveClose = resolve;
|
|
105
108
|
});
|
|
106
109
|
|
|
110
|
+
let queueWasOverflowing = false;
|
|
111
|
+
|
|
112
|
+
function maybeReportQueueLength() {
|
|
113
|
+
if (queueLength >= 100) {
|
|
114
|
+
queueWasOverflowing = true;
|
|
115
|
+
if (queueLength % 100 === 0) {
|
|
116
|
+
console.warn(pairName, "overflowing queue length", queueLength);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
if (queueWasOverflowing) {
|
|
120
|
+
console.debug(pairName, "ok queue length", queueLength);
|
|
121
|
+
queueWasOverflowing = false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
107
126
|
const readable = new ReadableStream<T>({
|
|
108
127
|
async start(controller) {
|
|
109
128
|
resolveEnqueue(controller.enqueue.bind(controller));
|
|
@@ -114,12 +133,26 @@ export function newStreamPair<T>(): [ReadableStream<T>, WritableStream<T>] {
|
|
|
114
133
|
console.log("Manually closing reader");
|
|
115
134
|
readerClosed = true;
|
|
116
135
|
},
|
|
117
|
-
})
|
|
136
|
+
}).pipeThrough(
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
138
|
+
new TransformStream<any, any>({
|
|
139
|
+
transform(
|
|
140
|
+
chunk: SyncMessage,
|
|
141
|
+
controller: { enqueue: (msg: SyncMessage) => void }
|
|
142
|
+
) {
|
|
143
|
+
queueLength -= 1;
|
|
144
|
+
maybeReportQueueLength();
|
|
145
|
+
controller.enqueue(chunk);
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
) as ReadableStream<T>;
|
|
118
149
|
|
|
119
150
|
let lastWritePromise = Promise.resolve();
|
|
120
151
|
|
|
121
152
|
const writable = new WritableStream<T>({
|
|
122
153
|
async write(chunk) {
|
|
154
|
+
queueLength += 1;
|
|
155
|
+
maybeReportQueueLength();
|
|
123
156
|
const enqueue = await enqueuePromise;
|
|
124
157
|
if (readerClosed) {
|
|
125
158
|
throw new Error("Reader closed");
|
package/src/sync.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { Signature } from "./crypto.js";
|
|
|
2
2
|
import { CoValueHeader, Transaction } from "./coValueCore.js";
|
|
3
3
|
import { CoValueCore } from "./coValueCore.js";
|
|
4
4
|
import { LocalNode } from "./localNode.js";
|
|
5
|
-
import { newLoadingState } from "./localNode.js";
|
|
6
5
|
import {
|
|
7
6
|
ReadableStream,
|
|
8
7
|
WritableStream,
|
|
@@ -67,6 +66,7 @@ export interface Peer {
|
|
|
67
66
|
outgoing: WritableStream<SyncMessage>;
|
|
68
67
|
role: "peer" | "server" | "client";
|
|
69
68
|
delayOnError?: number;
|
|
69
|
+
priority?: number;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export interface PeerState {
|
|
@@ -77,6 +77,7 @@ export interface PeerState {
|
|
|
77
77
|
outgoing: WritableStreamDefaultWriter<SyncMessage>;
|
|
78
78
|
role: "peer" | "server" | "client";
|
|
79
79
|
delayOnError?: number;
|
|
80
|
+
priority?: number;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
export function combinedKnownStates(
|
|
@@ -107,13 +108,30 @@ export function combinedKnownStates(
|
|
|
107
108
|
export class SyncManager {
|
|
108
109
|
peers: { [key: PeerID]: PeerState } = {};
|
|
109
110
|
local: LocalNode;
|
|
111
|
+
requestedSyncs: { [id: RawCoID]: {done: Promise<void>, nRequestsThisTick: number} | undefined } = {};
|
|
110
112
|
|
|
111
113
|
constructor(local: LocalNode) {
|
|
112
114
|
this.local = local;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
peersInPriorityOrder(): PeerState[] {
|
|
118
|
+
return Object.values(this.peers).sort((a, b) => {
|
|
119
|
+
const aPriority = a.priority || 0;
|
|
120
|
+
const bPriority = b.priority || 0;
|
|
121
|
+
|
|
122
|
+
return bPriority - aPriority;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async loadFromPeers(id: RawCoID, excludePeer?: PeerID) {
|
|
127
|
+
for (const peer of this.peersInPriorityOrder()) {
|
|
128
|
+
if (peer.id === excludePeer) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (peer.role !== "server") {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// console.log("loading", id, "from", peer.id);
|
|
117
135
|
peer.outgoing
|
|
118
136
|
.write({
|
|
119
137
|
action: "load",
|
|
@@ -124,6 +142,44 @@ export class SyncManager {
|
|
|
124
142
|
.catch((e) => {
|
|
125
143
|
console.error("Error writing to peer", e);
|
|
126
144
|
});
|
|
145
|
+
const coValueEntry = this.local.coValues[id];
|
|
146
|
+
if (coValueEntry?.state !== "loading") {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const firstStateEntry = coValueEntry.firstPeerState[peer.id];
|
|
150
|
+
if (firstStateEntry?.type !== "waiting") {
|
|
151
|
+
throw new Error("Expected firstPeerState to be waiting " + id);
|
|
152
|
+
}
|
|
153
|
+
await new Promise<void>((resolve) => {
|
|
154
|
+
const timeout = setTimeout(() => {
|
|
155
|
+
if (this.local.coValues[id]?.state === "loading") {
|
|
156
|
+
// console.warn(
|
|
157
|
+
// "Timeout waiting for peer to load",
|
|
158
|
+
// id,
|
|
159
|
+
// "from",
|
|
160
|
+
// peer.id,
|
|
161
|
+
// "and it hasn't loaded from other peers yet"
|
|
162
|
+
// );
|
|
163
|
+
}
|
|
164
|
+
resolve();
|
|
165
|
+
}, 1000);
|
|
166
|
+
firstStateEntry.done
|
|
167
|
+
.then(() => {
|
|
168
|
+
clearTimeout(timeout);
|
|
169
|
+
resolve();
|
|
170
|
+
})
|
|
171
|
+
.catch((e) => {
|
|
172
|
+
clearTimeout(timeout);
|
|
173
|
+
console.error(
|
|
174
|
+
"Error waiting for peer to load",
|
|
175
|
+
id,
|
|
176
|
+
"from",
|
|
177
|
+
peer.id,
|
|
178
|
+
e
|
|
179
|
+
);
|
|
180
|
+
resolve();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
127
183
|
}
|
|
128
184
|
}
|
|
129
185
|
|
|
@@ -139,6 +195,7 @@ export class SyncManager {
|
|
|
139
195
|
return await this.handleKnownState(msg, peer);
|
|
140
196
|
}
|
|
141
197
|
case "content":
|
|
198
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
142
199
|
return await this.handleNewContent(msg, peer);
|
|
143
200
|
case "done":
|
|
144
201
|
return await this.handleUnsubscribe(msg);
|
|
@@ -190,13 +247,11 @@ export class SyncManager {
|
|
|
190
247
|
) {
|
|
191
248
|
const coValue = this.local.expectCoValueLoaded(id);
|
|
192
249
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
);
|
|
199
|
-
}
|
|
250
|
+
await Promise.all(coValue.getDependedOnCoValues().map(dependentCoID => this.tellUntoldKnownStateIncludingDependencies(
|
|
251
|
+
dependentCoID,
|
|
252
|
+
peer,
|
|
253
|
+
asDependencyOf || id
|
|
254
|
+
)));
|
|
200
255
|
|
|
201
256
|
if (!peer.toldKnownState.has(id)) {
|
|
202
257
|
await this.trySendToPeer(peer, {
|
|
@@ -212,9 +267,7 @@ export class SyncManager {
|
|
|
212
267
|
async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
|
|
213
268
|
const coValue = this.local.expectCoValueLoaded(id);
|
|
214
269
|
|
|
215
|
-
|
|
216
|
-
await this.sendNewContentIncludingDependencies(id, peer);
|
|
217
|
-
}
|
|
270
|
+
await Promise.all(coValue.getDependedOnCoValues().map(id => this.sendNewContentIncludingDependencies(id, peer)));
|
|
218
271
|
|
|
219
272
|
const newContentPieces = coValue.newContentSince(
|
|
220
273
|
peer.optimisticKnownStates[id]
|
|
@@ -225,12 +278,19 @@ export class SyncManager {
|
|
|
225
278
|
peer.optimisticKnownStates[id] || emptyKnownState(id);
|
|
226
279
|
|
|
227
280
|
const sendPieces = async () => {
|
|
281
|
+
let lastYield = performance.now();
|
|
228
282
|
for (const [_i, piece] of newContentPieces.entries()) {
|
|
229
283
|
// console.log(
|
|
230
284
|
// `${id} -> ${peer.id}: Sending content piece ${i + 1}/${newContentPieces.length} header: ${!!piece.header}`,
|
|
231
285
|
// // Object.values(piece.new).map((s) => s.newTransactions)
|
|
232
286
|
// );
|
|
233
287
|
await this.trySendToPeer(peer, piece);
|
|
288
|
+
if (performance.now() - lastYield > 10) {
|
|
289
|
+
await new Promise<void>((resolve) => {
|
|
290
|
+
setTimeout(resolve, 0);
|
|
291
|
+
});
|
|
292
|
+
lastYield = performance.now();
|
|
293
|
+
}
|
|
234
294
|
}
|
|
235
295
|
};
|
|
236
296
|
|
|
@@ -256,6 +316,7 @@ export class SyncManager {
|
|
|
256
316
|
toldKnownState: new Set(),
|
|
257
317
|
role: peer.role,
|
|
258
318
|
delayOnError: peer.delayOnError,
|
|
319
|
+
priority: peer.priority,
|
|
259
320
|
};
|
|
260
321
|
this.peers[peer.id] = peerState;
|
|
261
322
|
|
|
@@ -264,6 +325,7 @@ export class SyncManager {
|
|
|
264
325
|
for (const id of Object.keys(
|
|
265
326
|
this.local.coValues
|
|
266
327
|
) as RawCoID[]) {
|
|
328
|
+
// console.log("subscribing to after peer added", id, peer.id)
|
|
267
329
|
await this.subscribeToIncludingDependencies(id, peerState);
|
|
268
330
|
|
|
269
331
|
peerState.optimisticKnownStates[id] = {
|
|
@@ -280,7 +342,19 @@ export class SyncManager {
|
|
|
280
342
|
try {
|
|
281
343
|
for await (const msg of peerState.incoming) {
|
|
282
344
|
try {
|
|
283
|
-
await this.handleSyncMessage(msg, peerState);
|
|
345
|
+
// await this.handleSyncMessage(msg, peerState);
|
|
346
|
+
this.handleSyncMessage(msg, peerState).catch((e) => {
|
|
347
|
+
console.error(
|
|
348
|
+
new Date(),
|
|
349
|
+
`Error reading from peer ${peer.id}, handling msg`,
|
|
350
|
+
JSON.stringify(msg, (k, v) =>
|
|
351
|
+
k === "changes" || k === "encryptedChanges"
|
|
352
|
+
? v.slice(0, 20) + "..."
|
|
353
|
+
: v
|
|
354
|
+
),
|
|
355
|
+
e
|
|
356
|
+
);
|
|
357
|
+
});
|
|
284
358
|
await new Promise<void>((resolve) => {
|
|
285
359
|
setTimeout(resolve, 0);
|
|
286
360
|
});
|
|
@@ -314,20 +388,28 @@ export class SyncManager {
|
|
|
314
388
|
}
|
|
315
389
|
|
|
316
390
|
trySendToPeer(peer: PeerState, msg: SyncMessage) {
|
|
391
|
+
if (!this.peers[peer.id]) {
|
|
392
|
+
// already disconnected, return to drain potential queue
|
|
393
|
+
return Promise.resolve();
|
|
394
|
+
}
|
|
395
|
+
|
|
317
396
|
return new Promise<void>((resolve) => {
|
|
318
|
-
const
|
|
319
|
-
console.error(
|
|
320
|
-
new Error(
|
|
321
|
-
`Writing to peer ${peer.id} took >1s - this should never happen as write should resolve quickly or error`
|
|
322
|
-
)
|
|
323
|
-
);
|
|
324
|
-
resolve();
|
|
325
|
-
}, 1000);
|
|
397
|
+
const start = Date.now();
|
|
326
398
|
peer.outgoing
|
|
327
399
|
.write(msg)
|
|
328
400
|
.then(() => {
|
|
329
|
-
|
|
330
|
-
|
|
401
|
+
const end = Date.now();
|
|
402
|
+
if (end - start > 1000) {
|
|
403
|
+
// console.error(
|
|
404
|
+
// new Error(
|
|
405
|
+
// `Writing to peer "${peer.id}" took ${
|
|
406
|
+
// Math.round((Date.now() - start) / 100) / 10
|
|
407
|
+
// }s - this should never happen as write should resolve quickly or error`
|
|
408
|
+
// )
|
|
409
|
+
// );
|
|
410
|
+
} else {
|
|
411
|
+
resolve();
|
|
412
|
+
}
|
|
331
413
|
})
|
|
332
414
|
.catch((e) => {
|
|
333
415
|
console.error(
|
|
@@ -344,42 +426,42 @@ export class SyncManager {
|
|
|
344
426
|
}
|
|
345
427
|
|
|
346
428
|
async handleLoad(msg: LoadMessage, peer: PeerState) {
|
|
347
|
-
|
|
429
|
+
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
|
|
430
|
+
let entry = this.local.coValues[msg.id];
|
|
348
431
|
|
|
349
|
-
if (!entry
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
e
|
|
359
|
-
);
|
|
360
|
-
resolve();
|
|
361
|
-
});
|
|
362
|
-
setTimeout(resolve, 1000);
|
|
432
|
+
if (!entry) {
|
|
433
|
+
// console.log(`Loading ${msg.id} from all peers except ${peer.id}`);
|
|
434
|
+
this.local
|
|
435
|
+
.loadCoValueCore(msg.id, {
|
|
436
|
+
dontLoadFrom: peer.id,
|
|
437
|
+
dontWaitFor: peer.id,
|
|
438
|
+
})
|
|
439
|
+
.catch((e) => {
|
|
440
|
+
console.error("Error loading coValue in handleLoad", e);
|
|
363
441
|
});
|
|
364
|
-
}
|
|
365
442
|
|
|
366
|
-
|
|
367
|
-
|
|
443
|
+
entry = this.local.coValues[msg.id]!;
|
|
444
|
+
}
|
|
368
445
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
id: msg.id,
|
|
372
|
-
header: false,
|
|
373
|
-
sessions: {},
|
|
374
|
-
});
|
|
446
|
+
if (entry.state === "loading") {
|
|
447
|
+
const loaded = await entry.done;
|
|
375
448
|
|
|
376
|
-
|
|
377
|
-
|
|
449
|
+
if (loaded === "unavailable") {
|
|
450
|
+
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
|
|
451
|
+
peer.toldKnownState.add(msg.id);
|
|
378
452
|
|
|
379
|
-
|
|
453
|
+
await this.trySendToPeer(peer, {
|
|
454
|
+
action: "known",
|
|
455
|
+
id: msg.id,
|
|
456
|
+
header: false,
|
|
457
|
+
sessions: {},
|
|
458
|
+
});
|
|
380
459
|
|
|
381
|
-
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
382
463
|
|
|
464
|
+
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
|
|
383
465
|
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
384
466
|
}
|
|
385
467
|
|
|
@@ -394,9 +476,15 @@ export class SyncManager {
|
|
|
394
476
|
if (!entry) {
|
|
395
477
|
if (msg.asDependencyOf) {
|
|
396
478
|
if (this.local.coValues[msg.asDependencyOf]) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
479
|
+
this.local
|
|
480
|
+
.loadCoValueCore(msg.id, { dontLoadFrom: peer.id })
|
|
481
|
+
.catch((e) => {
|
|
482
|
+
console.error(
|
|
483
|
+
`Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
|
|
484
|
+
e
|
|
485
|
+
);
|
|
486
|
+
});
|
|
487
|
+
entry = this.local.coValues[msg.id]!; // must exist after loadCoValueCore
|
|
400
488
|
} else {
|
|
401
489
|
throw new Error(
|
|
402
490
|
"Expected coValue dependency entry to be created, missing subscribe?"
|
|
@@ -410,6 +498,29 @@ export class SyncManager {
|
|
|
410
498
|
}
|
|
411
499
|
|
|
412
500
|
if (entry.state === "loading") {
|
|
501
|
+
const availableOnPeer = peer.optimisticKnownStates[msg.id]?.header;
|
|
502
|
+
const firstPeerStateEntry = entry.firstPeerState[peer.id];
|
|
503
|
+
if (firstPeerStateEntry?.type === "waiting") {
|
|
504
|
+
firstPeerStateEntry.resolve();
|
|
505
|
+
}
|
|
506
|
+
entry.firstPeerState[peer.id] = availableOnPeer
|
|
507
|
+
? { type: "available" }
|
|
508
|
+
: { type: "unavailable" };
|
|
509
|
+
// console.log(
|
|
510
|
+
// "Marking",
|
|
511
|
+
// msg.id,
|
|
512
|
+
// "as",
|
|
513
|
+
// entry.firstPeerState[peer.id]?.type,
|
|
514
|
+
// "from",
|
|
515
|
+
// peer.id
|
|
516
|
+
// );
|
|
517
|
+
if (
|
|
518
|
+
Object.values(entry.firstPeerState).every(
|
|
519
|
+
(s) => s.type === "unavailable"
|
|
520
|
+
)
|
|
521
|
+
) {
|
|
522
|
+
entry.resolve("unavailable");
|
|
523
|
+
}
|
|
413
524
|
return [];
|
|
414
525
|
}
|
|
415
526
|
|
|
@@ -441,6 +552,12 @@ export class SyncManager {
|
|
|
441
552
|
throw new Error("Expected header to be sent in first message");
|
|
442
553
|
}
|
|
443
554
|
|
|
555
|
+
const firstPeerStateEntry = entry.firstPeerState[peer.id];
|
|
556
|
+
if (firstPeerStateEntry?.type === "waiting") {
|
|
557
|
+
firstPeerStateEntry.resolve();
|
|
558
|
+
entry.firstPeerState[peer.id] = { type: "available" };
|
|
559
|
+
}
|
|
560
|
+
|
|
444
561
|
peerOptimisticKnownState.header = true;
|
|
445
562
|
|
|
446
563
|
const coValue = new CoValueCore(msg.header, this.local);
|
|
@@ -450,6 +567,7 @@ export class SyncManager {
|
|
|
450
567
|
entry = {
|
|
451
568
|
state: "loaded",
|
|
452
569
|
coValue: coValue,
|
|
570
|
+
onProgress: entry.onProgress,
|
|
453
571
|
};
|
|
454
572
|
|
|
455
573
|
this.local.coValues[msg.id] = entry;
|
|
@@ -463,7 +581,7 @@ export class SyncManager {
|
|
|
463
581
|
msg.new
|
|
464
582
|
) as [SessionID, SessionNewContent][]) {
|
|
465
583
|
const ourKnownTxIdx =
|
|
466
|
-
coValue.
|
|
584
|
+
coValue.sessionLogs.get(sessionID)?.transactions.length;
|
|
467
585
|
const theirFirstNewTxIdx = newContentForSession.after;
|
|
468
586
|
|
|
469
587
|
if ((ourKnownTxIdx || 0) < theirFirstNewTxIdx) {
|
|
@@ -490,7 +608,7 @@ export class SyncManager {
|
|
|
490
608
|
newContentForSession.lastSignature
|
|
491
609
|
);
|
|
492
610
|
const after = performance.now();
|
|
493
|
-
if (after - before >
|
|
611
|
+
if (after - before > 80) {
|
|
494
612
|
const totalTxLength = newTransactions
|
|
495
613
|
.map((t) =>
|
|
496
614
|
t.privacy === "private"
|
|
@@ -509,6 +627,16 @@ export class SyncManager {
|
|
|
509
627
|
);
|
|
510
628
|
}
|
|
511
629
|
|
|
630
|
+
const theirTotalnTxs = Object.values(
|
|
631
|
+
peer.optimisticKnownStates[msg.id]?.sessions || {}
|
|
632
|
+
).reduce((sum, nTxs) => sum + nTxs, 0);
|
|
633
|
+
const ourTotalnTxs = [...coValue.sessionLogs.values()].reduce(
|
|
634
|
+
(sum, session) => sum + session.transactions.length,
|
|
635
|
+
0
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
entry.onProgress?.(ourTotalnTxs / theirTotalnTxs);
|
|
639
|
+
|
|
512
640
|
if (!success) {
|
|
513
641
|
console.error(
|
|
514
642
|
"Failed to add transactions",
|
|
@@ -522,9 +650,11 @@ export class SyncManager {
|
|
|
522
650
|
continue;
|
|
523
651
|
}
|
|
524
652
|
|
|
525
|
-
peerOptimisticKnownState.sessions[sessionID] =
|
|
653
|
+
peerOptimisticKnownState.sessions[sessionID] = Math.max(
|
|
654
|
+
peerOptimisticKnownState.sessions[sessionID] || 0,
|
|
526
655
|
newContentForSession.after +
|
|
527
|
-
|
|
656
|
+
newContentForSession.newTransactions.length
|
|
657
|
+
);
|
|
528
658
|
}
|
|
529
659
|
|
|
530
660
|
if (resolveAfterDone) {
|
|
@@ -553,7 +683,38 @@ export class SyncManager {
|
|
|
553
683
|
}
|
|
554
684
|
|
|
555
685
|
async syncCoValue(coValue: CoValueCore) {
|
|
556
|
-
|
|
686
|
+
if (this.requestedSyncs[coValue.id]) {
|
|
687
|
+
this.requestedSyncs[coValue.id]!.nRequestsThisTick++;
|
|
688
|
+
return this.requestedSyncs[coValue.id]!.done;
|
|
689
|
+
} else {
|
|
690
|
+
const done = new Promise<void>((resolve) => {
|
|
691
|
+
setTimeout(async () => {
|
|
692
|
+
delete this.requestedSyncs[coValue.id];
|
|
693
|
+
if (entry.nRequestsThisTick >= 2) {
|
|
694
|
+
console.log("Syncing", coValue.id, "for", entry.nRequestsThisTick, "requests");
|
|
695
|
+
}
|
|
696
|
+
await this.actuallySyncCoValue(coValue);
|
|
697
|
+
resolve();
|
|
698
|
+
}, 0);
|
|
699
|
+
});
|
|
700
|
+
const entry = {
|
|
701
|
+
done,
|
|
702
|
+
nRequestsThisTick: 1,
|
|
703
|
+
};
|
|
704
|
+
this.requestedSyncs[coValue.id] = entry;
|
|
705
|
+
return done;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async actuallySyncCoValue(coValue: CoValueCore) {
|
|
710
|
+
let blockingSince = performance.now();
|
|
711
|
+
for (const peer of this.peersInPriorityOrder()) {
|
|
712
|
+
if (performance.now() - blockingSince > 5) {
|
|
713
|
+
await new Promise<void>((resolve) => {
|
|
714
|
+
setTimeout(resolve, 0);
|
|
715
|
+
});
|
|
716
|
+
blockingSince = performance.now();
|
|
717
|
+
}
|
|
557
718
|
const optimisticKnownState = peer.optimisticKnownStates[coValue.id];
|
|
558
719
|
|
|
559
720
|
if (optimisticKnownState) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { expectList, expectMap, expectStream } from "../coValue.js";
|
|
2
|
-
import { accountOrAgentIDfromSessionID } from "../coValueCore.js";
|
|
3
2
|
import { BinaryCoStream } from "../coValues/coStream.js";
|
|
4
3
|
import { createdNowUnique } from "../crypto.js";
|
|
5
4
|
import { MAX_RECOMMENDED_TX_SIZE, cojsonReady } from "../index.js";
|
|
6
5
|
import { LocalNode } from "../localNode.js";
|
|
6
|
+
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
7
7
|
import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
|
|
8
8
|
|
|
9
9
|
beforeEach(async () => {
|
|
@@ -423,7 +423,7 @@ test("When adding large transactions (small fraction of MAX_RECOMMENDED_TX_SIZE)
|
|
|
423
423
|
editable.endBinaryStream("trusting");
|
|
424
424
|
});
|
|
425
425
|
|
|
426
|
-
const sessionEntry = coValue.
|
|
426
|
+
const sessionEntry = coValue.sessionLogs.get(node.currentSessionID)!;
|
|
427
427
|
expect(sessionEntry.transactions.length).toEqual(12);
|
|
428
428
|
expect(sessionEntry.signatureAfter[0]).not.toBeDefined();
|
|
429
429
|
expect(sessionEntry.signatureAfter[1]).not.toBeDefined();
|
|
@@ -499,7 +499,7 @@ test("When adding large transactions (bigger than MAX_RECOMMENDED_TX_SIZE), we s
|
|
|
499
499
|
editable.endBinaryStream("trusting");
|
|
500
500
|
});
|
|
501
501
|
|
|
502
|
-
const sessionEntry = coValue.
|
|
502
|
+
const sessionEntry = coValue.sessionLogs.get(node.currentSessionID)!;
|
|
503
503
|
expect(sessionEntry.transactions.length).toEqual(5);
|
|
504
504
|
expect(sessionEntry.signatureAfter[0]).not.toBeDefined();
|
|
505
505
|
expect(sessionEntry.signatureAfter[1]).toBeDefined();
|