cojson 0.8.11 → 0.8.16
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/CHANGELOG.md +94 -82
- package/dist/native/PeerKnownStates.js +1 -1
- package/dist/native/PeerKnownStates.js.map +1 -1
- package/dist/native/PeerState.js +4 -1
- package/dist/native/PeerState.js.map +1 -1
- package/dist/native/PriorityBasedMessageQueue.js +1 -10
- package/dist/native/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/native/base64url.js.map +1 -1
- package/dist/native/base64url.test.js +1 -1
- package/dist/native/base64url.test.js.map +1 -1
- package/dist/native/coValue.js.map +1 -1
- package/dist/native/coValueCore.js +141 -149
- package/dist/native/coValueCore.js.map +1 -1
- package/dist/native/coValueState.js.map +1 -1
- package/dist/native/coValues/account.js +6 -6
- package/dist/native/coValues/account.js.map +1 -1
- package/dist/native/coValues/coList.js +2 -3
- package/dist/native/coValues/coList.js.map +1 -1
- package/dist/native/coValues/coMap.js +1 -1
- package/dist/native/coValues/coMap.js.map +1 -1
- package/dist/native/coValues/coStream.js +3 -5
- package/dist/native/coValues/coStream.js.map +1 -1
- package/dist/native/coValues/group.js +11 -11
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/native/coreToCoValue.js +2 -2
- package/dist/native/coreToCoValue.js.map +1 -1
- package/dist/native/crypto/PureJSCrypto.js +4 -4
- package/dist/native/crypto/PureJSCrypto.js.map +1 -1
- package/dist/native/crypto/crypto.js.map +1 -1
- package/dist/native/exports.js +12 -12
- package/dist/native/exports.js.map +1 -1
- package/dist/native/ids.js.map +1 -1
- package/dist/native/jsonStringify.js.map +1 -1
- package/dist/native/localNode.js +6 -8
- package/dist/native/localNode.js.map +1 -1
- package/dist/native/permissions.js +4 -7
- package/dist/native/permissions.js.map +1 -1
- package/dist/native/priority.js.map +1 -1
- package/dist/native/storage/FileSystem.js.map +1 -1
- package/dist/native/storage/chunksAndKnownStates.js +2 -4
- package/dist/native/storage/chunksAndKnownStates.js.map +1 -1
- package/dist/native/storage/index.js +7 -16
- package/dist/native/storage/index.js.map +1 -1
- package/dist/native/streamUtils.js.map +1 -1
- package/dist/native/sync.js +6 -8
- package/dist/native/sync.js.map +1 -1
- package/dist/native/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
- package/dist/native/typeUtils/expectGroup.js.map +1 -1
- package/dist/native/typeUtils/isAccountID.js.map +1 -1
- package/dist/native/typeUtils/isCoValue.js +1 -1
- package/dist/native/typeUtils/isCoValue.js.map +1 -1
- package/dist/web/PeerKnownStates.js +1 -1
- package/dist/web/PeerKnownStates.js.map +1 -1
- package/dist/web/PeerState.js +4 -1
- package/dist/web/PeerState.js.map +1 -1
- package/dist/web/PriorityBasedMessageQueue.js +1 -10
- package/dist/web/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/web/base64url.js.map +1 -1
- package/dist/web/base64url.test.js +1 -1
- package/dist/web/base64url.test.js.map +1 -1
- package/dist/web/coValue.js.map +1 -1
- package/dist/web/coValueCore.js +141 -149
- package/dist/web/coValueCore.js.map +1 -1
- package/dist/web/coValueState.js.map +1 -1
- package/dist/web/coValues/account.js +6 -6
- package/dist/web/coValues/account.js.map +1 -1
- package/dist/web/coValues/coList.js +2 -3
- package/dist/web/coValues/coList.js.map +1 -1
- package/dist/web/coValues/coMap.js +1 -1
- package/dist/web/coValues/coMap.js.map +1 -1
- package/dist/web/coValues/coStream.js +3 -5
- package/dist/web/coValues/coStream.js.map +1 -1
- package/dist/web/coValues/group.js +11 -11
- package/dist/web/coValues/group.js.map +1 -1
- package/dist/web/coreToCoValue.js +2 -2
- package/dist/web/coreToCoValue.js.map +1 -1
- package/dist/web/crypto/PureJSCrypto.js +4 -4
- package/dist/web/crypto/PureJSCrypto.js.map +1 -1
- package/dist/web/crypto/WasmCrypto.js +5 -5
- package/dist/web/crypto/WasmCrypto.js.map +1 -1
- package/dist/web/crypto/crypto.js.map +1 -1
- package/dist/web/exports.js +12 -12
- package/dist/web/exports.js.map +1 -1
- package/dist/web/ids.js.map +1 -1
- package/dist/web/jsonStringify.js.map +1 -1
- package/dist/web/localNode.js +6 -8
- package/dist/web/localNode.js.map +1 -1
- package/dist/web/permissions.js +4 -7
- package/dist/web/permissions.js.map +1 -1
- package/dist/web/priority.js.map +1 -1
- package/dist/web/storage/FileSystem.js.map +1 -1
- package/dist/web/storage/chunksAndKnownStates.js +2 -4
- package/dist/web/storage/chunksAndKnownStates.js.map +1 -1
- package/dist/web/storage/index.js +7 -16
- package/dist/web/storage/index.js.map +1 -1
- package/dist/web/streamUtils.js.map +1 -1
- package/dist/web/sync.js +6 -8
- package/dist/web/sync.js.map +1 -1
- package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
- package/dist/web/typeUtils/expectGroup.js.map +1 -1
- package/dist/web/typeUtils/isAccountID.js.map +1 -1
- package/dist/web/typeUtils/isCoValue.js +1 -1
- package/dist/web/typeUtils/isCoValue.js.map +1 -1
- package/package.json +4 -14
- package/src/PeerKnownStates.ts +91 -89
- package/src/PeerState.ts +72 -69
- package/src/PriorityBasedMessageQueue.ts +42 -49
- package/src/base64url.test.ts +24 -24
- package/src/base64url.ts +44 -45
- package/src/coValue.ts +45 -45
- package/src/coValueCore.ts +746 -785
- package/src/coValueState.ts +82 -72
- package/src/coValues/account.ts +143 -150
- package/src/coValues/coList.ts +520 -522
- package/src/coValues/coMap.ts +283 -285
- package/src/coValues/coStream.ts +320 -324
- package/src/coValues/group.ts +306 -305
- package/src/coreToCoValue.ts +28 -31
- package/src/crypto/PureJSCrypto.ts +188 -194
- package/src/crypto/WasmCrypto.ts +236 -254
- package/src/crypto/crypto.ts +302 -309
- package/src/exports.ts +116 -116
- package/src/ids.ts +9 -9
- package/src/jsonStringify.ts +46 -46
- package/src/jsonValue.ts +24 -10
- package/src/localNode.ts +635 -660
- package/src/media.ts +3 -3
- package/src/permissions.ts +272 -278
- package/src/priority.ts +21 -19
- package/src/storage/FileSystem.ts +91 -99
- package/src/storage/chunksAndKnownStates.ts +110 -115
- package/src/storage/index.ts +466 -497
- package/src/streamUtils.ts +60 -60
- package/src/sync.ts +593 -615
- package/src/tests/PeerKnownStates.test.ts +38 -34
- package/src/tests/PeerState.test.ts +101 -64
- package/src/tests/PriorityBasedMessageQueue.test.ts +91 -91
- package/src/tests/account.test.ts +59 -59
- package/src/tests/coList.test.ts +65 -65
- package/src/tests/coMap.test.ts +137 -137
- package/src/tests/coStream.test.ts +254 -257
- package/src/tests/coValueCore.test.ts +153 -156
- package/src/tests/crypto.test.ts +136 -144
- package/src/tests/cryptoImpl.test.ts +205 -197
- package/src/tests/group.test.ts +24 -24
- package/src/tests/permissions.test.ts +1306 -1371
- package/src/tests/priority.test.ts +65 -82
- package/src/tests/sync.test.ts +1300 -1291
- package/src/tests/testUtils.ts +52 -53
- package/src/typeUtils/accountOrAgentIDfromSessionID.ts +4 -4
- package/src/typeUtils/expectGroup.ts +9 -9
- package/src/typeUtils/isAccountID.ts +1 -1
- package/src/typeUtils/isCoValue.ts +9 -9
- package/tsconfig.json +4 -6
- package/tsconfig.native.json +9 -11
- package/tsconfig.web.json +4 -10
- package/.eslintrc.cjs +0 -25
- package/.prettierrc.js +0 -9
package/src/coValueCore.ts
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
+
import { Result, err, ok } from "neverthrow";
|
|
1
2
|
import { AnyRawCoValue, RawCoValue } from "./coValue.js";
|
|
3
|
+
import { ControlledAccountOrAgent, RawAccountID } from "./coValues/account.js";
|
|
4
|
+
import { RawGroup } from "./coValues/group.js";
|
|
5
|
+
import { coreToCoValue } from "./coreToCoValue.js";
|
|
2
6
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
CryptoProvider,
|
|
8
|
+
Encrypted,
|
|
9
|
+
Hash,
|
|
10
|
+
KeyID,
|
|
11
|
+
KeySecret,
|
|
12
|
+
Signature,
|
|
13
|
+
SignerID,
|
|
14
|
+
StreamingHash,
|
|
11
15
|
} from "./crypto/crypto.js";
|
|
16
|
+
import { RawCoID, SessionID, TransactionID } from "./ids.js";
|
|
17
|
+
import { Stringified, parseJSON, stableStringify } from "./jsonStringify.js";
|
|
12
18
|
import { JsonObject, JsonValue } from "./jsonValue.js";
|
|
19
|
+
import { LocalNode, ResolveAccountAgentError } from "./localNode.js";
|
|
13
20
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
PermissionsDef as RulesetDef,
|
|
22
|
+
determineValidTransactions,
|
|
23
|
+
isKeyForKeyField,
|
|
17
24
|
} from "./permissions.js";
|
|
18
|
-
import {
|
|
19
|
-
import { LocalNode, ResolveAccountAgentError } from "./localNode.js";
|
|
25
|
+
import { getPriorityFromHeader } from "./priority.js";
|
|
20
26
|
import { CoValueKnownState, NewContentMessage } from "./sync.js";
|
|
21
|
-
import {
|
|
22
|
-
import { RawAccountID, ControlledAccountOrAgent } from "./coValues/account.js";
|
|
23
|
-
import { Stringified, parseJSON, stableStringify } from "./jsonStringify.js";
|
|
24
|
-
import { coreToCoValue } from "./coreToCoValue.js";
|
|
27
|
+
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
|
25
28
|
import { expectGroup } from "./typeUtils/expectGroup.js";
|
|
26
29
|
import { isAccountID } from "./typeUtils/isAccountID.js";
|
|
27
|
-
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
|
28
|
-
import { err, ok, Result } from "neverthrow";
|
|
29
|
-
import { getPriorityFromHeader } from "./priority.js";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
|
|
@@ -38,227 +38,221 @@ import { getPriorityFromHeader } from "./priority.js";
|
|
|
38
38
|
export const MAX_RECOMMENDED_TX_SIZE = 100 * 1024;
|
|
39
39
|
|
|
40
40
|
export type CoValueHeader = {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
type: AnyRawCoValue["type"];
|
|
42
|
+
ruleset: RulesetDef;
|
|
43
|
+
meta: JsonObject | null;
|
|
44
44
|
} & CoValueUniqueness;
|
|
45
45
|
|
|
46
46
|
export type CoValueUniqueness = {
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
uniqueness: JsonValue;
|
|
48
|
+
createdAt?: `2${string}` | null;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
export function idforHeader(
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
header: CoValueHeader,
|
|
53
|
+
crypto: CryptoProvider,
|
|
54
54
|
): RawCoID {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const hash = crypto.shortHash(header);
|
|
56
|
+
return `co_z${hash.slice("shortHash_z".length)}`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
type SessionLog = {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
transactions: Transaction[];
|
|
61
|
+
lastHash?: Hash;
|
|
62
|
+
streamingHash: StreamingHash;
|
|
63
|
+
signatureAfter: { [txIdx: number]: Signature | undefined };
|
|
64
|
+
lastSignature: Signature;
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
export type PrivateTransaction = {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
JsonValue[],
|
|
73
|
-
{ in: RawCoID; tx: TransactionID }
|
|
74
|
-
>;
|
|
68
|
+
privacy: "private";
|
|
69
|
+
madeAt: number;
|
|
70
|
+
keyUsed: KeyID;
|
|
71
|
+
encryptedChanges: Encrypted<JsonValue[], { in: RawCoID; tx: TransactionID }>;
|
|
75
72
|
};
|
|
76
73
|
|
|
77
74
|
export type TrustingTransaction = {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
privacy: "trusting";
|
|
76
|
+
madeAt: number;
|
|
77
|
+
changes: Stringified<JsonValue[]>;
|
|
81
78
|
};
|
|
82
79
|
|
|
83
80
|
export type Transaction = PrivateTransaction | TrustingTransaction;
|
|
84
81
|
|
|
85
82
|
export type DecryptedTransaction = {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
txID: TransactionID;
|
|
84
|
+
changes: JsonValue[];
|
|
85
|
+
madeAt: number;
|
|
89
86
|
};
|
|
90
87
|
|
|
91
88
|
const readKeyCache = new WeakMap<CoValueCore, { [id: KeyID]: KeySecret }>();
|
|
92
89
|
|
|
93
90
|
export class CoValueCore {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
91
|
+
id: RawCoID;
|
|
92
|
+
node: LocalNode;
|
|
93
|
+
crypto: CryptoProvider;
|
|
94
|
+
header: CoValueHeader;
|
|
95
|
+
_sessionLogs: Map<SessionID, SessionLog>;
|
|
96
|
+
_cachedContent?: RawCoValue;
|
|
97
|
+
listeners: Set<(content?: RawCoValue) => void> = new Set();
|
|
98
|
+
_decryptionCache: {
|
|
99
|
+
[key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
|
|
100
|
+
} = {};
|
|
101
|
+
_cachedKnownState?: CoValueKnownState;
|
|
102
|
+
_cachedDependentOn?: RawCoID[];
|
|
103
|
+
_cachedNewContentSinceEmpty?: NewContentMessage[] | undefined;
|
|
104
|
+
_currentAsyncAddTransaction?: Promise<void>;
|
|
105
|
+
|
|
106
|
+
constructor(
|
|
107
|
+
header: CoValueHeader,
|
|
108
|
+
node: LocalNode,
|
|
109
|
+
internalInitSessions: Map<SessionID, SessionLog> = new Map(),
|
|
110
|
+
) {
|
|
111
|
+
this.crypto = node.crypto;
|
|
112
|
+
this.id = idforHeader(header, node.crypto);
|
|
113
|
+
this.header = header;
|
|
114
|
+
this._sessionLogs = internalInitSessions;
|
|
115
|
+
this.node = node;
|
|
116
|
+
|
|
117
|
+
if (header.ruleset.type == "ownedByGroup") {
|
|
118
|
+
this.node
|
|
119
|
+
.expectCoValueLoaded(header.ruleset.group)
|
|
120
|
+
.subscribe((_groupUpdate) => {
|
|
121
|
+
this._cachedContent = undefined;
|
|
122
|
+
const newContent = this.getCurrentContent();
|
|
123
|
+
for (const listener of this.listeners) {
|
|
124
|
+
listener(newContent);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
131
127
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get sessionLogs(): Map<SessionID, SessionLog> {
|
|
131
|
+
return this._sessionLogs;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
testWithDifferentAccount(
|
|
135
|
+
account: ControlledAccountOrAgent,
|
|
136
|
+
currentSessionID: SessionID,
|
|
137
|
+
): CoValueCore {
|
|
138
|
+
const newNode = this.node.testWithDifferentAccount(
|
|
139
|
+
account,
|
|
140
|
+
currentSessionID,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return newNode.expectCoValueLoaded(this.id);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
knownState(): CoValueKnownState {
|
|
147
|
+
if (this._cachedKnownState) {
|
|
148
|
+
return this._cachedKnownState;
|
|
149
|
+
} else {
|
|
150
|
+
const knownState = this.knownStateUncached();
|
|
151
|
+
this._cachedKnownState = knownState;
|
|
152
|
+
return knownState;
|
|
135
153
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** @internal */
|
|
157
|
+
knownStateUncached(): CoValueKnownState {
|
|
158
|
+
return {
|
|
159
|
+
id: this.id,
|
|
160
|
+
header: true,
|
|
161
|
+
sessions: Object.fromEntries(
|
|
162
|
+
[...this.sessionLogs.entries()].map(([k, v]) => [
|
|
163
|
+
k,
|
|
164
|
+
v.transactions.length,
|
|
165
|
+
]),
|
|
166
|
+
),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get meta(): JsonValue {
|
|
171
|
+
return this.header?.meta ?? null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
nextTransactionID(): TransactionID {
|
|
175
|
+
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
176
|
+
const sessionID =
|
|
177
|
+
this.header.meta?.type === "account"
|
|
178
|
+
? (this.node.currentSessionID.replace(
|
|
179
|
+
this.node.account.id,
|
|
180
|
+
this.node.account
|
|
181
|
+
.currentAgentID()
|
|
182
|
+
._unsafeUnwrap({ withStackTrace: true }),
|
|
183
|
+
) as SessionID)
|
|
184
|
+
: this.node.currentSessionID;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
sessionID,
|
|
188
|
+
txIndex: this.sessionLogs.get(sessionID)?.transactions.length || 0,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
tryAddTransactions(
|
|
193
|
+
sessionID: SessionID,
|
|
194
|
+
newTransactions: Transaction[],
|
|
195
|
+
givenExpectedNewHash: Hash | undefined,
|
|
196
|
+
newSignature: Signature,
|
|
197
|
+
): Result<true, TryAddTransactionsError> {
|
|
198
|
+
return this.node
|
|
199
|
+
.resolveAccountAgent(
|
|
200
|
+
accountOrAgentIDfromSessionID(sessionID),
|
|
201
|
+
"Expected to know signer of transaction",
|
|
202
|
+
)
|
|
203
|
+
.andThen((agent) => {
|
|
204
|
+
const signerID = this.crypto.getAgentSignerID(agent);
|
|
205
|
+
|
|
206
|
+
// const beforeHash = performance.now();
|
|
207
|
+
const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(
|
|
208
|
+
sessionID,
|
|
209
|
+
newTransactions,
|
|
144
210
|
);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this.
|
|
155
|
-
|
|
211
|
+
// const afterHash = performance.now();
|
|
212
|
+
// console.log(
|
|
213
|
+
// "Hashing took",
|
|
214
|
+
// afterHash - beforeHash
|
|
215
|
+
// );
|
|
216
|
+
|
|
217
|
+
if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
|
|
218
|
+
return err({
|
|
219
|
+
type: "InvalidHash",
|
|
220
|
+
id: this.id,
|
|
221
|
+
expectedNewHash,
|
|
222
|
+
givenExpectedNewHash,
|
|
223
|
+
} satisfies InvalidHashError);
|
|
156
224
|
}
|
|
157
|
-
}
|
|
158
225
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
226
|
+
// const beforeVerify = performance.now();
|
|
227
|
+
if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
|
|
228
|
+
return err({
|
|
229
|
+
type: "InvalidSignature",
|
|
162
230
|
id: this.id,
|
|
163
|
-
|
|
164
|
-
sessions: Object.fromEntries(
|
|
165
|
-
[...this.sessionLogs.entries()].map(([k, v]) => [
|
|
166
|
-
k,
|
|
167
|
-
v.transactions.length,
|
|
168
|
-
]),
|
|
169
|
-
),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
get meta(): JsonValue {
|
|
174
|
-
return this.header?.meta ?? null;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
nextTransactionID(): TransactionID {
|
|
178
|
-
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
179
|
-
const sessionID =
|
|
180
|
-
this.header.meta?.type === "account"
|
|
181
|
-
? (this.node.currentSessionID.replace(
|
|
182
|
-
this.node.account.id,
|
|
183
|
-
this.node.account
|
|
184
|
-
.currentAgentID()
|
|
185
|
-
._unsafeUnwrap({ withStackTrace: true }),
|
|
186
|
-
) as SessionID)
|
|
187
|
-
: this.node.currentSessionID;
|
|
188
|
-
|
|
189
|
-
return {
|
|
231
|
+
newSignature,
|
|
190
232
|
sessionID,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
233
|
+
signerID,
|
|
234
|
+
} satisfies InvalidSignatureError);
|
|
235
|
+
}
|
|
236
|
+
// const afterVerify = performance.now();
|
|
237
|
+
// console.log(
|
|
238
|
+
// "Verify took",
|
|
239
|
+
// afterVerify - beforeVerify
|
|
240
|
+
// );
|
|
241
|
+
|
|
242
|
+
this.doAddTransactions(
|
|
243
|
+
sessionID,
|
|
244
|
+
newTransactions,
|
|
245
|
+
newSignature,
|
|
246
|
+
expectedNewHash,
|
|
247
|
+
newStreamingHash,
|
|
248
|
+
"immediate",
|
|
249
|
+
);
|
|
194
250
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
givenExpectedNewHash: Hash | undefined,
|
|
199
|
-
newSignature: Signature,
|
|
200
|
-
): Result<true, TryAddTransactionsError> {
|
|
201
|
-
return this.node
|
|
202
|
-
.resolveAccountAgent(
|
|
203
|
-
accountOrAgentIDfromSessionID(sessionID),
|
|
204
|
-
"Expected to know signer of transaction",
|
|
205
|
-
)
|
|
206
|
-
.andThen((agent) => {
|
|
207
|
-
const signerID = this.crypto.getAgentSignerID(agent);
|
|
208
|
-
|
|
209
|
-
// const beforeHash = performance.now();
|
|
210
|
-
const { expectedNewHash, newStreamingHash } =
|
|
211
|
-
this.expectedNewHashAfter(sessionID, newTransactions);
|
|
212
|
-
// const afterHash = performance.now();
|
|
213
|
-
// console.log(
|
|
214
|
-
// "Hashing took",
|
|
215
|
-
// afterHash - beforeHash
|
|
216
|
-
// );
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
givenExpectedNewHash &&
|
|
220
|
-
givenExpectedNewHash !== expectedNewHash
|
|
221
|
-
) {
|
|
222
|
-
return err({
|
|
223
|
-
type: "InvalidHash",
|
|
224
|
-
id: this.id,
|
|
225
|
-
expectedNewHash,
|
|
226
|
-
givenExpectedNewHash,
|
|
227
|
-
} satisfies InvalidHashError);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// const beforeVerify = performance.now();
|
|
231
|
-
if (
|
|
232
|
-
!this.crypto.verify(newSignature, expectedNewHash, signerID)
|
|
233
|
-
) {
|
|
234
|
-
return err({
|
|
235
|
-
type: "InvalidSignature",
|
|
236
|
-
id: this.id,
|
|
237
|
-
newSignature,
|
|
238
|
-
sessionID,
|
|
239
|
-
signerID,
|
|
240
|
-
} satisfies InvalidSignatureError);
|
|
241
|
-
}
|
|
242
|
-
// const afterVerify = performance.now();
|
|
243
|
-
// console.log(
|
|
244
|
-
// "Verify took",
|
|
245
|
-
// afterVerify - beforeVerify
|
|
246
|
-
// );
|
|
247
|
-
|
|
248
|
-
this.doAddTransactions(
|
|
249
|
-
sessionID,
|
|
250
|
-
newTransactions,
|
|
251
|
-
newSignature,
|
|
252
|
-
expectedNewHash,
|
|
253
|
-
newStreamingHash,
|
|
254
|
-
"immediate",
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
return ok(true as const);
|
|
258
|
-
});
|
|
259
|
-
}
|
|
251
|
+
return ok(true as const);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
260
254
|
|
|
261
|
-
|
|
255
|
+
/*tryAddTransactionsAsync(
|
|
262
256
|
sessionID: SessionID,
|
|
263
257
|
newTransactions: Transaction[],
|
|
264
258
|
givenExpectedNewHash: Hash | undefined,
|
|
@@ -390,655 +384,622 @@ export class CoValueCore {
|
|
|
390
384
|
});
|
|
391
385
|
}*/
|
|
392
386
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
387
|
+
private doAddTransactions(
|
|
388
|
+
sessionID: SessionID,
|
|
389
|
+
newTransactions: Transaction[],
|
|
390
|
+
newSignature: Signature,
|
|
391
|
+
expectedNewHash: Hash,
|
|
392
|
+
newStreamingHash: StreamingHash,
|
|
393
|
+
notifyMode: "immediate" | "deferred",
|
|
394
|
+
) {
|
|
395
|
+
if (this.node.crashed) {
|
|
396
|
+
throw new Error("Trying to add transactions after node is crashed");
|
|
397
|
+
}
|
|
398
|
+
const transactions = this.sessionLogs.get(sessionID)?.transactions ?? [];
|
|
399
|
+
transactions.push(...newTransactions);
|
|
400
|
+
|
|
401
|
+
const signatureAfter =
|
|
402
|
+
this.sessionLogs.get(sessionID)?.signatureAfter ?? {};
|
|
403
|
+
|
|
404
|
+
const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce(
|
|
405
|
+
(max, idx) => (parseInt(idx) > max ? parseInt(idx) : max),
|
|
406
|
+
-1,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const sizeOfTxsSinceLastInbetweenSignature = transactions
|
|
410
|
+
.slice(lastInbetweenSignatureIdx + 1)
|
|
411
|
+
.reduce(
|
|
412
|
+
(sum, tx) =>
|
|
413
|
+
sum +
|
|
414
|
+
(tx.privacy === "private"
|
|
415
|
+
? tx.encryptedChanges.length
|
|
416
|
+
: tx.changes.length),
|
|
417
|
+
0,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
if (sizeOfTxsSinceLastInbetweenSignature > MAX_RECOMMENDED_TX_SIZE) {
|
|
421
|
+
// console.log(
|
|
422
|
+
// "Saving inbetween signature for tx ",
|
|
423
|
+
// sessionID,
|
|
424
|
+
// transactions.length - 1,
|
|
425
|
+
// sizeOfTxsSinceLastInbetweenSignature
|
|
426
|
+
// );
|
|
427
|
+
signatureAfter[transactions.length - 1] = newSignature;
|
|
428
|
+
}
|
|
426
429
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
430
|
+
this._sessionLogs.set(sessionID, {
|
|
431
|
+
transactions,
|
|
432
|
+
lastHash: expectedNewHash,
|
|
433
|
+
streamingHash: newStreamingHash,
|
|
434
|
+
lastSignature: newSignature,
|
|
435
|
+
signatureAfter: signatureAfter,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
this._cachedContent = undefined;
|
|
439
|
+
this._cachedKnownState = undefined;
|
|
440
|
+
this._cachedDependentOn = undefined;
|
|
441
|
+
this._cachedNewContentSinceEmpty = undefined;
|
|
442
|
+
|
|
443
|
+
if (this.listeners.size > 0) {
|
|
444
|
+
if (notifyMode === "immediate") {
|
|
445
|
+
const content = this.getCurrentContent();
|
|
446
|
+
for (const listener of this.listeners) {
|
|
447
|
+
listener(content);
|
|
435
448
|
}
|
|
436
|
-
|
|
437
|
-
this.
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (this.listeners.size > 0) {
|
|
451
|
-
if (notifyMode === "immediate") {
|
|
452
|
-
const content = this.getCurrentContent();
|
|
453
|
-
for (const listener of this.listeners) {
|
|
454
|
-
listener(content);
|
|
455
|
-
}
|
|
456
|
-
} else {
|
|
457
|
-
if (!this.nextDeferredNotify) {
|
|
458
|
-
this.nextDeferredNotify = new Promise((resolve) => {
|
|
459
|
-
setTimeout(() => {
|
|
460
|
-
this.nextDeferredNotify = undefined;
|
|
461
|
-
this.deferredUpdates = 0;
|
|
462
|
-
const content = this.getCurrentContent();
|
|
463
|
-
for (const listener of this.listeners) {
|
|
464
|
-
listener(content);
|
|
465
|
-
}
|
|
466
|
-
resolve();
|
|
467
|
-
}, 0);
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
this.deferredUpdates++;
|
|
471
|
-
}
|
|
449
|
+
} else {
|
|
450
|
+
if (!this.nextDeferredNotify) {
|
|
451
|
+
this.nextDeferredNotify = new Promise((resolve) => {
|
|
452
|
+
setTimeout(() => {
|
|
453
|
+
this.nextDeferredNotify = undefined;
|
|
454
|
+
this.deferredUpdates = 0;
|
|
455
|
+
const content = this.getCurrentContent();
|
|
456
|
+
for (const listener of this.listeners) {
|
|
457
|
+
listener(content);
|
|
458
|
+
}
|
|
459
|
+
resolve();
|
|
460
|
+
}, 0);
|
|
461
|
+
});
|
|
472
462
|
}
|
|
463
|
+
this.deferredUpdates++;
|
|
464
|
+
}
|
|
473
465
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
deferredUpdates = 0;
|
|
469
|
+
nextDeferredNotify: Promise<void> | undefined;
|
|
470
|
+
|
|
471
|
+
subscribe(listener: (content?: RawCoValue) => void): () => void {
|
|
472
|
+
this.listeners.add(listener);
|
|
473
|
+
listener(this.getCurrentContent());
|
|
474
|
+
|
|
475
|
+
return () => {
|
|
476
|
+
this.listeners.delete(listener);
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
expectedNewHashAfter(
|
|
481
|
+
sessionID: SessionID,
|
|
482
|
+
newTransactions: Transaction[],
|
|
483
|
+
): { expectedNewHash: Hash; newStreamingHash: StreamingHash } {
|
|
484
|
+
const streamingHash =
|
|
485
|
+
this.sessionLogs.get(sessionID)?.streamingHash.clone() ??
|
|
486
|
+
new StreamingHash(this.crypto);
|
|
487
|
+
for (const transaction of newTransactions) {
|
|
488
|
+
streamingHash.update(transaction);
|
|
485
489
|
}
|
|
486
490
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
491
|
+
const newStreamingHash = streamingHash.clone();
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
expectedNewHash: streamingHash.digest(),
|
|
495
|
+
newStreamingHash,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async expectedNewHashAfterAsync(
|
|
500
|
+
sessionID: SessionID,
|
|
501
|
+
newTransactions: Transaction[],
|
|
502
|
+
): Promise<{ expectedNewHash: Hash; newStreamingHash: StreamingHash }> {
|
|
503
|
+
const streamingHash =
|
|
504
|
+
this.sessionLogs.get(sessionID)?.streamingHash.clone() ??
|
|
505
|
+
new StreamingHash(this.crypto);
|
|
506
|
+
let before = performance.now();
|
|
507
|
+
for (const transaction of newTransactions) {
|
|
508
|
+
streamingHash.update(transaction);
|
|
509
|
+
const after = performance.now();
|
|
510
|
+
if (after - before > 1) {
|
|
511
|
+
// console.log("Hashing blocked for", after - before);
|
|
512
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
513
|
+
before = performance.now();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
499
516
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
517
|
+
const newStreamingHash = streamingHash.clone();
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
expectedNewHash: streamingHash.digest(),
|
|
521
|
+
newStreamingHash,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
makeTransaction(
|
|
526
|
+
changes: JsonValue[],
|
|
527
|
+
privacy: "private" | "trusting",
|
|
528
|
+
): boolean {
|
|
529
|
+
const madeAt = Date.now();
|
|
530
|
+
|
|
531
|
+
let transaction: Transaction;
|
|
532
|
+
|
|
533
|
+
if (privacy === "private") {
|
|
534
|
+
const { secret: keySecret, id: keyID } = this.getCurrentReadKey();
|
|
535
|
+
|
|
536
|
+
if (!keySecret) {
|
|
537
|
+
throw new Error("Can't make transaction without read key secret");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const encrypted = this.crypto.encryptForTransaction(changes, keySecret, {
|
|
541
|
+
in: this.id,
|
|
542
|
+
tx: this.nextTransactionID(),
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
this._decryptionCache[encrypted] = changes;
|
|
546
|
+
|
|
547
|
+
transaction = {
|
|
548
|
+
privacy: "private",
|
|
549
|
+
madeAt,
|
|
550
|
+
keyUsed: keyID,
|
|
551
|
+
encryptedChanges: encrypted,
|
|
552
|
+
};
|
|
553
|
+
} else {
|
|
554
|
+
transaction = {
|
|
555
|
+
privacy: "trusting",
|
|
556
|
+
madeAt,
|
|
557
|
+
changes: stableStringify(changes),
|
|
558
|
+
};
|
|
504
559
|
}
|
|
505
560
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
this.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
561
|
+
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
562
|
+
const sessionID =
|
|
563
|
+
this.header.meta?.type === "account"
|
|
564
|
+
? (this.node.currentSessionID.replace(
|
|
565
|
+
this.node.account.id,
|
|
566
|
+
this.node.account
|
|
567
|
+
.currentAgentID()
|
|
568
|
+
._unsafeUnwrap({ withStackTrace: true }),
|
|
569
|
+
) as SessionID)
|
|
570
|
+
: this.node.currentSessionID;
|
|
571
|
+
|
|
572
|
+
const { expectedNewHash } = this.expectedNewHashAfter(sessionID, [
|
|
573
|
+
transaction,
|
|
574
|
+
]);
|
|
575
|
+
|
|
576
|
+
const signature = this.crypto.sign(
|
|
577
|
+
this.node.account.currentSignerSecret(),
|
|
578
|
+
expectedNewHash,
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
const success = this.tryAddTransactions(
|
|
582
|
+
sessionID,
|
|
583
|
+
[transaction],
|
|
584
|
+
expectedNewHash,
|
|
585
|
+
signature,
|
|
586
|
+
)._unsafeUnwrap({ withStackTrace: true });
|
|
587
|
+
|
|
588
|
+
if (success) {
|
|
589
|
+
void this.node.syncManager.syncCoValue(this);
|
|
590
|
+
}
|
|
523
591
|
|
|
524
|
-
|
|
592
|
+
return success;
|
|
593
|
+
}
|
|
525
594
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
595
|
+
getCurrentContent(options?: {
|
|
596
|
+
ignorePrivateTransactions: true;
|
|
597
|
+
}): RawCoValue {
|
|
598
|
+
if (!options?.ignorePrivateTransactions && this._cachedContent) {
|
|
599
|
+
return this._cachedContent;
|
|
530
600
|
}
|
|
531
601
|
|
|
532
|
-
|
|
533
|
-
changes: JsonValue[],
|
|
534
|
-
privacy: "private" | "trusting",
|
|
535
|
-
): boolean {
|
|
536
|
-
const madeAt = Date.now();
|
|
602
|
+
const newContent = coreToCoValue(this, options);
|
|
537
603
|
|
|
538
|
-
|
|
604
|
+
if (!options?.ignorePrivateTransactions) {
|
|
605
|
+
this._cachedContent = newContent;
|
|
606
|
+
}
|
|
539
607
|
|
|
540
|
-
|
|
541
|
-
|
|
608
|
+
return newContent;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
getValidSortedTransactions(options?: {
|
|
612
|
+
ignorePrivateTransactions: true;
|
|
613
|
+
}): DecryptedTransaction[] {
|
|
614
|
+
const validTransactions = determineValidTransactions(this);
|
|
615
|
+
|
|
616
|
+
const allTransactions: DecryptedTransaction[] = validTransactions
|
|
617
|
+
.flatMap(({ txID, tx }) => {
|
|
618
|
+
if (tx.privacy === "trusting") {
|
|
619
|
+
return {
|
|
620
|
+
txID,
|
|
621
|
+
madeAt: tx.madeAt,
|
|
622
|
+
changes: parseJSON(tx.changes),
|
|
623
|
+
};
|
|
624
|
+
} else {
|
|
625
|
+
if (options?.ignorePrivateTransactions) {
|
|
626
|
+
return undefined;
|
|
627
|
+
}
|
|
628
|
+
const readKey = this.getReadKey(tx.keyUsed);
|
|
542
629
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
630
|
+
if (!readKey) {
|
|
631
|
+
return undefined;
|
|
632
|
+
} else {
|
|
633
|
+
let decrytedChanges = this._decryptionCache[tx.encryptedChanges];
|
|
548
634
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
635
|
+
if (!decrytedChanges) {
|
|
636
|
+
const decryptedString = this.crypto.decryptRawForTransaction(
|
|
637
|
+
tx.encryptedChanges,
|
|
638
|
+
readKey,
|
|
552
639
|
{
|
|
553
|
-
|
|
554
|
-
|
|
640
|
+
in: this.id,
|
|
641
|
+
tx: txID,
|
|
555
642
|
},
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
643
|
+
);
|
|
644
|
+
decrytedChanges = decryptedString && parseJSON(decryptedString);
|
|
645
|
+
this._decryptionCache[tx.encryptedChanges] = decrytedChanges;
|
|
646
|
+
}
|
|
559
647
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
privacy: "trusting",
|
|
569
|
-
madeAt,
|
|
570
|
-
changes: stableStringify(changes),
|
|
648
|
+
if (!decrytedChanges) {
|
|
649
|
+
console.error("Failed to decrypt transaction despite having key");
|
|
650
|
+
return undefined;
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
txID,
|
|
654
|
+
madeAt: tx.madeAt,
|
|
655
|
+
changes: decrytedChanges,
|
|
571
656
|
};
|
|
657
|
+
}
|
|
572
658
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
);
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
659
|
+
})
|
|
660
|
+
.filter((x): x is Exclude<typeof x, undefined> => !!x);
|
|
661
|
+
allTransactions.sort(
|
|
662
|
+
(a, b) =>
|
|
663
|
+
a.madeAt - b.madeAt ||
|
|
664
|
+
(a.txID.sessionID < b.txID.sessionID ? -1 : 1) ||
|
|
665
|
+
a.txID.txIndex - b.txID.txIndex,
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
return allTransactions;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
getCurrentReadKey(): { secret: KeySecret | undefined; id: KeyID } {
|
|
672
|
+
if (this.header.ruleset.type === "group") {
|
|
673
|
+
const content = expectGroup(this.getCurrentContent());
|
|
674
|
+
|
|
675
|
+
const currentKeyId = content.get("readKey");
|
|
676
|
+
|
|
677
|
+
if (!currentKeyId) {
|
|
678
|
+
throw new Error("No readKey set");
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const secret = this.getReadKey(currentKeyId);
|
|
682
|
+
|
|
683
|
+
return {
|
|
684
|
+
secret: secret,
|
|
685
|
+
id: currentKeyId,
|
|
686
|
+
};
|
|
687
|
+
} else if (this.header.ruleset.type === "ownedByGroup") {
|
|
688
|
+
return this.node
|
|
689
|
+
.expectCoValueLoaded(this.header.ruleset.group)
|
|
690
|
+
.getCurrentReadKey();
|
|
691
|
+
} else {
|
|
692
|
+
throw new Error(
|
|
693
|
+
"Only groups or values owned by groups have read secrets",
|
|
694
|
+
);
|
|
606
695
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
this._cachedContent = newContent;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
getReadKey(keyID: KeyID): KeySecret | undefined {
|
|
699
|
+
let key = readKeyCache.get(this)?.[keyID];
|
|
700
|
+
if (!key) {
|
|
701
|
+
key = this.getUncachedReadKey(keyID);
|
|
702
|
+
if (key) {
|
|
703
|
+
let cache = readKeyCache.get(this);
|
|
704
|
+
if (!cache) {
|
|
705
|
+
cache = {};
|
|
706
|
+
readKeyCache.set(this, cache);
|
|
619
707
|
}
|
|
620
|
-
|
|
621
|
-
|
|
708
|
+
cache[keyID] = key;
|
|
709
|
+
}
|
|
622
710
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
this._decryptionCache[tx.encryptedChanges] =
|
|
662
|
-
decrytedChanges;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
if (!decrytedChanges) {
|
|
666
|
-
console.error(
|
|
667
|
-
"Failed to decrypt transaction despite having key",
|
|
668
|
-
);
|
|
669
|
-
return undefined;
|
|
670
|
-
}
|
|
671
|
-
return {
|
|
672
|
-
txID,
|
|
673
|
-
madeAt: tx.madeAt,
|
|
674
|
-
changes: decrytedChanges,
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
})
|
|
679
|
-
.filter((x): x is Exclude<typeof x, undefined> => !!x);
|
|
680
|
-
allTransactions.sort(
|
|
681
|
-
(a, b) =>
|
|
682
|
-
a.madeAt - b.madeAt ||
|
|
683
|
-
(a.txID.sessionID < b.txID.sessionID ? -1 : 1) ||
|
|
684
|
-
a.txID.txIndex - b.txID.txIndex,
|
|
711
|
+
return key;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
getUncachedReadKey(keyID: KeyID): KeySecret | undefined {
|
|
715
|
+
if (this.header.ruleset.type === "group") {
|
|
716
|
+
const content = expectGroup(
|
|
717
|
+
this.getCurrentContent({ ignorePrivateTransactions: true }),
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
const keyForEveryone = content.get(`${keyID}_for_everyone`);
|
|
721
|
+
if (keyForEveryone) return keyForEveryone;
|
|
722
|
+
|
|
723
|
+
// Try to find key revelation for us
|
|
724
|
+
const lookupAccountOrAgentID =
|
|
725
|
+
this.header.meta?.type === "account"
|
|
726
|
+
? this.node.account
|
|
727
|
+
.currentAgentID()
|
|
728
|
+
._unsafeUnwrap({ withStackTrace: true })
|
|
729
|
+
: this.node.account.id;
|
|
730
|
+
|
|
731
|
+
const lastReadyKeyEdit = content.lastEditAt(
|
|
732
|
+
`${keyID}_for_${lookupAccountOrAgentID}`,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
if (lastReadyKeyEdit?.value) {
|
|
736
|
+
const revealer = lastReadyKeyEdit.by;
|
|
737
|
+
const revealerAgent = this.node
|
|
738
|
+
.resolveAccountAgent(revealer, "Expected to know revealer")
|
|
739
|
+
._unsafeUnwrap({ withStackTrace: true });
|
|
740
|
+
|
|
741
|
+
const secret = this.crypto.unseal(
|
|
742
|
+
lastReadyKeyEdit.value,
|
|
743
|
+
this.node.account.currentSealerSecret(),
|
|
744
|
+
this.crypto.getAgentSealerID(revealerAgent),
|
|
745
|
+
{
|
|
746
|
+
in: this.id,
|
|
747
|
+
tx: lastReadyKeyEdit.tx,
|
|
748
|
+
},
|
|
685
749
|
);
|
|
686
750
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
751
|
+
if (secret) {
|
|
752
|
+
return secret as KeySecret;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Try to find indirect revelation through previousKeys
|
|
757
|
+
|
|
758
|
+
for (const co of content.keys()) {
|
|
759
|
+
if (isKeyForKeyField(co) && co.startsWith(keyID)) {
|
|
760
|
+
const encryptingKeyID = co.split("_for_")[1] as KeyID;
|
|
761
|
+
const encryptingKeySecret = this.getReadKey(encryptingKeyID);
|
|
762
|
+
|
|
763
|
+
if (!encryptingKeySecret) {
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const encryptedPreviousKey = content.get(co)!;
|
|
768
|
+
|
|
769
|
+
const secret = this.crypto.decryptKeySecret(
|
|
770
|
+
{
|
|
771
|
+
encryptedID: keyID,
|
|
772
|
+
encryptingID: encryptingKeyID,
|
|
773
|
+
encrypted: encryptedPreviousKey,
|
|
774
|
+
},
|
|
775
|
+
encryptingKeySecret,
|
|
776
|
+
);
|
|
777
|
+
|
|
778
|
+
if (secret) {
|
|
779
|
+
return secret as KeySecret;
|
|
780
|
+
} else {
|
|
781
|
+
console.error(
|
|
782
|
+
`Encrypting ${encryptingKeyID} key didn't decrypt ${keyID}`,
|
|
713
783
|
);
|
|
784
|
+
}
|
|
714
785
|
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
return undefined;
|
|
789
|
+
} else if (this.header.ruleset.type === "ownedByGroup") {
|
|
790
|
+
return this.node
|
|
791
|
+
.expectCoValueLoaded(this.header.ruleset.group)
|
|
792
|
+
.getReadKey(keyID);
|
|
793
|
+
} else {
|
|
794
|
+
throw new Error(
|
|
795
|
+
"Only groups or values owned by groups have read secrets",
|
|
796
|
+
);
|
|
715
797
|
}
|
|
798
|
+
}
|
|
716
799
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
key = this.getUncachedReadKey(keyID);
|
|
721
|
-
if (key) {
|
|
722
|
-
let cache = readKeyCache.get(this);
|
|
723
|
-
if (!cache) {
|
|
724
|
-
cache = {};
|
|
725
|
-
readKeyCache.set(this, cache);
|
|
726
|
-
}
|
|
727
|
-
cache[keyID] = key;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
return key;
|
|
800
|
+
getGroup(): RawGroup {
|
|
801
|
+
if (this.header.ruleset.type !== "ownedByGroup") {
|
|
802
|
+
throw new Error("Only values owned by groups have groups");
|
|
731
803
|
}
|
|
732
804
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
805
|
+
return expectGroup(
|
|
806
|
+
this.node
|
|
807
|
+
.expectCoValueLoaded(this.header.ruleset.group)
|
|
808
|
+
.getCurrentContent(),
|
|
809
|
+
);
|
|
810
|
+
}
|
|
738
811
|
|
|
739
|
-
|
|
740
|
-
|
|
812
|
+
getTx(txID: TransactionID): Transaction | undefined {
|
|
813
|
+
return this.sessionLogs.get(txID.sessionID)?.transactions[txID.txIndex];
|
|
814
|
+
}
|
|
741
815
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
.currentAgentID()
|
|
747
|
-
._unsafeUnwrap({ withStackTrace: true })
|
|
748
|
-
: this.node.account.id;
|
|
816
|
+
newContentSince(
|
|
817
|
+
knownState: CoValueKnownState | undefined,
|
|
818
|
+
): NewContentMessage[] | undefined {
|
|
819
|
+
const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
|
|
749
820
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
821
|
+
if (isKnownStateEmpty && this._cachedNewContentSinceEmpty) {
|
|
822
|
+
return this._cachedNewContentSinceEmpty;
|
|
823
|
+
}
|
|
753
824
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
825
|
+
let currentPiece: NewContentMessage = {
|
|
826
|
+
action: "content",
|
|
827
|
+
id: this.id,
|
|
828
|
+
header: knownState?.header ? undefined : this.header,
|
|
829
|
+
priority: getPriorityFromHeader(this.header),
|
|
830
|
+
new: {},
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
const pieces = [currentPiece];
|
|
834
|
+
|
|
835
|
+
const sentState: CoValueKnownState["sessions"] = {};
|
|
836
|
+
|
|
837
|
+
let pieceSize = 0;
|
|
838
|
+
|
|
839
|
+
let sessionsTodoAgain: Set<SessionID> | undefined | "first" = "first";
|
|
840
|
+
|
|
841
|
+
while (sessionsTodoAgain === "first" || sessionsTodoAgain?.size || 0 > 0) {
|
|
842
|
+
if (sessionsTodoAgain === "first") {
|
|
843
|
+
sessionsTodoAgain = undefined;
|
|
844
|
+
}
|
|
845
|
+
const sessionsTodo = sessionsTodoAgain ?? this.sessionLogs.keys();
|
|
846
|
+
|
|
847
|
+
for (const sessionIDKey of sessionsTodo) {
|
|
848
|
+
const sessionID = sessionIDKey as SessionID;
|
|
849
|
+
const log = this.sessionLogs.get(sessionID)!;
|
|
850
|
+
const knownStateForSessionID = knownState?.sessions[sessionID];
|
|
851
|
+
const sentStateForSessionID = sentState[sessionID];
|
|
852
|
+
const nextKnownSignatureIdx = getNextKnownSignatureIdx(
|
|
853
|
+
log,
|
|
854
|
+
knownStateForSessionID,
|
|
855
|
+
sentStateForSessionID,
|
|
856
|
+
);
|
|
774
857
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
this.getReadKey(encryptingKeyID);
|
|
782
|
-
|
|
783
|
-
if (!encryptingKeySecret) {
|
|
784
|
-
continue;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
const encryptedPreviousKey = content.get(co)!;
|
|
788
|
-
|
|
789
|
-
const secret = this.crypto.decryptKeySecret(
|
|
790
|
-
{
|
|
791
|
-
encryptedID: keyID,
|
|
792
|
-
encryptingID: encryptingKeyID,
|
|
793
|
-
encrypted: encryptedPreviousKey,
|
|
794
|
-
},
|
|
795
|
-
encryptingKeySecret,
|
|
796
|
-
);
|
|
797
|
-
|
|
798
|
-
if (secret) {
|
|
799
|
-
return secret as KeySecret;
|
|
800
|
-
} else {
|
|
801
|
-
console.error(
|
|
802
|
-
`Encrypting ${encryptingKeyID} key didn't decrypt ${keyID}`,
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
}
|
|
858
|
+
const firstNewTxIdx =
|
|
859
|
+
sentStateForSessionID ?? knownStateForSessionID ?? 0;
|
|
860
|
+
const afterLastNewTxIdx =
|
|
861
|
+
nextKnownSignatureIdx === undefined
|
|
862
|
+
? log.transactions.length
|
|
863
|
+
: nextKnownSignatureIdx + 1;
|
|
807
864
|
|
|
808
|
-
|
|
809
|
-
} else if (this.header.ruleset.type === "ownedByGroup") {
|
|
810
|
-
return this.node
|
|
811
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
812
|
-
.getReadKey(keyID);
|
|
813
|
-
} else {
|
|
814
|
-
throw new Error(
|
|
815
|
-
"Only groups or values owned by groups have read secrets",
|
|
816
|
-
);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
865
|
+
const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
|
|
819
866
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
867
|
+
if (nNewTx === 0) {
|
|
868
|
+
sessionsTodoAgain?.delete(sessionID);
|
|
869
|
+
continue;
|
|
823
870
|
}
|
|
824
871
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
getTx(txID: TransactionID): Transaction | undefined {
|
|
833
|
-
return this.sessionLogs.get(txID.sessionID)?.transactions[txID.txIndex];
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
newContentSince(
|
|
837
|
-
knownState: CoValueKnownState | undefined,
|
|
838
|
-
): NewContentMessage[] | undefined {
|
|
839
|
-
const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
|
|
872
|
+
if (afterLastNewTxIdx < log.transactions.length) {
|
|
873
|
+
if (!sessionsTodoAgain) {
|
|
874
|
+
sessionsTodoAgain = new Set();
|
|
875
|
+
}
|
|
876
|
+
sessionsTodoAgain.add(sessionID);
|
|
877
|
+
}
|
|
840
878
|
|
|
841
|
-
|
|
842
|
-
|
|
879
|
+
const oldPieceSize = pieceSize;
|
|
880
|
+
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
881
|
+
const tx = log.transactions[txIdx]!;
|
|
882
|
+
pieceSize +=
|
|
883
|
+
tx.privacy === "private"
|
|
884
|
+
? tx.encryptedChanges.length
|
|
885
|
+
: tx.changes.length;
|
|
843
886
|
}
|
|
844
887
|
|
|
845
|
-
|
|
888
|
+
if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
|
|
889
|
+
currentPiece = {
|
|
846
890
|
action: "content",
|
|
847
891
|
id: this.id,
|
|
848
|
-
header:
|
|
849
|
-
priority: getPriorityFromHeader(this.header),
|
|
892
|
+
header: undefined,
|
|
850
893
|
new: {},
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
let pieceSize = 0;
|
|
894
|
+
priority: getPriorityFromHeader(this.header),
|
|
895
|
+
};
|
|
896
|
+
pieces.push(currentPiece);
|
|
897
|
+
pieceSize = pieceSize - oldPieceSize;
|
|
898
|
+
}
|
|
858
899
|
|
|
859
|
-
let
|
|
900
|
+
let sessionEntry = currentPiece.new[sessionID];
|
|
901
|
+
if (!sessionEntry) {
|
|
902
|
+
sessionEntry = {
|
|
903
|
+
after: sentStateForSessionID ?? knownStateForSessionID ?? 0,
|
|
904
|
+
newTransactions: [],
|
|
905
|
+
lastSignature: "WILL_BE_REPLACED" as Signature,
|
|
906
|
+
};
|
|
907
|
+
currentPiece.new[sessionID] = sessionEntry;
|
|
908
|
+
}
|
|
860
909
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
0 > 0
|
|
865
|
-
) {
|
|
866
|
-
if (sessionsTodoAgain === "first") {
|
|
867
|
-
sessionsTodoAgain = undefined;
|
|
868
|
-
}
|
|
869
|
-
const sessionsTodo = sessionsTodoAgain ?? this.sessionLogs.keys();
|
|
870
|
-
|
|
871
|
-
for (const sessionIDKey of sessionsTodo) {
|
|
872
|
-
const sessionID = sessionIDKey as SessionID;
|
|
873
|
-
const log = this.sessionLogs.get(sessionID)!;
|
|
874
|
-
const knownStateForSessionID = knownState?.sessions[sessionID];
|
|
875
|
-
const sentStateForSessionID = sentState[sessionID];
|
|
876
|
-
const nextKnownSignatureIdx = getNextKnownSignatureIdx(
|
|
877
|
-
log,
|
|
878
|
-
knownStateForSessionID,
|
|
879
|
-
sentStateForSessionID,
|
|
880
|
-
);
|
|
881
|
-
|
|
882
|
-
const firstNewTxIdx =
|
|
883
|
-
sentStateForSessionID ?? knownStateForSessionID ?? 0;
|
|
884
|
-
const afterLastNewTxIdx =
|
|
885
|
-
nextKnownSignatureIdx === undefined
|
|
886
|
-
? log.transactions.length
|
|
887
|
-
: nextKnownSignatureIdx + 1;
|
|
888
|
-
|
|
889
|
-
const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
|
|
890
|
-
|
|
891
|
-
if (nNewTx === 0) {
|
|
892
|
-
sessionsTodoAgain?.delete(sessionID);
|
|
893
|
-
continue;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
if (afterLastNewTxIdx < log.transactions.length) {
|
|
897
|
-
if (!sessionsTodoAgain) {
|
|
898
|
-
sessionsTodoAgain = new Set();
|
|
899
|
-
}
|
|
900
|
-
sessionsTodoAgain.add(sessionID);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
const oldPieceSize = pieceSize;
|
|
904
|
-
for (
|
|
905
|
-
let txIdx = firstNewTxIdx;
|
|
906
|
-
txIdx < afterLastNewTxIdx;
|
|
907
|
-
txIdx++
|
|
908
|
-
) {
|
|
909
|
-
const tx = log.transactions[txIdx]!;
|
|
910
|
-
pieceSize +=
|
|
911
|
-
tx.privacy === "private"
|
|
912
|
-
? tx.encryptedChanges.length
|
|
913
|
-
: tx.changes.length;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
|
|
917
|
-
currentPiece = {
|
|
918
|
-
action: "content",
|
|
919
|
-
id: this.id,
|
|
920
|
-
header: undefined,
|
|
921
|
-
new: {},
|
|
922
|
-
priority: getPriorityFromHeader(this.header),
|
|
923
|
-
};
|
|
924
|
-
pieces.push(currentPiece);
|
|
925
|
-
pieceSize = pieceSize - oldPieceSize;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
let sessionEntry = currentPiece.new[sessionID];
|
|
929
|
-
if (!sessionEntry) {
|
|
930
|
-
sessionEntry = {
|
|
931
|
-
after:
|
|
932
|
-
sentStateForSessionID ??
|
|
933
|
-
knownStateForSessionID ??
|
|
934
|
-
0,
|
|
935
|
-
newTransactions: [],
|
|
936
|
-
lastSignature: "WILL_BE_REPLACED" as Signature,
|
|
937
|
-
};
|
|
938
|
-
currentPiece.new[sessionID] = sessionEntry;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
for (
|
|
942
|
-
let txIdx = firstNewTxIdx;
|
|
943
|
-
txIdx < afterLastNewTxIdx;
|
|
944
|
-
txIdx++
|
|
945
|
-
) {
|
|
946
|
-
const tx = log.transactions[txIdx]!;
|
|
947
|
-
sessionEntry.newTransactions.push(tx);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
sessionEntry.lastSignature =
|
|
951
|
-
nextKnownSignatureIdx === undefined
|
|
952
|
-
? log.lastSignature!
|
|
953
|
-
: log.signatureAfter[nextKnownSignatureIdx]!;
|
|
954
|
-
|
|
955
|
-
sentState[sessionID] =
|
|
956
|
-
(sentStateForSessionID ?? knownStateForSessionID ?? 0) +
|
|
957
|
-
nNewTx;
|
|
958
|
-
}
|
|
910
|
+
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
911
|
+
const tx = log.transactions[txIdx]!;
|
|
912
|
+
sessionEntry.newTransactions.push(tx);
|
|
959
913
|
}
|
|
960
914
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
915
|
+
sessionEntry.lastSignature =
|
|
916
|
+
nextKnownSignatureIdx === undefined
|
|
917
|
+
? log.lastSignature!
|
|
918
|
+
: log.signatureAfter[nextKnownSignatureIdx]!;
|
|
964
919
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
920
|
+
sentState[sessionID] =
|
|
921
|
+
(sentStateForSessionID ?? knownStateForSessionID ?? 0) + nNewTx;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
968
924
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
925
|
+
const piecesWithContent = pieces.filter(
|
|
926
|
+
(piece) => Object.keys(piece.new).length > 0 || piece.header,
|
|
927
|
+
);
|
|
972
928
|
|
|
973
|
-
|
|
929
|
+
if (piecesWithContent.length === 0) {
|
|
930
|
+
return undefined;
|
|
974
931
|
}
|
|
975
932
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
return this._cachedDependentOn;
|
|
979
|
-
} else {
|
|
980
|
-
const dependentOn = this.getDependedOnCoValuesUncached();
|
|
981
|
-
this._cachedDependentOn = dependentOn;
|
|
982
|
-
return dependentOn;
|
|
983
|
-
}
|
|
933
|
+
if (isKnownStateEmpty) {
|
|
934
|
+
this._cachedNewContentSinceEmpty = piecesWithContent;
|
|
984
935
|
}
|
|
985
936
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
[...this.sessionLogs.keys()]
|
|
997
|
-
.map((sessionID) =>
|
|
998
|
-
accountOrAgentIDfromSessionID(
|
|
999
|
-
sessionID as SessionID,
|
|
1000
|
-
),
|
|
1001
|
-
)
|
|
1002
|
-
.filter(
|
|
1003
|
-
(session): session is RawAccountID =>
|
|
1004
|
-
isAccountID(session) && session !== this.id,
|
|
1005
|
-
),
|
|
1006
|
-
),
|
|
1007
|
-
]
|
|
1008
|
-
: [];
|
|
937
|
+
return piecesWithContent;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
getDependedOnCoValues(): RawCoID[] {
|
|
941
|
+
if (this._cachedDependentOn) {
|
|
942
|
+
return this._cachedDependentOn;
|
|
943
|
+
} else {
|
|
944
|
+
const dependentOn = this.getDependedOnCoValuesUncached();
|
|
945
|
+
this._cachedDependentOn = dependentOn;
|
|
946
|
+
return dependentOn;
|
|
1009
947
|
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/** @internal */
|
|
951
|
+
getDependedOnCoValuesUncached(): RawCoID[] {
|
|
952
|
+
return this.header.ruleset.type === "group"
|
|
953
|
+
? expectGroup(this.getCurrentContent())
|
|
954
|
+
.keys()
|
|
955
|
+
.filter((k): k is RawAccountID => k.startsWith("co_"))
|
|
956
|
+
: this.header.ruleset.type === "ownedByGroup"
|
|
957
|
+
? [
|
|
958
|
+
this.header.ruleset.group,
|
|
959
|
+
...new Set(
|
|
960
|
+
[...this.sessionLogs.keys()]
|
|
961
|
+
.map((sessionID) =>
|
|
962
|
+
accountOrAgentIDfromSessionID(sessionID as SessionID),
|
|
963
|
+
)
|
|
964
|
+
.filter(
|
|
965
|
+
(session): session is RawAccountID =>
|
|
966
|
+
isAccountID(session) && session !== this.id,
|
|
967
|
+
),
|
|
968
|
+
),
|
|
969
|
+
]
|
|
970
|
+
: [];
|
|
971
|
+
}
|
|
1010
972
|
}
|
|
1011
973
|
|
|
1012
974
|
function getNextKnownSignatureIdx(
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
975
|
+
log: SessionLog,
|
|
976
|
+
knownStateForSessionID?: number,
|
|
977
|
+
sentStateForSessionID?: number,
|
|
1016
978
|
) {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
);
|
|
979
|
+
return Object.keys(log.signatureAfter)
|
|
980
|
+
.map(Number)
|
|
981
|
+
.sort((a, b) => a - b)
|
|
982
|
+
.find(
|
|
983
|
+
(idx) => idx >= (sentStateForSessionID ?? knownStateForSessionID ?? -1),
|
|
984
|
+
);
|
|
1024
985
|
}
|
|
1025
986
|
|
|
1026
987
|
export type InvalidHashError = {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
988
|
+
type: "InvalidHash";
|
|
989
|
+
id: RawCoID;
|
|
990
|
+
expectedNewHash: Hash;
|
|
991
|
+
givenExpectedNewHash: Hash;
|
|
1031
992
|
};
|
|
1032
993
|
|
|
1033
994
|
export type InvalidSignatureError = {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
995
|
+
type: "InvalidSignature";
|
|
996
|
+
id: RawCoID;
|
|
997
|
+
newSignature: Signature;
|
|
998
|
+
sessionID: SessionID;
|
|
999
|
+
signerID: SignerID;
|
|
1039
1000
|
};
|
|
1040
1001
|
|
|
1041
1002
|
export type TryAddTransactionsError =
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1003
|
+
| ResolveAccountAgentError
|
|
1004
|
+
| InvalidHashError
|
|
1005
|
+
| InvalidSignatureError;
|