cojson 0.13.17 → 0.13.18
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 +9 -0
- package/dist/PeerState.d.ts +3 -0
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +9 -0
- package/dist/PeerState.js.map +1 -1
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +2 -3
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValue.d.ts +4 -4
- package/dist/coValue.d.ts.map +1 -1
- package/dist/coValue.js +4 -4
- package/dist/coValue.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +143 -0
- package/dist/coValueCore/coValueCore.d.ts.map +1 -0
- package/dist/{coValueCore.js → coValueCore/coValueCore.js} +314 -246
- package/dist/coValueCore/coValueCore.js.map +1 -0
- package/dist/coValueCore/verifiedState.d.ts +65 -0
- package/dist/coValueCore/verifiedState.d.ts.map +1 -0
- package/dist/coValueCore/verifiedState.js +210 -0
- package/dist/coValueCore/verifiedState.js.map +1 -0
- package/dist/coValues/account.d.ts +8 -10
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +12 -13
- package/dist/coValues/account.js.map +1 -1
- package/dist/coValues/coList.d.ts +3 -3
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +6 -3
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -3
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +3 -3
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coPlainText.d.ts +2 -2
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +4 -4
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/coStream.d.ts +3 -3
- package/dist/coValues/coStream.d.ts.map +1 -1
- package/dist/coValues/coStream.js +3 -3
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/coValues/group.d.ts +7 -2
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +29 -26
- package/dist/coValues/group.js.map +1 -1
- package/dist/coreToCoValue.d.ts +2 -2
- package/dist/coreToCoValue.d.ts.map +1 -1
- package/dist/coreToCoValue.js +10 -14
- package/dist/coreToCoValue.js.map +1 -1
- package/dist/exports.d.ts +6 -5
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +3 -4
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +30 -24
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +139 -170
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts +2 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +15 -11
- package/dist/permissions.js.map +1 -1
- package/dist/priority.d.ts +1 -1
- package/dist/priority.d.ts.map +1 -1
- package/dist/sync.d.ts +2 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +86 -55
- package/dist/sync.js.map +1 -1
- package/dist/tests/coList.test.js +19 -16
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coMap.test.js +12 -13
- package/dist/tests/coMap.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +9 -10
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coStream.test.js +22 -17
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +22 -28
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueCoreLoadingState.test.d.ts +2 -0
- package/dist/tests/coValueCoreLoadingState.test.d.ts.map +1 -0
- package/dist/tests/{coValueState.test.js → coValueCoreLoadingState.test.js} +62 -46
- package/dist/tests/coValueCoreLoadingState.test.js.map +1 -0
- package/dist/tests/group.test.js +42 -43
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +2 -2
- package/dist/tests/messagesTestUtils.d.ts.map +1 -1
- package/dist/tests/messagesTestUtils.js +1 -1
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/permissions.test.js +224 -292
- package/dist/tests/permissions.test.js.map +1 -1
- package/dist/tests/priority.test.js +13 -14
- package/dist/tests/priority.test.js.map +1 -1
- package/dist/tests/sync.auth.test.d.ts +2 -0
- package/dist/tests/sync.auth.test.d.ts.map +1 -0
- package/dist/tests/sync.auth.test.js +141 -0
- package/dist/tests/sync.auth.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +4 -4
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +25 -12
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +19 -19
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +20 -13
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +32 -39
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +126 -37
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +24 -15
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +88 -61
- package/dist/tests/testUtils.js.map +1 -1
- package/dist/typeUtils/expectGroup.js +1 -1
- package/dist/typeUtils/expectGroup.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerState.ts +11 -0
- package/src/SyncStateManager.ts +2 -3
- package/src/coValue.ts +11 -8
- package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +469 -413
- package/src/coValueCore/verifiedState.ts +376 -0
- package/src/coValues/account.ts +20 -25
- package/src/coValues/coList.ts +12 -6
- package/src/coValues/coMap.ts +9 -6
- package/src/coValues/coPlainText.ts +9 -6
- package/src/coValues/coStream.ts +9 -6
- package/src/coValues/group.ts +50 -28
- package/src/coreToCoValue.ts +14 -15
- package/src/exports.ts +9 -7
- package/src/localNode.ts +227 -273
- package/src/permissions.ts +18 -12
- package/src/priority.ts +1 -1
- package/src/sync.ts +96 -63
- package/src/tests/coList.test.ts +21 -15
- package/src/tests/coMap.test.ts +12 -13
- package/src/tests/coPlainText.test.ts +12 -9
- package/src/tests/coStream.test.ts +25 -16
- package/src/tests/coValueCore.test.ts +30 -27
- package/src/tests/{coValueState.test.ts → coValueCoreLoadingState.test.ts} +67 -57
- package/src/tests/group.test.ts +44 -68
- package/src/tests/messagesTestUtils.ts +3 -8
- package/src/tests/permissions.test.ts +283 -449
- package/src/tests/priority.test.ts +17 -13
- package/src/tests/sync.auth.test.ts +188 -0
- package/src/tests/sync.load.test.ts +4 -4
- package/src/tests/sync.mesh.test.ts +25 -12
- package/src/tests/sync.peerReconciliation.test.ts +25 -25
- package/src/tests/sync.storage.test.ts +20 -13
- package/src/tests/sync.test.ts +43 -43
- package/src/tests/sync.upload.test.ts +157 -37
- package/src/tests/testUtils.ts +120 -74
- package/src/typeUtils/expectGroup.ts +1 -1
- package/dist/CoValuesStore.d.ts +0 -14
- package/dist/CoValuesStore.d.ts.map +0 -1
- package/dist/CoValuesStore.js +0 -32
- package/dist/CoValuesStore.js.map +0 -1
- package/dist/coValueCore.d.ts +0 -142
- package/dist/coValueCore.d.ts.map +0 -1
- package/dist/coValueCore.js.map +0 -1
- package/dist/coValueState.d.ts +0 -34
- package/dist/coValueState.d.ts.map +0 -1
- package/dist/coValueState.js +0 -190
- package/dist/coValueState.js.map +0 -1
- package/dist/tests/coValueState.test.d.ts +0 -2
- package/dist/tests/coValueState.test.d.ts.map +0 -1
- package/dist/tests/coValueState.test.js.map +0 -1
- package/src/CoValuesStore.ts +0 -41
- package/src/coValueState.ts +0 -245
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
|
|
2
|
+
import { Result, err } from "neverthrow";
|
|
3
|
+
import { PeerState } from "../PeerState.js";
|
|
4
|
+
import { RawCoValue } from "../coValue.js";
|
|
5
|
+
import {
|
|
6
|
+
ControlledAccount,
|
|
7
|
+
ControlledAccountOrAgent,
|
|
8
|
+
RawAccountID,
|
|
9
|
+
} from "../coValues/account.js";
|
|
10
|
+
import { RawGroup } from "../coValues/group.js";
|
|
11
|
+
import { coreToCoValue } from "../coreToCoValue.js";
|
|
6
12
|
import {
|
|
7
13
|
CryptoProvider,
|
|
8
14
|
Encrypted,
|
|
@@ -12,7 +18,7 @@ import {
|
|
|
12
18
|
Signature,
|
|
13
19
|
SignerID,
|
|
14
20
|
StreamingHash,
|
|
15
|
-
} from "
|
|
21
|
+
} from "../crypto/crypto.js";
|
|
16
22
|
import {
|
|
17
23
|
RawCoID,
|
|
18
24
|
SessionID,
|
|
@@ -20,21 +26,20 @@ import {
|
|
|
20
26
|
getGroupDependentKeyList,
|
|
21
27
|
getParentGroupId,
|
|
22
28
|
isParentGroupReference,
|
|
23
|
-
} from "
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { LocalNode, ResolveAccountAgentError } from "
|
|
27
|
-
import { logger } from "
|
|
29
|
+
} from "../ids.js";
|
|
30
|
+
import { parseJSON, stableStringify } from "../jsonStringify.js";
|
|
31
|
+
import { JsonValue } from "../jsonValue.js";
|
|
32
|
+
import { LocalNode, ResolveAccountAgentError } from "../localNode.js";
|
|
33
|
+
import { logger } from "../logger.js";
|
|
28
34
|
import {
|
|
29
|
-
PermissionsDef as RulesetDef,
|
|
30
35
|
determineValidTransactions,
|
|
31
36
|
isKeyForKeyField,
|
|
32
|
-
} from "
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
37
|
+
} from "../permissions.js";
|
|
38
|
+
import { CoValueKnownState, PeerID, emptyKnownState } from "../sync.js";
|
|
39
|
+
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
40
|
+
import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
41
|
+
import { isAccountID } from "../typeUtils/isAccountID.js";
|
|
42
|
+
import { CoValueHeader, Transaction, VerifiedState } from "./verifiedState.js";
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
|
|
@@ -45,17 +50,6 @@ import { isAccountID } from "./typeUtils/isAccountID.js";
|
|
|
45
50
|
**/
|
|
46
51
|
export const MAX_RECOMMENDED_TX_SIZE = 100 * 1024;
|
|
47
52
|
|
|
48
|
-
export type CoValueHeader = {
|
|
49
|
-
type: AnyRawCoValue["type"];
|
|
50
|
-
ruleset: RulesetDef;
|
|
51
|
-
meta: JsonObject | null;
|
|
52
|
-
} & CoValueUniqueness;
|
|
53
|
-
|
|
54
|
-
export type CoValueUniqueness = {
|
|
55
|
-
uniqueness: JsonValue;
|
|
56
|
-
createdAt?: `2${string}` | null;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
53
|
export function idforHeader(
|
|
60
54
|
header: CoValueHeader,
|
|
61
55
|
crypto: CryptoProvider,
|
|
@@ -64,29 +58,6 @@ export function idforHeader(
|
|
|
64
58
|
return `co_z${hash.slice("shortHash_z".length)}`;
|
|
65
59
|
}
|
|
66
60
|
|
|
67
|
-
type SessionLog = {
|
|
68
|
-
transactions: Transaction[];
|
|
69
|
-
lastHash?: Hash;
|
|
70
|
-
streamingHash: StreamingHash;
|
|
71
|
-
signatureAfter: { [txIdx: number]: Signature | undefined };
|
|
72
|
-
lastSignature: Signature;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
export type PrivateTransaction = {
|
|
76
|
-
privacy: "private";
|
|
77
|
-
madeAt: number;
|
|
78
|
-
keyUsed: KeyID;
|
|
79
|
-
encryptedChanges: Encrypted<JsonValue[], { in: RawCoID; tx: TransactionID }>;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export type TrustingTransaction = {
|
|
83
|
-
privacy: "trusting";
|
|
84
|
-
madeAt: number;
|
|
85
|
-
changes: Stringified<JsonValue[]>;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
export type Transaction = PrivateTransaction | TrustingTransaction;
|
|
89
|
-
|
|
90
61
|
export type DecryptedTransaction = {
|
|
91
62
|
txID: TransactionID;
|
|
92
63
|
changes: JsonValue[];
|
|
@@ -95,55 +66,246 @@ export type DecryptedTransaction = {
|
|
|
95
66
|
|
|
96
67
|
const readKeyCache = new WeakMap<CoValueCore, { [id: KeyID]: KeySecret }>();
|
|
97
68
|
|
|
69
|
+
export type AvailableCoValueCore = CoValueCore & { verified: VerifiedState };
|
|
70
|
+
|
|
71
|
+
export const CO_VALUE_LOADING_CONFIG = {
|
|
72
|
+
MAX_RETRIES: 2,
|
|
73
|
+
TIMEOUT: 30_000,
|
|
74
|
+
};
|
|
75
|
+
|
|
98
76
|
export class CoValueCore {
|
|
77
|
+
// context
|
|
78
|
+
readonly node: LocalNode;
|
|
79
|
+
private readonly crypto: CryptoProvider;
|
|
80
|
+
|
|
81
|
+
// state
|
|
99
82
|
id: RawCoID;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
83
|
+
private _verified: VerifiedState | null;
|
|
84
|
+
/** Holds the fundamental syncable content of a CoValue,
|
|
85
|
+
* consisting of the header (verified by hash -> RawCoID)
|
|
86
|
+
* and the sessions (verified by signature).
|
|
87
|
+
*
|
|
88
|
+
* It does not do any *validation* or *decryption* and as such doesn't
|
|
89
|
+
* depend on other CoValues or the LocalNode.
|
|
90
|
+
*
|
|
91
|
+
* `CoValueCore.verified` may be null when a CoValue is requested to be
|
|
92
|
+
* loaded but no content has been received from storage or peers yet.
|
|
93
|
+
* In this case, it acts as a centralised entry to keep track of peer loading
|
|
94
|
+
* state and to subscribe to its content when it does become available. */
|
|
95
|
+
get verified() {
|
|
96
|
+
return this._verified;
|
|
97
|
+
}
|
|
98
|
+
private readonly peers = new Map<
|
|
99
|
+
PeerID,
|
|
100
|
+
| { type: "unknown" | "pending" | "available" | "unavailable" }
|
|
101
|
+
| {
|
|
102
|
+
type: "errored";
|
|
103
|
+
error: TryAddTransactionsError;
|
|
104
|
+
}
|
|
105
|
+
>();
|
|
106
|
+
|
|
107
|
+
// cached state and listeners
|
|
108
|
+
private _cachedContent?: RawCoValue;
|
|
109
|
+
private readonly listeners: Set<
|
|
110
|
+
(core: CoValueCore, unsub: () => void) => void
|
|
111
|
+
> = new Set();
|
|
112
|
+
private readonly _decryptionCache: {
|
|
107
113
|
[key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
|
|
108
114
|
} = {};
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
_cachedNewContentSinceEmpty?: NewContentMessage[] | undefined;
|
|
112
|
-
_currentAsyncAddTransaction?: Promise<void>;
|
|
115
|
+
private _cachedDependentOn?: RawCoID[];
|
|
116
|
+
private counter: UpDownCounter;
|
|
113
117
|
|
|
114
|
-
constructor(
|
|
115
|
-
header: CoValueHeader,
|
|
118
|
+
private constructor(
|
|
119
|
+
init: { header: CoValueHeader } | { id: RawCoID },
|
|
116
120
|
node: LocalNode,
|
|
117
|
-
internalInitSessions: Map<SessionID, SessionLog> = new Map(),
|
|
118
121
|
) {
|
|
119
122
|
this.crypto = node.crypto;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
if ("header" in init) {
|
|
124
|
+
this.id = idforHeader(init.header, node.crypto);
|
|
125
|
+
this._verified = new VerifiedState(
|
|
126
|
+
this.id,
|
|
127
|
+
node.crypto,
|
|
128
|
+
init.header,
|
|
129
|
+
new Map(),
|
|
130
|
+
);
|
|
131
|
+
} else {
|
|
132
|
+
this.id = init.id;
|
|
133
|
+
this._verified = null;
|
|
134
|
+
}
|
|
123
135
|
this.node = node;
|
|
136
|
+
|
|
137
|
+
this.counter = metrics
|
|
138
|
+
.getMeter("cojson")
|
|
139
|
+
.createUpDownCounter("jazz.covalues.loaded", {
|
|
140
|
+
description: "The number of covalues in the system",
|
|
141
|
+
unit: "covalue",
|
|
142
|
+
valueType: ValueType.INT,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.updateCounter(null);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static fromID(id: RawCoID, node: LocalNode): CoValueCore {
|
|
149
|
+
return new CoValueCore({ id }, node);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static fromHeader(
|
|
153
|
+
header: CoValueHeader,
|
|
154
|
+
node: LocalNode,
|
|
155
|
+
): AvailableCoValueCore {
|
|
156
|
+
return new CoValueCore({ header }, node) as AvailableCoValueCore;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get loadingState() {
|
|
160
|
+
if (this.verified) {
|
|
161
|
+
return "available";
|
|
162
|
+
} else if (this.peers.size === 0) {
|
|
163
|
+
return "unknown";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const peer of this.peers.values()) {
|
|
167
|
+
if (peer.type === "pending") {
|
|
168
|
+
return "loading";
|
|
169
|
+
} else if (peer.type === "unknown") {
|
|
170
|
+
return "unknown";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return "unavailable";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
isAvailable(): this is AvailableCoValueCore {
|
|
178
|
+
return !!this.verified;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
isErroredInPeer(peerId: PeerID) {
|
|
182
|
+
return this.peers.get(peerId)?.type === "errored";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
waitForAvailableOrUnavailable(): Promise<CoValueCore> {
|
|
186
|
+
return new Promise<CoValueCore>((resolve) => {
|
|
187
|
+
const listener = (core: CoValueCore) => {
|
|
188
|
+
if (core.isAvailable() || core.loadingState === "unavailable") {
|
|
189
|
+
resolve(core);
|
|
190
|
+
this.listeners.delete(listener);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
this.listeners.add(listener);
|
|
195
|
+
listener(this);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
getStateForPeer(peerId: PeerID) {
|
|
200
|
+
return this.peers.get(peerId);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private updateCounter(previousState: string | null) {
|
|
204
|
+
const newState = this.loadingState;
|
|
205
|
+
|
|
206
|
+
if (previousState !== newState) {
|
|
207
|
+
if (previousState) {
|
|
208
|
+
this.counter.add(-1, { state: previousState });
|
|
209
|
+
}
|
|
210
|
+
this.counter.add(1, { state: newState });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
markNotFoundInPeer(peerId: PeerID) {
|
|
215
|
+
const previousState = this.loadingState;
|
|
216
|
+
this.peers.set(peerId, { type: "unavailable" });
|
|
217
|
+
this.updateCounter(previousState);
|
|
218
|
+
this.notifyUpdate("immediate");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// TODO: rename to "provided"
|
|
222
|
+
markAvailable(header: CoValueHeader, fromPeerId: PeerID) {
|
|
223
|
+
const previousState = this.loadingState;
|
|
224
|
+
|
|
225
|
+
if (this._verified?.sessions.size) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
"CoValueCore: markAvailable called on coValue with verified sessions present!",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
this._verified = new VerifiedState(
|
|
231
|
+
this.id,
|
|
232
|
+
this.node.crypto,
|
|
233
|
+
header,
|
|
234
|
+
new Map(),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
this.peers.set(fromPeerId, { type: "available" });
|
|
238
|
+
this.updateCounter(previousState);
|
|
239
|
+
this.notifyUpdate("immediate");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
internalMarkMagicallyAvailable(
|
|
243
|
+
verified: VerifiedState,
|
|
244
|
+
{ forceOverwrite = false }: { forceOverwrite?: boolean } = {},
|
|
245
|
+
) {
|
|
246
|
+
const previousState = this.loadingState;
|
|
247
|
+
this.internalShamefullyCloneVerifiedStateFrom(verified, {
|
|
248
|
+
forceOverwrite,
|
|
249
|
+
});
|
|
250
|
+
this.updateCounter(previousState);
|
|
251
|
+
this.notifyUpdate("immediate");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
markErrored(peerId: PeerID, error: TryAddTransactionsError) {
|
|
255
|
+
const previousState = this.loadingState;
|
|
256
|
+
this.peers.set(peerId, { type: "errored", error });
|
|
257
|
+
this.updateCounter(previousState);
|
|
258
|
+
this.notifyUpdate("immediate");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private markPending(peerId: PeerID) {
|
|
262
|
+
const previousState = this.loadingState;
|
|
263
|
+
this.peers.set(peerId, { type: "pending" });
|
|
264
|
+
this.updateCounter(previousState);
|
|
265
|
+
this.notifyUpdate("immediate");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
internalShamefullyCloneVerifiedStateFrom(
|
|
269
|
+
state: VerifiedState,
|
|
270
|
+
{ forceOverwrite = false }: { forceOverwrite?: boolean } = {},
|
|
271
|
+
) {
|
|
272
|
+
if (!forceOverwrite && this._verified?.sessions.size) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
"CoValueCore: internalShamefullyCloneVerifiedStateFrom called on coValue with verified sessions present!",
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
this._verified = state.clone();
|
|
278
|
+
this._cachedContent = undefined;
|
|
279
|
+
this._cachedDependentOn = undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
internalShamefullyResetCachedContent() {
|
|
283
|
+
this._cachedContent = undefined;
|
|
284
|
+
this._cachedDependentOn = undefined;
|
|
124
285
|
}
|
|
125
286
|
|
|
126
287
|
groupInvalidationSubscription?: () => void;
|
|
127
288
|
|
|
128
289
|
subscribeToGroupInvalidation() {
|
|
290
|
+
if (!this.verified) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
129
294
|
if (this.groupInvalidationSubscription) {
|
|
130
295
|
return;
|
|
131
296
|
}
|
|
132
297
|
|
|
133
|
-
const header = this.header;
|
|
298
|
+
const header = this.verified.header;
|
|
134
299
|
|
|
135
300
|
if (header.ruleset.type == "ownedByGroup") {
|
|
136
301
|
const groupId = header.ruleset.group;
|
|
137
|
-
const entry = this.node.
|
|
302
|
+
const entry = this.node.getCoValue(groupId);
|
|
138
303
|
|
|
139
304
|
if (entry.isAvailable()) {
|
|
140
|
-
this.groupInvalidationSubscription = entry.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
},
|
|
145
|
-
false,
|
|
146
|
-
);
|
|
305
|
+
this.groupInvalidationSubscription = entry.subscribe((_groupUpdate) => {
|
|
306
|
+
this._cachedContent = undefined;
|
|
307
|
+
this.notifyUpdate("immediate");
|
|
308
|
+
}, false);
|
|
147
309
|
} else {
|
|
148
310
|
logger.error("CoValueCore: Owner group not available", {
|
|
149
311
|
id: this.id,
|
|
@@ -153,64 +315,47 @@ export class CoValueCore {
|
|
|
153
315
|
}
|
|
154
316
|
}
|
|
155
317
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
account: ControlledAccountOrAgent,
|
|
162
|
-
currentSessionID: SessionID,
|
|
163
|
-
): CoValueCore {
|
|
164
|
-
const newNode = this.node.testWithDifferentAccount(
|
|
165
|
-
account,
|
|
166
|
-
currentSessionID,
|
|
318
|
+
contentInClonedNodeWithDifferentAccount(
|
|
319
|
+
controlledAccountOrAgent: ControlledAccountOrAgent,
|
|
320
|
+
): RawCoValue {
|
|
321
|
+
const newNode = this.node.cloneWithDifferentAccount(
|
|
322
|
+
controlledAccountOrAgent,
|
|
167
323
|
);
|
|
168
324
|
|
|
169
|
-
return newNode.expectCoValueLoaded(this.id);
|
|
325
|
+
return newNode.expectCoValueLoaded(this.id).getCurrentContent();
|
|
170
326
|
}
|
|
171
327
|
|
|
172
328
|
knownState(): CoValueKnownState {
|
|
173
|
-
if (this.
|
|
174
|
-
return this.
|
|
329
|
+
if (this.isAvailable()) {
|
|
330
|
+
return this.verified.knownState();
|
|
175
331
|
} else {
|
|
176
|
-
|
|
177
|
-
this._cachedKnownState = knownState;
|
|
178
|
-
return knownState;
|
|
332
|
+
return emptyKnownState(this.id);
|
|
179
333
|
}
|
|
180
334
|
}
|
|
181
335
|
|
|
182
|
-
/** @internal */
|
|
183
|
-
knownStateUncached(): CoValueKnownState {
|
|
184
|
-
const sessions: CoValueKnownState["sessions"] = {};
|
|
185
|
-
|
|
186
|
-
for (const [sessionID, sessionLog] of this.sessionLogs.entries()) {
|
|
187
|
-
sessions[sessionID] = sessionLog.transactions.length;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
id: this.id,
|
|
192
|
-
header: true,
|
|
193
|
-
sessions,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
336
|
get meta(): JsonValue {
|
|
198
|
-
return this.header
|
|
337
|
+
return this.verified?.header.meta ?? null;
|
|
199
338
|
}
|
|
200
339
|
|
|
201
340
|
nextTransactionID(): TransactionID {
|
|
341
|
+
if (!this.verified) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
"CoValueCore: nextTransactionID called on coValue without verified state",
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
202
347
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
203
348
|
const sessionID =
|
|
204
|
-
this.header.meta?.type === "account"
|
|
349
|
+
this.verified.header.meta?.type === "account"
|
|
205
350
|
? (this.node.currentSessionID.replace(
|
|
206
|
-
this.node.
|
|
207
|
-
this.node.
|
|
351
|
+
this.node.getCurrentAgent().id,
|
|
352
|
+
this.node.getCurrentAgent().currentAgentID(),
|
|
208
353
|
) as SessionID)
|
|
209
354
|
: this.node.currentSessionID;
|
|
210
355
|
|
|
211
356
|
return {
|
|
212
357
|
sessionID,
|
|
213
|
-
txIndex: this.
|
|
358
|
+
txIndex: this.verified.sessions.get(sessionID)?.transactions.length || 0,
|
|
214
359
|
};
|
|
215
360
|
}
|
|
216
361
|
|
|
@@ -219,6 +364,7 @@ export class CoValueCore {
|
|
|
219
364
|
newTransactions: Transaction[],
|
|
220
365
|
givenExpectedNewHash: Hash | undefined,
|
|
221
366
|
newSignature: Signature,
|
|
367
|
+
notifyMode: "immediate" | "deferred",
|
|
222
368
|
skipVerify: boolean = false,
|
|
223
369
|
givenNewStreamingHash?: StreamingHash,
|
|
224
370
|
): Result<true, TryAddTransactionsError> {
|
|
@@ -228,126 +374,45 @@ export class CoValueCore {
|
|
|
228
374
|
"Expected to know signer of transaction",
|
|
229
375
|
)
|
|
230
376
|
.andThen((agent) => {
|
|
377
|
+
if (!this.verified) {
|
|
378
|
+
return err({
|
|
379
|
+
type: "TriedToAddTransactionsWithoutVerifiedState",
|
|
380
|
+
id: this.id,
|
|
381
|
+
} satisfies TriedToAddTransactionsWithoutVerifiedStateErrpr);
|
|
382
|
+
}
|
|
383
|
+
|
|
231
384
|
const signerID = this.crypto.getAgentSignerID(agent);
|
|
232
385
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
givenExpectedNewHash,
|
|
243
|
-
givenNewStreamingHash,
|
|
244
|
-
"immediate",
|
|
245
|
-
);
|
|
246
|
-
} else {
|
|
247
|
-
const { expectedNewHash, newStreamingHash } =
|
|
248
|
-
this.expectedNewHashAfter(sessionID, newTransactions);
|
|
386
|
+
const result = this.verified.tryAddTransactions(
|
|
387
|
+
sessionID,
|
|
388
|
+
signerID,
|
|
389
|
+
newTransactions,
|
|
390
|
+
givenExpectedNewHash,
|
|
391
|
+
newSignature,
|
|
392
|
+
skipVerify,
|
|
393
|
+
givenNewStreamingHash,
|
|
394
|
+
);
|
|
249
395
|
|
|
396
|
+
if (result.isOk()) {
|
|
250
397
|
if (
|
|
251
|
-
|
|
252
|
-
|
|
398
|
+
this._cachedContent &&
|
|
399
|
+
"processNewTransactions" in this._cachedContent &&
|
|
400
|
+
typeof this._cachedContent.processNewTransactions === "function"
|
|
253
401
|
) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
expectedNewHash,
|
|
258
|
-
givenExpectedNewHash,
|
|
259
|
-
} satisfies InvalidHashError);
|
|
402
|
+
this._cachedContent.processNewTransactions();
|
|
403
|
+
} else {
|
|
404
|
+
this._cachedContent = undefined;
|
|
260
405
|
}
|
|
261
406
|
|
|
262
|
-
|
|
263
|
-
return err({
|
|
264
|
-
type: "InvalidSignature",
|
|
265
|
-
id: this.id,
|
|
266
|
-
newSignature,
|
|
267
|
-
sessionID,
|
|
268
|
-
signerID,
|
|
269
|
-
} satisfies InvalidSignatureError);
|
|
270
|
-
}
|
|
407
|
+
this._cachedDependentOn = undefined;
|
|
271
408
|
|
|
272
|
-
this.
|
|
273
|
-
sessionID,
|
|
274
|
-
newTransactions,
|
|
275
|
-
newSignature,
|
|
276
|
-
expectedNewHash,
|
|
277
|
-
newStreamingHash,
|
|
278
|
-
"immediate",
|
|
279
|
-
);
|
|
409
|
+
this.notifyUpdate(notifyMode);
|
|
280
410
|
}
|
|
281
411
|
|
|
282
|
-
return
|
|
412
|
+
return result;
|
|
283
413
|
});
|
|
284
414
|
}
|
|
285
415
|
|
|
286
|
-
private doAddTransactions(
|
|
287
|
-
sessionID: SessionID,
|
|
288
|
-
newTransactions: Transaction[],
|
|
289
|
-
newSignature: Signature,
|
|
290
|
-
expectedNewHash: Hash,
|
|
291
|
-
newStreamingHash: StreamingHash,
|
|
292
|
-
notifyMode: "immediate" | "deferred",
|
|
293
|
-
) {
|
|
294
|
-
if (this.node.crashed) {
|
|
295
|
-
throw new Error("Trying to add transactions after node is crashed");
|
|
296
|
-
}
|
|
297
|
-
const transactions = this.sessionLogs.get(sessionID)?.transactions ?? [];
|
|
298
|
-
|
|
299
|
-
for (const tx of newTransactions) {
|
|
300
|
-
transactions.push(tx);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const signatureAfter =
|
|
304
|
-
this.sessionLogs.get(sessionID)?.signatureAfter ?? {};
|
|
305
|
-
|
|
306
|
-
const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce(
|
|
307
|
-
(max, idx) => (parseInt(idx) > max ? parseInt(idx) : max),
|
|
308
|
-
-1,
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
const sizeOfTxsSinceLastInbetweenSignature = transactions
|
|
312
|
-
.slice(lastInbetweenSignatureIdx + 1)
|
|
313
|
-
.reduce(
|
|
314
|
-
(sum, tx) =>
|
|
315
|
-
sum +
|
|
316
|
-
(tx.privacy === "private"
|
|
317
|
-
? tx.encryptedChanges.length
|
|
318
|
-
: tx.changes.length),
|
|
319
|
-
0,
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
if (sizeOfTxsSinceLastInbetweenSignature > MAX_RECOMMENDED_TX_SIZE) {
|
|
323
|
-
signatureAfter[transactions.length - 1] = newSignature;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
this._sessionLogs.set(sessionID, {
|
|
327
|
-
transactions,
|
|
328
|
-
lastHash: expectedNewHash,
|
|
329
|
-
streamingHash: newStreamingHash,
|
|
330
|
-
lastSignature: newSignature,
|
|
331
|
-
signatureAfter: signatureAfter,
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
if (
|
|
335
|
-
this._cachedContent &&
|
|
336
|
-
"processNewTransactions" in this._cachedContent &&
|
|
337
|
-
typeof this._cachedContent.processNewTransactions === "function"
|
|
338
|
-
) {
|
|
339
|
-
this._cachedContent.processNewTransactions();
|
|
340
|
-
} else {
|
|
341
|
-
this._cachedContent = undefined;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
this._cachedKnownState = undefined;
|
|
345
|
-
this._cachedDependentOn = undefined;
|
|
346
|
-
this._cachedNewContentSinceEmpty = undefined;
|
|
347
|
-
|
|
348
|
-
this.notifyUpdate(notifyMode);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
416
|
deferredUpdates = 0;
|
|
352
417
|
nextDeferredNotify: Promise<void> | undefined;
|
|
353
418
|
|
|
@@ -357,10 +422,11 @@ export class CoValueCore {
|
|
|
357
422
|
}
|
|
358
423
|
|
|
359
424
|
if (notifyMode === "immediate") {
|
|
360
|
-
const content = this.getCurrentContent();
|
|
361
425
|
for (const listener of this.listeners) {
|
|
362
426
|
try {
|
|
363
|
-
listener(
|
|
427
|
+
listener(this, () => {
|
|
428
|
+
this.listeners.delete(listener);
|
|
429
|
+
});
|
|
364
430
|
} catch (e) {
|
|
365
431
|
logger.error("Error in listener for coValue " + this.id, { err: e });
|
|
366
432
|
}
|
|
@@ -371,10 +437,11 @@ export class CoValueCore {
|
|
|
371
437
|
setTimeout(() => {
|
|
372
438
|
this.nextDeferredNotify = undefined;
|
|
373
439
|
this.deferredUpdates = 0;
|
|
374
|
-
const content = this.getCurrentContent();
|
|
375
440
|
for (const listener of this.listeners) {
|
|
376
441
|
try {
|
|
377
|
-
listener(
|
|
442
|
+
listener(this, () => {
|
|
443
|
+
this.listeners.delete(listener);
|
|
444
|
+
});
|
|
378
445
|
} catch (e) {
|
|
379
446
|
logger.error("Error in listener for coValue " + this.id, {
|
|
380
447
|
err: e,
|
|
@@ -390,13 +457,15 @@ export class CoValueCore {
|
|
|
390
457
|
}
|
|
391
458
|
|
|
392
459
|
subscribe(
|
|
393
|
-
listener: (
|
|
460
|
+
listener: (core: CoValueCore, unsub: () => void) => void,
|
|
394
461
|
immediateInvoke = true,
|
|
395
462
|
): () => void {
|
|
396
463
|
this.listeners.add(listener);
|
|
397
464
|
|
|
398
465
|
if (immediateInvoke) {
|
|
399
|
-
listener(this
|
|
466
|
+
listener(this, () => {
|
|
467
|
+
this.listeners.delete(listener);
|
|
468
|
+
});
|
|
400
469
|
}
|
|
401
470
|
|
|
402
471
|
return () => {
|
|
@@ -404,28 +473,16 @@ export class CoValueCore {
|
|
|
404
473
|
};
|
|
405
474
|
}
|
|
406
475
|
|
|
407
|
-
expectedNewHashAfter(
|
|
408
|
-
sessionID: SessionID,
|
|
409
|
-
newTransactions: Transaction[],
|
|
410
|
-
): { expectedNewHash: Hash; newStreamingHash: StreamingHash } {
|
|
411
|
-
const streamingHash =
|
|
412
|
-
this.sessionLogs.get(sessionID)?.streamingHash.clone() ??
|
|
413
|
-
new StreamingHash(this.crypto);
|
|
414
|
-
|
|
415
|
-
for (const transaction of newTransactions) {
|
|
416
|
-
streamingHash.update(transaction);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return {
|
|
420
|
-
expectedNewHash: streamingHash.digest(),
|
|
421
|
-
newStreamingHash: streamingHash,
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
|
|
425
476
|
makeTransaction(
|
|
426
477
|
changes: JsonValue[],
|
|
427
478
|
privacy: "private" | "trusting",
|
|
428
479
|
): boolean {
|
|
480
|
+
if (!this.verified) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
"CoValueCore: makeTransaction called on coValue without verified state",
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
429
486
|
const madeAt = Date.now();
|
|
430
487
|
|
|
431
488
|
let transaction: Transaction;
|
|
@@ -460,20 +517,18 @@ export class CoValueCore {
|
|
|
460
517
|
|
|
461
518
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
462
519
|
const sessionID =
|
|
463
|
-
this.header.meta?.type === "account"
|
|
520
|
+
this.verified.header.meta?.type === "account"
|
|
464
521
|
? (this.node.currentSessionID.replace(
|
|
465
|
-
this.node.
|
|
466
|
-
this.node.
|
|
522
|
+
this.node.getCurrentAgent().id,
|
|
523
|
+
this.node.getCurrentAgent().currentAgentID(),
|
|
467
524
|
) as SessionID)
|
|
468
525
|
: this.node.currentSessionID;
|
|
469
526
|
|
|
470
|
-
const { expectedNewHash, newStreamingHash } =
|
|
471
|
-
sessionID,
|
|
472
|
-
[transaction],
|
|
473
|
-
);
|
|
527
|
+
const { expectedNewHash, newStreamingHash } =
|
|
528
|
+
this.verified.expectedNewHashAfter(sessionID, [transaction]);
|
|
474
529
|
|
|
475
530
|
const signature = this.crypto.sign(
|
|
476
|
-
this.node.
|
|
531
|
+
this.node.getCurrentAgent().currentSignerSecret(),
|
|
477
532
|
expectedNewHash,
|
|
478
533
|
);
|
|
479
534
|
|
|
@@ -482,6 +537,7 @@ export class CoValueCore {
|
|
|
482
537
|
[transaction],
|
|
483
538
|
expectedNewHash,
|
|
484
539
|
signature,
|
|
540
|
+
"immediate",
|
|
485
541
|
true,
|
|
486
542
|
newStreamingHash,
|
|
487
543
|
)._unsafeUnwrap({ withStackTrace: true });
|
|
@@ -497,13 +553,19 @@ export class CoValueCore {
|
|
|
497
553
|
getCurrentContent(options?: {
|
|
498
554
|
ignorePrivateTransactions: true;
|
|
499
555
|
}): RawCoValue {
|
|
556
|
+
if (!this.verified) {
|
|
557
|
+
throw new Error(
|
|
558
|
+
"CoValueCore: getCurrentContent called on coValue without verified state",
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
500
562
|
if (!options?.ignorePrivateTransactions && this._cachedContent) {
|
|
501
563
|
return this._cachedContent;
|
|
502
564
|
}
|
|
503
565
|
|
|
504
566
|
this.subscribeToGroupInvalidation();
|
|
505
567
|
|
|
506
|
-
const newContent = coreToCoValue(this, options);
|
|
568
|
+
const newContent = coreToCoValue(this as AvailableCoValueCore, options);
|
|
507
569
|
|
|
508
570
|
if (!options?.ignorePrivateTransactions) {
|
|
509
571
|
this._cachedContent = newContent;
|
|
@@ -605,8 +667,17 @@ export class CoValueCore {
|
|
|
605
667
|
);
|
|
606
668
|
}
|
|
607
669
|
|
|
608
|
-
getCurrentReadKey(): {
|
|
609
|
-
|
|
670
|
+
getCurrentReadKey(): {
|
|
671
|
+
secret: KeySecret | undefined;
|
|
672
|
+
id: KeyID;
|
|
673
|
+
} {
|
|
674
|
+
if (!this.verified) {
|
|
675
|
+
throw new Error(
|
|
676
|
+
"CoValueCore: getCurrentReadKey called on coValue without verified state",
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (this.verified.header.ruleset.type === "group") {
|
|
610
681
|
const content = expectGroup(this.getCurrentContent());
|
|
611
682
|
|
|
612
683
|
const currentKeyId = content.getCurrentReadKeyId();
|
|
@@ -621,9 +692,9 @@ export class CoValueCore {
|
|
|
621
692
|
secret: secret,
|
|
622
693
|
id: currentKeyId,
|
|
623
694
|
};
|
|
624
|
-
} else if (this.header.ruleset.type === "ownedByGroup") {
|
|
695
|
+
} else if (this.verified.header.ruleset.type === "ownedByGroup") {
|
|
625
696
|
return this.node
|
|
626
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
697
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
627
698
|
.getCurrentReadKey();
|
|
628
699
|
} else {
|
|
629
700
|
throw new Error(
|
|
@@ -649,19 +720,32 @@ export class CoValueCore {
|
|
|
649
720
|
}
|
|
650
721
|
|
|
651
722
|
getUncachedReadKey(keyID: KeyID): KeySecret | undefined {
|
|
652
|
-
if (this.
|
|
653
|
-
|
|
654
|
-
|
|
723
|
+
if (!this.verified) {
|
|
724
|
+
throw new Error(
|
|
725
|
+
"CoValueCore: getUncachedReadKey called on coValue without verified state",
|
|
655
726
|
);
|
|
727
|
+
}
|
|
656
728
|
|
|
729
|
+
if (this.verified.header.ruleset.type === "group") {
|
|
730
|
+
const content = expectGroup(
|
|
731
|
+
this.getCurrentContent({ ignorePrivateTransactions: true }), // to prevent recursion
|
|
732
|
+
);
|
|
657
733
|
const keyForEveryone = content.get(`${keyID}_for_everyone`);
|
|
658
|
-
if (keyForEveryone)
|
|
734
|
+
if (keyForEveryone) {
|
|
735
|
+
return keyForEveryone;
|
|
736
|
+
}
|
|
659
737
|
|
|
660
738
|
// Try to find key revelation for us
|
|
661
|
-
const
|
|
662
|
-
this.
|
|
663
|
-
|
|
664
|
-
|
|
739
|
+
const currentAgentOrAccountID = accountOrAgentIDfromSessionID(
|
|
740
|
+
this.node.currentSessionID,
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
// being careful here to avoid recursion
|
|
744
|
+
const lookupAccountOrAgentID = isAccountID(currentAgentOrAccountID)
|
|
745
|
+
? this.id === currentAgentOrAccountID
|
|
746
|
+
? this.crypto.getAgentID(this.node.agentSecret) // in accounts, the read key is revealed for the primitive agent
|
|
747
|
+
: currentAgentOrAccountID // current account ID
|
|
748
|
+
: currentAgentOrAccountID; // current agent ID
|
|
665
749
|
|
|
666
750
|
const lastReadyKeyEdit = content.lastEditAt(
|
|
667
751
|
`${keyID}_for_${lookupAccountOrAgentID}`,
|
|
@@ -675,7 +759,7 @@ export class CoValueCore {
|
|
|
675
759
|
|
|
676
760
|
const secret = this.crypto.unseal(
|
|
677
761
|
lastReadyKeyEdit.value,
|
|
678
|
-
this.node.
|
|
762
|
+
this.crypto.getAgentSealerSecret(this.node.agentSecret), // being careful here to avoid recursion
|
|
679
763
|
this.crypto.getAgentSealerID(revealerAgent),
|
|
680
764
|
{
|
|
681
765
|
in: this.id,
|
|
@@ -763,9 +847,9 @@ export class CoValueCore {
|
|
|
763
847
|
}
|
|
764
848
|
|
|
765
849
|
return undefined;
|
|
766
|
-
} else if (this.header.ruleset.type === "ownedByGroup") {
|
|
850
|
+
} else if (this.verified.header.ruleset.type === "ownedByGroup") {
|
|
767
851
|
return this.node
|
|
768
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
852
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
769
853
|
.getReadKey(keyID);
|
|
770
854
|
} else {
|
|
771
855
|
throw new Error(
|
|
@@ -797,143 +881,27 @@ export class CoValueCore {
|
|
|
797
881
|
}
|
|
798
882
|
|
|
799
883
|
getGroup(): RawGroup {
|
|
800
|
-
if (this.
|
|
884
|
+
if (!this.verified) {
|
|
885
|
+
throw new Error(
|
|
886
|
+
"CoValueCore: getGroup called on coValue without verified state",
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (this.verified.header.ruleset.type !== "ownedByGroup") {
|
|
801
891
|
throw new Error("Only values owned by groups have groups");
|
|
802
892
|
}
|
|
803
893
|
|
|
804
894
|
return expectGroup(
|
|
805
895
|
this.node
|
|
806
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
896
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
807
897
|
.getCurrentContent(),
|
|
808
898
|
);
|
|
809
899
|
}
|
|
810
900
|
|
|
811
901
|
getTx(txID: TransactionID): Transaction | undefined {
|
|
812
|
-
return this.
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
newContentSince(
|
|
816
|
-
knownState: CoValueKnownState | undefined,
|
|
817
|
-
): NewContentMessage[] | undefined {
|
|
818
|
-
const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
|
|
819
|
-
|
|
820
|
-
if (isKnownStateEmpty && this._cachedNewContentSinceEmpty) {
|
|
821
|
-
return this._cachedNewContentSinceEmpty;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
let currentPiece: NewContentMessage = {
|
|
825
|
-
action: "content",
|
|
826
|
-
id: this.id,
|
|
827
|
-
header: knownState?.header ? undefined : this.header,
|
|
828
|
-
priority: getPriorityFromHeader(this.header),
|
|
829
|
-
new: {},
|
|
830
|
-
};
|
|
831
|
-
|
|
832
|
-
const pieces = [currentPiece];
|
|
833
|
-
|
|
834
|
-
const sentState: CoValueKnownState["sessions"] = {};
|
|
835
|
-
|
|
836
|
-
let pieceSize = 0;
|
|
837
|
-
|
|
838
|
-
let sessionsTodoAgain: Set<SessionID> | undefined | "first" = "first";
|
|
839
|
-
|
|
840
|
-
while (sessionsTodoAgain === "first" || sessionsTodoAgain?.size || 0 > 0) {
|
|
841
|
-
if (sessionsTodoAgain === "first") {
|
|
842
|
-
sessionsTodoAgain = undefined;
|
|
843
|
-
}
|
|
844
|
-
const sessionsTodo = sessionsTodoAgain ?? this.sessionLogs.keys();
|
|
845
|
-
|
|
846
|
-
for (const sessionIDKey of sessionsTodo) {
|
|
847
|
-
const sessionID = sessionIDKey as SessionID;
|
|
848
|
-
const log = this.sessionLogs.get(sessionID)!;
|
|
849
|
-
const knownStateForSessionID = knownState?.sessions[sessionID];
|
|
850
|
-
const sentStateForSessionID = sentState[sessionID];
|
|
851
|
-
const nextKnownSignatureIdx = getNextKnownSignatureIdx(
|
|
852
|
-
log,
|
|
853
|
-
knownStateForSessionID,
|
|
854
|
-
sentStateForSessionID,
|
|
855
|
-
);
|
|
856
|
-
|
|
857
|
-
const firstNewTxIdx =
|
|
858
|
-
sentStateForSessionID ?? knownStateForSessionID ?? 0;
|
|
859
|
-
const afterLastNewTxIdx =
|
|
860
|
-
nextKnownSignatureIdx === undefined
|
|
861
|
-
? log.transactions.length
|
|
862
|
-
: nextKnownSignatureIdx + 1;
|
|
863
|
-
|
|
864
|
-
const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
|
|
865
|
-
|
|
866
|
-
if (nNewTx === 0) {
|
|
867
|
-
sessionsTodoAgain?.delete(sessionID);
|
|
868
|
-
continue;
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
if (afterLastNewTxIdx < log.transactions.length) {
|
|
872
|
-
if (!sessionsTodoAgain) {
|
|
873
|
-
sessionsTodoAgain = new Set();
|
|
874
|
-
}
|
|
875
|
-
sessionsTodoAgain.add(sessionID);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
const oldPieceSize = pieceSize;
|
|
879
|
-
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
880
|
-
const tx = log.transactions[txIdx]!;
|
|
881
|
-
pieceSize +=
|
|
882
|
-
tx.privacy === "private"
|
|
883
|
-
? tx.encryptedChanges.length
|
|
884
|
-
: tx.changes.length;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
|
|
888
|
-
currentPiece = {
|
|
889
|
-
action: "content",
|
|
890
|
-
id: this.id,
|
|
891
|
-
header: undefined,
|
|
892
|
-
new: {},
|
|
893
|
-
priority: getPriorityFromHeader(this.header),
|
|
894
|
-
};
|
|
895
|
-
pieces.push(currentPiece);
|
|
896
|
-
pieceSize = pieceSize - oldPieceSize;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
let sessionEntry = currentPiece.new[sessionID];
|
|
900
|
-
if (!sessionEntry) {
|
|
901
|
-
sessionEntry = {
|
|
902
|
-
after: sentStateForSessionID ?? knownStateForSessionID ?? 0,
|
|
903
|
-
newTransactions: [],
|
|
904
|
-
lastSignature: "WILL_BE_REPLACED" as Signature,
|
|
905
|
-
};
|
|
906
|
-
currentPiece.new[sessionID] = sessionEntry;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
910
|
-
const tx = log.transactions[txIdx]!;
|
|
911
|
-
sessionEntry.newTransactions.push(tx);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
sessionEntry.lastSignature =
|
|
915
|
-
nextKnownSignatureIdx === undefined
|
|
916
|
-
? log.lastSignature!
|
|
917
|
-
: log.signatureAfter[nextKnownSignatureIdx]!;
|
|
918
|
-
|
|
919
|
-
sentState[sessionID] =
|
|
920
|
-
(sentStateForSessionID ?? knownStateForSessionID ?? 0) + nNewTx;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
const piecesWithContent = pieces.filter(
|
|
925
|
-
(piece) => Object.keys(piece.new).length > 0 || piece.header,
|
|
926
|
-
);
|
|
927
|
-
|
|
928
|
-
if (piecesWithContent.length === 0) {
|
|
929
|
-
return undefined;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
if (isKnownStateEmpty) {
|
|
933
|
-
this._cachedNewContentSinceEmpty = piecesWithContent;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
return piecesWithContent;
|
|
902
|
+
return this.verified?.sessions.get(txID.sessionID)?.transactions[
|
|
903
|
+
txID.txIndex
|
|
904
|
+
];
|
|
937
905
|
}
|
|
938
906
|
|
|
939
907
|
getDependedOnCoValues(): RawCoID[] {
|
|
@@ -948,13 +916,17 @@ export class CoValueCore {
|
|
|
948
916
|
|
|
949
917
|
/** @internal */
|
|
950
918
|
getDependedOnCoValuesUncached(): RawCoID[] {
|
|
951
|
-
|
|
919
|
+
if (!this.verified) {
|
|
920
|
+
return [];
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
return this.verified.header.ruleset.type === "group"
|
|
952
924
|
? getGroupDependentKeyList(expectGroup(this.getCurrentContent()).keys())
|
|
953
|
-
: this.header.ruleset.type === "ownedByGroup"
|
|
925
|
+
: this.verified.header.ruleset.type === "ownedByGroup"
|
|
954
926
|
? [
|
|
955
|
-
this.header.ruleset.group,
|
|
927
|
+
this.verified.header.ruleset.group,
|
|
956
928
|
...new Set(
|
|
957
|
-
[...this.
|
|
929
|
+
[...this.verified.sessions.keys()]
|
|
958
930
|
.map((sessionID) =>
|
|
959
931
|
accountOrAgentIDfromSessionID(sessionID as SessionID),
|
|
960
932
|
)
|
|
@@ -972,19 +944,97 @@ export class CoValueCore {
|
|
|
972
944
|
}) {
|
|
973
945
|
return this.node.syncManager.waitForSync(this.id, options?.timeout);
|
|
974
946
|
}
|
|
975
|
-
}
|
|
976
947
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
948
|
+
async loadFromPeers(peers: PeerState[]) {
|
|
949
|
+
if (peers.length === 0) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const peersToActuallyLoadFrom = [];
|
|
954
|
+
for (const peer of peers) {
|
|
955
|
+
const currentState = this.peers.get(peer.id);
|
|
956
|
+
|
|
957
|
+
if (
|
|
958
|
+
currentState?.type === "available" ||
|
|
959
|
+
currentState?.type === "pending"
|
|
960
|
+
) {
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (currentState?.type === "errored") {
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (currentState?.type === "unavailable") {
|
|
969
|
+
if (peer.shouldRetryUnavailableCoValues()) {
|
|
970
|
+
this.markPending(peer.id);
|
|
971
|
+
peersToActuallyLoadFrom.push(peer);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (!currentState || currentState?.type === "unknown") {
|
|
978
|
+
this.markPending(peer.id);
|
|
979
|
+
peersToActuallyLoadFrom.push(peer);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
for (const peer of peersToActuallyLoadFrom) {
|
|
984
|
+
if (peer.closed) {
|
|
985
|
+
this.markNotFoundInPeer(peer.id);
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
peer.pushOutgoingMessage({
|
|
990
|
+
action: "load",
|
|
991
|
+
...this.knownState(),
|
|
992
|
+
});
|
|
993
|
+
peer.trackLoadRequestSent(this.id);
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Use a very long timeout for storage peers, because under pressure
|
|
997
|
+
* they may take a long time to consume the messages queue
|
|
998
|
+
*
|
|
999
|
+
* TODO: Track errors on storage and do not rely on timeout
|
|
1000
|
+
*/
|
|
1001
|
+
const timeoutDuration =
|
|
1002
|
+
peer.role === "storage"
|
|
1003
|
+
? CO_VALUE_LOADING_CONFIG.TIMEOUT * 10
|
|
1004
|
+
: CO_VALUE_LOADING_CONFIG.TIMEOUT;
|
|
1005
|
+
|
|
1006
|
+
const waitingForPeer = new Promise<void>((resolve) => {
|
|
1007
|
+
const markNotFound = () => {
|
|
1008
|
+
if (this.peers.get(peer.id)?.type === "pending") {
|
|
1009
|
+
this.markNotFoundInPeer(peer.id);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
const timeout = setTimeout(markNotFound, timeoutDuration);
|
|
1014
|
+
const removeCloseListener = peer.addCloseListener(markNotFound);
|
|
1015
|
+
|
|
1016
|
+
const listener = (state: CoValueCore) => {
|
|
1017
|
+
const peerState = state.peers.get(peer.id);
|
|
1018
|
+
if (
|
|
1019
|
+
state.isAvailable() || // might have become available from another peer e.g. through handleNewContent
|
|
1020
|
+
peerState?.type === "available" ||
|
|
1021
|
+
peerState?.type === "errored" ||
|
|
1022
|
+
peerState?.type === "unavailable"
|
|
1023
|
+
) {
|
|
1024
|
+
this.listeners.delete(listener);
|
|
1025
|
+
removeCloseListener();
|
|
1026
|
+
clearTimeout(timeout);
|
|
1027
|
+
resolve();
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
this.listeners.add(listener);
|
|
1032
|
+
listener(this);
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
await waitingForPeer;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
988
1038
|
}
|
|
989
1039
|
|
|
990
1040
|
export type InvalidHashError = {
|
|
@@ -1002,7 +1052,13 @@ export type InvalidSignatureError = {
|
|
|
1002
1052
|
signerID: SignerID;
|
|
1003
1053
|
};
|
|
1004
1054
|
|
|
1055
|
+
export type TriedToAddTransactionsWithoutVerifiedStateErrpr = {
|
|
1056
|
+
type: "TriedToAddTransactionsWithoutVerifiedState";
|
|
1057
|
+
id: RawCoID;
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1005
1060
|
export type TryAddTransactionsError =
|
|
1061
|
+
| TriedToAddTransactionsWithoutVerifiedStateErrpr
|
|
1006
1062
|
| ResolveAccountAgentError
|
|
1007
1063
|
| InvalidHashError
|
|
1008
1064
|
| InvalidSignatureError;
|