cojson 0.17.10 → 0.17.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +20 -0
- package/dist/coValueCore/SessionMap.d.ts +3 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +9 -4
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +16 -8
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +2 -2
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +1 -1
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +6 -2
- package/dist/coValues/group.js.map +1 -1
- package/dist/crypto/PureJSCrypto.d.ts +2 -2
- package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
- package/dist/crypto/PureJSCrypto.js +3 -0
- package/dist/crypto/PureJSCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +1 -1
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/crypto.d.ts +1 -1
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/localNode.js +1 -1
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts +17 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.d.ts +15 -0
- package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js +25 -0
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
- package/dist/sync.d.ts +8 -5
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +78 -66
- package/dist/sync.js.map +1 -1
- package/dist/tests/PureJSCrypto.test.js +15 -1
- package/dist/tests/PureJSCrypto.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +1 -1
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +2 -2
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/group.addMember.test.js +6 -11
- package/dist/tests/group.addMember.test.js.map +1 -1
- package/dist/tests/sync.known.test.d.ts +2 -0
- package/dist/tests/sync.known.test.d.ts.map +1 -0
- package/dist/tests/sync.known.test.js +78 -0
- package/dist/tests/sync.known.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +40 -1
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.sharding.test.d.ts +2 -0
- package/dist/tests/sync.sharding.test.d.ts.map +1 -0
- package/dist/tests/sync.sharding.test.js +51 -0
- package/dist/tests/sync.sharding.test.js.map +1 -0
- package/dist/tests/sync.test.js +30 -1
- package/dist/tests/sync.test.js.map +1 -1
- package/package.json +2 -2
- package/src/coValueCore/SessionMap.ts +4 -5
- package/src/coValueCore/coValueCore.ts +42 -32
- package/src/coValueCore/verifiedState.ts +1 -3
- package/src/coValues/group.ts +10 -2
- package/src/crypto/PureJSCrypto.ts +6 -2
- package/src/crypto/WasmCrypto.ts +1 -1
- package/src/crypto/crypto.ts +1 -1
- package/src/localNode.ts +1 -1
- package/src/permissions.ts +17 -1
- package/src/queue/LocalTransactionsSyncQueue.ts +32 -1
- package/src/sync.ts +103 -80
- package/src/tests/PureJSCrypto.test.ts +25 -2
- package/src/tests/WasmCrypto.test.ts +0 -2
- package/src/tests/coValueCore.test.ts +0 -4
- package/src/tests/group.addMember.test.ts +69 -63
- package/src/tests/sync.known.test.ts +109 -0
- package/src/tests/sync.load.test.ts +52 -0
- package/src/tests/sync.sharding.test.ts +76 -0
- package/src/tests/sync.test.ts +43 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
|
|
2
|
-
import { Result, err } from "neverthrow";
|
|
2
|
+
import { Result, err, ok } from "neverthrow";
|
|
3
3
|
import type { PeerState } from "../PeerState.js";
|
|
4
4
|
import type { RawCoValue } from "../coValue.js";
|
|
5
5
|
import type { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
@@ -431,43 +431,46 @@ export class CoValueCore {
|
|
|
431
431
|
tryAddTransactions(
|
|
432
432
|
sessionID: SessionID,
|
|
433
433
|
newTransactions: Transaction[],
|
|
434
|
-
givenExpectedNewHash: Hash | undefined,
|
|
435
434
|
newSignature: Signature,
|
|
436
|
-
notifyMode: "immediate" | "deferred",
|
|
437
435
|
skipVerify: boolean = false,
|
|
438
|
-
givenNewStreamingHash?: StreamingHash,
|
|
439
436
|
): Result<true, TryAddTransactionsError> {
|
|
440
|
-
|
|
441
|
-
.resolveAccountAgent(
|
|
442
|
-
accountOrAgentIDfromSessionID(sessionID),
|
|
443
|
-
"Expected to know signer of transaction",
|
|
444
|
-
)
|
|
445
|
-
.andThen((agent) => {
|
|
446
|
-
if (!this.verified) {
|
|
447
|
-
return err({
|
|
448
|
-
type: "TriedToAddTransactionsWithoutVerifiedState",
|
|
449
|
-
id: this.id,
|
|
450
|
-
} satisfies TriedToAddTransactionsWithoutVerifiedStateErrpr);
|
|
451
|
-
}
|
|
437
|
+
let result: Result<SignerID | undefined, TryAddTransactionsError>;
|
|
452
438
|
|
|
453
|
-
|
|
439
|
+
if (skipVerify) {
|
|
440
|
+
result = ok(undefined);
|
|
441
|
+
} else {
|
|
442
|
+
result = this.node
|
|
443
|
+
.resolveAccountAgent(
|
|
444
|
+
accountOrAgentIDfromSessionID(sessionID),
|
|
445
|
+
"Expected to know signer of transaction",
|
|
446
|
+
)
|
|
447
|
+
.andThen((agent) => {
|
|
448
|
+
return ok(this.crypto.getAgentSignerID(agent));
|
|
449
|
+
});
|
|
450
|
+
}
|
|
454
451
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
givenNewStreamingHash,
|
|
463
|
-
);
|
|
452
|
+
return result.andThen((signerID) => {
|
|
453
|
+
if (!this.verified) {
|
|
454
|
+
return err({
|
|
455
|
+
type: "TriedToAddTransactionsWithoutVerifiedState",
|
|
456
|
+
id: this.id,
|
|
457
|
+
} satisfies TriedToAddTransactionsWithoutVerifiedStateErrpr);
|
|
458
|
+
}
|
|
464
459
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
460
|
+
const result = this.verified.tryAddTransactions(
|
|
461
|
+
sessionID,
|
|
462
|
+
signerID,
|
|
463
|
+
newTransactions,
|
|
464
|
+
newSignature,
|
|
465
|
+
skipVerify,
|
|
466
|
+
);
|
|
468
467
|
|
|
469
|
-
|
|
470
|
-
|
|
468
|
+
if (result.isOk()) {
|
|
469
|
+
this.updateContentAndNotifyUpdate("immediate");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return result;
|
|
473
|
+
});
|
|
471
474
|
}
|
|
472
475
|
|
|
473
476
|
deferredUpdates = 0;
|
|
@@ -973,7 +976,7 @@ export type InvalidSignatureError = {
|
|
|
973
976
|
id: RawCoID;
|
|
974
977
|
newSignature: Signature;
|
|
975
978
|
sessionID: SessionID;
|
|
976
|
-
signerID: SignerID;
|
|
979
|
+
signerID: SignerID | undefined;
|
|
977
980
|
};
|
|
978
981
|
|
|
979
982
|
export type TriedToAddTransactionsWithoutVerifiedStateErrpr = {
|
|
@@ -981,8 +984,15 @@ export type TriedToAddTransactionsWithoutVerifiedStateErrpr = {
|
|
|
981
984
|
id: RawCoID;
|
|
982
985
|
};
|
|
983
986
|
|
|
987
|
+
export type TriedToAddTransactionsWithoutSignerIDError = {
|
|
988
|
+
type: "TriedToAddTransactionsWithoutSignerID";
|
|
989
|
+
id: RawCoID;
|
|
990
|
+
sessionID: SessionID;
|
|
991
|
+
};
|
|
992
|
+
|
|
984
993
|
export type TryAddTransactionsError =
|
|
985
994
|
| TriedToAddTransactionsWithoutVerifiedStateErrpr
|
|
995
|
+
| TriedToAddTransactionsWithoutSignerIDError
|
|
986
996
|
| ResolveAccountAgentError
|
|
987
997
|
| InvalidHashError
|
|
988
998
|
| InvalidSignatureError;
|
|
@@ -89,12 +89,10 @@ export class VerifiedState {
|
|
|
89
89
|
|
|
90
90
|
tryAddTransactions(
|
|
91
91
|
sessionID: SessionID,
|
|
92
|
-
signerID: SignerID,
|
|
92
|
+
signerID: SignerID | undefined,
|
|
93
93
|
newTransactions: Transaction[],
|
|
94
|
-
givenExpectedNewHash: Hash | undefined,
|
|
95
94
|
newSignature: Signature,
|
|
96
95
|
skipVerify: boolean = false,
|
|
97
|
-
givenNewStreamingHash?: StreamingHash,
|
|
98
96
|
): Result<true, TryAddTransactionsError> {
|
|
99
97
|
const result = this.sessions.addTransaction(
|
|
100
98
|
sessionID,
|
package/src/coValues/group.ts
CHANGED
|
@@ -370,16 +370,24 @@ export class RawGroup<
|
|
|
370
370
|
if (role === "writeOnly" || role === "writeOnlyInvite") {
|
|
371
371
|
const previousRole = this.get(memberKey);
|
|
372
372
|
|
|
373
|
-
|
|
373
|
+
if (
|
|
374
|
+
previousRole === "admin" &&
|
|
375
|
+
memberKey !== this.core.node.getCurrentAgent().id
|
|
376
|
+
) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
"Administrators cannot demote other administrators in a group",
|
|
379
|
+
);
|
|
380
|
+
}
|
|
374
381
|
|
|
375
382
|
if (
|
|
376
383
|
previousRole === "reader" ||
|
|
377
384
|
previousRole === "writer" ||
|
|
378
385
|
previousRole === "admin"
|
|
379
386
|
) {
|
|
380
|
-
this.rotateReadKey();
|
|
387
|
+
this.rotateReadKey(memberKey);
|
|
381
388
|
}
|
|
382
389
|
|
|
390
|
+
this.set(memberKey, role, "trusting");
|
|
383
391
|
this.internalCreateWriteOnlyKeyForMember(memberKey, agent);
|
|
384
392
|
} else {
|
|
385
393
|
const currentReadKey = this.getCurrentReadKey();
|
|
@@ -212,7 +212,7 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
|
|
|
212
212
|
createSessionLog(
|
|
213
213
|
coID: RawCoID,
|
|
214
214
|
sessionID: SessionID,
|
|
215
|
-
signerID
|
|
215
|
+
signerID?: SignerID,
|
|
216
216
|
): SessionLogImpl {
|
|
217
217
|
return new PureJSSessionLog(coID, sessionID, signerID, this);
|
|
218
218
|
}
|
|
@@ -226,7 +226,7 @@ export class PureJSSessionLog implements SessionLogImpl {
|
|
|
226
226
|
constructor(
|
|
227
227
|
private readonly coID: RawCoID,
|
|
228
228
|
private readonly sessionID: SessionID,
|
|
229
|
-
private readonly signerID: SignerID,
|
|
229
|
+
private readonly signerID: SignerID | undefined,
|
|
230
230
|
private readonly crypto: PureJSCrypto,
|
|
231
231
|
) {
|
|
232
232
|
this.streamingHash = this.crypto.emptyBlake3State();
|
|
@@ -263,6 +263,10 @@ export class PureJSSessionLog implements SessionLogImpl {
|
|
|
263
263
|
skipVerify: boolean,
|
|
264
264
|
) {
|
|
265
265
|
if (!skipVerify) {
|
|
266
|
+
if (!this.signerID) {
|
|
267
|
+
throw new Error("Tried to add transactions without signer ID");
|
|
268
|
+
}
|
|
269
|
+
|
|
266
270
|
const checkHasher = this.crypto.cloneBlake3State(this.streamingHash);
|
|
267
271
|
|
|
268
272
|
for (const tx of transactions) {
|
package/src/crypto/WasmCrypto.ts
CHANGED
|
@@ -205,7 +205,7 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
createSessionLog(coID: RawCoID, sessionID: SessionID, signerID
|
|
208
|
+
createSessionLog(coID: RawCoID, sessionID: SessionID, signerID?: SignerID) {
|
|
209
209
|
return new SessionLogAdapter(new SessionLog(coID, sessionID, signerID));
|
|
210
210
|
}
|
|
211
211
|
}
|
package/src/crypto/crypto.ts
CHANGED
package/src/localNode.ts
CHANGED
|
@@ -408,7 +408,7 @@ export class LocalNode {
|
|
|
408
408
|
coValue.loadingState === "unknown" ||
|
|
409
409
|
coValue.loadingState === "unavailable"
|
|
410
410
|
) {
|
|
411
|
-
const peers = this.syncManager.getServerPeers(skipLoadingFromPeer);
|
|
411
|
+
const peers = this.syncManager.getServerPeers(id, skipLoadingFromPeer);
|
|
412
412
|
|
|
413
413
|
if (!this.storage && peers.length === 0) {
|
|
414
414
|
return coValue;
|
package/src/permissions.ts
CHANGED
|
@@ -31,7 +31,23 @@ export type PermissionsDef =
|
|
|
31
31
|
| { type: "ownedByGroup"; group: RawCoID }
|
|
32
32
|
| { type: "unsafeAllowAll" };
|
|
33
33
|
|
|
34
|
-
export type AccountRole =
|
|
34
|
+
export type AccountRole =
|
|
35
|
+
/**
|
|
36
|
+
* Can read the group's CoValues
|
|
37
|
+
*/
|
|
38
|
+
| "reader"
|
|
39
|
+
/**
|
|
40
|
+
* Can read and write to the group's CoValues
|
|
41
|
+
*/
|
|
42
|
+
| "writer"
|
|
43
|
+
/**
|
|
44
|
+
* Can read and write to the group, and change group member roles
|
|
45
|
+
*/
|
|
46
|
+
| "admin"
|
|
47
|
+
/**
|
|
48
|
+
* Can only write to the group's CoValues and read their own changes
|
|
49
|
+
*/
|
|
50
|
+
| "writeOnly";
|
|
35
51
|
|
|
36
52
|
export type Role =
|
|
37
53
|
| AccountRole
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "../coValueContentMessage.js";
|
|
5
5
|
import { Transaction, VerifiedState } from "../coValueCore/verifiedState.js";
|
|
6
6
|
import { Signature } from "../crypto/crypto.js";
|
|
7
|
-
import { SessionID } from "../ids.js";
|
|
7
|
+
import { RawCoID, SessionID } from "../ids.js";
|
|
8
8
|
import { NewContentMessage } from "../sync.js";
|
|
9
9
|
import { LinkedList } from "./LinkedList.js";
|
|
10
10
|
|
|
@@ -73,8 +73,39 @@ export class LocalTransactionsSyncQueue {
|
|
|
73
73
|
this.queue.push(content);
|
|
74
74
|
|
|
75
75
|
this.processPendingSyncs();
|
|
76
|
+
|
|
77
|
+
for (const trackingSet of this.dirtyCoValuesTrackingSets) {
|
|
78
|
+
trackingSet.add(content.id);
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
private dirtyCoValuesTrackingSets: Set<Set<RawCoID>> = new Set();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* It starts tracking all changed CoValues. Returns a `done()` function that returns a set of coValues' ids that have been modified since the start.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const tracking = node.syncManager.trackDirtyCoValues();
|
|
90
|
+
* // Any CoValue mutation
|
|
91
|
+
* const tracked = tracking.done();
|
|
92
|
+
* console.log("CoValue mutated: " Array.from(tracked))
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
trackDirtyCoValues = () => {
|
|
96
|
+
const trackingSet = new Set<RawCoID>();
|
|
97
|
+
|
|
98
|
+
this.dirtyCoValuesTrackingSets.add(trackingSet);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
done: () => {
|
|
102
|
+
this.dirtyCoValuesTrackingSets.delete(trackingSet);
|
|
103
|
+
|
|
104
|
+
return trackingSet;
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
78
109
|
private processingSyncs = false;
|
|
79
110
|
processPendingSyncs() {
|
|
80
111
|
if (this.processingSyncs) return;
|
package/src/sync.ts
CHANGED
|
@@ -128,6 +128,11 @@ export function combinedKnownStates(
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
export type ServerPeerSelector = (
|
|
132
|
+
id: RawCoID,
|
|
133
|
+
serverPeers: PeerState[],
|
|
134
|
+
) => PeerState[];
|
|
135
|
+
|
|
131
136
|
export class SyncManager {
|
|
132
137
|
peers: { [key: PeerID]: PeerState } = {};
|
|
133
138
|
local: LocalNode;
|
|
@@ -144,6 +149,8 @@ export class SyncManager {
|
|
|
144
149
|
});
|
|
145
150
|
private transactionsSizeHistogram: Histogram;
|
|
146
151
|
|
|
152
|
+
serverPeerSelector?: ServerPeerSelector;
|
|
153
|
+
|
|
147
154
|
constructor(local: LocalNode) {
|
|
148
155
|
this.local = local;
|
|
149
156
|
this.syncState = new SyncStateManager(this);
|
|
@@ -176,10 +183,13 @@ export class SyncManager {
|
|
|
176
183
|
return Object.values(this.peers);
|
|
177
184
|
}
|
|
178
185
|
|
|
179
|
-
getServerPeers(excludePeerId?: PeerID): PeerState[] {
|
|
180
|
-
|
|
186
|
+
getServerPeers(id: RawCoID, excludePeerId?: PeerID): PeerState[] {
|
|
187
|
+
const serverPeers = this.getPeers().filter(
|
|
181
188
|
(peer) => peer.role === "server" && peer.id !== excludePeerId,
|
|
182
189
|
);
|
|
190
|
+
return this.serverPeerSelector
|
|
191
|
+
? this.serverPeerSelector(id, serverPeers)
|
|
192
|
+
: serverPeers;
|
|
183
193
|
}
|
|
184
194
|
|
|
185
195
|
handleSyncMessage(msg: SyncMessage, peer: PeerState) {
|
|
@@ -221,6 +231,23 @@ export class SyncManager {
|
|
|
221
231
|
id: RawCoID,
|
|
222
232
|
peer: PeerState,
|
|
223
233
|
seen: Set<RawCoID> = new Set(),
|
|
234
|
+
) {
|
|
235
|
+
this.sendNewContent(id, peer, seen, true);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
sendNewContentWithoutDependencies(
|
|
239
|
+
id: RawCoID,
|
|
240
|
+
peer: PeerState,
|
|
241
|
+
seen: Set<RawCoID> = new Set(),
|
|
242
|
+
) {
|
|
243
|
+
this.sendNewContent(id, peer, seen, false);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private sendNewContent(
|
|
247
|
+
id: RawCoID,
|
|
248
|
+
peer: PeerState,
|
|
249
|
+
seen: Set<RawCoID> = new Set(),
|
|
250
|
+
includeDependencies: boolean,
|
|
224
251
|
) {
|
|
225
252
|
if (seen.has(id)) {
|
|
226
253
|
return;
|
|
@@ -234,8 +261,10 @@ export class SyncManager {
|
|
|
234
261
|
return;
|
|
235
262
|
}
|
|
236
263
|
|
|
237
|
-
|
|
238
|
-
|
|
264
|
+
if (includeDependencies) {
|
|
265
|
+
for (const dependency of coValue.getDependedOnCoValues()) {
|
|
266
|
+
this.sendNewContentIncludingDependencies(dependency, peer, seen);
|
|
267
|
+
}
|
|
239
268
|
}
|
|
240
269
|
|
|
241
270
|
const newContentPieces = coValue.verified.newContentSince(
|
|
@@ -402,7 +431,7 @@ export class SyncManager {
|
|
|
402
431
|
return;
|
|
403
432
|
}
|
|
404
433
|
|
|
405
|
-
const peers = this.getServerPeers(peer.id);
|
|
434
|
+
const peers = this.getServerPeers(msg.id, peer.id);
|
|
406
435
|
|
|
407
436
|
coValue.load(peers);
|
|
408
437
|
|
|
@@ -442,7 +471,11 @@ export class SyncManager {
|
|
|
442
471
|
}
|
|
443
472
|
|
|
444
473
|
if (coValue.isAvailable()) {
|
|
445
|
-
|
|
474
|
+
if (peer.role === "server") {
|
|
475
|
+
this.sendNewContentWithoutDependencies(msg.id, peer);
|
|
476
|
+
} else {
|
|
477
|
+
this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
478
|
+
}
|
|
446
479
|
}
|
|
447
480
|
}
|
|
448
481
|
|
|
@@ -511,28 +544,31 @@ export class SyncManager {
|
|
|
511
544
|
(content) => content.newTransactions,
|
|
512
545
|
);
|
|
513
546
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
547
|
+
// If we'll be performing transaction verification, ensure all the dependencies available.
|
|
548
|
+
if (!this.skipVerify) {
|
|
549
|
+
for (const dependency of getDependedOnCoValuesFromRawData(
|
|
550
|
+
msg.id,
|
|
551
|
+
msg.header,
|
|
552
|
+
sessionIDs,
|
|
553
|
+
transactions,
|
|
554
|
+
)) {
|
|
555
|
+
const dependencyCoValue = this.local.getCoValue(dependency);
|
|
521
556
|
|
|
522
|
-
|
|
523
|
-
|
|
557
|
+
if (!dependencyCoValue.hasVerifiedContent()) {
|
|
558
|
+
coValue.markMissingDependency(dependency);
|
|
524
559
|
|
|
525
|
-
|
|
560
|
+
const peers = this.getServerPeers(dependencyCoValue.id);
|
|
526
561
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
562
|
+
// if the peer that sent the content is a client, we add it to the list of peers
|
|
563
|
+
// to also ask them for the dependency
|
|
564
|
+
if (peer?.role === "client") {
|
|
565
|
+
peers.push(peer);
|
|
566
|
+
}
|
|
532
567
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
568
|
+
dependencyCoValue.load(peers);
|
|
569
|
+
} else if (!dependencyCoValue.isAvailable()) {
|
|
570
|
+
coValue.markMissingDependency(dependency);
|
|
571
|
+
}
|
|
536
572
|
}
|
|
537
573
|
}
|
|
538
574
|
|
|
@@ -592,60 +628,61 @@ export class SyncManager {
|
|
|
592
628
|
continue;
|
|
593
629
|
}
|
|
594
630
|
|
|
595
|
-
|
|
631
|
+
// If we'll be performing transaction verification, ensure the account is available.
|
|
632
|
+
if (!this.skipVerify) {
|
|
633
|
+
const accountId = accountOrAgentIDfromSessionID(sessionID);
|
|
596
634
|
|
|
597
|
-
|
|
598
|
-
|
|
635
|
+
if (isAccountID(accountId)) {
|
|
636
|
+
const account = this.local.getCoValue(accountId);
|
|
599
637
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
638
|
+
// We can't verify the transaction without the account, so we delay the session content handling until the account is available
|
|
639
|
+
if (!account.isAvailable()) {
|
|
640
|
+
// This covers the case where we are getting a new session on an already loaded coValue
|
|
641
|
+
// where we need to load the account to get their public key
|
|
642
|
+
if (!coValue.missingDependencies.has(accountId)) {
|
|
643
|
+
const peers = this.getServerPeers(account.id);
|
|
606
644
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
645
|
+
if (peer?.role === "client") {
|
|
646
|
+
// if the peer that sent the content is a client, we add it to the list of peers
|
|
647
|
+
// to also ask them for the dependency
|
|
648
|
+
peers.push(peer);
|
|
649
|
+
}
|
|
612
650
|
|
|
613
|
-
|
|
614
|
-
|
|
651
|
+
account.load(peers);
|
|
652
|
+
}
|
|
615
653
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
654
|
+
// We need to wait for the account to be available before we can verify the transaction
|
|
655
|
+
// Currently doing this by delaying the handleNewContent for the session to when we have the account
|
|
656
|
+
//
|
|
657
|
+
// This is not the best solution, because the knownState is not updated and the ACK response will be given
|
|
658
|
+
// by excluding the session.
|
|
659
|
+
// This is good enough implementation for now because the only case for the account to be missing are out-of-order
|
|
660
|
+
// dependencies push, so the gap should be short lived.
|
|
661
|
+
//
|
|
662
|
+
// When we are going to have sharded-peers we should revisit this, and store unverified sessions that are considered as part of the
|
|
663
|
+
// knwonState, but not actively used until they can be verified.
|
|
664
|
+
void account.waitForAvailable().then(() => {
|
|
665
|
+
this.handleNewContent(
|
|
666
|
+
{
|
|
667
|
+
action: "content",
|
|
668
|
+
id: coValue.id,
|
|
669
|
+
new: {
|
|
670
|
+
[sessionID]: newContentForSession,
|
|
671
|
+
},
|
|
672
|
+
priority: msg.priority,
|
|
633
673
|
},
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
continue;
|
|
674
|
+
from,
|
|
675
|
+
);
|
|
676
|
+
});
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
640
679
|
}
|
|
641
680
|
}
|
|
642
681
|
|
|
643
682
|
const result = coValue.tryAddTransactions(
|
|
644
683
|
sessionID,
|
|
645
684
|
newTransactions,
|
|
646
|
-
undefined,
|
|
647
685
|
newContentForSession.lastSignature,
|
|
648
|
-
"immediate",
|
|
649
686
|
this.skipVerify,
|
|
650
687
|
);
|
|
651
688
|
|
|
@@ -767,26 +804,12 @@ export class SyncManager {
|
|
|
767
804
|
return this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
768
805
|
}
|
|
769
806
|
|
|
770
|
-
dirtyCoValuesTrackingSets: Set<Set<RawCoID>> = new Set();
|
|
771
|
-
trackDirtyCoValues() {
|
|
772
|
-
const trackingSet = new Set<RawCoID>();
|
|
773
|
-
|
|
774
|
-
this.dirtyCoValuesTrackingSets.add(trackingSet);
|
|
775
|
-
|
|
776
|
-
return {
|
|
777
|
-
done: () => {
|
|
778
|
-
this.dirtyCoValuesTrackingSets.delete(trackingSet);
|
|
779
|
-
|
|
780
|
-
return trackingSet;
|
|
781
|
-
},
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
|
|
785
807
|
private syncQueue = new LocalTransactionsSyncQueue((content) =>
|
|
786
808
|
this.syncContent(content),
|
|
787
809
|
);
|
|
788
810
|
syncHeader = this.syncQueue.syncHeader;
|
|
789
811
|
syncLocalTransaction = this.syncQueue.syncTransaction;
|
|
812
|
+
trackDirtyCoValues = this.syncQueue.trackDirtyCoValues;
|
|
790
813
|
|
|
791
814
|
syncContent(content: NewContentMessage) {
|
|
792
815
|
const coValue = this.local.getCoValue(content.id);
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
setCurrentTestCryptoProvider,
|
|
5
5
|
setupTestNode,
|
|
6
6
|
setupTestAccount,
|
|
7
|
+
randomAgentAndSessionID,
|
|
7
8
|
} from "./testUtils";
|
|
8
9
|
import { PureJSCrypto } from "../crypto/PureJSCrypto";
|
|
9
10
|
import { stableStringify } from "../jsonStringify";
|
|
@@ -113,9 +114,7 @@ describe("PureJSCrypto", () => {
|
|
|
113
114
|
madeAt: Date.now(),
|
|
114
115
|
},
|
|
115
116
|
],
|
|
116
|
-
"hash_z12345678",
|
|
117
117
|
"signature_z12345678",
|
|
118
|
-
"immediate",
|
|
119
118
|
true,
|
|
120
119
|
);
|
|
121
120
|
|
|
@@ -128,3 +127,27 @@ describe("PureJSCrypto", () => {
|
|
|
128
127
|
expect(map.get("count")).toEqual(0);
|
|
129
128
|
});
|
|
130
129
|
});
|
|
130
|
+
|
|
131
|
+
describe("PureJSSessionLog", () => {
|
|
132
|
+
it("fails to verify signatures without a signer ID", async () => {
|
|
133
|
+
const agentSecret = jsCrypto.newRandomAgentSecret();
|
|
134
|
+
const sessionID = jsCrypto.newRandomSessionID(
|
|
135
|
+
jsCrypto.getAgentID(agentSecret),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const sessionLog = jsCrypto.createSessionLog("co_z12345678", sessionID);
|
|
139
|
+
expect(() =>
|
|
140
|
+
sessionLog.tryAdd(
|
|
141
|
+
[
|
|
142
|
+
{
|
|
143
|
+
privacy: "trusting",
|
|
144
|
+
changes: stableStringify([{ op: "set", key: "count", value: 1 }]),
|
|
145
|
+
madeAt: Date.now(),
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
"signature_z12345678",
|
|
149
|
+
false,
|
|
150
|
+
),
|
|
151
|
+
).toThrow("Tried to add transactions without signer ID");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -76,9 +76,7 @@ test("transactions with wrong signature are rejected", () => {
|
|
|
76
76
|
const result = newEntry.tryAddTransactions(
|
|
77
77
|
node.currentSessionID,
|
|
78
78
|
[transaction],
|
|
79
|
-
undefined,
|
|
80
79
|
signature,
|
|
81
|
-
"immediate",
|
|
82
80
|
);
|
|
83
81
|
|
|
84
82
|
expect(result.isErr()).toBe(true);
|
|
@@ -301,9 +299,7 @@ test("getValidTransactions should skip private transactions with invalid JSON",
|
|
|
301
299
|
.tryAddTransactions(
|
|
302
300
|
fixtures.session,
|
|
303
301
|
[fixtures.transaction],
|
|
304
|
-
undefined,
|
|
305
302
|
fixtures.signature,
|
|
306
|
-
"immediate",
|
|
307
303
|
)
|
|
308
304
|
._unsafeUnwrap();
|
|
309
305
|
|