@secondlayer/subgraphs 3.24.0 → 3.24.2
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/index.js +379 -52
- package/dist/src/index.js.map +9 -6
- package/dist/src/runtime/block-processor.d.ts +8 -1
- package/dist/src/runtime/block-processor.js +349 -22
- package/dist/src/runtime/block-processor.js.map +9 -6
- package/dist/src/runtime/catchup.js +358 -31
- package/dist/src/runtime/catchup.js.map +9 -6
- package/dist/src/runtime/context.js +19 -14
- package/dist/src/runtime/context.js.map +4 -4
- package/dist/src/runtime/processor.js +419 -92
- package/dist/src/runtime/processor.js.map +9 -6
- package/dist/src/runtime/reindex.js +379 -52
- package/dist/src/runtime/reindex.js.map +9 -6
- package/dist/src/runtime/reorg.js +365 -38
- package/dist/src/runtime/reorg.js.map +9 -6
- package/dist/src/runtime/replay.js +6 -1
- package/dist/src/runtime/replay.js.map +3 -3
- package/dist/src/schema/index.js +19 -14
- package/dist/src/schema/index.js.map +4 -4
- package/dist/src/service.js +422 -95
- package/dist/src/service.js.map +9 -6
- package/dist/src/testing/index.js +19 -14
- package/dist/src/testing/index.js.map +4 -4
- package/package.json +1 -1
|
@@ -11,6 +11,11 @@ import { createHash } from "node:crypto";
|
|
|
11
11
|
|
|
12
12
|
// src/schema/utils.ts
|
|
13
13
|
import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
|
|
14
|
+
function quotePgIdent(name) {
|
|
15
|
+
if (/^[a-z_][a-z0-9_]*$/i.test(name))
|
|
16
|
+
return name;
|
|
17
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
18
|
+
}
|
|
14
19
|
|
|
15
20
|
// src/schema/generator.ts
|
|
16
21
|
var TYPE_MAP = {
|
|
@@ -35,7 +40,7 @@ function tableNeedsTrgm(tableDef) {
|
|
|
35
40
|
return Object.values(tableDef.columns).some((col) => col.search);
|
|
36
41
|
}
|
|
37
42
|
function emitTableDDL(schemaName, tableName, tableDef) {
|
|
38
|
-
const qualifiedName = `${schemaName}.${tableName}`;
|
|
43
|
+
const qualifiedName = `${quotePgIdent(schemaName)}.${quotePgIdent(tableName)}`;
|
|
39
44
|
const statements = [];
|
|
40
45
|
const columnDefs = [
|
|
41
46
|
"_id BIGSERIAL PRIMARY KEY",
|
|
@@ -46,12 +51,12 @@ function emitTableDDL(schemaName, tableName, tableDef) {
|
|
|
46
51
|
for (const [colName, col] of Object.entries(tableDef.columns)) {
|
|
47
52
|
const sqlType = TYPE_MAP[col.type];
|
|
48
53
|
const nullable = col.nullable ? "" : " NOT NULL";
|
|
49
|
-
let colDef = `${colName} ${sqlType}${nullable}`;
|
|
54
|
+
let colDef = `${quotePgIdent(colName)} ${sqlType}${nullable}`;
|
|
50
55
|
if (col.default !== undefined) {
|
|
51
56
|
colDef += ` DEFAULT ${escapeLiteralDefault(col.default)}`;
|
|
52
57
|
}
|
|
53
58
|
if (col.type === "uint") {
|
|
54
|
-
colDef += ` CHECK (${colName} >= 0)`;
|
|
59
|
+
colDef += ` CHECK (${quotePgIdent(colName)} >= 0)`;
|
|
55
60
|
}
|
|
56
61
|
columnDefs.push(colDef);
|
|
57
62
|
}
|
|
@@ -59,30 +64,30 @@ function emitTableDDL(schemaName, tableName, tableDef) {
|
|
|
59
64
|
${columnDefs.join(`,
|
|
60
65
|
`)}
|
|
61
66
|
)`);
|
|
62
|
-
statements.push(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_block_height ON ${qualifiedName} (_block_height)`);
|
|
63
|
-
statements.push(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_tx_id ON ${qualifiedName} (_tx_id)`);
|
|
67
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${quotePgIdent(`idx_${schemaName}_${tableName}_block_height`)} ON ${qualifiedName} (_block_height)`);
|
|
68
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${quotePgIdent(`idx_${schemaName}_${tableName}_tx_id`)} ON ${qualifiedName} (_tx_id)`);
|
|
64
69
|
for (const [colName, col] of Object.entries(tableDef.columns)) {
|
|
65
70
|
if (col.indexed) {
|
|
66
|
-
statements.push(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName} ON ${qualifiedName} (${colName})`);
|
|
71
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${quotePgIdent(`idx_${schemaName}_${tableName}_${colName}`)} ON ${qualifiedName} (${quotePgIdent(colName)})`);
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
for (const [colName, col] of Object.entries(tableDef.columns)) {
|
|
70
75
|
if (col.search) {
|
|
71
|
-
statements.push(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName}_trgm ON ${qualifiedName} USING gin (${colName} gin_trgm_ops)`);
|
|
76
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${quotePgIdent(`idx_${schemaName}_${tableName}_${colName}_trgm`)} ON ${qualifiedName} USING gin (${quotePgIdent(colName)} gin_trgm_ops)`);
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
79
|
if (tableDef.indexes) {
|
|
75
80
|
for (let i = 0;i < tableDef.indexes.length; i++) {
|
|
76
81
|
const cols = tableDef.indexes[i];
|
|
77
|
-
const idxName = `idx_${schemaName}_${tableName}_composite_${i}
|
|
78
|
-
statements.push(`CREATE INDEX IF NOT EXISTS ${idxName} ON ${qualifiedName} (${cols.join(", ")})`);
|
|
82
|
+
const idxName = quotePgIdent(`idx_${schemaName}_${tableName}_composite_${i}`);
|
|
83
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${idxName} ON ${qualifiedName} (${cols.map(quotePgIdent).join(", ")})`);
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
if (tableDef.uniqueKeys) {
|
|
82
87
|
for (let i = 0;i < tableDef.uniqueKeys.length; i++) {
|
|
83
88
|
const cols = tableDef.uniqueKeys[i];
|
|
84
|
-
const constraintName = `uq_${schemaName}_${tableName}_${cols.join("_")}
|
|
85
|
-
statements.push(`ALTER TABLE ${qualifiedName} ADD CONSTRAINT ${constraintName} UNIQUE (${cols.join(", ")})`);
|
|
89
|
+
const constraintName = quotePgIdent(`uq_${schemaName}_${tableName}_${cols.join("_")}`);
|
|
90
|
+
statements.push(`ALTER TABLE ${qualifiedName} ADD CONSTRAINT ${constraintName} UNIQUE (${cols.map(quotePgIdent).join(", ")})`);
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
93
|
return statements;
|
|
@@ -114,8 +119,8 @@ function emitFactoryDDL(schemaName) {
|
|
|
114
119
|
}
|
|
115
120
|
function emitForeignKeyDDL(schemaName, tableName, tableDef) {
|
|
116
121
|
return (tableDef.relations ?? []).map((rel) => {
|
|
117
|
-
const constraintName = `fk_${schemaName}_${tableName}_${rel.name}
|
|
118
|
-
return `ALTER TABLE ${schemaName}.${tableName} ADD CONSTRAINT ${constraintName} ` + `FOREIGN KEY (${rel.fields.join(", ")}) ` + `REFERENCES ${schemaName}.${rel.references} (${rel.referencedColumns.join(", ")})`;
|
|
122
|
+
const constraintName = quotePgIdent(`fk_${schemaName}_${tableName}_${rel.name}`);
|
|
123
|
+
return `ALTER TABLE ${quotePgIdent(schemaName)}.${quotePgIdent(tableName)} ADD CONSTRAINT ${constraintName} ` + `FOREIGN KEY (${rel.fields.map(quotePgIdent).join(", ")}) ` + `REFERENCES ${quotePgIdent(schemaName)}.${quotePgIdent(rel.references)} (${rel.referencedColumns.map(quotePgIdent).join(", ")})`;
|
|
119
124
|
});
|
|
120
125
|
}
|
|
121
126
|
function generateSubgraphSQL(def, schemaNameOverride) {
|
|
@@ -1634,7 +1639,7 @@ import {
|
|
|
1634
1639
|
rewindLiveProgress,
|
|
1635
1640
|
updateSubgraphStatus
|
|
1636
1641
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
1637
|
-
import { logger as
|
|
1642
|
+
import { logger as logger7 } from "@secondlayer/shared/logger";
|
|
1638
1643
|
import { sql as sql3 } from "kysely";
|
|
1639
1644
|
|
|
1640
1645
|
// src/runtime/block-source.ts
|
|
@@ -2075,6 +2080,315 @@ async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block
|
|
|
2075
2080
|
return rows.length;
|
|
2076
2081
|
}
|
|
2077
2082
|
|
|
2083
|
+
// src/runtime/sandbox/flag.ts
|
|
2084
|
+
function sandboxWorkersGloballyEnabled() {
|
|
2085
|
+
return process.env.SUBGRAPH_SANDBOX_WORKERS === "1";
|
|
2086
|
+
}
|
|
2087
|
+
function sandboxEnabled(subgraph) {
|
|
2088
|
+
return sandboxWorkersGloballyEnabled() && subgraph.sandbox_workers === true;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
// src/runtime/sandbox/host.ts
|
|
2092
|
+
import { tmpdir } from "node:os";
|
|
2093
|
+
import { fileURLToPath } from "node:url";
|
|
2094
|
+
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
2095
|
+
|
|
2096
|
+
// src/runtime/sandbox/bundle.ts
|
|
2097
|
+
import esbuild from "esbuild";
|
|
2098
|
+
var ALLOWED_BARE_SPECIFIERS = new Set;
|
|
2099
|
+
var SUBGRAPHS_PACKAGE_SPECIFIER = "@secondlayer/subgraphs";
|
|
2100
|
+
var SUBGRAPHS_STUB_SOURCE = `export function defineSubgraph(def) { return def; }
|
|
2101
|
+
`;
|
|
2102
|
+
function resolverLockdownPlugin() {
|
|
2103
|
+
return {
|
|
2104
|
+
name: "f071-sandbox-resolver-lockdown",
|
|
2105
|
+
setup(build) {
|
|
2106
|
+
build.onResolve({ filter: /^@secondlayer\/subgraphs$/ }, (args) => ({
|
|
2107
|
+
path: args.path,
|
|
2108
|
+
namespace: "f071-sandbox-subgraphs-stub"
|
|
2109
|
+
}));
|
|
2110
|
+
build.onLoad({ filter: /.*/, namespace: "f071-sandbox-subgraphs-stub" }, () => ({ contents: SUBGRAPHS_STUB_SOURCE, loader: "js" }));
|
|
2111
|
+
build.onResolve({ filter: /^(node:|bun:)/ }, (args) => ({
|
|
2112
|
+
path: args.path,
|
|
2113
|
+
namespace: "f071-sandbox-blocked"
|
|
2114
|
+
}));
|
|
2115
|
+
build.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
2116
|
+
if (args.path === SUBGRAPHS_PACKAGE_SPECIFIER)
|
|
2117
|
+
return;
|
|
2118
|
+
if (ALLOWED_BARE_SPECIFIERS.has(args.path))
|
|
2119
|
+
return;
|
|
2120
|
+
return { path: args.path, namespace: "f071-sandbox-blocked" };
|
|
2121
|
+
});
|
|
2122
|
+
build.onLoad({ filter: /.*/, namespace: "f071-sandbox-blocked" }, (args) => ({
|
|
2123
|
+
contents: `throw new Error(${JSON.stringify(`subgraph sandbox: import of "${args.path}" is blocked in sandboxed handler code — handlers must use the injected \`ctx\` for all I/O`)});`,
|
|
2124
|
+
loader: "js"
|
|
2125
|
+
}));
|
|
2126
|
+
}
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
async function bundleHandlerCode(handlerCode) {
|
|
2130
|
+
const result = await esbuild.build({
|
|
2131
|
+
stdin: {
|
|
2132
|
+
contents: handlerCode,
|
|
2133
|
+
loader: "ts",
|
|
2134
|
+
resolveDir: process.cwd()
|
|
2135
|
+
},
|
|
2136
|
+
bundle: true,
|
|
2137
|
+
platform: "neutral",
|
|
2138
|
+
format: "esm",
|
|
2139
|
+
write: false,
|
|
2140
|
+
logLevel: "silent",
|
|
2141
|
+
plugins: [resolverLockdownPlugin()]
|
|
2142
|
+
});
|
|
2143
|
+
const out = result.outputFiles?.[0];
|
|
2144
|
+
if (!out)
|
|
2145
|
+
throw new Error("esbuild produced no output for handler_code bundle");
|
|
2146
|
+
return new TextDecoder().decode(out.contents);
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// src/runtime/sandbox/host.ts
|
|
2150
|
+
var BLOCK_TIMEOUT_MS = Number.parseInt(process.env.SUBGRAPH_SANDBOX_BLOCK_TIMEOUT_MS ?? "60000");
|
|
2151
|
+
var SUBPROCESS_ENTRY_PATH = fileURLToPath(new URL("./subprocess-entry.ts", import.meta.url));
|
|
2152
|
+
function spawnSandboxProcess() {
|
|
2153
|
+
let onMessage = null;
|
|
2154
|
+
const proc = Bun.spawn({
|
|
2155
|
+
cmd: [process.execPath, "--no-env-file", SUBPROCESS_ENTRY_PATH],
|
|
2156
|
+
env: { PATH: process.env.PATH ?? "" },
|
|
2157
|
+
cwd: tmpdir(),
|
|
2158
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
2159
|
+
serialization: "advanced",
|
|
2160
|
+
ipc(message) {
|
|
2161
|
+
onMessage?.(message);
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
(async () => {
|
|
2165
|
+
const reader = proc.stderr.getReader();
|
|
2166
|
+
const decoder = new TextDecoder;
|
|
2167
|
+
try {
|
|
2168
|
+
while (true) {
|
|
2169
|
+
const { done, value } = await reader.read();
|
|
2170
|
+
if (done)
|
|
2171
|
+
break;
|
|
2172
|
+
const text = decoder.decode(value, { stream: true });
|
|
2173
|
+
if (text.trim()) {
|
|
2174
|
+
logger6.warn("Sandbox subprocess stderr", { stderr: text.trim() });
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
} catch {}
|
|
2178
|
+
})();
|
|
2179
|
+
return {
|
|
2180
|
+
send: (msg) => proc.send(msg),
|
|
2181
|
+
kill: () => proc.kill(),
|
|
2182
|
+
exited: proc.exited,
|
|
2183
|
+
setOnMessage: (handler) => {
|
|
2184
|
+
onMessage = handler;
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
var pool = new Map;
|
|
2189
|
+
|
|
2190
|
+
class SandboxWorkerCrash extends Error {
|
|
2191
|
+
constructor(subgraphName, cause) {
|
|
2192
|
+
super(`sandbox subprocess for "${subgraphName}" failed: ${cause}`);
|
|
2193
|
+
this.name = "SandboxWorkerCrash";
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
async function initSandboxProcess(proc, subgraphName, version, handlerCode) {
|
|
2197
|
+
const bundledCode = await bundleHandlerCode(handlerCode);
|
|
2198
|
+
let settled = false;
|
|
2199
|
+
const ready = new Promise((resolve, reject) => {
|
|
2200
|
+
proc.setOnMessage((msg) => {
|
|
2201
|
+
if (msg.type === "ready" && msg.version === version) {
|
|
2202
|
+
settled = true;
|
|
2203
|
+
proc.setOnMessage(null);
|
|
2204
|
+
resolve();
|
|
2205
|
+
} else if (msg.type === "error") {
|
|
2206
|
+
settled = true;
|
|
2207
|
+
proc.setOnMessage(null);
|
|
2208
|
+
reject(new SandboxWorkerCrash(subgraphName, msg.message));
|
|
2209
|
+
}
|
|
2210
|
+
});
|
|
2211
|
+
const init = { type: "init", bundledCode, version };
|
|
2212
|
+
proc.send(init);
|
|
2213
|
+
});
|
|
2214
|
+
const crashed = proc.exited.then((code) => {
|
|
2215
|
+
if (settled)
|
|
2216
|
+
return;
|
|
2217
|
+
throw new SandboxWorkerCrash(subgraphName, `subprocess exited (code ${code}) during init`);
|
|
2218
|
+
});
|
|
2219
|
+
await Promise.race([ready, crashed]);
|
|
2220
|
+
}
|
|
2221
|
+
async function ensureSandboxProcess(subgraphName, version, handlerCode) {
|
|
2222
|
+
if (handlerCode == null) {
|
|
2223
|
+
throw new SandboxWorkerCrash(subgraphName, "subgraph has no handler_code; cannot bundle for the sandbox path");
|
|
2224
|
+
}
|
|
2225
|
+
const existing = pool.get(subgraphName);
|
|
2226
|
+
if (existing && existing.version === version)
|
|
2227
|
+
return existing;
|
|
2228
|
+
if (existing) {
|
|
2229
|
+
try {
|
|
2230
|
+
await initSandboxProcess(existing.proc, subgraphName, version, handlerCode);
|
|
2231
|
+
existing.version = version;
|
|
2232
|
+
logger6.info("Sandbox subprocess handler reloaded", {
|
|
2233
|
+
subgraph: subgraphName,
|
|
2234
|
+
version
|
|
2235
|
+
});
|
|
2236
|
+
return existing;
|
|
2237
|
+
} catch (err) {
|
|
2238
|
+
existing.proc.kill();
|
|
2239
|
+
pool.delete(subgraphName);
|
|
2240
|
+
logger6.warn("Sandbox subprocess re-init failed; respawning", {
|
|
2241
|
+
subgraph: subgraphName,
|
|
2242
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
const proc = spawnSandboxProcess();
|
|
2247
|
+
try {
|
|
2248
|
+
await initSandboxProcess(proc, subgraphName, version, handlerCode);
|
|
2249
|
+
} catch (err) {
|
|
2250
|
+
proc.kill();
|
|
2251
|
+
throw err;
|
|
2252
|
+
}
|
|
2253
|
+
const entry = { proc, version, chain: Promise.resolve() };
|
|
2254
|
+
pool.set(subgraphName, entry);
|
|
2255
|
+
return entry;
|
|
2256
|
+
}
|
|
2257
|
+
function evictSandboxWorker(subgraphName) {
|
|
2258
|
+
const entry = pool.get(subgraphName);
|
|
2259
|
+
if (!entry)
|
|
2260
|
+
return;
|
|
2261
|
+
entry.proc.kill();
|
|
2262
|
+
pool.delete(subgraphName);
|
|
2263
|
+
}
|
|
2264
|
+
async function runHandlersSandboxed(params) {
|
|
2265
|
+
const { subgraphName, version, handlerCode, hostCtx, block, matched } = params;
|
|
2266
|
+
const entry = await ensureSandboxProcess(subgraphName, version, handlerCode);
|
|
2267
|
+
const run = entry.chain.then(() => runBlockThroughProcess(entry, subgraphName, hostCtx, block, matched));
|
|
2268
|
+
entry.chain = run.catch(() => {
|
|
2269
|
+
return;
|
|
2270
|
+
});
|
|
2271
|
+
return run;
|
|
2272
|
+
}
|
|
2273
|
+
async function runBlockThroughProcess(entry, subgraphName, hostCtx, block, matched) {
|
|
2274
|
+
const { proc } = entry;
|
|
2275
|
+
let timeoutHandle;
|
|
2276
|
+
let settled = false;
|
|
2277
|
+
try {
|
|
2278
|
+
const done = new Promise((resolve, reject) => {
|
|
2279
|
+
proc.setOnMessage((msg) => {
|
|
2280
|
+
if (msg.type === "readRequest") {
|
|
2281
|
+
answerRead(hostCtx, msg).then((reply) => {
|
|
2282
|
+
proc.send({ type: "readResponse", id: msg.id, reply });
|
|
2283
|
+
}).catch((err) => {
|
|
2284
|
+
settled = true;
|
|
2285
|
+
reject(new SandboxWorkerCrash(subgraphName, `host read failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
2286
|
+
});
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
if (msg.type === "blockDone") {
|
|
2290
|
+
settled = true;
|
|
2291
|
+
resolve({
|
|
2292
|
+
ops: msg.ops,
|
|
2293
|
+
result: { processed: msg.processed, errors: msg.errors }
|
|
2294
|
+
});
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
if (msg.type === "error") {
|
|
2298
|
+
settled = true;
|
|
2299
|
+
reject(new SandboxWorkerCrash(subgraphName, msg.message));
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
proc.send({ type: "runBlock", block, matched });
|
|
2303
|
+
});
|
|
2304
|
+
const crashed = proc.exited.then((code) => {
|
|
2305
|
+
if (settled)
|
|
2306
|
+
return;
|
|
2307
|
+
throw new SandboxWorkerCrash(subgraphName, `subprocess exited unexpectedly (code ${code}) mid-block`);
|
|
2308
|
+
});
|
|
2309
|
+
const deadline = new Promise((_, reject) => {
|
|
2310
|
+
timeoutHandle = setTimeout(() => {
|
|
2311
|
+
settled = true;
|
|
2312
|
+
reject(new SandboxWorkerCrash(subgraphName, `block ${block.height} exceeded ${BLOCK_TIMEOUT_MS}ms deadline`));
|
|
2313
|
+
}, BLOCK_TIMEOUT_MS);
|
|
2314
|
+
});
|
|
2315
|
+
const { ops, result } = await Promise.race([done, crashed, deadline]);
|
|
2316
|
+
for (const op of ops) {
|
|
2317
|
+
hostCtx.setTx(op.tx);
|
|
2318
|
+
replayOp(hostCtx, op);
|
|
2319
|
+
}
|
|
2320
|
+
return result;
|
|
2321
|
+
} catch (err) {
|
|
2322
|
+
evictSandboxWorker(subgraphName);
|
|
2323
|
+
logger6.warn("Sandbox subprocess run failed; subprocess evicted", {
|
|
2324
|
+
subgraph: subgraphName,
|
|
2325
|
+
blockHeight: block.height,
|
|
2326
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2327
|
+
});
|
|
2328
|
+
throw err;
|
|
2329
|
+
} finally {
|
|
2330
|
+
if (timeoutHandle !== undefined)
|
|
2331
|
+
clearTimeout(timeoutHandle);
|
|
2332
|
+
proc.setOnMessage(null);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
function replayOp(hostCtx, op) {
|
|
2336
|
+
switch (op.method) {
|
|
2337
|
+
case "insert":
|
|
2338
|
+
hostCtx.insert(op.table, op.args[0]);
|
|
2339
|
+
return;
|
|
2340
|
+
case "upsert":
|
|
2341
|
+
hostCtx.upsert(op.table, op.args[0], op.args[1]);
|
|
2342
|
+
return;
|
|
2343
|
+
case "increment":
|
|
2344
|
+
hostCtx.increment(op.table, op.args[0], op.args[1]);
|
|
2345
|
+
return;
|
|
2346
|
+
case "update":
|
|
2347
|
+
hostCtx.update(op.table, op.args[0], op.args[1]);
|
|
2348
|
+
return;
|
|
2349
|
+
case "delete":
|
|
2350
|
+
hostCtx.delete(op.table, op.args[0]);
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
async function answerRead(hostCtx, msg) {
|
|
2355
|
+
const { method, table, where, column } = msg;
|
|
2356
|
+
switch (method) {
|
|
2357
|
+
case "findOne":
|
|
2358
|
+
return { kind: "row", row: await hostCtx.findOne(table, where) };
|
|
2359
|
+
case "findMany":
|
|
2360
|
+
return { kind: "rows", rows: await hostCtx.findMany(table, where) };
|
|
2361
|
+
case "count":
|
|
2362
|
+
return { kind: "count", count: await hostCtx.count(table, where) };
|
|
2363
|
+
case "countDistinct": {
|
|
2364
|
+
if (!column)
|
|
2365
|
+
throw new Error("countDistinct read missing column");
|
|
2366
|
+
return {
|
|
2367
|
+
kind: "count",
|
|
2368
|
+
count: await hostCtx.countDistinct(table, column, where)
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
case "sum": {
|
|
2372
|
+
if (!column)
|
|
2373
|
+
throw new Error("sum read missing column");
|
|
2374
|
+
const v = await hostCtx.sum(table, column, where);
|
|
2375
|
+
return { kind: "amount", amount: v.toString() };
|
|
2376
|
+
}
|
|
2377
|
+
case "min": {
|
|
2378
|
+
if (!column)
|
|
2379
|
+
throw new Error("min read missing column");
|
|
2380
|
+
const v = await hostCtx.min(table, column, where);
|
|
2381
|
+
return { kind: "amount", amount: v == null ? null : v.toString() };
|
|
2382
|
+
}
|
|
2383
|
+
case "max": {
|
|
2384
|
+
if (!column)
|
|
2385
|
+
throw new Error("max read missing column");
|
|
2386
|
+
const v = await hostCtx.max(table, column, where);
|
|
2387
|
+
return { kind: "amount", amount: v == null ? null : v.toString() };
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2078
2392
|
// src/runtime/subscription-state.ts
|
|
2079
2393
|
import { listSubscriptions } from "@secondlayer/shared/db/queries/subscriptions";
|
|
2080
2394
|
import { sql as sql2 } from "kysely";
|
|
@@ -2259,13 +2573,17 @@ async function resolveRoute(subgraphName, targetDb) {
|
|
|
2259
2573
|
const route = {
|
|
2260
2574
|
schemaName: row?.schema_name ?? pgSchemaName(subgraphName),
|
|
2261
2575
|
dataDb: row && byo ? resolveSubgraphDb(row) : targetDb,
|
|
2262
|
-
byo
|
|
2576
|
+
byo,
|
|
2577
|
+
sandboxWorkers: row?.sandbox_workers === true,
|
|
2578
|
+
handlerCode: row?.handler_code ?? null,
|
|
2579
|
+
version: row?.version ?? ""
|
|
2263
2580
|
};
|
|
2264
2581
|
routeCache.set(subgraphName, route);
|
|
2265
2582
|
return route;
|
|
2266
2583
|
}
|
|
2267
2584
|
function invalidateSubgraphRoute(subgraphName) {
|
|
2268
2585
|
routeCache.delete(subgraphName);
|
|
2586
|
+
evictSandboxWorker(subgraphName);
|
|
2269
2587
|
}
|
|
2270
2588
|
async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, txs, evts) {
|
|
2271
2589
|
const factories = new Map;
|
|
@@ -2371,7 +2689,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
|
|
|
2371
2689
|
const delay = retryDelaysMs[attempt];
|
|
2372
2690
|
if (delay === undefined)
|
|
2373
2691
|
break;
|
|
2374
|
-
|
|
2692
|
+
logger7.warn("Block processing failed, retrying", {
|
|
2375
2693
|
subgraph: subgraphName,
|
|
2376
2694
|
blockHeight,
|
|
2377
2695
|
attempt: attempt + 1,
|
|
@@ -2403,7 +2721,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2403
2721
|
} else {
|
|
2404
2722
|
const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
|
|
2405
2723
|
if (!data) {
|
|
2406
|
-
|
|
2724
|
+
logger7.debug("Block not found or non-canonical for subgraph processing", {
|
|
2407
2725
|
subgraph: subgraphName,
|
|
2408
2726
|
blockHeight
|
|
2409
2727
|
});
|
|
@@ -2539,7 +2857,16 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2539
2857
|
}
|
|
2540
2858
|
const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
|
|
2541
2859
|
const handlerStart = performance.now();
|
|
2542
|
-
const runResult =
|
|
2860
|
+
const runResult = sandboxEnabled({
|
|
2861
|
+
sandbox_workers: route.sandboxWorkers
|
|
2862
|
+
}) ? await runHandlersSandboxed({
|
|
2863
|
+
subgraphName,
|
|
2864
|
+
version: route.version,
|
|
2865
|
+
handlerCode: route.handlerCode,
|
|
2866
|
+
hostCtx: ctx,
|
|
2867
|
+
block: blockMeta,
|
|
2868
|
+
matched
|
|
2869
|
+
}) : await runHandlers(subgraph, matched, ctx);
|
|
2543
2870
|
handlerMs = performance.now() - handlerStart;
|
|
2544
2871
|
result.processed = runResult.processed;
|
|
2545
2872
|
result.errors = runResult.errors;
|
|
@@ -2565,7 +2892,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2565
2892
|
});
|
|
2566
2893
|
} catch (err) {
|
|
2567
2894
|
if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
|
|
2568
|
-
|
|
2895
|
+
logger7.warn("cursor race lost — block already covered", {
|
|
2569
2896
|
subgraph: subgraphName,
|
|
2570
2897
|
blockHeight,
|
|
2571
2898
|
error: err.message
|
|
@@ -2591,7 +2918,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2591
2918
|
const { rows } = await sql3.raw(`SELECT n_live_tup AS count FROM pg_stat_user_tables WHERE schemaname = '${schemaName}' AND relname = '${table}'`).execute(route.dataDb);
|
|
2592
2919
|
const count = Number(rows[0]?.count ?? 0);
|
|
2593
2920
|
if (count >= 1e7) {
|
|
2594
|
-
|
|
2921
|
+
logger7.warn("Subgraph table exceeds 10M rows (estimate)", {
|
|
2595
2922
|
subgraph: subgraphName,
|
|
2596
2923
|
table,
|
|
2597
2924
|
count
|
|
@@ -2599,7 +2926,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2599
2926
|
}
|
|
2600
2927
|
}
|
|
2601
2928
|
} catch (err) {
|
|
2602
|
-
|
|
2929
|
+
logger7.debug("Row count sample failed", {
|
|
2603
2930
|
subgraph: subgraphName,
|
|
2604
2931
|
error: err instanceof Error ? err.message : String(err)
|
|
2605
2932
|
});
|
|
@@ -2677,7 +3004,7 @@ class StatsAccumulator {
|
|
|
2677
3004
|
// src/runtime/catchup.ts
|
|
2678
3005
|
import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
|
|
2679
3006
|
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
2680
|
-
import { logger as
|
|
3007
|
+
import { logger as logger8 } from "@secondlayer/shared/logger";
|
|
2681
3008
|
|
|
2682
3009
|
// src/runtime/catchup-leader.ts
|
|
2683
3010
|
import {
|
|
@@ -2811,7 +3138,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2811
3138
|
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
2812
3139
|
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
2813
3140
|
const totalBlocks = chainTip - lastProcessedBlock;
|
|
2814
|
-
|
|
3141
|
+
logger8.info("Subgraph catch-up starting", {
|
|
2815
3142
|
subgraph: subgraphName,
|
|
2816
3143
|
from: startBlock,
|
|
2817
3144
|
to: chainTip,
|
|
@@ -2827,7 +3154,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2827
3154
|
let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
|
|
2828
3155
|
while (currentHeight <= chainTip) {
|
|
2829
3156
|
if (!isCatchUpLeader()) {
|
|
2830
|
-
|
|
3157
|
+
logger8.info("Lost catch-up leadership, stopping catch-up", {
|
|
2831
3158
|
subgraph: subgraphName,
|
|
2832
3159
|
processed
|
|
2833
3160
|
});
|
|
@@ -2835,7 +3162,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2835
3162
|
}
|
|
2836
3163
|
const currentRow = await getSubgraph(targetDb, subgraphName);
|
|
2837
3164
|
if (!currentRow || currentRow.status !== "active") {
|
|
2838
|
-
|
|
3165
|
+
logger8.info("Subgraph status changed, stopping catch-up", {
|
|
2839
3166
|
subgraph: subgraphName,
|
|
2840
3167
|
status: currentRow?.status ?? "deleted"
|
|
2841
3168
|
});
|
|
@@ -2864,7 +3191,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2864
3191
|
blockData = (await source.loadBlockRange(height, height)).get(height);
|
|
2865
3192
|
}
|
|
2866
3193
|
if (!blockData) {
|
|
2867
|
-
|
|
3194
|
+
logger8.warn("Block missing during catch-up, deferring to next tick", {
|
|
2868
3195
|
subgraph: subgraphName,
|
|
2869
3196
|
blockHeight: height
|
|
2870
3197
|
});
|
|
@@ -2884,12 +3211,12 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2884
3211
|
});
|
|
2885
3212
|
} catch (err) {
|
|
2886
3213
|
if (err instanceof ReorgEpochChangedError) {
|
|
2887
|
-
|
|
3214
|
+
logger8.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
|
|
2888
3215
|
stopCatchup = true;
|
|
2889
3216
|
break;
|
|
2890
3217
|
}
|
|
2891
3218
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2892
|
-
|
|
3219
|
+
logger8.error("Block processing failed persistently during catch-up", {
|
|
2893
3220
|
subgraph: subgraphName,
|
|
2894
3221
|
blockHeight: height,
|
|
2895
3222
|
error: errorMsg
|
|
@@ -2908,7 +3235,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2908
3235
|
}
|
|
2909
3236
|
}
|
|
2910
3237
|
if (processed % LOG_INTERVAL === 0) {
|
|
2911
|
-
|
|
3238
|
+
logger8.info("Subgraph catch-up progress", {
|
|
2912
3239
|
subgraph: subgraphName,
|
|
2913
3240
|
processed,
|
|
2914
3241
|
total: totalBlocks,
|
|
@@ -2924,7 +3251,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2924
3251
|
currentHeight = batchEnd + 1;
|
|
2925
3252
|
}
|
|
2926
3253
|
await stats.flush(targetDb);
|
|
2927
|
-
|
|
3254
|
+
logger8.info("Subgraph catch-up complete", {
|
|
2928
3255
|
subgraph: subgraphName,
|
|
2929
3256
|
processed
|
|
2930
3257
|
});
|
|
@@ -2940,5 +3267,5 @@ export {
|
|
|
2940
3267
|
bumpReorgEpoch
|
|
2941
3268
|
};
|
|
2942
3269
|
|
|
2943
|
-
//# debugId=
|
|
3270
|
+
//# debugId=E2B1B807C9857E3764756E2164756E21
|
|
2944
3271
|
//# sourceMappingURL=catchup.js.map
|