cojson 0.13.17 → 0.13.20
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 +18 -0
- package/dist/PeerState.d.ts +4 -1
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +16 -36
- package/dist/PeerState.js.map +1 -1
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +2 -3
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValue.d.ts +4 -4
- package/dist/coValue.d.ts.map +1 -1
- package/dist/coValue.js +4 -4
- package/dist/coValue.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +143 -0
- package/dist/coValueCore/coValueCore.d.ts.map +1 -0
- package/dist/{coValueCore.js → coValueCore/coValueCore.js} +325 -253
- package/dist/coValueCore/coValueCore.js.map +1 -0
- package/dist/coValueCore/verifiedState.d.ts +65 -0
- package/dist/coValueCore/verifiedState.d.ts.map +1 -0
- package/dist/coValueCore/verifiedState.js +210 -0
- package/dist/coValueCore/verifiedState.js.map +1 -0
- package/dist/coValues/account.d.ts +8 -10
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +12 -13
- package/dist/coValues/account.js.map +1 -1
- package/dist/coValues/coList.d.ts +3 -3
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +6 -3
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -3
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +3 -3
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coPlainText.d.ts +2 -2
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +4 -4
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/coStream.d.ts +3 -3
- package/dist/coValues/coStream.d.ts.map +1 -1
- package/dist/coValues/coStream.js +3 -3
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/coValues/group.d.ts +7 -2
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +29 -26
- package/dist/coValues/group.js.map +1 -1
- package/dist/coreToCoValue.d.ts +2 -2
- package/dist/coreToCoValue.d.ts.map +1 -1
- package/dist/coreToCoValue.js +10 -14
- package/dist/coreToCoValue.js.map +1 -1
- package/dist/exports.d.ts +6 -5
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +3 -4
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +30 -24
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +147 -173
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts +2 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +15 -11
- package/dist/permissions.js.map +1 -1
- package/dist/priority.d.ts +1 -1
- package/dist/priority.d.ts.map +1 -1
- package/dist/streamUtils.d.ts +5 -5
- package/dist/streamUtils.d.ts.map +1 -1
- package/dist/streamUtils.js +5 -20
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.d.ts +8 -6
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +121 -74
- package/dist/sync.js.map +1 -1
- package/dist/tests/PeerState.test.js +0 -31
- package/dist/tests/PeerState.test.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +41 -6
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/account.test.js +16 -0
- package/dist/tests/account.test.js.map +1 -1
- package/dist/tests/coList.test.js +19 -16
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coMap.test.js +12 -13
- package/dist/tests/coMap.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +9 -10
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coStream.test.js +22 -17
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +22 -28
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueCoreLoadingState.test.d.ts +2 -0
- package/dist/tests/coValueCoreLoadingState.test.d.ts.map +1 -0
- package/dist/tests/{coValueState.test.js → coValueCoreLoadingState.test.js} +62 -46
- package/dist/tests/coValueCoreLoadingState.test.js.map +1 -0
- package/dist/tests/group.test.js +42 -43
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +2 -2
- package/dist/tests/messagesTestUtils.d.ts.map +1 -1
- package/dist/tests/messagesTestUtils.js +1 -1
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/permissions.test.js +224 -292
- package/dist/tests/permissions.test.js.map +1 -1
- package/dist/tests/priority.test.js +13 -14
- package/dist/tests/priority.test.js.map +1 -1
- package/dist/tests/sync.auth.test.d.ts +2 -0
- package/dist/tests/sync.auth.test.d.ts.map +1 -0
- package/dist/tests/sync.auth.test.js +190 -0
- package/dist/tests/sync.auth.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +6 -6
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +25 -12
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +19 -19
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +20 -13
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +32 -39
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +126 -37
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +35 -17
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +103 -79
- package/dist/tests/testUtils.js.map +1 -1
- package/dist/typeUtils/expectGroup.js +1 -1
- package/dist/typeUtils/expectGroup.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerState.ts +19 -40
- package/src/SyncStateManager.ts +2 -3
- package/src/coValue.ts +11 -8
- package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +478 -422
- package/src/coValueCore/verifiedState.ts +376 -0
- package/src/coValues/account.ts +20 -25
- package/src/coValues/coList.ts +12 -6
- package/src/coValues/coMap.ts +9 -6
- package/src/coValues/coPlainText.ts +9 -6
- package/src/coValues/coStream.ts +9 -6
- package/src/coValues/group.ts +50 -28
- package/src/coreToCoValue.ts +14 -15
- package/src/exports.ts +9 -7
- package/src/localNode.ts +236 -275
- package/src/permissions.ts +18 -12
- package/src/priority.ts +1 -1
- package/src/streamUtils.ts +7 -34
- package/src/sync.ts +146 -84
- package/src/tests/PeerState.test.ts +0 -37
- package/src/tests/SyncStateManager.test.ts +56 -6
- package/src/tests/account.test.ts +24 -0
- package/src/tests/coList.test.ts +21 -15
- package/src/tests/coMap.test.ts +12 -13
- package/src/tests/coPlainText.test.ts +12 -9
- package/src/tests/coStream.test.ts +25 -16
- package/src/tests/coValueCore.test.ts +30 -27
- package/src/tests/{coValueState.test.ts → coValueCoreLoadingState.test.ts} +67 -57
- package/src/tests/group.test.ts +44 -69
- package/src/tests/messagesTestUtils.ts +3 -8
- package/src/tests/permissions.test.ts +283 -449
- package/src/tests/priority.test.ts +17 -13
- package/src/tests/sync.auth.test.ts +246 -0
- package/src/tests/sync.load.test.ts +7 -6
- package/src/tests/sync.mesh.test.ts +25 -12
- package/src/tests/sync.peerReconciliation.test.ts +25 -25
- package/src/tests/sync.storage.test.ts +20 -13
- package/src/tests/sync.test.ts +43 -43
- package/src/tests/sync.upload.test.ts +157 -37
- package/src/tests/testUtils.ts +143 -96
- package/src/typeUtils/expectGroup.ts +1 -1
- package/dist/CoValuesStore.d.ts +0 -14
- package/dist/CoValuesStore.d.ts.map +0 -1
- package/dist/CoValuesStore.js +0 -32
- package/dist/CoValuesStore.js.map +0 -1
- package/dist/coValueCore.d.ts +0 -142
- package/dist/coValueCore.d.ts.map +0 -1
- package/dist/coValueCore.js.map +0 -1
- package/dist/coValueState.d.ts +0 -34
- package/dist/coValueState.d.ts.map +0 -1
- package/dist/coValueState.js +0 -190
- package/dist/coValueState.js.map +0 -1
- package/dist/tests/coValueState.test.d.ts +0 -2
- package/dist/tests/coValueState.test.d.ts.map +0 -1
- package/dist/tests/coValueState.test.js.map +0 -1
- package/src/CoValuesStore.ts +0 -41
- package/src/coValueState.ts +0 -245
package/src/permissions.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CoID } from "./coValue.js";
|
|
2
|
-
import { CoValueCore
|
|
2
|
+
import { CoValueCore } from "./coValueCore/coValueCore.js";
|
|
3
|
+
import { Transaction } from "./coValueCore/verifiedState.js";
|
|
3
4
|
import { RawAccount, RawAccountID, RawProfile } from "./coValues/account.js";
|
|
4
5
|
import { MapOpPayload } from "./coValues/coMap.js";
|
|
5
6
|
import {
|
|
@@ -64,19 +65,23 @@ export function determineValidTransactions(
|
|
|
64
65
|
coValue: CoValueCore,
|
|
65
66
|
knownTransactions?: CoValueKnownState["sessions"],
|
|
66
67
|
): { txID: TransactionID; tx: Transaction }[] {
|
|
67
|
-
if (coValue.
|
|
68
|
-
|
|
68
|
+
if (!coValue.isAvailable()) {
|
|
69
|
+
throw new Error("determineValidTransactions CoValue is not available");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (coValue.verified.header.ruleset.type === "group") {
|
|
73
|
+
const initialAdmin = coValue.verified.header.ruleset.initialAdmin;
|
|
69
74
|
if (!initialAdmin) {
|
|
70
75
|
throw new Error("Group must have initialAdmin");
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
return determineValidTransactionsForGroup(coValue, initialAdmin)
|
|
74
79
|
.validTransactions;
|
|
75
|
-
} else if (coValue.header.ruleset.type === "ownedByGroup") {
|
|
80
|
+
} else if (coValue.verified.header.ruleset.type === "ownedByGroup") {
|
|
76
81
|
const groupContent = expectGroup(
|
|
77
82
|
coValue.node
|
|
78
83
|
.expectCoValueLoaded(
|
|
79
|
-
coValue.header.ruleset.group,
|
|
84
|
+
coValue.verified.header.ruleset.group,
|
|
80
85
|
"Determining valid transaction in owned object but its group wasn't loaded",
|
|
81
86
|
)
|
|
82
87
|
.getCurrentContent(),
|
|
@@ -88,7 +93,7 @@ export function determineValidTransactions(
|
|
|
88
93
|
|
|
89
94
|
const validTransactions: ValidTransactionsResult[] = [];
|
|
90
95
|
|
|
91
|
-
for (const [sessionID, sessionLog] of coValue.
|
|
96
|
+
for (const [sessionID, sessionLog] of coValue.verified.sessions.entries()) {
|
|
92
97
|
const transactor = accountOrAgentIDfromSessionID(sessionID);
|
|
93
98
|
const knownTransactionsForSession = knownTransactions?.[sessionID] ?? -1;
|
|
94
99
|
|
|
@@ -123,10 +128,10 @@ export function determineValidTransactions(
|
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
return validTransactions;
|
|
126
|
-
} else if (coValue.header.ruleset.type === "unsafeAllowAll") {
|
|
131
|
+
} else if (coValue.verified.header.ruleset.type === "unsafeAllowAll") {
|
|
127
132
|
const validTransactions: ValidTransactionsResult[] = [];
|
|
128
133
|
|
|
129
|
-
for (const [sessionID, sessionLog] of coValue.
|
|
134
|
+
for (const [sessionID, sessionLog] of coValue.verified.sessions.entries()) {
|
|
130
135
|
const knownTransactionsForSession = knownTransactions?.[sessionID] ?? -1;
|
|
131
136
|
|
|
132
137
|
sessionLog.transactions.forEach((tx, txIndex) => {
|
|
@@ -141,7 +146,7 @@ export function determineValidTransactions(
|
|
|
141
146
|
} else {
|
|
142
147
|
throw new Error(
|
|
143
148
|
"Unknown ruleset type " +
|
|
144
|
-
(coValue.header.ruleset as { type: string }).type,
|
|
149
|
+
(coValue.verified.header.ruleset as { type: string }).type,
|
|
145
150
|
);
|
|
146
151
|
}
|
|
147
152
|
}
|
|
@@ -167,7 +172,7 @@ function resolveMemberStateFromParentReference(
|
|
|
167
172
|
"Expected parent group to be loaded",
|
|
168
173
|
);
|
|
169
174
|
|
|
170
|
-
if (parentGroup.header.ruleset.type !== "group") {
|
|
175
|
+
if (parentGroup.verified.header.ruleset.type !== "group") {
|
|
171
176
|
return;
|
|
172
177
|
}
|
|
173
178
|
|
|
@@ -176,7 +181,7 @@ function resolveMemberStateFromParentReference(
|
|
|
176
181
|
return;
|
|
177
182
|
}
|
|
178
183
|
|
|
179
|
-
const initialAdmin = parentGroup.header.ruleset.initialAdmin;
|
|
184
|
+
const initialAdmin = parentGroup.verified.header.ruleset.initialAdmin;
|
|
180
185
|
|
|
181
186
|
if (!initialAdmin) {
|
|
182
187
|
throw new Error("Group must have initialAdmin");
|
|
@@ -214,7 +219,8 @@ function determineValidTransactionsForGroup(
|
|
|
214
219
|
tx: Transaction;
|
|
215
220
|
}[] = [];
|
|
216
221
|
|
|
217
|
-
for (const [sessionID, sessionLog] of coValue.
|
|
222
|
+
for (const [sessionID, sessionLog] of coValue.verified?.sessions.entries() ??
|
|
223
|
+
[]) {
|
|
218
224
|
sessionLog.transactions.forEach((tx, txIndex) => {
|
|
219
225
|
allTransactionsSorted.push({ sessionID, txIndex, tx });
|
|
220
226
|
});
|
package/src/priority.ts
CHANGED
package/src/streamUtils.ts
CHANGED
|
@@ -6,23 +6,17 @@ export function connectedPeers(
|
|
|
6
6
|
peer1id: PeerID,
|
|
7
7
|
peer2id: PeerID,
|
|
8
8
|
{
|
|
9
|
-
trace = false,
|
|
10
9
|
peer1role = "client",
|
|
11
10
|
peer2role = "client",
|
|
12
11
|
crashOnClose = false,
|
|
13
12
|
}: {
|
|
14
|
-
trace?: boolean;
|
|
15
13
|
peer1role?: Peer["role"];
|
|
16
14
|
peer2role?: Peer["role"];
|
|
17
15
|
crashOnClose?: boolean;
|
|
18
16
|
} = {},
|
|
19
17
|
): [Peer, Peer] {
|
|
20
|
-
const [from1to2Rx, from1to2Tx] = newQueuePair(
|
|
21
|
-
|
|
22
|
-
);
|
|
23
|
-
const [from2to1Rx, from2to1Tx] = newQueuePair(
|
|
24
|
-
trace ? { traceAs: `${peer2id} -> ${peer1id}` } : undefined,
|
|
25
|
-
);
|
|
18
|
+
const [from1to2Rx, from1to2Tx] = newQueuePair();
|
|
19
|
+
const [from2to1Rx, from2to1Tx] = newQueuePair();
|
|
26
20
|
|
|
27
21
|
const peer2AsPeer: Peer = {
|
|
28
22
|
id: peer2id,
|
|
@@ -43,32 +37,11 @@ export function connectedPeers(
|
|
|
43
37
|
return [peer1AsPeer, peer2AsPeer];
|
|
44
38
|
}
|
|
45
39
|
|
|
46
|
-
export function newQueuePair(
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
export function newQueuePair(): [
|
|
41
|
+
AsyncIterable<SyncMessage>,
|
|
42
|
+
Channel<SyncMessage>,
|
|
43
|
+
] {
|
|
49
44
|
const channel = new Channel<SyncMessage>();
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
return [
|
|
53
|
-
(async function* () {
|
|
54
|
-
for await (const msg of channel) {
|
|
55
|
-
console.debug(
|
|
56
|
-
options.traceAs,
|
|
57
|
-
JSON.stringify(
|
|
58
|
-
msg,
|
|
59
|
-
(k, v) =>
|
|
60
|
-
k === "changes" || k === "encryptedChanges"
|
|
61
|
-
? v.slice(0, 20) + "..."
|
|
62
|
-
: v,
|
|
63
|
-
2,
|
|
64
|
-
),
|
|
65
|
-
);
|
|
66
|
-
yield msg;
|
|
67
|
-
}
|
|
68
|
-
})(),
|
|
69
|
-
channel,
|
|
70
|
-
];
|
|
71
|
-
} else {
|
|
72
|
-
return [channel.wrap(), channel];
|
|
73
|
-
}
|
|
46
|
+
return [channel.wrap(), channel];
|
|
74
47
|
}
|
package/src/sync.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Histogram, ValueType, metrics } from "@opentelemetry/api";
|
|
2
2
|
import { PeerState } from "./PeerState.js";
|
|
3
3
|
import { SyncStateManager } from "./SyncStateManager.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {
|
|
5
|
+
AvailableCoValueCore,
|
|
6
|
+
CoValueCore,
|
|
7
|
+
} from "./coValueCore/coValueCore.js";
|
|
8
|
+
import { CoValueHeader, Transaction } from "./coValueCore/verifiedState.js";
|
|
7
9
|
import { Signature } from "./crypto/crypto.js";
|
|
8
10
|
import { RawCoID, SessionID } from "./ids.js";
|
|
9
11
|
import { LocalNode } from "./localNode.js";
|
|
@@ -157,8 +159,14 @@ export class SyncManager {
|
|
|
157
159
|
);
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
hasStoragePeers(): boolean {
|
|
163
|
+
return this.getPeers().some(
|
|
164
|
+
(peer) => peer.role === "storage" && !peer.closed,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
160
168
|
handleSyncMessage(msg: SyncMessage, peer: PeerState) {
|
|
161
|
-
if (this.local.
|
|
169
|
+
if (this.local.getCoValue(msg.id).isErroredInPeer(peer.id)) {
|
|
162
170
|
logger.warn(
|
|
163
171
|
`Skipping message ${msg.action} on errored coValue ${msg.id} from peer ${peer.id}`,
|
|
164
172
|
);
|
|
@@ -197,13 +205,17 @@ export class SyncManager {
|
|
|
197
205
|
}
|
|
198
206
|
|
|
199
207
|
sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
|
|
200
|
-
const coValue = this.local.
|
|
208
|
+
const coValue = this.local.getCoValue(id);
|
|
209
|
+
|
|
210
|
+
if (!coValue.isAvailable()) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
201
213
|
|
|
202
214
|
coValue
|
|
203
215
|
.getDependedOnCoValues()
|
|
204
216
|
.map((id) => this.sendNewContentIncludingDependencies(id, peer));
|
|
205
217
|
|
|
206
|
-
const newContentPieces = coValue.newContentSince(
|
|
218
|
+
const newContentPieces = coValue.verified.newContentSince(
|
|
207
219
|
peer.optimisticKnownStates.get(id),
|
|
208
220
|
);
|
|
209
221
|
|
|
@@ -212,16 +224,15 @@ export class SyncManager {
|
|
|
212
224
|
this.trySendToPeer(peer, piece);
|
|
213
225
|
}
|
|
214
226
|
|
|
215
|
-
peer.toldKnownState.add(id);
|
|
216
227
|
peer.combineOptimisticWith(id, coValue.knownState());
|
|
217
228
|
} else if (!peer.toldKnownState.has(id)) {
|
|
218
229
|
this.trySendToPeer(peer, {
|
|
219
230
|
action: "known",
|
|
220
231
|
...coValue.knownState(),
|
|
221
232
|
});
|
|
222
|
-
|
|
223
|
-
peer.toldKnownState.add(id);
|
|
224
233
|
}
|
|
234
|
+
|
|
235
|
+
peer.trackToldKnownState(id);
|
|
225
236
|
}
|
|
226
237
|
|
|
227
238
|
startPeerReconciliation(peer: PeerState) {
|
|
@@ -237,40 +248,38 @@ export class SyncManager {
|
|
|
237
248
|
gathered.add(coValue.id);
|
|
238
249
|
|
|
239
250
|
for (const id of coValue.getDependedOnCoValues()) {
|
|
240
|
-
const
|
|
251
|
+
const coValue = this.local.getCoValue(id);
|
|
241
252
|
|
|
242
|
-
if (
|
|
243
|
-
buildOrderedCoValueList(
|
|
253
|
+
if (coValue.isAvailable()) {
|
|
254
|
+
buildOrderedCoValueList(coValue);
|
|
244
255
|
}
|
|
245
256
|
}
|
|
246
257
|
|
|
247
258
|
coValuesOrderedByDependency.push(coValue);
|
|
248
259
|
};
|
|
249
260
|
|
|
250
|
-
for (const
|
|
251
|
-
if (!
|
|
261
|
+
for (const coValue of this.local.allCoValues()) {
|
|
262
|
+
if (!coValue.isAvailable()) {
|
|
252
263
|
// If the coValue is unavailable and we never tried this peer
|
|
253
264
|
// we try to load it from the peer
|
|
254
|
-
if (!peer.
|
|
255
|
-
peer.
|
|
265
|
+
if (!peer.loadRequestSent.has(coValue.id)) {
|
|
266
|
+
peer.trackLoadRequestSent(coValue.id);
|
|
256
267
|
this.trySendToPeer(peer, {
|
|
257
268
|
action: "load",
|
|
258
269
|
header: false,
|
|
259
|
-
id:
|
|
270
|
+
id: coValue.id,
|
|
260
271
|
sessions: {},
|
|
261
272
|
});
|
|
262
273
|
}
|
|
263
274
|
} else {
|
|
264
|
-
const coValue = entry.core;
|
|
265
|
-
|
|
266
275
|
// Build the list of coValues ordered by dependency
|
|
267
276
|
// so we can send the load message in the correct order
|
|
268
277
|
buildOrderedCoValueList(coValue);
|
|
269
278
|
}
|
|
270
279
|
|
|
271
280
|
// Fill the missing known states with empty known states
|
|
272
|
-
if (!peer.optimisticKnownStates.has(
|
|
273
|
-
peer.setOptimisticKnownState(
|
|
281
|
+
if (!peer.optimisticKnownStates.has(coValue.id)) {
|
|
282
|
+
peer.setOptimisticKnownState(coValue.id, "empty");
|
|
274
283
|
}
|
|
275
284
|
}
|
|
276
285
|
|
|
@@ -281,7 +290,7 @@ export class SyncManager {
|
|
|
281
290
|
* - Start the sync process in case we or the other peer
|
|
282
291
|
* lacks some transactions
|
|
283
292
|
*/
|
|
284
|
-
peer.
|
|
293
|
+
peer.trackLoadRequestSent(coValue.id);
|
|
285
294
|
this.trySendToPeer(peer, {
|
|
286
295
|
action: "load",
|
|
287
296
|
...coValue.knownState(),
|
|
@@ -368,11 +377,11 @@ export class SyncManager {
|
|
|
368
377
|
*
|
|
369
378
|
*/
|
|
370
379
|
peer.setKnownState(msg.id, knownStateIn(msg));
|
|
371
|
-
const
|
|
380
|
+
const coValue = this.local.getCoValue(msg.id);
|
|
372
381
|
|
|
373
382
|
if (
|
|
374
|
-
|
|
375
|
-
|
|
383
|
+
coValue.loadingState === "unknown" ||
|
|
384
|
+
coValue.loadingState === "unavailable"
|
|
376
385
|
) {
|
|
377
386
|
const eligiblePeers = this.getServerAndStoragePeers(peer.id);
|
|
378
387
|
|
|
@@ -380,8 +389,7 @@ export class SyncManager {
|
|
|
380
389
|
// We don't have any eligible peers to load the coValue from
|
|
381
390
|
// so we send a known state back to the sender to let it know
|
|
382
391
|
// that the coValue is unavailable
|
|
383
|
-
peer.
|
|
384
|
-
|
|
392
|
+
peer.trackToldKnownState(msg.id);
|
|
385
393
|
this.trySendToPeer(peer, {
|
|
386
394
|
action: "known",
|
|
387
395
|
id: msg.id,
|
|
@@ -391,24 +399,25 @@ export class SyncManager {
|
|
|
391
399
|
|
|
392
400
|
return;
|
|
393
401
|
} else {
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
+
});
|
|
398
408
|
}
|
|
399
409
|
}
|
|
400
410
|
|
|
401
|
-
if (
|
|
411
|
+
if (coValue.loadingState === "loading") {
|
|
402
412
|
// We need to return from handleLoad immediately and wait for the CoValue to be loaded
|
|
403
413
|
// in a new task, otherwise we might block further incoming content messages that would
|
|
404
414
|
// resolve the CoValue as available. This can happen when we receive fresh
|
|
405
415
|
// content from a client, but we are a server with our own upstream server(s)
|
|
406
|
-
|
|
407
|
-
.
|
|
416
|
+
coValue
|
|
417
|
+
.waitForAvailableOrUnavailable()
|
|
408
418
|
.then(async (value) => {
|
|
409
|
-
if (value
|
|
410
|
-
peer.
|
|
411
|
-
|
|
419
|
+
if (!value.isAvailable()) {
|
|
420
|
+
peer.trackToldKnownState(msg.id);
|
|
412
421
|
this.trySendToPeer(peer, {
|
|
413
422
|
action: "known",
|
|
414
423
|
id: msg.id,
|
|
@@ -426,9 +435,10 @@ export class SyncManager {
|
|
|
426
435
|
err: e,
|
|
427
436
|
});
|
|
428
437
|
});
|
|
429
|
-
} else if (
|
|
438
|
+
} else if (coValue.isAvailable()) {
|
|
430
439
|
this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
431
440
|
} else {
|
|
441
|
+
peer.trackToldKnownState(msg.id);
|
|
432
442
|
this.trySendToPeer(peer, {
|
|
433
443
|
action: "known",
|
|
434
444
|
id: msg.id,
|
|
@@ -439,7 +449,7 @@ export class SyncManager {
|
|
|
439
449
|
}
|
|
440
450
|
|
|
441
451
|
handleKnownState(msg: KnownStateMessage, peer: PeerState) {
|
|
442
|
-
const
|
|
452
|
+
const coValue = this.local.getCoValue(msg.id);
|
|
443
453
|
|
|
444
454
|
peer.combineWith(msg.id, knownStateIn(msg));
|
|
445
455
|
|
|
@@ -448,10 +458,10 @@ export class SyncManager {
|
|
|
448
458
|
const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
|
|
449
459
|
|
|
450
460
|
if (!availableOnPeer) {
|
|
451
|
-
|
|
461
|
+
coValue.markNotFoundInPeer(peer.id);
|
|
452
462
|
}
|
|
453
463
|
|
|
454
|
-
if (
|
|
464
|
+
if (coValue.isAvailable()) {
|
|
455
465
|
this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
456
466
|
}
|
|
457
467
|
}
|
|
@@ -470,11 +480,9 @@ export class SyncManager {
|
|
|
470
480
|
}
|
|
471
481
|
|
|
472
482
|
handleNewContent(msg: NewContentMessage, peer: PeerState) {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
let coValue: CoValueCore;
|
|
483
|
+
const coValue = this.local.getCoValue(msg.id);
|
|
476
484
|
|
|
477
|
-
if (!
|
|
485
|
+
if (!coValue.isAvailable()) {
|
|
478
486
|
if (!msg.header) {
|
|
479
487
|
this.trySendToPeer(peer, {
|
|
480
488
|
action: "known",
|
|
@@ -487,12 +495,11 @@ export class SyncManager {
|
|
|
487
495
|
}
|
|
488
496
|
|
|
489
497
|
peer.updateHeader(msg.id, true);
|
|
498
|
+
coValue.markAvailable(msg.header, peer.id);
|
|
499
|
+
}
|
|
490
500
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
entry.markAvailable(coValue, peer.id);
|
|
494
|
-
} else {
|
|
495
|
-
coValue = entry.core;
|
|
501
|
+
if (!coValue.isAvailable()) {
|
|
502
|
+
throw new Error("Unreachable: CoValue should be available in every case");
|
|
496
503
|
}
|
|
497
504
|
|
|
498
505
|
let invalidStateAssumed = false;
|
|
@@ -502,7 +509,7 @@ export class SyncManager {
|
|
|
502
509
|
SessionNewContent,
|
|
503
510
|
][]) {
|
|
504
511
|
const ourKnownTxIdx =
|
|
505
|
-
coValue.
|
|
512
|
+
coValue.verified.sessions.get(sessionID)?.transactions.length;
|
|
506
513
|
const theirFirstNewTxIdx = newContentForSession.after;
|
|
507
514
|
|
|
508
515
|
if ((ourKnownTxIdx || 0) < theirFirstNewTxIdx) {
|
|
@@ -526,16 +533,17 @@ export class SyncManager {
|
|
|
526
533
|
newTransactions,
|
|
527
534
|
undefined,
|
|
528
535
|
newContentForSession.lastSignature,
|
|
536
|
+
"immediate", // TODO: can we change this to deferred?
|
|
529
537
|
);
|
|
530
538
|
|
|
531
539
|
if (result.isErr()) {
|
|
532
|
-
|
|
540
|
+
console.error("Failed to add transactions", {
|
|
533
541
|
peerId: peer.id,
|
|
534
542
|
peerRole: peer.role,
|
|
535
543
|
id: msg.id,
|
|
536
544
|
err: result.error,
|
|
537
545
|
});
|
|
538
|
-
|
|
546
|
+
coValue.markErrored(peer.id, result.error);
|
|
539
547
|
continue;
|
|
540
548
|
}
|
|
541
549
|
|
|
@@ -555,7 +563,7 @@ export class SyncManager {
|
|
|
555
563
|
isCorrection: true,
|
|
556
564
|
...coValue.knownState(),
|
|
557
565
|
});
|
|
558
|
-
peer.
|
|
566
|
+
peer.trackToldKnownState(msg.id);
|
|
559
567
|
} else {
|
|
560
568
|
/**
|
|
561
569
|
* We are sending a known state message to the peer to acknowledge the
|
|
@@ -568,15 +576,50 @@ export class SyncManager {
|
|
|
568
576
|
action: "known",
|
|
569
577
|
...coValue.knownState(),
|
|
570
578
|
});
|
|
571
|
-
peer.
|
|
579
|
+
peer.trackToldKnownState(msg.id);
|
|
572
580
|
}
|
|
573
581
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
582
|
+
const sourcePeer = peer;
|
|
583
|
+
const syncedPeers = [];
|
|
584
|
+
|
|
585
|
+
for (const peer of this.peersInPriorityOrder()) {
|
|
586
|
+
/**
|
|
587
|
+
* We sync the content against the source peer if it is a client or server peers
|
|
588
|
+
* to upload any content that is available on the current node and not on the source peer.
|
|
589
|
+
*
|
|
590
|
+
* We don't need to do this with storage peers because we don't get updates from those peers,
|
|
591
|
+
* only load and store content.
|
|
592
|
+
*/
|
|
593
|
+
if (peer.id === sourcePeer.id && sourcePeer.role === "storage") continue;
|
|
594
|
+
if (peer.closed) continue;
|
|
595
|
+
if (coValue.isErroredInPeer(peer.id)) continue;
|
|
596
|
+
|
|
597
|
+
// We directly forward the new content to peers that have an active subscription
|
|
598
|
+
if (peer.optimisticKnownStates.has(coValue.id)) {
|
|
599
|
+
this.sendNewContentIncludingDependencies(coValue.id, peer);
|
|
600
|
+
syncedPeers.push(peer);
|
|
601
|
+
} else if (
|
|
602
|
+
peer.isServerOrStoragePeer() &&
|
|
603
|
+
!peer.loadRequestSent.has(coValue.id)
|
|
604
|
+
) {
|
|
605
|
+
const state = coValue.getStateForPeer(peer.id)?.type;
|
|
606
|
+
|
|
607
|
+
// Check if there is a inflight load operation and we
|
|
608
|
+
// are waiting for other peers to send the load request
|
|
609
|
+
if (state === "unknown" || state === undefined) {
|
|
610
|
+
this.trySendToPeer(peer, {
|
|
611
|
+
action: "load",
|
|
612
|
+
...coValue.knownState(),
|
|
613
|
+
});
|
|
614
|
+
peer.trackLoadRequestSent(coValue.id);
|
|
615
|
+
syncedPeers.push(peer);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
for (const peer of syncedPeers) {
|
|
621
|
+
this.syncState.triggerUpdate(peer.id, coValue.id);
|
|
622
|
+
}
|
|
580
623
|
}
|
|
581
624
|
|
|
582
625
|
handleCorrection(msg: KnownStateMessage, peer: PeerState) {
|
|
@@ -587,33 +630,27 @@ export class SyncManager {
|
|
|
587
630
|
|
|
588
631
|
handleUnsubscribe(_msg: DoneMessage) {}
|
|
589
632
|
|
|
590
|
-
requestedSyncs = new
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
633
|
+
requestedSyncs = new Set<RawCoID>();
|
|
634
|
+
requestCoValueSync(coValue: CoValueCore) {
|
|
635
|
+
if (this.requestedSyncs.has(coValue.id)) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
594
638
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
this.requestedSyncs.delete(coValue.id);
|
|
601
|
-
this.syncCoValue(coValue);
|
|
602
|
-
resolve();
|
|
603
|
-
});
|
|
604
|
-
});
|
|
639
|
+
queueMicrotask(() => {
|
|
640
|
+
if (this.requestedSyncs.has(coValue.id)) {
|
|
641
|
+
this.syncCoValue(coValue);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
605
644
|
|
|
606
|
-
|
|
607
|
-
return promise;
|
|
608
|
-
}
|
|
645
|
+
this.requestedSyncs.add(coValue.id);
|
|
609
646
|
}
|
|
610
647
|
|
|
611
648
|
async syncCoValue(coValue: CoValueCore) {
|
|
612
|
-
|
|
649
|
+
this.requestedSyncs.delete(coValue.id);
|
|
613
650
|
|
|
614
651
|
for (const peer of this.peersInPriorityOrder()) {
|
|
615
652
|
if (peer.closed) continue;
|
|
616
|
-
if (
|
|
653
|
+
if (coValue.isErroredInPeer(peer.id)) continue;
|
|
617
654
|
|
|
618
655
|
// Only subscribed CoValues are synced to clients
|
|
619
656
|
if (
|
|
@@ -641,6 +678,21 @@ export class SyncManager {
|
|
|
641
678
|
return true;
|
|
642
679
|
}
|
|
643
680
|
|
|
681
|
+
const peerState = this.peers[peerId];
|
|
682
|
+
|
|
683
|
+
// The peer has been closed, so it isn't possible to sync
|
|
684
|
+
if (!peerState || peerState.closed) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// The client isn't subscribed to the coValue, so we won't sync it
|
|
689
|
+
if (
|
|
690
|
+
peerState.role === "client" &&
|
|
691
|
+
!peerState.optimisticKnownStates.has(id)
|
|
692
|
+
) {
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
|
|
644
696
|
return new Promise((resolve, reject) => {
|
|
645
697
|
const unsubscribe = this.syncState.subscribeToPeerUpdates(
|
|
646
698
|
peerId,
|
|
@@ -660,20 +712,30 @@ export class SyncManager {
|
|
|
660
712
|
});
|
|
661
713
|
}
|
|
662
714
|
|
|
715
|
+
async waitForStorageSync(id: RawCoID, timeout = 30_000) {
|
|
716
|
+
const peers = this.getPeers();
|
|
717
|
+
|
|
718
|
+
await Promise.all(
|
|
719
|
+
peers
|
|
720
|
+
.filter((peer) => peer.role === "storage")
|
|
721
|
+
.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
663
725
|
async waitForSync(id: RawCoID, timeout = 30_000) {
|
|
664
726
|
const peers = this.getPeers();
|
|
665
727
|
|
|
666
|
-
|
|
728
|
+
await Promise.all(
|
|
667
729
|
peers.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
|
|
668
730
|
);
|
|
669
731
|
}
|
|
670
732
|
|
|
671
733
|
async waitForAllCoValuesSync(timeout = 60_000) {
|
|
672
|
-
const coValues = this.local.
|
|
734
|
+
const coValues = this.local.allCoValues();
|
|
673
735
|
const validCoValues = Array.from(coValues).filter(
|
|
674
736
|
(coValue) =>
|
|
675
|
-
coValue.
|
|
676
|
-
coValue.
|
|
737
|
+
coValue.loadingState === "available" ||
|
|
738
|
+
coValue.loadingState === "loading",
|
|
677
739
|
);
|
|
678
740
|
|
|
679
741
|
return Promise.all(
|
|
@@ -174,9 +174,6 @@ describe("PeerState", () => {
|
|
|
174
174
|
test("should dispatch to both states", () => {
|
|
175
175
|
const { peerState } = setup();
|
|
176
176
|
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
|
|
177
|
-
if (peerState._optimisticKnownStates === "assumeInfallible") {
|
|
178
|
-
throw new Error("Expected normal optimisticKnownStates");
|
|
179
|
-
}
|
|
180
177
|
|
|
181
178
|
const optimisticKnownStatesSpy = vi.spyOn(
|
|
182
179
|
peerState._optimisticKnownStates,
|
|
@@ -195,40 +192,6 @@ describe("PeerState", () => {
|
|
|
195
192
|
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith("co_z1", state);
|
|
196
193
|
});
|
|
197
194
|
|
|
198
|
-
test("should use same reference for knownStates and optimisticKnownStates for storage peers", () => {
|
|
199
|
-
const mockStoragePeer: Peer = {
|
|
200
|
-
id: "test-storage-peer",
|
|
201
|
-
role: "storage",
|
|
202
|
-
priority: 1,
|
|
203
|
-
crashOnClose: false,
|
|
204
|
-
incoming: (async function* () {})(),
|
|
205
|
-
outgoing: {
|
|
206
|
-
push: vi.fn().mockResolvedValue(undefined),
|
|
207
|
-
close: vi.fn(),
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
const peerState = new PeerState(mockStoragePeer, undefined);
|
|
211
|
-
|
|
212
|
-
// Verify they are the same reference
|
|
213
|
-
expect(peerState.knownStates).toBe(peerState.optimisticKnownStates);
|
|
214
|
-
|
|
215
|
-
// Verify that dispatching only updates one state
|
|
216
|
-
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
|
|
217
|
-
expect(peerState._optimisticKnownStates).toBe("assumeInfallible");
|
|
218
|
-
|
|
219
|
-
const state: CoValueKnownState = {
|
|
220
|
-
id: "co_z1",
|
|
221
|
-
header: false,
|
|
222
|
-
sessions: {},
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
peerState.setKnownState("co_z1", state);
|
|
226
|
-
|
|
227
|
-
// Only one dispatch should happen since they're the same reference
|
|
228
|
-
expect(knownStatesSpy).toHaveBeenCalledTimes(1);
|
|
229
|
-
expect(knownStatesSpy).toHaveBeenCalledWith("co_z1", state);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
195
|
test("should use separate references for knownStates and optimisticKnownStates for non-storage peers", () => {
|
|
233
196
|
const { peerState } = setup(); // Uses a regular peer
|
|
234
197
|
|