@secondlayer/subgraphs 3.19.2 → 3.19.4
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/runtime/catchup.d.ts +8 -1
- package/dist/src/runtime/catchup.js +64 -4
- package/dist/src/runtime/catchup.js.map +3 -3
- package/dist/src/runtime/emitter.d.ts +66 -2
- package/dist/src/runtime/emitter.js +118 -38
- package/dist/src/runtime/emitter.js.map +3 -3
- package/dist/src/runtime/processor.js +144 -77
- package/dist/src/runtime/processor.js.map +4 -4
- package/dist/src/runtime/reorg.js +378 -80
- package/dist/src/runtime/reorg.js.map +6 -4
- package/dist/src/service.js +144 -77
- package/dist/src/service.js.map +4 -4
- package/package.json +2 -4
|
@@ -2238,20 +2238,309 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2238
2238
|
return result;
|
|
2239
2239
|
}
|
|
2240
2240
|
|
|
2241
|
+
// src/runtime/stats.ts
|
|
2242
|
+
var FLUSH_INTERVAL_BLOCKS = 100;
|
|
2243
|
+
var FLUSH_INTERVAL_MS = 60000;
|
|
2244
|
+
|
|
2245
|
+
class StatsAccumulator {
|
|
2246
|
+
subgraphName;
|
|
2247
|
+
isCatchup;
|
|
2248
|
+
current;
|
|
2249
|
+
lastFlush = Date.now();
|
|
2250
|
+
constructor(subgraphName, isCatchup) {
|
|
2251
|
+
this.subgraphName = subgraphName;
|
|
2252
|
+
this.isCatchup = isCatchup;
|
|
2253
|
+
this.current = this.newEntry();
|
|
2254
|
+
}
|
|
2255
|
+
record(timing, opsCount) {
|
|
2256
|
+
this.current.blocksProcessed++;
|
|
2257
|
+
this.current.totalTimeMs += timing.totalMs;
|
|
2258
|
+
this.current.handlerTimeMs += timing.handlerMs;
|
|
2259
|
+
this.current.flushTimeMs += timing.flushMs;
|
|
2260
|
+
this.current.maxBlockTimeMs = Math.max(this.current.maxBlockTimeMs, timing.totalMs);
|
|
2261
|
+
this.current.maxHandlerTimeMs = Math.max(this.current.maxHandlerTimeMs, timing.handlerMs);
|
|
2262
|
+
this.current.totalOps += opsCount;
|
|
2263
|
+
}
|
|
2264
|
+
shouldFlush() {
|
|
2265
|
+
return this.current.blocksProcessed >= FLUSH_INTERVAL_BLOCKS || Date.now() - this.lastFlush >= FLUSH_INTERVAL_MS;
|
|
2266
|
+
}
|
|
2267
|
+
async flush(db) {
|
|
2268
|
+
if (this.current.blocksProcessed === 0)
|
|
2269
|
+
return;
|
|
2270
|
+
const entry = this.current;
|
|
2271
|
+
this.current = this.newEntry();
|
|
2272
|
+
this.lastFlush = Date.now();
|
|
2273
|
+
const avgOpsPerBlock = entry.blocksProcessed > 0 ? entry.totalOps / entry.blocksProcessed : 0;
|
|
2274
|
+
await db.insertInto("subgraph_processing_stats").values({
|
|
2275
|
+
subgraph_name: entry.subgraphName,
|
|
2276
|
+
bucket_start: entry.bucketStart,
|
|
2277
|
+
bucket_end: new Date,
|
|
2278
|
+
blocks_processed: entry.blocksProcessed,
|
|
2279
|
+
total_time_ms: Math.round(entry.totalTimeMs),
|
|
2280
|
+
handler_time_ms: Math.round(entry.handlerTimeMs),
|
|
2281
|
+
flush_time_ms: Math.round(entry.flushTimeMs),
|
|
2282
|
+
max_block_time_ms: Math.round(entry.maxBlockTimeMs),
|
|
2283
|
+
max_handler_time_ms: Math.round(entry.maxHandlerTimeMs),
|
|
2284
|
+
avg_ops_per_block: Number.parseFloat(avgOpsPerBlock.toFixed(2)),
|
|
2285
|
+
is_catchup: entry.isCatchup
|
|
2286
|
+
}).execute();
|
|
2287
|
+
}
|
|
2288
|
+
newEntry() {
|
|
2289
|
+
return {
|
|
2290
|
+
subgraphName: this.subgraphName,
|
|
2291
|
+
bucketStart: new Date,
|
|
2292
|
+
blocksProcessed: 0,
|
|
2293
|
+
totalTimeMs: 0,
|
|
2294
|
+
handlerTimeMs: 0,
|
|
2295
|
+
flushTimeMs: 0,
|
|
2296
|
+
maxBlockTimeMs: 0,
|
|
2297
|
+
maxHandlerTimeMs: 0,
|
|
2298
|
+
totalOps: 0,
|
|
2299
|
+
isCatchup: this.isCatchup
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
// src/runtime/catchup.ts
|
|
2305
|
+
import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
|
|
2306
|
+
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
2307
|
+
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
2308
|
+
var LOG_INTERVAL = 1000;
|
|
2309
|
+
var STANDARD_CATCHUP_BATCH_CONFIG = {
|
|
2310
|
+
defaultBatchSize: 500,
|
|
2311
|
+
minBatchSize: 100,
|
|
2312
|
+
maxBatchSize: 1000,
|
|
2313
|
+
prefetch: true
|
|
2314
|
+
};
|
|
2315
|
+
var catchingUp = new Set;
|
|
2316
|
+
var subgraphBlockLockHeld = new Set;
|
|
2317
|
+
var subgraphBlockLockWaiters = new Map;
|
|
2318
|
+
var reorgEpoch = new Map;
|
|
2319
|
+
async function acquireSubgraphBlockLock(name) {
|
|
2320
|
+
if (!subgraphBlockLockHeld.has(name)) {
|
|
2321
|
+
subgraphBlockLockHeld.add(name);
|
|
2322
|
+
return () => releaseSubgraphBlockLock(name);
|
|
2323
|
+
}
|
|
2324
|
+
return new Promise((resolve) => {
|
|
2325
|
+
const waiters = subgraphBlockLockWaiters.get(name) ?? [];
|
|
2326
|
+
waiters.push(() => resolve(() => releaseSubgraphBlockLock(name)));
|
|
2327
|
+
subgraphBlockLockWaiters.set(name, waiters);
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
function releaseSubgraphBlockLock(name) {
|
|
2331
|
+
const waiters = subgraphBlockLockWaiters.get(name);
|
|
2332
|
+
if (waiters && waiters.length > 0) {
|
|
2333
|
+
const next = waiters.shift();
|
|
2334
|
+
if (waiters.length === 0)
|
|
2335
|
+
subgraphBlockLockWaiters.delete(name);
|
|
2336
|
+
next();
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
subgraphBlockLockHeld.delete(name);
|
|
2340
|
+
}
|
|
2341
|
+
async function withSubgraphBlockLock(name, fn) {
|
|
2342
|
+
const release = await acquireSubgraphBlockLock(name);
|
|
2343
|
+
try {
|
|
2344
|
+
return await fn();
|
|
2345
|
+
} finally {
|
|
2346
|
+
release();
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
function bumpReorgEpoch(name) {
|
|
2350
|
+
reorgEpoch.set(name, (reorgEpoch.get(name) ?? 0) + 1);
|
|
2351
|
+
}
|
|
2352
|
+
function getReorgEpoch(name) {
|
|
2353
|
+
return reorgEpoch.get(name) ?? 0;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
class ReorgEpochChangedError extends Error {
|
|
2357
|
+
constructor(name) {
|
|
2358
|
+
super(`reorg epoch changed for ${name} mid catch-up`);
|
|
2359
|
+
this.name = "ReorgEpochChangedError";
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
function parsePositiveInt(value) {
|
|
2363
|
+
if (value == null || value.trim() === "")
|
|
2364
|
+
return;
|
|
2365
|
+
const parsed = Number.parseInt(value, 10);
|
|
2366
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
2367
|
+
}
|
|
2368
|
+
function parseBoolean(value) {
|
|
2369
|
+
if (value == null || value.trim() === "")
|
|
2370
|
+
return;
|
|
2371
|
+
const normalized = value.trim().toLowerCase();
|
|
2372
|
+
if (normalized === "true")
|
|
2373
|
+
return true;
|
|
2374
|
+
if (normalized === "false")
|
|
2375
|
+
return false;
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
function resolveCatchupBatchConfig(env = process.env) {
|
|
2379
|
+
const base = STANDARD_CATCHUP_BATCH_CONFIG;
|
|
2380
|
+
const minBatchSize = parsePositiveInt(env.SUBGRAPH_CATCHUP_MIN_BATCH_SIZE) ?? base.minBatchSize;
|
|
2381
|
+
const maxBatchSize = parsePositiveInt(env.SUBGRAPH_CATCHUP_MAX_BATCH_SIZE) ?? base.maxBatchSize;
|
|
2382
|
+
const defaultBatchSize = parsePositiveInt(env.SUBGRAPH_CATCHUP_BATCH_SIZE) ?? base.defaultBatchSize;
|
|
2383
|
+
return {
|
|
2384
|
+
minBatchSize,
|
|
2385
|
+
maxBatchSize,
|
|
2386
|
+
defaultBatchSize: Math.min(Math.max(defaultBatchSize, minBatchSize), maxBatchSize),
|
|
2387
|
+
prefetch: parseBoolean(env.SUBGRAPH_CATCHUP_PREFETCH) ?? base.prefetch
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
function adjustBatchSize(current, avgEvents, config) {
|
|
2391
|
+
if (avgEvents > 50)
|
|
2392
|
+
return Math.max(Math.round(current * 0.5), config.minBatchSize);
|
|
2393
|
+
if (avgEvents < 10)
|
|
2394
|
+
return Math.min(Math.round(current * 1.5), config.maxBatchSize);
|
|
2395
|
+
return current;
|
|
2396
|
+
}
|
|
2397
|
+
async function catchUpSubgraph(subgraph, subgraphName) {
|
|
2398
|
+
if (catchingUp.has(subgraphName))
|
|
2399
|
+
return 0;
|
|
2400
|
+
catchingUp.add(subgraphName);
|
|
2401
|
+
try {
|
|
2402
|
+
const source = resolveBlockSource(subgraph);
|
|
2403
|
+
const targetDb = getTargetDb2();
|
|
2404
|
+
const subgraphRow = await getSubgraph(targetDb, subgraphName);
|
|
2405
|
+
if (!subgraphRow)
|
|
2406
|
+
return 0;
|
|
2407
|
+
const lastProcessedBlock = Number(subgraphRow.last_processed_block);
|
|
2408
|
+
const chainTip = await source.getTip();
|
|
2409
|
+
if (chainTip <= 0 || lastProcessedBlock >= chainTip)
|
|
2410
|
+
return 0;
|
|
2411
|
+
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
2412
|
+
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
2413
|
+
const totalBlocks = chainTip - lastProcessedBlock;
|
|
2414
|
+
logger6.info("Subgraph catch-up starting", {
|
|
2415
|
+
subgraph: subgraphName,
|
|
2416
|
+
from: startBlock,
|
|
2417
|
+
to: chainTip,
|
|
2418
|
+
blocks: totalBlocks
|
|
2419
|
+
});
|
|
2420
|
+
const stats = new StatsAccumulator(subgraphName, true);
|
|
2421
|
+
let processed = 0;
|
|
2422
|
+
const batchConfig = resolveCatchupBatchConfig();
|
|
2423
|
+
let batchSize = batchConfig.defaultBatchSize;
|
|
2424
|
+
let currentHeight = startBlock;
|
|
2425
|
+
const walkEpoch = getReorgEpoch(subgraphName);
|
|
2426
|
+
let prefetchedBatchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
2427
|
+
let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
|
|
2428
|
+
while (currentHeight <= chainTip) {
|
|
2429
|
+
const currentRow = await getSubgraph(targetDb, subgraphName);
|
|
2430
|
+
if (!currentRow || currentRow.status !== "active") {
|
|
2431
|
+
logger6.info("Subgraph status changed, stopping catch-up", {
|
|
2432
|
+
subgraph: subgraphName,
|
|
2433
|
+
status: currentRow?.status ?? "deleted"
|
|
2434
|
+
});
|
|
2435
|
+
break;
|
|
2436
|
+
}
|
|
2437
|
+
let batchEnd;
|
|
2438
|
+
let batch;
|
|
2439
|
+
if (nextBatchPromise) {
|
|
2440
|
+
batch = await nextBatchPromise;
|
|
2441
|
+
batchEnd = prefetchedBatchEnd;
|
|
2442
|
+
const nextStart = batchEnd + 1;
|
|
2443
|
+
if (nextStart <= chainTip) {
|
|
2444
|
+
prefetchedBatchEnd = Math.min(nextStart + batchSize - 1, chainTip);
|
|
2445
|
+
nextBatchPromise = source.loadBlockRange(nextStart, prefetchedBatchEnd);
|
|
2446
|
+
} else {
|
|
2447
|
+
nextBatchPromise = undefined;
|
|
2448
|
+
}
|
|
2449
|
+
} else {
|
|
2450
|
+
batchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
2451
|
+
batch = await source.loadBlockRange(currentHeight, batchEnd);
|
|
2452
|
+
}
|
|
2453
|
+
let stopCatchup = false;
|
|
2454
|
+
for (let height = currentHeight;height <= batchEnd; height++) {
|
|
2455
|
+
let blockData = batch.get(height);
|
|
2456
|
+
if (!blockData) {
|
|
2457
|
+
blockData = (await source.loadBlockRange(height, height)).get(height);
|
|
2458
|
+
}
|
|
2459
|
+
if (!blockData) {
|
|
2460
|
+
logger6.warn("Block missing during catch-up, deferring to next tick", {
|
|
2461
|
+
subgraph: subgraphName,
|
|
2462
|
+
blockHeight: height
|
|
2463
|
+
});
|
|
2464
|
+
stopCatchup = true;
|
|
2465
|
+
break;
|
|
2466
|
+
}
|
|
2467
|
+
const preloaded = blockData;
|
|
2468
|
+
let result;
|
|
2469
|
+
try {
|
|
2470
|
+
result = await withSubgraphBlockLock(subgraphName, async () => {
|
|
2471
|
+
if (getReorgEpoch(subgraphName) !== walkEpoch) {
|
|
2472
|
+
throw new ReorgEpochChangedError(subgraphName);
|
|
2473
|
+
}
|
|
2474
|
+
return processBlockWithRetry(subgraph, subgraphName, height, {
|
|
2475
|
+
preloaded
|
|
2476
|
+
});
|
|
2477
|
+
});
|
|
2478
|
+
} catch (err) {
|
|
2479
|
+
if (err instanceof ReorgEpochChangedError) {
|
|
2480
|
+
logger6.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
|
|
2481
|
+
stopCatchup = true;
|
|
2482
|
+
break;
|
|
2483
|
+
}
|
|
2484
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2485
|
+
logger6.error("Block processing failed persistently during catch-up", {
|
|
2486
|
+
subgraph: subgraphName,
|
|
2487
|
+
blockHeight: height,
|
|
2488
|
+
error: errorMsg
|
|
2489
|
+
});
|
|
2490
|
+
const { updateSubgraphStatus: updateSubgraphStatus2, recordSubgraphProcessed: recordSubgraphProcessed2 } = await import("@secondlayer/shared/db/queries/subgraphs");
|
|
2491
|
+
await recordSubgraphProcessed2(targetDb, subgraphName, 0, 1, `catch-up halted at block ${height}: ${errorMsg}`).catch(() => {});
|
|
2492
|
+
await updateSubgraphStatus2(targetDb, subgraphName, "error").catch(() => {});
|
|
2493
|
+
stopCatchup = true;
|
|
2494
|
+
break;
|
|
2495
|
+
}
|
|
2496
|
+
processed++;
|
|
2497
|
+
if (result.timing) {
|
|
2498
|
+
stats.record(result.timing, result.processed);
|
|
2499
|
+
if (stats.shouldFlush()) {
|
|
2500
|
+
await stats.flush(targetDb);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
if (processed % LOG_INTERVAL === 0) {
|
|
2504
|
+
logger6.info("Subgraph catch-up progress", {
|
|
2505
|
+
subgraph: subgraphName,
|
|
2506
|
+
processed,
|
|
2507
|
+
total: totalBlocks,
|
|
2508
|
+
currentBlock: height,
|
|
2509
|
+
pct: Math.round(processed / totalBlocks * 100)
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
if (stopCatchup)
|
|
2514
|
+
break;
|
|
2515
|
+
const avg = avgEventsPerBlock(batch);
|
|
2516
|
+
batchSize = adjustBatchSize(batchSize, avg, batchConfig);
|
|
2517
|
+
currentHeight = batchEnd + 1;
|
|
2518
|
+
}
|
|
2519
|
+
await stats.flush(targetDb);
|
|
2520
|
+
logger6.info("Subgraph catch-up complete", {
|
|
2521
|
+
subgraph: subgraphName,
|
|
2522
|
+
processed
|
|
2523
|
+
});
|
|
2524
|
+
return processed;
|
|
2525
|
+
} finally {
|
|
2526
|
+
catchingUp.delete(subgraphName);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2241
2530
|
// src/runtime/reorg.ts
|
|
2242
2531
|
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
2243
|
-
import { getTargetDb as
|
|
2532
|
+
import { getTargetDb as getTargetDb3 } from "@secondlayer/shared/db";
|
|
2244
2533
|
import {
|
|
2245
2534
|
listSubgraphs,
|
|
2246
2535
|
resolveSubgraphRawClient
|
|
2247
2536
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
2248
|
-
import { logger as
|
|
2537
|
+
import { logger as logger7 } from "@secondlayer/shared/logger";
|
|
2249
2538
|
async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
2250
|
-
const targetDb =
|
|
2539
|
+
const targetDb = getTargetDb3();
|
|
2251
2540
|
const activeSubgraphs = (await listSubgraphs(targetDb)).filter((v) => v.status === "active");
|
|
2252
2541
|
if (activeSubgraphs.length === 0)
|
|
2253
2542
|
return;
|
|
2254
|
-
|
|
2543
|
+
logger7.info("Propagating reorg to subgraphs", {
|
|
2255
2544
|
blockHeight,
|
|
2256
2545
|
subgraphCount: activeSubgraphs.length
|
|
2257
2546
|
});
|
|
@@ -2260,88 +2549,97 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
|
2260
2549
|
const schema = sg.definition.schema;
|
|
2261
2550
|
if (!schema)
|
|
2262
2551
|
continue;
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
const
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
await client.
|
|
2552
|
+
bumpReorgEpoch(sg.name);
|
|
2553
|
+
await withSubgraphBlockLock(sg.name, async () => {
|
|
2554
|
+
const schemaName = sg.schema_name ?? pgSchemaName(sg.name);
|
|
2555
|
+
const client = resolveSubgraphRawClient(sg);
|
|
2556
|
+
const tableNames = Object.keys(schema);
|
|
2557
|
+
const revertedByTable = {};
|
|
2558
|
+
for (const tableName of tableNames) {
|
|
2559
|
+
const rows = await client.unsafe(`SELECT * FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1 LIMIT 1000`, [blockHeight]);
|
|
2560
|
+
if (rows.length > 0)
|
|
2561
|
+
revertedByTable[tableName] = rows;
|
|
2562
|
+
}
|
|
2563
|
+
const hasJournal = (await client.unsafe(`SELECT to_regclass('"${schemaName}"."_journal"') AS r`))[0]?.r;
|
|
2564
|
+
if (hasJournal) {
|
|
2565
|
+
await client.begin(async (tx) => {
|
|
2566
|
+
for (const tableName of tableNames) {
|
|
2567
|
+
const earliest = `
|
|
2568
|
+
SELECT DISTINCT ON (row_key) row_key, prev_row
|
|
2569
|
+
FROM "${schemaName}"."_journal"
|
|
2570
|
+
WHERE block_height >= $1 AND table_name = $2
|
|
2571
|
+
ORDER BY row_key, _jid ASC`;
|
|
2572
|
+
await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" t USING (${earliest}) e WHERE to_jsonb(t.*) @> e.row_key`, [blockHeight, tableName]);
|
|
2573
|
+
await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
|
|
2574
|
+
await tx.unsafe(`INSERT INTO "${schemaName}"."${tableName}"
|
|
2575
|
+
SELECT r.* FROM (${earliest}) e
|
|
2576
|
+
CROSS JOIN LATERAL jsonb_populate_record(NULL::"${schemaName}"."${tableName}", e.prev_row) r
|
|
2577
|
+
WHERE e.prev_row IS NOT NULL`, [blockHeight, tableName]);
|
|
2578
|
+
}
|
|
2579
|
+
await tx.unsafe(`DELETE FROM "${schemaName}"."_journal" WHERE block_height >= $1`, [blockHeight]);
|
|
2580
|
+
});
|
|
2581
|
+
} else {
|
|
2582
|
+
logger7.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
|
|
2275
2583
|
for (const tableName of tableNames) {
|
|
2276
|
-
|
|
2277
|
-
SELECT DISTINCT ON (row_key) row_key, prev_row
|
|
2278
|
-
FROM "${schemaName}"."_journal"
|
|
2279
|
-
WHERE block_height >= $1 AND table_name = $2
|
|
2280
|
-
ORDER BY row_key, _jid ASC`;
|
|
2281
|
-
await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" t USING (${earliest}) e WHERE to_jsonb(t.*) @> e.row_key`, [blockHeight, tableName]);
|
|
2282
|
-
await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
|
|
2283
|
-
await tx.unsafe(`INSERT INTO "${schemaName}"."${tableName}"
|
|
2284
|
-
SELECT r.* FROM (${earliest}) e
|
|
2285
|
-
CROSS JOIN LATERAL jsonb_populate_record(NULL::"${schemaName}"."${tableName}", e.prev_row) r
|
|
2286
|
-
WHERE e.prev_row IS NOT NULL`, [blockHeight, tableName]);
|
|
2584
|
+
await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
|
|
2287
2585
|
}
|
|
2288
|
-
await tx.unsafe(`DELETE FROM "${schemaName}"."_journal" WHERE block_height >= $1`, [blockHeight]);
|
|
2289
|
-
});
|
|
2290
|
-
} else {
|
|
2291
|
-
logger6.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
|
|
2292
|
-
for (const tableName of tableNames) {
|
|
2293
|
-
await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
|
|
2294
2586
|
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
error
|
|
2587
|
+
for (const [tableName, rows] of Object.entries(revertedByTable)) {
|
|
2588
|
+
if (rows.length === 0)
|
|
2589
|
+
continue;
|
|
2590
|
+
await targetDb.insertInto("subscription_outbox").columns([
|
|
2591
|
+
"subscription_id",
|
|
2592
|
+
"subgraph_name",
|
|
2593
|
+
"table_name",
|
|
2594
|
+
"block_height",
|
|
2595
|
+
"event_type",
|
|
2596
|
+
"payload",
|
|
2597
|
+
"dedup_key",
|
|
2598
|
+
"row_pk",
|
|
2599
|
+
"status"
|
|
2600
|
+
]).expression((eb) => eb.selectFrom("subscriptions").select((eb2) => [
|
|
2601
|
+
"id as subscription_id",
|
|
2602
|
+
eb2.val(sg.name).as("subgraph_name"),
|
|
2603
|
+
eb2.val(tableName).as("table_name"),
|
|
2604
|
+
eb2.val(blockHeight).as("block_height"),
|
|
2605
|
+
eb2.val(`${sg.name}.${tableName}.reverted`).as("event_type"),
|
|
2606
|
+
eb2.val(JSON.stringify({
|
|
2607
|
+
subgraph: sg.name,
|
|
2608
|
+
table: tableName,
|
|
2609
|
+
reorgFromHeight: blockHeight,
|
|
2610
|
+
rows
|
|
2611
|
+
})).as("payload"),
|
|
2612
|
+
eb2.val(`reorg:${sg.name}:${tableName}:${blockHeight}`).as("dedup_key"),
|
|
2613
|
+
eb2.val({
|
|
2614
|
+
subgraph: sg.name,
|
|
2615
|
+
table: tableName,
|
|
2616
|
+
reorgRoot: blockHeight
|
|
2617
|
+
}).as("row_pk"),
|
|
2618
|
+
eb2.val("pending").as("status")
|
|
2619
|
+
]).where("subgraph_name", "=", sg.name).where("table_name", "=", tableName).where("status", "=", "active")).onConflict((oc) => oc.columns(["subscription_id", "dedup_key"]).doNothing()).execute().catch((err) => {
|
|
2620
|
+
logger7.error("Failed to emit revert event for subscriptions", {
|
|
2621
|
+
event: "reorg_revert_emit_dropped",
|
|
2622
|
+
subgraph: sg.name,
|
|
2623
|
+
table: tableName,
|
|
2624
|
+
blockHeight,
|
|
2625
|
+
error: getErrorMessage2(err)
|
|
2626
|
+
});
|
|
2329
2627
|
});
|
|
2628
|
+
}
|
|
2629
|
+
logger7.info("Subgraph reorg cleanup done", {
|
|
2630
|
+
subgraph: sg.name,
|
|
2631
|
+
blockHeight,
|
|
2632
|
+
tablesAffected: Object.keys(revertedByTable).length
|
|
2633
|
+
});
|
|
2634
|
+
const def = await loadSubgraphDef(sg);
|
|
2635
|
+
await processBlock(def, sg.name, blockHeight);
|
|
2636
|
+
logger7.info("Subgraph reorg reprocessed", {
|
|
2637
|
+
subgraph: sg.name,
|
|
2638
|
+
blockHeight
|
|
2330
2639
|
});
|
|
2331
|
-
}
|
|
2332
|
-
logger6.info("Subgraph reorg cleanup done", {
|
|
2333
|
-
subgraph: sg.name,
|
|
2334
|
-
blockHeight,
|
|
2335
|
-
tablesAffected: Object.keys(revertedByTable).length
|
|
2336
|
-
});
|
|
2337
|
-
const def = await loadSubgraphDef(sg);
|
|
2338
|
-
await processBlock(def, sg.name, blockHeight);
|
|
2339
|
-
logger6.info("Subgraph reorg reprocessed", {
|
|
2340
|
-
subgraph: sg.name,
|
|
2341
|
-
blockHeight
|
|
2342
2640
|
});
|
|
2343
2641
|
} catch (err) {
|
|
2344
|
-
|
|
2642
|
+
logger7.error("Subgraph reorg handling failed", {
|
|
2345
2643
|
subgraph: sg.name,
|
|
2346
2644
|
blockHeight,
|
|
2347
2645
|
error: getErrorMessage2(err)
|
|
@@ -2353,5 +2651,5 @@ export {
|
|
|
2353
2651
|
handleSubgraphReorg
|
|
2354
2652
|
};
|
|
2355
2653
|
|
|
2356
|
-
//# debugId=
|
|
2654
|
+
//# debugId=283CF7962DBBE8DC64756E2164756E21
|
|
2357
2655
|
//# sourceMappingURL=reorg.js.map
|