@peerbit/trusted-network 6.0.106 → 6.0.108
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-anchor.d.ts +11 -1
- package/dist/src/v2-policy-anchor.d.ts.map +1 -1
- package/dist/src/v2-policy-anchor.js +304 -100
- package/dist/src/v2-policy-anchor.js.map +1 -1
- package/dist/src/v2-policy-engine.d.ts +10 -0
- package/dist/src/v2-policy-engine.d.ts.map +1 -1
- package/dist/src/v2-policy-engine.js +57 -12
- package/dist/src/v2-policy-engine.js.map +1 -1
- package/package.json +4 -3
- package/src/v2-policy-anchor.ts +406 -119
- package/src/v2-policy-engine.ts +76 -12
package/src/v2-policy-anchor.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
PolicySnapshotResolverV2,
|
|
16
16
|
} from "./v2-policy-engine.js";
|
|
17
17
|
import {
|
|
18
|
+
TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES,
|
|
18
19
|
TrustedNetworkV2PolicyReducer,
|
|
19
20
|
authenticatePolicySnapshotEntryV2,
|
|
20
21
|
} from "./v2-policy-engine.js";
|
|
@@ -41,9 +42,16 @@ export const TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_GENERATION_BYTES =
|
|
|
41
42
|
TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES * 4;
|
|
42
43
|
|
|
43
44
|
const GENERATION_KEY_PREFIX = `${TRUSTED_NETWORK_V2_POLICY_ANCHOR_STORE_OWNER}/generation/`;
|
|
44
|
-
const
|
|
45
|
+
const ANCHOR_FORMAT_VERSION = 2;
|
|
46
|
+
const REDUCER_DURABLE_FORMAT_VERSION = 1;
|
|
45
47
|
const MAX_U64 = 0xffffffffffffffffn;
|
|
46
48
|
const MAX_UNAVAILABLE_REASON_LENGTH = 512;
|
|
49
|
+
// The core admits at most 64 pending policies. A fork transition can surface
|
|
50
|
+
// that complete set plus its accepted and triggering children. Once the
|
|
51
|
+
// transition is durable the wrapper stops policy admission entirely.
|
|
52
|
+
const MAX_FORK_OBSERVATIONS_V2 = TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES + 2;
|
|
53
|
+
const MAX_QUEUED_POLICY_ENTRY_BYTES_V2 =
|
|
54
|
+
TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES * 2;
|
|
47
55
|
const ZERO_DIGEST = new Uint8Array(32);
|
|
48
56
|
const textEncoder = new TextEncoder();
|
|
49
57
|
const DESCRIPTOR_DIGEST_DOMAIN = textEncoder.encode(
|
|
@@ -52,6 +60,9 @@ const DESCRIPTOR_DIGEST_DOMAIN = textEncoder.encode(
|
|
|
52
60
|
const GENERATION_CHECKSUM_DOMAIN = textEncoder.encode(
|
|
53
61
|
"peerbit/trusted-network/v2/policy-anchor/generation/v1",
|
|
54
62
|
);
|
|
63
|
+
const FORK_OBSERVATION_COMMITMENT_DOMAIN = textEncoder.encode(
|
|
64
|
+
"peerbit/trusted-network/v2/policy-anchor/fork-observations/v1",
|
|
65
|
+
);
|
|
55
66
|
const ANCHOR_STATE = Object.freeze({
|
|
56
67
|
ACTIVE: 1,
|
|
57
68
|
UNAVAILABLE: 2,
|
|
@@ -133,7 +144,17 @@ class PolicyAnchorStateGenerationPayloadV2 {
|
|
|
133
144
|
@field({ type: PolicyAnchorCoreStateRecordV2 })
|
|
134
145
|
coreState: PolicyAnchorCoreStateRecordV2;
|
|
135
146
|
|
|
136
|
-
|
|
147
|
+
@field({ type: "u8" })
|
|
148
|
+
forkObservationCount: number;
|
|
149
|
+
|
|
150
|
+
@field({ type: fixedArray("u8", 32) })
|
|
151
|
+
forkObservationCommitment: Uint8Array;
|
|
152
|
+
|
|
153
|
+
constructor(properties?: {
|
|
154
|
+
coreState: PolicyAnchorCoreStateRecordV2;
|
|
155
|
+
forkObservationCount: number;
|
|
156
|
+
forkObservationCommitment: Uint8Array;
|
|
157
|
+
}) {
|
|
137
158
|
if (properties) Object.assign(this, properties);
|
|
138
159
|
}
|
|
139
160
|
}
|
|
@@ -208,7 +229,56 @@ type PublishedProjectionV2 = {
|
|
|
208
229
|
roles: Map<string, number>;
|
|
209
230
|
};
|
|
210
231
|
|
|
211
|
-
const
|
|
232
|
+
const TYPED_ARRAY_PROTOTYPE = Object.getPrototypeOf(Uint8Array.prototype);
|
|
233
|
+
const TYPED_ARRAY_BYTE_LENGTH = Object.getOwnPropertyDescriptor(
|
|
234
|
+
TYPED_ARRAY_PROTOTYPE,
|
|
235
|
+
"byteLength",
|
|
236
|
+
)!.get!;
|
|
237
|
+
const TYPED_ARRAY_TAG = Object.getOwnPropertyDescriptor(
|
|
238
|
+
TYPED_ARRAY_PROTOTYPE,
|
|
239
|
+
Symbol.toStringTag,
|
|
240
|
+
)!.get!;
|
|
241
|
+
const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView;
|
|
242
|
+
const UINT8_ARRAY_SET = Uint8Array.prototype.set;
|
|
243
|
+
|
|
244
|
+
const exactUint8ArrayByteLength = (input: unknown): number => {
|
|
245
|
+
if (
|
|
246
|
+
!ARRAY_BUFFER_IS_VIEW(input) ||
|
|
247
|
+
TYPED_ARRAY_TAG.call(input) !== "Uint8Array"
|
|
248
|
+
) {
|
|
249
|
+
throw new TypeError("Expected a genuine Uint8Array");
|
|
250
|
+
}
|
|
251
|
+
const byteLength = TYPED_ARRAY_BYTE_LENGTH.call(input) as number;
|
|
252
|
+
if (byteLength === 0) {
|
|
253
|
+
UINT8_ARRAY_SET.call(new Uint8Array(0), input as Uint8Array);
|
|
254
|
+
}
|
|
255
|
+
return byteLength;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const copyBytesWithLength = (
|
|
259
|
+
bytes: Uint8Array,
|
|
260
|
+
byteLength: number,
|
|
261
|
+
): Uint8Array => {
|
|
262
|
+
const copy = new Uint8Array(byteLength);
|
|
263
|
+
UINT8_ARRAY_SET.call(copy, bytes);
|
|
264
|
+
return copy;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const copyBytes = (bytes: Uint8Array): Uint8Array => {
|
|
268
|
+
const byteLength = exactUint8ArrayByteLength(bytes);
|
|
269
|
+
return copyBytesWithLength(bytes, byteLength);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const hasExactUint8ArrayByteLength = (
|
|
273
|
+
input: unknown,
|
|
274
|
+
expected: number,
|
|
275
|
+
): input is Uint8Array => {
|
|
276
|
+
try {
|
|
277
|
+
return exactUint8ArrayByteLength(input) === expected;
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
212
282
|
|
|
213
283
|
const bytesKey = (bytes: Uint8Array): string => {
|
|
214
284
|
let key = "";
|
|
@@ -262,6 +332,44 @@ const generationChecksum = (body: PolicyAnchorGenerationBodyV2): Uint8Array =>
|
|
|
262
332
|
const observationContentHash = (entryBytes: Uint8Array): Uint8Array =>
|
|
263
333
|
sha256Sync(entryBytes);
|
|
264
334
|
|
|
335
|
+
const u32Bytes = (value: number): Uint8Array => {
|
|
336
|
+
const bytes = new Uint8Array(4);
|
|
337
|
+
new DataView(bytes.buffer).setUint32(0, value, false);
|
|
338
|
+
return bytes;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const canonicalForkObservationEntries = (
|
|
342
|
+
entries: Iterable<Uint8Array>,
|
|
343
|
+
): Uint8Array[] =>
|
|
344
|
+
[...entries]
|
|
345
|
+
.map((entryBytes) => ({
|
|
346
|
+
entryBytes: copyBytes(entryBytes),
|
|
347
|
+
hash: observationContentHash(entryBytes),
|
|
348
|
+
}))
|
|
349
|
+
.sort((left, right) => {
|
|
350
|
+
const hashOrder = compare(left.hash, right.hash);
|
|
351
|
+
return hashOrder === 0
|
|
352
|
+
? compare(left.entryBytes, right.entryBytes)
|
|
353
|
+
: hashOrder;
|
|
354
|
+
})
|
|
355
|
+
.map(({ entryBytes }) => entryBytes);
|
|
356
|
+
|
|
357
|
+
const forkObservationCommitment = (
|
|
358
|
+
descriptorHash: Uint8Array,
|
|
359
|
+
canonicalEntries: readonly Uint8Array[],
|
|
360
|
+
): Uint8Array =>
|
|
361
|
+
sha256Sync(
|
|
362
|
+
concat([
|
|
363
|
+
FORK_OBSERVATION_COMMITMENT_DOMAIN,
|
|
364
|
+
descriptorHash,
|
|
365
|
+
Uint8Array.of(canonicalEntries.length),
|
|
366
|
+
...canonicalEntries.flatMap((entryBytes) => [
|
|
367
|
+
u32Bytes(entryBytes.byteLength),
|
|
368
|
+
entryBytes,
|
|
369
|
+
]),
|
|
370
|
+
]),
|
|
371
|
+
);
|
|
372
|
+
|
|
265
373
|
const generationKey = (generation: bigint): string =>
|
|
266
374
|
`${GENERATION_KEY_PREFIX}${generation.toString().padStart(20, "0")}`;
|
|
267
375
|
|
|
@@ -272,10 +380,15 @@ const assertOpenNotAborted = (signal: AbortSignal | undefined): void => {
|
|
|
272
380
|
};
|
|
273
381
|
|
|
274
382
|
const assertEntryBytes = (bytes: Uint8Array, label: string): void => {
|
|
383
|
+
let byteLength: number;
|
|
384
|
+
try {
|
|
385
|
+
byteLength = exactUint8ArrayByteLength(bytes);
|
|
386
|
+
} catch {
|
|
387
|
+
byteLength = -1;
|
|
388
|
+
}
|
|
275
389
|
if (
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
bytes.byteLength > TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES
|
|
390
|
+
byteLength < 1 ||
|
|
391
|
+
byteLength > TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES
|
|
279
392
|
) {
|
|
280
393
|
throw new Error(
|
|
281
394
|
`${label} must contain 1-${TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES} bytes`,
|
|
@@ -312,7 +425,7 @@ const retainCanonicalForkChild = (
|
|
|
312
425
|
};
|
|
313
426
|
|
|
314
427
|
const assertEmptyBytes = (bytes: Uint8Array, label: string): void => {
|
|
315
|
-
if (!(bytes
|
|
428
|
+
if (!hasExactUint8ArrayByteLength(bytes, 0)) {
|
|
316
429
|
throw new Error(`${label} must be empty`);
|
|
317
430
|
}
|
|
318
431
|
};
|
|
@@ -373,7 +486,7 @@ const durableStateFromCoreRecord = (
|
|
|
373
486
|
assertEmptyBytes(record.forkChildEntryBytes0, "active fork child 0");
|
|
374
487
|
assertEmptyBytes(record.forkChildEntryBytes1, "active fork child 1");
|
|
375
488
|
return {
|
|
376
|
-
formatVersion:
|
|
489
|
+
formatVersion: REDUCER_DURABLE_FORMAT_VERSION,
|
|
377
490
|
state: "ACTIVE",
|
|
378
491
|
acceptedHeadEntryBytes: copyBytes(record.acceptedHeadEntryBytes),
|
|
379
492
|
};
|
|
@@ -383,7 +496,7 @@ const durableStateFromCoreRecord = (
|
|
|
383
496
|
record.comparisonCandidateEntryBytes,
|
|
384
497
|
"unavailable comparison candidate",
|
|
385
498
|
);
|
|
386
|
-
if (record.acceptedAncestorDigest
|
|
499
|
+
if (!hasExactUint8ArrayByteLength(record.acceptedAncestorDigest, 32)) {
|
|
387
500
|
throw new Error("Unavailable accepted-ancestor digest must be 32 bytes");
|
|
388
501
|
}
|
|
389
502
|
if (
|
|
@@ -397,7 +510,7 @@ const durableStateFromCoreRecord = (
|
|
|
397
510
|
assertEmptyBytes(record.forkChildEntryBytes0, "unavailable fork child 0");
|
|
398
511
|
assertEmptyBytes(record.forkChildEntryBytes1, "unavailable fork child 1");
|
|
399
512
|
return {
|
|
400
|
-
formatVersion:
|
|
513
|
+
formatVersion: REDUCER_DURABLE_FORMAT_VERSION,
|
|
401
514
|
state: "UNAVAILABLE",
|
|
402
515
|
acceptedHeadEntryBytes: copyBytes(record.acceptedHeadEntryBytes),
|
|
403
516
|
comparisonCandidateEntryBytes: copyBytes(
|
|
@@ -421,7 +534,7 @@ const durableStateFromCoreRecord = (
|
|
|
421
534
|
assertEntryBytes(record.forkChildEntryBytes0, "fork child 0");
|
|
422
535
|
assertEntryBytes(record.forkChildEntryBytes1, "fork child 1");
|
|
423
536
|
return {
|
|
424
|
-
formatVersion:
|
|
537
|
+
formatVersion: REDUCER_DURABLE_FORMAT_VERSION,
|
|
425
538
|
state: "FORKED",
|
|
426
539
|
commonParentEntryBytes: copyBytes(record.acceptedHeadEntryBytes),
|
|
427
540
|
childEntryBytes: [
|
|
@@ -450,14 +563,16 @@ const decodeCanonicalPayload = <T>(
|
|
|
450
563
|
maxBytes: number,
|
|
451
564
|
label: string,
|
|
452
565
|
): T => {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
566
|
+
let byteLength: number;
|
|
567
|
+
try {
|
|
568
|
+
byteLength = exactUint8ArrayByteLength(input);
|
|
569
|
+
} catch {
|
|
570
|
+
byteLength = -1;
|
|
571
|
+
}
|
|
572
|
+
if (byteLength < 1 || byteLength > maxBytes) {
|
|
458
573
|
throw new Error(`${label} exceeds its byte ceiling`);
|
|
459
574
|
}
|
|
460
|
-
const bytes =
|
|
575
|
+
const bytes = copyBytesWithLength(input, byteLength);
|
|
461
576
|
const payload = deserialize(bytes, type);
|
|
462
577
|
if (!equals(bytes, serialize(payload))) {
|
|
463
578
|
throw new Error(`${label} is not canonical`);
|
|
@@ -475,10 +590,28 @@ const decodeGenerationPayload = (
|
|
|
475
590
|
MAX_STATE_GENERATION_PAYLOAD_BYTES,
|
|
476
591
|
"Policy-anchor state payload",
|
|
477
592
|
);
|
|
593
|
+
const durableState = durableStateFromCoreRecord(payload.coreState);
|
|
594
|
+
if (durableState.state === "FORKED") {
|
|
595
|
+
if (
|
|
596
|
+
payload.forkObservationCount < 2 ||
|
|
597
|
+
payload.forkObservationCount > MAX_FORK_OBSERVATIONS_V2
|
|
598
|
+
) {
|
|
599
|
+
throw new Error(
|
|
600
|
+
`Forked policy-anchor state must commit to 2-${MAX_FORK_OBSERVATIONS_V2} observations`,
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
} else if (
|
|
604
|
+
payload.forkObservationCount !== 0 ||
|
|
605
|
+
!equals(payload.forkObservationCommitment, ZERO_DIGEST)
|
|
606
|
+
) {
|
|
607
|
+
throw new Error(
|
|
608
|
+
"Non-forked policy-anchor state must not contain a fork commitment",
|
|
609
|
+
);
|
|
610
|
+
}
|
|
478
611
|
return {
|
|
479
612
|
kind: "state",
|
|
480
613
|
payload,
|
|
481
|
-
durableState
|
|
614
|
+
durableState,
|
|
482
615
|
};
|
|
483
616
|
}
|
|
484
617
|
if (body.kind === GENERATION_KIND.FORK_OBSERVATION) {
|
|
@@ -500,19 +633,24 @@ const decodeGenerationRecord = (
|
|
|
500
633
|
record: PolicyAnchorGenerationRecordV2;
|
|
501
634
|
payload: DecodedGenerationPayloadV2;
|
|
502
635
|
} => {
|
|
636
|
+
let byteLength: number;
|
|
637
|
+
try {
|
|
638
|
+
byteLength = exactUint8ArrayByteLength(input);
|
|
639
|
+
} catch {
|
|
640
|
+
byteLength = -1;
|
|
641
|
+
}
|
|
503
642
|
if (
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
input.byteLength > TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_GENERATION_BYTES
|
|
643
|
+
byteLength < 1 ||
|
|
644
|
+
byteLength > TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_GENERATION_BYTES
|
|
507
645
|
) {
|
|
508
646
|
throw new Error("Policy-anchor generation record exceeds its byte ceiling");
|
|
509
647
|
}
|
|
510
|
-
const bytes =
|
|
648
|
+
const bytes = copyBytesWithLength(input, byteLength);
|
|
511
649
|
const record = deserialize(bytes, PolicyAnchorGenerationRecordV2);
|
|
512
650
|
if (!equals(bytes, serialize(record))) {
|
|
513
651
|
throw new Error("Policy-anchor generation record is not canonical");
|
|
514
652
|
}
|
|
515
|
-
if (record.body.formatVersion !==
|
|
653
|
+
if (record.body.formatVersion !== ANCHOR_FORMAT_VERSION) {
|
|
516
654
|
throw new Error("Unsupported policy-anchor generation format");
|
|
517
655
|
}
|
|
518
656
|
if (!equals(record.checksum, generationChecksum(record.body))) {
|
|
@@ -571,9 +709,10 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
571
709
|
private generation = 0n;
|
|
572
710
|
private previousGenerationChecksum = copyBytes(ZERO_DIGEST);
|
|
573
711
|
private durableCoreBytes?: Uint8Array;
|
|
574
|
-
private observedHashes = new Set<string>();
|
|
575
712
|
private operationTail: Promise<void> = Promise.resolve();
|
|
576
713
|
private authorizationFences = 0;
|
|
714
|
+
private bufferedAdmissions = 0;
|
|
715
|
+
private bufferedAdmissionEntryBytes = 0;
|
|
577
716
|
private terminalError?: Error;
|
|
578
717
|
|
|
579
718
|
private constructor(properties: {
|
|
@@ -584,7 +723,6 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
584
723
|
generation?: bigint;
|
|
585
724
|
previousGenerationChecksum?: Uint8Array;
|
|
586
725
|
durableCoreBytes?: Uint8Array;
|
|
587
|
-
observedHashes?: ReadonlySet<string>;
|
|
588
726
|
}) {
|
|
589
727
|
this.store = properties.store;
|
|
590
728
|
this.durability = properties.durability;
|
|
@@ -603,7 +741,6 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
603
741
|
properties.durableCoreBytes === undefined
|
|
604
742
|
? undefined
|
|
605
743
|
: copyBytes(properties.durableCoreBytes);
|
|
606
|
-
this.observedHashes = new Set(properties.observedHashes);
|
|
607
744
|
}
|
|
608
745
|
|
|
609
746
|
static async open(
|
|
@@ -652,11 +789,15 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
652
789
|
if (!/^\d{20}$/.test(suffix)) {
|
|
653
790
|
throw new Error("Malformed policy-anchor generation key");
|
|
654
791
|
}
|
|
792
|
+
let byteLength: number;
|
|
793
|
+
try {
|
|
794
|
+
byteLength = exactUint8ArrayByteLength(input);
|
|
795
|
+
} catch {
|
|
796
|
+
byteLength = -1;
|
|
797
|
+
}
|
|
655
798
|
if (
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
input.byteLength >
|
|
659
|
-
TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_GENERATION_BYTES
|
|
799
|
+
byteLength < 1 ||
|
|
800
|
+
byteLength > TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_GENERATION_BYTES
|
|
660
801
|
) {
|
|
661
802
|
throw new Error(
|
|
662
803
|
"Policy-anchor generation record exceeds its byte ceiling",
|
|
@@ -687,8 +828,9 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
687
828
|
let core: TrustedNetworkV2PolicyReducer | undefined;
|
|
688
829
|
let durableCoreBytes: Uint8Array | undefined;
|
|
689
830
|
let forkEvidence: PolicyForkEvidenceV2 | undefined;
|
|
690
|
-
|
|
691
|
-
|
|
831
|
+
let expectedForkObservationCount: number | undefined;
|
|
832
|
+
let expectedForkObservationCommitment: Uint8Array | undefined;
|
|
833
|
+
const observedEntries = new Map<string, Uint8Array>();
|
|
692
834
|
const canonicalChildren: CanonicalForkChildV2[] = [];
|
|
693
835
|
try {
|
|
694
836
|
for (let index = 0; index < generationKeys.length; index++) {
|
|
@@ -731,6 +873,10 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
731
873
|
latestCoreBytes = serialize(payload.payload.coreState);
|
|
732
874
|
latestDurableState = payload.durableState;
|
|
733
875
|
if (latestDurableState.state === "FORKED") {
|
|
876
|
+
expectedForkObservationCount = payload.payload.forkObservationCount;
|
|
877
|
+
expectedForkObservationCommitment = copyBytes(
|
|
878
|
+
payload.payload.forkObservationCommitment,
|
|
879
|
+
);
|
|
734
880
|
core = await TrustedNetworkV2PolicyReducer.restore({
|
|
735
881
|
...coreProperties,
|
|
736
882
|
durableState: latestDurableState,
|
|
@@ -748,17 +894,12 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
748
894
|
const hashKey = bytesKey(
|
|
749
895
|
observationContentHash(child.entryBytes),
|
|
750
896
|
);
|
|
751
|
-
|
|
752
|
-
if (
|
|
753
|
-
retained !== undefined &&
|
|
754
|
-
!equals(retained, child.entryBytes)
|
|
755
|
-
) {
|
|
897
|
+
if (observedEntries.has(hashKey)) {
|
|
756
898
|
throw new Error(
|
|
757
|
-
"
|
|
899
|
+
"Duplicate policy fork observation in durable state",
|
|
758
900
|
);
|
|
759
901
|
}
|
|
760
|
-
|
|
761
|
-
observedHashes.add(hashKey);
|
|
902
|
+
observedEntries.set(hashKey, copyBytes(child.entryBytes));
|
|
762
903
|
retainCanonicalForkChild(canonicalChildren, child);
|
|
763
904
|
}
|
|
764
905
|
}
|
|
@@ -774,38 +915,36 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
774
915
|
}
|
|
775
916
|
const entryBytes = payload.payload.entryBytes;
|
|
776
917
|
const hashKey = bytesKey(observationContentHash(entryBytes));
|
|
777
|
-
if (
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
throw new Error("Policy fork-observation content hash collision");
|
|
784
|
-
}
|
|
785
|
-
} else {
|
|
786
|
-
const authenticated = await authenticatePolicySnapshotEntryV2(
|
|
787
|
-
entryBytes,
|
|
788
|
-
descriptor,
|
|
918
|
+
if (observedEntries.has(hashKey)) {
|
|
919
|
+
throw new Error("Duplicate policy fork-observation generation");
|
|
920
|
+
}
|
|
921
|
+
if (observedEntries.size >= MAX_FORK_OBSERVATIONS_V2) {
|
|
922
|
+
throw new Error(
|
|
923
|
+
`Policy fork evidence exceeds ${MAX_FORK_OBSERVATIONS_V2} children`,
|
|
789
924
|
);
|
|
790
|
-
assertOpenNotAborted(signal);
|
|
791
|
-
if (
|
|
792
|
-
authenticated.body.sequence !==
|
|
793
|
-
forkEvidence.commonParent.sequence + 1n ||
|
|
794
|
-
!equals(
|
|
795
|
-
authenticated.body.previousPolicyDigest,
|
|
796
|
-
forkEvidence.commonParent.digest,
|
|
797
|
-
)
|
|
798
|
-
) {
|
|
799
|
-
throw new Error(
|
|
800
|
-
"Stored policy fork observation is not a direct child of the common parent",
|
|
801
|
-
);
|
|
802
|
-
}
|
|
803
|
-
observedHashes.add(hashKey);
|
|
804
|
-
retainCanonicalForkChild(canonicalChildren, {
|
|
805
|
-
digest: authenticated.digest,
|
|
806
|
-
entryBytes,
|
|
807
|
-
});
|
|
808
925
|
}
|
|
926
|
+
const authenticated = await authenticatePolicySnapshotEntryV2(
|
|
927
|
+
entryBytes,
|
|
928
|
+
descriptor,
|
|
929
|
+
);
|
|
930
|
+
assertOpenNotAborted(signal);
|
|
931
|
+
if (
|
|
932
|
+
authenticated.body.sequence !==
|
|
933
|
+
forkEvidence.commonParent.sequence + 1n ||
|
|
934
|
+
!equals(
|
|
935
|
+
authenticated.body.previousPolicyDigest,
|
|
936
|
+
forkEvidence.commonParent.digest,
|
|
937
|
+
)
|
|
938
|
+
) {
|
|
939
|
+
throw new Error(
|
|
940
|
+
"Stored policy fork observation is not a direct child of the common parent",
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
observedEntries.set(hashKey, copyBytes(entryBytes));
|
|
944
|
+
retainCanonicalForkChild(canonicalChildren, {
|
|
945
|
+
digest: authenticated.digest,
|
|
946
|
+
entryBytes,
|
|
947
|
+
});
|
|
809
948
|
}
|
|
810
949
|
previousChecksum = copyBytes(record.checksum);
|
|
811
950
|
}
|
|
@@ -832,9 +971,25 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
832
971
|
}
|
|
833
972
|
|
|
834
973
|
if (latestDurableState?.state === "FORKED") {
|
|
835
|
-
if (
|
|
974
|
+
if (
|
|
975
|
+
forkEvidence === undefined ||
|
|
976
|
+
expectedForkObservationCount === undefined ||
|
|
977
|
+
expectedForkObservationCommitment === undefined
|
|
978
|
+
) {
|
|
836
979
|
throw new Error("Restored forked policy anchor has no fork evidence");
|
|
837
980
|
}
|
|
981
|
+
if (observedEntries.size !== expectedForkObservationCount) {
|
|
982
|
+
throw new Error(
|
|
983
|
+
`Policy fork evidence is incomplete: expected ${expectedForkObservationCount}, restored ${observedEntries.size}`,
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
const restoredCommitment = forkObservationCommitment(
|
|
987
|
+
expectedDescriptorHash,
|
|
988
|
+
canonicalForkObservationEntries(observedEntries.values()),
|
|
989
|
+
);
|
|
990
|
+
if (!equals(restoredCommitment, expectedForkObservationCommitment)) {
|
|
991
|
+
throw new Error("Policy fork evidence commitment mismatch");
|
|
992
|
+
}
|
|
838
993
|
if (canonicalChildren.length !== 2) {
|
|
839
994
|
throw new Error(
|
|
840
995
|
"Stored policy fork evidence has fewer than two distinct children",
|
|
@@ -844,7 +999,7 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
844
999
|
PolicyReducerDurableStateV2,
|
|
845
1000
|
{ state: "FORKED" }
|
|
846
1001
|
> = {
|
|
847
|
-
formatVersion:
|
|
1002
|
+
formatVersion: REDUCER_DURABLE_FORMAT_VERSION,
|
|
848
1003
|
state: "FORKED",
|
|
849
1004
|
commonParentEntryBytes: copyBytes(
|
|
850
1005
|
latestDurableState.commonParentEntryBytes,
|
|
@@ -876,7 +1031,7 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
876
1031
|
}
|
|
877
1032
|
latestDurableState = normalizedForkState;
|
|
878
1033
|
durableCoreBytes = serialize(coreRecordFromState(normalizedForkState));
|
|
879
|
-
} else if (
|
|
1034
|
+
} else if (observedEntries.size !== 0) {
|
|
880
1035
|
throw new Error("Non-forked policy anchor has fork observations");
|
|
881
1036
|
}
|
|
882
1037
|
|
|
@@ -888,7 +1043,6 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
888
1043
|
generation: highestGeneration,
|
|
889
1044
|
previousGenerationChecksum: previousChecksum,
|
|
890
1045
|
durableCoreBytes,
|
|
891
|
-
observedHashes,
|
|
892
1046
|
});
|
|
893
1047
|
} catch (error) {
|
|
894
1048
|
core?.abort();
|
|
@@ -897,9 +1051,11 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
897
1051
|
}
|
|
898
1052
|
|
|
899
1053
|
get state(): "EMPTY" | "ACTIVE" | "UNAVAILABLE" | "FORKED" | "HALTED" {
|
|
900
|
-
if (this.terminalError !== undefined
|
|
1054
|
+
if (this.terminalError !== undefined) {
|
|
901
1055
|
return "HALTED";
|
|
902
1056
|
}
|
|
1057
|
+
if (this.published.state === "FORKED") return "FORKED";
|
|
1058
|
+
if (this.core.state === "HALTED") return "HALTED";
|
|
903
1059
|
return this.published.state;
|
|
904
1060
|
}
|
|
905
1061
|
|
|
@@ -923,6 +1079,16 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
923
1079
|
return this.core.pendingDigests.map(copyBytes);
|
|
924
1080
|
}
|
|
925
1081
|
|
|
1082
|
+
/** Internal diagnostics for the fixed pre-publication admission bound. */
|
|
1083
|
+
get bufferedAdmissionCount(): number {
|
|
1084
|
+
return this.bufferedAdmissions;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/** Internal diagnostics for copied entry bytes awaiting settlement. */
|
|
1088
|
+
get bufferedAdmissionBytes(): number {
|
|
1089
|
+
return this.bufferedAdmissionEntryBytes;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
926
1092
|
/** Projection query only; use isAuthorized() for the fail-closed gate. */
|
|
927
1093
|
rolesFor(subject: PublicSignKey): number {
|
|
928
1094
|
return this.published.roles.get(keyId(subject)) ?? 0;
|
|
@@ -946,27 +1112,148 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
946
1112
|
}
|
|
947
1113
|
|
|
948
1114
|
ingest(entryBytes: Uint8Array): Promise<PolicyAdmissionResultV2> {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1115
|
+
if (this.terminalError !== undefined) {
|
|
1116
|
+
return Promise.reject(this.terminalError);
|
|
1117
|
+
}
|
|
1118
|
+
if (this.published.state === "FORKED") {
|
|
1119
|
+
return Promise.resolve(this.forkFailStopResult());
|
|
1120
|
+
}
|
|
1121
|
+
if (this.core.state === "HALTED") {
|
|
1122
|
+
return Promise.resolve(this.lifecycleHaltedResult());
|
|
1123
|
+
}
|
|
1124
|
+
let reservedEntryBytes: number;
|
|
1125
|
+
try {
|
|
1126
|
+
reservedEntryBytes = exactUint8ArrayByteLength(entryBytes);
|
|
1127
|
+
} catch {
|
|
1128
|
+
return Promise.resolve(
|
|
1129
|
+
this.immediateAdmissionResult(
|
|
1130
|
+
"rejected",
|
|
1131
|
+
"Policy snapshot entry must be a Uint8Array",
|
|
1132
|
+
),
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
if (reservedEntryBytes > TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES) {
|
|
1136
|
+
return Promise.resolve(
|
|
1137
|
+
this.immediateAdmissionResult(
|
|
1138
|
+
"rejected",
|
|
1139
|
+
`Policy snapshot entry exceeds ${TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES} bytes`,
|
|
1140
|
+
),
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
if (!this.reserveAdmission(reservedEntryBytes)) {
|
|
1144
|
+
return Promise.resolve(
|
|
1145
|
+
this.immediateAdmissionResult(
|
|
1146
|
+
"capacity",
|
|
1147
|
+
"Durable policy admission queue is at its fixed capacity",
|
|
1148
|
+
this.core.pendingCount,
|
|
1149
|
+
this.core.pendingBytes,
|
|
1150
|
+
),
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
let captured: Uint8Array;
|
|
1154
|
+
try {
|
|
1155
|
+
captured = copyBytesWithLength(entryBytes, reservedEntryBytes);
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
this.releaseAdmission(reservedEntryBytes);
|
|
1158
|
+
return Promise.reject(error);
|
|
1159
|
+
}
|
|
954
1160
|
return this.enqueue(async () => {
|
|
1161
|
+
if (this.published.state === "FORKED") {
|
|
1162
|
+
return this.forkFailStopResult();
|
|
1163
|
+
}
|
|
1164
|
+
if (this.core.state === "HALTED") {
|
|
1165
|
+
return this.lifecycleHaltedResult();
|
|
1166
|
+
}
|
|
955
1167
|
const result = await this.core.ingest(captured);
|
|
956
1168
|
await this.persistCorePublication(result);
|
|
957
1169
|
return result;
|
|
958
|
-
});
|
|
1170
|
+
}, reservedEntryBytes);
|
|
959
1171
|
}
|
|
960
1172
|
|
|
961
1173
|
retryUnavailable(): Promise<PolicyAdmissionResultV2> {
|
|
1174
|
+
if (this.terminalError !== undefined) {
|
|
1175
|
+
return Promise.reject(this.terminalError);
|
|
1176
|
+
}
|
|
1177
|
+
if (this.published.state === "FORKED") {
|
|
1178
|
+
return Promise.resolve(this.forkFailStopResult());
|
|
1179
|
+
}
|
|
1180
|
+
if (this.core.state === "HALTED") {
|
|
1181
|
+
return Promise.resolve(this.lifecycleHaltedResult());
|
|
1182
|
+
}
|
|
1183
|
+
if (!this.reserveAdmission(0)) {
|
|
1184
|
+
return Promise.resolve(
|
|
1185
|
+
this.immediateAdmissionResult(
|
|
1186
|
+
"capacity",
|
|
1187
|
+
"Durable policy admission queue is at its fixed capacity",
|
|
1188
|
+
this.core.pendingCount,
|
|
1189
|
+
this.core.pendingBytes,
|
|
1190
|
+
),
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
962
1193
|
return this.enqueue(async () => {
|
|
1194
|
+
if (this.published.state === "FORKED") {
|
|
1195
|
+
return this.forkFailStopResult();
|
|
1196
|
+
}
|
|
1197
|
+
if (this.core.state === "HALTED") {
|
|
1198
|
+
return this.lifecycleHaltedResult();
|
|
1199
|
+
}
|
|
963
1200
|
const result = await this.core.retryUnavailable();
|
|
964
1201
|
await this.persistCorePublication(result);
|
|
965
1202
|
return result;
|
|
966
|
-
});
|
|
1203
|
+
}, 0);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
private forkFailStopResult(): PolicyAdmissionResultV2 {
|
|
1207
|
+
return {
|
|
1208
|
+
status: "halted",
|
|
1209
|
+
reason: "Policy reducer is halted by authority equivocation",
|
|
1210
|
+
fetchHints: [],
|
|
1211
|
+
pendingCount: 0,
|
|
1212
|
+
pendingBytes: 0,
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
private lifecycleHaltedResult(): PolicyAdmissionResultV2 {
|
|
1217
|
+
return {
|
|
1218
|
+
status: "halted",
|
|
1219
|
+
reason: "Policy reducer lifecycle is aborted",
|
|
1220
|
+
fetchHints: [],
|
|
1221
|
+
pendingCount: this.core.pendingCount,
|
|
1222
|
+
pendingBytes: this.core.pendingBytes,
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
private immediateAdmissionResult(
|
|
1227
|
+
status: "capacity" | "rejected",
|
|
1228
|
+
reason: string,
|
|
1229
|
+
pendingCount = 0,
|
|
1230
|
+
pendingBytes = 0,
|
|
1231
|
+
): PolicyAdmissionResultV2 {
|
|
1232
|
+
return { status, reason, fetchHints: [], pendingCount, pendingBytes };
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
private reserveAdmission(entryBytes: number): boolean {
|
|
1236
|
+
if (
|
|
1237
|
+
this.bufferedAdmissions >= TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES ||
|
|
1238
|
+
entryBytes >
|
|
1239
|
+
MAX_QUEUED_POLICY_ENTRY_BYTES_V2 - this.bufferedAdmissionEntryBytes
|
|
1240
|
+
) {
|
|
1241
|
+
return false;
|
|
1242
|
+
}
|
|
1243
|
+
this.bufferedAdmissions += 1;
|
|
1244
|
+
this.bufferedAdmissionEntryBytes += entryBytes;
|
|
1245
|
+
return true;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
private releaseAdmission(entryBytes: number): void {
|
|
1249
|
+
this.bufferedAdmissions -= 1;
|
|
1250
|
+
this.bufferedAdmissionEntryBytes -= entryBytes;
|
|
967
1251
|
}
|
|
968
1252
|
|
|
969
|
-
private enqueue<T>(
|
|
1253
|
+
private enqueue<T>(
|
|
1254
|
+
operation: () => Promise<T>,
|
|
1255
|
+
bufferedEntryBytes: number,
|
|
1256
|
+
): Promise<T> {
|
|
970
1257
|
this.authorizationFences++;
|
|
971
1258
|
const result = this.operationTail.then(async () => {
|
|
972
1259
|
if (this.terminalError !== undefined) throw this.terminalError;
|
|
@@ -982,6 +1269,7 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
982
1269
|
);
|
|
983
1270
|
return result.finally(() => {
|
|
984
1271
|
this.authorizationFences--;
|
|
1272
|
+
this.releaseAdmission(bufferedEntryBytes);
|
|
985
1273
|
});
|
|
986
1274
|
}
|
|
987
1275
|
|
|
@@ -1010,13 +1298,9 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1010
1298
|
|
|
1011
1299
|
const coreRecord = coreRecordFromState(durableState);
|
|
1012
1300
|
const coreBytes = serialize(coreRecord);
|
|
1013
|
-
// FORKED is terminal. Once its common-parent anchor exists, later child
|
|
1014
|
-
// proofs are observation deltas even when they change the live canonical
|
|
1015
|
-
// pair; reopen derives that pair from the complete delta history.
|
|
1016
1301
|
const stateChanged =
|
|
1017
|
-
|
|
1018
|
-
(this.durableCoreBytes
|
|
1019
|
-
!equals(this.durableCoreBytes, coreBytes));
|
|
1302
|
+
this.durableCoreBytes === undefined ||
|
|
1303
|
+
!equals(this.durableCoreBytes, coreBytes);
|
|
1020
1304
|
const suppliedObservations = new Map<string, Uint8Array>();
|
|
1021
1305
|
for (const proof of result.forkObservations ?? []) {
|
|
1022
1306
|
const contentHash = observationContentHash(proof.entryBytes);
|
|
@@ -1027,31 +1311,45 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1027
1311
|
}
|
|
1028
1312
|
suppliedObservations.set(hashKey, copyBytes(proof.entryBytes));
|
|
1029
1313
|
}
|
|
1314
|
+
let committedForkObservationCount = 0;
|
|
1315
|
+
let committedForkObservationDigest = copyBytes(ZERO_DIGEST);
|
|
1030
1316
|
if (durableState.state !== "FORKED") {
|
|
1031
|
-
if (suppliedObservations.size !== 0
|
|
1317
|
+
if (suppliedObservations.size !== 0) {
|
|
1032
1318
|
throw this.halt("Only a forked policy anchor may contain observations");
|
|
1033
1319
|
}
|
|
1034
1320
|
} else {
|
|
1321
|
+
if (this.published.state === "FORKED" || !stateChanged) {
|
|
1322
|
+
throw this.halt("A durable fork transition may be published only once");
|
|
1323
|
+
}
|
|
1324
|
+
if (
|
|
1325
|
+
suppliedObservations.size < 2 ||
|
|
1326
|
+
suppliedObservations.size > MAX_FORK_OBSERVATIONS_V2
|
|
1327
|
+
) {
|
|
1328
|
+
throw this.halt(
|
|
1329
|
+
`A durable fork transition must contain 2-${MAX_FORK_OBSERVATIONS_V2} distinct observations`,
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
const completeObservationEntries = canonicalForkObservationEntries(
|
|
1333
|
+
suppliedObservations.values(),
|
|
1334
|
+
);
|
|
1335
|
+
committedForkObservationCount = completeObservationEntries.length;
|
|
1336
|
+
committedForkObservationDigest = forkObservationCommitment(
|
|
1337
|
+
this.descriptorHash,
|
|
1338
|
+
completeObservationEntries,
|
|
1339
|
+
);
|
|
1035
1340
|
for (const entryBytes of durableState.childEntryBytes) {
|
|
1036
1341
|
const hashKey = bytesKey(observationContentHash(entryBytes));
|
|
1037
|
-
if (
|
|
1038
|
-
else if (
|
|
1039
|
-
!this.observedHashes.has(hashKey) &&
|
|
1040
|
-
!suppliedObservations.has(hashKey)
|
|
1041
|
-
) {
|
|
1342
|
+
if (!suppliedObservations.delete(hashKey)) {
|
|
1042
1343
|
throw this.halt(
|
|
1043
1344
|
"Published fork state is missing a canonical observation",
|
|
1044
1345
|
);
|
|
1045
1346
|
}
|
|
1046
1347
|
}
|
|
1047
1348
|
}
|
|
1048
|
-
for (const hashKey of this.observedHashes) {
|
|
1049
|
-
suppliedObservations.delete(hashKey);
|
|
1050
|
-
}
|
|
1051
1349
|
|
|
1052
|
-
const observationEntries =
|
|
1053
|
-
.
|
|
1054
|
-
|
|
1350
|
+
const observationEntries = canonicalForkObservationEntries(
|
|
1351
|
+
suppliedObservations.values(),
|
|
1352
|
+
);
|
|
1055
1353
|
const recordCount = (stateChanged ? 1 : 0) + observationEntries.length;
|
|
1056
1354
|
if (recordCount === 0) return;
|
|
1057
1355
|
if (BigInt(recordCount) > MAX_U64 - this.generation) {
|
|
@@ -1060,22 +1358,24 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1060
1358
|
|
|
1061
1359
|
let nextGeneration = this.generation;
|
|
1062
1360
|
let previousChecksum = copyBytes(this.previousGenerationChecksum);
|
|
1063
|
-
const stagedObservedHashes = new Set(this.observedHashes);
|
|
1064
1361
|
const drafts: Array<{
|
|
1065
1362
|
kind: number;
|
|
1066
1363
|
payloadBytes: Uint8Array;
|
|
1067
|
-
applyObservationHash?: string;
|
|
1068
1364
|
}> = [];
|
|
1069
1365
|
if (stateChanged) {
|
|
1070
1366
|
const payloadBytes = serialize(
|
|
1071
|
-
new PolicyAnchorStateGenerationPayloadV2({
|
|
1367
|
+
new PolicyAnchorStateGenerationPayloadV2({
|
|
1368
|
+
coreState: coreRecord,
|
|
1369
|
+
forkObservationCount: committedForkObservationCount,
|
|
1370
|
+
forkObservationCommitment: committedForkObservationDigest,
|
|
1371
|
+
}),
|
|
1072
1372
|
);
|
|
1073
1373
|
if (payloadBytes.byteLength > MAX_STATE_GENERATION_PAYLOAD_BYTES) {
|
|
1074
1374
|
throw this.halt("Policy-anchor state payload exceeds its byte ceiling");
|
|
1075
1375
|
}
|
|
1076
1376
|
drafts.push({ kind: GENERATION_KIND.STATE, payloadBytes });
|
|
1077
1377
|
}
|
|
1078
|
-
for (const
|
|
1378
|
+
for (const entryBytes of observationEntries) {
|
|
1079
1379
|
const payloadBytes = serialize(
|
|
1080
1380
|
new PolicyAnchorObservationGenerationPayloadV2({
|
|
1081
1381
|
entryBytes: copyBytes(entryBytes),
|
|
@@ -1089,25 +1389,13 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1089
1389
|
drafts.push({
|
|
1090
1390
|
kind: GENERATION_KIND.FORK_OBSERVATION,
|
|
1091
1391
|
payloadBytes,
|
|
1092
|
-
applyObservationHash: hashKey,
|
|
1093
1392
|
});
|
|
1094
1393
|
}
|
|
1095
1394
|
|
|
1096
1395
|
for (const draft of drafts) {
|
|
1097
1396
|
nextGeneration += 1n;
|
|
1098
|
-
if (draft.kind === GENERATION_KIND.STATE) {
|
|
1099
|
-
if (durableState.state === "FORKED") {
|
|
1100
|
-
for (const entryBytes of durableState.childEntryBytes) {
|
|
1101
|
-
stagedObservedHashes.add(
|
|
1102
|
-
bytesKey(observationContentHash(entryBytes)),
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
} else {
|
|
1107
|
-
stagedObservedHashes.add(draft.applyObservationHash!);
|
|
1108
|
-
}
|
|
1109
1397
|
const body = new PolicyAnchorGenerationBodyV2({
|
|
1110
|
-
formatVersion:
|
|
1398
|
+
formatVersion: ANCHOR_FORMAT_VERSION,
|
|
1111
1399
|
generation: nextGeneration,
|
|
1112
1400
|
descriptorDigest: copyBytes(this.descriptorHash),
|
|
1113
1401
|
previousGenerationChecksum: copyBytes(previousChecksum),
|
|
@@ -1142,7 +1430,6 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1142
1430
|
this.generation = nextGeneration;
|
|
1143
1431
|
this.previousGenerationChecksum = previousChecksum;
|
|
1144
1432
|
this.durableCoreBytes = copyBytes(coreBytes);
|
|
1145
|
-
this.observedHashes = stagedObservedHashes;
|
|
1146
1433
|
try {
|
|
1147
1434
|
this.published = publishedFromReducer(this.core);
|
|
1148
1435
|
} catch (error) {
|