cojson 0.13.17 → 0.13.20
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 +18 -0
- package/dist/PeerState.d.ts +4 -1
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +16 -36
- 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} +325 -253
- 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 +147 -173
- 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/streamUtils.d.ts +5 -5
- package/dist/streamUtils.d.ts.map +1 -1
- package/dist/streamUtils.js +5 -20
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.d.ts +8 -6
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +121 -74
- package/dist/sync.js.map +1 -1
- package/dist/tests/PeerState.test.js +0 -31
- package/dist/tests/PeerState.test.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +41 -6
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/account.test.js +16 -0
- package/dist/tests/account.test.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 +190 -0
- package/dist/tests/sync.auth.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +6 -6
- 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 +35 -17
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +103 -79
- 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 +19 -40
- package/src/SyncStateManager.ts +2 -3
- package/src/coValue.ts +11 -8
- package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +478 -422
- 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 +236 -275
- package/src/permissions.ts +18 -12
- package/src/priority.ts +1 -1
- package/src/streamUtils.ts +7 -34
- package/src/sync.ts +146 -84
- package/src/tests/PeerState.test.ts +0 -37
- package/src/tests/SyncStateManager.test.ts +56 -6
- package/src/tests/account.test.ts +24 -0
- 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 -69
- 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 +246 -0
- package/src/tests/sync.load.test.ts +7 -6
- 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 +143 -96
- 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,10 @@
|
|
|
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 { ControlledAccountOrAgent, RawAccountID } from "../coValues/account.js";
|
|
6
|
+
import { RawGroup } from "../coValues/group.js";
|
|
7
|
+
import { coreToCoValue } from "../coreToCoValue.js";
|
|
6
8
|
import {
|
|
7
9
|
CryptoProvider,
|
|
8
10
|
Encrypted,
|
|
@@ -12,7 +14,7 @@ import {
|
|
|
12
14
|
Signature,
|
|
13
15
|
SignerID,
|
|
14
16
|
StreamingHash,
|
|
15
|
-
} from "
|
|
17
|
+
} from "../crypto/crypto.js";
|
|
16
18
|
import {
|
|
17
19
|
RawCoID,
|
|
18
20
|
SessionID,
|
|
@@ -20,21 +22,20 @@ import {
|
|
|
20
22
|
getGroupDependentKeyList,
|
|
21
23
|
getParentGroupId,
|
|
22
24
|
isParentGroupReference,
|
|
23
|
-
} from "
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { LocalNode, ResolveAccountAgentError } from "
|
|
27
|
-
import { logger } from "
|
|
25
|
+
} from "../ids.js";
|
|
26
|
+
import { parseJSON, stableStringify } from "../jsonStringify.js";
|
|
27
|
+
import { JsonValue } from "../jsonValue.js";
|
|
28
|
+
import { LocalNode, ResolveAccountAgentError } from "../localNode.js";
|
|
29
|
+
import { logger } from "../logger.js";
|
|
28
30
|
import {
|
|
29
|
-
PermissionsDef as RulesetDef,
|
|
30
31
|
determineValidTransactions,
|
|
31
32
|
isKeyForKeyField,
|
|
32
|
-
} from "
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
33
|
+
} from "../permissions.js";
|
|
34
|
+
import { CoValueKnownState, PeerID, emptyKnownState } from "../sync.js";
|
|
35
|
+
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
36
|
+
import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
37
|
+
import { isAccountID } from "../typeUtils/isAccountID.js";
|
|
38
|
+
import { CoValueHeader, Transaction, VerifiedState } from "./verifiedState.js";
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
|
|
@@ -45,17 +46,6 @@ import { isAccountID } from "./typeUtils/isAccountID.js";
|
|
|
45
46
|
**/
|
|
46
47
|
export const MAX_RECOMMENDED_TX_SIZE = 100 * 1024;
|
|
47
48
|
|
|
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
49
|
export function idforHeader(
|
|
60
50
|
header: CoValueHeader,
|
|
61
51
|
crypto: CryptoProvider,
|
|
@@ -64,29 +54,6 @@ export function idforHeader(
|
|
|
64
54
|
return `co_z${hash.slice("shortHash_z".length)}`;
|
|
65
55
|
}
|
|
66
56
|
|
|
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
57
|
export type DecryptedTransaction = {
|
|
91
58
|
txID: TransactionID;
|
|
92
59
|
changes: JsonValue[];
|
|
@@ -95,55 +62,246 @@ export type DecryptedTransaction = {
|
|
|
95
62
|
|
|
96
63
|
const readKeyCache = new WeakMap<CoValueCore, { [id: KeyID]: KeySecret }>();
|
|
97
64
|
|
|
65
|
+
export type AvailableCoValueCore = CoValueCore & { verified: VerifiedState };
|
|
66
|
+
|
|
67
|
+
export const CO_VALUE_LOADING_CONFIG = {
|
|
68
|
+
MAX_RETRIES: 2,
|
|
69
|
+
TIMEOUT: 30_000,
|
|
70
|
+
};
|
|
71
|
+
|
|
98
72
|
export class CoValueCore {
|
|
73
|
+
// context
|
|
74
|
+
readonly node: LocalNode;
|
|
75
|
+
private readonly crypto: CryptoProvider;
|
|
76
|
+
|
|
77
|
+
// state
|
|
99
78
|
id: RawCoID;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
79
|
+
private _verified: VerifiedState | null;
|
|
80
|
+
/** Holds the fundamental syncable content of a CoValue,
|
|
81
|
+
* consisting of the header (verified by hash -> RawCoID)
|
|
82
|
+
* and the sessions (verified by signature).
|
|
83
|
+
*
|
|
84
|
+
* It does not do any *validation* or *decryption* and as such doesn't
|
|
85
|
+
* depend on other CoValues or the LocalNode.
|
|
86
|
+
*
|
|
87
|
+
* `CoValueCore.verified` may be null when a CoValue is requested to be
|
|
88
|
+
* loaded but no content has been received from storage or peers yet.
|
|
89
|
+
* In this case, it acts as a centralised entry to keep track of peer loading
|
|
90
|
+
* state and to subscribe to its content when it does become available. */
|
|
91
|
+
get verified() {
|
|
92
|
+
return this._verified;
|
|
93
|
+
}
|
|
94
|
+
private readonly peers = new Map<
|
|
95
|
+
PeerID,
|
|
96
|
+
| { type: "unknown" | "pending" | "available" | "unavailable" }
|
|
97
|
+
| {
|
|
98
|
+
type: "errored";
|
|
99
|
+
error: TryAddTransactionsError;
|
|
100
|
+
}
|
|
101
|
+
>();
|
|
102
|
+
|
|
103
|
+
// cached state and listeners
|
|
104
|
+
private _cachedContent?: RawCoValue;
|
|
105
|
+
private readonly listeners: Set<
|
|
106
|
+
(core: CoValueCore, unsub: () => void) => void
|
|
107
|
+
> = new Set();
|
|
108
|
+
private readonly _decryptionCache: {
|
|
107
109
|
[key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
|
|
108
110
|
} = {};
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
_cachedNewContentSinceEmpty?: NewContentMessage[] | undefined;
|
|
112
|
-
_currentAsyncAddTransaction?: Promise<void>;
|
|
111
|
+
private _cachedDependentOn?: RawCoID[];
|
|
112
|
+
private counter: UpDownCounter;
|
|
113
113
|
|
|
114
|
-
constructor(
|
|
115
|
-
header: CoValueHeader,
|
|
114
|
+
private constructor(
|
|
115
|
+
init: { header: CoValueHeader } | { id: RawCoID },
|
|
116
116
|
node: LocalNode,
|
|
117
|
-
internalInitSessions: Map<SessionID, SessionLog> = new Map(),
|
|
118
117
|
) {
|
|
119
118
|
this.crypto = node.crypto;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
if ("header" in init) {
|
|
120
|
+
this.id = idforHeader(init.header, node.crypto);
|
|
121
|
+
this._verified = new VerifiedState(
|
|
122
|
+
this.id,
|
|
123
|
+
node.crypto,
|
|
124
|
+
init.header,
|
|
125
|
+
new Map(),
|
|
126
|
+
);
|
|
127
|
+
} else {
|
|
128
|
+
this.id = init.id;
|
|
129
|
+
this._verified = null;
|
|
130
|
+
}
|
|
123
131
|
this.node = node;
|
|
132
|
+
|
|
133
|
+
this.counter = metrics
|
|
134
|
+
.getMeter("cojson")
|
|
135
|
+
.createUpDownCounter("jazz.covalues.loaded", {
|
|
136
|
+
description: "The number of covalues in the system",
|
|
137
|
+
unit: "covalue",
|
|
138
|
+
valueType: ValueType.INT,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
this.updateCounter(null);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static fromID(id: RawCoID, node: LocalNode): CoValueCore {
|
|
145
|
+
return new CoValueCore({ id }, node);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static fromHeader(
|
|
149
|
+
header: CoValueHeader,
|
|
150
|
+
node: LocalNode,
|
|
151
|
+
): AvailableCoValueCore {
|
|
152
|
+
return new CoValueCore({ header }, node) as AvailableCoValueCore;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get loadingState() {
|
|
156
|
+
if (this.verified) {
|
|
157
|
+
return "available";
|
|
158
|
+
} else if (this.peers.size === 0) {
|
|
159
|
+
return "unknown";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const peer of this.peers.values()) {
|
|
163
|
+
if (peer.type === "pending") {
|
|
164
|
+
return "loading";
|
|
165
|
+
} else if (peer.type === "unknown") {
|
|
166
|
+
return "unknown";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return "unavailable";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
isAvailable(): this is AvailableCoValueCore {
|
|
174
|
+
return !!this.verified;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
isErroredInPeer(peerId: PeerID) {
|
|
178
|
+
return this.peers.get(peerId)?.type === "errored";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
waitForAvailableOrUnavailable(): Promise<CoValueCore> {
|
|
182
|
+
return new Promise<CoValueCore>((resolve) => {
|
|
183
|
+
const listener = (core: CoValueCore) => {
|
|
184
|
+
if (core.isAvailable() || core.loadingState === "unavailable") {
|
|
185
|
+
resolve(core);
|
|
186
|
+
this.listeners.delete(listener);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
this.listeners.add(listener);
|
|
191
|
+
listener(this);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
getStateForPeer(peerId: PeerID) {
|
|
196
|
+
return this.peers.get(peerId);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private updateCounter(previousState: string | null) {
|
|
200
|
+
const newState = this.loadingState;
|
|
201
|
+
|
|
202
|
+
if (previousState !== newState) {
|
|
203
|
+
if (previousState) {
|
|
204
|
+
this.counter.add(-1, { state: previousState });
|
|
205
|
+
}
|
|
206
|
+
this.counter.add(1, { state: newState });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
markNotFoundInPeer(peerId: PeerID) {
|
|
211
|
+
const previousState = this.loadingState;
|
|
212
|
+
this.peers.set(peerId, { type: "unavailable" });
|
|
213
|
+
this.updateCounter(previousState);
|
|
214
|
+
this.notifyUpdate("immediate");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// TODO: rename to "provided"
|
|
218
|
+
markAvailable(header: CoValueHeader, fromPeerId: PeerID) {
|
|
219
|
+
const previousState = this.loadingState;
|
|
220
|
+
|
|
221
|
+
if (this._verified?.sessions.size) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
"CoValueCore: markAvailable called on coValue with verified sessions present!",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
this._verified = new VerifiedState(
|
|
227
|
+
this.id,
|
|
228
|
+
this.node.crypto,
|
|
229
|
+
header,
|
|
230
|
+
new Map(),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
this.peers.set(fromPeerId, { type: "available" });
|
|
234
|
+
this.updateCounter(previousState);
|
|
235
|
+
this.notifyUpdate("immediate");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
internalMarkMagicallyAvailable(
|
|
239
|
+
verified: VerifiedState,
|
|
240
|
+
{ forceOverwrite = false }: { forceOverwrite?: boolean } = {},
|
|
241
|
+
) {
|
|
242
|
+
const previousState = this.loadingState;
|
|
243
|
+
this.internalShamefullyCloneVerifiedStateFrom(verified, {
|
|
244
|
+
forceOverwrite,
|
|
245
|
+
});
|
|
246
|
+
this.updateCounter(previousState);
|
|
247
|
+
this.notifyUpdate("immediate");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
markErrored(peerId: PeerID, error: TryAddTransactionsError) {
|
|
251
|
+
const previousState = this.loadingState;
|
|
252
|
+
this.peers.set(peerId, { type: "errored", error });
|
|
253
|
+
this.updateCounter(previousState);
|
|
254
|
+
this.notifyUpdate("immediate");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private markPending(peerId: PeerID) {
|
|
258
|
+
const previousState = this.loadingState;
|
|
259
|
+
this.peers.set(peerId, { type: "pending" });
|
|
260
|
+
this.updateCounter(previousState);
|
|
261
|
+
this.notifyUpdate("immediate");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
internalShamefullyCloneVerifiedStateFrom(
|
|
265
|
+
state: VerifiedState,
|
|
266
|
+
{ forceOverwrite = false }: { forceOverwrite?: boolean } = {},
|
|
267
|
+
) {
|
|
268
|
+
if (!forceOverwrite && this._verified?.sessions.size) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
"CoValueCore: internalShamefullyCloneVerifiedStateFrom called on coValue with verified sessions present!",
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
this._verified = state.clone();
|
|
274
|
+
this._cachedContent = undefined;
|
|
275
|
+
this._cachedDependentOn = undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
internalShamefullyResetCachedContent() {
|
|
279
|
+
this._cachedContent = undefined;
|
|
280
|
+
this._cachedDependentOn = undefined;
|
|
124
281
|
}
|
|
125
282
|
|
|
126
283
|
groupInvalidationSubscription?: () => void;
|
|
127
284
|
|
|
128
285
|
subscribeToGroupInvalidation() {
|
|
286
|
+
if (!this.verified) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
129
290
|
if (this.groupInvalidationSubscription) {
|
|
130
291
|
return;
|
|
131
292
|
}
|
|
132
293
|
|
|
133
|
-
const header = this.header;
|
|
294
|
+
const header = this.verified.header;
|
|
134
295
|
|
|
135
296
|
if (header.ruleset.type == "ownedByGroup") {
|
|
136
297
|
const groupId = header.ruleset.group;
|
|
137
|
-
const entry = this.node.
|
|
298
|
+
const entry = this.node.getCoValue(groupId);
|
|
138
299
|
|
|
139
300
|
if (entry.isAvailable()) {
|
|
140
|
-
this.groupInvalidationSubscription = entry.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
},
|
|
145
|
-
false,
|
|
146
|
-
);
|
|
301
|
+
this.groupInvalidationSubscription = entry.subscribe((_groupUpdate) => {
|
|
302
|
+
this._cachedContent = undefined;
|
|
303
|
+
this.notifyUpdate("immediate");
|
|
304
|
+
}, false);
|
|
147
305
|
} else {
|
|
148
306
|
logger.error("CoValueCore: Owner group not available", {
|
|
149
307
|
id: this.id,
|
|
@@ -153,64 +311,47 @@ export class CoValueCore {
|
|
|
153
311
|
}
|
|
154
312
|
}
|
|
155
313
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
account: ControlledAccountOrAgent,
|
|
162
|
-
currentSessionID: SessionID,
|
|
163
|
-
): CoValueCore {
|
|
164
|
-
const newNode = this.node.testWithDifferentAccount(
|
|
165
|
-
account,
|
|
166
|
-
currentSessionID,
|
|
314
|
+
contentInClonedNodeWithDifferentAccount(
|
|
315
|
+
controlledAccountOrAgent: ControlledAccountOrAgent,
|
|
316
|
+
): RawCoValue {
|
|
317
|
+
const newNode = this.node.cloneWithDifferentAccount(
|
|
318
|
+
controlledAccountOrAgent,
|
|
167
319
|
);
|
|
168
320
|
|
|
169
|
-
return newNode.expectCoValueLoaded(this.id);
|
|
321
|
+
return newNode.expectCoValueLoaded(this.id).getCurrentContent();
|
|
170
322
|
}
|
|
171
323
|
|
|
172
324
|
knownState(): CoValueKnownState {
|
|
173
|
-
if (this.
|
|
174
|
-
return this.
|
|
325
|
+
if (this.isAvailable()) {
|
|
326
|
+
return this.verified.knownState();
|
|
175
327
|
} else {
|
|
176
|
-
|
|
177
|
-
this._cachedKnownState = knownState;
|
|
178
|
-
return knownState;
|
|
328
|
+
return emptyKnownState(this.id);
|
|
179
329
|
}
|
|
180
330
|
}
|
|
181
331
|
|
|
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
332
|
get meta(): JsonValue {
|
|
198
|
-
return this.header
|
|
333
|
+
return this.verified?.header.meta ?? null;
|
|
199
334
|
}
|
|
200
335
|
|
|
201
336
|
nextTransactionID(): TransactionID {
|
|
337
|
+
if (!this.verified) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
"CoValueCore: nextTransactionID called on coValue without verified state",
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
202
343
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
203
344
|
const sessionID =
|
|
204
|
-
this.header.meta?.type === "account"
|
|
345
|
+
this.verified.header.meta?.type === "account"
|
|
205
346
|
? (this.node.currentSessionID.replace(
|
|
206
|
-
this.node.
|
|
207
|
-
this.node.
|
|
347
|
+
this.node.getCurrentAgent().id,
|
|
348
|
+
this.node.getCurrentAgent().currentAgentID(),
|
|
208
349
|
) as SessionID)
|
|
209
350
|
: this.node.currentSessionID;
|
|
210
351
|
|
|
211
352
|
return {
|
|
212
353
|
sessionID,
|
|
213
|
-
txIndex: this.
|
|
354
|
+
txIndex: this.verified.sessions.get(sessionID)?.transactions.length || 0,
|
|
214
355
|
};
|
|
215
356
|
}
|
|
216
357
|
|
|
@@ -219,6 +360,7 @@ export class CoValueCore {
|
|
|
219
360
|
newTransactions: Transaction[],
|
|
220
361
|
givenExpectedNewHash: Hash | undefined,
|
|
221
362
|
newSignature: Signature,
|
|
363
|
+
notifyMode: "immediate" | "deferred",
|
|
222
364
|
skipVerify: boolean = false,
|
|
223
365
|
givenNewStreamingHash?: StreamingHash,
|
|
224
366
|
): Result<true, TryAddTransactionsError> {
|
|
@@ -228,126 +370,45 @@ export class CoValueCore {
|
|
|
228
370
|
"Expected to know signer of transaction",
|
|
229
371
|
)
|
|
230
372
|
.andThen((agent) => {
|
|
373
|
+
if (!this.verified) {
|
|
374
|
+
return err({
|
|
375
|
+
type: "TriedToAddTransactionsWithoutVerifiedState",
|
|
376
|
+
id: this.id,
|
|
377
|
+
} satisfies TriedToAddTransactionsWithoutVerifiedStateErrpr);
|
|
378
|
+
}
|
|
379
|
+
|
|
231
380
|
const signerID = this.crypto.getAgentSignerID(agent);
|
|
232
381
|
|
|
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);
|
|
382
|
+
const result = this.verified.tryAddTransactions(
|
|
383
|
+
sessionID,
|
|
384
|
+
signerID,
|
|
385
|
+
newTransactions,
|
|
386
|
+
givenExpectedNewHash,
|
|
387
|
+
newSignature,
|
|
388
|
+
skipVerify,
|
|
389
|
+
givenNewStreamingHash,
|
|
390
|
+
);
|
|
249
391
|
|
|
392
|
+
if (result.isOk()) {
|
|
250
393
|
if (
|
|
251
|
-
|
|
252
|
-
|
|
394
|
+
this._cachedContent &&
|
|
395
|
+
"processNewTransactions" in this._cachedContent &&
|
|
396
|
+
typeof this._cachedContent.processNewTransactions === "function"
|
|
253
397
|
) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
expectedNewHash,
|
|
258
|
-
givenExpectedNewHash,
|
|
259
|
-
} satisfies InvalidHashError);
|
|
398
|
+
this._cachedContent.processNewTransactions();
|
|
399
|
+
} else {
|
|
400
|
+
this._cachedContent = undefined;
|
|
260
401
|
}
|
|
261
402
|
|
|
262
|
-
|
|
263
|
-
return err({
|
|
264
|
-
type: "InvalidSignature",
|
|
265
|
-
id: this.id,
|
|
266
|
-
newSignature,
|
|
267
|
-
sessionID,
|
|
268
|
-
signerID,
|
|
269
|
-
} satisfies InvalidSignatureError);
|
|
270
|
-
}
|
|
403
|
+
this._cachedDependentOn = undefined;
|
|
271
404
|
|
|
272
|
-
this.
|
|
273
|
-
sessionID,
|
|
274
|
-
newTransactions,
|
|
275
|
-
newSignature,
|
|
276
|
-
expectedNewHash,
|
|
277
|
-
newStreamingHash,
|
|
278
|
-
"immediate",
|
|
279
|
-
);
|
|
405
|
+
this.notifyUpdate(notifyMode);
|
|
280
406
|
}
|
|
281
407
|
|
|
282
|
-
return
|
|
408
|
+
return result;
|
|
283
409
|
});
|
|
284
410
|
}
|
|
285
411
|
|
|
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
412
|
deferredUpdates = 0;
|
|
352
413
|
nextDeferredNotify: Promise<void> | undefined;
|
|
353
414
|
|
|
@@ -357,10 +418,11 @@ export class CoValueCore {
|
|
|
357
418
|
}
|
|
358
419
|
|
|
359
420
|
if (notifyMode === "immediate") {
|
|
360
|
-
const content = this.getCurrentContent();
|
|
361
421
|
for (const listener of this.listeners) {
|
|
362
422
|
try {
|
|
363
|
-
listener(
|
|
423
|
+
listener(this, () => {
|
|
424
|
+
this.listeners.delete(listener);
|
|
425
|
+
});
|
|
364
426
|
} catch (e) {
|
|
365
427
|
logger.error("Error in listener for coValue " + this.id, { err: e });
|
|
366
428
|
}
|
|
@@ -371,10 +433,11 @@ export class CoValueCore {
|
|
|
371
433
|
setTimeout(() => {
|
|
372
434
|
this.nextDeferredNotify = undefined;
|
|
373
435
|
this.deferredUpdates = 0;
|
|
374
|
-
const content = this.getCurrentContent();
|
|
375
436
|
for (const listener of this.listeners) {
|
|
376
437
|
try {
|
|
377
|
-
listener(
|
|
438
|
+
listener(this, () => {
|
|
439
|
+
this.listeners.delete(listener);
|
|
440
|
+
});
|
|
378
441
|
} catch (e) {
|
|
379
442
|
logger.error("Error in listener for coValue " + this.id, {
|
|
380
443
|
err: e,
|
|
@@ -390,13 +453,15 @@ export class CoValueCore {
|
|
|
390
453
|
}
|
|
391
454
|
|
|
392
455
|
subscribe(
|
|
393
|
-
listener: (
|
|
456
|
+
listener: (core: CoValueCore, unsub: () => void) => void,
|
|
394
457
|
immediateInvoke = true,
|
|
395
458
|
): () => void {
|
|
396
459
|
this.listeners.add(listener);
|
|
397
460
|
|
|
398
461
|
if (immediateInvoke) {
|
|
399
|
-
listener(this
|
|
462
|
+
listener(this, () => {
|
|
463
|
+
this.listeners.delete(listener);
|
|
464
|
+
});
|
|
400
465
|
}
|
|
401
466
|
|
|
402
467
|
return () => {
|
|
@@ -404,28 +469,16 @@ export class CoValueCore {
|
|
|
404
469
|
};
|
|
405
470
|
}
|
|
406
471
|
|
|
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
472
|
makeTransaction(
|
|
426
473
|
changes: JsonValue[],
|
|
427
474
|
privacy: "private" | "trusting",
|
|
428
475
|
): boolean {
|
|
476
|
+
if (!this.verified) {
|
|
477
|
+
throw new Error(
|
|
478
|
+
"CoValueCore: makeTransaction called on coValue without verified state",
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
429
482
|
const madeAt = Date.now();
|
|
430
483
|
|
|
431
484
|
let transaction: Transaction;
|
|
@@ -460,20 +513,18 @@ export class CoValueCore {
|
|
|
460
513
|
|
|
461
514
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
462
515
|
const sessionID =
|
|
463
|
-
this.header.meta?.type === "account"
|
|
516
|
+
this.verified.header.meta?.type === "account"
|
|
464
517
|
? (this.node.currentSessionID.replace(
|
|
465
|
-
this.node.
|
|
466
|
-
this.node.
|
|
518
|
+
this.node.getCurrentAgent().id,
|
|
519
|
+
this.node.getCurrentAgent().currentAgentID(),
|
|
467
520
|
) as SessionID)
|
|
468
521
|
: this.node.currentSessionID;
|
|
469
522
|
|
|
470
|
-
const { expectedNewHash, newStreamingHash } =
|
|
471
|
-
sessionID,
|
|
472
|
-
[transaction],
|
|
473
|
-
);
|
|
523
|
+
const { expectedNewHash, newStreamingHash } =
|
|
524
|
+
this.verified.expectedNewHashAfter(sessionID, [transaction]);
|
|
474
525
|
|
|
475
526
|
const signature = this.crypto.sign(
|
|
476
|
-
this.node.
|
|
527
|
+
this.node.getCurrentAgent().currentSignerSecret(),
|
|
477
528
|
expectedNewHash,
|
|
478
529
|
);
|
|
479
530
|
|
|
@@ -482,6 +533,7 @@ export class CoValueCore {
|
|
|
482
533
|
[transaction],
|
|
483
534
|
expectedNewHash,
|
|
484
535
|
signature,
|
|
536
|
+
"immediate",
|
|
485
537
|
true,
|
|
486
538
|
newStreamingHash,
|
|
487
539
|
)._unsafeUnwrap({ withStackTrace: true });
|
|
@@ -497,13 +549,19 @@ export class CoValueCore {
|
|
|
497
549
|
getCurrentContent(options?: {
|
|
498
550
|
ignorePrivateTransactions: true;
|
|
499
551
|
}): RawCoValue {
|
|
552
|
+
if (!this.verified) {
|
|
553
|
+
throw new Error(
|
|
554
|
+
"CoValueCore: getCurrentContent called on coValue without verified state",
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
500
558
|
if (!options?.ignorePrivateTransactions && this._cachedContent) {
|
|
501
559
|
return this._cachedContent;
|
|
502
560
|
}
|
|
503
561
|
|
|
504
562
|
this.subscribeToGroupInvalidation();
|
|
505
563
|
|
|
506
|
-
const newContent = coreToCoValue(this, options);
|
|
564
|
+
const newContent = coreToCoValue(this as AvailableCoValueCore, options);
|
|
507
565
|
|
|
508
566
|
if (!options?.ignorePrivateTransactions) {
|
|
509
567
|
this._cachedContent = newContent;
|
|
@@ -594,19 +652,28 @@ export class CoValueCore {
|
|
|
594
652
|
a: Pick<DecryptedTransaction, "madeAt" | "txID">,
|
|
595
653
|
b: Pick<DecryptedTransaction, "madeAt" | "txID">,
|
|
596
654
|
) {
|
|
597
|
-
|
|
598
|
-
a.madeAt - b.madeAt
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
655
|
+
if (a.madeAt !== b.madeAt) {
|
|
656
|
+
return a.madeAt - b.madeAt;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (a.txID.sessionID === b.txID.sessionID) {
|
|
660
|
+
return a.txID.txIndex - b.txID.txIndex;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return 0;
|
|
606
664
|
}
|
|
607
665
|
|
|
608
|
-
getCurrentReadKey(): {
|
|
609
|
-
|
|
666
|
+
getCurrentReadKey(): {
|
|
667
|
+
secret: KeySecret | undefined;
|
|
668
|
+
id: KeyID;
|
|
669
|
+
} {
|
|
670
|
+
if (!this.verified) {
|
|
671
|
+
throw new Error(
|
|
672
|
+
"CoValueCore: getCurrentReadKey called on coValue without verified state",
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (this.verified.header.ruleset.type === "group") {
|
|
610
677
|
const content = expectGroup(this.getCurrentContent());
|
|
611
678
|
|
|
612
679
|
const currentKeyId = content.getCurrentReadKeyId();
|
|
@@ -621,9 +688,9 @@ export class CoValueCore {
|
|
|
621
688
|
secret: secret,
|
|
622
689
|
id: currentKeyId,
|
|
623
690
|
};
|
|
624
|
-
} else if (this.header.ruleset.type === "ownedByGroup") {
|
|
691
|
+
} else if (this.verified.header.ruleset.type === "ownedByGroup") {
|
|
625
692
|
return this.node
|
|
626
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
693
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
627
694
|
.getCurrentReadKey();
|
|
628
695
|
} else {
|
|
629
696
|
throw new Error(
|
|
@@ -649,19 +716,32 @@ export class CoValueCore {
|
|
|
649
716
|
}
|
|
650
717
|
|
|
651
718
|
getUncachedReadKey(keyID: KeyID): KeySecret | undefined {
|
|
652
|
-
if (this.
|
|
653
|
-
|
|
654
|
-
|
|
719
|
+
if (!this.verified) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
"CoValueCore: getUncachedReadKey called on coValue without verified state",
|
|
655
722
|
);
|
|
723
|
+
}
|
|
656
724
|
|
|
725
|
+
if (this.verified.header.ruleset.type === "group") {
|
|
726
|
+
const content = expectGroup(
|
|
727
|
+
this.getCurrentContent({ ignorePrivateTransactions: true }), // to prevent recursion
|
|
728
|
+
);
|
|
657
729
|
const keyForEveryone = content.get(`${keyID}_for_everyone`);
|
|
658
|
-
if (keyForEveryone)
|
|
730
|
+
if (keyForEveryone) {
|
|
731
|
+
return keyForEveryone;
|
|
732
|
+
}
|
|
659
733
|
|
|
660
734
|
// Try to find key revelation for us
|
|
661
|
-
const
|
|
662
|
-
this.
|
|
663
|
-
|
|
664
|
-
|
|
735
|
+
const currentAgentOrAccountID = accountOrAgentIDfromSessionID(
|
|
736
|
+
this.node.currentSessionID,
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
// being careful here to avoid recursion
|
|
740
|
+
const lookupAccountOrAgentID = isAccountID(currentAgentOrAccountID)
|
|
741
|
+
? this.id === currentAgentOrAccountID
|
|
742
|
+
? this.crypto.getAgentID(this.node.agentSecret) // in accounts, the read key is revealed for the primitive agent
|
|
743
|
+
: currentAgentOrAccountID // current account ID
|
|
744
|
+
: currentAgentOrAccountID; // current agent ID
|
|
665
745
|
|
|
666
746
|
const lastReadyKeyEdit = content.lastEditAt(
|
|
667
747
|
`${keyID}_for_${lookupAccountOrAgentID}`,
|
|
@@ -675,7 +755,7 @@ export class CoValueCore {
|
|
|
675
755
|
|
|
676
756
|
const secret = this.crypto.unseal(
|
|
677
757
|
lastReadyKeyEdit.value,
|
|
678
|
-
this.node.
|
|
758
|
+
this.crypto.getAgentSealerSecret(this.node.agentSecret), // being careful here to avoid recursion
|
|
679
759
|
this.crypto.getAgentSealerID(revealerAgent),
|
|
680
760
|
{
|
|
681
761
|
in: this.id,
|
|
@@ -763,9 +843,9 @@ export class CoValueCore {
|
|
|
763
843
|
}
|
|
764
844
|
|
|
765
845
|
return undefined;
|
|
766
|
-
} else if (this.header.ruleset.type === "ownedByGroup") {
|
|
846
|
+
} else if (this.verified.header.ruleset.type === "ownedByGroup") {
|
|
767
847
|
return this.node
|
|
768
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
848
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
769
849
|
.getReadKey(keyID);
|
|
770
850
|
} else {
|
|
771
851
|
throw new Error(
|
|
@@ -797,143 +877,27 @@ export class CoValueCore {
|
|
|
797
877
|
}
|
|
798
878
|
|
|
799
879
|
getGroup(): RawGroup {
|
|
800
|
-
if (this.
|
|
880
|
+
if (!this.verified) {
|
|
881
|
+
throw new Error(
|
|
882
|
+
"CoValueCore: getGroup called on coValue without verified state",
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (this.verified.header.ruleset.type !== "ownedByGroup") {
|
|
801
887
|
throw new Error("Only values owned by groups have groups");
|
|
802
888
|
}
|
|
803
889
|
|
|
804
890
|
return expectGroup(
|
|
805
891
|
this.node
|
|
806
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
892
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
807
893
|
.getCurrentContent(),
|
|
808
894
|
);
|
|
809
895
|
}
|
|
810
896
|
|
|
811
897
|
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;
|
|
898
|
+
return this.verified?.sessions.get(txID.sessionID)?.transactions[
|
|
899
|
+
txID.txIndex
|
|
900
|
+
];
|
|
937
901
|
}
|
|
938
902
|
|
|
939
903
|
getDependedOnCoValues(): RawCoID[] {
|
|
@@ -948,13 +912,17 @@ export class CoValueCore {
|
|
|
948
912
|
|
|
949
913
|
/** @internal */
|
|
950
914
|
getDependedOnCoValuesUncached(): RawCoID[] {
|
|
951
|
-
|
|
915
|
+
if (!this.verified) {
|
|
916
|
+
return [];
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return this.verified.header.ruleset.type === "group"
|
|
952
920
|
? getGroupDependentKeyList(expectGroup(this.getCurrentContent()).keys())
|
|
953
|
-
: this.header.ruleset.type === "ownedByGroup"
|
|
921
|
+
: this.verified.header.ruleset.type === "ownedByGroup"
|
|
954
922
|
? [
|
|
955
|
-
this.header.ruleset.group,
|
|
923
|
+
this.verified.header.ruleset.group,
|
|
956
924
|
...new Set(
|
|
957
|
-
[...this.
|
|
925
|
+
[...this.verified.sessions.keys()]
|
|
958
926
|
.map((sessionID) =>
|
|
959
927
|
accountOrAgentIDfromSessionID(sessionID as SessionID),
|
|
960
928
|
)
|
|
@@ -972,19 +940,101 @@ export class CoValueCore {
|
|
|
972
940
|
}) {
|
|
973
941
|
return this.node.syncManager.waitForSync(this.id, options?.timeout);
|
|
974
942
|
}
|
|
975
|
-
}
|
|
976
943
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
944
|
+
async loadFromPeers(peers: PeerState[]) {
|
|
945
|
+
if (peers.length === 0) {
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const peersToActuallyLoadFrom = [];
|
|
950
|
+
for (const peer of peers) {
|
|
951
|
+
const currentState = this.peers.get(peer.id);
|
|
952
|
+
|
|
953
|
+
if (
|
|
954
|
+
currentState?.type === "available" ||
|
|
955
|
+
currentState?.type === "pending"
|
|
956
|
+
) {
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (currentState?.type === "errored") {
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (currentState?.type === "unavailable") {
|
|
965
|
+
if (peer.shouldRetryUnavailableCoValues()) {
|
|
966
|
+
this.markPending(peer.id);
|
|
967
|
+
peersToActuallyLoadFrom.push(peer);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (!currentState || currentState?.type === "unknown") {
|
|
974
|
+
this.markPending(peer.id);
|
|
975
|
+
peersToActuallyLoadFrom.push(peer);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
for (const peer of peersToActuallyLoadFrom) {
|
|
980
|
+
if (peer.closed) {
|
|
981
|
+
this.markNotFoundInPeer(peer.id);
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
peer.pushOutgoingMessage({
|
|
986
|
+
action: "load",
|
|
987
|
+
...this.knownState(),
|
|
988
|
+
});
|
|
989
|
+
peer.trackLoadRequestSent(this.id);
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Use a very long timeout for storage peers, because under pressure
|
|
993
|
+
* they may take a long time to consume the messages queue
|
|
994
|
+
*
|
|
995
|
+
* TODO: Track errors on storage and do not rely on timeout
|
|
996
|
+
*/
|
|
997
|
+
const timeoutDuration =
|
|
998
|
+
peer.role === "storage"
|
|
999
|
+
? CO_VALUE_LOADING_CONFIG.TIMEOUT * 10
|
|
1000
|
+
: CO_VALUE_LOADING_CONFIG.TIMEOUT;
|
|
1001
|
+
|
|
1002
|
+
const waitingForPeer = new Promise<void>((resolve) => {
|
|
1003
|
+
const markNotFound = () => {
|
|
1004
|
+
if (this.peers.get(peer.id)?.type === "pending") {
|
|
1005
|
+
logger.warn("Timeout waiting for peer to load coValue", {
|
|
1006
|
+
id: this.id,
|
|
1007
|
+
peerID: peer.id,
|
|
1008
|
+
});
|
|
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;
|