cojson 0.17.12 → 0.17.14
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 +16 -0
- package/dist/coValueCore/SessionMap.d.ts +4 -3
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +15 -4
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +2 -2
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +33 -32
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/utils.d.ts.map +1 -1
- package/dist/coValueCore/utils.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +8 -2
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +7 -4
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/account.d.ts +6 -2
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +12 -0
- package/dist/coValues/account.js.map +1 -1
- package/dist/crypto/PureJSCrypto.d.ts +4 -3
- package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
- package/dist/crypto/PureJSCrypto.js +34 -2
- package/dist/crypto/PureJSCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +4 -3
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +10 -4
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/crypto.d.ts +4 -3
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/sync.d.ts +14 -5
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +44 -13
- package/dist/sync.js.map +1 -1
- package/dist/tests/PureJSCrypto.test.js +43 -0
- package/dist/tests/PureJSCrypto.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +55 -0
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/account.test.js +40 -0
- package/dist/tests/account.test.js.map +1 -1
- package/dist/tests/coList.test.js +13 -0
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coMap.test.js +14 -0
- package/dist/tests/coMap.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +13 -0
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coStream.test.js +25 -0
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +28 -2
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.js +1 -1
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/sync.sharding.test.js +104 -1
- package/dist/tests/sync.sharding.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +31 -1
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +1 -1
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/sync.test.js +2 -2
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +26 -0
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +3 -1
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/coValueCore/SessionMap.ts +24 -1
- package/src/coValueCore/coValueCore.ts +46 -41
- package/src/coValueCore/utils.ts +0 -1
- package/src/coValueCore/verifiedState.ts +14 -3
- package/src/coValues/account.ts +27 -3
- package/src/crypto/PureJSCrypto.ts +49 -1
- package/src/crypto/WasmCrypto.ts +15 -1
- package/src/crypto/crypto.ts +7 -1
- package/src/sync.ts +53 -19
- package/src/tests/PureJSCrypto.test.ts +66 -0
- package/src/tests/WasmCrypto.test.ts +88 -0
- package/src/tests/account.test.ts +56 -0
- package/src/tests/coList.test.ts +19 -0
- package/src/tests/coMap.test.ts +21 -0
- package/src/tests/coPlainText.test.ts +18 -0
- package/src/tests/coStream.test.ts +36 -0
- package/src/tests/coValueCore.test.ts +49 -0
- package/src/tests/coreWasm.test.ts +1 -0
- package/src/tests/sync.sharding.test.ts +156 -6
- package/src/tests/sync.storage.test.ts +43 -1
- package/src/tests/sync.storageAsync.test.ts +1 -1
- package/src/tests/sync.test.ts +6 -2
- package/src/tests/sync.upload.test.ts +35 -0
- package/src/tests/testUtils.ts +3 -1
|
@@ -8,19 +8,16 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
CryptoProvider,
|
|
10
10
|
Encrypted,
|
|
11
|
-
Hash,
|
|
12
11
|
KeyID,
|
|
13
12
|
KeySecret,
|
|
14
13
|
Signature,
|
|
15
14
|
SignerID,
|
|
16
|
-
StreamingHash,
|
|
17
15
|
} from "../crypto/crypto.js";
|
|
18
16
|
import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
19
17
|
import { Stringified } from "../jsonStringify.js";
|
|
20
18
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
21
19
|
import { PermissionsDef as RulesetDef } from "../permissions.js";
|
|
22
20
|
import { CoValueKnownState, NewContentMessage } from "../sync.js";
|
|
23
|
-
import { InvalidHashError, InvalidSignatureError } from "./coValueCore.js";
|
|
24
21
|
import { TryAddTransactionsError } from "./coValueCore.js";
|
|
25
22
|
import { SessionLog, SessionMap } from "./SessionMap.js";
|
|
26
23
|
import { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
@@ -41,12 +38,14 @@ export type PrivateTransaction = {
|
|
|
41
38
|
madeAt: number;
|
|
42
39
|
keyUsed: KeyID;
|
|
43
40
|
encryptedChanges: Encrypted<JsonValue[], { in: RawCoID; tx: TransactionID }>;
|
|
41
|
+
meta?: Encrypted<JsonObject, { in: RawCoID; tx: TransactionID }>;
|
|
44
42
|
};
|
|
45
43
|
|
|
46
44
|
export type TrustingTransaction = {
|
|
47
45
|
privacy: "trusting";
|
|
48
46
|
madeAt: number;
|
|
49
47
|
changes: Stringified<JsonValue[]>;
|
|
48
|
+
meta?: Stringified<JsonObject>;
|
|
50
49
|
};
|
|
51
50
|
|
|
52
51
|
export type Transaction = PrivateTransaction | TrustingTransaction;
|
|
@@ -114,11 +113,13 @@ export class VerifiedState {
|
|
|
114
113
|
sessionID: SessionID,
|
|
115
114
|
signerAgent: ControlledAccountOrAgent,
|
|
116
115
|
changes: JsonValue[],
|
|
116
|
+
meta: JsonObject | undefined,
|
|
117
117
|
) {
|
|
118
118
|
const result = this.sessions.makeNewTrustingTransaction(
|
|
119
119
|
sessionID,
|
|
120
120
|
signerAgent,
|
|
121
121
|
changes,
|
|
122
|
+
meta,
|
|
122
123
|
);
|
|
123
124
|
|
|
124
125
|
this._cachedNewContentSinceEmpty = undefined;
|
|
@@ -133,6 +134,7 @@ export class VerifiedState {
|
|
|
133
134
|
changes: JsonValue[],
|
|
134
135
|
keyID: KeyID,
|
|
135
136
|
keySecret: KeySecret,
|
|
137
|
+
meta: JsonObject | undefined,
|
|
136
138
|
) {
|
|
137
139
|
const result = this.sessions.makeNewPrivateTransaction(
|
|
138
140
|
sessionID,
|
|
@@ -140,6 +142,7 @@ export class VerifiedState {
|
|
|
140
142
|
changes,
|
|
141
143
|
keyID,
|
|
142
144
|
keySecret,
|
|
145
|
+
meta,
|
|
143
146
|
);
|
|
144
147
|
|
|
145
148
|
this._cachedNewContentSinceEmpty = undefined;
|
|
@@ -353,6 +356,14 @@ export class VerifiedState {
|
|
|
353
356
|
): JsonValue[] | undefined {
|
|
354
357
|
return this.sessions.decryptTransaction(sessionID, txIndex, keySecret);
|
|
355
358
|
}
|
|
359
|
+
|
|
360
|
+
decryptTransactionMeta(
|
|
361
|
+
sessionID: SessionID,
|
|
362
|
+
txIndex: number,
|
|
363
|
+
keySecret: KeySecret,
|
|
364
|
+
): JsonObject | undefined {
|
|
365
|
+
return this.sessions.decryptTransactionMeta(sessionID, txIndex, keySecret);
|
|
366
|
+
}
|
|
356
367
|
}
|
|
357
368
|
|
|
358
369
|
function getNextKnownSignatureIdx(
|
package/src/coValues/account.ts
CHANGED
|
@@ -19,9 +19,9 @@ import { AgentID } from "../ids.js";
|
|
|
19
19
|
import { JsonObject } from "../jsonValue.js";
|
|
20
20
|
import { LocalNode } from "../localNode.js";
|
|
21
21
|
import { logger } from "../logger.js";
|
|
22
|
-
import type { AccountRole } from "../permissions.js";
|
|
22
|
+
import type { AccountRole, Role } from "../permissions.js";
|
|
23
23
|
import { RawCoMap } from "./coMap.js";
|
|
24
|
-
import { InviteSecret, RawGroup } from "./group.js";
|
|
24
|
+
import { Everyone, InviteSecret, RawGroup } from "./group.js";
|
|
25
25
|
|
|
26
26
|
export function accountHeaderForInitialAgentSecret(
|
|
27
27
|
agentSecret: AgentSecret,
|
|
@@ -71,9 +71,33 @@ export class RawAccount<
|
|
|
71
71
|
return agents[0]!;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
createInvite(_: AccountRole): InviteSecret {
|
|
74
|
+
override createInvite(_: AccountRole): InviteSecret {
|
|
75
75
|
throw new Error("Cannot create invite from an account");
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
override addMember(
|
|
79
|
+
account: RawAccount | ControlledAccountOrAgent | Everyone,
|
|
80
|
+
role: Role,
|
|
81
|
+
) {
|
|
82
|
+
throw new Error("Cannot add a member to an account");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override removeMember(
|
|
86
|
+
account: RawAccount | ControlledAccountOrAgent | Everyone,
|
|
87
|
+
) {
|
|
88
|
+
throw new Error("Cannot remove a member from an account");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override extend(
|
|
92
|
+
parent: RawGroup,
|
|
93
|
+
role: "reader" | "writer" | "admin" | "inherit" = "inherit",
|
|
94
|
+
) {
|
|
95
|
+
throw new Error("Cannot extend an account");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
override revokeExtend(parent: RawGroup) {
|
|
99
|
+
throw new Error("Cannot unextend an account");
|
|
100
|
+
}
|
|
77
101
|
}
|
|
78
102
|
|
|
79
103
|
export interface ControlledAccountOrAgent {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "../coValueCore/verifiedState.js";
|
|
11
11
|
import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
12
12
|
import { Stringified, stableStringify } from "../jsonStringify.js";
|
|
13
|
-
import { JsonValue } from "../jsonValue.js";
|
|
13
|
+
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
14
14
|
import { logger } from "../logger.js";
|
|
15
15
|
import {
|
|
16
16
|
CryptoProvider,
|
|
@@ -328,16 +328,26 @@ export class PureJSSessionLog implements SessionLogImpl {
|
|
|
328
328
|
keyID: KeyID,
|
|
329
329
|
keySecret: KeySecret,
|
|
330
330
|
madeAt: number,
|
|
331
|
+
meta: JsonObject | undefined,
|
|
331
332
|
): { signature: Signature; transaction: PrivateTransaction } {
|
|
332
333
|
const encryptedChanges = this.crypto.encrypt(changes, keySecret, {
|
|
333
334
|
in: this.coID,
|
|
334
335
|
tx: { sessionID: this.sessionID, txIndex: this.transactions.length },
|
|
335
336
|
});
|
|
337
|
+
|
|
338
|
+
const encryptedMeta = meta
|
|
339
|
+
? this.crypto.encrypt(meta, keySecret, {
|
|
340
|
+
in: this.coID,
|
|
341
|
+
tx: { sessionID: this.sessionID, txIndex: this.transactions.length },
|
|
342
|
+
})
|
|
343
|
+
: undefined;
|
|
344
|
+
|
|
336
345
|
const tx = {
|
|
337
346
|
encryptedChanges: encryptedChanges,
|
|
338
347
|
madeAt: madeAt,
|
|
339
348
|
privacy: "private",
|
|
340
349
|
keyUsed: keyID,
|
|
350
|
+
meta: encryptedMeta,
|
|
341
351
|
} satisfies Transaction;
|
|
342
352
|
const signature = this.internalAddNewTransaction(
|
|
343
353
|
stableStringify(tx),
|
|
@@ -353,11 +363,13 @@ export class PureJSSessionLog implements SessionLogImpl {
|
|
|
353
363
|
signerAgent: ControlledAccountOrAgent,
|
|
354
364
|
changes: JsonValue[],
|
|
355
365
|
madeAt: number,
|
|
366
|
+
meta: JsonObject | undefined,
|
|
356
367
|
): { signature: Signature; transaction: TrustingTransaction } {
|
|
357
368
|
const tx = {
|
|
358
369
|
changes: stableStringify(changes),
|
|
359
370
|
madeAt: madeAt,
|
|
360
371
|
privacy: "trusting",
|
|
372
|
+
meta: meta ? stableStringify(meta) : undefined,
|
|
361
373
|
} satisfies Transaction;
|
|
362
374
|
const signature = this.internalAddNewTransaction(
|
|
363
375
|
stableStringify(tx),
|
|
@@ -400,6 +412,42 @@ export class PureJSSessionLog implements SessionLogImpl {
|
|
|
400
412
|
}
|
|
401
413
|
}
|
|
402
414
|
|
|
415
|
+
decryptNextTransactionMetaJson(
|
|
416
|
+
txIndex: number,
|
|
417
|
+
keySecret: KeySecret,
|
|
418
|
+
): string | undefined {
|
|
419
|
+
const txJson = this.transactions[txIndex];
|
|
420
|
+
if (!txJson) {
|
|
421
|
+
throw new Error("Transaction not found");
|
|
422
|
+
}
|
|
423
|
+
const tx = JSON.parse(txJson) as Transaction;
|
|
424
|
+
|
|
425
|
+
if (!tx.meta) {
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (tx.privacy === "private") {
|
|
430
|
+
const nOnceMaterial = {
|
|
431
|
+
in: this.coID,
|
|
432
|
+
tx: { sessionID: this.sessionID, txIndex: txIndex },
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const nOnce = this.crypto.generateJsonNonce(nOnceMaterial);
|
|
436
|
+
|
|
437
|
+
const ciphertext = base64URLtoBytes(
|
|
438
|
+
tx.meta.substring("encrypted_U".length),
|
|
439
|
+
);
|
|
440
|
+
const keySecretBytes = base58.decode(
|
|
441
|
+
keySecret.substring("keySecret_z".length),
|
|
442
|
+
);
|
|
443
|
+
const plaintext = xsalsa20(keySecretBytes, nOnce, ciphertext);
|
|
444
|
+
|
|
445
|
+
return textDecoder.decode(plaintext);
|
|
446
|
+
} else {
|
|
447
|
+
return tx.meta;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
403
451
|
free(): void {
|
|
404
452
|
// no-op
|
|
405
453
|
}
|
package/src/crypto/WasmCrypto.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
|
|
20
20
|
import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
21
21
|
import { Stringified, stableStringify } from "../jsonStringify.js";
|
|
22
|
-
import { JsonValue } from "../jsonValue.js";
|
|
22
|
+
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
23
23
|
import { logger } from "../logger.js";
|
|
24
24
|
import { PureJSCrypto } from "./PureJSCrypto.js";
|
|
25
25
|
import {
|
|
@@ -231,6 +231,7 @@ class SessionLogAdapter {
|
|
|
231
231
|
keyID: KeyID,
|
|
232
232
|
keySecret: KeySecret,
|
|
233
233
|
madeAt: number,
|
|
234
|
+
meta: JsonObject | undefined,
|
|
234
235
|
): { signature: Signature; transaction: PrivateTransaction } {
|
|
235
236
|
const output = this.sessionLog.addNewPrivateTransaction(
|
|
236
237
|
stableStringify(changes),
|
|
@@ -238,6 +239,7 @@ class SessionLogAdapter {
|
|
|
238
239
|
keySecret,
|
|
239
240
|
keyID,
|
|
240
241
|
madeAt,
|
|
242
|
+
meta ? stableStringify(meta) : undefined,
|
|
241
243
|
);
|
|
242
244
|
const parsedOutput = JSON.parse(output);
|
|
243
245
|
const transaction: PrivateTransaction = {
|
|
@@ -245,6 +247,7 @@ class SessionLogAdapter {
|
|
|
245
247
|
madeAt,
|
|
246
248
|
encryptedChanges: parsedOutput.encrypted_changes,
|
|
247
249
|
keyUsed: keyID,
|
|
250
|
+
meta: parsedOutput.meta,
|
|
248
251
|
};
|
|
249
252
|
return { signature: parsedOutput.signature, transaction };
|
|
250
253
|
}
|
|
@@ -253,17 +256,21 @@ class SessionLogAdapter {
|
|
|
253
256
|
signerAgent: ControlledAccountOrAgent,
|
|
254
257
|
changes: JsonValue[],
|
|
255
258
|
madeAt: number,
|
|
259
|
+
meta: JsonObject | undefined,
|
|
256
260
|
): { signature: Signature; transaction: TrustingTransaction } {
|
|
257
261
|
const stringifiedChanges = stableStringify(changes);
|
|
262
|
+
const stringifiedMeta = meta ? stableStringify(meta) : undefined;
|
|
258
263
|
const output = this.sessionLog.addNewTrustingTransaction(
|
|
259
264
|
stringifiedChanges,
|
|
260
265
|
signerAgent.currentSignerSecret(),
|
|
261
266
|
madeAt,
|
|
267
|
+
stringifiedMeta,
|
|
262
268
|
);
|
|
263
269
|
const transaction: TrustingTransaction = {
|
|
264
270
|
privacy: "trusting",
|
|
265
271
|
madeAt,
|
|
266
272
|
changes: stringifiedChanges,
|
|
273
|
+
meta: stringifiedMeta,
|
|
267
274
|
};
|
|
268
275
|
return { signature: output as Signature, transaction };
|
|
269
276
|
}
|
|
@@ -279,6 +286,13 @@ class SessionLogAdapter {
|
|
|
279
286
|
return output;
|
|
280
287
|
}
|
|
281
288
|
|
|
289
|
+
decryptNextTransactionMetaJson(
|
|
290
|
+
txIndex: number,
|
|
291
|
+
keySecret: KeySecret,
|
|
292
|
+
): string | undefined {
|
|
293
|
+
return this.sessionLog.decryptNextTransactionMetaJson(txIndex, keySecret);
|
|
294
|
+
}
|
|
295
|
+
|
|
282
296
|
free() {
|
|
283
297
|
this.sessionLog.free();
|
|
284
298
|
}
|
package/src/crypto/crypto.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ControlledAccountOrAgent, RawAccountID } from "../coValues/account.js";
|
|
|
3
3
|
import { AgentID, RawCoID, TransactionID } from "../ids.js";
|
|
4
4
|
import { SessionID } from "../ids.js";
|
|
5
5
|
import { Stringified, parseJSON, stableStringify } from "../jsonStringify.js";
|
|
6
|
-
import { JsonValue } from "../jsonValue.js";
|
|
6
|
+
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
7
7
|
import { logger } from "../logger.js";
|
|
8
8
|
import {
|
|
9
9
|
PrivateTransaction,
|
|
@@ -366,15 +366,21 @@ export interface SessionLogImpl {
|
|
|
366
366
|
keyID: KeyID,
|
|
367
367
|
keySecret: KeySecret,
|
|
368
368
|
madeAt: number,
|
|
369
|
+
meta: JsonObject | undefined,
|
|
369
370
|
): { signature: Signature; transaction: PrivateTransaction };
|
|
370
371
|
addNewTrustingTransaction(
|
|
371
372
|
signerAgent: ControlledAccountOrAgent,
|
|
372
373
|
changes: JsonValue[],
|
|
373
374
|
madeAt: number,
|
|
375
|
+
meta: JsonObject | undefined,
|
|
374
376
|
): { signature: Signature; transaction: TrustingTransaction };
|
|
375
377
|
decryptNextTransactionChangesJson(
|
|
376
378
|
tx_index: number,
|
|
377
379
|
key_secret: KeySecret,
|
|
378
380
|
): string;
|
|
379
381
|
free(): void;
|
|
382
|
+
decryptNextTransactionMetaJson(
|
|
383
|
+
tx_index: number,
|
|
384
|
+
key_secret: KeySecret,
|
|
385
|
+
): string | undefined;
|
|
380
386
|
}
|
package/src/sync.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { md5 } from "@noble/hashes/legacy";
|
|
1
2
|
import { Histogram, ValueType, metrics } from "@opentelemetry/api";
|
|
2
3
|
import { PeerState } from "./PeerState.js";
|
|
3
4
|
import { SyncStateManager } from "./SyncStateManager.js";
|
|
@@ -8,11 +9,7 @@ import {
|
|
|
8
9
|
} from "./coValueContentMessage.js";
|
|
9
10
|
import { CoValueCore } from "./coValueCore/coValueCore.js";
|
|
10
11
|
import { getDependedOnCoValuesFromRawData } from "./coValueCore/utils.js";
|
|
11
|
-
import {
|
|
12
|
-
CoValueHeader,
|
|
13
|
-
Transaction,
|
|
14
|
-
VerifiedState,
|
|
15
|
-
} from "./coValueCore/verifiedState.js";
|
|
12
|
+
import { CoValueHeader, Transaction } from "./coValueCore/verifiedState.js";
|
|
16
13
|
import { Signature } from "./crypto/crypto.js";
|
|
17
14
|
import { RawCoID, SessionID, isRawCoID } from "./ids.js";
|
|
18
15
|
import { LocalNode } from "./localNode.js";
|
|
@@ -170,21 +167,16 @@ export class SyncManager {
|
|
|
170
167
|
this.skipVerify = true;
|
|
171
168
|
}
|
|
172
169
|
|
|
173
|
-
|
|
174
|
-
return
|
|
175
|
-
const aPriority = a.priority || 0;
|
|
176
|
-
const bPriority = b.priority || 0;
|
|
177
|
-
|
|
178
|
-
return bPriority - aPriority;
|
|
179
|
-
});
|
|
170
|
+
getPeers(id: RawCoID): PeerState[] {
|
|
171
|
+
return this.getServerPeers(id).concat(this.getClientPeers());
|
|
180
172
|
}
|
|
181
173
|
|
|
182
|
-
|
|
183
|
-
return Object.values(this.peers);
|
|
174
|
+
getClientPeers(): PeerState[] {
|
|
175
|
+
return Object.values(this.peers).filter((peer) => peer.role === "client");
|
|
184
176
|
}
|
|
185
177
|
|
|
186
178
|
getServerPeers(id: RawCoID, excludePeerId?: PeerID): PeerState[] {
|
|
187
|
-
const serverPeers = this.
|
|
179
|
+
const serverPeers = Object.values(this.peers).filter(
|
|
188
180
|
(peer) => peer.role === "server" && peer.id !== excludePeerId,
|
|
189
181
|
);
|
|
190
182
|
return this.serverPeerSelector
|
|
@@ -760,7 +752,7 @@ export class SyncManager {
|
|
|
760
752
|
this.storeContent(contentToStore);
|
|
761
753
|
}
|
|
762
754
|
|
|
763
|
-
for (const peer of this.
|
|
755
|
+
for (const peer of this.getPeers(coValue.id)) {
|
|
764
756
|
/**
|
|
765
757
|
* We sync the content against the source peer if it is a client or server peers
|
|
766
758
|
* to upload any content that is available on the current node and not on the source peer.
|
|
@@ -818,7 +810,7 @@ export class SyncManager {
|
|
|
818
810
|
|
|
819
811
|
const contentKnownState = knownStateFromContent(content);
|
|
820
812
|
|
|
821
|
-
for (const peer of this.
|
|
813
|
+
for (const peer of this.getPeers(coValue.id)) {
|
|
822
814
|
if (peer.closed) continue;
|
|
823
815
|
if (coValue.isErroredInPeer(peer.id)) continue;
|
|
824
816
|
|
|
@@ -837,7 +829,7 @@ export class SyncManager {
|
|
|
837
829
|
peer.trackToldKnownState(coValue.id);
|
|
838
830
|
}
|
|
839
831
|
|
|
840
|
-
for (const peer of this.getPeers()) {
|
|
832
|
+
for (const peer of this.getPeers(coValue.id)) {
|
|
841
833
|
this.syncState.triggerUpdate(peer.id, coValue.id);
|
|
842
834
|
}
|
|
843
835
|
}
|
|
@@ -918,7 +910,7 @@ export class SyncManager {
|
|
|
918
910
|
}
|
|
919
911
|
|
|
920
912
|
waitForSync(id: RawCoID, timeout = 60_000) {
|
|
921
|
-
const peers = this.getPeers();
|
|
913
|
+
const peers = this.getPeers(id);
|
|
922
914
|
|
|
923
915
|
return Promise.all(
|
|
924
916
|
peers
|
|
@@ -954,3 +946,45 @@ function knownStateIn(msg: LoadMessage | KnownStateMessage) {
|
|
|
954
946
|
sessions: msg.sessions,
|
|
955
947
|
};
|
|
956
948
|
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Returns a ServerPeerSelector that implements the Highest Weighted Random (HWR) algorithm.
|
|
952
|
+
*
|
|
953
|
+
* The HWR algorithm deterministically selects the top `n` peers for a given CoValue ID by assigning
|
|
954
|
+
* each peer a "weight" based on the MD5 hash of the concatenation of the CoValue ID and the peer's ID.
|
|
955
|
+
* The first 4 bytes of the hash are interpreted as a 32-bit unsigned integer, which serves as the peer's weight.
|
|
956
|
+
* Peers are then sorted in descending order of weight, and the top `n` are selected.
|
|
957
|
+
*/
|
|
958
|
+
export function hwrServerPeerSelector(n: number): ServerPeerSelector {
|
|
959
|
+
if (n === 0) {
|
|
960
|
+
throw new Error("n must be greater than 0");
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const enc = new TextEncoder();
|
|
964
|
+
|
|
965
|
+
// Take the md5 hash of the peer ID and CoValue ID and convert the first 4 bytes to a 32-bit unsigned integer
|
|
966
|
+
const getWeight = (id: RawCoID, peer: PeerState): number => {
|
|
967
|
+
const hash = md5(enc.encode(id + peer.id));
|
|
968
|
+
return (
|
|
969
|
+
((hash[0]! << 24) | (hash[1]! << 16) | (hash[2]! << 8) | hash[3]!) >>> 0
|
|
970
|
+
);
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
return (id, serverPeers) => {
|
|
974
|
+
if (serverPeers.length <= n) {
|
|
975
|
+
return serverPeers;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const weightedPeers = serverPeers.map((peer) => {
|
|
979
|
+
return {
|
|
980
|
+
peer,
|
|
981
|
+
weight: getWeight(id, peer),
|
|
982
|
+
};
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
return weightedPeers
|
|
986
|
+
.sort((a, b) => b.weight - a.weight)
|
|
987
|
+
.slice(0, n)
|
|
988
|
+
.map((wp) => wp.peer);
|
|
989
|
+
};
|
|
990
|
+
}
|
|
@@ -126,6 +126,72 @@ describe("PureJSCrypto", () => {
|
|
|
126
126
|
|
|
127
127
|
expect(map.get("count")).toEqual(0);
|
|
128
128
|
});
|
|
129
|
+
|
|
130
|
+
it("can add a meta to a private transaction", async () => {
|
|
131
|
+
const client = setupTestNode({
|
|
132
|
+
connected: true,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const group = client.node.createGroup();
|
|
136
|
+
const map = group.createMap();
|
|
137
|
+
|
|
138
|
+
map.core.makeTransaction([], "private", {
|
|
139
|
+
meta: {
|
|
140
|
+
count: 1,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await map.core.waitForSync();
|
|
145
|
+
|
|
146
|
+
const session2 = client.spawnNewSession();
|
|
147
|
+
|
|
148
|
+
const mapInOtherSession = await loadCoValueOrFail(session2.node, map.id);
|
|
149
|
+
|
|
150
|
+
const decryptedMeta =
|
|
151
|
+
mapInOtherSession.core.verified.decryptTransactionMeta(
|
|
152
|
+
client.node.currentSessionID,
|
|
153
|
+
0,
|
|
154
|
+
map.core.getCurrentReadKey().secret!,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(decryptedMeta).toEqual({
|
|
158
|
+
meta: {
|
|
159
|
+
count: 1,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("can add a meta to a trusting transaction", async () => {
|
|
165
|
+
const client = setupTestNode({
|
|
166
|
+
connected: true,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const group = client.node.createGroup();
|
|
170
|
+
const map = group.createMap();
|
|
171
|
+
|
|
172
|
+
map.core.makeTransaction([], "trusting", {
|
|
173
|
+
meta: {
|
|
174
|
+
count: 1,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await map.core.waitForSync();
|
|
179
|
+
|
|
180
|
+
const session2 = client.spawnNewSession();
|
|
181
|
+
|
|
182
|
+
const mapInOtherSession = await loadCoValueOrFail(session2.node, map.id);
|
|
183
|
+
|
|
184
|
+
const transferredMeta = JSON.parse(
|
|
185
|
+
mapInOtherSession.core.verified.sessions.get(client.node.currentSessionID)
|
|
186
|
+
?.transactions[0]?.meta!,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(transferredMeta).toEqual({
|
|
190
|
+
meta: {
|
|
191
|
+
count: 1,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
});
|
|
129
195
|
});
|
|
130
196
|
|
|
131
197
|
describe("PureJSSessionLog", () => {
|
|
@@ -125,4 +125,92 @@ describe("WasmCrypto", () => {
|
|
|
125
125
|
|
|
126
126
|
expect(map.get("count")).toEqual(0);
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
it("can add a meta to a private transaction", async () => {
|
|
130
|
+
const client = setupTestNode({
|
|
131
|
+
connected: true,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const group = client.node.createGroup();
|
|
135
|
+
const map = group.createMap();
|
|
136
|
+
|
|
137
|
+
map.core.makeTransaction([], "private", {
|
|
138
|
+
meta: {
|
|
139
|
+
count: 1,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await map.core.waitForSync();
|
|
144
|
+
|
|
145
|
+
const session2 = client.spawnNewSession();
|
|
146
|
+
|
|
147
|
+
const mapInOtherSession = await loadCoValueOrFail(session2.node, map.id);
|
|
148
|
+
|
|
149
|
+
const decryptedMeta =
|
|
150
|
+
mapInOtherSession.core.verified.decryptTransactionMeta(
|
|
151
|
+
client.node.currentSessionID,
|
|
152
|
+
0,
|
|
153
|
+
mapInOtherSession.core.getCurrentReadKey().secret!,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(decryptedMeta).toEqual({
|
|
157
|
+
meta: {
|
|
158
|
+
count: 1,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("can add a meta to a trusting transaction", async () => {
|
|
164
|
+
const client = setupTestNode({
|
|
165
|
+
connected: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const group = client.node.createGroup();
|
|
169
|
+
const map = group.createMap();
|
|
170
|
+
|
|
171
|
+
map.core.makeTransaction([], "trusting", {
|
|
172
|
+
meta: {
|
|
173
|
+
count: 1,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await map.core.waitForSync();
|
|
178
|
+
|
|
179
|
+
const session2 = client.spawnNewSession();
|
|
180
|
+
|
|
181
|
+
const mapInOtherSession = await loadCoValueOrFail(session2.node, map.id);
|
|
182
|
+
|
|
183
|
+
const transferredMeta = JSON.parse(
|
|
184
|
+
mapInOtherSession.core.verified.sessions.get(client.node.currentSessionID)
|
|
185
|
+
?.transactions[0]?.meta!,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
expect(transferredMeta).toEqual({
|
|
189
|
+
meta: {
|
|
190
|
+
count: 1,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("fails to verify signatures without a signer ID", async () => {
|
|
196
|
+
const agentSecret = wasmCrypto.newRandomAgentSecret();
|
|
197
|
+
const sessionID = wasmCrypto.newRandomSessionID(
|
|
198
|
+
wasmCrypto.getAgentID(agentSecret),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const sessionLog = wasmCrypto.createSessionLog("co_z12345678", sessionID);
|
|
202
|
+
expect(() =>
|
|
203
|
+
sessionLog.tryAdd(
|
|
204
|
+
[
|
|
205
|
+
{
|
|
206
|
+
privacy: "trusting",
|
|
207
|
+
changes: stableStringify([{ op: "set", key: "count", value: 1 }]),
|
|
208
|
+
madeAt: Date.now(),
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
"signature_z12345678",
|
|
212
|
+
false,
|
|
213
|
+
),
|
|
214
|
+
).toThrow(expect.stringContaining("Signature verification failed"));
|
|
215
|
+
});
|
|
128
216
|
});
|
|
@@ -137,6 +137,62 @@ test("Should migrate the root from private to trusting", async () => {
|
|
|
137
137
|
expect(account3.ops).toEqual(account2.ops); // No new transactions were made
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
+
test("throws an error if the user tried to add a member to an account", async () => {
|
|
141
|
+
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
|
142
|
+
creationProps: { name: "Hermes Puggington" },
|
|
143
|
+
crypto: Crypto,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const account = await node.load(accountID);
|
|
147
|
+
if (account === "unavailable") throw new Error("Account unavailable");
|
|
148
|
+
|
|
149
|
+
expect(() => account.addMember("everyone", "admin")).toThrow(
|
|
150
|
+
"Cannot add a member to an account",
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("throws an error if the user tried to remove a member from an account", async () => {
|
|
155
|
+
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
|
156
|
+
creationProps: { name: "Hermes Puggington" },
|
|
157
|
+
crypto: Crypto,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const account = await node.load(accountID);
|
|
161
|
+
if (account === "unavailable") throw new Error("Account unavailable");
|
|
162
|
+
|
|
163
|
+
expect(() => account.removeMember("everyone")).toThrow(
|
|
164
|
+
"Cannot remove a member from an account",
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("throws an error if the user tried to extend an account", async () => {
|
|
169
|
+
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
|
170
|
+
creationProps: { name: "Hermes Puggington" },
|
|
171
|
+
crypto: Crypto,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const account = await node.load(accountID);
|
|
175
|
+
if (account === "unavailable") throw new Error("Account unavailable");
|
|
176
|
+
|
|
177
|
+
expect(() => account.extend(node.createGroup())).toThrow(
|
|
178
|
+
"Cannot extend an account",
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("throws an error if the user tried to revoke extend from an account", async () => {
|
|
183
|
+
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
|
184
|
+
creationProps: { name: "Hermes Puggington" },
|
|
185
|
+
crypto: Crypto,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const account = await node.load(accountID);
|
|
189
|
+
if (account === "unavailable") throw new Error("Account unavailable");
|
|
190
|
+
|
|
191
|
+
expect(() => account.revokeExtend(node.createGroup())).toThrow(
|
|
192
|
+
"Cannot unextend an account",
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
140
196
|
test("throws an error if the user tried to create an invite from an account", async () => {
|
|
141
197
|
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
|
142
198
|
creationProps: { name: "Hermes Puggington" },
|
package/src/tests/coList.test.ts
CHANGED
|
@@ -409,3 +409,22 @@ test("totalValidTransactions should return the number of valid transactions proc
|
|
|
409
409
|
listOnOtherClient.core.getCurrentContent().totalValidTransactions,
|
|
410
410
|
).toEqual(2);
|
|
411
411
|
});
|
|
412
|
+
|
|
413
|
+
test("Should ignore unknown meta transactions", () => {
|
|
414
|
+
const node = nodeWithRandomAgentAndSessionID();
|
|
415
|
+
|
|
416
|
+
const coValue = node.createCoValue({
|
|
417
|
+
type: "colist",
|
|
418
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
419
|
+
meta: null,
|
|
420
|
+
...Crypto.createdNowUnique(),
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
coValue.makeTransaction([], "trusting", { unknownMeta: 1 });
|
|
424
|
+
|
|
425
|
+
const content = expectList(coValue.getCurrentContent());
|
|
426
|
+
|
|
427
|
+
content.append("first", 0, "trusting");
|
|
428
|
+
|
|
429
|
+
expect(content.toJSON()).toEqual(["first"]);
|
|
430
|
+
});
|