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