@secondlayer/subgraphs 3.24.1 → 3.24.3
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 +372 -39
- package/dist/src/index.js.map +7 -4
- package/dist/src/runtime/block-processor.d.ts +20 -2
- package/dist/src/runtime/block-processor.js +343 -9
- package/dist/src/runtime/block-processor.js.map +7 -4
- package/dist/src/runtime/catchup.js +351 -18
- package/dist/src/runtime/catchup.js.map +7 -4
- package/dist/src/runtime/processor.js +412 -79
- package/dist/src/runtime/processor.js.map +7 -4
- package/dist/src/runtime/reindex.js +372 -39
- package/dist/src/runtime/reindex.js.map +7 -4
- package/dist/src/runtime/reorg.js +358 -25
- package/dist/src/runtime/reorg.js.map +7 -4
- package/dist/src/service.js +415 -82
- package/dist/src/service.js.map +7 -4
- package/package.json +2 -2
|
@@ -1627,6 +1627,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map, f
|
|
|
1627
1627
|
}
|
|
1628
1628
|
|
|
1629
1629
|
// src/runtime/block-processor.ts
|
|
1630
|
+
import { secretsKeyAvailable } from "@secondlayer/shared/crypto/secrets";
|
|
1630
1631
|
import { getTargetDb } from "@secondlayer/shared/db";
|
|
1631
1632
|
import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
|
|
1632
1633
|
import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
|
|
@@ -1639,7 +1640,7 @@ import {
|
|
|
1639
1640
|
rewindLiveProgress,
|
|
1640
1641
|
updateSubgraphStatus
|
|
1641
1642
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
1642
|
-
import { logger as
|
|
1643
|
+
import { logger as logger7 } from "@secondlayer/shared/logger";
|
|
1643
1644
|
import { sql as sql3 } from "kysely";
|
|
1644
1645
|
|
|
1645
1646
|
// src/runtime/block-source.ts
|
|
@@ -2080,6 +2081,315 @@ async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block
|
|
|
2080
2081
|
return rows.length;
|
|
2081
2082
|
}
|
|
2082
2083
|
|
|
2084
|
+
// src/runtime/sandbox/flag.ts
|
|
2085
|
+
function sandboxWorkersGloballyEnabled() {
|
|
2086
|
+
return process.env.SUBGRAPH_SANDBOX_WORKERS === "1";
|
|
2087
|
+
}
|
|
2088
|
+
function sandboxEnabled(subgraph) {
|
|
2089
|
+
return sandboxWorkersGloballyEnabled() && subgraph.sandbox_workers === true;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// src/runtime/sandbox/host.ts
|
|
2093
|
+
import { tmpdir } from "node:os";
|
|
2094
|
+
import { fileURLToPath } from "node:url";
|
|
2095
|
+
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
2096
|
+
|
|
2097
|
+
// src/runtime/sandbox/bundle.ts
|
|
2098
|
+
import esbuild from "esbuild";
|
|
2099
|
+
var ALLOWED_BARE_SPECIFIERS = new Set;
|
|
2100
|
+
var SUBGRAPHS_PACKAGE_SPECIFIER = "@secondlayer/subgraphs";
|
|
2101
|
+
var SUBGRAPHS_STUB_SOURCE = `export function defineSubgraph(def) { return def; }
|
|
2102
|
+
`;
|
|
2103
|
+
function resolverLockdownPlugin() {
|
|
2104
|
+
return {
|
|
2105
|
+
name: "f071-sandbox-resolver-lockdown",
|
|
2106
|
+
setup(build) {
|
|
2107
|
+
build.onResolve({ filter: /^@secondlayer\/subgraphs$/ }, (args) => ({
|
|
2108
|
+
path: args.path,
|
|
2109
|
+
namespace: "f071-sandbox-subgraphs-stub"
|
|
2110
|
+
}));
|
|
2111
|
+
build.onLoad({ filter: /.*/, namespace: "f071-sandbox-subgraphs-stub" }, () => ({ contents: SUBGRAPHS_STUB_SOURCE, loader: "js" }));
|
|
2112
|
+
build.onResolve({ filter: /^(node:|bun:)/ }, (args) => ({
|
|
2113
|
+
path: args.path,
|
|
2114
|
+
namespace: "f071-sandbox-blocked"
|
|
2115
|
+
}));
|
|
2116
|
+
build.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
2117
|
+
if (args.path === SUBGRAPHS_PACKAGE_SPECIFIER)
|
|
2118
|
+
return;
|
|
2119
|
+
if (ALLOWED_BARE_SPECIFIERS.has(args.path))
|
|
2120
|
+
return;
|
|
2121
|
+
return { path: args.path, namespace: "f071-sandbox-blocked" };
|
|
2122
|
+
});
|
|
2123
|
+
build.onLoad({ filter: /.*/, namespace: "f071-sandbox-blocked" }, (args) => ({
|
|
2124
|
+
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`)});`,
|
|
2125
|
+
loader: "js"
|
|
2126
|
+
}));
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
async function bundleHandlerCode(handlerCode) {
|
|
2131
|
+
const result = await esbuild.build({
|
|
2132
|
+
stdin: {
|
|
2133
|
+
contents: handlerCode,
|
|
2134
|
+
loader: "ts",
|
|
2135
|
+
resolveDir: process.cwd()
|
|
2136
|
+
},
|
|
2137
|
+
bundle: true,
|
|
2138
|
+
platform: "neutral",
|
|
2139
|
+
format: "esm",
|
|
2140
|
+
write: false,
|
|
2141
|
+
logLevel: "silent",
|
|
2142
|
+
plugins: [resolverLockdownPlugin()]
|
|
2143
|
+
});
|
|
2144
|
+
const out = result.outputFiles?.[0];
|
|
2145
|
+
if (!out)
|
|
2146
|
+
throw new Error("esbuild produced no output for handler_code bundle");
|
|
2147
|
+
return new TextDecoder().decode(out.contents);
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// src/runtime/sandbox/host.ts
|
|
2151
|
+
var BLOCK_TIMEOUT_MS = Number.parseInt(process.env.SUBGRAPH_SANDBOX_BLOCK_TIMEOUT_MS ?? "60000");
|
|
2152
|
+
var SUBPROCESS_ENTRY_PATH = fileURLToPath(new URL("./subprocess-entry.ts", import.meta.url));
|
|
2153
|
+
function spawnSandboxProcess() {
|
|
2154
|
+
let onMessage = null;
|
|
2155
|
+
const proc = Bun.spawn({
|
|
2156
|
+
cmd: [process.execPath, "--no-env-file", SUBPROCESS_ENTRY_PATH],
|
|
2157
|
+
env: { PATH: process.env.PATH ?? "" },
|
|
2158
|
+
cwd: tmpdir(),
|
|
2159
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
2160
|
+
serialization: "advanced",
|
|
2161
|
+
ipc(message) {
|
|
2162
|
+
onMessage?.(message);
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
(async () => {
|
|
2166
|
+
const reader = proc.stderr.getReader();
|
|
2167
|
+
const decoder = new TextDecoder;
|
|
2168
|
+
try {
|
|
2169
|
+
while (true) {
|
|
2170
|
+
const { done, value } = await reader.read();
|
|
2171
|
+
if (done)
|
|
2172
|
+
break;
|
|
2173
|
+
const text = decoder.decode(value, { stream: true });
|
|
2174
|
+
if (text.trim()) {
|
|
2175
|
+
logger6.warn("Sandbox subprocess stderr", { stderr: text.trim() });
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
} catch {}
|
|
2179
|
+
})();
|
|
2180
|
+
return {
|
|
2181
|
+
send: (msg) => proc.send(msg),
|
|
2182
|
+
kill: () => proc.kill(),
|
|
2183
|
+
exited: proc.exited,
|
|
2184
|
+
setOnMessage: (handler) => {
|
|
2185
|
+
onMessage = handler;
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
var pool = new Map;
|
|
2190
|
+
|
|
2191
|
+
class SandboxWorkerCrash extends Error {
|
|
2192
|
+
constructor(subgraphName, cause) {
|
|
2193
|
+
super(`sandbox subprocess for "${subgraphName}" failed: ${cause}`);
|
|
2194
|
+
this.name = "SandboxWorkerCrash";
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
async function initSandboxProcess(proc, subgraphName, version, handlerCode) {
|
|
2198
|
+
const bundledCode = await bundleHandlerCode(handlerCode);
|
|
2199
|
+
let settled = false;
|
|
2200
|
+
const ready = new Promise((resolve, reject) => {
|
|
2201
|
+
proc.setOnMessage((msg) => {
|
|
2202
|
+
if (msg.type === "ready" && msg.version === version) {
|
|
2203
|
+
settled = true;
|
|
2204
|
+
proc.setOnMessage(null);
|
|
2205
|
+
resolve();
|
|
2206
|
+
} else if (msg.type === "error") {
|
|
2207
|
+
settled = true;
|
|
2208
|
+
proc.setOnMessage(null);
|
|
2209
|
+
reject(new SandboxWorkerCrash(subgraphName, msg.message));
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
const init = { type: "init", bundledCode, version };
|
|
2213
|
+
proc.send(init);
|
|
2214
|
+
});
|
|
2215
|
+
const crashed = proc.exited.then((code) => {
|
|
2216
|
+
if (settled)
|
|
2217
|
+
return;
|
|
2218
|
+
throw new SandboxWorkerCrash(subgraphName, `subprocess exited (code ${code}) during init`);
|
|
2219
|
+
});
|
|
2220
|
+
await Promise.race([ready, crashed]);
|
|
2221
|
+
}
|
|
2222
|
+
async function ensureSandboxProcess(subgraphName, version, handlerCode) {
|
|
2223
|
+
if (handlerCode == null) {
|
|
2224
|
+
throw new SandboxWorkerCrash(subgraphName, "subgraph has no handler_code; cannot bundle for the sandbox path");
|
|
2225
|
+
}
|
|
2226
|
+
const existing = pool.get(subgraphName);
|
|
2227
|
+
if (existing && existing.version === version)
|
|
2228
|
+
return existing;
|
|
2229
|
+
if (existing) {
|
|
2230
|
+
try {
|
|
2231
|
+
await initSandboxProcess(existing.proc, subgraphName, version, handlerCode);
|
|
2232
|
+
existing.version = version;
|
|
2233
|
+
logger6.info("Sandbox subprocess handler reloaded", {
|
|
2234
|
+
subgraph: subgraphName,
|
|
2235
|
+
version
|
|
2236
|
+
});
|
|
2237
|
+
return existing;
|
|
2238
|
+
} catch (err) {
|
|
2239
|
+
existing.proc.kill();
|
|
2240
|
+
pool.delete(subgraphName);
|
|
2241
|
+
logger6.warn("Sandbox subprocess re-init failed; respawning", {
|
|
2242
|
+
subgraph: subgraphName,
|
|
2243
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
const proc = spawnSandboxProcess();
|
|
2248
|
+
try {
|
|
2249
|
+
await initSandboxProcess(proc, subgraphName, version, handlerCode);
|
|
2250
|
+
} catch (err) {
|
|
2251
|
+
proc.kill();
|
|
2252
|
+
throw err;
|
|
2253
|
+
}
|
|
2254
|
+
const entry = { proc, version, chain: Promise.resolve() };
|
|
2255
|
+
pool.set(subgraphName, entry);
|
|
2256
|
+
return entry;
|
|
2257
|
+
}
|
|
2258
|
+
function evictSandboxWorker(subgraphName) {
|
|
2259
|
+
const entry = pool.get(subgraphName);
|
|
2260
|
+
if (!entry)
|
|
2261
|
+
return;
|
|
2262
|
+
entry.proc.kill();
|
|
2263
|
+
pool.delete(subgraphName);
|
|
2264
|
+
}
|
|
2265
|
+
async function runHandlersSandboxed(params) {
|
|
2266
|
+
const { subgraphName, version, handlerCode, hostCtx, block, matched } = params;
|
|
2267
|
+
const entry = await ensureSandboxProcess(subgraphName, version, handlerCode);
|
|
2268
|
+
const run = entry.chain.then(() => runBlockThroughProcess(entry, subgraphName, hostCtx, block, matched));
|
|
2269
|
+
entry.chain = run.catch(() => {
|
|
2270
|
+
return;
|
|
2271
|
+
});
|
|
2272
|
+
return run;
|
|
2273
|
+
}
|
|
2274
|
+
async function runBlockThroughProcess(entry, subgraphName, hostCtx, block, matched) {
|
|
2275
|
+
const { proc } = entry;
|
|
2276
|
+
let timeoutHandle;
|
|
2277
|
+
let settled = false;
|
|
2278
|
+
try {
|
|
2279
|
+
const done = new Promise((resolve, reject) => {
|
|
2280
|
+
proc.setOnMessage((msg) => {
|
|
2281
|
+
if (msg.type === "readRequest") {
|
|
2282
|
+
answerRead(hostCtx, msg).then((reply) => {
|
|
2283
|
+
proc.send({ type: "readResponse", id: msg.id, reply });
|
|
2284
|
+
}).catch((err) => {
|
|
2285
|
+
settled = true;
|
|
2286
|
+
reject(new SandboxWorkerCrash(subgraphName, `host read failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
2287
|
+
});
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
if (msg.type === "blockDone") {
|
|
2291
|
+
settled = true;
|
|
2292
|
+
resolve({
|
|
2293
|
+
ops: msg.ops,
|
|
2294
|
+
result: { processed: msg.processed, errors: msg.errors }
|
|
2295
|
+
});
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
if (msg.type === "error") {
|
|
2299
|
+
settled = true;
|
|
2300
|
+
reject(new SandboxWorkerCrash(subgraphName, msg.message));
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
proc.send({ type: "runBlock", block, matched });
|
|
2304
|
+
});
|
|
2305
|
+
const crashed = proc.exited.then((code) => {
|
|
2306
|
+
if (settled)
|
|
2307
|
+
return;
|
|
2308
|
+
throw new SandboxWorkerCrash(subgraphName, `subprocess exited unexpectedly (code ${code}) mid-block`);
|
|
2309
|
+
});
|
|
2310
|
+
const deadline = new Promise((_, reject) => {
|
|
2311
|
+
timeoutHandle = setTimeout(() => {
|
|
2312
|
+
settled = true;
|
|
2313
|
+
reject(new SandboxWorkerCrash(subgraphName, `block ${block.height} exceeded ${BLOCK_TIMEOUT_MS}ms deadline`));
|
|
2314
|
+
}, BLOCK_TIMEOUT_MS);
|
|
2315
|
+
});
|
|
2316
|
+
const { ops, result } = await Promise.race([done, crashed, deadline]);
|
|
2317
|
+
for (const op of ops) {
|
|
2318
|
+
hostCtx.setTx(op.tx);
|
|
2319
|
+
replayOp(hostCtx, op);
|
|
2320
|
+
}
|
|
2321
|
+
return result;
|
|
2322
|
+
} catch (err) {
|
|
2323
|
+
evictSandboxWorker(subgraphName);
|
|
2324
|
+
logger6.warn("Sandbox subprocess run failed; subprocess evicted", {
|
|
2325
|
+
subgraph: subgraphName,
|
|
2326
|
+
blockHeight: block.height,
|
|
2327
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2328
|
+
});
|
|
2329
|
+
throw err;
|
|
2330
|
+
} finally {
|
|
2331
|
+
if (timeoutHandle !== undefined)
|
|
2332
|
+
clearTimeout(timeoutHandle);
|
|
2333
|
+
proc.setOnMessage(null);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
function replayOp(hostCtx, op) {
|
|
2337
|
+
switch (op.method) {
|
|
2338
|
+
case "insert":
|
|
2339
|
+
hostCtx.insert(op.table, op.args[0]);
|
|
2340
|
+
return;
|
|
2341
|
+
case "upsert":
|
|
2342
|
+
hostCtx.upsert(op.table, op.args[0], op.args[1]);
|
|
2343
|
+
return;
|
|
2344
|
+
case "increment":
|
|
2345
|
+
hostCtx.increment(op.table, op.args[0], op.args[1]);
|
|
2346
|
+
return;
|
|
2347
|
+
case "update":
|
|
2348
|
+
hostCtx.update(op.table, op.args[0], op.args[1]);
|
|
2349
|
+
return;
|
|
2350
|
+
case "delete":
|
|
2351
|
+
hostCtx.delete(op.table, op.args[0]);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
async function answerRead(hostCtx, msg) {
|
|
2356
|
+
const { method, table, where, column } = msg;
|
|
2357
|
+
switch (method) {
|
|
2358
|
+
case "findOne":
|
|
2359
|
+
return { kind: "row", row: await hostCtx.findOne(table, where) };
|
|
2360
|
+
case "findMany":
|
|
2361
|
+
return { kind: "rows", rows: await hostCtx.findMany(table, where) };
|
|
2362
|
+
case "count":
|
|
2363
|
+
return { kind: "count", count: await hostCtx.count(table, where) };
|
|
2364
|
+
case "countDistinct": {
|
|
2365
|
+
if (!column)
|
|
2366
|
+
throw new Error("countDistinct read missing column");
|
|
2367
|
+
return {
|
|
2368
|
+
kind: "count",
|
|
2369
|
+
count: await hostCtx.countDistinct(table, column, where)
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
case "sum": {
|
|
2373
|
+
if (!column)
|
|
2374
|
+
throw new Error("sum read missing column");
|
|
2375
|
+
const v = await hostCtx.sum(table, column, where);
|
|
2376
|
+
return { kind: "amount", amount: v.toString() };
|
|
2377
|
+
}
|
|
2378
|
+
case "min": {
|
|
2379
|
+
if (!column)
|
|
2380
|
+
throw new Error("min read missing column");
|
|
2381
|
+
const v = await hostCtx.min(table, column, where);
|
|
2382
|
+
return { kind: "amount", amount: v == null ? null : v.toString() };
|
|
2383
|
+
}
|
|
2384
|
+
case "max": {
|
|
2385
|
+
if (!column)
|
|
2386
|
+
throw new Error("max read missing column");
|
|
2387
|
+
const v = await hostCtx.max(table, column, where);
|
|
2388
|
+
return { kind: "amount", amount: v == null ? null : v.toString() };
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2083
2393
|
// src/runtime/subscription-state.ts
|
|
2084
2394
|
import { listSubscriptions } from "@secondlayer/shared/db/queries/subscriptions";
|
|
2085
2395
|
import { sql as sql2 } from "kysely";
|
|
@@ -2255,22 +2565,36 @@ async function refreshMatcher(db) {
|
|
|
2255
2565
|
|
|
2256
2566
|
// src/runtime/block-processor.ts
|
|
2257
2567
|
var routeCache = new Map;
|
|
2568
|
+
|
|
2569
|
+
class ByoKeyUnavailableError extends Error {
|
|
2570
|
+
constructor(subgraphName) {
|
|
2571
|
+
super(`subgraph ${subgraphName} is BYO (database_url_enc set) but this process has no SECONDLAYER_SECRETS_KEY — the subgraph processor deliberately does not hold it (f072). Route BYO subgraphs to a process that does, or unset BYO.`);
|
|
2572
|
+
this.name = "ByoKeyUnavailableError";
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2258
2575
|
async function resolveRoute(subgraphName, targetDb) {
|
|
2259
2576
|
const cached = routeCache.get(subgraphName);
|
|
2260
2577
|
if (cached)
|
|
2261
2578
|
return cached;
|
|
2262
2579
|
const row = await targetDb.selectFrom("subgraphs").selectAll().where("name", "=", subgraphName).executeTakeFirst();
|
|
2263
2580
|
const byo = row ? isByoSubgraph(row) : false;
|
|
2581
|
+
if (row && byo && !secretsKeyAvailable()) {
|
|
2582
|
+
throw new ByoKeyUnavailableError(subgraphName);
|
|
2583
|
+
}
|
|
2264
2584
|
const route = {
|
|
2265
2585
|
schemaName: row?.schema_name ?? pgSchemaName(subgraphName),
|
|
2266
2586
|
dataDb: row && byo ? resolveSubgraphDb(row) : targetDb,
|
|
2267
|
-
byo
|
|
2587
|
+
byo,
|
|
2588
|
+
sandboxWorkers: row?.sandbox_workers === true,
|
|
2589
|
+
handlerCode: row?.handler_code ?? null,
|
|
2590
|
+
version: row?.version ?? ""
|
|
2268
2591
|
};
|
|
2269
2592
|
routeCache.set(subgraphName, route);
|
|
2270
2593
|
return route;
|
|
2271
2594
|
}
|
|
2272
2595
|
function invalidateSubgraphRoute(subgraphName) {
|
|
2273
2596
|
routeCache.delete(subgraphName);
|
|
2597
|
+
evictSandboxWorker(subgraphName);
|
|
2274
2598
|
}
|
|
2275
2599
|
async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, txs, evts) {
|
|
2276
2600
|
const factories = new Map;
|
|
@@ -2376,7 +2700,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
|
|
|
2376
2700
|
const delay = retryDelaysMs[attempt];
|
|
2377
2701
|
if (delay === undefined)
|
|
2378
2702
|
break;
|
|
2379
|
-
|
|
2703
|
+
logger7.warn("Block processing failed, retrying", {
|
|
2380
2704
|
subgraph: subgraphName,
|
|
2381
2705
|
blockHeight,
|
|
2382
2706
|
attempt: attempt + 1,
|
|
@@ -2408,7 +2732,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2408
2732
|
} else {
|
|
2409
2733
|
const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
|
|
2410
2734
|
if (!data) {
|
|
2411
|
-
|
|
2735
|
+
logger7.debug("Block not found or non-canonical for subgraph processing", {
|
|
2412
2736
|
subgraph: subgraphName,
|
|
2413
2737
|
blockHeight
|
|
2414
2738
|
});
|
|
@@ -2544,7 +2868,16 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2544
2868
|
}
|
|
2545
2869
|
const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
|
|
2546
2870
|
const handlerStart = performance.now();
|
|
2547
|
-
const runResult =
|
|
2871
|
+
const runResult = sandboxEnabled({
|
|
2872
|
+
sandbox_workers: route.sandboxWorkers
|
|
2873
|
+
}) ? await runHandlersSandboxed({
|
|
2874
|
+
subgraphName,
|
|
2875
|
+
version: route.version,
|
|
2876
|
+
handlerCode: route.handlerCode,
|
|
2877
|
+
hostCtx: ctx,
|
|
2878
|
+
block: blockMeta,
|
|
2879
|
+
matched
|
|
2880
|
+
}) : await runHandlers(subgraph, matched, ctx);
|
|
2548
2881
|
handlerMs = performance.now() - handlerStart;
|
|
2549
2882
|
result.processed = runResult.processed;
|
|
2550
2883
|
result.errors = runResult.errors;
|
|
@@ -2570,7 +2903,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2570
2903
|
});
|
|
2571
2904
|
} catch (err) {
|
|
2572
2905
|
if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
|
|
2573
|
-
|
|
2906
|
+
logger7.warn("cursor race lost — block already covered", {
|
|
2574
2907
|
subgraph: subgraphName,
|
|
2575
2908
|
blockHeight,
|
|
2576
2909
|
error: err.message
|
|
@@ -2596,7 +2929,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2596
2929
|
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);
|
|
2597
2930
|
const count = Number(rows[0]?.count ?? 0);
|
|
2598
2931
|
if (count >= 1e7) {
|
|
2599
|
-
|
|
2932
|
+
logger7.warn("Subgraph table exceeds 10M rows (estimate)", {
|
|
2600
2933
|
subgraph: subgraphName,
|
|
2601
2934
|
table,
|
|
2602
2935
|
count
|
|
@@ -2604,7 +2937,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2604
2937
|
}
|
|
2605
2938
|
}
|
|
2606
2939
|
} catch (err) {
|
|
2607
|
-
|
|
2940
|
+
logger7.debug("Row count sample failed", {
|
|
2608
2941
|
subgraph: subgraphName,
|
|
2609
2942
|
error: err instanceof Error ? err.message : String(err)
|
|
2610
2943
|
});
|
|
@@ -2682,7 +3015,7 @@ class StatsAccumulator {
|
|
|
2682
3015
|
// src/runtime/catchup.ts
|
|
2683
3016
|
import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
|
|
2684
3017
|
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
2685
|
-
import { logger as
|
|
3018
|
+
import { logger as logger8 } from "@secondlayer/shared/logger";
|
|
2686
3019
|
|
|
2687
3020
|
// src/runtime/catchup-leader.ts
|
|
2688
3021
|
import {
|
|
@@ -2816,7 +3149,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2816
3149
|
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
2817
3150
|
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
2818
3151
|
const totalBlocks = chainTip - lastProcessedBlock;
|
|
2819
|
-
|
|
3152
|
+
logger8.info("Subgraph catch-up starting", {
|
|
2820
3153
|
subgraph: subgraphName,
|
|
2821
3154
|
from: startBlock,
|
|
2822
3155
|
to: chainTip,
|
|
@@ -2832,7 +3165,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2832
3165
|
let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
|
|
2833
3166
|
while (currentHeight <= chainTip) {
|
|
2834
3167
|
if (!isCatchUpLeader()) {
|
|
2835
|
-
|
|
3168
|
+
logger8.info("Lost catch-up leadership, stopping catch-up", {
|
|
2836
3169
|
subgraph: subgraphName,
|
|
2837
3170
|
processed
|
|
2838
3171
|
});
|
|
@@ -2840,7 +3173,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2840
3173
|
}
|
|
2841
3174
|
const currentRow = await getSubgraph(targetDb, subgraphName);
|
|
2842
3175
|
if (!currentRow || currentRow.status !== "active") {
|
|
2843
|
-
|
|
3176
|
+
logger8.info("Subgraph status changed, stopping catch-up", {
|
|
2844
3177
|
subgraph: subgraphName,
|
|
2845
3178
|
status: currentRow?.status ?? "deleted"
|
|
2846
3179
|
});
|
|
@@ -2869,7 +3202,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2869
3202
|
blockData = (await source.loadBlockRange(height, height)).get(height);
|
|
2870
3203
|
}
|
|
2871
3204
|
if (!blockData) {
|
|
2872
|
-
|
|
3205
|
+
logger8.warn("Block missing during catch-up, deferring to next tick", {
|
|
2873
3206
|
subgraph: subgraphName,
|
|
2874
3207
|
blockHeight: height
|
|
2875
3208
|
});
|
|
@@ -2889,12 +3222,12 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2889
3222
|
});
|
|
2890
3223
|
} catch (err) {
|
|
2891
3224
|
if (err instanceof ReorgEpochChangedError) {
|
|
2892
|
-
|
|
3225
|
+
logger8.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
|
|
2893
3226
|
stopCatchup = true;
|
|
2894
3227
|
break;
|
|
2895
3228
|
}
|
|
2896
3229
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2897
|
-
|
|
3230
|
+
logger8.error("Block processing failed persistently during catch-up", {
|
|
2898
3231
|
subgraph: subgraphName,
|
|
2899
3232
|
blockHeight: height,
|
|
2900
3233
|
error: errorMsg
|
|
@@ -2913,7 +3246,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2913
3246
|
}
|
|
2914
3247
|
}
|
|
2915
3248
|
if (processed % LOG_INTERVAL === 0) {
|
|
2916
|
-
|
|
3249
|
+
logger8.info("Subgraph catch-up progress", {
|
|
2917
3250
|
subgraph: subgraphName,
|
|
2918
3251
|
processed,
|
|
2919
3252
|
total: totalBlocks,
|
|
@@ -2929,7 +3262,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2929
3262
|
currentHeight = batchEnd + 1;
|
|
2930
3263
|
}
|
|
2931
3264
|
await stats.flush(targetDb);
|
|
2932
|
-
|
|
3265
|
+
logger8.info("Subgraph catch-up complete", {
|
|
2933
3266
|
subgraph: subgraphName,
|
|
2934
3267
|
processed
|
|
2935
3268
|
});
|
|
@@ -2946,14 +3279,14 @@ import {
|
|
|
2946
3279
|
listSubgraphs,
|
|
2947
3280
|
resolveSubgraphRawClient
|
|
2948
3281
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
2949
|
-
import { logger as
|
|
3282
|
+
import { logger as logger9 } from "@secondlayer/shared/logger";
|
|
2950
3283
|
import { sql as sql4 } from "kysely";
|
|
2951
3284
|
async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
2952
3285
|
const targetDb = getTargetDb3();
|
|
2953
3286
|
const activeSubgraphs = (await listSubgraphs(targetDb)).filter((v) => v.status === "active");
|
|
2954
3287
|
if (activeSubgraphs.length === 0)
|
|
2955
3288
|
return;
|
|
2956
|
-
|
|
3289
|
+
logger9.info("Propagating reorg to subgraphs", {
|
|
2957
3290
|
blockHeight,
|
|
2958
3291
|
subgraphCount: activeSubgraphs.length
|
|
2959
3292
|
});
|
|
@@ -2970,7 +3303,7 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
|
2970
3303
|
});
|
|
2971
3304
|
});
|
|
2972
3305
|
} catch (err) {
|
|
2973
|
-
|
|
3306
|
+
logger9.error("Subgraph reorg handling failed", {
|
|
2974
3307
|
subgraph: sg.name,
|
|
2975
3308
|
blockHeight,
|
|
2976
3309
|
error: getErrorMessage2(err)
|
|
@@ -3008,7 +3341,7 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
|
|
|
3008
3341
|
await tx.unsafe(`DELETE FROM "${schemaName}"."_journal" WHERE block_height >= $1`, [blockHeight]);
|
|
3009
3342
|
});
|
|
3010
3343
|
} else {
|
|
3011
|
-
|
|
3344
|
+
logger9.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
|
|
3012
3345
|
for (const tableName of tableNames) {
|
|
3013
3346
|
await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
|
|
3014
3347
|
}
|
|
@@ -3051,7 +3384,7 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
|
|
|
3051
3384
|
}).as("row_pk"),
|
|
3052
3385
|
eb2.val("pending").as("status")
|
|
3053
3386
|
]).where("subgraph_name", "=", sg.name).where("table_name", "=", tableName).where("status", "=", "active")).onConflict((oc) => oc.columns(["subscription_id", "dedup_key"]).doNothing()).execute().catch((err) => {
|
|
3054
|
-
|
|
3387
|
+
logger9.error("Failed to emit revert event for subscriptions", {
|
|
3055
3388
|
event: "reorg_revert_emit_dropped",
|
|
3056
3389
|
subgraph: sg.name,
|
|
3057
3390
|
table: tableName,
|
|
@@ -3060,14 +3393,14 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
|
|
|
3060
3393
|
});
|
|
3061
3394
|
});
|
|
3062
3395
|
}
|
|
3063
|
-
|
|
3396
|
+
logger9.info("Subgraph reorg cleanup done", {
|
|
3064
3397
|
subgraph: sg.name,
|
|
3065
3398
|
blockHeight,
|
|
3066
3399
|
tablesAffected: Object.keys(revertedByTable).length
|
|
3067
3400
|
});
|
|
3068
3401
|
const def = await loadSubgraphDef(sg);
|
|
3069
3402
|
await processBlock(def, sg.name, blockHeight, { reorgRewind: true });
|
|
3070
|
-
|
|
3403
|
+
logger9.info("Subgraph reorg reprocessed", {
|
|
3071
3404
|
subgraph: sg.name,
|
|
3072
3405
|
blockHeight
|
|
3073
3406
|
});
|
|
@@ -3076,5 +3409,5 @@ export {
|
|
|
3076
3409
|
handleSubgraphReorg
|
|
3077
3410
|
};
|
|
3078
3411
|
|
|
3079
|
-
//# debugId=
|
|
3412
|
+
//# debugId=E41A1A11273A69DD64756E2164756E21
|
|
3080
3413
|
//# sourceMappingURL=reorg.js.map
|