@secondlayer/subgraphs 7.2.0 → 7.4.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.d.ts +10 -2
- package/dist/index.js +195 -127
- package/dist/index.js.map +5 -5
- 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 +30 -12
- package/dist/runtime/replay.js.map +4 -4
- package/dist/schema/index.js +3 -3
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/index.js +1691 -1627
- package/dist/testing/index.js.map +7 -7
- package/dist/verification.js +3 -3
- package/dist/verification.js.map +1 -1
- package/dist/verify/index.js +101 -39
- package/dist/verify/index.js.map +5 -5
- 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.4.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.3.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=967809A9ACF2C51964756E2164756E21
|
|
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": "967809A9ACF2C51964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/verify/index.js
CHANGED
|
@@ -475,7 +475,7 @@ import { createHash } from "node:crypto";
|
|
|
475
475
|
// package.json
|
|
476
476
|
var package_default = {
|
|
477
477
|
name: "@secondlayer/subgraphs",
|
|
478
|
-
version: "7.
|
|
478
|
+
version: "7.4.0",
|
|
479
479
|
description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
|
|
480
480
|
license: "MIT",
|
|
481
481
|
repository: {
|
|
@@ -535,7 +535,7 @@ var package_default = {
|
|
|
535
535
|
prepublishOnly: "bun run build"
|
|
536
536
|
},
|
|
537
537
|
dependencies: {
|
|
538
|
-
"@secondlayer/shared": "^13.
|
|
538
|
+
"@secondlayer/shared": "^13.3.0",
|
|
539
539
|
"@secondlayer/stacks": "^6.4.0",
|
|
540
540
|
"@secondlayer/verify": "^0.6.0",
|
|
541
541
|
esbuild: "^0.28.2",
|
|
@@ -1990,9 +1990,17 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
1990
1990
|
}
|
|
1991
1991
|
|
|
1992
1992
|
// src/runtime/chain-read.ts
|
|
1993
|
+
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
1994
|
+
import { chainReadArgsHash } from "@secondlayer/shared/chain-read";
|
|
1993
1995
|
import { getSourceDb } from "@secondlayer/shared/db";
|
|
1996
|
+
import { hasNoLocalChain } from "@secondlayer/shared/db/queries/instance";
|
|
1994
1997
|
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
1995
|
-
import {
|
|
1998
|
+
import {
|
|
1999
|
+
http,
|
|
2000
|
+
HttpRequestError,
|
|
2001
|
+
ReadContractError,
|
|
2002
|
+
createPublicClient
|
|
2003
|
+
} from "@secondlayer/stacks";
|
|
1996
2004
|
import {
|
|
1997
2005
|
ContractResponseError,
|
|
1998
2006
|
buildFunctionArgs,
|
|
@@ -2013,9 +2021,48 @@ function readContractAt(ctx, contractId, abi, options) {
|
|
|
2013
2021
|
class ChainReadError extends Error {
|
|
2014
2022
|
name = "ChainReadError";
|
|
2015
2023
|
}
|
|
2016
|
-
|
|
2024
|
+
var NO_NODE_MESSAGE = "ctx.client needs a Stacks node: set STACKS_NODE_RPC_URL on the subgraph processor.";
|
|
2025
|
+
async function resolveReadTarget(rpcUrl, db) {
|
|
2026
|
+
if (rpcUrl)
|
|
2027
|
+
return { kind: "node", rpcUrl };
|
|
2028
|
+
const apiUrl = process.env.SUBGRAPH_INDEX_API_URL;
|
|
2029
|
+
if (!apiUrl || !await hasNoLocalChain(db)) {
|
|
2030
|
+
throw new HandlerReadError(NO_NODE_MESSAGE);
|
|
2031
|
+
}
|
|
2032
|
+
const apiKey = process.env.INDEX_INTERNAL_API_KEY?.trim();
|
|
2033
|
+
if (!apiKey) {
|
|
2034
|
+
throw new HandlerReadError("Hosted contract reads need the processor's key (INDEX_INTERNAL_API_KEY), and none is set. Set STACKS_NODE_RPC_URL to read a node directly instead.");
|
|
2035
|
+
}
|
|
2036
|
+
return { kind: "hosted", apiUrl, apiKey };
|
|
2037
|
+
}
|
|
2038
|
+
function hostedReadError(err, label) {
|
|
2039
|
+
if (!(err instanceof HttpRequestError))
|
|
2040
|
+
return;
|
|
2041
|
+
if (err.status === 400) {
|
|
2042
|
+
return new ChainReadError(`${label} was refused as invalid: ${err.details ?? err.message}`);
|
|
2043
|
+
}
|
|
2044
|
+
if (err.status === 403) {
|
|
2045
|
+
return new HandlerReadError(`${label}: hosted contract reads serve hosted subgraphs only. ${NO_NODE_MESSAGE}`, { cause: err });
|
|
2046
|
+
}
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
function isChainAnswer(err) {
|
|
2050
|
+
return err instanceof ContractResponseError || err instanceof ReadContractError;
|
|
2051
|
+
}
|
|
2052
|
+
function readClient(target) {
|
|
2053
|
+
if (target.kind === "node") {
|
|
2054
|
+
return createPublicClient({
|
|
2055
|
+
transport: http(target.rpcUrl, { retryCount: 0, timeout: 1e4 })
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2017
2058
|
return createPublicClient({
|
|
2018
|
-
transport: http(
|
|
2059
|
+
transport: http(`${target.apiUrl.replace(/\/+$/, "")}/v1/chain-read`, {
|
|
2060
|
+
retryCount: 3,
|
|
2061
|
+
timeout: 1e4,
|
|
2062
|
+
fetchOptions: {
|
|
2063
|
+
headers: { authorization: `Bearer ${target.apiKey}` }
|
|
2064
|
+
}
|
|
2065
|
+
})
|
|
2019
2066
|
});
|
|
2020
2067
|
}
|
|
2021
2068
|
|
|
@@ -2047,8 +2094,7 @@ function maxConcurrentReads() {
|
|
|
2047
2094
|
}
|
|
2048
2095
|
var gate = new ConcurrencyGate(maxConcurrentReads());
|
|
2049
2096
|
function hashArgs(args) {
|
|
2050
|
-
|
|
2051
|
-
return Bun.hash(serialized).toString(16);
|
|
2097
|
+
return chainReadArgsHash(args.map((arg) => serializeCV(arg)));
|
|
2052
2098
|
}
|
|
2053
2099
|
function findFunction(abi, name) {
|
|
2054
2100
|
const fn = abi.functions.find((f) => f.name === name);
|
|
@@ -2062,46 +2108,62 @@ function findFunction(abi, name) {
|
|
|
2062
2108
|
}
|
|
2063
2109
|
function createChainReadClient(ctx) {
|
|
2064
2110
|
const readValue = async (contractId, abi, functionName, args, options) => {
|
|
2065
|
-
const
|
|
2066
|
-
|
|
2067
|
-
|
|
2111
|
+
const db = ctx.db ?? getSourceDb();
|
|
2112
|
+
const rpcUrl = ctx.rpcUrl || process.env.STACKS_NODE_RPC_URL || undefined;
|
|
2113
|
+
if (!rpcUrl && !process.env.SUBGRAPH_INDEX_API_URL) {
|
|
2114
|
+
throw new HandlerReadError(NO_NODE_MESSAGE);
|
|
2068
2115
|
}
|
|
2069
2116
|
const constant = options.cache === "contract-constant";
|
|
2070
2117
|
if (!constant && !ctx.indexBlockHash) {
|
|
2071
|
-
throw new
|
|
2118
|
+
throw new HandlerReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a read cannot be pinned to it. Reading at the node's tip would make this handler non-deterministic (the same reindex would produce different rows). Re-ingest the block, or pass cache: "contract-constant" if the value genuinely cannot change.`);
|
|
2072
2119
|
}
|
|
2073
2120
|
const fn = findFunction(abi, functionName);
|
|
2074
2121
|
const clarityArgs = buildFunctionArgs(fn, args);
|
|
2075
|
-
const db = ctx.db ?? getSourceDb();
|
|
2076
2122
|
const argsHash = hashArgs(clarityArgs);
|
|
2077
2123
|
const pinnedTo = constant ? null : ctx.indexBlockHash;
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2124
|
+
let resultHex;
|
|
2125
|
+
try {
|
|
2126
|
+
const cached = await db.selectFrom("chain_read_cache").select("result_hex").where("contract_id", "=", contractId).where("function_name", "=", fn.name).where("args_hash", "=", argsHash).where((eb) => pinnedTo === null ? eb("index_block_hash", "is", null) : eb("index_block_hash", "=", pinnedTo)).executeTakeFirst();
|
|
2127
|
+
resultHex = cached?.result_hex ?? await gate.run(async () => {
|
|
2128
|
+
const target = await resolveReadTarget(rpcUrl, db);
|
|
2129
|
+
if (!ctx.indexBlockHash && target.kind === "hosted") {
|
|
2130
|
+
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
|
+
}
|
|
2132
|
+
if (!ctx.indexBlockHash) {
|
|
2133
|
+
logger3.warn("Chain read is not pinned to a block (no index_block_hash)", {
|
|
2134
|
+
contractId,
|
|
2135
|
+
functionName: fn.name,
|
|
2136
|
+
blockHeight: ctx.blockHeight,
|
|
2137
|
+
note: 'cache: "contract-constant" asserts this value cannot change; the node answered at its own tip'
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
try {
|
|
2141
|
+
const value = await readContract(readClient(target), {
|
|
2142
|
+
contract: contractId,
|
|
2143
|
+
functionName: fn.name,
|
|
2144
|
+
args: clarityArgs,
|
|
2145
|
+
tip: ctx.indexBlockHash ?? undefined
|
|
2146
|
+
});
|
|
2147
|
+
return serializeCV(value);
|
|
2148
|
+
} catch (err) {
|
|
2149
|
+
throw target.kind === "hosted" && hostedReadError(err, `Chain read ${contractId}::${fn.name}`) || err;
|
|
2150
|
+
}
|
|
2093
2151
|
});
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2152
|
+
if (!cached) {
|
|
2153
|
+
await db.insertInto("chain_read_cache").values({
|
|
2154
|
+
contract_id: contractId,
|
|
2155
|
+
function_name: fn.name,
|
|
2156
|
+
args_hash: argsHash,
|
|
2157
|
+
index_block_hash: pinnedTo,
|
|
2158
|
+
block_height: constant ? null : ctx.blockHeight,
|
|
2159
|
+
result_hex: resultHex
|
|
2160
|
+
}).onConflict((oc) => oc.doNothing()).execute();
|
|
2161
|
+
}
|
|
2162
|
+
} catch (err) {
|
|
2163
|
+
if (isChainAnswer(err) || err instanceof ChainReadError || err instanceof HandlerReadError) {
|
|
2164
|
+
throw err;
|
|
2165
|
+
}
|
|
2166
|
+
throw new HandlerReadError(`Chain read ${contractId}::${fn.name} at block ${ctx.blockHeight} failed: ${getErrorMessage2(err)}`, { cause: err });
|
|
2105
2167
|
}
|
|
2106
2168
|
const value = deserializeCV4(resultHex);
|
|
2107
2169
|
const js = clarityValueToJSUntyped(fn.outputs, value);
|
|
@@ -3403,5 +3465,5 @@ export {
|
|
|
3403
3465
|
replaySubgraph
|
|
3404
3466
|
};
|
|
3405
3467
|
|
|
3406
|
-
//# debugId=
|
|
3468
|
+
//# debugId=73562E7C772D0E6364756E2164756E21
|
|
3407
3469
|
//# sourceMappingURL=index.js.map
|