@peerbit/trusted-network 6.0.104 → 6.0.105
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/dist/src/v2-policy-engine.d.ts +16 -5
- package/dist/src/v2-policy-engine.d.ts.map +1 -1
- package/dist/src/v2-policy-engine.js +212 -45
- package/dist/src/v2-policy-engine.js.map +1 -1
- package/dist/src/v2.d.ts +6 -0
- package/dist/src/v2.d.ts.map +1 -1
- package/dist/src/v2.js +6 -0
- package/dist/src/v2.js.map +1 -1
- package/package.json +4 -4
- package/src/v2-policy-engine.ts +277 -48
- package/src/v2.ts +7 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { PublicSignKey } from "@peerbit/crypto";
|
|
2
|
-
import { EntryV0 } from "@peerbit/log";
|
|
3
2
|
import { NetworkDescriptorV2, PolicySnapshotBodyV2, PolicySubjectBindingV2 } from "./v2.js";
|
|
4
3
|
type ValidatedPolicySnapshotV2 = {
|
|
5
4
|
body: PolicySnapshotBodyV2;
|
|
@@ -8,7 +7,9 @@ type ValidatedPolicySnapshotV2 = {
|
|
|
8
7
|
entryBytes: Uint8Array;
|
|
9
8
|
accountedBytes: number;
|
|
10
9
|
};
|
|
11
|
-
export type PolicySnapshotResolverV2 = (digest: Uint8Array
|
|
10
|
+
export type PolicySnapshotResolverV2 = (digest: Uint8Array, options: {
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
}) => Uint8Array | undefined | Promise<Uint8Array | undefined>;
|
|
12
13
|
export type PolicyParentFetchHintV2 = {
|
|
13
14
|
kind: "policy-parent";
|
|
14
15
|
digest: Uint8Array;
|
|
@@ -37,12 +38,16 @@ export type PolicyAdmissionResultV2 = {
|
|
|
37
38
|
pendingBytes: number;
|
|
38
39
|
evictedPolicyDigests?: Uint8Array[];
|
|
39
40
|
};
|
|
40
|
-
export declare const authenticatePolicySnapshotEntryV2: (
|
|
41
|
+
export declare const authenticatePolicySnapshotEntryV2: (entryBytes: Uint8Array, descriptor: NetworkDescriptorV2) => Promise<ValidatedPolicySnapshotV2>;
|
|
41
42
|
export declare class TrustedNetworkV2PolicyReducer {
|
|
42
43
|
private readonly descriptor;
|
|
43
44
|
private readonly resolvePolicyEntry;
|
|
45
|
+
private readonly resolveTimeoutMs;
|
|
44
46
|
private readonly maxPending;
|
|
45
47
|
private readonly maxPendingPolicyBytes;
|
|
48
|
+
private readonly lifecycleController;
|
|
49
|
+
private externalSignal?;
|
|
50
|
+
private externalAbortListener?;
|
|
46
51
|
private acceptedHead?;
|
|
47
52
|
private projectedRoles;
|
|
48
53
|
private readonly pending;
|
|
@@ -52,10 +57,12 @@ export declare class TrustedNetworkV2PolicyReducer {
|
|
|
52
57
|
constructor(properties: {
|
|
53
58
|
descriptor: NetworkDescriptorV2;
|
|
54
59
|
resolvePolicyEntry: PolicySnapshotResolverV2;
|
|
60
|
+
resolveTimeoutMs?: number;
|
|
61
|
+
signal?: AbortSignal;
|
|
55
62
|
maxPending?: number;
|
|
56
63
|
maxPendingPolicyBytes?: number;
|
|
57
64
|
});
|
|
58
|
-
get state(): "EMPTY" | "ACTIVE" | "UNAVAILABLE" | "FORKED";
|
|
65
|
+
get state(): "EMPTY" | "ACTIVE" | "UNAVAILABLE" | "FORKED" | "HALTED";
|
|
59
66
|
get head(): PolicyHeadProjectionV2 | undefined;
|
|
60
67
|
get forkEvidence(): PolicyForkEvidenceV2 | undefined;
|
|
61
68
|
get pendingCount(): number;
|
|
@@ -63,15 +70,19 @@ export declare class TrustedNetworkV2PolicyReducer {
|
|
|
63
70
|
get pendingDigests(): Uint8Array[];
|
|
64
71
|
rolesFor(subject: PublicSignKey): number;
|
|
65
72
|
isAuthorized(subject: PublicSignKey, roles: number): boolean;
|
|
73
|
+
abort(): void;
|
|
66
74
|
private fetchHints;
|
|
67
75
|
private result;
|
|
68
76
|
private forkedResult;
|
|
77
|
+
private haltedResult;
|
|
69
78
|
private unavailableResult;
|
|
70
79
|
private completedDrainResult;
|
|
71
80
|
private project;
|
|
72
81
|
private retainCanonicalHeadEntry;
|
|
82
|
+
private resolveExternalSnapshot;
|
|
73
83
|
private resolveSnapshot;
|
|
74
84
|
private parentOf;
|
|
85
|
+
private candidateAncestryResult;
|
|
75
86
|
private acceptedAncestryUnavailable;
|
|
76
87
|
private evaluate;
|
|
77
88
|
private setFork;
|
|
@@ -81,7 +92,7 @@ export declare class TrustedNetworkV2PolicyReducer {
|
|
|
81
92
|
private enterUnavailable;
|
|
82
93
|
private drainPending;
|
|
83
94
|
private enqueueAdmission;
|
|
84
|
-
ingest(
|
|
95
|
+
ingest(entryBytes: Uint8Array): Promise<PolicyAdmissionResultV2>;
|
|
85
96
|
retryUnavailable(): Promise<PolicyAdmissionResultV2>;
|
|
86
97
|
private ingestOne;
|
|
87
98
|
private retryUnavailableOne;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2-policy-engine.d.ts","sourceRoot":"","sources":["../../src/v2-policy-engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,aAAa,EAAU,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"v2-policy-engine.d.ts","sourceRoot":"","sources":["../../src/v2-policy-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { deserialize, serialize } from "@dao-xyz/borsh";
|
|
|
2
2
|
import { DecryptedThing, PublicSignKey, verify } from "@peerbit/crypto";
|
|
3
3
|
import { Entry, EntryV0, NO_ENCODING } from "@peerbit/log";
|
|
4
4
|
import { compare, equals } from "uint8arrays";
|
|
5
|
-
import { NetworkDescriptorV2, PolicySnapshotBodyV2, PolicySubjectBindingV2, TRUSTED_NETWORK_V2_KNOWN_ROLE_BITS, assertNetworkDescriptorV2, decodePolicySnapshotBodyV2, digestPolicySnapshotBodyV2, } from "./v2.js";
|
|
5
|
+
import { NetworkDescriptorV2, PolicySnapshotBodyV2, PolicySubjectBindingV2, TRUSTED_NETWORK_V2_KNOWN_ROLE_BITS, TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES, assertNetworkDescriptorV2, decodePolicySnapshotBodyV2, digestPolicySnapshotBodyV2, } from "./v2.js";
|
|
6
6
|
/**
|
|
7
7
|
* Internal policy reducer for the non-activatable TrustedNetwork v2 scaffold.
|
|
8
8
|
*
|
|
@@ -12,8 +12,12 @@ import { NetworkDescriptorV2, PolicySnapshotBodyV2, PolicySubjectBindingV2, TRUS
|
|
|
12
12
|
* resolver instead of being accumulated in memory.
|
|
13
13
|
*/
|
|
14
14
|
const DEFAULT_MAX_PENDING_POLICIES_V2 = 64;
|
|
15
|
-
const DEFAULT_MAX_PENDING_POLICY_BYTES_V2 = 256 * 1024;
|
|
16
15
|
const PENDING_POLICY_ACCOUNTING_OVERHEAD_V2 = 64;
|
|
16
|
+
const MAX_PENDING_POLICY_ACCOUNTED_BYTES_V2 = TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES * 2 +
|
|
17
|
+
PENDING_POLICY_ACCOUNTING_OVERHEAD_V2;
|
|
18
|
+
const DEFAULT_MAX_PENDING_POLICY_BYTES_V2 = MAX_PENDING_POLICY_ACCOUNTED_BYTES_V2;
|
|
19
|
+
const DEFAULT_POLICY_RESOLUTION_TIMEOUT_MS_V2 = 10 * 1000;
|
|
20
|
+
const MAX_TIMER_DELAY_MS_V2 = 0x7fffffff;
|
|
17
21
|
const MAX_UNAVAILABLE_REASON_LENGTH_V2 = 512;
|
|
18
22
|
const copyBytes = (bytes) => Uint8Array.from(bytes);
|
|
19
23
|
const bytesKey = (bytes) => {
|
|
@@ -44,29 +48,47 @@ const copySnapshot = (snapshot) => ({
|
|
|
44
48
|
});
|
|
45
49
|
const validationMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
46
50
|
const boundedUnavailableReason = (reason) => reason.slice(0, MAX_UNAVAILABLE_REASON_LENGTH_V2);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
class PolicyDependencyUnavailableErrorV2 extends Error {
|
|
52
|
+
constructor(message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = "PolicyDependencyUnavailableErrorV2";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const capturePolicySnapshotEntryBytesV2 = (entryBytes) => {
|
|
58
|
+
if (!(entryBytes instanceof Uint8Array)) {
|
|
59
|
+
throw new Error("Policy snapshot must use canonical EntryV0 bytes");
|
|
60
|
+
}
|
|
61
|
+
if (entryBytes.byteLength === 0) {
|
|
62
|
+
throw new Error("Policy snapshot must use EntryV0");
|
|
63
|
+
}
|
|
64
|
+
if (entryBytes.byteLength > TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES) {
|
|
65
|
+
throw new Error(`Policy snapshot entry must contain 1-${TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES} bytes`);
|
|
66
|
+
}
|
|
67
|
+
// Apply the protocol byte ceiling before this first copy, decode, or crypto
|
|
68
|
+
// operation. The retained copy also prevents caller mutation during awaits.
|
|
69
|
+
return copyBytes(entryBytes);
|
|
70
|
+
};
|
|
71
|
+
const authenticateCapturedPolicySnapshotEntryV2 = async (canonicalEntryBytes, descriptor) => {
|
|
72
|
+
const authenticatedEntry = deserialize(canonicalEntryBytes, Entry);
|
|
73
|
+
if (!(authenticatedEntry instanceof EntryV0)) {
|
|
50
74
|
throw new Error("Policy snapshot must use EntryV0");
|
|
51
75
|
}
|
|
52
|
-
if (!(
|
|
76
|
+
if (!equals(canonicalEntryBytes, serialize(authenticatedEntry))) {
|
|
77
|
+
throw new Error("Policy snapshot entry encoding is not canonical");
|
|
78
|
+
}
|
|
79
|
+
if (!(authenticatedEntry._meta instanceof DecryptedThing)) {
|
|
53
80
|
throw new Error("Policy snapshot metadata must be public");
|
|
54
81
|
}
|
|
55
|
-
if (!(
|
|
82
|
+
if (!(authenticatedEntry._payload instanceof DecryptedThing)) {
|
|
56
83
|
throw new Error("Policy snapshot payload must be public");
|
|
57
84
|
}
|
|
58
|
-
if (
|
|
59
|
-
|
|
85
|
+
if (authenticatedEntry._signatures === undefined ||
|
|
86
|
+
authenticatedEntry._signatures.signatures.length !== 1) {
|
|
60
87
|
throw new Error("Policy snapshot must contain exactly one signature");
|
|
61
88
|
}
|
|
62
|
-
if (!(
|
|
89
|
+
if (!(authenticatedEntry._signatures.signatures[0] instanceof DecryptedThing)) {
|
|
63
90
|
throw new Error("Policy snapshot signature must be public");
|
|
64
91
|
}
|
|
65
|
-
const entryBytes = serialize(entry);
|
|
66
|
-
const authenticatedEntry = deserialize(entryBytes, Entry);
|
|
67
|
-
if (!(authenticatedEntry instanceof EntryV0)) {
|
|
68
|
-
throw new Error("Policy snapshot must decode as EntryV0");
|
|
69
|
-
}
|
|
70
92
|
authenticatedEntry.init({ encoding: NO_ENCODING });
|
|
71
93
|
const signatures = await authenticatedEntry.getSignatures();
|
|
72
94
|
if (signatures.length !== 1) {
|
|
@@ -89,12 +111,16 @@ export const authenticatePolicySnapshotEntryV2 = async (entry, descriptor) => {
|
|
|
89
111
|
body: copyBody(body),
|
|
90
112
|
digest: copyBytes(digest),
|
|
91
113
|
digestKey: bytesKey(digest),
|
|
92
|
-
entryBytes,
|
|
93
|
-
accountedBytes:
|
|
114
|
+
entryBytes: canonicalEntryBytes,
|
|
115
|
+
accountedBytes: canonicalEntryBytes.byteLength +
|
|
94
116
|
serialize(body).byteLength +
|
|
95
117
|
PENDING_POLICY_ACCOUNTING_OVERHEAD_V2,
|
|
96
118
|
};
|
|
97
119
|
};
|
|
120
|
+
export const authenticatePolicySnapshotEntryV2 = async (entryBytes, descriptor) => {
|
|
121
|
+
assertNetworkDescriptorV2(descriptor);
|
|
122
|
+
return authenticateCapturedPolicySnapshotEntryV2(capturePolicySnapshotEntryBytesV2(entryBytes), descriptor);
|
|
123
|
+
};
|
|
98
124
|
const projectionFromSnapshot = (snapshot) => ({
|
|
99
125
|
sequence: snapshot.body.sequence,
|
|
100
126
|
digest: copyBytes(snapshot.digest),
|
|
@@ -131,8 +157,12 @@ const copyForkEvidence = (evidence) => ({
|
|
|
131
157
|
export class TrustedNetworkV2PolicyReducer {
|
|
132
158
|
descriptor;
|
|
133
159
|
resolvePolicyEntry;
|
|
160
|
+
resolveTimeoutMs;
|
|
134
161
|
maxPending;
|
|
135
162
|
maxPendingPolicyBytes;
|
|
163
|
+
lifecycleController = new AbortController();
|
|
164
|
+
externalSignal;
|
|
165
|
+
externalAbortListener;
|
|
136
166
|
acceptedHead;
|
|
137
167
|
projectedRoles = new Map();
|
|
138
168
|
pending = new Map();
|
|
@@ -149,15 +179,37 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
149
179
|
}
|
|
150
180
|
const maxPendingPolicyBytes = properties.maxPendingPolicyBytes ?? DEFAULT_MAX_PENDING_POLICY_BYTES_V2;
|
|
151
181
|
if (!Number.isSafeInteger(maxPendingPolicyBytes) ||
|
|
152
|
-
maxPendingPolicyBytes < 1
|
|
153
|
-
|
|
182
|
+
maxPendingPolicyBytes < 1 ||
|
|
183
|
+
maxPendingPolicyBytes > MAX_PENDING_POLICY_ACCOUNTED_BYTES_V2) {
|
|
184
|
+
throw new Error(`maxPendingPolicyBytes must be a positive safe integer no greater than ${MAX_PENDING_POLICY_ACCOUNTED_BYTES_V2}`);
|
|
185
|
+
}
|
|
186
|
+
const resolveTimeoutMs = properties.resolveTimeoutMs ?? DEFAULT_POLICY_RESOLUTION_TIMEOUT_MS_V2;
|
|
187
|
+
if (!Number.isSafeInteger(resolveTimeoutMs) ||
|
|
188
|
+
resolveTimeoutMs < 1 ||
|
|
189
|
+
resolveTimeoutMs > MAX_TIMER_DELAY_MS_V2) {
|
|
190
|
+
throw new Error(`resolveTimeoutMs must be a positive safe integer no greater than ${MAX_TIMER_DELAY_MS_V2}`);
|
|
154
191
|
}
|
|
155
192
|
this.descriptor = deserialize(serialize(properties.descriptor), NetworkDescriptorV2);
|
|
156
193
|
this.resolvePolicyEntry = properties.resolvePolicyEntry;
|
|
194
|
+
this.resolveTimeoutMs = resolveTimeoutMs;
|
|
157
195
|
this.maxPending = maxPending;
|
|
158
196
|
this.maxPendingPolicyBytes = maxPendingPolicyBytes;
|
|
197
|
+
if (properties.signal?.aborted) {
|
|
198
|
+
this.lifecycleController.abort();
|
|
199
|
+
}
|
|
200
|
+
else if (properties.signal !== undefined) {
|
|
201
|
+
this.externalSignal = properties.signal;
|
|
202
|
+
this.externalAbortListener = () => {
|
|
203
|
+
this.externalSignal = undefined;
|
|
204
|
+
this.externalAbortListener = undefined;
|
|
205
|
+
this.lifecycleController.abort();
|
|
206
|
+
};
|
|
207
|
+
this.externalSignal.addEventListener("abort", this.externalAbortListener, { once: true });
|
|
208
|
+
}
|
|
159
209
|
}
|
|
160
210
|
get state() {
|
|
211
|
+
if (this.lifecycleController.signal.aborted)
|
|
212
|
+
return "HALTED";
|
|
161
213
|
if (this.fork !== undefined)
|
|
162
214
|
return "FORKED";
|
|
163
215
|
if (this.unavailable !== undefined)
|
|
@@ -199,6 +251,15 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
199
251
|
}
|
|
200
252
|
return (this.rolesFor(subject) & roles) === roles;
|
|
201
253
|
}
|
|
254
|
+
abort() {
|
|
255
|
+
if (this.externalSignal !== undefined &&
|
|
256
|
+
this.externalAbortListener !== undefined) {
|
|
257
|
+
this.externalSignal.removeEventListener("abort", this.externalAbortListener);
|
|
258
|
+
}
|
|
259
|
+
this.externalSignal = undefined;
|
|
260
|
+
this.externalAbortListener = undefined;
|
|
261
|
+
this.lifecycleController.abort();
|
|
262
|
+
}
|
|
202
263
|
fetchHints() {
|
|
203
264
|
const unique = new Map();
|
|
204
265
|
for (const { missingParentDigest } of this.pending.values()) {
|
|
@@ -230,6 +291,9 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
230
291
|
forkedResult() {
|
|
231
292
|
return this.result("forked", "Policy authority signed competing children");
|
|
232
293
|
}
|
|
294
|
+
haltedResult() {
|
|
295
|
+
return this.result("halted", "Policy reducer lifecycle is aborted");
|
|
296
|
+
}
|
|
233
297
|
unavailableResult(retention) {
|
|
234
298
|
const blockedCandidateRetained = this.unavailable !== undefined &&
|
|
235
299
|
this.pending.has(this.unavailable.candidateDigestKey);
|
|
@@ -244,6 +308,8 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
244
308
|
completedDrainResult(outcome) {
|
|
245
309
|
if (outcome?.status === "forked")
|
|
246
310
|
return this.forkedResult();
|
|
311
|
+
if (outcome?.status === "halted")
|
|
312
|
+
return this.haltedResult();
|
|
247
313
|
return outcome?.status === "unavailable"
|
|
248
314
|
? this.unavailableResult(outcome)
|
|
249
315
|
: undefined;
|
|
@@ -261,6 +327,67 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
261
327
|
this.acceptedHead = copySnapshot(snapshot);
|
|
262
328
|
}
|
|
263
329
|
}
|
|
330
|
+
async resolveExternalSnapshot(digest) {
|
|
331
|
+
if (this.lifecycleController.signal.aborted) {
|
|
332
|
+
throw new PolicyDependencyUnavailableErrorV2("Policy resolver lifecycle is aborted");
|
|
333
|
+
}
|
|
334
|
+
const attemptController = new AbortController();
|
|
335
|
+
let timedOut = false;
|
|
336
|
+
const abortFromLifecycle = () => attemptController.abort();
|
|
337
|
+
this.lifecycleController.signal.addEventListener("abort", abortFromLifecycle, { once: true });
|
|
338
|
+
const timeout = setTimeout(() => {
|
|
339
|
+
timedOut = true;
|
|
340
|
+
attemptController.abort();
|
|
341
|
+
}, this.resolveTimeoutMs);
|
|
342
|
+
const abortError = () => new PolicyDependencyUnavailableErrorV2(timedOut
|
|
343
|
+
? `Policy resolver timed out after ${this.resolveTimeoutMs} ms`
|
|
344
|
+
: "Policy resolver attempt was aborted");
|
|
345
|
+
let rejectOnAbort;
|
|
346
|
+
const abortPromise = new Promise((_resolve, reject) => {
|
|
347
|
+
rejectOnAbort = () => {
|
|
348
|
+
reject(abortError());
|
|
349
|
+
};
|
|
350
|
+
attemptController.signal.addEventListener("abort", rejectOnAbort, {
|
|
351
|
+
once: true,
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
const resolution = Promise.resolve().then(async () => {
|
|
355
|
+
if (attemptController.signal.aborted)
|
|
356
|
+
throw abortError();
|
|
357
|
+
const entryBytes = await this.resolvePolicyEntry(copyBytes(digest), {
|
|
358
|
+
signal: attemptController.signal,
|
|
359
|
+
});
|
|
360
|
+
// A resolver may ignore cancellation. Do not spend decode or signature
|
|
361
|
+
// verification work on bytes that arrive after this attempt expired.
|
|
362
|
+
if (attemptController.signal.aborted)
|
|
363
|
+
throw abortError();
|
|
364
|
+
if (entryBytes === undefined)
|
|
365
|
+
return undefined;
|
|
366
|
+
const snapshot = await authenticatePolicySnapshotEntryV2(entryBytes, this.descriptor);
|
|
367
|
+
if (!equals(snapshot.digest, digest)) {
|
|
368
|
+
throw new Error("Policy resolver returned the wrong body digest");
|
|
369
|
+
}
|
|
370
|
+
return snapshot;
|
|
371
|
+
});
|
|
372
|
+
// Promise.race installs handlers, but this explicit observer documents and
|
|
373
|
+
// preserves consumption if the resolver settles after its deadline.
|
|
374
|
+
void resolution.then(() => undefined, () => undefined);
|
|
375
|
+
try {
|
|
376
|
+
return await Promise.race([resolution, abortPromise]);
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
if (error instanceof PolicyDependencyUnavailableErrorV2)
|
|
380
|
+
throw error;
|
|
381
|
+
throw new PolicyDependencyUnavailableErrorV2(`Policy resolver dependency is unavailable: ${validationMessage(error)}`);
|
|
382
|
+
}
|
|
383
|
+
finally {
|
|
384
|
+
clearTimeout(timeout);
|
|
385
|
+
this.lifecycleController.signal.removeEventListener("abort", abortFromLifecycle);
|
|
386
|
+
if (rejectOnAbort !== undefined) {
|
|
387
|
+
attemptController.signal.removeEventListener("abort", rejectOnAbort);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
264
391
|
async resolveSnapshot(digest, cache) {
|
|
265
392
|
const digestKey = bytesKey(digest);
|
|
266
393
|
if (this.acceptedHead?.digestKey === digestKey) {
|
|
@@ -271,16 +398,7 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
271
398
|
return copySnapshot(pending.snapshot);
|
|
272
399
|
let resolution = cache?.get(digestKey);
|
|
273
400
|
if (resolution === undefined) {
|
|
274
|
-
resolution = (
|
|
275
|
-
const entry = await this.resolvePolicyEntry(copyBytes(digest));
|
|
276
|
-
if (entry === undefined)
|
|
277
|
-
return undefined;
|
|
278
|
-
const snapshot = await authenticatePolicySnapshotEntryV2(entry, this.descriptor);
|
|
279
|
-
if (!equals(snapshot.digest, digest)) {
|
|
280
|
-
throw new Error("Policy resolver returned the wrong body digest");
|
|
281
|
-
}
|
|
282
|
-
return snapshot;
|
|
283
|
-
})();
|
|
401
|
+
resolution = this.resolveExternalSnapshot(digest);
|
|
284
402
|
cache?.set(digestKey, resolution);
|
|
285
403
|
}
|
|
286
404
|
const snapshot = await resolution;
|
|
@@ -300,9 +418,9 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
300
418
|
}
|
|
301
419
|
catch (error) {
|
|
302
420
|
return {
|
|
303
|
-
status: "
|
|
421
|
+
status: "unavailable",
|
|
304
422
|
digest: copyBytes(child.body.previousPolicyDigest),
|
|
305
|
-
reason: `Policy parent
|
|
423
|
+
reason: `Policy parent dependency is unavailable: ${validationMessage(error)}`,
|
|
306
424
|
};
|
|
307
425
|
}
|
|
308
426
|
if (parent === undefined) {
|
|
@@ -320,13 +438,24 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
320
438
|
}
|
|
321
439
|
return { status: "found", parent };
|
|
322
440
|
}
|
|
441
|
+
candidateAncestryResult(resolution) {
|
|
442
|
+
return resolution.status === "unavailable"
|
|
443
|
+
? {
|
|
444
|
+
status: "missing",
|
|
445
|
+
digest: copyBytes(resolution.digest),
|
|
446
|
+
reason: resolution.reason,
|
|
447
|
+
}
|
|
448
|
+
: resolution;
|
|
449
|
+
}
|
|
323
450
|
acceptedAncestryUnavailable(resolution) {
|
|
324
451
|
return {
|
|
325
452
|
status: "unavailable",
|
|
326
453
|
digest: copyBytes(resolution.digest),
|
|
327
454
|
reason: resolution.status === "missing"
|
|
328
455
|
? "Accepted policy ancestry is unavailable from the resolver"
|
|
329
|
-
:
|
|
456
|
+
: resolution.status === "unavailable"
|
|
457
|
+
? `Accepted policy ancestry is unavailable: ${resolution.reason}`
|
|
458
|
+
: `Accepted policy ancestry validation failed: ${resolution.reason}`,
|
|
330
459
|
};
|
|
331
460
|
}
|
|
332
461
|
async evaluate(candidate) {
|
|
@@ -335,8 +464,9 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
335
464
|
let cursor = candidate;
|
|
336
465
|
while (cursor.body.sequence !== 0n) {
|
|
337
466
|
const parent = await this.parentOf(cursor, resolutionCache);
|
|
338
|
-
if (parent.status !== "found")
|
|
339
|
-
return parent;
|
|
467
|
+
if (parent.status !== "found") {
|
|
468
|
+
return this.candidateAncestryResult(parent);
|
|
469
|
+
}
|
|
340
470
|
cursor = parent.parent;
|
|
341
471
|
}
|
|
342
472
|
return { status: "accept" };
|
|
@@ -348,8 +478,9 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
348
478
|
while (candidateCursor.body.sequence > acceptedCursor.body.sequence) {
|
|
349
479
|
candidateChild = candidateCursor;
|
|
350
480
|
const parent = await this.parentOf(candidateCursor, resolutionCache);
|
|
351
|
-
if (parent.status !== "found")
|
|
352
|
-
return parent;
|
|
481
|
+
if (parent.status !== "found") {
|
|
482
|
+
return this.candidateAncestryResult(parent);
|
|
483
|
+
}
|
|
353
484
|
candidateCursor = parent.parent;
|
|
354
485
|
}
|
|
355
486
|
while (acceptedCursor.body.sequence > candidateCursor.body.sequence) {
|
|
@@ -377,8 +508,9 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
377
508
|
if (acceptedParent.status !== "found") {
|
|
378
509
|
return this.acceptedAncestryUnavailable(acceptedParent);
|
|
379
510
|
}
|
|
380
|
-
if (candidateParent.status !== "found")
|
|
381
|
-
return candidateParent;
|
|
511
|
+
if (candidateParent.status !== "found") {
|
|
512
|
+
return this.candidateAncestryResult(candidateParent);
|
|
513
|
+
}
|
|
382
514
|
candidateCursor = candidateParent.parent;
|
|
383
515
|
acceptedCursor = acceptedParent.parent;
|
|
384
516
|
}
|
|
@@ -490,6 +622,9 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
490
622
|
return retention;
|
|
491
623
|
}
|
|
492
624
|
async drainPending() {
|
|
625
|
+
if (this.lifecycleController.signal.aborted) {
|
|
626
|
+
return { status: "halted" };
|
|
627
|
+
}
|
|
493
628
|
let accepted = false;
|
|
494
629
|
let progress = true;
|
|
495
630
|
while (progress &&
|
|
@@ -502,6 +637,9 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
502
637
|
if (!this.pending.has(pending.snapshot.digestKey))
|
|
503
638
|
continue;
|
|
504
639
|
const evaluation = await this.evaluate(pending.snapshot);
|
|
640
|
+
if (this.lifecycleController.signal.aborted) {
|
|
641
|
+
return { status: "halted" };
|
|
642
|
+
}
|
|
505
643
|
if (evaluation.status === "missing") {
|
|
506
644
|
pending.missingParentDigest = copyBytes(evaluation.digest);
|
|
507
645
|
continue;
|
|
@@ -525,24 +663,47 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
525
663
|
return accepted ? { status: "accepted" } : undefined;
|
|
526
664
|
}
|
|
527
665
|
enqueueAdmission(operation) {
|
|
528
|
-
const result = this.admissionTail.then(
|
|
666
|
+
const result = this.admissionTail.then(async () => {
|
|
667
|
+
if (this.lifecycleController.signal.aborted) {
|
|
668
|
+
return this.haltedResult();
|
|
669
|
+
}
|
|
670
|
+
const admission = await operation();
|
|
671
|
+
return this.lifecycleController.signal.aborted
|
|
672
|
+
? this.haltedResult()
|
|
673
|
+
: admission;
|
|
674
|
+
});
|
|
529
675
|
this.admissionTail = result.then(() => { }, (_reason) => { });
|
|
530
676
|
return result;
|
|
531
677
|
}
|
|
532
|
-
ingest(
|
|
533
|
-
|
|
678
|
+
ingest(entryBytes) {
|
|
679
|
+
if (this.lifecycleController.signal.aborted) {
|
|
680
|
+
return Promise.resolve(this.haltedResult());
|
|
681
|
+
}
|
|
682
|
+
let capturedEntryBytes;
|
|
683
|
+
try {
|
|
684
|
+
// Capture at the API boundary, before this admission waits behind earlier
|
|
685
|
+
// work. Otherwise a caller could mutate a queued entry before validation.
|
|
686
|
+
capturedEntryBytes = capturePolicySnapshotEntryBytesV2(entryBytes);
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
const reason = validationMessage(error);
|
|
690
|
+
return this.enqueueAdmission(async () => this.result("rejected", reason));
|
|
691
|
+
}
|
|
692
|
+
return this.enqueueAdmission(() => this.ingestOne(capturedEntryBytes));
|
|
534
693
|
}
|
|
535
694
|
retryUnavailable() {
|
|
536
695
|
return this.enqueueAdmission(() => this.retryUnavailableOne());
|
|
537
696
|
}
|
|
538
|
-
async ingestOne(
|
|
697
|
+
async ingestOne(entryBytes) {
|
|
539
698
|
let snapshot;
|
|
540
699
|
try {
|
|
541
|
-
snapshot = await
|
|
700
|
+
snapshot = await authenticateCapturedPolicySnapshotEntryV2(entryBytes, this.descriptor);
|
|
542
701
|
}
|
|
543
702
|
catch (error) {
|
|
544
703
|
return this.result("rejected", validationMessage(error));
|
|
545
704
|
}
|
|
705
|
+
if (this.lifecycleController.signal.aborted)
|
|
706
|
+
return this.haltedResult();
|
|
546
707
|
if (this.fork !== undefined) {
|
|
547
708
|
this.observeAfterFork(snapshot);
|
|
548
709
|
return this.result("halted", "Policy reducer is halted by authority equivocation");
|
|
@@ -566,6 +727,8 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
566
727
|
return this.result("pending", "Policy snapshot is already pending");
|
|
567
728
|
}
|
|
568
729
|
const evaluation = await this.evaluate(snapshot);
|
|
730
|
+
if (this.lifecycleController.signal.aborted)
|
|
731
|
+
return this.haltedResult();
|
|
569
732
|
if (evaluation.status === "reject") {
|
|
570
733
|
return this.result("rejected", evaluation.reason);
|
|
571
734
|
}
|
|
@@ -583,7 +746,7 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
583
746
|
if (evaluation.status === "missing") {
|
|
584
747
|
const pending = this.addPending(snapshot, evaluation.digest);
|
|
585
748
|
return this.result(pending.retained ? "pending" : "capacity", pending.retained
|
|
586
|
-
? "Policy parent is missing"
|
|
749
|
+
? (evaluation.reason ?? "Policy parent is missing")
|
|
587
750
|
: "Policy pending capacity did not retain this candidate", pending.evictedPolicyDigests);
|
|
588
751
|
}
|
|
589
752
|
this.project(snapshot);
|
|
@@ -591,6 +754,8 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
591
754
|
this.result("accepted"));
|
|
592
755
|
}
|
|
593
756
|
async retryUnavailableOne() {
|
|
757
|
+
if (this.lifecycleController.signal.aborted)
|
|
758
|
+
return this.haltedResult();
|
|
594
759
|
if (this.fork !== undefined) {
|
|
595
760
|
return this.result("halted", "Policy reducer is halted by authority equivocation");
|
|
596
761
|
}
|
|
@@ -603,6 +768,8 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
603
768
|
return this.result("capacity", "Unavailable comparison candidate is not retained; re-ingest it before retrying");
|
|
604
769
|
}
|
|
605
770
|
const evaluation = await this.evaluate(pending.snapshot);
|
|
771
|
+
if (this.lifecycleController.signal.aborted)
|
|
772
|
+
return this.haltedResult();
|
|
606
773
|
if (evaluation.status === "unavailable") {
|
|
607
774
|
pending.missingParentDigest = copyBytes(evaluation.digest);
|
|
608
775
|
this.unavailable = {
|
|
@@ -618,7 +785,7 @@ export class TrustedNetworkV2PolicyReducer {
|
|
|
618
785
|
if (evaluation.status === "missing") {
|
|
619
786
|
pending.missingParentDigest = copyBytes(evaluation.digest);
|
|
620
787
|
status = "pending";
|
|
621
|
-
reason = "Policy parent is missing";
|
|
788
|
+
reason = evaluation.reason ?? "Policy parent is missing";
|
|
622
789
|
}
|
|
623
790
|
else {
|
|
624
791
|
this.pending.delete(pending.snapshot.digestKey);
|