@secondlayer/subgraphs 0.6.0 → 0.7.1
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.d.ts +3 -1
- package/dist/src/index.js +233 -119
- package/dist/src/index.js.map +16 -16
- package/dist/src/runtime/block-processor.d.ts +2 -0
- package/dist/src/runtime/block-processor.js +130 -100
- package/dist/src/runtime/block-processor.js.map +9 -9
- package/dist/src/runtime/catchup.d.ts +2 -0
- package/dist/src/runtime/catchup.js +180 -104
- package/dist/src/runtime/catchup.js.map +12 -12
- package/dist/src/runtime/clarity.js.map +2 -2
- package/dist/src/runtime/context.d.ts +1 -1
- package/dist/src/runtime/context.js +17 -4
- package/dist/src/runtime/context.js.map +3 -3
- package/dist/src/runtime/processor.js +198 -110
- package/dist/src/runtime/processor.js.map +14 -14
- package/dist/src/runtime/reindex.d.ts +2 -0
- package/dist/src/runtime/reindex.js +223 -117
- package/dist/src/runtime/reindex.js.map +13 -13
- package/dist/src/runtime/reorg.d.ts +2 -0
- package/dist/src/runtime/reorg.js +143 -104
- package/dist/src/runtime/reorg.js.map +10 -10
- package/dist/src/runtime/runner.d.ts +3 -1
- package/dist/src/runtime/runner.js +7 -3
- package/dist/src/runtime/runner.js.map +4 -4
- package/dist/src/runtime/source-matcher.js.map +3 -3
- package/dist/src/runtime/stats.d.ts +1 -1
- package/dist/src/runtime/stats.js +2 -2
- package/dist/src/runtime/stats.js.map +3 -3
- package/dist/src/schema/index.d.ts +3 -1
- package/dist/src/schema/index.js +11 -3
- package/dist/src/schema/index.js.map +5 -5
- package/dist/src/service.js +199 -111
- package/dist/src/service.js.map +15 -15
- package/dist/src/templates.js.map +2 -2
- package/dist/src/types.d.ts +2 -0
- package/dist/src/types.js.map +2 -2
- package/dist/src/validate.d.ts +2 -0
- package/dist/src/validate.js.map +2 -2
- package/package.json +51 -51
|
@@ -16,8 +16,8 @@ function sourceKey(source) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// src/runtime/context.ts
|
|
19
|
-
import { sql } from "kysely";
|
|
20
19
|
import { logger } from "@secondlayer/shared/logger";
|
|
20
|
+
import { sql } from "kysely";
|
|
21
21
|
function validateColumnName(name) {
|
|
22
22
|
if (!/^[a-z_][a-z0-9_]*$/i.test(name)) {
|
|
23
23
|
throw new Error(`Invalid column name: ${name}`);
|
|
@@ -58,13 +58,26 @@ class SubgraphContext {
|
|
|
58
58
|
const keyColumns = Object.keys(key);
|
|
59
59
|
const hasUniqueConstraint = tableDef.uniqueKeys?.some((uk) => uk.length === keyColumns.length && uk.every((c) => keyColumns.includes(c)));
|
|
60
60
|
if (hasUniqueConstraint) {
|
|
61
|
-
this.ops.push({
|
|
61
|
+
this.ops.push({
|
|
62
|
+
kind: "insert",
|
|
63
|
+
table,
|
|
64
|
+
data: { ...key, ...row, _upsert_keys: keyColumns }
|
|
65
|
+
});
|
|
62
66
|
} else {
|
|
63
67
|
logger.warn("upsert called without matching uniqueKeys constraint, using fallback", {
|
|
64
68
|
table,
|
|
65
69
|
keys: keyColumns
|
|
66
70
|
});
|
|
67
|
-
this.ops.push({
|
|
71
|
+
this.ops.push({
|
|
72
|
+
kind: "insert",
|
|
73
|
+
table,
|
|
74
|
+
data: {
|
|
75
|
+
...key,
|
|
76
|
+
...row,
|
|
77
|
+
_upsert_fallback_keys: keyColumns,
|
|
78
|
+
_upsert_fallback_set: row
|
|
79
|
+
}
|
|
80
|
+
});
|
|
68
81
|
}
|
|
69
82
|
}
|
|
70
83
|
delete(table, where) {
|
|
@@ -227,95 +240,6 @@ function buildWhereClause(where) {
|
|
|
227
240
|
return { clause: parts.join(" AND "), values: [] };
|
|
228
241
|
}
|
|
229
242
|
|
|
230
|
-
// src/runtime/source-matcher.ts
|
|
231
|
-
function matchPattern(value, pattern) {
|
|
232
|
-
if (!pattern.includes("*"))
|
|
233
|
-
return value === pattern;
|
|
234
|
-
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
235
|
-
return new RegExp(`^${regex}$`).test(value);
|
|
236
|
-
}
|
|
237
|
-
function matchSource(source, transactions, eventsByTx) {
|
|
238
|
-
const results = [];
|
|
239
|
-
const key = sourceKey(source);
|
|
240
|
-
for (const tx of transactions) {
|
|
241
|
-
if (source.type) {
|
|
242
|
-
if (!matchPattern(tx.type, source.type))
|
|
243
|
-
continue;
|
|
244
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
245
|
-
let matchedEvents = txEvents;
|
|
246
|
-
if (source.minAmount !== undefined) {
|
|
247
|
-
const amountEvents = matchedEvents.filter((e) => {
|
|
248
|
-
const data = e.data;
|
|
249
|
-
const rawAmount = data?.amount;
|
|
250
|
-
if (rawAmount === undefined)
|
|
251
|
-
return false;
|
|
252
|
-
const amount = BigInt(rawAmount);
|
|
253
|
-
return amount >= source.minAmount;
|
|
254
|
-
});
|
|
255
|
-
if (amountEvents.length === 0)
|
|
256
|
-
continue;
|
|
257
|
-
matchedEvents = amountEvents;
|
|
258
|
-
}
|
|
259
|
-
results.push({ tx, events: matchedEvents, sourceKey: key });
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (source.contract) {
|
|
263
|
-
const txContractMatch = tx.contract_id && matchPattern(tx.contract_id, source.contract);
|
|
264
|
-
if (source.function && tx.function_name) {
|
|
265
|
-
if (!matchPattern(tx.function_name, source.function))
|
|
266
|
-
continue;
|
|
267
|
-
} else if (source.function && !tx.function_name) {
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
271
|
-
let matchedEvents = txEvents;
|
|
272
|
-
if (!txContractMatch) {
|
|
273
|
-
matchedEvents = txEvents.filter((e) => {
|
|
274
|
-
const data = e.data;
|
|
275
|
-
const contractIdentifier = data?.contract_identifier;
|
|
276
|
-
return contractIdentifier && matchPattern(contractIdentifier, source.contract);
|
|
277
|
-
});
|
|
278
|
-
if (matchedEvents.length === 0)
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
if (source.event) {
|
|
282
|
-
matchedEvents = matchedEvents.filter((e) => {
|
|
283
|
-
if (matchPattern(e.type, source.event))
|
|
284
|
-
return true;
|
|
285
|
-
const data = e.data;
|
|
286
|
-
const topic = data?.topic;
|
|
287
|
-
return topic ? matchPattern(topic, source.event) : false;
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (txContractMatch || matchedEvents.length > 0) {
|
|
291
|
-
results.push({ tx, events: matchedEvents, sourceKey: key });
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
return results;
|
|
296
|
-
}
|
|
297
|
-
function matchSources(sources, transactions, events) {
|
|
298
|
-
const eventsByTx = new Map;
|
|
299
|
-
for (const event of events) {
|
|
300
|
-
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
301
|
-
list.push(event);
|
|
302
|
-
eventsByTx.set(event.tx_id, list);
|
|
303
|
-
}
|
|
304
|
-
const seen = new Set;
|
|
305
|
-
const results = [];
|
|
306
|
-
for (const source of sources) {
|
|
307
|
-
const matches = matchSource(source, transactions, eventsByTx);
|
|
308
|
-
for (const match of matches) {
|
|
309
|
-
const dedupeKey = `${match.tx.tx_id}:${match.sourceKey}`;
|
|
310
|
-
if (!seen.has(dedupeKey)) {
|
|
311
|
-
seen.add(dedupeKey);
|
|
312
|
-
results.push(match);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return results;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
243
|
// src/runtime/clarity.ts
|
|
320
244
|
import { cvToJSON, deserializeCV } from "@secondlayer/stacks/clarity";
|
|
321
245
|
function decodeClarityValue(hex) {
|
|
@@ -348,8 +272,8 @@ function decodeFunctionArgs(args) {
|
|
|
348
272
|
}
|
|
349
273
|
|
|
350
274
|
// src/runtime/runner.ts
|
|
351
|
-
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
352
275
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
276
|
+
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
353
277
|
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
354
278
|
function resolveHandler(handlers, key) {
|
|
355
279
|
return handlers[key] ?? handlers["*"] ?? null;
|
|
@@ -361,7 +285,11 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
361
285
|
for (const { tx, events, sourceKey: sourceKey2 } of matched) {
|
|
362
286
|
const handler = resolveHandler(subgraph.handlers, sourceKey2);
|
|
363
287
|
if (!handler) {
|
|
364
|
-
logger2.warn("No handler found for source key", {
|
|
288
|
+
logger2.warn("No handler found for source key", {
|
|
289
|
+
subgraph: subgraph.name,
|
|
290
|
+
sourceKey: sourceKey2,
|
|
291
|
+
txId: tx.tx_id
|
|
292
|
+
});
|
|
365
293
|
continue;
|
|
366
294
|
}
|
|
367
295
|
ctx.setTx({
|
|
@@ -438,11 +366,103 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
438
366
|
return { processed, errors };
|
|
439
367
|
}
|
|
440
368
|
|
|
369
|
+
// src/runtime/source-matcher.ts
|
|
370
|
+
function matchPattern(value, pattern) {
|
|
371
|
+
if (!pattern.includes("*"))
|
|
372
|
+
return value === pattern;
|
|
373
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
374
|
+
return new RegExp(`^${regex}$`).test(value);
|
|
375
|
+
}
|
|
376
|
+
function matchSource(source, transactions, eventsByTx) {
|
|
377
|
+
const results = [];
|
|
378
|
+
const key = sourceKey(source);
|
|
379
|
+
for (const tx of transactions) {
|
|
380
|
+
if (source.type) {
|
|
381
|
+
if (!matchPattern(tx.type, source.type))
|
|
382
|
+
continue;
|
|
383
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
384
|
+
let matchedEvents = txEvents;
|
|
385
|
+
if (source.minAmount !== undefined) {
|
|
386
|
+
const amountEvents = matchedEvents.filter((e) => {
|
|
387
|
+
const data = e.data;
|
|
388
|
+
const rawAmount = data?.amount;
|
|
389
|
+
if (rawAmount === undefined)
|
|
390
|
+
return false;
|
|
391
|
+
const amount = BigInt(rawAmount);
|
|
392
|
+
return amount >= source.minAmount;
|
|
393
|
+
});
|
|
394
|
+
if (amountEvents.length === 0)
|
|
395
|
+
continue;
|
|
396
|
+
matchedEvents = amountEvents;
|
|
397
|
+
}
|
|
398
|
+
results.push({ tx, events: matchedEvents, sourceKey: key });
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (source.contract) {
|
|
402
|
+
const txContractMatch = tx.contract_id && matchPattern(tx.contract_id, source.contract);
|
|
403
|
+
if (source.function && tx.function_name) {
|
|
404
|
+
if (!matchPattern(tx.function_name, source.function))
|
|
405
|
+
continue;
|
|
406
|
+
} else if (source.function && !tx.function_name) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
410
|
+
let matchedEvents = txEvents;
|
|
411
|
+
if (!txContractMatch) {
|
|
412
|
+
matchedEvents = txEvents.filter((e) => {
|
|
413
|
+
const data = e.data;
|
|
414
|
+
const contractIdentifier = data?.contract_identifier;
|
|
415
|
+
return contractIdentifier && matchPattern(contractIdentifier, source.contract);
|
|
416
|
+
});
|
|
417
|
+
if (matchedEvents.length === 0)
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (source.event) {
|
|
421
|
+
matchedEvents = matchedEvents.filter((e) => {
|
|
422
|
+
if (matchPattern(e.type, source.event))
|
|
423
|
+
return true;
|
|
424
|
+
const data = e.data;
|
|
425
|
+
const topic = data?.topic;
|
|
426
|
+
return topic ? matchPattern(topic, source.event) : false;
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
if (txContractMatch || matchedEvents.length > 0) {
|
|
430
|
+
results.push({ tx, events: matchedEvents, sourceKey: key });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return results;
|
|
435
|
+
}
|
|
436
|
+
function matchSources(sources, transactions, events) {
|
|
437
|
+
const eventsByTx = new Map;
|
|
438
|
+
for (const event of events) {
|
|
439
|
+
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
440
|
+
list.push(event);
|
|
441
|
+
eventsByTx.set(event.tx_id, list);
|
|
442
|
+
}
|
|
443
|
+
const seen = new Set;
|
|
444
|
+
const results = [];
|
|
445
|
+
for (const source of sources) {
|
|
446
|
+
const matches = matchSource(source, transactions, eventsByTx);
|
|
447
|
+
for (const match of matches) {
|
|
448
|
+
const dedupeKey = `${match.tx.tx_id}:${match.sourceKey}`;
|
|
449
|
+
if (!seen.has(dedupeKey)) {
|
|
450
|
+
seen.add(dedupeKey);
|
|
451
|
+
results.push(match);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return results;
|
|
456
|
+
}
|
|
457
|
+
|
|
441
458
|
// src/runtime/block-processor.ts
|
|
442
|
-
import { sql as sql2 } from "kysely";
|
|
443
459
|
import { getDb } from "@secondlayer/shared/db";
|
|
460
|
+
import {
|
|
461
|
+
recordSubgraphProcessed,
|
|
462
|
+
updateSubgraphStatus
|
|
463
|
+
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
444
464
|
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
445
|
-
import {
|
|
465
|
+
import { sql as sql2 } from "kysely";
|
|
446
466
|
|
|
447
467
|
// src/schema/utils.ts
|
|
448
468
|
import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
|
|
@@ -467,12 +487,18 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
467
487
|
} else {
|
|
468
488
|
block = await db.selectFrom("blocks").selectAll().where("height", "=", blockHeight).executeTakeFirst();
|
|
469
489
|
if (!block) {
|
|
470
|
-
logger3.warn("Block not found for subgraph processing", {
|
|
490
|
+
logger3.warn("Block not found for subgraph processing", {
|
|
491
|
+
subgraph: subgraphName,
|
|
492
|
+
blockHeight
|
|
493
|
+
});
|
|
471
494
|
result.skipped = true;
|
|
472
495
|
return result;
|
|
473
496
|
}
|
|
474
497
|
if (!block.canonical) {
|
|
475
|
-
logger3.debug("Skipping non-canonical block", {
|
|
498
|
+
logger3.debug("Skipping non-canonical block", {
|
|
499
|
+
subgraph: subgraphName,
|
|
500
|
+
blockHeight
|
|
501
|
+
});
|
|
476
502
|
result.skipped = true;
|
|
477
503
|
return result;
|
|
478
504
|
}
|
|
@@ -541,7 +567,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
541
567
|
const { rows } = await sql2.raw(`SELECT COUNT(*) as count FROM "${schemaName}"."${table}"`).execute(db);
|
|
542
568
|
const count = Number(rows[0].count);
|
|
543
569
|
if (count >= 1e7) {
|
|
544
|
-
logger3.warn("Subgraph table exceeds 10M rows", {
|
|
570
|
+
logger3.warn("Subgraph table exceeds 10M rows", {
|
|
571
|
+
subgraph: subgraphName,
|
|
572
|
+
table,
|
|
573
|
+
count
|
|
574
|
+
});
|
|
545
575
|
}
|
|
546
576
|
}
|
|
547
577
|
} catch {}
|
|
@@ -595,7 +625,7 @@ class StatsAccumulator {
|
|
|
595
625
|
flush_time_ms: Math.round(entry.flushTimeMs),
|
|
596
626
|
max_block_time_ms: Math.round(entry.maxBlockTimeMs),
|
|
597
627
|
max_handler_time_ms: Math.round(entry.maxHandlerTimeMs),
|
|
598
|
-
avg_ops_per_block: parseFloat(avgOpsPerBlock.toFixed(2)),
|
|
628
|
+
avg_ops_per_block: Number.parseFloat(avgOpsPerBlock.toFixed(2)),
|
|
599
629
|
is_catchup: entry.isCatchup
|
|
600
630
|
}).execute();
|
|
601
631
|
}
|
|
@@ -618,8 +648,11 @@ class StatsAccumulator {
|
|
|
618
648
|
|
|
619
649
|
// src/runtime/catchup.ts
|
|
620
650
|
import { getDb as getDb2 } from "@secondlayer/shared/db";
|
|
621
|
-
import {
|
|
651
|
+
import {
|
|
652
|
+
recordGapBatch
|
|
653
|
+
} from "@secondlayer/shared/db/queries/subgraph-gaps";
|
|
622
654
|
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
655
|
+
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
623
656
|
|
|
624
657
|
// src/runtime/batch-loader.ts
|
|
625
658
|
async function loadBlockRange(db, fromHeight, toHeight) {
|
|
@@ -669,6 +702,28 @@ var DEFAULT_BATCH_SIZE = 500;
|
|
|
669
702
|
var MIN_BATCH_SIZE = 100;
|
|
670
703
|
var MAX_BATCH_SIZE = 1000;
|
|
671
704
|
var catchingUp = new Set;
|
|
705
|
+
function coalesceGaps(blocks) {
|
|
706
|
+
if (blocks.length === 0)
|
|
707
|
+
return [];
|
|
708
|
+
blocks.sort((a, b) => a.height - b.height);
|
|
709
|
+
const ranges = [];
|
|
710
|
+
let start = blocks[0].height;
|
|
711
|
+
let end = blocks[0].height;
|
|
712
|
+
let reason = blocks[0].reason;
|
|
713
|
+
for (let i = 1;i < blocks.length; i++) {
|
|
714
|
+
const b = blocks[i];
|
|
715
|
+
if (b.height === end + 1 && b.reason === reason) {
|
|
716
|
+
end = b.height;
|
|
717
|
+
} else {
|
|
718
|
+
ranges.push({ start, end, reason });
|
|
719
|
+
start = b.height;
|
|
720
|
+
end = b.height;
|
|
721
|
+
reason = b.reason;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
ranges.push({ start, end, reason });
|
|
725
|
+
return ranges;
|
|
726
|
+
}
|
|
672
727
|
function adjustBatchSize(current, avgEvents) {
|
|
673
728
|
if (avgEvents > 50)
|
|
674
729
|
return Math.max(Math.round(current * 0.5), MIN_BATCH_SIZE);
|
|
@@ -689,10 +744,11 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
689
744
|
const progress = await db.selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
|
|
690
745
|
if (!progress)
|
|
691
746
|
return 0;
|
|
692
|
-
const chainTip = Number(progress.
|
|
747
|
+
const chainTip = Number(progress.highest_seen_block);
|
|
693
748
|
if (lastProcessedBlock >= chainTip)
|
|
694
749
|
return 0;
|
|
695
|
-
const
|
|
750
|
+
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
751
|
+
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
696
752
|
const totalBlocks = chainTip - lastProcessedBlock;
|
|
697
753
|
logger4.info("Subgraph catch-up starting", {
|
|
698
754
|
subgraph: subgraphName,
|
|
@@ -706,6 +762,14 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
706
762
|
let currentHeight = startBlock;
|
|
707
763
|
let nextBatchPromise = loadBlockRange(db, currentHeight, Math.min(currentHeight + batchSize - 1, chainTip));
|
|
708
764
|
while (currentHeight <= chainTip) {
|
|
765
|
+
const currentRow = await getSubgraph(db, subgraphName);
|
|
766
|
+
if (!currentRow || currentRow.status !== "active") {
|
|
767
|
+
logger4.info("Subgraph status changed, stopping catch-up", {
|
|
768
|
+
subgraph: subgraphName,
|
|
769
|
+
status: currentRow?.status ?? "deleted"
|
|
770
|
+
});
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
709
773
|
const batch = await nextBatchPromise;
|
|
710
774
|
const batchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
711
775
|
const nextStart = batchEnd + 1;
|
|
@@ -713,9 +777,11 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
713
777
|
const nextEnd = Math.min(nextStart + batchSize - 1, chainTip);
|
|
714
778
|
nextBatchPromise = loadBlockRange(db, nextStart, nextEnd);
|
|
715
779
|
}
|
|
780
|
+
const batchFailedBlocks = [];
|
|
716
781
|
for (let height = currentHeight;height <= batchEnd; height++) {
|
|
717
782
|
const blockData = batch.get(height);
|
|
718
783
|
if (!blockData) {
|
|
784
|
+
batchFailedBlocks.push({ height, reason: "block_missing" });
|
|
719
785
|
processed++;
|
|
720
786
|
continue;
|
|
721
787
|
}
|
|
@@ -730,6 +796,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
730
796
|
blockHeight: height,
|
|
731
797
|
error: err instanceof Error ? err.message : String(err)
|
|
732
798
|
});
|
|
799
|
+
batchFailedBlocks.push({ height, reason: "processing_error" });
|
|
733
800
|
const { updateSubgraphStatus: updateSubgraphStatus2 } = await import("@secondlayer/shared/db/queries/subgraphs");
|
|
734
801
|
await updateSubgraphStatus2(db, subgraphName, "active", height).catch(() => {});
|
|
735
802
|
processed++;
|
|
@@ -752,6 +819,15 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
752
819
|
});
|
|
753
820
|
}
|
|
754
821
|
}
|
|
822
|
+
if (batchFailedBlocks.length > 0) {
|
|
823
|
+
const gaps = coalesceGaps(batchFailedBlocks);
|
|
824
|
+
await recordGapBatch(db, subgraphRow.id, subgraphName, gaps).catch((err) => {
|
|
825
|
+
logger4.warn("Failed to record subgraph gaps", {
|
|
826
|
+
subgraph: subgraphName,
|
|
827
|
+
error: err instanceof Error ? err.message : String(err)
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
}
|
|
755
831
|
const avg = avgEventsPerBlock(batch);
|
|
756
832
|
batchSize = adjustBatchSize(batchSize, avg);
|
|
757
833
|
currentHeight = batchEnd + 1;
|
|
@@ -768,17 +844,20 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
768
844
|
}
|
|
769
845
|
|
|
770
846
|
// src/runtime/reorg.ts
|
|
847
|
+
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
771
848
|
import { getDb as getDb3, getRawClient } from "@secondlayer/shared/db";
|
|
772
849
|
import { listSubgraphs } from "@secondlayer/shared/db/queries/subgraphs";
|
|
773
850
|
import { logger as logger5 } from "@secondlayer/shared/logger";
|
|
774
|
-
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
775
851
|
async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
776
852
|
const db = getDb3();
|
|
777
853
|
const client = getRawClient();
|
|
778
854
|
const activeSubgraphs = (await listSubgraphs(db)).filter((v) => v.status === "active");
|
|
779
855
|
if (activeSubgraphs.length === 0)
|
|
780
856
|
return;
|
|
781
|
-
logger5.info("Propagating reorg to subgraphs", {
|
|
857
|
+
logger5.info("Propagating reorg to subgraphs", {
|
|
858
|
+
blockHeight,
|
|
859
|
+
subgraphCount: activeSubgraphs.length
|
|
860
|
+
});
|
|
782
861
|
for (const sg of activeSubgraphs) {
|
|
783
862
|
try {
|
|
784
863
|
const schema = sg.definition.schema;
|
|
@@ -788,10 +867,16 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
|
788
867
|
for (const tableName of Object.keys(schema)) {
|
|
789
868
|
await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" = $1`, [blockHeight]);
|
|
790
869
|
}
|
|
791
|
-
logger5.info("Subgraph reorg cleanup done", {
|
|
870
|
+
logger5.info("Subgraph reorg cleanup done", {
|
|
871
|
+
subgraph: sg.name,
|
|
872
|
+
blockHeight
|
|
873
|
+
});
|
|
792
874
|
const def = await loadSubgraphDef(sg.handler_path);
|
|
793
875
|
await processBlock(def, sg.name, blockHeight);
|
|
794
|
-
logger5.info("Subgraph reorg reprocessed", {
|
|
876
|
+
logger5.info("Subgraph reorg reprocessed", {
|
|
877
|
+
subgraph: sg.name,
|
|
878
|
+
blockHeight
|
|
879
|
+
});
|
|
795
880
|
} catch (err) {
|
|
796
881
|
logger5.error("Subgraph reorg handling failed", {
|
|
797
882
|
subgraph: sg.name,
|
|
@@ -803,11 +888,14 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
|
803
888
|
}
|
|
804
889
|
|
|
805
890
|
// src/runtime/processor.ts
|
|
891
|
+
import { getErrorMessage as getErrorMessage3 } from "@secondlayer/shared";
|
|
806
892
|
import { getDb as getDb4 } from "@secondlayer/shared/db";
|
|
893
|
+
import {
|
|
894
|
+
listSubgraphs as listSubgraphs2,
|
|
895
|
+
updateSubgraphStatus as updateSubgraphStatus2
|
|
896
|
+
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
807
897
|
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
808
|
-
import { getErrorMessage as getErrorMessage3 } from "@secondlayer/shared";
|
|
809
898
|
import { listen } from "@secondlayer/shared/queue/listener";
|
|
810
|
-
import { listSubgraphs as listSubgraphs2, updateSubgraphStatus as updateSubgraphStatus2 } from "@secondlayer/shared/db/queries/subgraphs";
|
|
811
899
|
var CHANNEL_NEW_BLOCK = "streams:new_job";
|
|
812
900
|
var DEFAULT_CONCURRENCY = 5;
|
|
813
901
|
var POLL_INTERVAL_MS = 5000;
|
|
@@ -910,5 +998,5 @@ export {
|
|
|
910
998
|
startSubgraphProcessor
|
|
911
999
|
};
|
|
912
1000
|
|
|
913
|
-
//# debugId=
|
|
1001
|
+
//# debugId=5F33BE7F1A25C9CB64756E2164756E21
|
|
914
1002
|
//# sourceMappingURL=processor.js.map
|