@secondlayer/subgraphs 7.3.0 → 7.5.0
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/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/runtime/emitter.d.ts +2 -1
- package/dist/runtime/emitter.js +134 -24
- package/dist/runtime/emitter.js.map +6 -5
- package/dist/runtime/replay.js +3 -3
- package/dist/runtime/replay.js.map +1 -1
- package/dist/schema/index.js +3 -3
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/index.js +174 -14
- package/dist/testing/index.js.map +5 -4
- package/dist/verification.js +3 -3
- package/dist/verification.js.map +1 -1
- package/dist/verify/index.js +198 -38
- package/dist/verify/index.js.map +5 -4
- package/package.json +2 -2
package/dist/verification.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@secondlayer/subgraphs",
|
|
6
|
-
version: "7.
|
|
6
|
+
version: "7.5.0",
|
|
7
7
|
description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
repository: {
|
|
@@ -63,7 +63,7 @@ var package_default = {
|
|
|
63
63
|
prepublishOnly: "bun run build"
|
|
64
64
|
},
|
|
65
65
|
dependencies: {
|
|
66
|
-
"@secondlayer/shared": "^13.
|
|
66
|
+
"@secondlayer/shared": "^13.4.0",
|
|
67
67
|
"@secondlayer/stacks": "^6.4.0",
|
|
68
68
|
"@secondlayer/verify": "^0.6.0",
|
|
69
69
|
esbuild: "^0.28.2",
|
|
@@ -279,5 +279,5 @@ export {
|
|
|
279
279
|
pinPreimage
|
|
280
280
|
};
|
|
281
281
|
|
|
282
|
-
//# debugId=
|
|
282
|
+
//# debugId=77C06EADF44962D564756E2164756E21
|
|
283
283
|
//# sourceMappingURL=verification.js.map
|
package/dist/verification.js.map
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"/**\n * Verifiable subgraphs: the determinism contract, the derived level and the\n * pin. Pure (node:crypto only) so the bundler's deploy scan, the API and the\n * runtime realm all read the same rules.\n *\n * A subgraph is never configured as verifiable. Its level is derived from\n * its sources plus a scan of the bundled handler, and only a subgraph whose\n * every input is provable today (`state`: named state writes) has the contract\n * enforced. Everything else gets the same findings as advice.\n */\nimport { createHash } from \"node:crypto\";\nimport type { SubgraphVerification } from \"@secondlayer/shared/schemas/subgraphs\";\nimport pkg from \"../package.json\" with { type: \"json\" };\nimport type { SubgraphDefinition, SubgraphFilter } from \"./types.ts\";\n\nexport type { SubgraphVerification };\n\n/** Runtime identity baked into every pin: matcher, decode and flush semantics. */\nexport const SUBGRAPHS_RUNTIME: string = `@secondlayer/subgraphs@${pkg.version}`;\n\n// ── Determinism contract ────────────────────────────────────────────────\n\n/**\n * Globals a handler may not touch, with the reason shown to the developer.\n * The deploy scan reports them; the realm replaces each with a getter that\n * throws `NondeterminismError`.\n */\nexport const FORBIDDEN_GLOBALS: Readonly<Record<string, string>> = {\n\tDate: \"wall-clock time differs per run\",\n\tperformance: \"timers differ per run\",\n\tprocess: \"host environment differs per run\",\n\tfetch: \"network reads are not chain inputs\",\n\tcrypto: \"randomness differs per run\",\n\tsetTimeout: \"scheduling order differs per run\",\n\tsetInterval: \"scheduling order differs per run\",\n\tsetImmediate: \"scheduling order differs per run\",\n\tclearTimeout: \"scheduling order differs per run\",\n\tclearInterval: \"scheduling order differs per run\",\n\tqueueMicrotask: \"scheduling order differs per run\",\n\tWeakRef: \"garbage collection timing differs per run\",\n\tFinalizationRegistry: \"garbage collection timing differs per run\",\n\tAtomics: \"shared memory is not single-threaded\",\n\tSharedArrayBuffer: \"shared memory is not single-threaded\",\n\tIntl: \"locale and time zone data differ per host\",\n\tTemporal: \"locale and time zone data differ per host\",\n\teval: \"generated code hides inputs from the scan\",\n\tWebAssembly: \"compiled code hides inputs from the scan\",\n\tShadowRealm: \"a nested realm escapes the allow-list\",\n\trequire: \"imports must be bundled into the handler\",\n};\n\n/**\n * Globals a handler may use: ECMAScript built-ins whose results are fully\n * specified, plus deterministic host encoders. `Math` is narrowed separately\n * ({@link ALLOWED_MATH}).\n */\nexport const ALLOWED_GLOBALS: readonly string[] = [\n\t\"undefined\",\n\t\"NaN\",\n\t\"Infinity\",\n\t\"globalThis\",\n\t\"isNaN\",\n\t\"isFinite\",\n\t\"parseInt\",\n\t\"parseFloat\",\n\t\"decodeURI\",\n\t\"decodeURIComponent\",\n\t\"encodeURI\",\n\t\"encodeURIComponent\",\n\t\"escape\",\n\t\"unescape\",\n\t\"Object\",\n\t\"Function\",\n\t\"Array\",\n\t\"Number\",\n\t\"Boolean\",\n\t\"String\",\n\t\"Symbol\",\n\t\"BigInt\",\n\t\"Math\",\n\t\"JSON\",\n\t\"Reflect\",\n\t\"Proxy\",\n\t\"Promise\",\n\t\"Map\",\n\t\"Set\",\n\t\"WeakMap\",\n\t\"WeakSet\",\n\t\"RegExp\",\n\t\"Iterator\",\n\t\"DisposableStack\",\n\t\"AsyncDisposableStack\",\n\t\"Error\",\n\t\"AggregateError\",\n\t\"SuppressedError\",\n\t\"EvalError\",\n\t\"RangeError\",\n\t\"ReferenceError\",\n\t\"SyntaxError\",\n\t\"TypeError\",\n\t\"URIError\",\n\t\"ArrayBuffer\",\n\t\"DataView\",\n\t\"Int8Array\",\n\t\"Uint8Array\",\n\t\"Uint8ClampedArray\",\n\t\"Int16Array\",\n\t\"Uint16Array\",\n\t\"Int32Array\",\n\t\"Uint32Array\",\n\t\"Float16Array\",\n\t\"Float32Array\",\n\t\"Float64Array\",\n\t\"BigInt64Array\",\n\t\"BigUint64Array\",\n\t\"TextEncoder\",\n\t\"TextDecoder\",\n\t\"atob\",\n\t\"btoa\",\n\t\"console\",\n];\n\n/** `Math` members whose results are exact on every engine. The rest\n * (`sin`, `pow`, `log`, …) are implementation-approximated. */\nexport const ALLOWED_MATH: readonly string[] = [\n\t\"abs\",\n\t\"min\",\n\t\"max\",\n\t\"floor\",\n\t\"ceil\",\n\t\"trunc\",\n\t\"sign\",\n\t\"round\",\n\t\"E\",\n\t\"LN10\",\n\t\"LN2\",\n\t\"LOG10E\",\n\t\"LOG2E\",\n\t\"PI\",\n\t\"SQRT1_2\",\n\t\"SQRT2\",\n];\n\n/** Methods whose output depends on the host's locale data. */\nexport const LOCALE_METHODS: readonly string[] = [\n\t\"toLocaleString\",\n\t\"toLocaleDateString\",\n\t\"toLocaleTimeString\",\n\t\"toLocaleUpperCase\",\n\t\"toLocaleLowerCase\",\n\t\"localeCompare\",\n];\n\n/**\n * One thing the deploy scan found in a bundled handler.\n *\n * - `nondeterministic`: breaks the determinism contract. Fails the deploy of\n * a state subgraph; advice for everything else.\n * - `needs-events`: deterministic, but reads something only re-execution proofs cover\n * (`ctx.client`). Raises the level instead of failing.\n */\nexport interface HandlerFinding {\n\tkind: \"nondeterministic\" | \"needs-events\";\n\t/** What was used, e.g. `Date`, `Math.random`, `import()`, `ctx.client`. */\n\tname: string;\n\treason: string;\n\t/** Original source file when a sourcemap was available, else the bundle. */\n\tfile: string;\n\t/** 1-based. */\n\tline: number;\n\t/** 1-based. */\n\tcolumn: number;\n}\n\n/** Formats a handler finding as `file:line:column name: reason`. */\nexport function formatFinding(f: HandlerFinding): string {\n\treturn `${f.file}:${f.line}:${f.column} ${f.name}: ${f.reason}`;\n}\n\n// ── Derived level ───────────────────────────────────────────────────────\n\n/**\n * Sources whose every field is a named state write: provable per block from\n * the header-backed witness plus `state_writes`. Not `map_insert`: storage\n * holds `(some v)` for an insert and a set alike, so telling them apart takes\n * re-execution (event proofs).\n */\nconst STATE_SOURCE_TYPES: ReadonlySet<string> = new Set([\n\t\"var_set\",\n\t\"map_set\",\n\t\"map_delete\",\n]);\n\nconst LEVEL_RANK = { state: 0, events: 1, none: 2 } as const;\n\n/** Handler findings listed in `reasons`; the rest are counted. */\nconst MAX_LISTED_FINDINGS = 10;\n\n/**\n * Derive how far a subgraph's rows can be checked. Level = the highest any\n * source or handler read needs. `reasons` lists everything standing between\n * the subgraph and checkable rows; a `state` subgraph with any reason is\n * refused at deploy, so every stored `state` row has none. Never configured,\n * never a flag.\n */\nexport function deriveVerification(\n\tdef: Pick<SubgraphDefinition, \"sources\" | \"backfillMode\">,\n\tfindings: readonly HandlerFinding[] = [],\n): SubgraphVerification {\n\tlet level: keyof typeof LEVEL_RANK = \"state\";\n\tconst raise = (to: keyof typeof LEVEL_RANK) => {\n\t\tif (LEVEL_RANK[to] > LEVEL_RANK[level]) level = to;\n\t};\n\tconst reasons: string[] = [];\n\n\tfor (const [name, source] of Object.entries(def.sources ?? {})) {\n\t\tconst filter = source as SubgraphFilter & { trait?: string };\n\t\tif (filter.trait) {\n\t\t\traise(\"none\");\n\t\t\treasons.push(\n\t\t\t\t`source \"${name}\": trait scope needs a proven contract registry`,\n\t\t\t);\n\t\t} else if (filter.type === \"map_insert\") {\n\t\t\traise(\"events\");\n\t\t\treasons.push(\n\t\t\t\t`map_insert source \"${name}\": map_insert vs map_set needs event proofs`,\n\t\t\t);\n\t\t} else if (!STATE_SOURCE_TYPES.has(filter.type)) {\n\t\t\traise(\"events\");\n\t\t\treasons.push(`${filter.type} source \"${name}\" needs event proofs`);\n\t\t}\n\t}\n\tif (def.backfillMode === \"concurrent\") {\n\t\traise(\"none\");\n\t\treasons.push(\"backfillMode concurrent: tip-first order is not chain order\");\n\t}\n\tif (findings.some((f) => f.kind === \"needs-events\")) raise(\"events\");\n\t// A large bundled library can trip dozens of findings; list a few.\n\tfor (const f of findings.slice(0, MAX_LISTED_FINDINGS)) {\n\t\treasons.push(\n\t\t\tf.kind === \"needs-events\"\n\t\t\t\t? `${formatFinding(f)} (needs event proofs)`\n\t\t\t\t: formatFinding(f),\n\t\t);\n\t}\n\tif (findings.length > MAX_LISTED_FINDINGS) {\n\t\treasons.push(\n\t\t\t`…and ${findings.length - MAX_LISTED_FINDINGS} more handler findings`,\n\t\t);\n\t}\n\n\treturn {\n\t\tlevel,\n\t\treasons,\n\t\tunproven:\n\t\t\treasons.length === 0\n\t\t\t\t? [\n\t\t\t\t\t\t\"tx attribution of writes: event.tx, _tx_id (needs event proofs)\",\n\t\t\t\t\t\t\"intermediate writes within a block (needs event proofs)\",\n\t\t\t\t\t]\n\t\t\t\t: [],\n\t};\n}\n\n/**\n * Tells whether a deploy must be refused for breaking determinism.\n *\n * It is refused when the sources are provable (`state`), so the determinism\n * contract is enforced, and the scan broke it.\n */\nexport function isDeterminismViolation(v: SubgraphVerification): boolean {\n\treturn v.level === \"state\" && v.reasons.length > 0;\n}\n\n// ── Pin ─────────────────────────────────────────────────────────────────\n\n/** What a pin is computed from: schema hash, handler code, network. */\nexport interface PinInput {\n\t/** Existing `schema_hash` (name + schema + sources). */\n\tschemaHash: string;\n\t/** Bundled ESM exactly as stored in `subgraphs.handler_code`. */\n\thandlerCode: string;\n\tstartBlock?: number | null;\n\tnetwork: string;\n\t/** Defaults to {@link SUBGRAPHS_RUNTIME}. */\n\truntime?: string;\n}\n\nfunction sha256(input: string): string {\n\treturn createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\n/** What a pin commits to, as stored in `pin_preimage`. */\nexport interface PinFields {\n\t/** sha256 of the bundled handler, hex. */\n\thandlerHash: string;\n\tnetwork: string;\n\truntime: string;\n\tschemaHash: string;\n\tstartBlock: number | null;\n}\n\n/**\n * The canonical JSON a pin is the sha256 of: fixed key order, primitives\n * only. Stored beside the pin so anyone can recompute it from the served\n * handler code.\n */\nexport function pinPreimage(input: PinInput): string {\n\tconst fields: PinFields = {\n\t\thandlerHash: sha256(input.handlerCode),\n\t\tnetwork: input.network,\n\t\truntime: input.runtime ?? SUBGRAPHS_RUNTIME,\n\t\tschemaHash: input.schemaHash,\n\t\tstartBlock: input.startBlock ?? null,\n\t};\n\treturn JSON.stringify(fields);\n}\n\n/**\n * Content hash over everything that shapes a subgraph's rows. Unlike\n * `schema_hash`, it changes with the handler, `startBlock` and runtime, so\n * two deploys with the same pin produce the same rows from the same chain.\n */\nexport function computePin(input: PinInput): string {\n\treturn sha256(pinPreimage(input));\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "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",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "77C06EADF44962D564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/verify/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/verify/replay.ts
|
|
2
|
-
import { createHash as
|
|
2
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3
3
|
import { decodeRawTx } from "@secondlayer/shared/node/tx-summary";
|
|
4
4
|
import {
|
|
5
5
|
BlockVerifier,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/runtime/runner.ts
|
|
11
11
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
12
|
-
import { logger as
|
|
12
|
+
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
13
13
|
import {
|
|
14
14
|
clarityValueToJS,
|
|
15
15
|
deserializeCV as deserializeCV3,
|
|
@@ -388,6 +388,158 @@ function decodeEventData(data) {
|
|
|
388
388
|
return data;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
// src/runtime/error-groups.ts
|
|
392
|
+
import { createHash } from "node:crypto";
|
|
393
|
+
import { getTargetDb } from "@secondlayer/shared/db";
|
|
394
|
+
import { logger } from "@secondlayer/shared/logger";
|
|
395
|
+
import { sql } from "kysely";
|
|
396
|
+
var ERROR_GROUPS_CAP_PER_SUBGRAPH = 50;
|
|
397
|
+
var MESSAGE_MAX_CHARS = 500;
|
|
398
|
+
function normalizeMessage(message) {
|
|
399
|
+
return message.replace(/0x[0-9a-f]+/gi, "<hex>").replace(/(["'`])(?:(?!\1).)*\1/g, "<str>").replace(/\d+/g, "<n>");
|
|
400
|
+
}
|
|
401
|
+
function errorFingerprint(input) {
|
|
402
|
+
return createHash("sha1").update([
|
|
403
|
+
input.sourceName,
|
|
404
|
+
input.eventType ?? "",
|
|
405
|
+
normalizeMessage(input.message),
|
|
406
|
+
input.frame ?? ""
|
|
407
|
+
].join("|")).digest("hex");
|
|
408
|
+
}
|
|
409
|
+
function topFrame(err) {
|
|
410
|
+
if (!(err instanceof Error) || !err.stack)
|
|
411
|
+
return null;
|
|
412
|
+
for (const line of err.stack.split(`
|
|
413
|
+
`).slice(1)) {
|
|
414
|
+
const match = /([^/\\\s()]+):(\d+):\d+\)?\s*$/.exec(line);
|
|
415
|
+
if (match)
|
|
416
|
+
return `${match[1]}:${match[2]}`;
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
var FLUSH_INTERVAL_MS = 5000;
|
|
421
|
+
var MAX_PENDING_KEYS = 1000;
|
|
422
|
+
var pending = new Map;
|
|
423
|
+
var droppedKeys = 0;
|
|
424
|
+
var flushTimer = null;
|
|
425
|
+
function startFlusher() {
|
|
426
|
+
if (flushTimer)
|
|
427
|
+
return;
|
|
428
|
+
flushTimer = setInterval(() => {
|
|
429
|
+
flushHandlerErrors();
|
|
430
|
+
}, FLUSH_INTERVAL_MS);
|
|
431
|
+
flushTimer.unref();
|
|
432
|
+
}
|
|
433
|
+
async function flushHandlerErrors() {
|
|
434
|
+
if (pending.size === 0 && droppedKeys === 0)
|
|
435
|
+
return;
|
|
436
|
+
const batch = pending;
|
|
437
|
+
const dropped = droppedKeys;
|
|
438
|
+
pending = new Map;
|
|
439
|
+
droppedKeys = 0;
|
|
440
|
+
if (dropped > 0) {
|
|
441
|
+
logger.warn("Handler error groups dropped: too many distinct errors", {
|
|
442
|
+
dropped,
|
|
443
|
+
cap: MAX_PENDING_KEYS
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
if (batch.size === 0)
|
|
447
|
+
return;
|
|
448
|
+
try {
|
|
449
|
+
const db = getTargetDb();
|
|
450
|
+
const subgraphs = new Set;
|
|
451
|
+
for (const group of batch.values()) {
|
|
452
|
+
subgraphs.add(group.subgraphName);
|
|
453
|
+
await db.insertInto("subgraph_error_groups").values({
|
|
454
|
+
subgraph_name: group.subgraphName,
|
|
455
|
+
fingerprint: group.fingerprint,
|
|
456
|
+
source_name: group.sourceName,
|
|
457
|
+
event_type: group.eventType,
|
|
458
|
+
message: group.message,
|
|
459
|
+
frame: group.frame,
|
|
460
|
+
count: group.count,
|
|
461
|
+
sample_tx_id: group.sampleTxId,
|
|
462
|
+
sample_block_height: group.sampleBlockHeight,
|
|
463
|
+
first_seen: group.firstSeenAt,
|
|
464
|
+
last_seen: group.lastSeenAt
|
|
465
|
+
}).onConflict((oc) => oc.columns(["subgraph_name", "fingerprint"]).doUpdateSet({
|
|
466
|
+
count: sql`subgraph_error_groups.count + ${group.count}`,
|
|
467
|
+
last_seen: group.lastSeenAt,
|
|
468
|
+
sample_tx_id: group.sampleTxId,
|
|
469
|
+
sample_block_height: group.sampleBlockHeight
|
|
470
|
+
})).execute();
|
|
471
|
+
}
|
|
472
|
+
for (const subgraphName of subgraphs) {
|
|
473
|
+
await sql`
|
|
474
|
+
DELETE FROM subgraph_error_groups
|
|
475
|
+
WHERE id IN (
|
|
476
|
+
SELECT id FROM subgraph_error_groups
|
|
477
|
+
WHERE subgraph_name = ${subgraphName}
|
|
478
|
+
ORDER BY last_seen DESC, id
|
|
479
|
+
OFFSET ${ERROR_GROUPS_CAP_PER_SUBGRAPH}
|
|
480
|
+
)
|
|
481
|
+
`.execute(db);
|
|
482
|
+
}
|
|
483
|
+
} catch (err) {
|
|
484
|
+
logger.warn("Failed to flush handler error groups", {
|
|
485
|
+
error: err instanceof Error ? err.message : String(err)
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
var recorderOverride = null;
|
|
490
|
+
function recordHandlerError(input) {
|
|
491
|
+
try {
|
|
492
|
+
if (recorderOverride) {
|
|
493
|
+
recorderOverride(input).catch((err) => {
|
|
494
|
+
logger.warn("Failed to record handler error group", {
|
|
495
|
+
subgraph: input.subgraphName,
|
|
496
|
+
error: err instanceof Error ? err.message : String(err)
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const message = (input.err instanceof Error ? input.err.message : String(input.err)).slice(0, MESSAGE_MAX_CHARS);
|
|
502
|
+
const frame = topFrame(input.err);
|
|
503
|
+
const fingerprint = errorFingerprint({
|
|
504
|
+
sourceName: input.sourceName,
|
|
505
|
+
eventType: input.eventType,
|
|
506
|
+
message,
|
|
507
|
+
frame
|
|
508
|
+
});
|
|
509
|
+
const key = `${input.subgraphName}|${fingerprint}`;
|
|
510
|
+
const now = new Date;
|
|
511
|
+
const existing = pending.get(key);
|
|
512
|
+
if (existing) {
|
|
513
|
+
existing.count++;
|
|
514
|
+
existing.sampleTxId = input.txId;
|
|
515
|
+
existing.sampleBlockHeight = input.blockHeight;
|
|
516
|
+
existing.lastSeenAt = now;
|
|
517
|
+
} else if (pending.size >= MAX_PENDING_KEYS) {
|
|
518
|
+
droppedKeys++;
|
|
519
|
+
} else {
|
|
520
|
+
pending.set(key, {
|
|
521
|
+
subgraphName: input.subgraphName,
|
|
522
|
+
fingerprint,
|
|
523
|
+
sourceName: input.sourceName,
|
|
524
|
+
eventType: input.eventType,
|
|
525
|
+
message,
|
|
526
|
+
frame,
|
|
527
|
+
count: 1,
|
|
528
|
+
sampleTxId: input.txId,
|
|
529
|
+
sampleBlockHeight: input.blockHeight,
|
|
530
|
+
firstSeenAt: now,
|
|
531
|
+
lastSeenAt: now
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
startFlusher();
|
|
535
|
+
} catch (err) {
|
|
536
|
+
logger.warn("Failed to record handler error group", {
|
|
537
|
+
subgraph: input.subgraphName,
|
|
538
|
+
error: err instanceof Error ? err.message : String(err)
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
391
543
|
// src/runtime/print-validate.ts
|
|
392
544
|
var COLUMN_TYPES = new Set([
|
|
393
545
|
"text",
|
|
@@ -471,11 +623,11 @@ function validatePrintPayload(prints, topic, data) {
|
|
|
471
623
|
import vm from "node:vm";
|
|
472
624
|
|
|
473
625
|
// src/verification.ts
|
|
474
|
-
import { createHash } from "node:crypto";
|
|
626
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
475
627
|
// package.json
|
|
476
628
|
var package_default = {
|
|
477
629
|
name: "@secondlayer/subgraphs",
|
|
478
|
-
version: "7.
|
|
630
|
+
version: "7.5.0",
|
|
479
631
|
description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
|
|
480
632
|
license: "MIT",
|
|
481
633
|
repository: {
|
|
@@ -535,7 +687,7 @@ var package_default = {
|
|
|
535
687
|
prepublishOnly: "bun run build"
|
|
536
688
|
},
|
|
537
689
|
dependencies: {
|
|
538
|
-
"@secondlayer/shared": "^13.
|
|
690
|
+
"@secondlayer/shared": "^13.4.0",
|
|
539
691
|
"@secondlayer/stacks": "^6.4.0",
|
|
540
692
|
"@secondlayer/verify": "^0.6.0",
|
|
541
693
|
esbuild: "^0.28.2",
|
|
@@ -723,7 +875,7 @@ function isDeterminismViolation(v) {
|
|
|
723
875
|
return v.level === "state" && v.reasons.length > 0;
|
|
724
876
|
}
|
|
725
877
|
function sha256(input) {
|
|
726
|
-
return
|
|
878
|
+
return createHash2("sha256").update(input).digest("hex");
|
|
727
879
|
}
|
|
728
880
|
function pinPreimage(input) {
|
|
729
881
|
const fields = {
|
|
@@ -1307,9 +1459,9 @@ function matchSources(sources, transactions, events, traitContracts = new Map, f
|
|
|
1307
1459
|
}
|
|
1308
1460
|
|
|
1309
1461
|
// src/runtime/violations.ts
|
|
1310
|
-
import { getTargetDb } from "@secondlayer/shared/db";
|
|
1311
|
-
import { logger } from "@secondlayer/shared/logger";
|
|
1312
|
-
import { sql } from "kysely";
|
|
1462
|
+
import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
|
|
1463
|
+
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
1464
|
+
import { sql as sql2 } from "kysely";
|
|
1313
1465
|
var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
|
|
1314
1466
|
function jsonSafe(value) {
|
|
1315
1467
|
if (typeof value === "bigint")
|
|
@@ -1329,7 +1481,7 @@ function jsonSafe(value) {
|
|
|
1329
1481
|
}
|
|
1330
1482
|
async function defaultRecordPrintViolation(input) {
|
|
1331
1483
|
try {
|
|
1332
|
-
const db =
|
|
1484
|
+
const db = getTargetDb2();
|
|
1333
1485
|
const sample = jsonSafe(input.samplePayload);
|
|
1334
1486
|
await db.insertInto("subgraph_violations").values({
|
|
1335
1487
|
subgraph_name: input.subgraphName,
|
|
@@ -1339,7 +1491,7 @@ async function defaultRecordPrintViolation(input) {
|
|
|
1339
1491
|
reason: input.reason,
|
|
1340
1492
|
sample_payload: sample
|
|
1341
1493
|
}).execute();
|
|
1342
|
-
await
|
|
1494
|
+
await sql2`
|
|
1343
1495
|
DELETE FROM subgraph_violations
|
|
1344
1496
|
WHERE id IN (
|
|
1345
1497
|
SELECT id FROM subgraph_violations
|
|
@@ -1349,7 +1501,7 @@ async function defaultRecordPrintViolation(input) {
|
|
|
1349
1501
|
)
|
|
1350
1502
|
`.execute(db);
|
|
1351
1503
|
} catch (err) {
|
|
1352
|
-
|
|
1504
|
+
logger2.warn("Failed to record print violation", {
|
|
1353
1505
|
subgraph: input.subgraphName,
|
|
1354
1506
|
error: err instanceof Error ? err.message : String(err)
|
|
1355
1507
|
});
|
|
@@ -1691,7 +1843,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1691
1843
|
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || clockRank(a.event) - clockRank(b.event) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
1692
1844
|
for (const { tx, event, sourceName } of units) {
|
|
1693
1845
|
if (errors >= threshold) {
|
|
1694
|
-
|
|
1846
|
+
logger3.error("Subgraph error threshold reached, skipping remaining events", {
|
|
1695
1847
|
subgraph: subgraph.name,
|
|
1696
1848
|
errors,
|
|
1697
1849
|
threshold
|
|
@@ -1706,7 +1858,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1706
1858
|
const filter = filterLookup.get(sourceName);
|
|
1707
1859
|
const handler = subgraph.handlers[sourceName] ?? null;
|
|
1708
1860
|
if (!handler) {
|
|
1709
|
-
|
|
1861
|
+
logger3.warn("No handler found for source", {
|
|
1710
1862
|
subgraph: subgraph.name,
|
|
1711
1863
|
sourceName,
|
|
1712
1864
|
txId: tx.tx_id
|
|
@@ -1763,7 +1915,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1763
1915
|
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1764
1916
|
if (!verdict.ok) {
|
|
1765
1917
|
skipped++;
|
|
1766
|
-
|
|
1918
|
+
logger3.warn("Print payload does not match declared prints schema", {
|
|
1767
1919
|
subgraph: subgraph.name,
|
|
1768
1920
|
sourceName,
|
|
1769
1921
|
topic: printPayload.topic,
|
|
@@ -1791,7 +1943,15 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1791
1943
|
throw err;
|
|
1792
1944
|
ctx.rollbackTo(checkpoint);
|
|
1793
1945
|
errors++;
|
|
1794
|
-
|
|
1946
|
+
recordHandlerError({
|
|
1947
|
+
subgraphName: subgraph.name,
|
|
1948
|
+
sourceName,
|
|
1949
|
+
eventType: event?.type ?? null,
|
|
1950
|
+
txId: tx.tx_id,
|
|
1951
|
+
blockHeight: ctx.block.height,
|
|
1952
|
+
err
|
|
1953
|
+
});
|
|
1954
|
+
logger3.error("Subgraph handler error", {
|
|
1795
1955
|
subgraph: subgraph.name,
|
|
1796
1956
|
sourceName,
|
|
1797
1957
|
txId: tx.tx_id,
|
|
@@ -1819,11 +1979,11 @@ async function applyBlock(def, data, ctx, scopes = {}) {
|
|
|
1819
1979
|
import { normalizeAbi as normalizeAbi2, toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
|
|
1820
1980
|
|
|
1821
1981
|
// src/runtime/context.ts
|
|
1822
|
-
import { logger as
|
|
1823
|
-
import { sql as
|
|
1982
|
+
import { logger as logger5 } from "@secondlayer/shared/logger";
|
|
1983
|
+
import { sql as sql3 } from "kysely";
|
|
1824
1984
|
|
|
1825
1985
|
// src/schema/generator.ts
|
|
1826
|
-
import { createHash as
|
|
1986
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1827
1987
|
|
|
1828
1988
|
// src/schema/utils.ts
|
|
1829
1989
|
import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
|
|
@@ -1854,7 +2014,7 @@ var MAX_IDENT_BYTES = 63;
|
|
|
1854
2014
|
function safeIndexName(name) {
|
|
1855
2015
|
if (name.length <= MAX_IDENT_BYTES)
|
|
1856
2016
|
return name;
|
|
1857
|
-
const hash =
|
|
2017
|
+
const hash = createHash3("sha256").update(name).digest("hex").slice(0, 8);
|
|
1858
2018
|
return `${name.slice(0, MAX_IDENT_BYTES - hash.length - 1)}_${hash}`;
|
|
1859
2019
|
}
|
|
1860
2020
|
function emitIndexedColumnIndexDDL(schemaName, tableName, colName, opts) {
|
|
@@ -1985,7 +2145,7 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
1985
2145
|
schema: def.schema,
|
|
1986
2146
|
sources: def.sources
|
|
1987
2147
|
}, (_key, value) => typeof value === "bigint" ? value.toString() : value);
|
|
1988
|
-
const hash =
|
|
2148
|
+
const hash = createHash3("sha256").update(hashInput).digest("hex");
|
|
1989
2149
|
return { statements, hash };
|
|
1990
2150
|
}
|
|
1991
2151
|
|
|
@@ -1994,7 +2154,7 @@ import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
|
1994
2154
|
import { chainReadArgsHash } from "@secondlayer/shared/chain-read";
|
|
1995
2155
|
import { getSourceDb } from "@secondlayer/shared/db";
|
|
1996
2156
|
import { hasNoLocalChain } from "@secondlayer/shared/db/queries/instance";
|
|
1997
|
-
import { logger as
|
|
2157
|
+
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
1998
2158
|
import {
|
|
1999
2159
|
http,
|
|
2000
2160
|
HttpRequestError,
|
|
@@ -2130,7 +2290,7 @@ function createChainReadClient(ctx) {
|
|
|
2130
2290
|
throw new HandlerReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a hosted contract read cannot be pinned to it. Reindex the subgraph from this block once the block is re-ingested.`);
|
|
2131
2291
|
}
|
|
2132
2292
|
if (!ctx.indexBlockHash) {
|
|
2133
|
-
|
|
2293
|
+
logger4.warn("Chain read is not pinned to a block (no index_block_hash)", {
|
|
2134
2294
|
contractId,
|
|
2135
2295
|
functionName: fn.name,
|
|
2136
2296
|
blockHeight: ctx.blockHeight,
|
|
@@ -2258,7 +2418,7 @@ class SubgraphContext {
|
|
|
2258
2418
|
data: { ...key, ...row, ...meta, _upsert_keys: keyColumns }
|
|
2259
2419
|
});
|
|
2260
2420
|
} else {
|
|
2261
|
-
|
|
2421
|
+
logger5.warn("upsert called without matching uniqueKeys constraint, using fallback", {
|
|
2262
2422
|
table,
|
|
2263
2423
|
keys: keyColumns
|
|
2264
2424
|
});
|
|
@@ -2320,7 +2480,7 @@ class SubgraphContext {
|
|
|
2320
2480
|
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
2321
2481
|
const { clause } = buildWhereClause(where);
|
|
2322
2482
|
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} ORDER BY "_id"${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
|
|
2323
|
-
const { rows } = await
|
|
2483
|
+
const { rows } = await sql3.raw(query).execute(this.db);
|
|
2324
2484
|
return rows.map((r) => this.coerceRow(table, r));
|
|
2325
2485
|
}
|
|
2326
2486
|
async findOne(table, where) {
|
|
@@ -2466,16 +2626,16 @@ class SubgraphContext {
|
|
|
2466
2626
|
const affected = (r) => r.numAffectedRows ?? 0n;
|
|
2467
2627
|
for (const stmt of statements) {
|
|
2468
2628
|
if (typeof stmt === "string") {
|
|
2469
|
-
await
|
|
2629
|
+
await sql3.raw(stmt).execute(executor);
|
|
2470
2630
|
continue;
|
|
2471
2631
|
}
|
|
2472
|
-
const updated = await
|
|
2632
|
+
const updated = await sql3.raw(stmt.update).execute(executor);
|
|
2473
2633
|
if (affected(updated) > 0n)
|
|
2474
2634
|
continue;
|
|
2475
|
-
const inserted = await
|
|
2635
|
+
const inserted = await sql3.raw(stmt.insert).execute(executor);
|
|
2476
2636
|
if (affected(inserted) > 0n)
|
|
2477
2637
|
continue;
|
|
2478
|
-
const retried = await
|
|
2638
|
+
const retried = await sql3.raw(stmt.update).execute(executor);
|
|
2479
2639
|
if (affected(retried) === 0n) {
|
|
2480
2640
|
throw new Error(`increment could not be applied to ${this.pgSchemaName}.${stmt.table} where ${stmt.keyPred}: row neither updated nor inserted`);
|
|
2481
2641
|
}
|
|
@@ -2501,13 +2661,13 @@ class SubgraphContext {
|
|
|
2501
2661
|
async ensureJournalTable() {
|
|
2502
2662
|
if (!this.journal || journalEnsured.has(this.pgSchemaName))
|
|
2503
2663
|
return;
|
|
2504
|
-
const { rows } = await
|
|
2664
|
+
const { rows } = await sql3.raw(`SELECT to_regclass('"${this.pgSchemaName}"."_journal"') AS r`).execute(this.db);
|
|
2505
2665
|
if (rows[0]?.r) {
|
|
2506
2666
|
journalEnsured.add(this.pgSchemaName);
|
|
2507
2667
|
return;
|
|
2508
2668
|
}
|
|
2509
2669
|
for (const stmt of emitJournalDDL(this.pgSchemaName)) {
|
|
2510
|
-
await
|
|
2670
|
+
await sql3.raw(stmt).execute(this.db);
|
|
2511
2671
|
}
|
|
2512
2672
|
}
|
|
2513
2673
|
columnSqlType(table, col) {
|
|
@@ -2863,7 +3023,7 @@ function sameValue(a, b) {
|
|
|
2863
3023
|
}
|
|
2864
3024
|
|
|
2865
3025
|
// src/runtime/batch-loader.ts
|
|
2866
|
-
import { sql as
|
|
3026
|
+
import { sql as sql4 } from "kysely";
|
|
2867
3027
|
function stateWriteBlocks(writesByHeight, txsByHeight) {
|
|
2868
3028
|
const out = new Map;
|
|
2869
3029
|
for (const [height, writes] of writesByHeight) {
|
|
@@ -2945,8 +3105,8 @@ async function loadBlockRange(db, fromHeight, toHeight, opts = {}) {
|
|
|
2945
3105
|
return result;
|
|
2946
3106
|
}
|
|
2947
3107
|
async function loadStateWrites(db, fromHeight, toHeight, feed) {
|
|
2948
|
-
const scope = feed.contracts ?
|
|
2949
|
-
const { rows } = await
|
|
3108
|
+
const scope = feed.contracts ? sql4`AND (${sql4.join(feed.contracts.map((c) => sql4`starts_with(key, ${`vm::${c}::`})`), sql4` OR `)})` : sql4``;
|
|
3109
|
+
const { rows } = await sql4`
|
|
2950
3110
|
SELECT block_height, ordinal, tx_index, key, value_hex
|
|
2951
3111
|
FROM state_writes
|
|
2952
3112
|
WHERE block_height >= ${fromHeight} AND block_height <= ${toHeight} ${scope}
|
|
@@ -3087,7 +3247,7 @@ function replayContracts(def) {
|
|
|
3087
3247
|
}
|
|
3088
3248
|
return [...out];
|
|
3089
3249
|
}
|
|
3090
|
-
var sha2562 = (s) =>
|
|
3250
|
+
var sha2562 = (s) => createHash4("sha256").update(s).digest("hex");
|
|
3091
3251
|
function checkPin(served, def) {
|
|
3092
3252
|
if (!served.pin || !served.pinPreimage)
|
|
3093
3253
|
return { status: "unchecked", notes: [] };
|
|
@@ -3329,7 +3489,7 @@ async function replaySubgraph(opts) {
|
|
|
3329
3489
|
}, { ...opts, bitcoinCheckpoint: checkpoint.bitcoin.height });
|
|
3330
3490
|
}
|
|
3331
3491
|
// src/verify/compare.ts
|
|
3332
|
-
import { createHash as
|
|
3492
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
3333
3493
|
function canonicalJson(value) {
|
|
3334
3494
|
if (typeof value === "bigint")
|
|
3335
3495
|
return value.toString();
|
|
@@ -3372,7 +3532,7 @@ function canonicalRow(def, row, columns = Object.keys(def.columns).sort()) {
|
|
|
3372
3532
|
}
|
|
3373
3533
|
var line = (r) => JSON.stringify(r);
|
|
3374
3534
|
function digest(lines) {
|
|
3375
|
-
return
|
|
3535
|
+
return createHash5("sha256").update([...lines].sort().join(`
|
|
3376
3536
|
`)).digest("hex");
|
|
3377
3537
|
}
|
|
3378
3538
|
var heightOf = (row) => Number(row._block_height);
|
|
@@ -3465,5 +3625,5 @@ export {
|
|
|
3465
3625
|
replaySubgraph
|
|
3466
3626
|
};
|
|
3467
3627
|
|
|
3468
|
-
//# debugId=
|
|
3628
|
+
//# debugId=D8DAF87FB8AF0A3A64756E2164756E21
|
|
3469
3629
|
//# sourceMappingURL=index.js.map
|