cojson 0.8.12 → 0.8.16

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.
Files changed (158) hide show
  1. package/CHANGELOG.md +89 -83
  2. package/dist/native/PeerKnownStates.js +1 -1
  3. package/dist/native/PeerKnownStates.js.map +1 -1
  4. package/dist/native/PeerState.js +1 -1
  5. package/dist/native/PeerState.js.map +1 -1
  6. package/dist/native/PriorityBasedMessageQueue.js +1 -10
  7. package/dist/native/PriorityBasedMessageQueue.js.map +1 -1
  8. package/dist/native/base64url.js.map +1 -1
  9. package/dist/native/base64url.test.js +1 -1
  10. package/dist/native/base64url.test.js.map +1 -1
  11. package/dist/native/coValue.js.map +1 -1
  12. package/dist/native/coValueCore.js +141 -149
  13. package/dist/native/coValueCore.js.map +1 -1
  14. package/dist/native/coValueState.js.map +1 -1
  15. package/dist/native/coValues/account.js +6 -6
  16. package/dist/native/coValues/account.js.map +1 -1
  17. package/dist/native/coValues/coList.js +2 -3
  18. package/dist/native/coValues/coList.js.map +1 -1
  19. package/dist/native/coValues/coMap.js +1 -1
  20. package/dist/native/coValues/coMap.js.map +1 -1
  21. package/dist/native/coValues/coStream.js +3 -5
  22. package/dist/native/coValues/coStream.js.map +1 -1
  23. package/dist/native/coValues/group.js +11 -11
  24. package/dist/native/coValues/group.js.map +1 -1
  25. package/dist/native/coreToCoValue.js +2 -2
  26. package/dist/native/coreToCoValue.js.map +1 -1
  27. package/dist/native/crypto/PureJSCrypto.js +4 -4
  28. package/dist/native/crypto/PureJSCrypto.js.map +1 -1
  29. package/dist/native/crypto/crypto.js.map +1 -1
  30. package/dist/native/exports.js +12 -12
  31. package/dist/native/exports.js.map +1 -1
  32. package/dist/native/ids.js.map +1 -1
  33. package/dist/native/jsonStringify.js.map +1 -1
  34. package/dist/native/localNode.js +5 -7
  35. package/dist/native/localNode.js.map +1 -1
  36. package/dist/native/permissions.js +4 -7
  37. package/dist/native/permissions.js.map +1 -1
  38. package/dist/native/priority.js.map +1 -1
  39. package/dist/native/storage/FileSystem.js.map +1 -1
  40. package/dist/native/storage/chunksAndKnownStates.js +2 -4
  41. package/dist/native/storage/chunksAndKnownStates.js.map +1 -1
  42. package/dist/native/storage/index.js +6 -15
  43. package/dist/native/storage/index.js.map +1 -1
  44. package/dist/native/streamUtils.js.map +1 -1
  45. package/dist/native/sync.js +2 -4
  46. package/dist/native/sync.js.map +1 -1
  47. package/dist/native/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  48. package/dist/native/typeUtils/expectGroup.js.map +1 -1
  49. package/dist/native/typeUtils/isAccountID.js.map +1 -1
  50. package/dist/native/typeUtils/isCoValue.js +1 -1
  51. package/dist/native/typeUtils/isCoValue.js.map +1 -1
  52. package/dist/web/PeerKnownStates.js +1 -1
  53. package/dist/web/PeerKnownStates.js.map +1 -1
  54. package/dist/web/PeerState.js +1 -1
  55. package/dist/web/PeerState.js.map +1 -1
  56. package/dist/web/PriorityBasedMessageQueue.js +1 -10
  57. package/dist/web/PriorityBasedMessageQueue.js.map +1 -1
  58. package/dist/web/base64url.js.map +1 -1
  59. package/dist/web/base64url.test.js +1 -1
  60. package/dist/web/base64url.test.js.map +1 -1
  61. package/dist/web/coValue.js.map +1 -1
  62. package/dist/web/coValueCore.js +141 -149
  63. package/dist/web/coValueCore.js.map +1 -1
  64. package/dist/web/coValueState.js.map +1 -1
  65. package/dist/web/coValues/account.js +6 -6
  66. package/dist/web/coValues/account.js.map +1 -1
  67. package/dist/web/coValues/coList.js +2 -3
  68. package/dist/web/coValues/coList.js.map +1 -1
  69. package/dist/web/coValues/coMap.js +1 -1
  70. package/dist/web/coValues/coMap.js.map +1 -1
  71. package/dist/web/coValues/coStream.js +3 -5
  72. package/dist/web/coValues/coStream.js.map +1 -1
  73. package/dist/web/coValues/group.js +11 -11
  74. package/dist/web/coValues/group.js.map +1 -1
  75. package/dist/web/coreToCoValue.js +2 -2
  76. package/dist/web/coreToCoValue.js.map +1 -1
  77. package/dist/web/crypto/PureJSCrypto.js +4 -4
  78. package/dist/web/crypto/PureJSCrypto.js.map +1 -1
  79. package/dist/web/crypto/WasmCrypto.js +5 -5
  80. package/dist/web/crypto/WasmCrypto.js.map +1 -1
  81. package/dist/web/crypto/crypto.js.map +1 -1
  82. package/dist/web/exports.js +12 -12
  83. package/dist/web/exports.js.map +1 -1
  84. package/dist/web/ids.js.map +1 -1
  85. package/dist/web/jsonStringify.js.map +1 -1
  86. package/dist/web/localNode.js +5 -7
  87. package/dist/web/localNode.js.map +1 -1
  88. package/dist/web/permissions.js +4 -7
  89. package/dist/web/permissions.js.map +1 -1
  90. package/dist/web/priority.js.map +1 -1
  91. package/dist/web/storage/FileSystem.js.map +1 -1
  92. package/dist/web/storage/chunksAndKnownStates.js +2 -4
  93. package/dist/web/storage/chunksAndKnownStates.js.map +1 -1
  94. package/dist/web/storage/index.js +6 -15
  95. package/dist/web/storage/index.js.map +1 -1
  96. package/dist/web/streamUtils.js.map +1 -1
  97. package/dist/web/sync.js +2 -4
  98. package/dist/web/sync.js.map +1 -1
  99. package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  100. package/dist/web/typeUtils/expectGroup.js.map +1 -1
  101. package/dist/web/typeUtils/isAccountID.js.map +1 -1
  102. package/dist/web/typeUtils/isCoValue.js +1 -1
  103. package/dist/web/typeUtils/isCoValue.js.map +1 -1
  104. package/package.json +4 -14
  105. package/src/PeerKnownStates.ts +91 -89
  106. package/src/PeerState.ts +72 -73
  107. package/src/PriorityBasedMessageQueue.ts +42 -49
  108. package/src/base64url.test.ts +24 -24
  109. package/src/base64url.ts +44 -45
  110. package/src/coValue.ts +45 -45
  111. package/src/coValueCore.ts +746 -785
  112. package/src/coValueState.ts +82 -72
  113. package/src/coValues/account.ts +143 -150
  114. package/src/coValues/coList.ts +520 -522
  115. package/src/coValues/coMap.ts +283 -285
  116. package/src/coValues/coStream.ts +320 -324
  117. package/src/coValues/group.ts +306 -305
  118. package/src/coreToCoValue.ts +28 -31
  119. package/src/crypto/PureJSCrypto.ts +188 -194
  120. package/src/crypto/WasmCrypto.ts +236 -254
  121. package/src/crypto/crypto.ts +302 -309
  122. package/src/exports.ts +116 -116
  123. package/src/ids.ts +9 -9
  124. package/src/jsonStringify.ts +46 -46
  125. package/src/jsonValue.ts +24 -10
  126. package/src/localNode.ts +635 -660
  127. package/src/media.ts +3 -3
  128. package/src/permissions.ts +272 -278
  129. package/src/priority.ts +21 -19
  130. package/src/storage/FileSystem.ts +91 -99
  131. package/src/storage/chunksAndKnownStates.ts +110 -115
  132. package/src/storage/index.ts +466 -497
  133. package/src/streamUtils.ts +60 -60
  134. package/src/sync.ts +593 -615
  135. package/src/tests/PeerKnownStates.test.ts +38 -34
  136. package/src/tests/PeerState.test.ts +101 -64
  137. package/src/tests/PriorityBasedMessageQueue.test.ts +91 -91
  138. package/src/tests/account.test.ts +59 -59
  139. package/src/tests/coList.test.ts +65 -65
  140. package/src/tests/coMap.test.ts +137 -137
  141. package/src/tests/coStream.test.ts +254 -257
  142. package/src/tests/coValueCore.test.ts +153 -156
  143. package/src/tests/crypto.test.ts +136 -144
  144. package/src/tests/cryptoImpl.test.ts +205 -197
  145. package/src/tests/group.test.ts +24 -24
  146. package/src/tests/permissions.test.ts +1306 -1371
  147. package/src/tests/priority.test.ts +65 -82
  148. package/src/tests/sync.test.ts +1300 -1291
  149. package/src/tests/testUtils.ts +52 -53
  150. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +4 -4
  151. package/src/typeUtils/expectGroup.ts +9 -9
  152. package/src/typeUtils/isAccountID.ts +1 -1
  153. package/src/typeUtils/isCoValue.ts +9 -9
  154. package/tsconfig.json +4 -6
  155. package/tsconfig.native.json +9 -11
  156. package/tsconfig.web.json +4 -10
  157. package/.eslintrc.cjs +0 -25
  158. package/.prettierrc.js +0 -9
package/src/sync.ts CHANGED
@@ -1,60 +1,60 @@
1
- import { Signature } from "./crypto/crypto.js";
1
+ import { PeerState } from "./PeerState.js";
2
2
  import { CoValueHeader, Transaction } from "./coValueCore.js";
3
3
  import { CoValueCore } from "./coValueCore.js";
4
- import { LocalNode } from "./localNode.js";
5
4
  import { CoValueState } from "./coValueState.js";
5
+ import { Signature } from "./crypto/crypto.js";
6
6
  import { RawCoID, SessionID } from "./ids.js";
7
- import { PeerState } from "./PeerState.js";
7
+ import { LocalNode } from "./localNode.js";
8
8
  import { CoValuePriority } from "./priority.js";
9
9
 
10
10
  export type CoValueKnownState = {
11
- id: RawCoID;
12
- header: boolean;
13
- sessions: { [sessionID: SessionID]: number };
11
+ id: RawCoID;
12
+ header: boolean;
13
+ sessions: { [sessionID: SessionID]: number };
14
14
  };
15
15
 
16
16
  export function emptyKnownState(id: RawCoID): CoValueKnownState {
17
- return {
18
- id,
19
- header: false,
20
- sessions: {},
21
- };
17
+ return {
18
+ id,
19
+ header: false,
20
+ sessions: {},
21
+ };
22
22
  }
23
23
 
24
24
  export type SyncMessage =
25
- | LoadMessage
26
- | KnownStateMessage
27
- | NewContentMessage
28
- | DoneMessage;
25
+ | LoadMessage
26
+ | KnownStateMessage
27
+ | NewContentMessage
28
+ | DoneMessage;
29
29
 
30
30
  export type LoadMessage = {
31
- action: "load";
31
+ action: "load";
32
32
  } & CoValueKnownState;
33
33
 
34
34
  export type KnownStateMessage = {
35
- action: "known";
36
- asDependencyOf?: RawCoID;
37
- isCorrection?: boolean;
35
+ action: "known";
36
+ asDependencyOf?: RawCoID;
37
+ isCorrection?: boolean;
38
38
  } & CoValueKnownState;
39
39
 
40
40
  export type NewContentMessage = {
41
- action: "content";
42
- id: RawCoID;
43
- header?: CoValueHeader;
44
- priority: CoValuePriority;
45
- new: {
46
- [sessionID: SessionID]: SessionNewContent;
47
- };
41
+ action: "content";
42
+ id: RawCoID;
43
+ header?: CoValueHeader;
44
+ priority: CoValuePriority;
45
+ new: {
46
+ [sessionID: SessionID]: SessionNewContent;
47
+ };
48
48
  };
49
49
 
50
50
  export type SessionNewContent = {
51
- after: number;
52
- newTransactions: Transaction[];
53
- lastSignature: Signature;
51
+ after: number;
52
+ newTransactions: Transaction[];
53
+ lastSignature: Signature;
54
54
  };
55
55
  export type DoneMessage = {
56
- action: "done";
57
- id: RawCoID;
56
+ action: "done";
57
+ id: RawCoID;
58
58
  };
59
59
 
60
60
  export type PeerID = string;
@@ -64,661 +64,639 @@ export type DisconnectedError = "Disconnected";
64
64
  export type PingTimeoutError = "PingTimeout";
65
65
 
66
66
  export type IncomingSyncStream = AsyncIterable<
67
- SyncMessage | DisconnectedError | PingTimeoutError
67
+ SyncMessage | DisconnectedError | PingTimeoutError
68
68
  >;
69
69
  export type OutgoingSyncQueue = {
70
- push: (msg: SyncMessage) => Promise<unknown>;
71
- close: () => void;
70
+ push: (msg: SyncMessage) => Promise<unknown>;
71
+ close: () => void;
72
72
  };
73
73
 
74
74
  export interface Peer {
75
- id: PeerID;
76
- incoming: IncomingSyncStream;
77
- outgoing: OutgoingSyncQueue;
78
- role: "peer" | "server" | "client" | "storage";
79
- priority?: number;
80
- crashOnClose: boolean;
75
+ id: PeerID;
76
+ incoming: IncomingSyncStream;
77
+ outgoing: OutgoingSyncQueue;
78
+ role: "peer" | "server" | "client" | "storage";
79
+ priority?: number;
80
+ crashOnClose: boolean;
81
81
  }
82
82
 
83
83
  export function combinedKnownStates(
84
- stateA: CoValueKnownState,
85
- stateB: CoValueKnownState,
84
+ stateA: CoValueKnownState,
85
+ stateB: CoValueKnownState,
86
86
  ): CoValueKnownState {
87
- const sessionStates: CoValueKnownState["sessions"] = {};
87
+ const sessionStates: CoValueKnownState["sessions"] = {};
88
88
 
89
- const allSessions = new Set([
90
- ...Object.keys(stateA.sessions),
91
- ...Object.keys(stateB.sessions),
92
- ] as SessionID[]);
89
+ const allSessions = new Set([
90
+ ...Object.keys(stateA.sessions),
91
+ ...Object.keys(stateB.sessions),
92
+ ] as SessionID[]);
93
93
 
94
- for (const sessionID of allSessions) {
95
- const stateAValue = stateA.sessions[sessionID];
96
- const stateBValue = stateB.sessions[sessionID];
94
+ for (const sessionID of allSessions) {
95
+ const stateAValue = stateA.sessions[sessionID];
96
+ const stateBValue = stateB.sessions[sessionID];
97
97
 
98
- sessionStates[sessionID] = Math.max(stateAValue || 0, stateBValue || 0);
99
- }
98
+ sessionStates[sessionID] = Math.max(stateAValue || 0, stateBValue || 0);
99
+ }
100
100
 
101
- return {
102
- id: stateA.id,
103
- header: stateA.header || stateB.header,
104
- sessions: sessionStates,
105
- };
101
+ return {
102
+ id: stateA.id,
103
+ header: stateA.header || stateB.header,
104
+ sessions: sessionStates,
105
+ };
106
106
  }
107
107
 
108
108
  export class SyncManager {
109
- peers: { [key: PeerID]: PeerState } = {};
110
- local: LocalNode;
111
- requestedSyncs: {
112
- [id: RawCoID]:
113
- | { done: Promise<void>; nRequestsThisTick: number }
114
- | undefined;
115
- } = {};
116
-
117
- constructor(local: LocalNode) {
118
- this.local = local;
119
- }
120
-
121
- peersInPriorityOrder(): PeerState[] {
122
- return Object.values(this.peers).sort((a, b) => {
123
- const aPriority = a.priority || 0;
124
- const bPriority = b.priority || 0;
109
+ peers: { [key: PeerID]: PeerState } = {};
110
+ local: LocalNode;
111
+ requestedSyncs: {
112
+ [id: RawCoID]:
113
+ | { done: Promise<void>; nRequestsThisTick: number }
114
+ | undefined;
115
+ } = {};
116
+
117
+ constructor(local: LocalNode) {
118
+ this.local = local;
119
+ }
120
+
121
+ peersInPriorityOrder(): PeerState[] {
122
+ return Object.values(this.peers).sort((a, b) => {
123
+ const aPriority = a.priority || 0;
124
+ const bPriority = b.priority || 0;
125
+
126
+ return bPriority - aPriority;
127
+ });
128
+ }
129
+
130
+ async loadFromPeers(id: RawCoID, forPeer?: PeerID) {
131
+ const eligiblePeers = this.peersInPriorityOrder().filter(
132
+ (peer) => peer.id !== forPeer && peer.isServerOrStoragePeer(),
133
+ );
134
+
135
+ const coValueEntry = this.local.coValues[id];
136
+
137
+ for (const peer of eligiblePeers) {
138
+ await peer.pushOutgoingMessage({
139
+ action: "load",
140
+ id: id,
141
+ header: false,
142
+ sessions: {},
143
+ });
125
144
 
126
- return bPriority - aPriority;
127
- });
145
+ if (coValueEntry?.state.type === "unknown") {
146
+ await coValueEntry.state.waitForPeer(peer.id);
147
+ }
128
148
  }
129
-
130
- async loadFromPeers(id: RawCoID, forPeer?: PeerID) {
131
- const eligiblePeers = this.peersInPriorityOrder().filter(
132
- (peer) => peer.id !== forPeer && peer.isServerOrStoragePeer(),
149
+ }
150
+
151
+ async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
152
+ // TODO: validate
153
+ switch (msg.action) {
154
+ case "load":
155
+ return await this.handleLoad(msg, peer);
156
+ case "known":
157
+ if (msg.isCorrection) {
158
+ return await this.handleCorrection(msg, peer);
159
+ } else {
160
+ return await this.handleKnownState(msg, peer);
161
+ }
162
+ case "content":
163
+ // await new Promise<void>((resolve) => setTimeout(resolve, 0));
164
+ return await this.handleNewContent(msg, peer);
165
+ case "done":
166
+ return await this.handleUnsubscribe(msg);
167
+ default:
168
+ throw new Error(
169
+ `Unknown message type ${(msg as { action: "string" }).action}`,
133
170
  );
171
+ }
172
+ }
134
173
 
135
- const coValueEntry = this.local.coValues[id];
136
-
137
- for (const peer of eligiblePeers) {
138
- await peer.pushOutgoingMessage({
139
- action: "load",
140
- id: id,
141
- header: false,
142
- sessions: {},
143
- });
174
+ async subscribeToIncludingDependencies(id: RawCoID, peer: PeerState) {
175
+ const entry = this.local.coValues[id];
144
176
 
145
- if (coValueEntry?.state.type === "unknown") {
146
- await coValueEntry.state.waitForPeer(peer.id);
147
- }
148
- }
177
+ if (!entry) {
178
+ throw new Error("Expected coValue entry on subscribe");
149
179
  }
150
180
 
151
- async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
152
- // TODO: validate
153
- switch (msg.action) {
154
- case "load":
155
- return await this.handleLoad(msg, peer);
156
- case "known":
157
- if (msg.isCorrection) {
158
- return await this.handleCorrection(msg, peer);
159
- } else {
160
- return await this.handleKnownState(msg, peer);
161
- }
162
- case "content":
163
- // await new Promise<void>((resolve) => setTimeout(resolve, 0));
164
- return await this.handleNewContent(msg, peer);
165
- case "done":
166
- return await this.handleUnsubscribe(msg);
167
- default:
168
- throw new Error(
169
- `Unknown message type ${
170
- (msg as { action: "string" }).action
171
- }`,
172
- );
173
- }
181
+ if (entry.state.type === "unknown") {
182
+ this.trySendToPeer(peer, {
183
+ action: "load",
184
+ id,
185
+ header: false,
186
+ sessions: {},
187
+ }).catch((e: unknown) => {
188
+ console.error("Error sending load", e);
189
+ });
190
+ return;
174
191
  }
175
192
 
176
- async subscribeToIncludingDependencies(id: RawCoID, peer: PeerState) {
177
- const entry = this.local.coValues[id];
193
+ const coValue = entry.state.coValue;
178
194
 
179
- if (!entry) {
180
- throw new Error("Expected coValue entry on subscribe");
181
- }
195
+ for (const id of coValue.getDependedOnCoValues()) {
196
+ await this.subscribeToIncludingDependencies(id, peer);
197
+ }
182
198
 
183
- if (entry.state.type === "unknown") {
184
- this.trySendToPeer(peer, {
185
- action: "load",
186
- id,
187
- header: false,
188
- sessions: {},
189
- }).catch((e: unknown) => {
190
- console.error("Error sending load", e);
199
+ if (!peer.toldKnownState.has(id)) {
200
+ peer.toldKnownState.add(id);
201
+ this.trySendToPeer(peer, {
202
+ action: "load",
203
+ ...coValue.knownState(),
204
+ }).catch((e: unknown) => {
205
+ console.error("Error sending load", e);
206
+ });
207
+ }
208
+ }
209
+
210
+ async tellUntoldKnownStateIncludingDependencies(
211
+ id: RawCoID,
212
+ peer: PeerState,
213
+ asDependencyOf?: RawCoID,
214
+ ) {
215
+ const coValue = this.local.expectCoValueLoaded(id);
216
+
217
+ await Promise.all(
218
+ coValue
219
+ .getDependedOnCoValues()
220
+ .map((dependentCoID) =>
221
+ this.tellUntoldKnownStateIncludingDependencies(
222
+ dependentCoID,
223
+ peer,
224
+ asDependencyOf || id,
225
+ ),
226
+ ),
227
+ );
228
+
229
+ if (!peer.toldKnownState.has(id)) {
230
+ this.trySendToPeer(peer, {
231
+ action: "known",
232
+ asDependencyOf,
233
+ ...coValue.knownState(),
234
+ }).catch((e: unknown) => {
235
+ console.error("Error sending known state", e);
236
+ });
237
+
238
+ peer.toldKnownState.add(id);
239
+ }
240
+ }
241
+
242
+ async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
243
+ const coValue = this.local.expectCoValueLoaded(id);
244
+
245
+ await Promise.all(
246
+ coValue
247
+ .getDependedOnCoValues()
248
+ .map((id) => this.sendNewContentIncludingDependencies(id, peer)),
249
+ );
250
+
251
+ const newContentPieces = coValue.newContentSince(
252
+ peer.optimisticKnownStates.get(id),
253
+ );
254
+
255
+ if (newContentPieces) {
256
+ const optimisticKnownStateBefore =
257
+ peer.optimisticKnownStates.get(id) || emptyKnownState(id);
258
+
259
+ const sendPieces = async () => {
260
+ let lastYield = performance.now();
261
+ for (const [_i, piece] of newContentPieces.entries()) {
262
+ // console.log(
263
+ // `${id} -> ${peer.id}: Sending content piece ${i + 1}/${
264
+ // newContentPieces.length
265
+ // } header: ${!!piece.header}`,
266
+ // // Object.values(piece.new).map((s) => s.newTransactions)
267
+ // );
268
+
269
+ this.trySendToPeer(peer, piece).catch((e: unknown) => {
270
+ console.error("Error sending content piece", e);
271
+ });
272
+
273
+ if (performance.now() - lastYield > 10) {
274
+ await new Promise<void>((resolve) => {
275
+ setTimeout(resolve, 0);
191
276
  });
192
- return;
277
+ lastYield = performance.now();
278
+ }
193
279
  }
280
+ };
194
281
 
195
- const coValue = entry.state.coValue;
196
-
197
- for (const id of coValue.getDependedOnCoValues()) {
198
- await this.subscribeToIncludingDependencies(id, peer);
199
- }
282
+ sendPieces().catch((e) => {
283
+ console.error("Error sending new content piece, retrying", e);
284
+ peer.optimisticKnownStates.dispatch({
285
+ type: "SET",
286
+ id,
287
+ value: optimisticKnownStateBefore ?? emptyKnownState(id),
288
+ });
289
+ return this.sendNewContentIncludingDependencies(id, peer);
290
+ });
200
291
 
201
- if (!peer.toldKnownState.has(id)) {
202
- peer.toldKnownState.add(id);
203
- this.trySendToPeer(peer, {
204
- action: "load",
205
- ...coValue.knownState(),
206
- }).catch((e: unknown) => {
207
- console.error("Error sending load", e);
208
- });
209
- }
292
+ peer.optimisticKnownStates.dispatch({
293
+ type: "COMBINE_WITH",
294
+ id,
295
+ value: coValue.knownState(),
296
+ });
210
297
  }
211
-
212
- async tellUntoldKnownStateIncludingDependencies(
213
- id: RawCoID,
214
- peer: PeerState,
215
- asDependencyOf?: RawCoID,
216
- ) {
217
- const coValue = this.local.expectCoValueLoaded(id);
218
-
219
- await Promise.all(
220
- coValue
221
- .getDependedOnCoValues()
222
- .map((dependentCoID) =>
223
- this.tellUntoldKnownStateIncludingDependencies(
224
- dependentCoID,
225
- peer,
226
- asDependencyOf || id,
227
- ),
228
- ),
229
- );
230
-
231
- if (!peer.toldKnownState.has(id)) {
232
- this.trySendToPeer(peer, {
233
- action: "known",
234
- asDependencyOf,
235
- ...coValue.knownState(),
236
- }).catch((e: unknown) => {
237
- console.error("Error sending known state", e);
238
- });
239
-
240
- peer.toldKnownState.add(id);
298
+ }
299
+
300
+ addPeer(peer: Peer) {
301
+ const peerState = new PeerState(peer);
302
+ this.peers[peer.id] = peerState;
303
+
304
+ if (peerState.isServerOrStoragePeer()) {
305
+ const initialSync = async () => {
306
+ for (const id of Object.keys(this.local.coValues) as RawCoID[]) {
307
+ // console.log("subscribing to after peer added", id, peer.id)
308
+ await this.subscribeToIncludingDependencies(id, peerState);
309
+
310
+ peerState.optimisticKnownStates.dispatch({
311
+ type: "SET_AS_EMPTY",
312
+ id,
313
+ });
241
314
  }
315
+ };
316
+ void initialSync();
242
317
  }
243
318
 
244
- async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
245
- const coValue = this.local.expectCoValueLoaded(id);
246
-
247
- await Promise.all(
248
- coValue
249
- .getDependedOnCoValues()
250
- .map((id) =>
251
- this.sendNewContentIncludingDependencies(id, peer),
252
- ),
253
- );
254
-
255
- const newContentPieces = coValue.newContentSince(
256
- peer.optimisticKnownStates.get(id),
257
- );
258
-
259
- if (newContentPieces) {
260
- const optimisticKnownStateBefore =
261
- peer.optimisticKnownStates.get(id) || emptyKnownState(id);
262
-
263
- const sendPieces = async () => {
264
- let lastYield = performance.now();
265
- for (const [_i, piece] of newContentPieces.entries()) {
266
- // console.log(
267
- // `${id} -> ${peer.id}: Sending content piece ${i + 1}/${
268
- // newContentPieces.length
269
- // } header: ${!!piece.header}`,
270
- // // Object.values(piece.new).map((s) => s.newTransactions)
271
- // );
272
-
273
- this.trySendToPeer(peer, piece).catch((e: unknown) => {
274
- console.error("Error sending content piece", e);
275
- });
276
-
277
- if (performance.now() - lastYield > 10) {
278
- await new Promise<void>((resolve) => {
279
- setTimeout(resolve, 0);
280
- });
281
- lastYield = performance.now();
282
- }
283
- }
284
- };
285
-
286
- sendPieces().catch((e) => {
287
- console.error("Error sending new content piece, retrying", e);
288
- peer.optimisticKnownStates.dispatch({
289
- type: "SET",
290
- id,
291
- value: optimisticKnownStateBefore ?? emptyKnownState(id),
292
- });
293
- return this.sendNewContentIncludingDependencies(id, peer);
294
- });
295
-
296
- peer.optimisticKnownStates.dispatch({
297
- type: "COMBINE_WITH",
298
- id,
299
- value: coValue.knownState(),
300
- });
319
+ const processMessages = async () => {
320
+ for await (const msg of peerState.incoming) {
321
+ if (msg === "Disconnected") {
322
+ return;
301
323
  }
302
- }
303
-
304
- addPeer(peer: Peer) {
305
- const peerState = new PeerState(peer);
306
- this.peers[peer.id] = peerState;
307
-
308
- if (peerState.isServerOrStoragePeer()) {
309
- const initialSync = async () => {
310
- for (const id of Object.keys(
311
- this.local.coValues,
312
- ) as RawCoID[]) {
313
- // console.log("subscribing to after peer added", id, peer.id)
314
- await this.subscribeToIncludingDependencies(id, peerState);
315
-
316
- peerState.optimisticKnownStates.dispatch({
317
- type: "SET_AS_EMPTY",
318
- id,
319
- });
320
- }
321
- };
322
- void initialSync();
324
+ if (msg === "PingTimeout") {
325
+ console.error("Ping timeout from peer", peer.id);
326
+ return;
323
327
  }
324
-
325
- const processMessages = async () => {
326
- for await (const msg of peerState.incoming) {
327
- if (msg === "Disconnected") {
328
- return;
329
- }
330
- if (msg === "PingTimeout") {
331
- console.error("Ping timeout from peer", peer.id);
332
- return;
333
- }
334
- try {
335
- await this.handleSyncMessage(msg, peerState);
336
- } catch (e) {
337
- throw new Error(
338
- `Error reading from peer ${
339
- peer.id
340
- }, handling msg\n\n${JSON.stringify(msg, (k, v) =>
341
- k === "changes" || k === "encryptedChanges"
342
- ? v.slice(0, 20) + "..."
343
- : v,
344
- )}`,
345
- { cause: e },
346
- );
347
- }
348
- }
349
- };
350
-
351
- processMessages()
352
- .then(() => {
353
- if (peer.crashOnClose) {
354
- console.error("Unexepcted close from peer", peer.id);
355
- this.local.crashed = new Error(
356
- "Unexpected close from peer",
357
- );
358
- throw new Error("Unexpected close from peer");
359
- }
360
- })
361
- .catch((e) => {
362
- console.error(
363
- "Error processing messages from peer",
364
- peer.id,
365
- e,
366
- );
367
- if (peer.crashOnClose) {
368
- this.local.crashed = e;
369
- throw new Error(e);
370
- }
371
- })
372
- .finally(() => {
373
- peer.outgoing.close();
374
- delete this.peers[peer.id];
375
- });
376
- }
377
-
378
- trySendToPeer(peer: PeerState, msg: SyncMessage) {
379
- return peer.pushOutgoingMessage(msg);
380
- }
381
-
382
- async handleLoad(msg: LoadMessage, peer: PeerState) {
383
- peer.optimisticKnownStates.dispatch({
384
- type: "SET",
385
- id: msg.id,
386
- value: knownStateIn(msg),
387
- });
388
- let entry = this.local.coValues[msg.id];
389
-
390
- if (!entry) {
391
- // console.log(`Loading ${msg.id} from all peers except ${peer.id}`);
392
-
393
- // special case: we should be able to solve this much more neatly
394
- // with an explicit state machine in the future
395
- const eligiblePeers = this.peersInPriorityOrder().filter(
396
- (other) => other.id !== peer.id && other.isServerOrStoragePeer(),
397
- );
398
- if (eligiblePeers.length === 0) {
399
- if (msg.header || Object.keys(msg.sessions).length > 0) {
400
- this.local.coValues[msg.id] = CoValueState.Unknown(
401
- new Set([peer.id]),
402
- );
403
- this.trySendToPeer(peer, {
404
- action: "known",
405
- id: msg.id,
406
- header: false,
407
- sessions: {},
408
- }).catch((e) => {
409
- console.error("Error sending known state", e);
410
- });
411
- }
412
- return;
413
- } else {
414
- this.local
415
- .loadCoValueCore(msg.id, {
416
- dontLoadFrom: peer.id,
417
- dontWaitFor: peer.id,
418
- })
419
- .catch((e) => {
420
- console.error("Error loading coValue in handleLoad", e);
421
- });
422
- }
423
-
424
- entry = this.local.coValues[msg.id]!;
328
+ try {
329
+ await this.handleSyncMessage(msg, peerState);
330
+ } catch (e) {
331
+ throw new Error(
332
+ `Error reading from peer ${
333
+ peer.id
334
+ }, handling msg\n\n${JSON.stringify(msg, (k, v) =>
335
+ k === "changes" || k === "encryptedChanges"
336
+ ? v.slice(0, 20) + "..."
337
+ : v,
338
+ )}`,
339
+ { cause: e },
340
+ );
425
341
  }
342
+ }
343
+ };
426
344
 
427
- if (entry.state.type === "unknown") {
428
- // console.debug(
429
- // "Waiting for loaded",
430
- // msg.id,
431
- // "after message from",
432
- // peer.id,
433
- // );
434
- const loaded = await entry.state.ready;
435
-
436
- if (loaded === "unavailable") {
437
- peer.optimisticKnownStates.dispatch({
438
- type: "SET",
439
- id: msg.id,
440
- value: knownStateIn(msg),
441
- });
442
- peer.toldKnownState.add(msg.id);
443
-
444
- this.trySendToPeer(peer, {
445
- action: "known",
446
- id: msg.id,
447
- header: false,
448
- sessions: {},
449
- }).catch((e) => {
450
- console.error("Error sending known state back", e);
451
- });
452
-
453
- return;
454
- }
345
+ processMessages()
346
+ .then(() => {
347
+ if (peer.crashOnClose) {
348
+ console.error("Unexepcted close from peer", peer.id);
349
+ this.local.crashed = new Error("Unexpected close from peer");
350
+ throw new Error("Unexpected close from peer");
455
351
  }
456
-
457
- await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
458
- await this.sendNewContentIncludingDependencies(msg.id, peer);
352
+ })
353
+ .catch((e) => {
354
+ console.error("Error processing messages from peer", peer.id, e);
355
+ if (peer.crashOnClose) {
356
+ this.local.crashed = e;
357
+ throw new Error(e);
358
+ }
359
+ })
360
+ .finally(() => {
361
+ peer.outgoing.close();
362
+ delete this.peers[peer.id];
363
+ });
364
+ }
365
+
366
+ trySendToPeer(peer: PeerState, msg: SyncMessage) {
367
+ return peer.pushOutgoingMessage(msg);
368
+ }
369
+
370
+ async handleLoad(msg: LoadMessage, peer: PeerState) {
371
+ peer.optimisticKnownStates.dispatch({
372
+ type: "SET",
373
+ id: msg.id,
374
+ value: knownStateIn(msg),
375
+ });
376
+ let entry = this.local.coValues[msg.id];
377
+
378
+ if (!entry) {
379
+ // console.log(`Loading ${msg.id} from all peers except ${peer.id}`);
380
+
381
+ // special case: we should be able to solve this much more neatly
382
+ // with an explicit state machine in the future
383
+ const eligiblePeers = this.peersInPriorityOrder().filter(
384
+ (other) => other.id !== peer.id && other.isServerOrStoragePeer(),
385
+ );
386
+ if (eligiblePeers.length === 0) {
387
+ if (msg.header || Object.keys(msg.sessions).length > 0) {
388
+ this.local.coValues[msg.id] = CoValueState.Unknown(
389
+ new Set([peer.id]),
390
+ );
391
+ this.trySendToPeer(peer, {
392
+ action: "known",
393
+ id: msg.id,
394
+ header: false,
395
+ sessions: {},
396
+ }).catch((e) => {
397
+ console.error("Error sending known state", e);
398
+ });
399
+ }
400
+ return;
401
+ } else {
402
+ this.local
403
+ .loadCoValueCore(msg.id, {
404
+ dontLoadFrom: peer.id,
405
+ dontWaitFor: peer.id,
406
+ })
407
+ .catch((e) => {
408
+ console.error("Error loading coValue in handleLoad", e);
409
+ });
410
+ }
411
+
412
+ entry = this.local.coValues[msg.id]!;
459
413
  }
460
414
 
461
- async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
462
- let entry = this.local.coValues[msg.id];
415
+ if (entry.state.type === "unknown") {
416
+ // console.debug(
417
+ // "Waiting for loaded",
418
+ // msg.id,
419
+ // "after message from",
420
+ // peer.id,
421
+ // );
422
+ const loaded = await entry.state.ready;
463
423
 
424
+ if (loaded === "unavailable") {
464
425
  peer.optimisticKnownStates.dispatch({
465
- type: "COMBINE_WITH",
466
- id: msg.id,
467
- value: knownStateIn(msg),
426
+ type: "SET",
427
+ id: msg.id,
428
+ value: knownStateIn(msg),
429
+ });
430
+ peer.toldKnownState.add(msg.id);
431
+
432
+ this.trySendToPeer(peer, {
433
+ action: "known",
434
+ id: msg.id,
435
+ header: false,
436
+ sessions: {},
437
+ }).catch((e) => {
438
+ console.error("Error sending known state back", e);
468
439
  });
469
440
 
470
- if (!entry) {
471
- if (msg.asDependencyOf) {
472
- if (this.local.coValues[msg.asDependencyOf]) {
473
- this.local
474
- .loadCoValueCore(msg.id, { dontLoadFrom: peer.id })
475
- .catch((e) => {
476
- console.error(
477
- `Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
478
- e,
479
- );
480
- });
481
- entry = this.local.coValues[msg.id]!; // must exist after loadCoValueCore
482
- } else {
483
- throw new Error(
484
- "Expected coValue dependency entry to be created, missing subscribe?",
485
- );
486
- }
487
- } else {
488
- throw new Error(
489
- `Expected coValue entry for ${msg.id} to be created on known state, missing subscribe?`,
490
- );
491
- }
492
- }
441
+ return;
442
+ }
443
+ }
493
444
 
494
- if (entry.state.type === "unknown") {
495
- const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
445
+ await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
446
+ await this.sendNewContentIncludingDependencies(msg.id, peer);
447
+ }
496
448
 
497
- if (!availableOnPeer) {
498
- entry.dispatch({
499
- type: "not-found",
500
- peerId: peer.id,
501
- });
502
- }
449
+ async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
450
+ let entry = this.local.coValues[msg.id];
503
451
 
504
- return;
505
- }
452
+ peer.optimisticKnownStates.dispatch({
453
+ type: "COMBINE_WITH",
454
+ id: msg.id,
455
+ value: knownStateIn(msg),
456
+ });
506
457
 
507
- await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
508
- await this.sendNewContentIncludingDependencies(msg.id, peer);
458
+ if (!entry) {
459
+ if (msg.asDependencyOf) {
460
+ if (this.local.coValues[msg.asDependencyOf]) {
461
+ this.local
462
+ .loadCoValueCore(msg.id, { dontLoadFrom: peer.id })
463
+ .catch((e) => {
464
+ console.error(
465
+ `Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
466
+ e,
467
+ );
468
+ });
469
+ entry = this.local.coValues[msg.id]!; // must exist after loadCoValueCore
470
+ } else {
471
+ throw new Error(
472
+ "Expected coValue dependency entry to be created, missing subscribe?",
473
+ );
474
+ }
475
+ } else {
476
+ throw new Error(
477
+ `Expected coValue entry for ${msg.id} to be created on known state, missing subscribe?`,
478
+ );
479
+ }
509
480
  }
510
481
 
511
- async handleNewContent(msg: NewContentMessage, peer: PeerState) {
512
- const entry = this.local.coValues[msg.id];
482
+ if (entry.state.type === "unknown") {
483
+ const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
513
484
 
514
- if (!entry) {
515
- console.error(
516
- `Expected coValue entry for ${msg.id} to be created on new content, missing subscribe?`,
517
- );
518
- return;
519
- }
485
+ if (!availableOnPeer) {
486
+ entry.dispatch({
487
+ type: "not-found",
488
+ peerId: peer.id,
489
+ });
490
+ }
520
491
 
521
- let coValue: CoValueCore;
492
+ return;
493
+ }
522
494
 
523
- if (entry.state.type === "unknown") {
524
- if (!msg.header) {
525
- console.error("Expected header to be sent in first message");
526
- return;
527
- }
495
+ await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
496
+ await this.sendNewContentIncludingDependencies(msg.id, peer);
497
+ }
528
498
 
529
- peer.optimisticKnownStates.dispatch({
530
- type: "UPDATE_HEADER",
531
- id: msg.id,
532
- header: true,
533
- });
499
+ async handleNewContent(msg: NewContentMessage, peer: PeerState) {
500
+ const entry = this.local.coValues[msg.id];
534
501
 
535
- coValue = new CoValueCore(msg.header, this.local);
536
-
537
- entry.dispatch({
538
- type: "found",
539
- coValue,
540
- peerId: peer.id,
541
- });
542
- } else {
543
- coValue = entry.state.coValue;
544
- }
502
+ if (!entry) {
503
+ console.error(
504
+ `Expected coValue entry for ${msg.id} to be created on new content, missing subscribe?`,
505
+ );
506
+ return;
507
+ }
545
508
 
546
- let invalidStateAssumed = false;
547
-
548
- for (const [sessionID, newContentForSession] of Object.entries(
549
- msg.new,
550
- ) as [SessionID, SessionNewContent][]) {
551
- const ourKnownTxIdx =
552
- coValue.sessionLogs.get(sessionID)?.transactions.length;
553
- const theirFirstNewTxIdx = newContentForSession.after;
554
-
555
- if ((ourKnownTxIdx || 0) < theirFirstNewTxIdx) {
556
- invalidStateAssumed = true;
557
- continue;
558
- }
559
-
560
- const alreadyKnownOffset = ourKnownTxIdx
561
- ? ourKnownTxIdx - theirFirstNewTxIdx
562
- : 0;
563
-
564
- const newTransactions =
565
- newContentForSession.newTransactions.slice(alreadyKnownOffset);
566
-
567
- if (newTransactions.length === 0) {
568
- continue;
569
- }
570
-
571
- const before = performance.now();
572
- // eslint-disable-next-line neverthrow/must-use-result
573
- const result = coValue.tryAddTransactions(
574
- sessionID,
575
- newTransactions,
576
- undefined,
577
- newContentForSession.lastSignature,
578
- );
579
- const after = performance.now();
580
- if (after - before > 80) {
581
- const totalTxLength = newTransactions
582
- .map((t) =>
583
- t.privacy === "private"
584
- ? t.encryptedChanges.length
585
- : t.changes.length,
586
- )
587
- .reduce((a, b) => a + b, 0);
588
- console.log(
589
- `Adding incoming transactions took ${(
590
- after - before
591
- ).toFixed(2)}ms for ${totalTxLength} bytes = bandwidth: ${(
592
- (1000 * totalTxLength) /
593
- (after - before) /
594
- (1024 * 1024)
595
- ).toFixed(2)} MB/s`,
596
- );
597
- }
598
-
599
- // const theirTotalnTxs = Object.values(
600
- // peer.optimisticKnownStates[msg.id]?.sessions || {},
601
- // ).reduce((sum, nTxs) => sum + nTxs, 0);
602
- // const ourTotalnTxs = [...coValue.sessionLogs.values()].reduce(
603
- // (sum, session) => sum + session.transactions.length,
604
- // 0,
605
- // );
606
-
607
- if (result.isErr()) {
608
- console.error(
609
- "Failed to add transactions from",
610
- peer.id,
611
- result.error,
612
- msg.id,
613
- newTransactions.length + " new transactions",
614
- "after: " + newContentForSession.after,
615
- "our last known tx idx initially: " + ourKnownTxIdx,
616
- "our last known tx idx now: " +
617
- coValue.sessionLogs.get(sessionID)?.transactions.length,
618
- );
619
- continue;
620
- }
621
-
622
- peer.optimisticKnownStates.dispatch({
623
- type: "UPDATE_SESSION_COUNTER",
624
- id: msg.id,
625
- sessionId: sessionID,
626
- value:
627
- newContentForSession.after +
628
- newContentForSession.newTransactions.length,
629
- });
630
- }
509
+ let coValue: CoValueCore;
631
510
 
632
- await this.syncCoValue(coValue);
511
+ if (entry.state.type === "unknown") {
512
+ if (!msg.header) {
513
+ console.error("Expected header to be sent in first message");
514
+ return;
515
+ }
633
516
 
634
- if (invalidStateAssumed) {
635
- this.trySendToPeer(peer, {
636
- action: "known",
637
- isCorrection: true,
638
- ...coValue.knownState(),
639
- }).catch((e) => {
640
- console.error("Error sending known state correction", e);
641
- });
642
- }
517
+ peer.optimisticKnownStates.dispatch({
518
+ type: "UPDATE_HEADER",
519
+ id: msg.id,
520
+ header: true,
521
+ });
522
+
523
+ coValue = new CoValueCore(msg.header, this.local);
524
+
525
+ entry.dispatch({
526
+ type: "found",
527
+ coValue,
528
+ peerId: peer.id,
529
+ });
530
+ } else {
531
+ coValue = entry.state.coValue;
643
532
  }
644
533
 
645
- async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
646
- peer.optimisticKnownStates.dispatch({
647
- type: "SET",
648
- id: msg.id,
649
- value: knownStateIn(msg),
650
- });
534
+ let invalidStateAssumed = false;
535
+
536
+ for (const [sessionID, newContentForSession] of Object.entries(msg.new) as [
537
+ SessionID,
538
+ SessionNewContent,
539
+ ][]) {
540
+ const ourKnownTxIdx =
541
+ coValue.sessionLogs.get(sessionID)?.transactions.length;
542
+ const theirFirstNewTxIdx = newContentForSession.after;
543
+
544
+ if ((ourKnownTxIdx || 0) < theirFirstNewTxIdx) {
545
+ invalidStateAssumed = true;
546
+ continue;
547
+ }
548
+
549
+ const alreadyKnownOffset = ourKnownTxIdx
550
+ ? ourKnownTxIdx - theirFirstNewTxIdx
551
+ : 0;
552
+
553
+ const newTransactions =
554
+ newContentForSession.newTransactions.slice(alreadyKnownOffset);
555
+
556
+ if (newTransactions.length === 0) {
557
+ continue;
558
+ }
559
+
560
+ const before = performance.now();
561
+ // eslint-disable-next-line neverthrow/must-use-result
562
+ const result = coValue.tryAddTransactions(
563
+ sessionID,
564
+ newTransactions,
565
+ undefined,
566
+ newContentForSession.lastSignature,
567
+ );
568
+ const after = performance.now();
569
+ if (after - before > 80) {
570
+ const totalTxLength = newTransactions
571
+ .map((t) =>
572
+ t.privacy === "private"
573
+ ? t.encryptedChanges.length
574
+ : t.changes.length,
575
+ )
576
+ .reduce((a, b) => a + b, 0);
577
+ console.log(
578
+ `Adding incoming transactions took ${(after - before).toFixed(
579
+ 2,
580
+ )}ms for ${totalTxLength} bytes = bandwidth: ${(
581
+ (1000 * totalTxLength) / (after - before) / (1024 * 1024)
582
+ ).toFixed(2)} MB/s`,
583
+ );
584
+ }
585
+
586
+ // const theirTotalnTxs = Object.values(
587
+ // peer.optimisticKnownStates[msg.id]?.sessions || {},
588
+ // ).reduce((sum, nTxs) => sum + nTxs, 0);
589
+ // const ourTotalnTxs = [...coValue.sessionLogs.values()].reduce(
590
+ // (sum, session) => sum + session.transactions.length,
591
+ // 0,
592
+ // );
593
+
594
+ if (result.isErr()) {
595
+ console.error(
596
+ "Failed to add transactions from",
597
+ peer.id,
598
+ result.error,
599
+ msg.id,
600
+ newTransactions.length + " new transactions",
601
+ "after: " + newContentForSession.after,
602
+ "our last known tx idx initially: " + ourKnownTxIdx,
603
+ "our last known tx idx now: " +
604
+ coValue.sessionLogs.get(sessionID)?.transactions.length,
605
+ );
606
+ continue;
607
+ }
651
608
 
652
- return this.sendNewContentIncludingDependencies(msg.id, peer);
609
+ peer.optimisticKnownStates.dispatch({
610
+ type: "UPDATE_SESSION_COUNTER",
611
+ id: msg.id,
612
+ sessionId: sessionID,
613
+ value:
614
+ newContentForSession.after +
615
+ newContentForSession.newTransactions.length,
616
+ });
653
617
  }
654
618
 
655
- handleUnsubscribe(_msg: DoneMessage) {
656
- throw new Error("Method not implemented.");
657
- }
619
+ await this.syncCoValue(coValue);
658
620
 
659
- async syncCoValue(coValue: CoValueCore) {
660
- if (this.requestedSyncs[coValue.id]) {
661
- this.requestedSyncs[coValue.id]!.nRequestsThisTick++;
662
- return this.requestedSyncs[coValue.id]!.done;
663
- } else {
664
- const done = new Promise<void>((resolve) => {
665
- queueMicrotask(async () => {
666
- delete this.requestedSyncs[coValue.id];
667
- // if (entry.nRequestsThisTick >= 2) {
668
- // console.log("Syncing", coValue.id, "for", entry.nRequestsThisTick, "requests");
669
- // }
670
- await this.actuallySyncCoValue(coValue);
671
- resolve();
672
- });
673
- });
674
- const entry = {
675
- done,
676
- nRequestsThisTick: 1,
677
- };
678
- this.requestedSyncs[coValue.id] = entry;
679
- return done;
680
- }
621
+ if (invalidStateAssumed) {
622
+ this.trySendToPeer(peer, {
623
+ action: "known",
624
+ isCorrection: true,
625
+ ...coValue.knownState(),
626
+ }).catch((e) => {
627
+ console.error("Error sending known state correction", e);
628
+ });
681
629
  }
682
-
683
- async actuallySyncCoValue(coValue: CoValueCore) {
684
- // let blockingSince = performance.now();
685
- for (const peer of this.peersInPriorityOrder()) {
686
- // if (performance.now() - blockingSince > 5) {
687
- // await new Promise<void>((resolve) => {
688
- // setTimeout(resolve, 0);
689
- // });
690
- // blockingSince = performance.now();
691
- // }
692
- if (peer.optimisticKnownStates.has(coValue.id)) {
693
- await this.tellUntoldKnownStateIncludingDependencies(
694
- coValue.id,
695
- peer,
696
- );
697
- await this.sendNewContentIncludingDependencies(
698
- coValue.id,
699
- peer,
700
- );
701
- } else if (peer.isServerOrStoragePeer()) {
702
- await this.subscribeToIncludingDependencies(coValue.id, peer);
703
- await this.sendNewContentIncludingDependencies(
704
- coValue.id,
705
- peer,
706
- );
707
- }
708
- }
630
+ }
631
+
632
+ async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
633
+ peer.optimisticKnownStates.dispatch({
634
+ type: "SET",
635
+ id: msg.id,
636
+ value: knownStateIn(msg),
637
+ });
638
+
639
+ return this.sendNewContentIncludingDependencies(msg.id, peer);
640
+ }
641
+
642
+ handleUnsubscribe(_msg: DoneMessage) {
643
+ throw new Error("Method not implemented.");
644
+ }
645
+
646
+ async syncCoValue(coValue: CoValueCore) {
647
+ if (this.requestedSyncs[coValue.id]) {
648
+ this.requestedSyncs[coValue.id]!.nRequestsThisTick++;
649
+ return this.requestedSyncs[coValue.id]!.done;
650
+ } else {
651
+ const done = new Promise<void>((resolve) => {
652
+ queueMicrotask(async () => {
653
+ delete this.requestedSyncs[coValue.id];
654
+ // if (entry.nRequestsThisTick >= 2) {
655
+ // console.log("Syncing", coValue.id, "for", entry.nRequestsThisTick, "requests");
656
+ // }
657
+ await this.actuallySyncCoValue(coValue);
658
+ resolve();
659
+ });
660
+ });
661
+ const entry = {
662
+ done,
663
+ nRequestsThisTick: 1,
664
+ };
665
+ this.requestedSyncs[coValue.id] = entry;
666
+ return done;
667
+ }
668
+ }
669
+
670
+ async actuallySyncCoValue(coValue: CoValueCore) {
671
+ // let blockingSince = performance.now();
672
+ for (const peer of this.peersInPriorityOrder()) {
673
+ // if (performance.now() - blockingSince > 5) {
674
+ // await new Promise<void>((resolve) => {
675
+ // setTimeout(resolve, 0);
676
+ // });
677
+ // blockingSince = performance.now();
678
+ // }
679
+ if (peer.optimisticKnownStates.has(coValue.id)) {
680
+ await this.tellUntoldKnownStateIncludingDependencies(coValue.id, peer);
681
+ await this.sendNewContentIncludingDependencies(coValue.id, peer);
682
+ } else if (peer.isServerOrStoragePeer()) {
683
+ await this.subscribeToIncludingDependencies(coValue.id, peer);
684
+ await this.sendNewContentIncludingDependencies(coValue.id, peer);
685
+ }
709
686
  }
687
+ }
710
688
 
711
- gracefulShutdown() {
712
- for (const peer of Object.values(this.peers)) {
713
- peer.gracefulShutdown();
714
- }
689
+ gracefulShutdown() {
690
+ for (const peer of Object.values(this.peers)) {
691
+ peer.gracefulShutdown();
715
692
  }
693
+ }
716
694
  }
717
695
 
718
696
  function knownStateIn(msg: LoadMessage | KnownStateMessage) {
719
- return {
720
- id: msg.id,
721
- header: msg.header,
722
- sessions: msg.sessions,
723
- };
697
+ return {
698
+ id: msg.id,
699
+ header: msg.header,
700
+ sessions: msg.sessions,
701
+ };
724
702
  }