cojson 0.7.33-hotfixes.5 → 0.7.34-neverthrow.0

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.
@@ -7,6 +7,7 @@ import {
7
7
  StreamingHash,
8
8
  KeyID,
9
9
  CryptoProvider,
10
+ SignerID,
10
11
  } from "./crypto/crypto.js";
11
12
  import { JsonObject, JsonValue } from "./jsonValue.js";
12
13
  import { base58 } from "@scure/base";
@@ -16,7 +17,7 @@ import {
16
17
  isKeyForKeyField,
17
18
  } from "./permissions.js";
18
19
  import { RawGroup } from "./coValues/group.js";
19
- import { LocalNode } from "./localNode.js";
20
+ import { LocalNode, ResolveAccountAgentError } from "./localNode.js";
20
21
  import { CoValueKnownState, NewContentMessage } from "./sync.js";
21
22
  import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
22
23
  import { AccountID, ControlledAccountOrAgent } from "./coValues/account.js";
@@ -25,6 +26,7 @@ import { coreToCoValue } from "./coreToCoValue.js";
25
26
  import { expectGroup } from "./typeUtils/expectGroup.js";
26
27
  import { isAccountID } from "./typeUtils/isAccountID.js";
27
28
  import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
29
+ import { err, ok, Result, ResultAsync } from "neverthrow";
28
30
 
29
31
  /**
30
32
  In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
@@ -103,7 +105,6 @@ export class CoValueCore {
103
105
  _decryptionCache: {
104
106
  [key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
105
107
  } = {};
106
- currentlyAsyncApplyingTxDone?: Promise<void>;
107
108
  _cachedKnownState?: CoValueKnownState;
108
109
  _cachedDependentOn?: RawCoID[];
109
110
  _cachedNewContentSinceEmpty?: NewContentMessage[] | undefined;
@@ -182,7 +183,9 @@ export class CoValueCore {
182
183
  this.header.meta?.type === "account"
183
184
  ? (this.node.currentSessionID.replace(
184
185
  this.node.account.id,
185
- this.node.account.currentAgentID(),
186
+ this.node.account
187
+ .currentAgentID()
188
+ ._unsafeUnwrap({ withStackTrace: true }),
186
189
  ) as SessionID)
187
190
  : this.node.currentSessionID;
188
191
 
@@ -197,166 +200,163 @@ export class CoValueCore {
197
200
  newTransactions: Transaction[],
198
201
  givenExpectedNewHash: Hash | undefined,
199
202
  newSignature: Signature,
200
- ): boolean {
201
- const signerID = this.crypto.getAgentSignerID(
202
- this.node.resolveAccountAgent(
203
+ ): Result<true, TryAddTransactionsError> {
204
+ return this.node
205
+ .resolveAccountAgent(
203
206
  accountOrAgentIDfromSessionID(sessionID),
204
207
  "Expected to know signer of transaction",
205
- ),
206
- );
208
+ )
209
+ .andThen((agent) => {
210
+ const signerID = this.crypto.getAgentSignerID(agent);
211
+
212
+ // const beforeHash = performance.now();
213
+ const { expectedNewHash, newStreamingHash } =
214
+ this.expectedNewHashAfter(sessionID, newTransactions);
215
+ // const afterHash = performance.now();
216
+ // console.log(
217
+ // "Hashing took",
218
+ // afterHash - beforeHash
219
+ // );
220
+
221
+ if (
222
+ givenExpectedNewHash &&
223
+ givenExpectedNewHash !== expectedNewHash
224
+ ) {
225
+ return err({
226
+ type: "InvalidHash",
227
+ id: this.id,
228
+ expectedNewHash,
229
+ givenExpectedNewHash,
230
+ } satisfies InvalidHashError);
231
+ }
207
232
 
208
- if (!signerID) {
209
- console.warn(
210
- "Unknown agent",
211
- accountOrAgentIDfromSessionID(sessionID),
212
- );
213
- return false;
214
- }
233
+ // const beforeVerify = performance.now();
234
+ if (
235
+ !this.crypto.verify(newSignature, expectedNewHash, signerID)
236
+ ) {
237
+ return err({
238
+ type: "InvalidSignature",
239
+ id: this.id,
240
+ newSignature,
241
+ sessionID,
242
+ signerID,
243
+ } satisfies InvalidSignatureError);
244
+ }
245
+ // const afterVerify = performance.now();
246
+ // console.log(
247
+ // "Verify took",
248
+ // afterVerify - beforeVerify
249
+ // );
250
+
251
+ this.doAddTransactions(
252
+ sessionID,
253
+ newTransactions,
254
+ newSignature,
255
+ expectedNewHash,
256
+ newStreamingHash,
257
+ "immediate",
258
+ );
215
259
 
216
- // const beforeHash = performance.now();
217
- const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(
218
- sessionID,
219
- newTransactions,
220
- );
221
- // const afterHash = performance.now();
222
- // console.log(
223
- // "Hashing took",
224
- // afterHash - beforeHash
225
- // );
226
-
227
- if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
228
- console.warn("Invalid hash", {
229
- expectedNewHash,
230
- givenExpectedNewHash,
260
+ return ok(true as const);
231
261
  });
232
- return false;
233
- }
234
-
235
- // const beforeVerify = performance.now();
236
- if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
237
- console.warn(
238
- "Invalid signature in",
239
- this.id,
240
- newSignature,
241
- expectedNewHash,
242
- signerID,
243
- );
244
- return false;
245
- }
246
- // const afterVerify = performance.now();
247
- // console.log(
248
- // "Verify took",
249
- // afterVerify - beforeVerify
250
- // );
251
-
252
- this.doAddTransactions(
253
- sessionID,
254
- newTransactions,
255
- newSignature,
256
- expectedNewHash,
257
- newStreamingHash,
258
- "immediate",
259
- );
260
-
261
- return true;
262
262
  }
263
263
 
264
- async tryAddTransactionsAsync(
264
+ tryAddTransactionsAsync(
265
265
  sessionID: SessionID,
266
266
  newTransactions: Transaction[],
267
267
  givenExpectedNewHash: Hash | undefined,
268
268
  newSignature: Signature,
269
- ): Promise<boolean> {
270
- if (this.currentlyAsyncApplyingTxDone) {
271
- await this.currentlyAsyncApplyingTxDone;
272
- }
273
- let resolveDone!: () => void;
274
-
275
- this.currentlyAsyncApplyingTxDone = new Promise((resolve) => {
276
- resolveDone = resolve;
277
- });
278
-
279
- const signerID = this.crypto.getAgentSignerID(
280
- await this.node.resolveAccountAgentAsync(
269
+ ): ResultAsync<true, TryAddTransactionsError> {
270
+ return this.node
271
+ .resolveAccountAgentAsync(
281
272
  accountOrAgentIDfromSessionID(sessionID),
282
273
  "Expected to know signer of transaction",
283
- ),
284
- );
285
-
286
- if (!signerID) {
287
- console.warn(
288
- "Unknown agent",
289
- accountOrAgentIDfromSessionID(sessionID),
290
- );
291
- resolveDone();
292
- return false;
293
- }
294
-
295
- const nTxBefore =
296
- this.sessionLogs.get(sessionID)?.transactions.length ?? 0;
297
-
298
- // const beforeHash = performance.now();
299
- const { expectedNewHash, newStreamingHash } =
300
- await this.expectedNewHashAfterAsync(sessionID, newTransactions);
301
- // const afterHash = performance.now();
302
- // console.log(
303
- // "Hashing took",
304
- // afterHash - beforeHash
305
- // );
306
-
307
- const nTxAfter =
308
- this.sessionLogs.get(sessionID)?.transactions.length ?? 0;
309
-
310
- if (nTxAfter !== nTxBefore) {
311
- const newTransactionLengthBefore = newTransactions.length;
312
- newTransactions = newTransactions.slice(nTxAfter - nTxBefore);
313
- console.warn("Transactions changed while async hashing", {
314
- nTxBefore,
315
- nTxAfter,
316
- newTransactionLengthBefore,
317
- remainingNewTransactions: newTransactions.length,
318
- });
319
- }
274
+ )
275
+ .andThen((agent) => {
276
+ const signerID = this.crypto.getAgentSignerID(agent);
277
+
278
+ const nTxBefore =
279
+ this.sessionLogs.get(sessionID)?.transactions.length ?? 0;
280
+
281
+ // const beforeHash = performance.now();
282
+ return ResultAsync.fromSafePromise(
283
+ this.expectedNewHashAfterAsync(sessionID, newTransactions),
284
+ ).andThen(({ expectedNewHash, newStreamingHash }) => {
285
+ // const afterHash = performance.now();
286
+ // console.log(
287
+ // "Hashing took",
288
+ // afterHash - beforeHash
289
+ // );
290
+
291
+ const nTxAfter =
292
+ this.sessionLogs.get(sessionID)?.transactions.length ??
293
+ 0;
294
+
295
+ if (nTxAfter !== nTxBefore) {
296
+ const newTransactionLengthBefore =
297
+ newTransactions.length;
298
+ newTransactions = newTransactions.slice(
299
+ nTxAfter - nTxBefore,
300
+ );
301
+ console.warn(
302
+ "Transactions changed while async hashing",
303
+ {
304
+ nTxBefore,
305
+ nTxAfter,
306
+ newTransactionLengthBefore,
307
+ remainingNewTransactions:
308
+ newTransactions.length,
309
+ },
310
+ );
311
+ }
320
312
 
321
- if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
322
- console.warn("Invalid hash", {
323
- expectedNewHash,
324
- givenExpectedNewHash,
325
- });
326
- resolveDone();
327
- return false;
328
- }
313
+ if (
314
+ givenExpectedNewHash &&
315
+ givenExpectedNewHash !== expectedNewHash
316
+ ) {
317
+ return err({
318
+ type: "InvalidHash",
319
+ id: this.id,
320
+ expectedNewHash,
321
+ givenExpectedNewHash,
322
+ } satisfies InvalidHashError);
323
+ }
329
324
 
330
- performance.mark("verifyStart" + this.id);
331
- if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
332
- console.warn(
333
- "Invalid signature in",
334
- this.id,
335
- newSignature,
336
- expectedNewHash,
337
- signerID,
338
- );
339
- resolveDone();
340
- return false;
341
- }
342
- performance.mark("verifyEnd" + this.id);
343
- performance.measure(
344
- "verify" + this.id,
345
- "verifyStart" + this.id,
346
- "verifyEnd" + this.id,
347
- );
325
+ performance.mark("verifyStart" + this.id);
326
+ if (
327
+ !this.crypto.verify(
328
+ newSignature,
329
+ expectedNewHash,
330
+ signerID,
331
+ )
332
+ ) {
333
+ return err({
334
+ type: "InvalidSignature",
335
+ id: this.id,
336
+ newSignature,
337
+ sessionID,
338
+ signerID,
339
+ } satisfies InvalidSignatureError);
340
+ }
341
+ performance.mark("verifyEnd" + this.id);
342
+ performance.measure(
343
+ "verify" + this.id,
344
+ "verifyStart" + this.id,
345
+ "verifyEnd" + this.id,
346
+ );
348
347
 
349
- this.doAddTransactions(
350
- sessionID,
351
- newTransactions,
352
- newSignature,
353
- expectedNewHash,
354
- newStreamingHash,
355
- "deferred",
356
- );
348
+ this.doAddTransactions(
349
+ sessionID,
350
+ newTransactions,
351
+ newSignature,
352
+ expectedNewHash,
353
+ newStreamingHash,
354
+ "deferred",
355
+ );
357
356
 
358
- resolveDone();
359
- return true;
357
+ return ok(true as const);
358
+ });
359
+ });
360
360
  }
361
361
 
362
362
  private doAddTransactions(
@@ -545,7 +545,9 @@ export class CoValueCore {
545
545
  this.header.meta?.type === "account"
546
546
  ? (this.node.currentSessionID.replace(
547
547
  this.node.account.id,
548
- this.node.account.currentAgentID(),
548
+ this.node.account
549
+ .currentAgentID()
550
+ ._unsafeUnwrap({ withStackTrace: true }),
549
551
  ) as SessionID)
550
552
  : this.node.currentSessionID;
551
553
 
@@ -563,7 +565,7 @@ export class CoValueCore {
563
565
  [transaction],
564
566
  expectedNewHash,
565
567
  signature,
566
- );
568
+ )._unsafeUnwrap({ withStackTrace: true });
567
569
 
568
570
  if (success) {
569
571
  void this.node.syncManager.syncCoValue(this);
@@ -709,7 +711,9 @@ export class CoValueCore {
709
711
  // Try to find key revelation for us
710
712
  const lookupAccountOrAgentID =
711
713
  this.header.meta?.type === "account"
712
- ? this.node.account.currentAgentID()
714
+ ? this.node.account
715
+ .currentAgentID()
716
+ ._unsafeUnwrap({ withStackTrace: true })
713
717
  : this.node.account.id;
714
718
 
715
719
  const lastReadyKeyEdit = content.lastEditAt(
@@ -718,10 +722,9 @@ export class CoValueCore {
718
722
 
719
723
  if (lastReadyKeyEdit?.value) {
720
724
  const revealer = lastReadyKeyEdit.by;
721
- const revealerAgent = this.node.resolveAccountAgent(
722
- revealer,
723
- "Expected to know revealer",
724
- );
725
+ const revealerAgent = this.node
726
+ .resolveAccountAgent(revealer, "Expected to know revealer")
727
+ ._unsafeUnwrap({ withStackTrace: true });
725
728
 
726
729
  const secret = this.crypto.unseal(
727
730
  lastReadyKeyEdit.value,
@@ -986,3 +989,23 @@ function getNextKnownSignatureIdx(
986
989
  idx >= (sentStateForSessionID ?? knownStateForSessionID ?? -1),
987
990
  );
988
991
  }
992
+
993
+ export type InvalidHashError = {
994
+ type: "InvalidHash";
995
+ id: RawCoID;
996
+ expectedNewHash: Hash;
997
+ givenExpectedNewHash: Hash;
998
+ };
999
+
1000
+ export type InvalidSignatureError = {
1001
+ type: "InvalidSignature";
1002
+ id: RawCoID;
1003
+ newSignature: Signature;
1004
+ sessionID: SessionID;
1005
+ signerID: SignerID;
1006
+ };
1007
+
1008
+ export type TryAddTransactionsError =
1009
+ | ResolveAccountAgentError
1010
+ | InvalidHashError
1011
+ | InvalidSignatureError;
@@ -13,6 +13,7 @@ import { RawCoMap } from "./coMap.js";
13
13
  import { RawGroup, InviteSecret } from "./group.js";
14
14
  import { LocalNode } from "../index.js";
15
15
  import { JsonObject } from "../jsonValue.js";
16
+ import { err, ok, Result } from "neverthrow";
16
17
 
17
18
  export function accountHeaderForInitialAgentSecret(
18
19
  agentSecret: AgentSecret,
@@ -30,28 +31,34 @@ export function accountHeaderForInitialAgentSecret(
30
31
  };
31
32
  }
32
33
 
34
+ export type InvalidAccountAgentIDError = {
35
+ type: "InvalidAccountAgentID";
36
+ reason: string;
37
+ };
38
+
33
39
  export class RawAccount<
34
40
  Meta extends AccountMeta = AccountMeta,
35
41
  > extends RawGroup<Meta> {
36
42
  _cachedCurrentAgentID: AgentID | undefined;
37
43
 
38
- currentAgentID(): AgentID {
44
+ currentAgentID(): Result<AgentID, InvalidAccountAgentIDError> {
39
45
  if (this._cachedCurrentAgentID) {
40
- return this._cachedCurrentAgentID;
46
+ return ok(this._cachedCurrentAgentID);
41
47
  }
42
48
  const agents = this.keys().filter((k): k is AgentID =>
43
49
  k.startsWith("sealer_"),
44
50
  );
45
51
 
46
52
  if (agents.length !== 1) {
47
- throw new Error(
48
- "Expected exactly one agent in account, got " + agents.length,
49
- );
53
+ return err({
54
+ type: "InvalidAccountAgentID",
55
+ reason: "Account has " + agents.length + " agents",
56
+ });
50
57
  }
51
58
 
52
59
  this._cachedCurrentAgentID = agents[0];
53
60
 
54
- return agents[0]!;
61
+ return ok(agents[0]!);
55
62
  }
56
63
  }
57
64
 
@@ -59,10 +66,10 @@ export interface ControlledAccountOrAgent {
59
66
  id: AccountID | AgentID;
60
67
  agentSecret: AgentSecret;
61
68
 
62
- currentAgentID: () => AgentID;
63
- currentSignerID: () => SignerID;
69
+ currentAgentID: () => Result<AgentID, InvalidAccountAgentIDError>;
70
+ currentSignerID: () => Result<SignerID, InvalidAccountAgentIDError>;
64
71
  currentSignerSecret: () => SignerSecret;
65
- currentSealerID: () => SealerID;
72
+ currentSealerID: () => Result<SealerID, InvalidAccountAgentIDError>;
66
73
  currentSealerSecret: () => SealerSecret;
67
74
  }
68
75
 
@@ -96,25 +103,29 @@ export class RawControlledAccount<Meta extends AccountMeta = AccountMeta>
96
103
  return this.core.node.acceptInvite(groupOrOwnedValueID, inviteSecret);
97
104
  }
98
105
 
99
- currentAgentID(): AgentID {
106
+ currentAgentID(): Result<AgentID, InvalidAccountAgentIDError> {
100
107
  if (this._cachedCurrentAgentID) {
101
- return this._cachedCurrentAgentID;
108
+ return ok(this._cachedCurrentAgentID);
102
109
  }
103
110
  const agentID = this.crypto.getAgentID(this.agentSecret);
104
111
  this._cachedCurrentAgentID = agentID;
105
- return agentID;
112
+ return ok(agentID);
106
113
  }
107
114
 
108
- currentSignerID(): SignerID {
109
- return this.crypto.getAgentSignerID(this.currentAgentID());
115
+ currentSignerID() {
116
+ return this.currentAgentID().map((id) =>
117
+ this.crypto.getAgentSignerID(id),
118
+ );
110
119
  }
111
120
 
112
121
  currentSignerSecret(): SignerSecret {
113
122
  return this.crypto.getAgentSignerSecret(this.agentSecret);
114
123
  }
115
124
 
116
- currentSealerID(): SealerID {
117
- return this.crypto.getAgentSealerID(this.currentAgentID());
125
+ currentSealerID() {
126
+ return this.currentAgentID().map((id) =>
127
+ this.crypto.getAgentSealerID(id),
128
+ );
118
129
  }
119
130
 
120
131
  currentSealerSecret(): SealerSecret {
@@ -133,20 +144,24 @@ export class ControlledAgent implements ControlledAccountOrAgent {
133
144
  return this.crypto.getAgentID(this.agentSecret);
134
145
  }
135
146
 
136
- currentAgentID(): AgentID {
137
- return this.crypto.getAgentID(this.agentSecret);
147
+ currentAgentID() {
148
+ return ok(this.crypto.getAgentID(this.agentSecret));
138
149
  }
139
150
 
140
- currentSignerID(): SignerID {
141
- return this.crypto.getAgentSignerID(this.currentAgentID());
151
+ currentSignerID() {
152
+ return this.currentAgentID().map((id) =>
153
+ this.crypto.getAgentSignerID(id),
154
+ );
142
155
  }
143
156
 
144
157
  currentSignerSecret(): SignerSecret {
145
158
  return this.crypto.getAgentSignerSecret(this.agentSecret);
146
159
  }
147
160
 
148
- currentSealerID(): SealerID {
149
- return this.crypto.getAgentSealerID(this.currentAgentID());
161
+ currentSealerID() {
162
+ return this.currentAgentID().map((id) =>
163
+ this.crypto.getAgentSealerID(id),
164
+ );
150
165
  }
151
166
 
152
167
  currentSealerSecret(): SealerSecret {
@@ -120,7 +120,9 @@ export class RawGroup<
120
120
  const agent =
121
121
  typeof account === "string"
122
122
  ? account
123
- : account.currentAgentID();
123
+ : account
124
+ .currentAgentID()
125
+ ._unsafeUnwrap({ withStackTrace: true });
124
126
  this.set(memberKey, role, "trusting");
125
127
 
126
128
  if (this.get(memberKey) !== role) {
@@ -175,7 +177,7 @@ export class RawGroup<
175
177
  const reader = this.core.node.resolveAccountAgent(
176
178
  readerID,
177
179
  "Expected to know currently permitted reader",
178
- );
180
+ )._unsafeUnwrap({ withStackTrace: true });
179
181
 
180
182
  this.set(
181
183
  `${newReadKey.id}_for_${readerID}`,