cojson 0.19.22 → 0.20.1
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 +66 -0
- package/dist/PeerState.d.ts +6 -1
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +18 -3
- package/dist/PeerState.js.map +1 -1
- package/dist/coValueContentMessage.d.ts +0 -2
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +0 -8
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +4 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +30 -0
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +70 -5
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +302 -31
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +6 -1
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +9 -0
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coList.d.ts +4 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +3 -0
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +3 -6
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +2 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -12
- package/dist/config.js.map +1 -1
- package/dist/crypto/NapiCrypto.d.ts +1 -2
- package/dist/crypto/NapiCrypto.d.ts.map +1 -1
- package/dist/crypto/NapiCrypto.js +19 -4
- package/dist/crypto/NapiCrypto.js.map +1 -1
- package/dist/crypto/RNCrypto.d.ts.map +1 -1
- package/dist/crypto/RNCrypto.js +19 -4
- package/dist/crypto/RNCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +11 -4
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +52 -10
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
- package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
- package/dist/crypto/WasmCryptoEdge.js +4 -1
- package/dist/crypto/WasmCryptoEdge.js.map +1 -1
- package/dist/crypto/crypto.d.ts +3 -3
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js +6 -1
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/exports.d.ts +5 -5
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +4 -3
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +4 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +4 -0
- package/dist/ids.js.map +1 -1
- package/dist/knownState.d.ts +2 -0
- package/dist/knownState.d.ts.map +1 -1
- package/dist/localNode.d.ts +12 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +14 -0
- package/dist/localNode.js.map +1 -1
- package/dist/platformUtils.d.ts +3 -0
- package/dist/platformUtils.d.ts.map +1 -0
- package/dist/platformUtils.js +24 -0
- package/dist/platformUtils.js.map +1 -0
- package/dist/queue/LinkedList.d.ts +9 -3
- package/dist/queue/LinkedList.d.ts.map +1 -1
- package/dist/queue/LinkedList.js +30 -1
- package/dist/queue/LinkedList.js.map +1 -1
- package/dist/queue/OutgoingLoadQueue.d.ts +95 -0
- package/dist/queue/OutgoingLoadQueue.d.ts.map +1 -0
- package/dist/queue/OutgoingLoadQueue.js +240 -0
- package/dist/queue/OutgoingLoadQueue.js.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
- package/dist/storage/sqlite/client.d.ts +3 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +44 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +7 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +42 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +7 -0
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +48 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +6 -0
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +42 -0
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +59 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +66 -43
- package/dist/sync.js.map +1 -1
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
- package/dist/tests/GarbageCollector.test.js +5 -6
- package/dist/tests/GarbageCollector.test.js.map +1 -1
- package/dist/tests/LinkedList.test.js +90 -0
- package/dist/tests/LinkedList.test.js.map +1 -1
- package/dist/tests/OutgoingLoadQueue.test.d.ts +2 -0
- package/dist/tests/OutgoingLoadQueue.test.d.ts.map +1 -0
- package/dist/tests/OutgoingLoadQueue.test.js +814 -0
- package/dist/tests/OutgoingLoadQueue.test.js.map +1 -0
- package/dist/tests/StorageApiAsync.test.js +484 -152
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +505 -136
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +6 -3
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +34 -13
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.js +127 -4
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/crypto.test.js +89 -93
- package/dist/tests/crypto.test.js.map +1 -1
- package/dist/tests/deleteCoValue.test.d.ts +2 -0
- package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
- package/dist/tests/deleteCoValue.test.js +313 -0
- package/dist/tests/deleteCoValue.test.js.map +1 -0
- package/dist/tests/group.removeMember.test.js +18 -30
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.js +3 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
- package/dist/tests/sync.concurrentLoad.test.d.ts +2 -0
- package/dist/tests/sync.concurrentLoad.test.d.ts.map +1 -0
- package/dist/tests/sync.concurrentLoad.test.js +481 -0
- package/dist/tests/sync.concurrentLoad.test.js.map +1 -0
- package/dist/tests/sync.deleted.test.d.ts +2 -0
- package/dist/tests/sync.deleted.test.d.ts.map +1 -0
- package/dist/tests/sync.deleted.test.js +214 -0
- package/dist/tests/sync.deleted.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.js +3 -2
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +4 -3
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +3 -2
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +3 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +17 -1
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +7 -3
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +19 -4
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +6 -16
- package/src/PeerState.ts +26 -3
- package/src/coValueContentMessage.ts +0 -14
- package/src/coValueCore/SessionMap.ts +43 -1
- package/src/coValueCore/coValueCore.ts +415 -27
- package/src/coValueCore/verifiedState.ts +26 -3
- package/src/coValues/coList.ts +9 -3
- package/src/coValues/group.ts +5 -6
- package/src/config.ts +4 -13
- package/src/crypto/NapiCrypto.ts +29 -13
- package/src/crypto/RNCrypto.ts +29 -11
- package/src/crypto/WasmCrypto.ts +67 -20
- package/src/crypto/WasmCryptoEdge.ts +5 -1
- package/src/crypto/crypto.ts +16 -4
- package/src/exports.ts +4 -2
- package/src/ids.ts +11 -1
- package/src/localNode.ts +15 -0
- package/src/platformUtils.ts +26 -0
- package/src/queue/LinkedList.ts +34 -4
- package/src/queue/OutgoingLoadQueue.ts +307 -0
- package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
- package/src/storage/sqlite/client.ts +77 -0
- package/src/storage/sqlite/sqliteMigrations.ts +7 -0
- package/src/storage/sqliteAsync/client.ts +75 -0
- package/src/storage/storageAsync.ts +62 -0
- package/src/storage/storageSync.ts +58 -0
- package/src/storage/types.ts +69 -0
- package/src/sync.ts +78 -46
- package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
- package/src/tests/GarbageCollector.test.ts +6 -10
- package/src/tests/LinkedList.test.ts +111 -0
- package/src/tests/OutgoingLoadQueue.test.ts +1129 -0
- package/src/tests/StorageApiAsync.test.ts +572 -162
- package/src/tests/StorageApiSync.test.ts +580 -143
- package/src/tests/WasmCrypto.test.ts +8 -3
- package/src/tests/coValueCore.loadFromStorage.test.ts +6 -0
- package/src/tests/coValueCore.test.ts +49 -14
- package/src/tests/coreWasm.test.ts +319 -10
- package/src/tests/crypto.test.ts +141 -150
- package/src/tests/deleteCoValue.test.ts +528 -0
- package/src/tests/group.removeMember.test.ts +35 -35
- package/src/tests/knownState.lazyLoading.test.ts +6 -0
- package/src/tests/sync.concurrentLoad.test.ts +650 -0
- package/src/tests/sync.deleted.test.ts +294 -0
- package/src/tests/sync.mesh.test.ts +5 -2
- package/src/tests/sync.storage.test.ts +6 -3
- package/src/tests/sync.test.ts +5 -2
- package/src/tests/testStorage.ts +31 -2
- package/src/tests/testUtils.ts +31 -10
- package/dist/crypto/PureJSCrypto.d.ts +0 -77
- package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
- package/dist/crypto/PureJSCrypto.js +0 -236
- package/dist/crypto/PureJSCrypto.js.map +0 -1
- package/dist/tests/PureJSCrypto.test.d.ts +0 -2
- package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
- package/dist/tests/PureJSCrypto.test.js +0 -145
- package/dist/tests/PureJSCrypto.test.js.map +0 -1
- package/src/crypto/PureJSCrypto.ts +0 -429
- package/src/tests/PureJSCrypto.test.ts +0 -217
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
Signature,
|
|
8
8
|
SignerID,
|
|
9
9
|
} from "../crypto/crypto.js";
|
|
10
|
-
import { RawCoID, SessionID } from "../ids.js";
|
|
10
|
+
import { isDeleteSessionID, RawCoID, SessionID } from "../ids.js";
|
|
11
11
|
import { parseJSON, Stringified } from "../jsonStringify.js";
|
|
12
12
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
13
13
|
import { TryAddTransactionsError } from "./coValueCore.js";
|
|
@@ -34,6 +34,7 @@ export type SessionLog = {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
export class SessionMap {
|
|
37
|
+
private isDeleted: boolean = false;
|
|
37
38
|
sessions: Map<SessionID, SessionLog> = new Map();
|
|
38
39
|
|
|
39
40
|
// Known state related properies, mutated when adding transactions to the session map
|
|
@@ -60,7 +61,32 @@ export class SessionMap {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
markAsDeleted() {
|
|
65
|
+
this.isDeleted = true;
|
|
66
|
+
|
|
67
|
+
// We reset the known state to report only the deleted session/transaction
|
|
68
|
+
this.knownState = { id: this.id, header: true, sessions: {} };
|
|
69
|
+
|
|
70
|
+
// We remove the streaming statuses, because once deleted we don't need
|
|
71
|
+
// to wait for any streaming to be completed
|
|
72
|
+
this.knownStateWithStreaming = undefined;
|
|
73
|
+
this.streamingKnownState = undefined;
|
|
74
|
+
this.invalidateKnownStateCache();
|
|
75
|
+
|
|
76
|
+
for (const [sessionID, sessionLog] of this.sessions.entries()) {
|
|
77
|
+
if (!isDeleteSessionID(sessionID)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.knownState.sessions[sessionID] = sessionLog.transactions.length;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
63
85
|
setStreamingKnownState(streamingKnownState: KnownStateSessions) {
|
|
86
|
+
if (this.isDeleted) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
64
90
|
// if the streaming known state is a subset of the current known state, we can skip the update
|
|
65
91
|
if (isKnownStateSubsetOf(streamingKnownState, this.knownState.sessions)) {
|
|
66
92
|
return;
|
|
@@ -148,6 +174,10 @@ export class SessionMap {
|
|
|
148
174
|
newSignature: Signature,
|
|
149
175
|
skipVerify: boolean = false,
|
|
150
176
|
) {
|
|
177
|
+
if (this.isDeleted && !isDeleteSessionID(sessionID)) {
|
|
178
|
+
throw new Error("Cannot add transactions to a deleted coValue");
|
|
179
|
+
}
|
|
180
|
+
|
|
151
181
|
const sessionLog = this.getOrCreateSessionLog(sessionID, signerID);
|
|
152
182
|
|
|
153
183
|
sessionLog.impl.tryAdd(newTransactions, newSignature, skipVerify);
|
|
@@ -164,6 +194,12 @@ export class SessionMap {
|
|
|
164
194
|
meta: JsonObject | undefined,
|
|
165
195
|
madeAt: number,
|
|
166
196
|
): { signature: Signature; transaction: Transaction } {
|
|
197
|
+
if (this.isDeleted) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"Cannot make new private transaction on a deleted coValue",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
167
203
|
const sessionLog = this.getOrCreateSessionLog(
|
|
168
204
|
sessionID,
|
|
169
205
|
signerAgent.currentSignerID(),
|
|
@@ -194,6 +230,12 @@ export class SessionMap {
|
|
|
194
230
|
meta: JsonObject | undefined,
|
|
195
231
|
madeAt: number,
|
|
196
232
|
): { signature: Signature; transaction: Transaction } {
|
|
233
|
+
if (this.isDeleted) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
"Cannot make new trusting transaction on a deleted coValue",
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
197
239
|
const sessionLog = this.getOrCreateSessionLog(
|
|
198
240
|
sessionID,
|
|
199
241
|
signerAgent.currentSignerID(),
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
|
|
2
2
|
import type { PeerState } from "../PeerState.js";
|
|
3
3
|
import type { RawCoValue } from "../coValue.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { LoadMode } from "../queue/OutgoingLoadQueue.js";
|
|
5
|
+
import {
|
|
6
|
+
RawAccount,
|
|
7
|
+
type ControlledAccountOrAgent,
|
|
8
|
+
} from "../coValues/account.js";
|
|
5
9
|
import type { RawGroup } from "../coValues/group.js";
|
|
6
10
|
import { CO_VALUE_LOADING_CONFIG } from "../config.js";
|
|
7
|
-
import { validateTxSizeLimitInBytes } from "../coValueContentMessage.js";
|
|
8
11
|
import { coreToCoValue } from "../coreToCoValue.js";
|
|
9
12
|
import {
|
|
10
13
|
CryptoProvider,
|
|
@@ -14,12 +17,18 @@ import {
|
|
|
14
17
|
Signature,
|
|
15
18
|
SignerID,
|
|
16
19
|
} from "../crypto/crypto.js";
|
|
17
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
AgentID,
|
|
22
|
+
isDeleteSessionID,
|
|
23
|
+
RawCoID,
|
|
24
|
+
SessionID,
|
|
25
|
+
TransactionID,
|
|
26
|
+
} from "../ids.js";
|
|
18
27
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
19
28
|
import { LocalNode, ResolveAccountAgentError } from "../localNode.js";
|
|
20
29
|
import { logger } from "../logger.js";
|
|
21
30
|
import { determineValidTransactions } from "../permissions.js";
|
|
22
|
-
import { NewContentMessage, PeerID } from "../sync.js";
|
|
31
|
+
import { KnownStateMessage, NewContentMessage, PeerID } from "../sync.js";
|
|
23
32
|
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
24
33
|
import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
25
34
|
import {
|
|
@@ -27,7 +36,12 @@ import {
|
|
|
27
36
|
getDependenciesFromGroupRawTransactions,
|
|
28
37
|
getDependenciesFromHeader,
|
|
29
38
|
} from "./utils.js";
|
|
30
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
CoValueHeader,
|
|
41
|
+
Transaction,
|
|
42
|
+
Uniqueness,
|
|
43
|
+
VerifiedState,
|
|
44
|
+
} from "./verifiedState.js";
|
|
31
45
|
import { SessionMap } from "./SessionMap.js";
|
|
32
46
|
import {
|
|
33
47
|
MergeCommit,
|
|
@@ -51,6 +65,45 @@ import {
|
|
|
51
65
|
} from "../knownState.js";
|
|
52
66
|
import { safeParseJSON } from "../jsonStringify.js";
|
|
53
67
|
|
|
68
|
+
export type ValidationValue =
|
|
69
|
+
| { isOk: true }
|
|
70
|
+
| {
|
|
71
|
+
isOk: false;
|
|
72
|
+
message: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function validateUniqueness(uniqueness: Uniqueness): ValidationValue {
|
|
76
|
+
if (typeof uniqueness === "number" && !Number.isInteger(uniqueness)) {
|
|
77
|
+
return {
|
|
78
|
+
isOk: false,
|
|
79
|
+
message: "Uniqueness cannot be a non-integer number, got " + uniqueness,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (Array.isArray(uniqueness)) {
|
|
84
|
+
return {
|
|
85
|
+
isOk: false,
|
|
86
|
+
message: "Uniqueness cannot be an array, got " + uniqueness,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof uniqueness === "object" && uniqueness !== null) {
|
|
91
|
+
for (let [key, value] of Object.entries(uniqueness)) {
|
|
92
|
+
if (typeof value !== "string") {
|
|
93
|
+
return {
|
|
94
|
+
isOk: false,
|
|
95
|
+
message:
|
|
96
|
+
"Uniqueness object values must be a string, got " +
|
|
97
|
+
value +
|
|
98
|
+
" for key " +
|
|
99
|
+
key,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { isOk: true };
|
|
105
|
+
}
|
|
106
|
+
|
|
54
107
|
export function idforHeader(
|
|
55
108
|
header: CoValueHeader,
|
|
56
109
|
crypto: CryptoProvider,
|
|
@@ -197,6 +250,8 @@ export class CoValueCore {
|
|
|
197
250
|
// context
|
|
198
251
|
readonly node: LocalNode;
|
|
199
252
|
private readonly crypto: CryptoProvider;
|
|
253
|
+
// Whether the coValue is deleted
|
|
254
|
+
public isDeleted: boolean = false;
|
|
200
255
|
|
|
201
256
|
// state
|
|
202
257
|
id: RawCoID;
|
|
@@ -492,6 +547,15 @@ export class CoValueCore {
|
|
|
492
547
|
skipVerify?: boolean,
|
|
493
548
|
) {
|
|
494
549
|
if (!skipVerify) {
|
|
550
|
+
const validation = validateUniqueness(header.uniqueness);
|
|
551
|
+
if (!validation.isOk) {
|
|
552
|
+
logger.error("Invalid uniqueness", {
|
|
553
|
+
header,
|
|
554
|
+
errorMessage: validation.message,
|
|
555
|
+
});
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
|
|
495
559
|
const expectedId = idforHeader(header, this.node.crypto);
|
|
496
560
|
|
|
497
561
|
if (this.id !== expectedId) {
|
|
@@ -559,6 +623,33 @@ export class CoValueCore {
|
|
|
559
623
|
return this.verified?.immutableKnownState() ?? emptyKnownState(this.id);
|
|
560
624
|
}
|
|
561
625
|
|
|
626
|
+
/**
|
|
627
|
+
* Returns a known state message to signal to the peer that the coValue doesn't need to be synced anymore
|
|
628
|
+
*
|
|
629
|
+
* Implemented to be backward compatible with clients that don't support deleted coValues
|
|
630
|
+
*/
|
|
631
|
+
stopSyncingKnownStateMessage(
|
|
632
|
+
peerKnownState: CoValueKnownState | undefined,
|
|
633
|
+
): KnownStateMessage {
|
|
634
|
+
if (!peerKnownState) {
|
|
635
|
+
return {
|
|
636
|
+
action: "known",
|
|
637
|
+
...this.knownState(),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const knownState = cloneKnownState(this.knownState());
|
|
642
|
+
|
|
643
|
+
// We combine everything for backward compatibility with clients that don't support deleted coValues
|
|
644
|
+
// This way they won't try to sync their own content that we have discarded because the coValue is deleted
|
|
645
|
+
combineKnownStateSessions(knownState.sessions, peerKnownState.sessions);
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
action: "known",
|
|
649
|
+
...knownState,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
562
653
|
get meta(): JsonValue {
|
|
563
654
|
return this.verified?.header.meta ?? null;
|
|
564
655
|
}
|
|
@@ -593,6 +684,107 @@ export class CoValueCore {
|
|
|
593
684
|
}
|
|
594
685
|
}
|
|
595
686
|
|
|
687
|
+
#isDeleteTransaction(
|
|
688
|
+
sessionID: SessionID,
|
|
689
|
+
newTransactions: Transaction[],
|
|
690
|
+
skipVerify: boolean,
|
|
691
|
+
) {
|
|
692
|
+
if (!this.verified) {
|
|
693
|
+
return {
|
|
694
|
+
value: false,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Detect + validate delete transactions during ingestion
|
|
699
|
+
// Delete transactions are:
|
|
700
|
+
// - in a delete session (sessionID ends with `$`)
|
|
701
|
+
// - trusting (unencrypted)
|
|
702
|
+
// - have meta `{ deleted: true }`
|
|
703
|
+
let deleteTransaction: Transaction | undefined = undefined;
|
|
704
|
+
|
|
705
|
+
if (isDeleteSessionID(sessionID)) {
|
|
706
|
+
const txCount =
|
|
707
|
+
this.verified.sessions.get(sessionID)?.transactions.length ?? 0;
|
|
708
|
+
if (txCount > 0 || newTransactions.length > 1) {
|
|
709
|
+
return {
|
|
710
|
+
value: true,
|
|
711
|
+
err: {
|
|
712
|
+
type: "DeleteTransactionRejected",
|
|
713
|
+
id: this.id,
|
|
714
|
+
sessionID,
|
|
715
|
+
reason: "InvalidDeleteTransaction",
|
|
716
|
+
error: new Error(
|
|
717
|
+
"Delete transaction must be the only transaction in the session",
|
|
718
|
+
),
|
|
719
|
+
} as const,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const firstTransaction = newTransactions[0];
|
|
724
|
+
const deleteMarker =
|
|
725
|
+
firstTransaction && this.#getDeleteMarker(firstTransaction);
|
|
726
|
+
|
|
727
|
+
if (deleteMarker) {
|
|
728
|
+
deleteTransaction = firstTransaction;
|
|
729
|
+
|
|
730
|
+
if (deleteMarker.deleted !== this.id) {
|
|
731
|
+
return {
|
|
732
|
+
value: true,
|
|
733
|
+
err: {
|
|
734
|
+
type: "DeleteTransactionRejected",
|
|
735
|
+
id: this.id,
|
|
736
|
+
sessionID,
|
|
737
|
+
reason: "InvalidDeleteTransaction",
|
|
738
|
+
error: new Error(
|
|
739
|
+
`Delete transaction ID mismatch: expected ${this.id}, got ${deleteMarker.deleted}`,
|
|
740
|
+
),
|
|
741
|
+
} as const,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (this.isGroupOrAccount()) {
|
|
747
|
+
return {
|
|
748
|
+
value: true,
|
|
749
|
+
err: {
|
|
750
|
+
type: "DeleteTransactionRejected",
|
|
751
|
+
id: this.id,
|
|
752
|
+
sessionID,
|
|
753
|
+
reason: "CoValueNotDeletable",
|
|
754
|
+
error: new Error("Cannot delete Group or Account coValues"),
|
|
755
|
+
},
|
|
756
|
+
} as const;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (!skipVerify && deleteTransaction) {
|
|
761
|
+
const author = accountOrAgentIDfromSessionID(sessionID);
|
|
762
|
+
|
|
763
|
+
const permission = this.#canAuthorDeleteCoValueAtTime(
|
|
764
|
+
author,
|
|
765
|
+
deleteTransaction.madeAt,
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
if (!permission.ok) {
|
|
769
|
+
return {
|
|
770
|
+
value: true,
|
|
771
|
+
err: {
|
|
772
|
+
type: "DeleteTransactionRejected",
|
|
773
|
+
id: this.id,
|
|
774
|
+
sessionID,
|
|
775
|
+
author,
|
|
776
|
+
reason: permission.reason,
|
|
777
|
+
error: new Error(permission.message),
|
|
778
|
+
},
|
|
779
|
+
} as const;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return {
|
|
784
|
+
value: Boolean(deleteTransaction),
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
596
788
|
/**
|
|
597
789
|
* Apply new transactions that were not generated by the current node to the CoValue
|
|
598
790
|
*/
|
|
@@ -602,8 +794,22 @@ export class CoValueCore {
|
|
|
602
794
|
newSignature: Signature,
|
|
603
795
|
skipVerify: boolean = false,
|
|
604
796
|
) {
|
|
797
|
+
if (newTransactions.length === 0) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
605
801
|
let signerID: SignerID | undefined;
|
|
606
802
|
|
|
803
|
+
// sync should never try to add transactions to a deleted coValue
|
|
804
|
+
// this can only happen if `tryAddTransactions` is called directly, without going through `handleNewContent`
|
|
805
|
+
if (this.isDeleted && !isDeleteSessionID(sessionID)) {
|
|
806
|
+
return {
|
|
807
|
+
type: "CoValueDeleted",
|
|
808
|
+
id: this.id,
|
|
809
|
+
error: new Error("Cannot add transactions to a deleted coValue"),
|
|
810
|
+
} as const;
|
|
811
|
+
}
|
|
812
|
+
|
|
607
813
|
if (!skipVerify) {
|
|
608
814
|
const result = this.node.resolveAccountAgent(
|
|
609
815
|
accountOrAgentIDfromSessionID(sessionID),
|
|
@@ -629,6 +835,16 @@ export class CoValueCore {
|
|
|
629
835
|
};
|
|
630
836
|
}
|
|
631
837
|
|
|
838
|
+
const isDeleteTransaction = this.#isDeleteTransaction(
|
|
839
|
+
sessionID,
|
|
840
|
+
newTransactions,
|
|
841
|
+
skipVerify,
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
if (isDeleteTransaction.err) {
|
|
845
|
+
return isDeleteTransaction.err;
|
|
846
|
+
}
|
|
847
|
+
|
|
632
848
|
try {
|
|
633
849
|
this.verified.tryAddTransactions(
|
|
634
850
|
sessionID,
|
|
@@ -638,6 +854,13 @@ export class CoValueCore {
|
|
|
638
854
|
skipVerify,
|
|
639
855
|
);
|
|
640
856
|
|
|
857
|
+
// Mark deleted state when a delete marker transaction is accepted.
|
|
858
|
+
// - In skipVerify mode (storage shards), we accept + mark without permission checks.
|
|
859
|
+
// - In verify mode, we only reach here if the delete permission check passed.
|
|
860
|
+
if (isDeleteTransaction.value) {
|
|
861
|
+
this.#markAsDeleted();
|
|
862
|
+
}
|
|
863
|
+
|
|
641
864
|
this.processNewTransactions();
|
|
642
865
|
this.scheduleNotifyUpdate();
|
|
643
866
|
this.invalidateDependants();
|
|
@@ -646,6 +869,78 @@ export class CoValueCore {
|
|
|
646
869
|
}
|
|
647
870
|
}
|
|
648
871
|
|
|
872
|
+
#markAsDeleted() {
|
|
873
|
+
this.isDeleted = true;
|
|
874
|
+
this.verified?.markAsDeleted();
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
#getDeleteMarker(tx: Transaction): { deleted: RawCoID } | undefined {
|
|
878
|
+
if (tx.privacy !== "trusting") {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (!tx.meta) {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const meta = safeParseJSON(tx.meta);
|
|
885
|
+
|
|
886
|
+
return meta && typeof meta.deleted === "string"
|
|
887
|
+
? (meta as { deleted: RawCoID })
|
|
888
|
+
: undefined;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
#canAuthorDeleteCoValueAtTime(
|
|
892
|
+
author: RawAccountID | AgentID,
|
|
893
|
+
madeAt: number,
|
|
894
|
+
):
|
|
895
|
+
| { ok: true }
|
|
896
|
+
| {
|
|
897
|
+
ok: false;
|
|
898
|
+
reason: DeleteTransactionRejectedError["reason"];
|
|
899
|
+
message: string;
|
|
900
|
+
} {
|
|
901
|
+
if (!this.verified) {
|
|
902
|
+
return {
|
|
903
|
+
ok: false,
|
|
904
|
+
reason: "CannotVerifyPermissions",
|
|
905
|
+
message: "Cannot verify delete permissions without verified state",
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (this.isGroupOrAccount()) {
|
|
910
|
+
return {
|
|
911
|
+
ok: false,
|
|
912
|
+
reason: "CoValueNotDeletable",
|
|
913
|
+
message: "Cannot delete Group or Account coValues",
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const group = this.safeGetGroup();
|
|
918
|
+
|
|
919
|
+
// Today, delete permission is defined in terms of group-admin on the owning group.
|
|
920
|
+
// If we cannot derive that (non-owned coValues), we reject the delete when verification is required.
|
|
921
|
+
if (!group) {
|
|
922
|
+
return {
|
|
923
|
+
ok: false,
|
|
924
|
+
reason: "CannotVerifyPermissions",
|
|
925
|
+
message:
|
|
926
|
+
"Cannot verify delete permissions for coValues not owned by a group",
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const groupAtTime = group.atTime(madeAt);
|
|
931
|
+
const role = groupAtTime.roleOfInternal(author);
|
|
932
|
+
|
|
933
|
+
if (role !== "admin") {
|
|
934
|
+
return {
|
|
935
|
+
ok: false,
|
|
936
|
+
reason: "NotAdmin",
|
|
937
|
+
message: "Delete transaction rejected: author is not an admin",
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return { ok: true };
|
|
942
|
+
}
|
|
943
|
+
|
|
649
944
|
notifyDependants() {
|
|
650
945
|
if (!this.isGroup()) {
|
|
651
946
|
return;
|
|
@@ -743,6 +1038,70 @@ export class CoValueCore {
|
|
|
743
1038
|
};
|
|
744
1039
|
}
|
|
745
1040
|
|
|
1041
|
+
validateDeletePermissions() {
|
|
1042
|
+
if (!this.verified) {
|
|
1043
|
+
return {
|
|
1044
|
+
ok: false,
|
|
1045
|
+
reason: "CannotVerifyPermissions",
|
|
1046
|
+
message: "Cannot verify delete permissions without verified state",
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (this.isGroupOrAccount()) {
|
|
1051
|
+
return {
|
|
1052
|
+
ok: false,
|
|
1053
|
+
reason: "CoValueNotDeletable",
|
|
1054
|
+
message: "Cannot delete Group or Account coValues",
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const group = this.safeGetGroup();
|
|
1059
|
+
if (!group) {
|
|
1060
|
+
return {
|
|
1061
|
+
ok: false,
|
|
1062
|
+
reason: "CannotVerifyPermissions",
|
|
1063
|
+
message:
|
|
1064
|
+
"Cannot verify delete permissions for coValues not owned by a group",
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const role = group.myRole();
|
|
1069
|
+
if (role !== "admin") {
|
|
1070
|
+
return {
|
|
1071
|
+
ok: false,
|
|
1072
|
+
reason: "NotAdmin",
|
|
1073
|
+
message:
|
|
1074
|
+
"The current account lacks admin permissions to delete this coValue",
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
return { ok: true };
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Creates a delete marker transaction for this CoValue and sets the coValue as deleted
|
|
1083
|
+
*
|
|
1084
|
+
* Constraints:
|
|
1085
|
+
* - Account and Group CoValues cannot be deleted.
|
|
1086
|
+
* - Only admins can delete a coValue.
|
|
1087
|
+
*/
|
|
1088
|
+
deleteCoValue() {
|
|
1089
|
+
if (this.isDeleted) {
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const result = this.validateDeletePermissions();
|
|
1094
|
+
if (!result.ok) {
|
|
1095
|
+
throw new Error(result.message);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
this.makeTransaction(
|
|
1099
|
+
[], // Empty changes array
|
|
1100
|
+
"trusting", // Unencrypted
|
|
1101
|
+
{ deleted: this.id }, // Delete metadata
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
746
1105
|
/**
|
|
747
1106
|
* Creates a new transaction with local changes and syncs it to all peers
|
|
748
1107
|
*/
|
|
@@ -757,11 +1116,17 @@ export class CoValueCore {
|
|
|
757
1116
|
"CoValueCore: makeTransaction called on coValue without verified state",
|
|
758
1117
|
);
|
|
759
1118
|
}
|
|
1119
|
+
const isDeleteTransaction = Boolean(meta?.deleted);
|
|
760
1120
|
|
|
761
|
-
|
|
1121
|
+
if (this.isDeleted && !isDeleteTransaction) {
|
|
1122
|
+
logger.error("Cannot make transaction on a deleted coValue", {
|
|
1123
|
+
id: this.id,
|
|
1124
|
+
});
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
762
1127
|
|
|
763
1128
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
764
|
-
|
|
1129
|
+
let sessionID =
|
|
765
1130
|
this.verified.header.meta?.type === "account"
|
|
766
1131
|
? (this.node.currentSessionID.replace(
|
|
767
1132
|
this.node.getCurrentAgent().id,
|
|
@@ -769,6 +1134,12 @@ export class CoValueCore {
|
|
|
769
1134
|
) as SessionID)
|
|
770
1135
|
: this.node.currentSessionID;
|
|
771
1136
|
|
|
1137
|
+
if (isDeleteTransaction) {
|
|
1138
|
+
sessionID = this.crypto.newDeleteSessionID(
|
|
1139
|
+
this.node.getCurrentAccountOrAgentID(),
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
772
1143
|
const signerAgent = this.node.getCurrentAgent();
|
|
773
1144
|
|
|
774
1145
|
let result: { signature: Signature; transaction: Transaction };
|
|
@@ -801,6 +1172,10 @@ export class CoValueCore {
|
|
|
801
1172
|
);
|
|
802
1173
|
}
|
|
803
1174
|
|
|
1175
|
+
if (isDeleteTransaction) {
|
|
1176
|
+
this.#markAsDeleted();
|
|
1177
|
+
}
|
|
1178
|
+
|
|
804
1179
|
const { transaction } = result;
|
|
805
1180
|
|
|
806
1181
|
// Assign pre-parsed meta and changes to skip the parse/decrypt operation when loading
|
|
@@ -1266,6 +1641,14 @@ export class CoValueCore {
|
|
|
1266
1641
|
this.dependant.add(dependant);
|
|
1267
1642
|
}
|
|
1268
1643
|
|
|
1644
|
+
isGroupOrAccount() {
|
|
1645
|
+
if (!this.verified) {
|
|
1646
|
+
return false;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
return this.verified.header.ruleset.type === "group";
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1269
1652
|
isGroup() {
|
|
1270
1653
|
if (!this.verified) {
|
|
1271
1654
|
return false;
|
|
@@ -1465,11 +1848,11 @@ export class CoValueCore {
|
|
|
1465
1848
|
return this.node.syncManager.waitForSync(this.id, options?.timeout);
|
|
1466
1849
|
}
|
|
1467
1850
|
|
|
1468
|
-
load(peers: PeerState[]) {
|
|
1851
|
+
load(peers: PeerState[], mode?: LoadMode) {
|
|
1469
1852
|
this.loadFromStorage((found) => {
|
|
1470
1853
|
// When found the load is triggered by handleNewContent
|
|
1471
1854
|
if (!found) {
|
|
1472
|
-
this.loadFromPeers(peers);
|
|
1855
|
+
this.loadFromPeers(peers, mode);
|
|
1473
1856
|
}
|
|
1474
1857
|
});
|
|
1475
1858
|
}
|
|
@@ -1555,7 +1938,7 @@ export class CoValueCore {
|
|
|
1555
1938
|
this.node.storage.loadKnownState(this.id, done);
|
|
1556
1939
|
}
|
|
1557
1940
|
|
|
1558
|
-
loadFromPeers(peers: PeerState[]) {
|
|
1941
|
+
loadFromPeers(peers: PeerState[], mode?: LoadMode) {
|
|
1559
1942
|
if (peers.length === 0) {
|
|
1560
1943
|
return;
|
|
1561
1944
|
}
|
|
@@ -1565,29 +1948,17 @@ export class CoValueCore {
|
|
|
1565
1948
|
|
|
1566
1949
|
if (currentState === "unknown" || currentState === "unavailable") {
|
|
1567
1950
|
this.markPending(peer.id);
|
|
1568
|
-
this.internalLoadFromPeer(peer);
|
|
1951
|
+
this.internalLoadFromPeer(peer, mode);
|
|
1569
1952
|
}
|
|
1570
1953
|
}
|
|
1571
1954
|
}
|
|
1572
1955
|
|
|
1573
|
-
private internalLoadFromPeer(peer: PeerState) {
|
|
1956
|
+
private internalLoadFromPeer(peer: PeerState, mode?: LoadMode) {
|
|
1574
1957
|
if (peer.closed && !peer.persistent) {
|
|
1575
1958
|
this.markNotFoundInPeer(peer.id);
|
|
1576
1959
|
return;
|
|
1577
1960
|
}
|
|
1578
1961
|
|
|
1579
|
-
/**
|
|
1580
|
-
* On reconnection persistent peers will automatically fire the load request
|
|
1581
|
-
* as part of the reconnection process.
|
|
1582
|
-
*/
|
|
1583
|
-
if (!peer.closed) {
|
|
1584
|
-
peer.pushOutgoingMessage({
|
|
1585
|
-
action: "load",
|
|
1586
|
-
...this.knownState(),
|
|
1587
|
-
});
|
|
1588
|
-
peer.trackLoadRequestSent(this.id);
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
1962
|
const markNotFound = () => {
|
|
1592
1963
|
if (this.getLoadingStateForPeer(peer.id) === "pending") {
|
|
1593
1964
|
logger.warn("Timeout waiting for peer to load coValue", {
|
|
@@ -1598,11 +1969,19 @@ export class CoValueCore {
|
|
|
1598
1969
|
}
|
|
1599
1970
|
};
|
|
1600
1971
|
|
|
1601
|
-
|
|
1972
|
+
// Close listener for non-persistent peers
|
|
1602
1973
|
const removeCloseListener = peer.persistent
|
|
1603
1974
|
? undefined
|
|
1604
1975
|
: peer.addCloseListener(markNotFound);
|
|
1605
1976
|
|
|
1977
|
+
/**
|
|
1978
|
+
* On reconnection persistent peers will automatically fire the load request
|
|
1979
|
+
* as part of the reconnection process.
|
|
1980
|
+
*/
|
|
1981
|
+
if (!peer.closed) {
|
|
1982
|
+
peer.sendLoadRequest(this, mode);
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1606
1985
|
this.subscribe((state, unsubscribe) => {
|
|
1607
1986
|
const peerState = state.getLoadingStateForPeer(peer.id);
|
|
1608
1987
|
if (
|
|
@@ -1613,7 +1992,6 @@ export class CoValueCore {
|
|
|
1613
1992
|
) {
|
|
1614
1993
|
unsubscribe();
|
|
1615
1994
|
removeCloseListener?.();
|
|
1616
|
-
clearTimeout(timeout);
|
|
1617
1995
|
}
|
|
1618
1996
|
}, true);
|
|
1619
1997
|
}
|
|
@@ -1645,9 +2023,19 @@ export type TriedToAddTransactionsWithoutSignerIDError = {
|
|
|
1645
2023
|
sessionID: SessionID;
|
|
1646
2024
|
};
|
|
1647
2025
|
|
|
2026
|
+
export type DeleteTransactionRejectedError = {
|
|
2027
|
+
type: "DeleteTransactionRejected";
|
|
2028
|
+
id: RawCoID;
|
|
2029
|
+
sessionID: SessionID;
|
|
2030
|
+
author: RawAccountID | AgentID;
|
|
2031
|
+
reason: "NotAdmin" | "CoValueNotDeletable" | "CannotVerifyPermissions";
|
|
2032
|
+
error: Error;
|
|
2033
|
+
};
|
|
2034
|
+
|
|
1648
2035
|
export type TryAddTransactionsError =
|
|
1649
2036
|
| TriedToAddTransactionsWithoutVerifiedStateErrpr
|
|
1650
2037
|
| TriedToAddTransactionsWithoutSignerIDError
|
|
1651
2038
|
| ResolveAccountAgentError
|
|
1652
2039
|
| InvalidHashError
|
|
1653
|
-
| InvalidSignatureError
|
|
2040
|
+
| InvalidSignatureError
|
|
2041
|
+
| DeleteTransactionRejectedError;
|