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.
- package/CHANGELOG.md +89 -83
- package/dist/native/PeerKnownStates.js +1 -1
- package/dist/native/PeerKnownStates.js.map +1 -1
- package/dist/native/PeerState.js +1 -1
- package/dist/native/PeerState.js.map +1 -1
- package/dist/native/PriorityBasedMessageQueue.js +1 -10
- package/dist/native/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/native/base64url.js.map +1 -1
- package/dist/native/base64url.test.js +1 -1
- package/dist/native/base64url.test.js.map +1 -1
- package/dist/native/coValue.js.map +1 -1
- package/dist/native/coValueCore.js +141 -149
- package/dist/native/coValueCore.js.map +1 -1
- package/dist/native/coValueState.js.map +1 -1
- package/dist/native/coValues/account.js +6 -6
- package/dist/native/coValues/account.js.map +1 -1
- package/dist/native/coValues/coList.js +2 -3
- package/dist/native/coValues/coList.js.map +1 -1
- package/dist/native/coValues/coMap.js +1 -1
- package/dist/native/coValues/coMap.js.map +1 -1
- package/dist/native/coValues/coStream.js +3 -5
- package/dist/native/coValues/coStream.js.map +1 -1
- package/dist/native/coValues/group.js +11 -11
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/native/coreToCoValue.js +2 -2
- package/dist/native/coreToCoValue.js.map +1 -1
- package/dist/native/crypto/PureJSCrypto.js +4 -4
- package/dist/native/crypto/PureJSCrypto.js.map +1 -1
- package/dist/native/crypto/crypto.js.map +1 -1
- package/dist/native/exports.js +12 -12
- package/dist/native/exports.js.map +1 -1
- package/dist/native/ids.js.map +1 -1
- package/dist/native/jsonStringify.js.map +1 -1
- package/dist/native/localNode.js +5 -7
- package/dist/native/localNode.js.map +1 -1
- package/dist/native/permissions.js +4 -7
- package/dist/native/permissions.js.map +1 -1
- package/dist/native/priority.js.map +1 -1
- package/dist/native/storage/FileSystem.js.map +1 -1
- package/dist/native/storage/chunksAndKnownStates.js +2 -4
- package/dist/native/storage/chunksAndKnownStates.js.map +1 -1
- package/dist/native/storage/index.js +6 -15
- package/dist/native/storage/index.js.map +1 -1
- package/dist/native/streamUtils.js.map +1 -1
- package/dist/native/sync.js +2 -4
- package/dist/native/sync.js.map +1 -1
- package/dist/native/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
- package/dist/native/typeUtils/expectGroup.js.map +1 -1
- package/dist/native/typeUtils/isAccountID.js.map +1 -1
- package/dist/native/typeUtils/isCoValue.js +1 -1
- package/dist/native/typeUtils/isCoValue.js.map +1 -1
- package/dist/web/PeerKnownStates.js +1 -1
- package/dist/web/PeerKnownStates.js.map +1 -1
- package/dist/web/PeerState.js +1 -1
- package/dist/web/PeerState.js.map +1 -1
- package/dist/web/PriorityBasedMessageQueue.js +1 -10
- package/dist/web/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/web/base64url.js.map +1 -1
- package/dist/web/base64url.test.js +1 -1
- package/dist/web/base64url.test.js.map +1 -1
- package/dist/web/coValue.js.map +1 -1
- package/dist/web/coValueCore.js +141 -149
- package/dist/web/coValueCore.js.map +1 -1
- package/dist/web/coValueState.js.map +1 -1
- package/dist/web/coValues/account.js +6 -6
- package/dist/web/coValues/account.js.map +1 -1
- package/dist/web/coValues/coList.js +2 -3
- package/dist/web/coValues/coList.js.map +1 -1
- package/dist/web/coValues/coMap.js +1 -1
- package/dist/web/coValues/coMap.js.map +1 -1
- package/dist/web/coValues/coStream.js +3 -5
- package/dist/web/coValues/coStream.js.map +1 -1
- package/dist/web/coValues/group.js +11 -11
- package/dist/web/coValues/group.js.map +1 -1
- package/dist/web/coreToCoValue.js +2 -2
- package/dist/web/coreToCoValue.js.map +1 -1
- package/dist/web/crypto/PureJSCrypto.js +4 -4
- package/dist/web/crypto/PureJSCrypto.js.map +1 -1
- package/dist/web/crypto/WasmCrypto.js +5 -5
- package/dist/web/crypto/WasmCrypto.js.map +1 -1
- package/dist/web/crypto/crypto.js.map +1 -1
- package/dist/web/exports.js +12 -12
- package/dist/web/exports.js.map +1 -1
- package/dist/web/ids.js.map +1 -1
- package/dist/web/jsonStringify.js.map +1 -1
- package/dist/web/localNode.js +5 -7
- package/dist/web/localNode.js.map +1 -1
- package/dist/web/permissions.js +4 -7
- package/dist/web/permissions.js.map +1 -1
- package/dist/web/priority.js.map +1 -1
- package/dist/web/storage/FileSystem.js.map +1 -1
- package/dist/web/storage/chunksAndKnownStates.js +2 -4
- package/dist/web/storage/chunksAndKnownStates.js.map +1 -1
- package/dist/web/storage/index.js +6 -15
- package/dist/web/storage/index.js.map +1 -1
- package/dist/web/streamUtils.js.map +1 -1
- package/dist/web/sync.js +2 -4
- package/dist/web/sync.js.map +1 -1
- package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
- package/dist/web/typeUtils/expectGroup.js.map +1 -1
- package/dist/web/typeUtils/isAccountID.js.map +1 -1
- package/dist/web/typeUtils/isCoValue.js +1 -1
- package/dist/web/typeUtils/isCoValue.js.map +1 -1
- package/package.json +4 -14
- package/src/PeerKnownStates.ts +91 -89
- package/src/PeerState.ts +72 -73
- package/src/PriorityBasedMessageQueue.ts +42 -49
- package/src/base64url.test.ts +24 -24
- package/src/base64url.ts +44 -45
- package/src/coValue.ts +45 -45
- package/src/coValueCore.ts +746 -785
- package/src/coValueState.ts +82 -72
- package/src/coValues/account.ts +143 -150
- package/src/coValues/coList.ts +520 -522
- package/src/coValues/coMap.ts +283 -285
- package/src/coValues/coStream.ts +320 -324
- package/src/coValues/group.ts +306 -305
- package/src/coreToCoValue.ts +28 -31
- package/src/crypto/PureJSCrypto.ts +188 -194
- package/src/crypto/WasmCrypto.ts +236 -254
- package/src/crypto/crypto.ts +302 -309
- package/src/exports.ts +116 -116
- package/src/ids.ts +9 -9
- package/src/jsonStringify.ts +46 -46
- package/src/jsonValue.ts +24 -10
- package/src/localNode.ts +635 -660
- package/src/media.ts +3 -3
- package/src/permissions.ts +272 -278
- package/src/priority.ts +21 -19
- package/src/storage/FileSystem.ts +91 -99
- package/src/storage/chunksAndKnownStates.ts +110 -115
- package/src/storage/index.ts +466 -497
- package/src/streamUtils.ts +60 -60
- package/src/sync.ts +593 -615
- package/src/tests/PeerKnownStates.test.ts +38 -34
- package/src/tests/PeerState.test.ts +101 -64
- package/src/tests/PriorityBasedMessageQueue.test.ts +91 -91
- package/src/tests/account.test.ts +59 -59
- package/src/tests/coList.test.ts +65 -65
- package/src/tests/coMap.test.ts +137 -137
- package/src/tests/coStream.test.ts +254 -257
- package/src/tests/coValueCore.test.ts +153 -156
- package/src/tests/crypto.test.ts +136 -144
- package/src/tests/cryptoImpl.test.ts +205 -197
- package/src/tests/group.test.ts +24 -24
- package/src/tests/permissions.test.ts +1306 -1371
- package/src/tests/priority.test.ts +65 -82
- package/src/tests/sync.test.ts +1300 -1291
- package/src/tests/testUtils.ts +52 -53
- package/src/typeUtils/accountOrAgentIDfromSessionID.ts +4 -4
- package/src/typeUtils/expectGroup.ts +9 -9
- package/src/typeUtils/isAccountID.ts +1 -1
- package/src/typeUtils/isCoValue.ts +9 -9
- package/tsconfig.json +4 -6
- package/tsconfig.native.json +9 -11
- package/tsconfig.web.json +4 -10
- package/.eslintrc.cjs +0 -25
- package/.prettierrc.js +0 -9
package/src/sync.ts
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
7
|
+
import { LocalNode } from "./localNode.js";
|
|
8
8
|
import { CoValuePriority } from "./priority.js";
|
|
9
9
|
|
|
10
10
|
export type CoValueKnownState = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
id: RawCoID;
|
|
12
|
+
header: boolean;
|
|
13
|
+
sessions: { [sessionID: SessionID]: number };
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export function emptyKnownState(id: RawCoID): CoValueKnownState {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
header: false,
|
|
20
|
+
sessions: {},
|
|
21
|
+
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export type SyncMessage =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
| LoadMessage
|
|
26
|
+
| KnownStateMessage
|
|
27
|
+
| NewContentMessage
|
|
28
|
+
| DoneMessage;
|
|
29
29
|
|
|
30
30
|
export type LoadMessage = {
|
|
31
|
-
|
|
31
|
+
action: "load";
|
|
32
32
|
} & CoValueKnownState;
|
|
33
33
|
|
|
34
34
|
export type KnownStateMessage = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
action: "known";
|
|
36
|
+
asDependencyOf?: RawCoID;
|
|
37
|
+
isCorrection?: boolean;
|
|
38
38
|
} & CoValueKnownState;
|
|
39
39
|
|
|
40
40
|
export type NewContentMessage = {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
after: number;
|
|
52
|
+
newTransactions: Transaction[];
|
|
53
|
+
lastSignature: Signature;
|
|
54
54
|
};
|
|
55
55
|
export type DoneMessage = {
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
67
|
+
SyncMessage | DisconnectedError | PingTimeoutError
|
|
68
68
|
>;
|
|
69
69
|
export type OutgoingSyncQueue = {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
push: (msg: SyncMessage) => Promise<unknown>;
|
|
71
|
+
close: () => void;
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
export interface Peer {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
85
|
-
|
|
84
|
+
stateA: CoValueKnownState,
|
|
85
|
+
stateB: CoValueKnownState,
|
|
86
86
|
): CoValueKnownState {
|
|
87
|
-
|
|
87
|
+
const sessionStates: CoValueKnownState["sessions"] = {};
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
const allSessions = new Set([
|
|
90
|
+
...Object.keys(stateA.sessions),
|
|
91
|
+
...Object.keys(stateB.sessions),
|
|
92
|
+
] as SessionID[]);
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
for (const sessionID of allSessions) {
|
|
95
|
+
const stateAValue = stateA.sessions[sessionID];
|
|
96
|
+
const stateBValue = stateB.sessions[sessionID];
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
sessionStates[sessionID] = Math.max(stateAValue || 0, stateBValue || 0);
|
|
99
|
+
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
145
|
+
if (coValueEntry?.state.type === "unknown") {
|
|
146
|
+
await coValueEntry.state.waitForPeer(peer.id);
|
|
147
|
+
}
|
|
128
148
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
}
|
|
177
|
+
if (!entry) {
|
|
178
|
+
throw new Error("Expected coValue entry on subscribe");
|
|
149
179
|
}
|
|
150
180
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
177
|
-
const entry = this.local.coValues[id];
|
|
193
|
+
const coValue = entry.state.coValue;
|
|
178
194
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
195
|
+
for (const id of coValue.getDependedOnCoValues()) {
|
|
196
|
+
await this.subscribeToIncludingDependencies(id, peer);
|
|
197
|
+
}
|
|
182
198
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
277
|
+
lastYield = performance.now();
|
|
278
|
+
}
|
|
193
279
|
}
|
|
280
|
+
};
|
|
194
281
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
458
|
-
|
|
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
|
-
|
|
462
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
495
|
-
|
|
445
|
+
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
|
|
446
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
447
|
+
}
|
|
496
448
|
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
505
|
-
|
|
452
|
+
peer.optimisticKnownStates.dispatch({
|
|
453
|
+
type: "COMBINE_WITH",
|
|
454
|
+
id: msg.id,
|
|
455
|
+
value: knownStateIn(msg),
|
|
456
|
+
});
|
|
506
457
|
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
512
|
-
|
|
482
|
+
if (entry.state.type === "unknown") {
|
|
483
|
+
const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
|
|
513
484
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
485
|
+
if (!availableOnPeer) {
|
|
486
|
+
entry.dispatch({
|
|
487
|
+
type: "not-found",
|
|
488
|
+
peerId: peer.id,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
520
491
|
|
|
521
|
-
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
522
494
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
495
|
+
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
|
|
496
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
497
|
+
}
|
|
528
498
|
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
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
|
-
|
|
656
|
-
throw new Error("Method not implemented.");
|
|
657
|
-
}
|
|
619
|
+
await this.syncCoValue(coValue);
|
|
658
620
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
697
|
+
return {
|
|
698
|
+
id: msg.id,
|
|
699
|
+
header: msg.header,
|
|
700
|
+
sessions: msg.sessions,
|
|
701
|
+
};
|
|
724
702
|
}
|