@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
package/dist/src/index.js
CHANGED
|
@@ -1842,6 +1842,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map, f
|
|
|
1842
1842
|
}
|
|
1843
1843
|
|
|
1844
1844
|
// src/runtime/block-processor.ts
|
|
1845
|
+
import { secretsKeyAvailable } from "@secondlayer/shared/crypto/secrets";
|
|
1845
1846
|
import { getTargetDb } from "@secondlayer/shared/db";
|
|
1846
1847
|
import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
|
|
1847
1848
|
import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
|
|
@@ -1854,7 +1855,7 @@ import {
|
|
|
1854
1855
|
rewindLiveProgress,
|
|
1855
1856
|
updateSubgraphStatus
|
|
1856
1857
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
1857
|
-
import { logger as
|
|
1858
|
+
import { logger as logger7 } from "@secondlayer/shared/logger";
|
|
1858
1859
|
import { sql as sql3 } from "kysely";
|
|
1859
1860
|
|
|
1860
1861
|
// src/runtime/block-source.ts
|
|
@@ -2295,6 +2296,315 @@ async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block
|
|
|
2295
2296
|
return rows.length;
|
|
2296
2297
|
}
|
|
2297
2298
|
|
|
2299
|
+
// src/runtime/sandbox/flag.ts
|
|
2300
|
+
function sandboxWorkersGloballyEnabled() {
|
|
2301
|
+
return process.env.SUBGRAPH_SANDBOX_WORKERS === "1";
|
|
2302
|
+
}
|
|
2303
|
+
function sandboxEnabled(subgraph) {
|
|
2304
|
+
return sandboxWorkersGloballyEnabled() && subgraph.sandbox_workers === true;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
// src/runtime/sandbox/host.ts
|
|
2308
|
+
import { tmpdir } from "node:os";
|
|
2309
|
+
import { fileURLToPath } from "node:url";
|
|
2310
|
+
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
2311
|
+
|
|
2312
|
+
// src/runtime/sandbox/bundle.ts
|
|
2313
|
+
import esbuild from "esbuild";
|
|
2314
|
+
var ALLOWED_BARE_SPECIFIERS = new Set;
|
|
2315
|
+
var SUBGRAPHS_PACKAGE_SPECIFIER = "@secondlayer/subgraphs";
|
|
2316
|
+
var SUBGRAPHS_STUB_SOURCE = `export function defineSubgraph(def) { return def; }
|
|
2317
|
+
`;
|
|
2318
|
+
function resolverLockdownPlugin() {
|
|
2319
|
+
return {
|
|
2320
|
+
name: "f071-sandbox-resolver-lockdown",
|
|
2321
|
+
setup(build) {
|
|
2322
|
+
build.onResolve({ filter: /^@secondlayer\/subgraphs$/ }, (args) => ({
|
|
2323
|
+
path: args.path,
|
|
2324
|
+
namespace: "f071-sandbox-subgraphs-stub"
|
|
2325
|
+
}));
|
|
2326
|
+
build.onLoad({ filter: /.*/, namespace: "f071-sandbox-subgraphs-stub" }, () => ({ contents: SUBGRAPHS_STUB_SOURCE, loader: "js" }));
|
|
2327
|
+
build.onResolve({ filter: /^(node:|bun:)/ }, (args) => ({
|
|
2328
|
+
path: args.path,
|
|
2329
|
+
namespace: "f071-sandbox-blocked"
|
|
2330
|
+
}));
|
|
2331
|
+
build.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
2332
|
+
if (args.path === SUBGRAPHS_PACKAGE_SPECIFIER)
|
|
2333
|
+
return;
|
|
2334
|
+
if (ALLOWED_BARE_SPECIFIERS.has(args.path))
|
|
2335
|
+
return;
|
|
2336
|
+
return { path: args.path, namespace: "f071-sandbox-blocked" };
|
|
2337
|
+
});
|
|
2338
|
+
build.onLoad({ filter: /.*/, namespace: "f071-sandbox-blocked" }, (args) => ({
|
|
2339
|
+
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`)});`,
|
|
2340
|
+
loader: "js"
|
|
2341
|
+
}));
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
async function bundleHandlerCode(handlerCode) {
|
|
2346
|
+
const result = await esbuild.build({
|
|
2347
|
+
stdin: {
|
|
2348
|
+
contents: handlerCode,
|
|
2349
|
+
loader: "ts",
|
|
2350
|
+
resolveDir: process.cwd()
|
|
2351
|
+
},
|
|
2352
|
+
bundle: true,
|
|
2353
|
+
platform: "neutral",
|
|
2354
|
+
format: "esm",
|
|
2355
|
+
write: false,
|
|
2356
|
+
logLevel: "silent",
|
|
2357
|
+
plugins: [resolverLockdownPlugin()]
|
|
2358
|
+
});
|
|
2359
|
+
const out = result.outputFiles?.[0];
|
|
2360
|
+
if (!out)
|
|
2361
|
+
throw new Error("esbuild produced no output for handler_code bundle");
|
|
2362
|
+
return new TextDecoder().decode(out.contents);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// src/runtime/sandbox/host.ts
|
|
2366
|
+
var BLOCK_TIMEOUT_MS = Number.parseInt(process.env.SUBGRAPH_SANDBOX_BLOCK_TIMEOUT_MS ?? "60000");
|
|
2367
|
+
var SUBPROCESS_ENTRY_PATH = fileURLToPath(new URL("./subprocess-entry.ts", import.meta.url));
|
|
2368
|
+
function spawnSandboxProcess() {
|
|
2369
|
+
let onMessage = null;
|
|
2370
|
+
const proc = Bun.spawn({
|
|
2371
|
+
cmd: [process.execPath, "--no-env-file", SUBPROCESS_ENTRY_PATH],
|
|
2372
|
+
env: { PATH: process.env.PATH ?? "" },
|
|
2373
|
+
cwd: tmpdir(),
|
|
2374
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
2375
|
+
serialization: "advanced",
|
|
2376
|
+
ipc(message) {
|
|
2377
|
+
onMessage?.(message);
|
|
2378
|
+
}
|
|
2379
|
+
});
|
|
2380
|
+
(async () => {
|
|
2381
|
+
const reader = proc.stderr.getReader();
|
|
2382
|
+
const decoder = new TextDecoder;
|
|
2383
|
+
try {
|
|
2384
|
+
while (true) {
|
|
2385
|
+
const { done, value } = await reader.read();
|
|
2386
|
+
if (done)
|
|
2387
|
+
break;
|
|
2388
|
+
const text = decoder.decode(value, { stream: true });
|
|
2389
|
+
if (text.trim()) {
|
|
2390
|
+
logger6.warn("Sandbox subprocess stderr", { stderr: text.trim() });
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
} catch {}
|
|
2394
|
+
})();
|
|
2395
|
+
return {
|
|
2396
|
+
send: (msg) => proc.send(msg),
|
|
2397
|
+
kill: () => proc.kill(),
|
|
2398
|
+
exited: proc.exited,
|
|
2399
|
+
setOnMessage: (handler) => {
|
|
2400
|
+
onMessage = handler;
|
|
2401
|
+
}
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
var pool = new Map;
|
|
2405
|
+
|
|
2406
|
+
class SandboxWorkerCrash extends Error {
|
|
2407
|
+
constructor(subgraphName, cause) {
|
|
2408
|
+
super(`sandbox subprocess for "${subgraphName}" failed: ${cause}`);
|
|
2409
|
+
this.name = "SandboxWorkerCrash";
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
async function initSandboxProcess(proc, subgraphName, version, handlerCode) {
|
|
2413
|
+
const bundledCode = await bundleHandlerCode(handlerCode);
|
|
2414
|
+
let settled = false;
|
|
2415
|
+
const ready = new Promise((resolve, reject) => {
|
|
2416
|
+
proc.setOnMessage((msg) => {
|
|
2417
|
+
if (msg.type === "ready" && msg.version === version) {
|
|
2418
|
+
settled = true;
|
|
2419
|
+
proc.setOnMessage(null);
|
|
2420
|
+
resolve();
|
|
2421
|
+
} else if (msg.type === "error") {
|
|
2422
|
+
settled = true;
|
|
2423
|
+
proc.setOnMessage(null);
|
|
2424
|
+
reject(new SandboxWorkerCrash(subgraphName, msg.message));
|
|
2425
|
+
}
|
|
2426
|
+
});
|
|
2427
|
+
const init = { type: "init", bundledCode, version };
|
|
2428
|
+
proc.send(init);
|
|
2429
|
+
});
|
|
2430
|
+
const crashed = proc.exited.then((code) => {
|
|
2431
|
+
if (settled)
|
|
2432
|
+
return;
|
|
2433
|
+
throw new SandboxWorkerCrash(subgraphName, `subprocess exited (code ${code}) during init`);
|
|
2434
|
+
});
|
|
2435
|
+
await Promise.race([ready, crashed]);
|
|
2436
|
+
}
|
|
2437
|
+
async function ensureSandboxProcess(subgraphName, version, handlerCode) {
|
|
2438
|
+
if (handlerCode == null) {
|
|
2439
|
+
throw new SandboxWorkerCrash(subgraphName, "subgraph has no handler_code; cannot bundle for the sandbox path");
|
|
2440
|
+
}
|
|
2441
|
+
const existing = pool.get(subgraphName);
|
|
2442
|
+
if (existing && existing.version === version)
|
|
2443
|
+
return existing;
|
|
2444
|
+
if (existing) {
|
|
2445
|
+
try {
|
|
2446
|
+
await initSandboxProcess(existing.proc, subgraphName, version, handlerCode);
|
|
2447
|
+
existing.version = version;
|
|
2448
|
+
logger6.info("Sandbox subprocess handler reloaded", {
|
|
2449
|
+
subgraph: subgraphName,
|
|
2450
|
+
version
|
|
2451
|
+
});
|
|
2452
|
+
return existing;
|
|
2453
|
+
} catch (err) {
|
|
2454
|
+
existing.proc.kill();
|
|
2455
|
+
pool.delete(subgraphName);
|
|
2456
|
+
logger6.warn("Sandbox subprocess re-init failed; respawning", {
|
|
2457
|
+
subgraph: subgraphName,
|
|
2458
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
const proc = spawnSandboxProcess();
|
|
2463
|
+
try {
|
|
2464
|
+
await initSandboxProcess(proc, subgraphName, version, handlerCode);
|
|
2465
|
+
} catch (err) {
|
|
2466
|
+
proc.kill();
|
|
2467
|
+
throw err;
|
|
2468
|
+
}
|
|
2469
|
+
const entry = { proc, version, chain: Promise.resolve() };
|
|
2470
|
+
pool.set(subgraphName, entry);
|
|
2471
|
+
return entry;
|
|
2472
|
+
}
|
|
2473
|
+
function evictSandboxWorker(subgraphName) {
|
|
2474
|
+
const entry = pool.get(subgraphName);
|
|
2475
|
+
if (!entry)
|
|
2476
|
+
return;
|
|
2477
|
+
entry.proc.kill();
|
|
2478
|
+
pool.delete(subgraphName);
|
|
2479
|
+
}
|
|
2480
|
+
async function runHandlersSandboxed(params) {
|
|
2481
|
+
const { subgraphName, version, handlerCode, hostCtx, block, matched } = params;
|
|
2482
|
+
const entry = await ensureSandboxProcess(subgraphName, version, handlerCode);
|
|
2483
|
+
const run = entry.chain.then(() => runBlockThroughProcess(entry, subgraphName, hostCtx, block, matched));
|
|
2484
|
+
entry.chain = run.catch(() => {
|
|
2485
|
+
return;
|
|
2486
|
+
});
|
|
2487
|
+
return run;
|
|
2488
|
+
}
|
|
2489
|
+
async function runBlockThroughProcess(entry, subgraphName, hostCtx, block, matched) {
|
|
2490
|
+
const { proc } = entry;
|
|
2491
|
+
let timeoutHandle;
|
|
2492
|
+
let settled = false;
|
|
2493
|
+
try {
|
|
2494
|
+
const done = new Promise((resolve, reject) => {
|
|
2495
|
+
proc.setOnMessage((msg) => {
|
|
2496
|
+
if (msg.type === "readRequest") {
|
|
2497
|
+
answerRead(hostCtx, msg).then((reply) => {
|
|
2498
|
+
proc.send({ type: "readResponse", id: msg.id, reply });
|
|
2499
|
+
}).catch((err) => {
|
|
2500
|
+
settled = true;
|
|
2501
|
+
reject(new SandboxWorkerCrash(subgraphName, `host read failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
2502
|
+
});
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
if (msg.type === "blockDone") {
|
|
2506
|
+
settled = true;
|
|
2507
|
+
resolve({
|
|
2508
|
+
ops: msg.ops,
|
|
2509
|
+
result: { processed: msg.processed, errors: msg.errors }
|
|
2510
|
+
});
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
if (msg.type === "error") {
|
|
2514
|
+
settled = true;
|
|
2515
|
+
reject(new SandboxWorkerCrash(subgraphName, msg.message));
|
|
2516
|
+
}
|
|
2517
|
+
});
|
|
2518
|
+
proc.send({ type: "runBlock", block, matched });
|
|
2519
|
+
});
|
|
2520
|
+
const crashed = proc.exited.then((code) => {
|
|
2521
|
+
if (settled)
|
|
2522
|
+
return;
|
|
2523
|
+
throw new SandboxWorkerCrash(subgraphName, `subprocess exited unexpectedly (code ${code}) mid-block`);
|
|
2524
|
+
});
|
|
2525
|
+
const deadline = new Promise((_, reject) => {
|
|
2526
|
+
timeoutHandle = setTimeout(() => {
|
|
2527
|
+
settled = true;
|
|
2528
|
+
reject(new SandboxWorkerCrash(subgraphName, `block ${block.height} exceeded ${BLOCK_TIMEOUT_MS}ms deadline`));
|
|
2529
|
+
}, BLOCK_TIMEOUT_MS);
|
|
2530
|
+
});
|
|
2531
|
+
const { ops, result } = await Promise.race([done, crashed, deadline]);
|
|
2532
|
+
for (const op of ops) {
|
|
2533
|
+
hostCtx.setTx(op.tx);
|
|
2534
|
+
replayOp(hostCtx, op);
|
|
2535
|
+
}
|
|
2536
|
+
return result;
|
|
2537
|
+
} catch (err) {
|
|
2538
|
+
evictSandboxWorker(subgraphName);
|
|
2539
|
+
logger6.warn("Sandbox subprocess run failed; subprocess evicted", {
|
|
2540
|
+
subgraph: subgraphName,
|
|
2541
|
+
blockHeight: block.height,
|
|
2542
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2543
|
+
});
|
|
2544
|
+
throw err;
|
|
2545
|
+
} finally {
|
|
2546
|
+
if (timeoutHandle !== undefined)
|
|
2547
|
+
clearTimeout(timeoutHandle);
|
|
2548
|
+
proc.setOnMessage(null);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
function replayOp(hostCtx, op) {
|
|
2552
|
+
switch (op.method) {
|
|
2553
|
+
case "insert":
|
|
2554
|
+
hostCtx.insert(op.table, op.args[0]);
|
|
2555
|
+
return;
|
|
2556
|
+
case "upsert":
|
|
2557
|
+
hostCtx.upsert(op.table, op.args[0], op.args[1]);
|
|
2558
|
+
return;
|
|
2559
|
+
case "increment":
|
|
2560
|
+
hostCtx.increment(op.table, op.args[0], op.args[1]);
|
|
2561
|
+
return;
|
|
2562
|
+
case "update":
|
|
2563
|
+
hostCtx.update(op.table, op.args[0], op.args[1]);
|
|
2564
|
+
return;
|
|
2565
|
+
case "delete":
|
|
2566
|
+
hostCtx.delete(op.table, op.args[0]);
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
async function answerRead(hostCtx, msg) {
|
|
2571
|
+
const { method, table, where, column } = msg;
|
|
2572
|
+
switch (method) {
|
|
2573
|
+
case "findOne":
|
|
2574
|
+
return { kind: "row", row: await hostCtx.findOne(table, where) };
|
|
2575
|
+
case "findMany":
|
|
2576
|
+
return { kind: "rows", rows: await hostCtx.findMany(table, where) };
|
|
2577
|
+
case "count":
|
|
2578
|
+
return { kind: "count", count: await hostCtx.count(table, where) };
|
|
2579
|
+
case "countDistinct": {
|
|
2580
|
+
if (!column)
|
|
2581
|
+
throw new Error("countDistinct read missing column");
|
|
2582
|
+
return {
|
|
2583
|
+
kind: "count",
|
|
2584
|
+
count: await hostCtx.countDistinct(table, column, where)
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
case "sum": {
|
|
2588
|
+
if (!column)
|
|
2589
|
+
throw new Error("sum read missing column");
|
|
2590
|
+
const v = await hostCtx.sum(table, column, where);
|
|
2591
|
+
return { kind: "amount", amount: v.toString() };
|
|
2592
|
+
}
|
|
2593
|
+
case "min": {
|
|
2594
|
+
if (!column)
|
|
2595
|
+
throw new Error("min read missing column");
|
|
2596
|
+
const v = await hostCtx.min(table, column, where);
|
|
2597
|
+
return { kind: "amount", amount: v == null ? null : v.toString() };
|
|
2598
|
+
}
|
|
2599
|
+
case "max": {
|
|
2600
|
+
if (!column)
|
|
2601
|
+
throw new Error("max read missing column");
|
|
2602
|
+
const v = await hostCtx.max(table, column, where);
|
|
2603
|
+
return { kind: "amount", amount: v == null ? null : v.toString() };
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2298
2608
|
// src/runtime/subscription-state.ts
|
|
2299
2609
|
import { listSubscriptions } from "@secondlayer/shared/db/queries/subscriptions";
|
|
2300
2610
|
import { sql as sql2 } from "kysely";
|
|
@@ -2470,22 +2780,36 @@ async function refreshMatcher(db) {
|
|
|
2470
2780
|
|
|
2471
2781
|
// src/runtime/block-processor.ts
|
|
2472
2782
|
var routeCache = new Map;
|
|
2783
|
+
|
|
2784
|
+
class ByoKeyUnavailableError extends Error {
|
|
2785
|
+
constructor(subgraphName) {
|
|
2786
|
+
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.`);
|
|
2787
|
+
this.name = "ByoKeyUnavailableError";
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2473
2790
|
async function resolveRoute(subgraphName, targetDb) {
|
|
2474
2791
|
const cached = routeCache.get(subgraphName);
|
|
2475
2792
|
if (cached)
|
|
2476
2793
|
return cached;
|
|
2477
2794
|
const row = await targetDb.selectFrom("subgraphs").selectAll().where("name", "=", subgraphName).executeTakeFirst();
|
|
2478
2795
|
const byo = row ? isByoSubgraph(row) : false;
|
|
2796
|
+
if (row && byo && !secretsKeyAvailable()) {
|
|
2797
|
+
throw new ByoKeyUnavailableError(subgraphName);
|
|
2798
|
+
}
|
|
2479
2799
|
const route = {
|
|
2480
2800
|
schemaName: row?.schema_name ?? pgSchemaName(subgraphName),
|
|
2481
2801
|
dataDb: row && byo ? resolveSubgraphDb(row) : targetDb,
|
|
2482
|
-
byo
|
|
2802
|
+
byo,
|
|
2803
|
+
sandboxWorkers: row?.sandbox_workers === true,
|
|
2804
|
+
handlerCode: row?.handler_code ?? null,
|
|
2805
|
+
version: row?.version ?? ""
|
|
2483
2806
|
};
|
|
2484
2807
|
routeCache.set(subgraphName, route);
|
|
2485
2808
|
return route;
|
|
2486
2809
|
}
|
|
2487
2810
|
function invalidateSubgraphRoute(subgraphName) {
|
|
2488
2811
|
routeCache.delete(subgraphName);
|
|
2812
|
+
evictSandboxWorker(subgraphName);
|
|
2489
2813
|
}
|
|
2490
2814
|
async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, txs, evts) {
|
|
2491
2815
|
const factories = new Map;
|
|
@@ -2591,7 +2915,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
|
|
|
2591
2915
|
const delay = retryDelaysMs[attempt];
|
|
2592
2916
|
if (delay === undefined)
|
|
2593
2917
|
break;
|
|
2594
|
-
|
|
2918
|
+
logger7.warn("Block processing failed, retrying", {
|
|
2595
2919
|
subgraph: subgraphName,
|
|
2596
2920
|
blockHeight,
|
|
2597
2921
|
attempt: attempt + 1,
|
|
@@ -2623,7 +2947,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2623
2947
|
} else {
|
|
2624
2948
|
const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
|
|
2625
2949
|
if (!data) {
|
|
2626
|
-
|
|
2950
|
+
logger7.debug("Block not found or non-canonical for subgraph processing", {
|
|
2627
2951
|
subgraph: subgraphName,
|
|
2628
2952
|
blockHeight
|
|
2629
2953
|
});
|
|
@@ -2759,7 +3083,16 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2759
3083
|
}
|
|
2760
3084
|
const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
|
|
2761
3085
|
const handlerStart = performance.now();
|
|
2762
|
-
const runResult =
|
|
3086
|
+
const runResult = sandboxEnabled({
|
|
3087
|
+
sandbox_workers: route.sandboxWorkers
|
|
3088
|
+
}) ? await runHandlersSandboxed({
|
|
3089
|
+
subgraphName,
|
|
3090
|
+
version: route.version,
|
|
3091
|
+
handlerCode: route.handlerCode,
|
|
3092
|
+
hostCtx: ctx,
|
|
3093
|
+
block: blockMeta,
|
|
3094
|
+
matched
|
|
3095
|
+
}) : await runHandlers(subgraph, matched, ctx);
|
|
2763
3096
|
handlerMs = performance.now() - handlerStart;
|
|
2764
3097
|
result.processed = runResult.processed;
|
|
2765
3098
|
result.errors = runResult.errors;
|
|
@@ -2785,7 +3118,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2785
3118
|
});
|
|
2786
3119
|
} catch (err) {
|
|
2787
3120
|
if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
|
|
2788
|
-
|
|
3121
|
+
logger7.warn("cursor race lost — block already covered", {
|
|
2789
3122
|
subgraph: subgraphName,
|
|
2790
3123
|
blockHeight,
|
|
2791
3124
|
error: err.message
|
|
@@ -2811,7 +3144,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2811
3144
|
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);
|
|
2812
3145
|
const count = Number(rows[0]?.count ?? 0);
|
|
2813
3146
|
if (count >= 1e7) {
|
|
2814
|
-
|
|
3147
|
+
logger7.warn("Subgraph table exceeds 10M rows (estimate)", {
|
|
2815
3148
|
subgraph: subgraphName,
|
|
2816
3149
|
table,
|
|
2817
3150
|
count
|
|
@@ -2819,7 +3152,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2819
3152
|
}
|
|
2820
3153
|
}
|
|
2821
3154
|
} catch (err) {
|
|
2822
|
-
|
|
3155
|
+
logger7.debug("Row count sample failed", {
|
|
2823
3156
|
subgraph: subgraphName,
|
|
2824
3157
|
error: err instanceof Error ? err.message : String(err)
|
|
2825
3158
|
});
|
|
@@ -2909,11 +3242,11 @@ import {
|
|
|
2909
3242
|
recordSubgraphProcessed as recordSubgraphProcessed2,
|
|
2910
3243
|
updateSubgraphStatus as updateSubgraphStatus2
|
|
2911
3244
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
2912
|
-
import { logger as
|
|
3245
|
+
import { logger as logger9 } from "@secondlayer/shared/logger";
|
|
2913
3246
|
|
|
2914
3247
|
// src/runtime/reindex-notify.ts
|
|
2915
3248
|
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
2916
|
-
import { logger as
|
|
3249
|
+
import { logger as logger8 } from "@secondlayer/shared/logger";
|
|
2917
3250
|
async function notifyReindexComplete(db, subgraphName, stats) {
|
|
2918
3251
|
try {
|
|
2919
3252
|
const subgraph = await db.selectFrom("subgraphs").select(["account_id"]).where("name", "=", subgraphName).executeTakeFirst();
|
|
@@ -2924,7 +3257,7 @@ async function notifyReindexComplete(db, subgraphName, stats) {
|
|
|
2924
3257
|
return;
|
|
2925
3258
|
const resendKey = process.env.RESEND_API_KEY;
|
|
2926
3259
|
if (!resendKey) {
|
|
2927
|
-
|
|
3260
|
+
logger8.warn("RESEND_API_KEY unset — skipping reindex-complete email", {
|
|
2928
3261
|
subgraph: subgraphName
|
|
2929
3262
|
});
|
|
2930
3263
|
return;
|
|
@@ -2946,14 +3279,14 @@ async function notifyReindexComplete(db, subgraphName, stats) {
|
|
|
2946
3279
|
});
|
|
2947
3280
|
if (!res.ok) {
|
|
2948
3281
|
const text = await res.text().catch(() => "");
|
|
2949
|
-
|
|
3282
|
+
logger8.warn("reindex-complete email failed", {
|
|
2950
3283
|
subgraph: subgraphName,
|
|
2951
3284
|
status: res.status,
|
|
2952
3285
|
body: text.slice(0, 200)
|
|
2953
3286
|
});
|
|
2954
3287
|
}
|
|
2955
3288
|
} catch (err) {
|
|
2956
|
-
|
|
3289
|
+
logger8.warn("reindex-complete email threw", {
|
|
2957
3290
|
subgraph: subgraphName,
|
|
2958
3291
|
error: getErrorMessage2(err)
|
|
2959
3292
|
});
|
|
@@ -3065,7 +3398,7 @@ async function processBlockRange(def, opts) {
|
|
|
3065
3398
|
while (currentHeight <= toBlock) {
|
|
3066
3399
|
if (opts.signal?.aborted) {
|
|
3067
3400
|
aborted = true;
|
|
3068
|
-
|
|
3401
|
+
logger9.info("Block processing aborted", {
|
|
3069
3402
|
subgraph: subgraphName,
|
|
3070
3403
|
currentBlock: currentHeight,
|
|
3071
3404
|
reason: String(opts.signal.reason ?? "unknown")
|
|
@@ -3114,7 +3447,7 @@ async function processBlockRange(def, opts) {
|
|
|
3114
3447
|
});
|
|
3115
3448
|
} catch (err) {
|
|
3116
3449
|
const errorMsg = getErrorMessage3(err);
|
|
3117
|
-
|
|
3450
|
+
logger9.error("Block processing failed persistently", {
|
|
3118
3451
|
subgraph: subgraphName,
|
|
3119
3452
|
blockHeight: height,
|
|
3120
3453
|
error: errorMsg
|
|
@@ -3160,7 +3493,7 @@ async function processBlockRange(def, opts) {
|
|
|
3160
3493
|
lastProgressFlushAt = now;
|
|
3161
3494
|
}
|
|
3162
3495
|
if (blocksProcessed % LOG_INTERVAL === 0) {
|
|
3163
|
-
|
|
3496
|
+
logger9.info(`${status === "reindexing" ? "Reindex" : "Backfill"} progress`, {
|
|
3164
3497
|
subgraph: subgraphName,
|
|
3165
3498
|
processed: blocksProcessed,
|
|
3166
3499
|
total: totalBlocks,
|
|
@@ -3180,7 +3513,7 @@ async function processBlockRange(def, opts) {
|
|
|
3180
3513
|
if (batchFailedBlocks.length > 0 && opts.subgraphId) {
|
|
3181
3514
|
const gaps = coalesceFailedBlocks(batchFailedBlocks);
|
|
3182
3515
|
await recordGapBatch(targetDb, opts.subgraphId, subgraphName, gaps).catch((err) => {
|
|
3183
|
-
|
|
3516
|
+
logger9.warn("Failed to record subgraph gaps", {
|
|
3184
3517
|
subgraph: subgraphName,
|
|
3185
3518
|
error: err instanceof Error ? err.message : String(err)
|
|
3186
3519
|
});
|
|
@@ -3197,7 +3530,7 @@ async function processBlockRange(def, opts) {
|
|
|
3197
3530
|
} else {
|
|
3198
3531
|
await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
|
|
3199
3532
|
}
|
|
3200
|
-
|
|
3533
|
+
logger9.info("Sparse skip", {
|
|
3201
3534
|
subgraph: subgraphName,
|
|
3202
3535
|
from: batchEnd + 1,
|
|
3203
3536
|
to: jumpTo - 1,
|
|
@@ -3237,11 +3570,11 @@ async function reindexSubgraph(def, opts) {
|
|
|
3237
3570
|
const subgraphName = def.name;
|
|
3238
3571
|
const schemaName = opts?.schemaName ?? pgSchemaName(subgraphName);
|
|
3239
3572
|
await updateSubgraphStatus2(targetDb, subgraphName, "reindexing");
|
|
3240
|
-
|
|
3573
|
+
logger9.info("Reindex starting", { subgraph: subgraphName });
|
|
3241
3574
|
try {
|
|
3242
3575
|
const { fromBlock, toBlock } = await resolveBlockRange(source, def, opts);
|
|
3243
3576
|
if (fromBlock > toBlock) {
|
|
3244
|
-
|
|
3577
|
+
logger9.info("No blocks to reindex", {
|
|
3245
3578
|
subgraph: subgraphName,
|
|
3246
3579
|
fromBlock,
|
|
3247
3580
|
toBlock
|
|
@@ -3255,7 +3588,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3255
3588
|
await client.unsafe(stmt);
|
|
3256
3589
|
}
|
|
3257
3590
|
await updateSubgraphStatus2(targetDb, subgraphName, "reindexing", Math.max(0, fromBlock - 1));
|
|
3258
|
-
|
|
3591
|
+
logger9.info("Schema recreated for reindex", {
|
|
3259
3592
|
subgraph: subgraphName,
|
|
3260
3593
|
cursorResetTo: Math.max(0, fromBlock - 1)
|
|
3261
3594
|
});
|
|
@@ -3269,7 +3602,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3269
3602
|
last_error_at: null,
|
|
3270
3603
|
updated_at: new Date
|
|
3271
3604
|
}).where("name", "=", subgraphName).execute();
|
|
3272
|
-
|
|
3605
|
+
logger9.info("Reindexing blocks", {
|
|
3273
3606
|
subgraph: subgraphName,
|
|
3274
3607
|
fromBlock,
|
|
3275
3608
|
toBlock,
|
|
@@ -3291,9 +3624,9 @@ async function reindexSubgraph(def, opts) {
|
|
|
3291
3624
|
if (reason === "user-cancelled") {
|
|
3292
3625
|
await updateSubgraphStatus2(targetDb, subgraphName, "active");
|
|
3293
3626
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3294
|
-
|
|
3627
|
+
logger9.info("Reindex cancelled by user", { subgraph: subgraphName });
|
|
3295
3628
|
} else {
|
|
3296
|
-
|
|
3629
|
+
logger9.info("Reindex interrupted by shutdown, will resume", {
|
|
3297
3630
|
subgraph: subgraphName
|
|
3298
3631
|
});
|
|
3299
3632
|
}
|
|
@@ -3301,7 +3634,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3301
3634
|
}
|
|
3302
3635
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
3303
3636
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3304
|
-
|
|
3637
|
+
logger9.info("Reindex complete", {
|
|
3305
3638
|
subgraph: subgraphName,
|
|
3306
3639
|
blocks: result.blocksProcessed,
|
|
3307
3640
|
events: result.totalEventsProcessed,
|
|
@@ -3314,7 +3647,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3314
3647
|
});
|
|
3315
3648
|
return { processed: result.blocksProcessed };
|
|
3316
3649
|
} catch (err) {
|
|
3317
|
-
|
|
3650
|
+
logger9.error("Reindex failed", {
|
|
3318
3651
|
subgraph: subgraphName,
|
|
3319
3652
|
error: getErrorMessage3(err)
|
|
3320
3653
|
});
|
|
@@ -3336,7 +3669,7 @@ async function resumeReindex(def, opts) {
|
|
|
3336
3669
|
const fromBlock = resolveReindexResumeBlock(row);
|
|
3337
3670
|
const toBlock = Number(row.reindex_to_block);
|
|
3338
3671
|
if (fromBlock == null) {
|
|
3339
|
-
|
|
3672
|
+
logger9.info("No reindex metadata, starting fresh reindex", {
|
|
3340
3673
|
subgraph: subgraphName
|
|
3341
3674
|
});
|
|
3342
3675
|
return reindexSubgraph(def, {
|
|
@@ -3345,12 +3678,12 @@ async function resumeReindex(def, opts) {
|
|
|
3345
3678
|
});
|
|
3346
3679
|
}
|
|
3347
3680
|
if (fromBlock > toBlock) {
|
|
3348
|
-
|
|
3681
|
+
logger9.info("Resume: no remaining blocks", { subgraph: subgraphName });
|
|
3349
3682
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
3350
3683
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3351
3684
|
return { processed: 0 };
|
|
3352
3685
|
}
|
|
3353
|
-
|
|
3686
|
+
logger9.info("Resuming reindex", {
|
|
3354
3687
|
subgraph: subgraphName,
|
|
3355
3688
|
fromBlock,
|
|
3356
3689
|
toBlock,
|
|
@@ -3372,9 +3705,9 @@ async function resumeReindex(def, opts) {
|
|
|
3372
3705
|
if (reason === "user-cancelled") {
|
|
3373
3706
|
await updateSubgraphStatus2(targetDb, subgraphName, "active");
|
|
3374
3707
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3375
|
-
|
|
3708
|
+
logger9.info("Resume cancelled by user", { subgraph: subgraphName });
|
|
3376
3709
|
} else {
|
|
3377
|
-
|
|
3710
|
+
logger9.info("Resume interrupted by shutdown, will resume again", {
|
|
3378
3711
|
subgraph: subgraphName
|
|
3379
3712
|
});
|
|
3380
3713
|
}
|
|
@@ -3382,13 +3715,13 @@ async function resumeReindex(def, opts) {
|
|
|
3382
3715
|
}
|
|
3383
3716
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
3384
3717
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3385
|
-
|
|
3718
|
+
logger9.info("Resumed reindex complete", {
|
|
3386
3719
|
subgraph: subgraphName,
|
|
3387
3720
|
blocks: result.blocksProcessed
|
|
3388
3721
|
});
|
|
3389
3722
|
return { processed: result.blocksProcessed };
|
|
3390
3723
|
} catch (err) {
|
|
3391
|
-
|
|
3724
|
+
logger9.error("Resumed reindex failed", {
|
|
3392
3725
|
subgraph: subgraphName,
|
|
3393
3726
|
error: getErrorMessage3(err)
|
|
3394
3727
|
});
|
|
@@ -3399,7 +3732,7 @@ async function resumeReindex(def, opts) {
|
|
|
3399
3732
|
async function backfillSubgraph(def, opts) {
|
|
3400
3733
|
const targetDb = getTargetDb2();
|
|
3401
3734
|
const subgraphName = def.name;
|
|
3402
|
-
|
|
3735
|
+
logger9.info("Backfill starting", {
|
|
3403
3736
|
subgraph: subgraphName,
|
|
3404
3737
|
from: opts.fromBlock,
|
|
3405
3738
|
to: opts.toBlock
|
|
@@ -3417,17 +3750,17 @@ async function backfillSubgraph(def, opts) {
|
|
|
3417
3750
|
signal: opts.signal
|
|
3418
3751
|
});
|
|
3419
3752
|
if (result.aborted) {
|
|
3420
|
-
|
|
3753
|
+
logger9.info("Backfill aborted", { subgraph: subgraphName });
|
|
3421
3754
|
return { processed: result.blocksProcessed };
|
|
3422
3755
|
}
|
|
3423
3756
|
const resolved = await resolveGaps(targetDb, subgraphName, opts.fromBlock, opts.toBlock).catch(() => 0);
|
|
3424
3757
|
if (resolved > 0) {
|
|
3425
|
-
|
|
3758
|
+
logger9.info("Resolved subgraph gaps via backfill", {
|
|
3426
3759
|
subgraph: subgraphName,
|
|
3427
3760
|
resolved
|
|
3428
3761
|
});
|
|
3429
3762
|
}
|
|
3430
|
-
|
|
3763
|
+
logger9.info("Backfill complete", {
|
|
3431
3764
|
subgraph: subgraphName,
|
|
3432
3765
|
blocks: result.blocksProcessed,
|
|
3433
3766
|
events: result.totalEventsProcessed,
|
|
@@ -3435,7 +3768,7 @@ async function backfillSubgraph(def, opts) {
|
|
|
3435
3768
|
});
|
|
3436
3769
|
return { processed: result.blocksProcessed };
|
|
3437
3770
|
} catch (err) {
|
|
3438
|
-
|
|
3771
|
+
logger9.error("Backfill failed", {
|
|
3439
3772
|
subgraph: subgraphName,
|
|
3440
3773
|
error: getErrorMessage3(err)
|
|
3441
3774
|
});
|
|
@@ -4741,5 +5074,5 @@ export {
|
|
|
4741
5074
|
ByoBreakingChangeError
|
|
4742
5075
|
};
|
|
4743
5076
|
|
|
4744
|
-
//# debugId=
|
|
5077
|
+
//# debugId=AD9497F805C348C364756E2164756E21
|
|
4745
5078
|
//# sourceMappingURL=index.js.map
|