cojson 0.13.16 → 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 +16 -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 +6 -4
- package/dist/coValue.d.ts.map +1 -1
- package/dist/coValue.js +5 -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 +10 -6
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +41 -15
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +4 -3
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +5 -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 +5 -5
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/coStream.d.ts +5 -4
- package/dist/coValues/coStream.d.ts.map +1 -1
- package/dist/coValues/coStream.js +5 -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 +4 -3
- 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 +153 -177
- 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 +133 -13
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coMap.test.js +43 -14
- 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 +49 -18
- 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/coValueCoreLoadingState.test.js +227 -0
- 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 +60 -2
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +70 -10
- 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 +14 -8
- package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +470 -413
- package/src/coValueCore/verifiedState.ts +376 -0
- package/src/coValues/account.ts +20 -25
- package/src/coValues/coList.ts +63 -29
- package/src/coValues/coMap.ts +13 -6
- package/src/coValues/coPlainText.ts +10 -8
- package/src/coValues/coStream.ts +12 -7
- package/src/coValues/group.ts +50 -28
- package/src/coreToCoValue.ts +14 -15
- package/src/exports.ts +9 -7
- package/src/localNode.ts +248 -283
- package/src/permissions.ts +18 -12
- package/src/priority.ts +1 -1
- package/src/sync.ts +96 -63
- package/src/tests/coList.test.ts +200 -12
- package/src/tests/coMap.test.ts +65 -14
- package/src/tests/coPlainText.test.ts +12 -9
- package/src/tests/coStream.test.ts +80 -17
- package/src/tests/coValueCore.test.ts +30 -27
- package/src/tests/coValueCoreLoadingState.test.ts +337 -0
- 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 +79 -2
- package/src/tests/sync.mesh.test.ts +89 -9
- 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 -141
- 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 -228
- 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 +0 -344
- package/dist/tests/coValueState.test.js.map +0 -1
- package/src/CoValuesStore.ts +0 -41
- package/src/coValueState.ts +0 -300
- package/src/tests/coValueState.test.ts +0 -525
|
@@ -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;
|
|
@@ -581,6 +643,7 @@ export class CoValueCore {
|
|
|
581
643
|
|
|
582
644
|
getValidSortedTransactions(options?: {
|
|
583
645
|
ignorePrivateTransactions: boolean;
|
|
646
|
+
knownTransactions: CoValueKnownState["sessions"];
|
|
584
647
|
}): DecryptedTransaction[] {
|
|
585
648
|
const allTransactions = this.getValidTransactions(options);
|
|
586
649
|
|
|
@@ -604,8 +667,17 @@ export class CoValueCore {
|
|
|
604
667
|
);
|
|
605
668
|
}
|
|
606
669
|
|
|
607
|
-
getCurrentReadKey(): {
|
|
608
|
-
|
|
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") {
|
|
609
681
|
const content = expectGroup(this.getCurrentContent());
|
|
610
682
|
|
|
611
683
|
const currentKeyId = content.getCurrentReadKeyId();
|
|
@@ -620,9 +692,9 @@ export class CoValueCore {
|
|
|
620
692
|
secret: secret,
|
|
621
693
|
id: currentKeyId,
|
|
622
694
|
};
|
|
623
|
-
} else if (this.header.ruleset.type === "ownedByGroup") {
|
|
695
|
+
} else if (this.verified.header.ruleset.type === "ownedByGroup") {
|
|
624
696
|
return this.node
|
|
625
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
697
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
626
698
|
.getCurrentReadKey();
|
|
627
699
|
} else {
|
|
628
700
|
throw new Error(
|
|
@@ -648,19 +720,32 @@ export class CoValueCore {
|
|
|
648
720
|
}
|
|
649
721
|
|
|
650
722
|
getUncachedReadKey(keyID: KeyID): KeySecret | undefined {
|
|
651
|
-
if (this.
|
|
652
|
-
|
|
653
|
-
|
|
723
|
+
if (!this.verified) {
|
|
724
|
+
throw new Error(
|
|
725
|
+
"CoValueCore: getUncachedReadKey called on coValue without verified state",
|
|
654
726
|
);
|
|
727
|
+
}
|
|
655
728
|
|
|
729
|
+
if (this.verified.header.ruleset.type === "group") {
|
|
730
|
+
const content = expectGroup(
|
|
731
|
+
this.getCurrentContent({ ignorePrivateTransactions: true }), // to prevent recursion
|
|
732
|
+
);
|
|
656
733
|
const keyForEveryone = content.get(`${keyID}_for_everyone`);
|
|
657
|
-
if (keyForEveryone)
|
|
734
|
+
if (keyForEveryone) {
|
|
735
|
+
return keyForEveryone;
|
|
736
|
+
}
|
|
658
737
|
|
|
659
738
|
// Try to find key revelation for us
|
|
660
|
-
const
|
|
661
|
-
this.
|
|
662
|
-
|
|
663
|
-
|
|
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
|
|
664
749
|
|
|
665
750
|
const lastReadyKeyEdit = content.lastEditAt(
|
|
666
751
|
`${keyID}_for_${lookupAccountOrAgentID}`,
|
|
@@ -674,7 +759,7 @@ export class CoValueCore {
|
|
|
674
759
|
|
|
675
760
|
const secret = this.crypto.unseal(
|
|
676
761
|
lastReadyKeyEdit.value,
|
|
677
|
-
this.node.
|
|
762
|
+
this.crypto.getAgentSealerSecret(this.node.agentSecret), // being careful here to avoid recursion
|
|
678
763
|
this.crypto.getAgentSealerID(revealerAgent),
|
|
679
764
|
{
|
|
680
765
|
in: this.id,
|
|
@@ -762,9 +847,9 @@ export class CoValueCore {
|
|
|
762
847
|
}
|
|
763
848
|
|
|
764
849
|
return undefined;
|
|
765
|
-
} else if (this.header.ruleset.type === "ownedByGroup") {
|
|
850
|
+
} else if (this.verified.header.ruleset.type === "ownedByGroup") {
|
|
766
851
|
return this.node
|
|
767
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
852
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
768
853
|
.getReadKey(keyID);
|
|
769
854
|
} else {
|
|
770
855
|
throw new Error(
|
|
@@ -796,143 +881,27 @@ export class CoValueCore {
|
|
|
796
881
|
}
|
|
797
882
|
|
|
798
883
|
getGroup(): RawGroup {
|
|
799
|
-
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") {
|
|
800
891
|
throw new Error("Only values owned by groups have groups");
|
|
801
892
|
}
|
|
802
893
|
|
|
803
894
|
return expectGroup(
|
|
804
895
|
this.node
|
|
805
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
896
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
806
897
|
.getCurrentContent(),
|
|
807
898
|
);
|
|
808
899
|
}
|
|
809
900
|
|
|
810
901
|
getTx(txID: TransactionID): Transaction | undefined {
|
|
811
|
-
return this.
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
newContentSince(
|
|
815
|
-
knownState: CoValueKnownState | undefined,
|
|
816
|
-
): NewContentMessage[] | undefined {
|
|
817
|
-
const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
|
|
818
|
-
|
|
819
|
-
if (isKnownStateEmpty && this._cachedNewContentSinceEmpty) {
|
|
820
|
-
return this._cachedNewContentSinceEmpty;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
let currentPiece: NewContentMessage = {
|
|
824
|
-
action: "content",
|
|
825
|
-
id: this.id,
|
|
826
|
-
header: knownState?.header ? undefined : this.header,
|
|
827
|
-
priority: getPriorityFromHeader(this.header),
|
|
828
|
-
new: {},
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
const pieces = [currentPiece];
|
|
832
|
-
|
|
833
|
-
const sentState: CoValueKnownState["sessions"] = {};
|
|
834
|
-
|
|
835
|
-
let pieceSize = 0;
|
|
836
|
-
|
|
837
|
-
let sessionsTodoAgain: Set<SessionID> | undefined | "first" = "first";
|
|
838
|
-
|
|
839
|
-
while (sessionsTodoAgain === "first" || sessionsTodoAgain?.size || 0 > 0) {
|
|
840
|
-
if (sessionsTodoAgain === "first") {
|
|
841
|
-
sessionsTodoAgain = undefined;
|
|
842
|
-
}
|
|
843
|
-
const sessionsTodo = sessionsTodoAgain ?? this.sessionLogs.keys();
|
|
844
|
-
|
|
845
|
-
for (const sessionIDKey of sessionsTodo) {
|
|
846
|
-
const sessionID = sessionIDKey as SessionID;
|
|
847
|
-
const log = this.sessionLogs.get(sessionID)!;
|
|
848
|
-
const knownStateForSessionID = knownState?.sessions[sessionID];
|
|
849
|
-
const sentStateForSessionID = sentState[sessionID];
|
|
850
|
-
const nextKnownSignatureIdx = getNextKnownSignatureIdx(
|
|
851
|
-
log,
|
|
852
|
-
knownStateForSessionID,
|
|
853
|
-
sentStateForSessionID,
|
|
854
|
-
);
|
|
855
|
-
|
|
856
|
-
const firstNewTxIdx =
|
|
857
|
-
sentStateForSessionID ?? knownStateForSessionID ?? 0;
|
|
858
|
-
const afterLastNewTxIdx =
|
|
859
|
-
nextKnownSignatureIdx === undefined
|
|
860
|
-
? log.transactions.length
|
|
861
|
-
: nextKnownSignatureIdx + 1;
|
|
862
|
-
|
|
863
|
-
const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
|
|
864
|
-
|
|
865
|
-
if (nNewTx === 0) {
|
|
866
|
-
sessionsTodoAgain?.delete(sessionID);
|
|
867
|
-
continue;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
if (afterLastNewTxIdx < log.transactions.length) {
|
|
871
|
-
if (!sessionsTodoAgain) {
|
|
872
|
-
sessionsTodoAgain = new Set();
|
|
873
|
-
}
|
|
874
|
-
sessionsTodoAgain.add(sessionID);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
const oldPieceSize = pieceSize;
|
|
878
|
-
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
879
|
-
const tx = log.transactions[txIdx]!;
|
|
880
|
-
pieceSize +=
|
|
881
|
-
tx.privacy === "private"
|
|
882
|
-
? tx.encryptedChanges.length
|
|
883
|
-
: tx.changes.length;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
|
|
887
|
-
currentPiece = {
|
|
888
|
-
action: "content",
|
|
889
|
-
id: this.id,
|
|
890
|
-
header: undefined,
|
|
891
|
-
new: {},
|
|
892
|
-
priority: getPriorityFromHeader(this.header),
|
|
893
|
-
};
|
|
894
|
-
pieces.push(currentPiece);
|
|
895
|
-
pieceSize = pieceSize - oldPieceSize;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
let sessionEntry = currentPiece.new[sessionID];
|
|
899
|
-
if (!sessionEntry) {
|
|
900
|
-
sessionEntry = {
|
|
901
|
-
after: sentStateForSessionID ?? knownStateForSessionID ?? 0,
|
|
902
|
-
newTransactions: [],
|
|
903
|
-
lastSignature: "WILL_BE_REPLACED" as Signature,
|
|
904
|
-
};
|
|
905
|
-
currentPiece.new[sessionID] = sessionEntry;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
909
|
-
const tx = log.transactions[txIdx]!;
|
|
910
|
-
sessionEntry.newTransactions.push(tx);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
sessionEntry.lastSignature =
|
|
914
|
-
nextKnownSignatureIdx === undefined
|
|
915
|
-
? log.lastSignature!
|
|
916
|
-
: log.signatureAfter[nextKnownSignatureIdx]!;
|
|
917
|
-
|
|
918
|
-
sentState[sessionID] =
|
|
919
|
-
(sentStateForSessionID ?? knownStateForSessionID ?? 0) + nNewTx;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
const piecesWithContent = pieces.filter(
|
|
924
|
-
(piece) => Object.keys(piece.new).length > 0 || piece.header,
|
|
925
|
-
);
|
|
926
|
-
|
|
927
|
-
if (piecesWithContent.length === 0) {
|
|
928
|
-
return undefined;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (isKnownStateEmpty) {
|
|
932
|
-
this._cachedNewContentSinceEmpty = piecesWithContent;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
return piecesWithContent;
|
|
902
|
+
return this.verified?.sessions.get(txID.sessionID)?.transactions[
|
|
903
|
+
txID.txIndex
|
|
904
|
+
];
|
|
936
905
|
}
|
|
937
906
|
|
|
938
907
|
getDependedOnCoValues(): RawCoID[] {
|
|
@@ -947,13 +916,17 @@ export class CoValueCore {
|
|
|
947
916
|
|
|
948
917
|
/** @internal */
|
|
949
918
|
getDependedOnCoValuesUncached(): RawCoID[] {
|
|
950
|
-
|
|
919
|
+
if (!this.verified) {
|
|
920
|
+
return [];
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
return this.verified.header.ruleset.type === "group"
|
|
951
924
|
? getGroupDependentKeyList(expectGroup(this.getCurrentContent()).keys())
|
|
952
|
-
: this.header.ruleset.type === "ownedByGroup"
|
|
925
|
+
: this.verified.header.ruleset.type === "ownedByGroup"
|
|
953
926
|
? [
|
|
954
|
-
this.header.ruleset.group,
|
|
927
|
+
this.verified.header.ruleset.group,
|
|
955
928
|
...new Set(
|
|
956
|
-
[...this.
|
|
929
|
+
[...this.verified.sessions.keys()]
|
|
957
930
|
.map((sessionID) =>
|
|
958
931
|
accountOrAgentIDfromSessionID(sessionID as SessionID),
|
|
959
932
|
)
|
|
@@ -971,19 +944,97 @@ export class CoValueCore {
|
|
|
971
944
|
}) {
|
|
972
945
|
return this.node.syncManager.waitForSync(this.id, options?.timeout);
|
|
973
946
|
}
|
|
974
|
-
}
|
|
975
947
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
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
|
+
}
|
|
987
1038
|
}
|
|
988
1039
|
|
|
989
1040
|
export type InvalidHashError = {
|
|
@@ -1001,7 +1052,13 @@ export type InvalidSignatureError = {
|
|
|
1001
1052
|
signerID: SignerID;
|
|
1002
1053
|
};
|
|
1003
1054
|
|
|
1055
|
+
export type TriedToAddTransactionsWithoutVerifiedStateErrpr = {
|
|
1056
|
+
type: "TriedToAddTransactionsWithoutVerifiedState";
|
|
1057
|
+
id: RawCoID;
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1004
1060
|
export type TryAddTransactionsError =
|
|
1061
|
+
| TriedToAddTransactionsWithoutVerifiedStateErrpr
|
|
1005
1062
|
| ResolveAccountAgentError
|
|
1006
1063
|
| InvalidHashError
|
|
1007
1064
|
| InvalidSignatureError;
|