@xyo-network/event-kit-statement-graph 0.1.4 → 0.1.7
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/README.md +29 -11
- package/dist/neutral/finalizedReplayController.d.ts +16 -1
- package/dist/neutral/finalizedReplayController.d.ts.map +1 -1
- package/dist/neutral/index.d.ts +1 -1
- package/dist/neutral/index.d.ts.map +1 -1
- package/dist/neutral/index.mjs +207 -76
- package/dist/neutral/index.mjs.map +3 -3
- package/dist/neutral/replayCheckpoint.d.ts +23 -22
- package/dist/neutral/replayCheckpoint.d.ts.map +1 -1
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -30,27 +30,41 @@ const replay = await createFinalizedEventKitReplayController({
|
|
|
30
30
|
|
|
31
31
|
await replay.sync()
|
|
32
32
|
|
|
33
|
+
// A bounded worker still reports the real live finalized head while advancing
|
|
34
|
+
// its durable projection by at most the requested number of blocks.
|
|
35
|
+
await replay.sync({ maxBlocks: 4_096, signal })
|
|
36
|
+
|
|
37
|
+
// The durable projection exposes its exact canonical XL1 root.
|
|
38
|
+
const at = replay.anchor() // { block, hash } or null before the first commit
|
|
39
|
+
|
|
33
40
|
const handshakes = createEventKitHandshakeReadModel({
|
|
34
41
|
body: hash => replay.body(hash),
|
|
35
42
|
viewer: replay.viewer(),
|
|
36
43
|
})
|
|
37
44
|
```
|
|
38
45
|
|
|
39
|
-
The
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
The controller bulk-stages contiguous finalized ranges through Statement Graph
|
|
47
|
+
with the exact Event Kit object-schema allowlist. The default full `sync()`
|
|
48
|
+
catches up to the independently observed live head; `sync({ maxBlocks, signal })`
|
|
49
|
+
lets a bounded worker retain that real `finalizedHead` while advancing
|
|
50
|
+
`indexedThrough` by at most `maxBlocks`. A staged range is published only after
|
|
51
|
+
every publisher-scoped object and required challenge/response attachment in it
|
|
52
|
+
is verified. An unresolved Statement Graph event, a missing or corrupt claimed
|
|
53
|
+
object, or a missing or corrupt attachment records the earliest affected block
|
|
54
|
+
as an explicit blocked boundary and leaves `indexedThrough` before that block.
|
|
55
|
+
A clean prefix is re-scanned and may be committed first. A later `sync()` retries
|
|
56
|
+
the same blocked block; unavailability is never interpreted as absence or
|
|
57
|
+
revocation. Cancellation retains only prefixes whose atomic checkpoint save has
|
|
58
|
+
already resolved.
|
|
59
|
+
|
|
60
|
+
Published Statement Graph `0.5.3` admits consensus-elevated, subject-bearing
|
|
48
61
|
Claims and decodes Claim bodies from finalized block carriers while resolving
|
|
49
62
|
claimed objects and acceptance attachments through the datalake. The Gate 2
|
|
50
63
|
live rerun against that release remains the acceptance boundary.
|
|
51
64
|
|
|
52
|
-
Statement Graph `0.5.
|
|
53
|
-
Kit's publisher-scoped outer checkpoint is
|
|
65
|
+
Statement Graph `0.5.x` uses fold version 4 and scan-checkpoint format 2. Event
|
|
66
|
+
Kit's publisher-scoped outer checkpoint is format 3 and binds
|
|
67
|
+
`indexedThrough` to the exact canonical XL1 root hash. Only the exact
|
|
54
68
|
current checkpoint and ordered Event Kit schema policy are accepted. Missing,
|
|
55
69
|
additional, reordered, or version-suffixed policy entries fail closed as
|
|
56
70
|
invalid content; no alternative-policy reader or in-place migration exists.
|
|
@@ -61,6 +75,10 @@ invalid content; no alternative-policy reader or in-place migration exists.
|
|
|
61
75
|
- `indexedThrough` is the highest block whose Event Kit projection was fully
|
|
62
76
|
committed, or `null` before the floor block has been indexed.
|
|
63
77
|
|
|
78
|
+
`anchor()` returns the durable `{block, hash}` pair for `indexedThrough`. A
|
|
79
|
+
height-only checkpoint from the previous format is replayed from the floor; it
|
|
80
|
+
is never upgraded into root-bound evidence by assumption.
|
|
81
|
+
|
|
64
82
|
The strict `EventKitReplayCheckpointZod` checkpoint binds network, chain ID,
|
|
65
83
|
publisher, datalake source identity, floor block, Statement Graph fold version,
|
|
66
84
|
and the ordered schema policy. It retains the complete canonical
|
|
@@ -10,20 +10,35 @@ export interface EventKitReplayCheckpointStore {
|
|
|
10
10
|
export interface FinalizedEventKitReplayOptions extends EventKitReplayCheckpointIdentity {
|
|
11
11
|
readonly bodies: BodyReader;
|
|
12
12
|
readonly chain: FinalizedBlockViewer;
|
|
13
|
+
/** Maximum contiguous blocks staged before one atomic checkpoint save. */
|
|
14
|
+
readonly checkpointBatchSize?: number;
|
|
13
15
|
readonly checkpointStore: EventKitReplayCheckpointStore;
|
|
16
|
+
/** Maximum blocks requested by each Statement Graph bulk read. */
|
|
17
|
+
readonly scanBatchSize?: number;
|
|
14
18
|
}
|
|
15
19
|
export interface FinalizedEventKitReplayStatus {
|
|
16
20
|
readonly blocked?: ReplayBlockedState;
|
|
17
21
|
readonly finalizedHead: number;
|
|
18
22
|
readonly indexedThrough: number | null;
|
|
19
23
|
}
|
|
24
|
+
export interface FinalizedEventKitReplayAnchor {
|
|
25
|
+
readonly block: number;
|
|
26
|
+
readonly hash: string;
|
|
27
|
+
}
|
|
28
|
+
export interface FinalizedEventKitReplaySyncOptions {
|
|
29
|
+
/** Advance at most this many blocks while still reporting the uncapped live finalized head. */
|
|
30
|
+
readonly maxBlocks?: number;
|
|
31
|
+
readonly signal?: AbortSignal;
|
|
32
|
+
}
|
|
20
33
|
export interface FinalizedEventKitReplayController {
|
|
34
|
+
/** Exact canonical XL1 root retained with the current durable projection. */
|
|
35
|
+
anchor(): Readonly<FinalizedEventKitReplayAnchor> | null;
|
|
21
36
|
/** Resolve a verified lifecycle or acceptance-attachment body from the durable projection. */
|
|
22
37
|
body(hash: string): Readonly<Record<string, unknown>> | undefined;
|
|
23
38
|
snapshot(): Readonly<EventKitReplayCheckpoint>;
|
|
24
39
|
status(): FinalizedEventKitReplayStatus;
|
|
25
40
|
/** Walks clean finalized blocks and stops before the first unavailable body boundary. */
|
|
26
|
-
sync(): Promise<FinalizedEventKitReplayStatus>;
|
|
41
|
+
sync(options?: FinalizedEventKitReplaySyncOptions): Promise<FinalizedEventKitReplayStatus>;
|
|
27
42
|
/** Snapshot viewer; request it again after `sync()` to observe the new checkpoint. */
|
|
28
43
|
viewer(): StatementGraphViewer;
|
|
29
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finalizedReplayController.d.ts","sourceRoot":"","sources":["../../src/finalizedReplayController.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"finalizedReplayController.d.ts","sourceRoot":"","sources":["../../src/finalizedReplayController.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAA;AAMnF,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EAErB,MAAM,sCAAsC,CAAA;AAQ7C,OAAO,KAAK,EACV,wBAAwB,EACxB,gCAAgC,EAChC,kBAAkB,EAGnB,MAAM,uBAAuB,CAAA;AAO9B,MAAM,WAAW,6BAA6B;IAC5C,yEAAyE;IACzE,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACxB,oFAAoF;IACpF,IAAI,CAAC,UAAU,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,8BAA+B,SAAQ,gCAAgC;IACtF,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAA;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,eAAe,EAAE,6BAA6B,CAAA;IACvD,kEAAkE;IAClE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,kCAAkC;IACjD,+FAA+F;IAC/F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED,MAAM,WAAW,iCAAiC;IAChD,6EAA6E;IAC7E,MAAM,IAAI,QAAQ,CAAC,6BAA6B,CAAC,GAAG,IAAI,CAAA;IACxD,8FAA8F;IAC9F,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAA;IACjE,QAAQ,IAAI,QAAQ,CAAC,wBAAwB,CAAC,CAAA;IAC9C,MAAM,IAAI,6BAA6B,CAAA;IACvC,yFAAyF;IACzF,IAAI,CAAC,OAAO,CAAC,EAAE,kCAAkC,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAAA;IAC1F,sFAAsF;IACtF,MAAM,IAAI,oBAAoB,CAAA;CAC/B;AA4ZD,wBAAsB,uCAAuC,CAC3D,UAAU,EAAE,8BAA8B,GACzC,OAAO,CAAC,iCAAiC,CAAC,CAwB5C;AAED,uFAAuF;AACvF,wBAAgB,yCAAyC,CACvD,OAAO,CAAC,EAAE,OAAO,GAChB,6BAA6B,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAA;CAAE,CAazF"}
|
package/dist/neutral/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type { ActiveSourceWakeGrantAuthorizerOptions } from './activeSourceWakeG
|
|
|
2
2
|
export { createActiveSourceWakeGrantAuthorizer } from './activeSourceWakeGrantAuthorizer.ts';
|
|
3
3
|
export { createCheckpointStatementGraphViewer } from './checkpointViewer.ts';
|
|
4
4
|
export { EventKitClaimedObjectSchemas, EventKitClaimedObjectSchemaSet, } from './eventKitSchemaPolicy.ts';
|
|
5
|
-
export type { EventKitReplayCheckpointStore, FinalizedEventKitReplayController, FinalizedEventKitReplayOptions, FinalizedEventKitReplayStatus, } from './finalizedReplayController.ts';
|
|
5
|
+
export type { EventKitReplayCheckpointStore, FinalizedEventKitReplayController, FinalizedEventKitReplayOptions, FinalizedEventKitReplayStatus, FinalizedEventKitReplaySyncOptions, } from './finalizedReplayController.ts';
|
|
6
6
|
export { createFinalizedEventKitReplayController, createMemoryEventKitReplayCheckpointStore, } from './finalizedReplayController.ts';
|
|
7
7
|
export type { StatementGraphGrantReadModelOptions } from './grantReadModel.ts';
|
|
8
8
|
export { createStatementGraphGrantReadModel } from './grantReadModel.ts';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sCAAsC,EAAE,MAAM,sCAAsC,CAAA;AAClG,OAAO,EAAE,qCAAqC,EAAE,MAAM,sCAAsC,CAAA;AAC5F,OAAO,EAAE,oCAAoC,EAAE,MAAM,uBAAuB,CAAA;AAC5E,OAAO,EACL,4BAA4B,EAC5B,8BAA8B,GAC/B,MAAM,2BAA2B,CAAA;AAClC,YAAY,EACV,6BAA6B,EAC7B,iCAAiC,EACjC,8BAA8B,EAC9B,6BAA6B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sCAAsC,EAAE,MAAM,sCAAsC,CAAA;AAClG,OAAO,EAAE,qCAAqC,EAAE,MAAM,sCAAsC,CAAA;AAC5F,OAAO,EAAE,oCAAoC,EAAE,MAAM,uBAAuB,CAAA;AAC5E,OAAO,EACL,4BAA4B,EAC5B,8BAA8B,GAC/B,MAAM,2BAA2B,CAAA;AAClC,YAAY,EACV,6BAA6B,EAC7B,iCAAiC,EACjC,8BAA8B,EAC9B,6BAA6B,EAC7B,kCAAkC,GACnC,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,uCAAuC,EACvC,yCAAyC,GAC1C,MAAM,gCAAgC,CAAA;AACvC,YAAY,EAAE,mCAAmC,EAAE,MAAM,qBAAqB,CAAA;AAC9E,OAAO,EAAE,kCAAkC,EAAE,MAAM,qBAAqB,CAAA;AACxE,OAAO,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAA;AAC1E,YAAY,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,iCAAiC,EACjC,0BAA0B,EAC1B,sBAAsB,EACtB,4BAA4B,EAC5B,qCAAqC,EACrC,4CAA4C,EAC5C,iCAAiC,EACjC,uBAAuB,EACvB,sBAAsB,EACtB,6BAA6B,EAC7B,wCAAwC,EACxC,2CAA2C,EAC3C,yBAAyB,EACzB,gCAAgC,EAChC,sCAAsC,EACtC,gCAAgC,EAChC,yBAAyB,EACzB,4BAA4B,EAC5B,mCAAmC,EACnC,8CAA8C,EAC9C,iDAAiD,EACjD,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,8BAA8B,CAAA;AACrC,YAAY,EACV,wBAAwB,EACxB,gCAAgC,EAChC,4BAA4B,EAC5B,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,mCAAmC,EACnC,2BAA2B,EAC3B,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,uBAAuB,CAAA"}
|
package/dist/neutral/index.mjs
CHANGED
|
@@ -225,6 +225,8 @@ import {
|
|
|
225
225
|
WakeSubscriptionAcceptanceSchema as WakeSubscriptionAcceptanceSchema2,
|
|
226
226
|
WakeSubscriptionAcceptanceZod
|
|
227
227
|
} from "@xyo-network/event-kit-schemas";
|
|
228
|
+
import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk";
|
|
229
|
+
import { isBlockBoundWitness } from "@xyo-network/xl1-protocol";
|
|
228
230
|
import { STATEMENT_FOLD_VERSION as STATEMENT_FOLD_VERSION2 } from "@xyo-network/xl1-statement-graph-schemas";
|
|
229
231
|
import {
|
|
230
232
|
resolveVerifiedBodies,
|
|
@@ -249,7 +251,7 @@ import {
|
|
|
249
251
|
StatementScanCheckpointZod
|
|
250
252
|
} from "@xyo-network/xl1-statement-graph-sdk";
|
|
251
253
|
import { z } from "zod";
|
|
252
|
-
var EVENT_KIT_REPLAY_CHECKPOINT_VERSION =
|
|
254
|
+
var EVENT_KIT_REPLAY_CHECKPOINT_VERSION = 3;
|
|
253
255
|
var ResolvedReplayBodyZod = z.strictObject({
|
|
254
256
|
body: z.record(z.string(), z.unknown()),
|
|
255
257
|
hash: HashZod2
|
|
@@ -273,6 +275,7 @@ var EventKitReplayCheckpointZod = z.strictObject({
|
|
|
273
275
|
floorBlock: BlockNumberZod,
|
|
274
276
|
foldVersion: z.literal(STATEMENT_FOLD_VERSION),
|
|
275
277
|
indexedThrough: z.union([BlockNumberZod, z.null()]),
|
|
278
|
+
indexedThroughHash: z.union([HashZod2, z.null()]),
|
|
276
279
|
network: z.string().min(1),
|
|
277
280
|
objectSchemas: z.tuple(EventKitClaimedObjectSchemas.map((schema) => z.literal(schema))),
|
|
278
281
|
publisher: NormalizedAddressZod2,
|
|
@@ -280,6 +283,22 @@ var EventKitReplayCheckpointZod = z.strictObject({
|
|
|
280
283
|
sourceId: z.string().min(1),
|
|
281
284
|
version: z.literal(EVENT_KIT_REPLAY_CHECKPOINT_VERSION)
|
|
282
285
|
});
|
|
286
|
+
var LegacyEventKitReplayCheckpointIdentityZod = z.looseObject({
|
|
287
|
+
chainId: ChainIdZod,
|
|
288
|
+
floorBlock: BlockNumberZod,
|
|
289
|
+
network: z.string().min(1),
|
|
290
|
+
publisher: NormalizedAddressZod2,
|
|
291
|
+
sourceId: z.string().min(1),
|
|
292
|
+
version: z.literal(2)
|
|
293
|
+
});
|
|
294
|
+
function checkpointIdentityError(checkpoint, identity) {
|
|
295
|
+
if (checkpoint.chainId !== identity.chainId) return `chainId ${checkpoint.chainId} != ${identity.chainId}`;
|
|
296
|
+
if (checkpoint.floorBlock !== identity.floorBlock) return `floorBlock ${checkpoint.floorBlock} != ${identity.floorBlock}`;
|
|
297
|
+
if (checkpoint.network !== identity.network) return `network ${checkpoint.network} != ${identity.network}`;
|
|
298
|
+
if (checkpoint.sourceId !== identity.sourceId) return `sourceId ${checkpoint.sourceId} != ${identity.sourceId}`;
|
|
299
|
+
if (checkpoint.publisher !== identity.publisher) return `publisher ${checkpoint.publisher} != ${identity.publisher}`;
|
|
300
|
+
return void 0;
|
|
301
|
+
}
|
|
283
302
|
function appliedKey(record) {
|
|
284
303
|
return `${record.txHash}:${record.eventHash}:${record.hashIndex}`;
|
|
285
304
|
}
|
|
@@ -328,7 +347,7 @@ function sameClaimRecords(left, right) {
|
|
|
328
347
|
});
|
|
329
348
|
}
|
|
330
349
|
function progressIntegrityError(checkpoint) {
|
|
331
|
-
const { indexedThrough } = checkpoint;
|
|
350
|
+
const { indexedThrough, indexedThroughHash } = checkpoint;
|
|
332
351
|
if (indexedThrough !== null && indexedThrough > checkpoint.finalizedHead) {
|
|
333
352
|
return `indexedThrough ${indexedThrough} exceeds finalizedHead ${checkpoint.finalizedHead}`;
|
|
334
353
|
}
|
|
@@ -341,6 +360,9 @@ function progressIntegrityError(checkpoint) {
|
|
|
341
360
|
if (indexedThrough !== null && checkpoint.scan === null) {
|
|
342
361
|
return "scan checkpoint is missing for indexed state";
|
|
343
362
|
}
|
|
363
|
+
if (indexedThrough === null !== (indexedThroughHash === null)) {
|
|
364
|
+
return "indexedThrough and indexedThroughHash must be present together";
|
|
365
|
+
}
|
|
344
366
|
if (indexedThrough === null && checkpoint.applied.length > 0) {
|
|
345
367
|
return "applied events exist before floorBlock has been indexed";
|
|
346
368
|
}
|
|
@@ -512,26 +534,21 @@ async function persistedBodyIntegrityError(checkpoint) {
|
|
|
512
534
|
}
|
|
513
535
|
async function loadEventKitReplayCheckpoint(value, identity) {
|
|
514
536
|
if (value === void 0 || value === null) return { status: "missing" };
|
|
537
|
+
if (typeof value === "object" && value !== null && value.version === 2) {
|
|
538
|
+
const legacy = LegacyEventKitReplayCheckpointIdentityZod.safeParse(value);
|
|
539
|
+
if (!legacy.success) {
|
|
540
|
+
return { detail: "recognized Event Kit replay checkpoint v2 has invalid identity", status: "corrupt" };
|
|
541
|
+
}
|
|
542
|
+
const mismatch2 = checkpointIdentityError(legacy.data, identity);
|
|
543
|
+
return mismatch2 === void 0 ? { status: "missing" } : { detail: mismatch2, status: "identity-mismatch" };
|
|
544
|
+
}
|
|
515
545
|
const parsed = EventKitReplayCheckpointZod.safeParse(value);
|
|
516
546
|
if (!parsed.success) {
|
|
517
547
|
return { detail: "recognized Event Kit replay checkpoint has invalid content", status: "corrupt" };
|
|
518
548
|
}
|
|
519
549
|
const checkpoint = parsed.data;
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
if (checkpoint.floorBlock !== identity.floorBlock) {
|
|
524
|
-
return { detail: `floorBlock ${checkpoint.floorBlock} != ${identity.floorBlock}`, status: "identity-mismatch" };
|
|
525
|
-
}
|
|
526
|
-
if (checkpoint.network !== identity.network) {
|
|
527
|
-
return { detail: `network ${checkpoint.network} != ${identity.network}`, status: "identity-mismatch" };
|
|
528
|
-
}
|
|
529
|
-
if (checkpoint.sourceId !== identity.sourceId) {
|
|
530
|
-
return { detail: `sourceId ${checkpoint.sourceId} != ${identity.sourceId}`, status: "identity-mismatch" };
|
|
531
|
-
}
|
|
532
|
-
if (checkpoint.publisher !== identity.publisher) {
|
|
533
|
-
return { detail: `publisher ${checkpoint.publisher} != ${identity.publisher}`, status: "identity-mismatch" };
|
|
534
|
-
}
|
|
550
|
+
const mismatch = checkpointIdentityError(checkpoint, identity);
|
|
551
|
+
if (mismatch !== void 0) return { detail: mismatch, status: "identity-mismatch" };
|
|
535
552
|
const bodyIntegrityError = await persistedBodyIntegrityError(checkpoint);
|
|
536
553
|
if (bodyIntegrityError !== void 0) return { detail: bodyIntegrityError, status: "corrupt" };
|
|
537
554
|
const integrityError = checkpointIntegrityError(checkpoint);
|
|
@@ -540,6 +557,8 @@ async function loadEventKitReplayCheckpoint(value, identity) {
|
|
|
540
557
|
}
|
|
541
558
|
|
|
542
559
|
// src/finalizedReplayController.ts
|
|
560
|
+
var DEFAULT_CHECKPOINT_BATCH_SIZE = 4096;
|
|
561
|
+
var DEFAULT_SCAN_BATCH_SIZE = 4096;
|
|
543
562
|
function requireHead(value) {
|
|
544
563
|
const head = Number(value);
|
|
545
564
|
if (!Number.isSafeInteger(head) || head < 0) {
|
|
@@ -553,6 +572,19 @@ function requireFloor(value) {
|
|
|
553
572
|
}
|
|
554
573
|
return value;
|
|
555
574
|
}
|
|
575
|
+
function requirePositiveInteger(value, fallback, name) {
|
|
576
|
+
const selected = value ?? fallback;
|
|
577
|
+
if (!Number.isSafeInteger(selected) || selected < 1) {
|
|
578
|
+
throw new TypeError(`${name} must be a positive safe integer, received ${String(selected)}`);
|
|
579
|
+
}
|
|
580
|
+
return selected;
|
|
581
|
+
}
|
|
582
|
+
function requireMaxBlocks(value) {
|
|
583
|
+
if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) {
|
|
584
|
+
throw new TypeError(`maxBlocks must be a nonnegative safe integer, received ${String(value)}`);
|
|
585
|
+
}
|
|
586
|
+
return value;
|
|
587
|
+
}
|
|
556
588
|
function replayContract(options, throughBlock) {
|
|
557
589
|
return {
|
|
558
590
|
eventBodyCorpus: { sourceId: options.sourceId },
|
|
@@ -572,6 +604,7 @@ function initialCheckpoint(options) {
|
|
|
572
604
|
floorBlock: options.floorBlock,
|
|
573
605
|
foldVersion: STATEMENT_FOLD_VERSION2,
|
|
574
606
|
indexedThrough: null,
|
|
607
|
+
indexedThroughHash: null,
|
|
575
608
|
network: options.network,
|
|
576
609
|
objectSchemas: [...EventKitClaimedObjectSchemas],
|
|
577
610
|
publisher: options.publisher,
|
|
@@ -615,21 +648,23 @@ function mergeBodies(existing, additions) {
|
|
|
615
648
|
}
|
|
616
649
|
return [...merged].map(([hash, body]) => ({ body, hash: HashZod3.parse(hash) })).toSorted((left, right) => left.hash.localeCompare(right.hash));
|
|
617
650
|
}
|
|
618
|
-
async function
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
)
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
);
|
|
651
|
+
async function exactBlockHash(chain, block, chainId) {
|
|
652
|
+
const raw = await chain.block.blockByNumber(block);
|
|
653
|
+
if (!Array.isArray(raw) || raw.length !== 2 || !Array.isArray(raw[1])) {
|
|
654
|
+
throw new TypeError(`finalized block ${block} is unavailable`);
|
|
655
|
+
}
|
|
656
|
+
const header = raw[0];
|
|
657
|
+
if (!isBlockBoundWitness(header) || header.block !== block || header.chain !== chainId) {
|
|
658
|
+
throw new TypeError(`finalized block ${block} does not match the configured chain`);
|
|
659
|
+
}
|
|
660
|
+
const hash = await PayloadBuilder2.hash(PayloadBuilder2.omitStorageMeta(header));
|
|
661
|
+
const storageHash = header._hash;
|
|
662
|
+
if (typeof storageHash === "string" && storageHash !== hash) {
|
|
663
|
+
throw new TypeError(`finalized block ${block} has invalid storage hash metadata`);
|
|
664
|
+
}
|
|
665
|
+
return HashZod3.parse(hash);
|
|
666
|
+
}
|
|
667
|
+
async function stageAppliedBlock(options, applied) {
|
|
633
668
|
const appliedObjectHashes = uniqueStrings(applied.map((record) => record.objectHash));
|
|
634
669
|
const objects = await resolveVerifiedBodies(options.bodies, appliedObjectHashes);
|
|
635
670
|
const missingObjects = appliedObjectHashes.filter((hash) => !objects.bodies.has(hash));
|
|
@@ -655,19 +690,72 @@ async function stageBlock(options, prior, block) {
|
|
|
655
690
|
...schemaMismatchedObjects,
|
|
656
691
|
...missingAttachments
|
|
657
692
|
]).map((hash) => HashZod3.parse(hash));
|
|
658
|
-
const isBlocked = result.unresolved.length > 0 || objectHashes.length > 0 || corrupt.length > 0;
|
|
659
693
|
return {
|
|
660
|
-
applied,
|
|
661
694
|
bodies: new Map([...objects.bodies, ...attachments.bodies]),
|
|
695
|
+
corrupt,
|
|
696
|
+
objectHashes
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
async function stageRange(options, prior, fromBlock, throughBlock, signal) {
|
|
700
|
+
signal?.throwIfAborted();
|
|
701
|
+
const beforeHash = await exactBlockHash(options.chain, throughBlock, options.chainId);
|
|
702
|
+
signal?.throwIfAborted();
|
|
703
|
+
const result = await scanFinalizedStatements(
|
|
704
|
+
options.chain,
|
|
705
|
+
options.bodies,
|
|
706
|
+
replayContract(options, throughBlock),
|
|
707
|
+
{
|
|
708
|
+
batchSize: requirePositiveInteger(options.scanBatchSize, DEFAULT_SCAN_BATCH_SIZE, "scanBatchSize"),
|
|
709
|
+
...prior.scan === null ? {} : { resumeFrom: prior.scan },
|
|
710
|
+
...signal === void 0 ? {} : { signal },
|
|
711
|
+
strict: { rejectAmbiguousSchemaDeclarations: true },
|
|
712
|
+
through: throughBlock
|
|
713
|
+
}
|
|
714
|
+
);
|
|
715
|
+
signal?.throwIfAborted();
|
|
716
|
+
const throughHash = await exactBlockHash(options.chain, throughBlock, options.chainId);
|
|
717
|
+
signal?.throwIfAborted();
|
|
718
|
+
if (beforeHash !== throughHash) {
|
|
719
|
+
throw new Error(`finalized block root changed while staging through ${throughBlock}`);
|
|
720
|
+
}
|
|
721
|
+
if (result.head !== throughBlock || result.checkpoint.contract.throughBlock !== throughBlock) {
|
|
722
|
+
throw new RangeError(
|
|
723
|
+
`finalized range scan stopped at ${result.head}/${result.checkpoint.contract.throughBlock}; requested ${throughBlock}`
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
const applied = result.fold.applied.filter((record) => record.block >= fromBlock && record.block <= throughBlock && normalizeAddress3(record.subject) === options.publisher);
|
|
727
|
+
const appliedByBlock = Map.groupBy(applied, (record) => record.block);
|
|
728
|
+
const unresolvedByBlock = Map.groupBy(
|
|
729
|
+
result.unresolved.filter((event) => event.block >= fromBlock && event.block <= throughBlock),
|
|
730
|
+
(event) => event.block
|
|
731
|
+
);
|
|
732
|
+
const relevantBlocks = [.../* @__PURE__ */ new Set([...appliedByBlock.keys(), ...unresolvedByBlock.keys()])].toSorted((left, right) => left - right);
|
|
733
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
734
|
+
for (const block of relevantBlocks) {
|
|
735
|
+
signal?.throwIfAborted();
|
|
736
|
+
const staged = await stageAppliedBlock(options, appliedByBlock.get(block) ?? []);
|
|
737
|
+
for (const [hash, body] of staged.bodies) bodies.set(hash, body);
|
|
738
|
+
const unresolvedEvents = unresolvedByBlock.get(block) ?? [];
|
|
739
|
+
if (unresolvedEvents.length > 0 || staged.objectHashes.length > 0 || staged.corrupt.length > 0) {
|
|
740
|
+
return {
|
|
741
|
+
applied,
|
|
742
|
+
blocked: {
|
|
743
|
+
atBlock: block,
|
|
744
|
+
corrupt: staged.corrupt,
|
|
745
|
+
objectHashes: staged.objectHashes,
|
|
746
|
+
unresolvedEvents
|
|
747
|
+
},
|
|
748
|
+
bodies,
|
|
749
|
+
scan: result.checkpoint,
|
|
750
|
+
throughHash
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
applied,
|
|
756
|
+
bodies,
|
|
662
757
|
scan: result.checkpoint,
|
|
663
|
-
|
|
664
|
-
blocked: {
|
|
665
|
-
atBlock: block,
|
|
666
|
-
corrupt,
|
|
667
|
-
objectHashes,
|
|
668
|
-
unresolvedEvents: result.unresolved
|
|
669
|
-
}
|
|
670
|
-
} : {}
|
|
758
|
+
throughHash
|
|
671
759
|
};
|
|
672
760
|
}
|
|
673
761
|
var DefaultFinalizedEventKitReplayController = class {
|
|
@@ -678,6 +766,9 @@ var DefaultFinalizedEventKitReplayController = class {
|
|
|
678
766
|
this.#options = options;
|
|
679
767
|
this.#checkpoint = checkpoint;
|
|
680
768
|
}
|
|
769
|
+
anchor() {
|
|
770
|
+
return this.#checkpoint.indexedThrough === null || this.#checkpoint.indexedThroughHash === null ? null : Object.freeze({ block: this.#checkpoint.indexedThrough, hash: this.#checkpoint.indexedThroughHash });
|
|
771
|
+
}
|
|
681
772
|
body(hash) {
|
|
682
773
|
return this.#checkpoint.bodies.find((entry) => entry.hash === hash)?.body;
|
|
683
774
|
}
|
|
@@ -691,8 +782,8 @@ var DefaultFinalizedEventKitReplayController = class {
|
|
|
691
782
|
indexedThrough: this.#checkpoint.indexedThrough
|
|
692
783
|
};
|
|
693
784
|
}
|
|
694
|
-
sync() {
|
|
695
|
-
const result = this.#syncTail.then(async () => await this.#syncOnce());
|
|
785
|
+
sync(options) {
|
|
786
|
+
const result = this.#syncTail.then(async () => await this.#syncOnce(options));
|
|
696
787
|
this.#syncTail = result.then(
|
|
697
788
|
() => true,
|
|
698
789
|
() => false
|
|
@@ -702,8 +793,55 @@ var DefaultFinalizedEventKitReplayController = class {
|
|
|
702
793
|
viewer() {
|
|
703
794
|
return createCheckpointStatementGraphViewer(this.#checkpoint);
|
|
704
795
|
}
|
|
705
|
-
async #
|
|
796
|
+
async #commitRange(fromBlock, throughBlock, finalizedHead, signal) {
|
|
797
|
+
const staged = await stageRange(this.#options, this.#checkpoint, fromBlock, throughBlock, signal);
|
|
798
|
+
signal?.throwIfAborted();
|
|
799
|
+
if (staged.blocked !== void 0) {
|
|
800
|
+
const blockedAt = staged.blocked.atBlock;
|
|
801
|
+
if (blockedAt > fromBlock) {
|
|
802
|
+
const prefixBlocked = await this.#commitRange(fromBlock, blockedAt - 1, finalizedHead, signal);
|
|
803
|
+
if (prefixBlocked) return true;
|
|
804
|
+
}
|
|
805
|
+
const exact = await stageRange(this.#options, this.#checkpoint, blockedAt, blockedAt, signal);
|
|
806
|
+
signal?.throwIfAborted();
|
|
807
|
+
if (exact.blocked === void 0) {
|
|
808
|
+
await this.#commitStaged(exact, blockedAt, finalizedHead, signal);
|
|
809
|
+
return blockedAt === throughBlock ? false : await this.#commitRange(blockedAt + 1, throughBlock, finalizedHead, signal);
|
|
810
|
+
}
|
|
811
|
+
const blocked = EventKitReplayCheckpointZod.parse({
|
|
812
|
+
...this.#checkpoint,
|
|
813
|
+
blocked: exact.blocked,
|
|
814
|
+
finalizedHead
|
|
815
|
+
});
|
|
816
|
+
signal?.throwIfAborted();
|
|
817
|
+
await this.#options.checkpointStore.save(blocked);
|
|
818
|
+
this.#checkpoint = blocked;
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
821
|
+
await this.#commitStaged(staged, throughBlock, finalizedHead, signal);
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
async #commitStaged(staged, throughBlock, finalizedHead, signal) {
|
|
825
|
+
const committed = EventKitReplayCheckpointZod.parse({
|
|
826
|
+
...this.#checkpoint,
|
|
827
|
+
applied: mergeApplied(this.#checkpoint.applied, staged.applied),
|
|
828
|
+
bodies: mergeBodies(this.#checkpoint.bodies, staged.bodies),
|
|
829
|
+
blocked: void 0,
|
|
830
|
+
finalizedHead,
|
|
831
|
+
indexedThrough: throughBlock,
|
|
832
|
+
indexedThroughHash: staged.throughHash,
|
|
833
|
+
scan: staged.scan
|
|
834
|
+
});
|
|
835
|
+
signal?.throwIfAborted();
|
|
836
|
+
await this.#options.checkpointStore.save(committed);
|
|
837
|
+
this.#checkpoint = committed;
|
|
838
|
+
}
|
|
839
|
+
async #syncOnce(rawOptions) {
|
|
840
|
+
const maxBlocks = requireMaxBlocks(rawOptions?.maxBlocks);
|
|
841
|
+
const signal = rawOptions?.signal;
|
|
842
|
+
signal?.throwIfAborted();
|
|
706
843
|
const finalizedHead = requireHead(await this.#options.chain.finalization.headNumber());
|
|
844
|
+
signal?.throwIfAborted();
|
|
707
845
|
if (finalizedHead < this.#checkpoint.finalizedHead) {
|
|
708
846
|
throw new RangeError(
|
|
709
847
|
`finalized head regressed from ${this.#checkpoint.finalizedHead} to ${finalizedHead}; refusing to roll back`
|
|
@@ -714,39 +852,26 @@ var DefaultFinalizedEventKitReplayController = class {
|
|
|
714
852
|
throw new RangeError(`finalized head ${finalizedHead} is behind indexedThrough ${indexedThrough}`);
|
|
715
853
|
}
|
|
716
854
|
let next = indexedThrough === null ? this.#options.floorBlock : indexedThrough + 1;
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const committed = EventKitReplayCheckpointZod.parse({
|
|
731
|
-
...this.#checkpoint,
|
|
732
|
-
applied: mergeApplied(this.#checkpoint.applied, staged.applied),
|
|
733
|
-
bodies: mergeBodies(this.#checkpoint.bodies, staged.bodies),
|
|
734
|
-
blocked: void 0,
|
|
735
|
-
finalizedHead,
|
|
736
|
-
indexedThrough: next,
|
|
737
|
-
scan: staged.scan
|
|
738
|
-
});
|
|
739
|
-
await this.#options.checkpointStore.save(committed);
|
|
740
|
-
this.#checkpoint = committed;
|
|
741
|
-
changed = false;
|
|
742
|
-
next += 1;
|
|
855
|
+
const remaining = next <= finalizedHead ? finalizedHead - next + 1 : 0;
|
|
856
|
+
const budget = maxBlocks === void 0 ? remaining : Math.min(maxBlocks, remaining);
|
|
857
|
+
const target = budget === 0 ? next - 1 : next + budget - 1;
|
|
858
|
+
const checkpointBatchSize = requirePositiveInteger(
|
|
859
|
+
this.#options.checkpointBatchSize,
|
|
860
|
+
DEFAULT_CHECKPOINT_BATCH_SIZE,
|
|
861
|
+
"checkpointBatchSize"
|
|
862
|
+
);
|
|
863
|
+
while (next <= target) {
|
|
864
|
+
signal?.throwIfAborted();
|
|
865
|
+
const throughBlock = Math.min(target, next + Math.min(checkpointBatchSize, target - next + 1) - 1);
|
|
866
|
+
if (await this.#commitRange(next, throughBlock, finalizedHead, signal)) return this.status();
|
|
867
|
+
next = throughBlock + 1;
|
|
743
868
|
}
|
|
744
|
-
if (
|
|
869
|
+
if (this.#checkpoint.finalizedHead !== finalizedHead) {
|
|
745
870
|
const observed = EventKitReplayCheckpointZod.parse({
|
|
746
871
|
...this.#checkpoint,
|
|
747
|
-
blocked: void 0,
|
|
748
872
|
finalizedHead
|
|
749
873
|
});
|
|
874
|
+
signal?.throwIfAborted();
|
|
750
875
|
await this.#options.checkpointStore.save(observed);
|
|
751
876
|
this.#checkpoint = observed;
|
|
752
877
|
}
|
|
@@ -757,9 +882,15 @@ async function createFinalizedEventKitReplayController(rawOptions) {
|
|
|
757
882
|
const options = {
|
|
758
883
|
...rawOptions,
|
|
759
884
|
chainId: ChainIdZod2.parse(rawOptions.chainId),
|
|
885
|
+
checkpointBatchSize: requirePositiveInteger(
|
|
886
|
+
rawOptions.checkpointBatchSize,
|
|
887
|
+
DEFAULT_CHECKPOINT_BATCH_SIZE,
|
|
888
|
+
"checkpointBatchSize"
|
|
889
|
+
),
|
|
760
890
|
floorBlock: requireFloor(rawOptions.floorBlock),
|
|
761
891
|
network: rawOptions.network.trim(),
|
|
762
892
|
publisher: NormalizedAddressZod3.parse(rawOptions.publisher),
|
|
893
|
+
scanBatchSize: requirePositiveInteger(rawOptions.scanBatchSize, DEFAULT_SCAN_BATCH_SIZE, "scanBatchSize"),
|
|
763
894
|
sourceId: rawOptions.sourceId.trim()
|
|
764
895
|
};
|
|
765
896
|
if (options.network.length === 0) throw new TypeError("network must not be empty");
|
|
@@ -874,7 +1005,7 @@ import {
|
|
|
874
1005
|
PublisherServiceZod,
|
|
875
1006
|
SOURCE_WAKE_EVENT_KIND as SOURCE_WAKE_EVENT_KIND2
|
|
876
1007
|
} from "@xyo-network/event-kit-schemas";
|
|
877
|
-
import { PayloadBuilder as
|
|
1008
|
+
import { PayloadBuilder as PayloadBuilder3 } from "@xyo-network/sdk";
|
|
878
1009
|
import { XyoAddressZod as XyoAddressZod2 } from "@xyo-network/sdk-protocol/address";
|
|
879
1010
|
function handshakeEvidence(record) {
|
|
880
1011
|
return {
|
|
@@ -914,12 +1045,12 @@ async function verifiedHandshakeObjectBody(viewer, record) {
|
|
|
914
1045
|
const object = await viewer.object(record.objectHash);
|
|
915
1046
|
if (object?.assurance !== "resolved" || object.declaredSchema !== record.objectSchema || object.params === null) return void 0;
|
|
916
1047
|
const body = { schema: object.declaredSchema, ...object.params };
|
|
917
|
-
return await
|
|
1048
|
+
return await PayloadBuilder3.dataHash(body) === record.objectHash ? body : void 0;
|
|
918
1049
|
}
|
|
919
1050
|
async function verifiedHandshakeAttachmentBody(resolver, hash) {
|
|
920
1051
|
const body = resolver?.(hash);
|
|
921
1052
|
if (body === void 0) return void 0;
|
|
922
|
-
return await
|
|
1053
|
+
return await PayloadBuilder3.dataHash(body) === hash ? { ...body } : void 0;
|
|
923
1054
|
}
|
|
924
1055
|
function firstHandshakeRevocation(revocations, target) {
|
|
925
1056
|
const blocks = revocations.filter((item) => item.target === target).map((item) => item.block);
|