cojson 0.18.33 → 0.18.35
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 +17 -0
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +2 -2
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValueContentMessage.d.ts +5 -2
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +15 -0
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +4 -3
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +21 -30
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +14 -6
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +32 -52
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +6 -4
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +21 -7
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +20 -15
- package/dist/coValues/group.js.map +1 -1
- package/dist/knownState.d.ts +9 -1
- package/dist/knownState.d.ts.map +1 -1
- package/dist/knownState.js +29 -3
- package/dist/knownState.js.map +1 -1
- package/dist/localNode.d.ts +7 -2
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +10 -15
- package/dist/localNode.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.d.ts +10 -9
- package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js +53 -47
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
- package/dist/storage/knownState.js +2 -2
- package/dist/storage/knownState.js.map +1 -1
- package/dist/sync.d.ts +1 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +15 -19
- package/dist/sync.js.map +1 -1
- package/dist/tests/coPlainText.test.js +13 -14
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coValueContentMessage.test.js +130 -1
- package/dist/tests/coValueContentMessage.test.js.map +1 -1
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.js +3 -2
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.js.map +1 -1
- package/dist/tests/coValueCore.isStreaming.test.js +54 -3
- package/dist/tests/coValueCore.isStreaming.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +3 -6
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/group.childKeyRotation.test.js +9 -9
- package/dist/tests/group.childKeyRotation.test.js.map +1 -1
- package/dist/tests/knownState.test.js +82 -10
- package/dist/tests/knownState.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +29 -29
- package/dist/tests/sync.mesh.test.js +38 -31
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +24 -23
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +24 -23
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +58 -58
- package/dist/tests/testUtils.d.ts +11 -9
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +26 -16
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +3 -4
- package/src/SyncStateManager.ts +8 -2
- package/src/coValueContentMessage.ts +29 -2
- package/src/coValueCore/SessionMap.ts +41 -31
- package/src/coValueCore/coValueCore.ts +41 -74
- package/src/coValueCore/verifiedState.ts +36 -11
- package/src/coValues/group.ts +40 -27
- package/src/knownState.ts +39 -4
- package/src/localNode.ts +16 -21
- package/src/queue/LocalTransactionsSyncQueue.ts +77 -93
- package/src/storage/knownState.ts +2 -2
- package/src/sync.ts +24 -26
- package/src/tests/coPlainText.test.ts +13 -14
- package/src/tests/coValueContentMessage.test.ts +197 -2
- package/src/tests/coValueCore.isCompletelyDownloaded.test.ts +3 -2
- package/src/tests/coValueCore.isStreaming.test.ts +84 -2
- package/src/tests/coValueCore.test.ts +7 -10
- package/src/tests/group.childKeyRotation.test.ts +9 -9
- package/src/tests/knownState.test.ts +106 -9
- package/src/tests/sync.load.test.ts +29 -29
- package/src/tests/sync.mesh.test.ts +38 -31
- package/src/tests/sync.storage.test.ts +24 -23
- package/src/tests/sync.storageAsync.test.ts +24 -23
- package/src/tests/sync.upload.test.ts +58 -58
- package/src/tests/testUtils.ts +30 -18
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "../coValueContentMessage.js";
|
|
6
|
-
import { Transaction, VerifiedState } from "../coValueCore/verifiedState.js";
|
|
7
|
-
import { Signature } from "../crypto/crypto.js";
|
|
8
|
-
import { RawCoID, SessionID } from "../ids.js";
|
|
9
|
-
import {
|
|
10
|
-
combineKnownStateSessions,
|
|
11
|
-
KnownStateSessions,
|
|
12
|
-
} from "../knownState.js";
|
|
1
|
+
import { knownStateFromContent } from "../coValueContentMessage.js";
|
|
2
|
+
import { VerifiedState } from "../coValueCore/verifiedState.js";
|
|
3
|
+
import { RawCoID } from "../ids.js";
|
|
4
|
+
import { combineKnownStateSessions, CoValueKnownState } from "../knownState.js";
|
|
13
5
|
import { NewContentMessage } from "../sync.js";
|
|
14
|
-
import { LinkedList } from "./LinkedList.js";
|
|
15
6
|
|
|
16
7
|
/**
|
|
17
8
|
* This queue is used to batch the sync of local transactions while preserving the order of updates between CoValues.
|
|
@@ -23,65 +14,97 @@ import { LinkedList } from "./LinkedList.js";
|
|
|
23
14
|
* 2. If we do multiple updates on the same CoMap, the updates will be batched because it's safe to do so.
|
|
24
15
|
*/
|
|
25
16
|
export class LocalTransactionsSyncQueue {
|
|
26
|
-
private
|
|
17
|
+
private batch: NewContentMessage[] = [];
|
|
18
|
+
private firstChunks = new Map<RawCoID, NewContentMessage>();
|
|
19
|
+
private lastUpdatedValue: VerifiedState | undefined;
|
|
20
|
+
private lastUpdatedValueKnownState: CoValueKnownState | undefined;
|
|
27
21
|
|
|
28
22
|
constructor(private readonly sync: (content: NewContentMessage) => void) {}
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
syncTransaction = (
|
|
25
|
+
coValue: VerifiedState,
|
|
26
|
+
knownStateBefore: CoValueKnownState,
|
|
27
|
+
) => {
|
|
28
|
+
const lastUpdatedValue = this.lastUpdatedValue;
|
|
29
|
+
const lastUpdatedValueKnownState = this.lastUpdatedValueKnownState;
|
|
32
30
|
|
|
33
|
-
if (
|
|
34
|
-
|
|
31
|
+
if (lastUpdatedValue && lastUpdatedValueKnownState) {
|
|
32
|
+
if (lastUpdatedValue.id === coValue.id) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.addContentToBatch(lastUpdatedValue, lastUpdatedValueKnownState);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.lastUpdatedValue = coValue;
|
|
40
|
+
this.lastUpdatedValueKnownState = knownStateBefore;
|
|
41
|
+
|
|
42
|
+
for (const trackingSet of this.dirtyCoValuesTrackingSets) {
|
|
43
|
+
trackingSet.add(coValue.id);
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
this.
|
|
46
|
+
this.scheduleNextBatch();
|
|
38
47
|
};
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
private addContentToBatch(
|
|
41
50
|
coValue: VerifiedState,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const lastPendingSync = this.queue.tail?.value;
|
|
48
|
-
const lastSignatureIdx = coValue.getLastSignatureCheckpoint(sessionID);
|
|
49
|
-
const isSignatureCheckpoint =
|
|
50
|
-
lastSignatureIdx > -1 && lastSignatureIdx === txIdx;
|
|
51
|
-
|
|
52
|
-
if (lastPendingSync?.id === coValue.id && !isSignatureCheckpoint) {
|
|
53
|
-
addTransactionToContentMessage(
|
|
54
|
-
lastPendingSync,
|
|
55
|
-
transaction,
|
|
56
|
-
sessionID,
|
|
57
|
-
signature,
|
|
58
|
-
txIdx,
|
|
59
|
-
);
|
|
51
|
+
knownStateBefore: CoValueKnownState,
|
|
52
|
+
) {
|
|
53
|
+
const content = coValue.newContentSince(knownStateBefore, {
|
|
54
|
+
skipExpectContentUntil: true, // we need to calculate the streaming header considering the current batch
|
|
55
|
+
});
|
|
60
56
|
|
|
57
|
+
if (!content) {
|
|
61
58
|
return;
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
let firstChunk = this.firstChunks.get(coValue.id);
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
transaction,
|
|
69
|
-
sessionID,
|
|
70
|
-
signature,
|
|
71
|
-
txIdx,
|
|
72
|
-
);
|
|
63
|
+
for (const piece of content) {
|
|
64
|
+
this.batch.push(piece);
|
|
73
65
|
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
// Check if the local content updates are in streaming, if so we need to add the info to the first chunk
|
|
67
|
+
if (firstChunk) {
|
|
68
|
+
if (!firstChunk.expectContentUntil) {
|
|
69
|
+
firstChunk.expectContentUntil =
|
|
70
|
+
knownStateFromContent(firstChunk).sessions;
|
|
71
|
+
}
|
|
72
|
+
combineKnownStateSessions(
|
|
73
|
+
firstChunk.expectContentUntil,
|
|
74
|
+
knownStateFromContent(piece).sessions,
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
firstChunk = piece;
|
|
78
|
+
this.firstChunks.set(coValue.id, firstChunk);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
private nextBatchScheduled = false;
|
|
84
|
+
scheduleNextBatch() {
|
|
85
|
+
if (this.nextBatchScheduled) return;
|
|
79
86
|
|
|
80
|
-
this.
|
|
87
|
+
this.nextBatchScheduled = true;
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
queueMicrotask(() => {
|
|
90
|
+
if (this.lastUpdatedValue && this.lastUpdatedValueKnownState) {
|
|
91
|
+
this.addContentToBatch(
|
|
92
|
+
this.lastUpdatedValue,
|
|
93
|
+
this.lastUpdatedValueKnownState,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const batch = this.batch;
|
|
97
|
+
|
|
98
|
+
this.lastUpdatedValue = undefined;
|
|
99
|
+
this.lastUpdatedValueKnownState = undefined;
|
|
100
|
+
this.firstChunks = new Map();
|
|
101
|
+
this.batch = [];
|
|
102
|
+
this.nextBatchScheduled = false;
|
|
103
|
+
|
|
104
|
+
for (const content of batch) {
|
|
105
|
+
this.sync(content);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
private dirtyCoValuesTrackingSets: Set<Set<RawCoID>> = new Set();
|
|
@@ -110,43 +133,4 @@ export class LocalTransactionsSyncQueue {
|
|
|
110
133
|
},
|
|
111
134
|
};
|
|
112
135
|
};
|
|
113
|
-
|
|
114
|
-
private processingSyncs = false;
|
|
115
|
-
processPendingSyncs() {
|
|
116
|
-
if (this.processingSyncs) return;
|
|
117
|
-
|
|
118
|
-
this.processingSyncs = true;
|
|
119
|
-
|
|
120
|
-
queueMicrotask(() => {
|
|
121
|
-
const firstContentPieceMap = new Map<RawCoID, NewContentMessage>();
|
|
122
|
-
|
|
123
|
-
while (this.queue.head) {
|
|
124
|
-
const content = this.queue.head.value;
|
|
125
|
-
|
|
126
|
-
const firstContentPiece = firstContentPieceMap.get(content.id);
|
|
127
|
-
|
|
128
|
-
if (!firstContentPiece) {
|
|
129
|
-
firstContentPieceMap.set(content.id, content);
|
|
130
|
-
} else {
|
|
131
|
-
// There is already a content piece for this coValue, so this means that we need to flag
|
|
132
|
-
// that this content is going to be streamed
|
|
133
|
-
if (!firstContentPiece.expectContentUntil) {
|
|
134
|
-
firstContentPiece.expectContentUntil =
|
|
135
|
-
knownStateFromContent(firstContentPiece).sessions;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
combineKnownStateSessions(
|
|
139
|
-
firstContentPiece.expectContentUntil,
|
|
140
|
-
knownStateFromContent(content).sessions,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
this.sync(content);
|
|
145
|
-
|
|
146
|
-
this.queue.shift();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
this.processingSyncs = false;
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
136
|
}
|
|
@@ -3,7 +3,7 @@ import { RawCoID } from "../ids.js";
|
|
|
3
3
|
import {
|
|
4
4
|
CoValueKnownState,
|
|
5
5
|
emptyKnownState,
|
|
6
|
-
|
|
6
|
+
areCurrentSessionsInSyncWith,
|
|
7
7
|
} from "../knownState.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -87,7 +87,7 @@ function isInSync(
|
|
|
87
87
|
return false;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
return
|
|
90
|
+
return areCurrentSessionsInSyncWith(
|
|
91
91
|
knownState.sessions,
|
|
92
92
|
knownStateFromStorage.sessions,
|
|
93
93
|
);
|
package/src/sync.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { PeerState } from "./PeerState.js";
|
|
|
4
4
|
import { SyncStateManager } from "./SyncStateManager.js";
|
|
5
5
|
import {
|
|
6
6
|
getContenDebugInfo,
|
|
7
|
+
getNewTransactionsFromContentMessage,
|
|
8
|
+
getSessionEntriesFromContentMessage,
|
|
7
9
|
getTransactionSize,
|
|
8
10
|
knownStateFromContent,
|
|
9
11
|
} from "./coValueContentMessage.js";
|
|
@@ -609,56 +611,55 @@ export class SyncManager {
|
|
|
609
611
|
/**
|
|
610
612
|
* The coValue is in memory, load the transactions from the content message
|
|
611
613
|
*/
|
|
612
|
-
for (const [
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
]
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
614
|
+
for (const [
|
|
615
|
+
sessionID,
|
|
616
|
+
newContentForSession,
|
|
617
|
+
] of getSessionEntriesFromContentMessage(msg)) {
|
|
618
|
+
const newTransactions = getNewTransactionsFromContentMessage(
|
|
619
|
+
newContentForSession,
|
|
620
|
+
coValue.knownState(),
|
|
621
|
+
sessionID,
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
if (newTransactions === undefined) {
|
|
621
625
|
invalidStateAssumed = true;
|
|
622
626
|
continue;
|
|
623
627
|
}
|
|
624
628
|
|
|
625
|
-
const alreadyKnownOffset = ourKnownTxIdx
|
|
626
|
-
? ourKnownTxIdx - theirFirstNewTxIdx
|
|
627
|
-
: 0;
|
|
628
|
-
|
|
629
|
-
const newTransactions =
|
|
630
|
-
newContentForSession.newTransactions.slice(alreadyKnownOffset);
|
|
631
|
-
|
|
632
629
|
if (newTransactions.length === 0) {
|
|
633
630
|
continue;
|
|
634
631
|
}
|
|
635
632
|
|
|
636
633
|
// TODO: Handle invalid signatures in the middle of streaming
|
|
637
634
|
// This could cause a situation where we are unable to load a chunk, and ask for a correction for all the subsequent chunks
|
|
638
|
-
const
|
|
635
|
+
const error = coValue.tryAddTransactions(
|
|
639
636
|
sessionID,
|
|
640
637
|
newTransactions,
|
|
641
638
|
newContentForSession.lastSignature,
|
|
642
639
|
this.skipVerify,
|
|
643
640
|
);
|
|
644
641
|
|
|
645
|
-
if (
|
|
642
|
+
if (error) {
|
|
646
643
|
if (peer) {
|
|
647
644
|
logger.error("Failed to add transactions", {
|
|
648
645
|
peerId: peer.id,
|
|
649
646
|
peerRole: peer.role,
|
|
650
647
|
id: msg.id,
|
|
651
|
-
|
|
648
|
+
errorType: error.type,
|
|
649
|
+
err: error.error,
|
|
650
|
+
sessionID,
|
|
652
651
|
msgKnownState: knownStateFromContent(msg).sessions,
|
|
652
|
+
msgSummary: getContenDebugInfo(msg),
|
|
653
653
|
knownState: coValue.knownState().sessions,
|
|
654
|
-
newContent: validNewContent.new,
|
|
655
654
|
});
|
|
656
655
|
// TODO Mark only the session as errored, not the whole coValue
|
|
657
|
-
coValue.markErrored(peer.id,
|
|
656
|
+
coValue.markErrored(peer.id, error);
|
|
658
657
|
} else {
|
|
659
658
|
logger.error("Failed to add transactions from storage", {
|
|
660
659
|
id: msg.id,
|
|
661
|
-
err:
|
|
660
|
+
err: error.error,
|
|
661
|
+
sessionID,
|
|
662
|
+
errorType: error.type,
|
|
662
663
|
});
|
|
663
664
|
}
|
|
664
665
|
continue;
|
|
@@ -669,9 +670,7 @@ export class SyncManager {
|
|
|
669
670
|
}
|
|
670
671
|
|
|
671
672
|
// The new content for this session has been verified, so we can store it
|
|
672
|
-
|
|
673
|
-
validNewContent.new[sessionID] = newContentForSession;
|
|
674
|
-
}
|
|
673
|
+
validNewContent.new[sessionID] = newContentForSession;
|
|
675
674
|
}
|
|
676
675
|
|
|
677
676
|
if (peer) {
|
|
@@ -771,7 +770,6 @@ export class SyncManager {
|
|
|
771
770
|
private syncQueue = new LocalTransactionsSyncQueue((content) =>
|
|
772
771
|
this.syncContent(content),
|
|
773
772
|
);
|
|
774
|
-
syncHeader = this.syncQueue.syncHeader;
|
|
775
773
|
syncLocalTransaction = this.syncQueue.syncTransaction;
|
|
776
774
|
trackDirtyCoValues = this.syncQueue.trackDirtyCoValues;
|
|
777
775
|
|
|
@@ -371,13 +371,12 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
371
371
|
}),
|
|
372
372
|
).toMatchInlineSnapshot(`
|
|
373
373
|
[
|
|
374
|
-
"client -> storage | CONTENT CoPlainText header: true new: After: 0 New:
|
|
375
|
-
"client -> storage | CONTENT CoPlainText header: false new: After: 1 New: 1",
|
|
374
|
+
"client -> storage | CONTENT CoPlainText header: true new: After: 0 New: 2 expectContentUntil: header/42",
|
|
376
375
|
"client -> storage | CONTENT CoPlainText header: false new: After: 2 New: 1",
|
|
377
376
|
"client -> storage | CONTENT CoPlainText header: false new: After: 3 New: 1",
|
|
378
377
|
"client -> storage | CONTENT CoPlainText header: false new: After: 4 New: 1",
|
|
379
|
-
"client -> storage | CONTENT CoPlainText header: false new: After: 5 New:
|
|
380
|
-
"client -> storage | CONTENT CoPlainText header: false new: After:
|
|
378
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 5 New: 1",
|
|
379
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 6 New: 2",
|
|
381
380
|
"client -> storage | CONTENT CoPlainText header: false new: After: 8 New: 1",
|
|
382
381
|
"client -> storage | CONTENT CoPlainText header: false new: After: 9 New: 1",
|
|
383
382
|
"client -> storage | CONTENT CoPlainText header: false new: After: 10 New: 1",
|
|
@@ -389,8 +388,8 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
389
388
|
"client -> storage | CONTENT CoPlainText header: false new: After: 16 New: 1",
|
|
390
389
|
"client -> storage | CONTENT CoPlainText header: false new: After: 17 New: 1",
|
|
391
390
|
"client -> storage | CONTENT CoPlainText header: false new: After: 18 New: 1",
|
|
392
|
-
"client -> storage | CONTENT CoPlainText header: false new: After: 19 New:
|
|
393
|
-
"client -> storage | CONTENT CoPlainText header: false new: After:
|
|
391
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 19 New: 1",
|
|
392
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 20 New: 2",
|
|
394
393
|
"client -> storage | CONTENT CoPlainText header: false new: After: 22 New: 1",
|
|
395
394
|
"client -> storage | CONTENT CoPlainText header: false new: After: 23 New: 1",
|
|
396
395
|
"client -> storage | CONTENT CoPlainText header: false new: After: 24 New: 1",
|
|
@@ -406,16 +405,16 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
406
405
|
"client -> storage | CONTENT CoPlainText header: false new: After: 34 New: 1",
|
|
407
406
|
"client -> storage | CONTENT CoPlainText header: false new: After: 35 New: 1",
|
|
408
407
|
"client -> storage | CONTENT CoPlainText header: false new: After: 36 New: 1",
|
|
409
|
-
"client -> storage | CONTENT CoPlainText header: false new: After: 37 New:
|
|
410
|
-
"client -> storage | CONTENT CoPlainText header: false new: After:
|
|
408
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 37 New: 1",
|
|
409
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 38 New: 3",
|
|
411
410
|
"client -> storage | CONTENT CoPlainText header: false new: After: 41 New: 1",
|
|
412
411
|
"client -> storage | LOAD CoPlainText sessions: empty",
|
|
413
412
|
"storage -> client | CONTENT CoPlainText header: true new: After: 0 New: 2 expectContentUntil: header/42",
|
|
414
413
|
"storage -> client | CONTENT CoPlainText header: true new: After: 2 New: 1",
|
|
415
414
|
"storage -> client | CONTENT CoPlainText header: true new: After: 3 New: 1",
|
|
416
415
|
"storage -> client | CONTENT CoPlainText header: true new: After: 4 New: 1",
|
|
417
|
-
"storage -> client | CONTENT CoPlainText header: true new: After: 5 New:
|
|
418
|
-
"storage -> client | CONTENT CoPlainText header: true new: After:
|
|
416
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 5 New: 1",
|
|
417
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 6 New: 2",
|
|
419
418
|
"storage -> client | CONTENT CoPlainText header: true new: After: 8 New: 1",
|
|
420
419
|
"storage -> client | CONTENT CoPlainText header: true new: After: 9 New: 1",
|
|
421
420
|
"storage -> client | CONTENT CoPlainText header: true new: After: 10 New: 1",
|
|
@@ -427,8 +426,8 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
427
426
|
"storage -> client | CONTENT CoPlainText header: true new: After: 16 New: 1",
|
|
428
427
|
"storage -> client | CONTENT CoPlainText header: true new: After: 17 New: 1",
|
|
429
428
|
"storage -> client | CONTENT CoPlainText header: true new: After: 18 New: 1",
|
|
430
|
-
"storage -> client | CONTENT CoPlainText header: true new: After: 19 New:
|
|
431
|
-
"storage -> client | CONTENT CoPlainText header: true new: After:
|
|
429
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 19 New: 1",
|
|
430
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 20 New: 2",
|
|
432
431
|
"storage -> client | CONTENT CoPlainText header: true new: After: 22 New: 1",
|
|
433
432
|
"storage -> client | CONTENT CoPlainText header: true new: After: 23 New: 1",
|
|
434
433
|
"storage -> client | CONTENT CoPlainText header: true new: After: 24 New: 1",
|
|
@@ -444,8 +443,8 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
444
443
|
"storage -> client | CONTENT CoPlainText header: true new: After: 34 New: 1",
|
|
445
444
|
"storage -> client | CONTENT CoPlainText header: true new: After: 35 New: 1",
|
|
446
445
|
"storage -> client | CONTENT CoPlainText header: true new: After: 36 New: 1",
|
|
447
|
-
"storage -> client | CONTENT CoPlainText header: true new: After: 37 New:
|
|
448
|
-
"storage -> client | CONTENT CoPlainText header: true new: After:
|
|
446
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 37 New: 1",
|
|
447
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 38 New: 3",
|
|
449
448
|
"storage -> client | CONTENT CoPlainText header: true new: After: 41 New: 1",
|
|
450
449
|
]
|
|
451
450
|
`);
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { describe, expect, test } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getNewTransactionsFromContentMessage,
|
|
4
|
+
knownStateFromContent,
|
|
5
|
+
} from "../coValueContentMessage.js";
|
|
3
6
|
import { emptyKnownState } from "../knownState.js";
|
|
4
|
-
import { NewContentMessage } from "../sync.js";
|
|
7
|
+
import { NewContentMessage, SessionNewContent } from "../sync.js";
|
|
5
8
|
import type { RawCoID, SessionID } from "../ids.js";
|
|
6
9
|
import { stableStringify } from "../jsonStringify.js";
|
|
7
10
|
import { CO_VALUE_PRIORITY } from "../priority.js";
|
|
11
|
+
import { CoValueKnownState } from "../knownState.js";
|
|
12
|
+
import { Transaction } from "../coValueCore/verifiedState.js";
|
|
8
13
|
|
|
9
14
|
describe("knownStateFromContent", () => {
|
|
10
15
|
const mockCoID: RawCoID = "co_z1234567890abcdef";
|
|
@@ -213,3 +218,193 @@ describe("knownStateFromContent", () => {
|
|
|
213
218
|
expect(result.sessions[mockSessionID1]).toBe(1); // 0 + 1
|
|
214
219
|
});
|
|
215
220
|
});
|
|
221
|
+
|
|
222
|
+
describe("getNewTransactionsFromContentMessage", () => {
|
|
223
|
+
const mockCoID: RawCoID = "co_z1234567890abcdef";
|
|
224
|
+
const mockSessionID: SessionID = "sealer_z123/signer_z456_session_z789";
|
|
225
|
+
|
|
226
|
+
function createTransaction(): Transaction {
|
|
227
|
+
return {
|
|
228
|
+
privacy: "trusting",
|
|
229
|
+
madeAt: Date.now(),
|
|
230
|
+
changes: stableStringify([{ op: "set", key: "test", value: "value" }]),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function createKnownState(
|
|
235
|
+
sessionTxIdx: number | undefined,
|
|
236
|
+
): CoValueKnownState {
|
|
237
|
+
const knownState = emptyKnownState(mockCoID);
|
|
238
|
+
knownState.header = true;
|
|
239
|
+
if (sessionTxIdx !== undefined) {
|
|
240
|
+
knownState.sessions[mockSessionID] = sessionTxIdx;
|
|
241
|
+
}
|
|
242
|
+
return knownState;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
test("returns all transactions when we know none (ourKnownTxIdx = 0, theirFirstNewTxIdx = 0)", () => {
|
|
246
|
+
const transactions = [createTransaction(), createTransaction()];
|
|
247
|
+
const content: SessionNewContent = {
|
|
248
|
+
after: 0,
|
|
249
|
+
newTransactions: transactions,
|
|
250
|
+
lastSignature: "signature_z1234",
|
|
251
|
+
};
|
|
252
|
+
const knownState = createKnownState(undefined); // defaults to 0
|
|
253
|
+
|
|
254
|
+
const result = getNewTransactionsFromContentMessage(
|
|
255
|
+
content,
|
|
256
|
+
knownState,
|
|
257
|
+
mockSessionID,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
expect(result).toEqual(transactions);
|
|
261
|
+
expect(result).toHaveLength(2);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("returns subset of transactions when we know some (ourKnownTxIdx = 3, theirFirstNewTxIdx = 0)", () => {
|
|
265
|
+
const transactions = [
|
|
266
|
+
createTransaction(),
|
|
267
|
+
createTransaction(),
|
|
268
|
+
createTransaction(),
|
|
269
|
+
createTransaction(),
|
|
270
|
+
createTransaction(),
|
|
271
|
+
];
|
|
272
|
+
const content: SessionNewContent = {
|
|
273
|
+
after: 0,
|
|
274
|
+
newTransactions: transactions,
|
|
275
|
+
lastSignature: "signature_z1234",
|
|
276
|
+
};
|
|
277
|
+
const knownState = createKnownState(3); // we already know txs 0, 1, 2
|
|
278
|
+
|
|
279
|
+
const result = getNewTransactionsFromContentMessage(
|
|
280
|
+
content,
|
|
281
|
+
knownState,
|
|
282
|
+
mockSessionID,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
expect(result).toEqual(transactions.slice(3));
|
|
286
|
+
expect(result).toHaveLength(2);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("returns undefined when we're missing transactions (ourKnownTxIdx = 2, theirFirstNewTxIdx = 5)", () => {
|
|
290
|
+
const transactions = [createTransaction(), createTransaction()];
|
|
291
|
+
const content: SessionNewContent = {
|
|
292
|
+
after: 5, // they're sending txs starting from idx 5
|
|
293
|
+
newTransactions: transactions,
|
|
294
|
+
lastSignature: "signature_z1234",
|
|
295
|
+
};
|
|
296
|
+
const knownState = createKnownState(2); // but we only know up to idx 2
|
|
297
|
+
|
|
298
|
+
const result = getNewTransactionsFromContentMessage(
|
|
299
|
+
content,
|
|
300
|
+
knownState,
|
|
301
|
+
mockSessionID,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
expect(result).toBeUndefined();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("returns empty array when we know all transactions (ourKnownTxIdx = 5, theirFirstNewTxIdx = 2)", () => {
|
|
308
|
+
const transactions = [
|
|
309
|
+
createTransaction(),
|
|
310
|
+
createTransaction(),
|
|
311
|
+
createTransaction(),
|
|
312
|
+
];
|
|
313
|
+
const content: SessionNewContent = {
|
|
314
|
+
after: 2, // they're sending txs 2, 3, 4
|
|
315
|
+
newTransactions: transactions,
|
|
316
|
+
lastSignature: "signature_z1234",
|
|
317
|
+
};
|
|
318
|
+
const knownState = createKnownState(5); // we already know txs up to idx 5
|
|
319
|
+
|
|
320
|
+
const result = getNewTransactionsFromContentMessage(
|
|
321
|
+
content,
|
|
322
|
+
knownState,
|
|
323
|
+
mockSessionID,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
expect(result).toEqual([]);
|
|
327
|
+
expect(result).toHaveLength(0);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("returns all transactions when ourKnownTxIdx equals theirFirstNewTxIdx", () => {
|
|
331
|
+
const transactions = [createTransaction(), createTransaction()];
|
|
332
|
+
const content: SessionNewContent = {
|
|
333
|
+
after: 3,
|
|
334
|
+
newTransactions: transactions,
|
|
335
|
+
lastSignature: "signature_z1234",
|
|
336
|
+
};
|
|
337
|
+
const knownState = createKnownState(3); // we know up to idx 3
|
|
338
|
+
|
|
339
|
+
const result = getNewTransactionsFromContentMessage(
|
|
340
|
+
content,
|
|
341
|
+
knownState,
|
|
342
|
+
mockSessionID,
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
expect(result).toEqual(transactions);
|
|
346
|
+
expect(result).toHaveLength(2);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("handles session not in knownState (defaults to 0)", () => {
|
|
350
|
+
const transactions = [createTransaction()];
|
|
351
|
+
const content: SessionNewContent = {
|
|
352
|
+
after: 1,
|
|
353
|
+
newTransactions: transactions,
|
|
354
|
+
lastSignature: "signature_z1234",
|
|
355
|
+
};
|
|
356
|
+
const knownState = emptyKnownState(mockCoID);
|
|
357
|
+
knownState.header = true;
|
|
358
|
+
// no sessions defined, so mockSessionID defaults to 0
|
|
359
|
+
|
|
360
|
+
const result = getNewTransactionsFromContentMessage(
|
|
361
|
+
content,
|
|
362
|
+
knownState,
|
|
363
|
+
mockSessionID,
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// ourKnownTxIdx = 0, theirFirstNewTxIdx = 1, so 0 < 1 -> undefined
|
|
367
|
+
expect(result).toBeUndefined();
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("returns single transaction when offset is length - 1", () => {
|
|
371
|
+
const transactions = [
|
|
372
|
+
createTransaction(),
|
|
373
|
+
createTransaction(),
|
|
374
|
+
createTransaction(),
|
|
375
|
+
];
|
|
376
|
+
const content: SessionNewContent = {
|
|
377
|
+
after: 5,
|
|
378
|
+
newTransactions: transactions,
|
|
379
|
+
lastSignature: "signature_z1234",
|
|
380
|
+
};
|
|
381
|
+
const knownState = createKnownState(7); // we know up to idx 7 (5 + 2 transactions)
|
|
382
|
+
|
|
383
|
+
const result = getNewTransactionsFromContentMessage(
|
|
384
|
+
content,
|
|
385
|
+
knownState,
|
|
386
|
+
mockSessionID,
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
expect(result).toEqual([transactions[2]]);
|
|
390
|
+
expect(result).toHaveLength(1);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("handles empty newTransactions array", () => {
|
|
394
|
+
const content: SessionNewContent = {
|
|
395
|
+
after: 5,
|
|
396
|
+
newTransactions: [],
|
|
397
|
+
lastSignature: "signature_z1234",
|
|
398
|
+
};
|
|
399
|
+
const knownState = createKnownState(5);
|
|
400
|
+
|
|
401
|
+
const result = getNewTransactionsFromContentMessage(
|
|
402
|
+
content,
|
|
403
|
+
knownState,
|
|
404
|
+
mockSessionID,
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
expect(result).toEqual([]);
|
|
408
|
+
expect(result).toHaveLength(0);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
@@ -537,8 +537,9 @@ describe("CoValueCore.isCompletelyDownloaded", () => {
|
|
|
537
537
|
bobSession.syncManager.handleNewContent(lastChunk, "import");
|
|
538
538
|
|
|
539
539
|
// Wait for the notification to be scheduled and executed
|
|
540
|
-
await waitFor(() =>
|
|
541
|
-
|
|
540
|
+
await waitFor(() => {
|
|
541
|
+
expect(mapOnBob.core.isCompletelyDownloaded()).toBe(true);
|
|
542
|
+
});
|
|
542
543
|
});
|
|
543
544
|
|
|
544
545
|
test.skip("should return false when the owner of the value is streaming", async () => {
|