@secondlayer/subgraphs 3.23.1 → 3.24.0
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 +11 -7
- package/dist/src/index.js +108 -64
- package/dist/src/index.js.map +4 -4
- package/dist/src/runtime/block-processor.d.ts +75 -9
- package/dist/src/runtime/block-processor.js +79 -34
- package/dist/src/runtime/block-processor.js.map +4 -4
- package/dist/src/runtime/catchup.d.ts +11 -7
- package/dist/src/runtime/catchup.js +120 -42
- package/dist/src/runtime/catchup.js.map +7 -6
- package/dist/src/runtime/context.js +12 -3
- package/dist/src/runtime/context.js.map +3 -3
- package/dist/src/runtime/processor.js +275 -214
- package/dist/src/runtime/processor.js.map +8 -8
- package/dist/src/runtime/reindex.d.ts +11 -7
- package/dist/src/runtime/reindex.js +108 -64
- package/dist/src/runtime/reindex.js.map +4 -4
- package/dist/src/runtime/reorg.d.ts +11 -7
- package/dist/src/runtime/reorg.js +221 -135
- package/dist/src/runtime/reorg.js.map +8 -7
- package/dist/src/runtime/runner.d.ts +36 -32
- package/dist/src/runtime/source-matcher.d.ts +11 -7
- package/dist/src/runtime/stats.d.ts +3 -3
- package/dist/src/schema/index.d.ts +11 -7
- package/dist/src/service.js +280 -219
- package/dist/src/service.js.map +8 -8
- package/dist/src/testing/index.d.ts +32 -28
- package/dist/src/testing/index.js +17 -8
- package/dist/src/testing/index.js.map +3 -3
- package/dist/src/triggers/index.d.ts +11 -7
- package/dist/src/types.d.ts +11 -7
- package/dist/src/validate.d.ts +11 -7
- package/package.json +3 -5
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AbiContract as AbiContract2 } from "@secondlayer/stacks/clarity";
|
|
2
|
+
import { AssetIdentifier } from "@secondlayer/stacks/filters";
|
|
2
3
|
/**
|
|
3
4
|
* How long a read stays valid.
|
|
4
5
|
*
|
|
@@ -147,38 +148,38 @@ interface FactoryScope {
|
|
|
147
148
|
/** FT event filters */
|
|
148
149
|
interface FtTransferFilter extends TraitScope {
|
|
149
150
|
type: "ft_transfer";
|
|
150
|
-
assetIdentifier?:
|
|
151
|
+
assetIdentifier?: AssetIdentifier;
|
|
151
152
|
sender?: string;
|
|
152
153
|
recipient?: string;
|
|
153
154
|
minAmount?: bigint;
|
|
154
155
|
}
|
|
155
156
|
interface FtMintFilter extends TraitScope {
|
|
156
157
|
type: "ft_mint";
|
|
157
|
-
assetIdentifier?:
|
|
158
|
+
assetIdentifier?: AssetIdentifier;
|
|
158
159
|
recipient?: string;
|
|
159
160
|
minAmount?: bigint;
|
|
160
161
|
}
|
|
161
162
|
interface FtBurnFilter extends TraitScope {
|
|
162
163
|
type: "ft_burn";
|
|
163
|
-
assetIdentifier?:
|
|
164
|
+
assetIdentifier?: AssetIdentifier;
|
|
164
165
|
sender?: string;
|
|
165
166
|
minAmount?: bigint;
|
|
166
167
|
}
|
|
167
168
|
/** NFT event filters */
|
|
168
169
|
interface NftTransferFilter extends TraitScope {
|
|
169
170
|
type: "nft_transfer";
|
|
170
|
-
assetIdentifier?:
|
|
171
|
+
assetIdentifier?: AssetIdentifier;
|
|
171
172
|
sender?: string;
|
|
172
173
|
recipient?: string;
|
|
173
174
|
}
|
|
174
175
|
interface NftMintFilter extends TraitScope {
|
|
175
176
|
type: "nft_mint";
|
|
176
|
-
assetIdentifier?:
|
|
177
|
+
assetIdentifier?: AssetIdentifier;
|
|
177
178
|
recipient?: string;
|
|
178
179
|
}
|
|
179
180
|
interface NftBurnFilter extends TraitScope {
|
|
180
181
|
type: "nft_burn";
|
|
181
|
-
assetIdentifier?:
|
|
182
|
+
assetIdentifier?: AssetIdentifier;
|
|
182
183
|
sender?: string;
|
|
183
184
|
}
|
|
184
185
|
/** Contract event filters */
|
|
@@ -235,7 +236,10 @@ type PrintField = ColumnType | {
|
|
|
235
236
|
interface PrintEventFilter extends TraitScope, FactoryScope {
|
|
236
237
|
type: "print_event";
|
|
237
238
|
/** One contract id, or a set of them (max 20). Supports `*` wildcards.
|
|
238
|
-
*
|
|
239
|
+
* COMPOSES with `trait` — the matcher ANDs them, so the pair means
|
|
240
|
+
* "contracts conforming to this trait, narrowed to these ids". (The Index
|
|
241
|
+
* read API refuses the combination, but that is its query planner's
|
|
242
|
+
* constraint, not a semantic one.) */
|
|
239
243
|
contractId?: string | readonly string[];
|
|
240
244
|
topic?: string;
|
|
241
245
|
/**
|
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// src/runtime/context.ts
|
|
5
|
-
import { logger } from "@secondlayer/shared/logger";
|
|
5
|
+
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
6
6
|
import { formatUnits } from "@secondlayer/stacks/utils";
|
|
7
7
|
import { sql } from "kysely";
|
|
8
8
|
|
|
@@ -147,6 +147,7 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
147
147
|
|
|
148
148
|
// src/runtime/chain-read.ts
|
|
149
149
|
import { getSourceDb } from "@secondlayer/shared/db";
|
|
150
|
+
import { logger } from "@secondlayer/shared/logger";
|
|
150
151
|
import { http, createPublicClient } from "@secondlayer/stacks";
|
|
151
152
|
import {
|
|
152
153
|
ContractResponseError,
|
|
@@ -232,6 +233,14 @@ function createChainReadClient(ctx) {
|
|
|
232
233
|
const pinnedTo = constant ? null : ctx.indexBlockHash;
|
|
233
234
|
const cached = await db.selectFrom("chain_read_cache").select("result_hex").where("contract_id", "=", contractId).where("function_name", "=", fn.name).where("args_hash", "=", argsHash).where((eb) => pinnedTo === null ? eb("index_block_hash", "is", null) : eb("index_block_hash", "=", pinnedTo)).executeTakeFirst();
|
|
234
235
|
const resultHex = cached?.result_hex ?? await gate.run(async () => {
|
|
236
|
+
if (!ctx.indexBlockHash) {
|
|
237
|
+
logger.warn("Chain read is not pinned to a block (no index_block_hash)", {
|
|
238
|
+
contractId,
|
|
239
|
+
functionName: fn.name,
|
|
240
|
+
blockHeight: ctx.blockHeight,
|
|
241
|
+
note: 'cache: "contract-constant" asserts this value cannot change; the node answered at its own tip'
|
|
242
|
+
});
|
|
243
|
+
}
|
|
235
244
|
const value2 = await readContract(nodeClient(rpcUrl), {
|
|
236
245
|
contract: contractId,
|
|
237
246
|
functionName: fn.name,
|
|
@@ -346,7 +355,7 @@ class SubgraphContext {
|
|
|
346
355
|
data: { ...key, ...row, ...meta, _upsert_keys: keyColumns }
|
|
347
356
|
});
|
|
348
357
|
} else {
|
|
349
|
-
|
|
358
|
+
logger2.warn("upsert called without matching uniqueKeys constraint, using fallback", {
|
|
350
359
|
table,
|
|
351
360
|
keys: keyColumns
|
|
352
361
|
});
|
|
@@ -906,7 +915,7 @@ function decodeFunctionArgs(args) {
|
|
|
906
915
|
|
|
907
916
|
// src/runtime/runner.ts
|
|
908
917
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
909
|
-
import { logger as
|
|
918
|
+
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
910
919
|
import {
|
|
911
920
|
clarityValueToJS,
|
|
912
921
|
deserializeCV as deserializeCV3,
|
|
@@ -1257,7 +1266,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1257
1266
|
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
1258
1267
|
for (const { tx, event, sourceName } of units) {
|
|
1259
1268
|
if (errors >= threshold) {
|
|
1260
|
-
|
|
1269
|
+
logger3.error("Subgraph error threshold reached, skipping remaining events", {
|
|
1261
1270
|
subgraph: subgraph.name,
|
|
1262
1271
|
errors,
|
|
1263
1272
|
threshold
|
|
@@ -1266,7 +1275,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1266
1275
|
}
|
|
1267
1276
|
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
1268
1277
|
if (!handler) {
|
|
1269
|
-
|
|
1278
|
+
logger3.warn("No handler found for source", {
|
|
1270
1279
|
subgraph: subgraph.name,
|
|
1271
1280
|
sourceName,
|
|
1272
1281
|
txId: tx.tx_id
|
|
@@ -1323,7 +1332,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1323
1332
|
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1324
1333
|
if (!verdict.ok) {
|
|
1325
1334
|
skipped++;
|
|
1326
|
-
|
|
1335
|
+
logger3.warn("Print payload does not match declared prints schema", {
|
|
1327
1336
|
subgraph: subgraph.name,
|
|
1328
1337
|
sourceName,
|
|
1329
1338
|
topic: printPayload.topic,
|
|
@@ -1338,7 +1347,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1338
1347
|
} catch (err) {
|
|
1339
1348
|
ctx.rollbackTo(checkpoint);
|
|
1340
1349
|
errors++;
|
|
1341
|
-
|
|
1350
|
+
logger3.error("Subgraph handler error", {
|
|
1342
1351
|
subgraph: subgraph.name,
|
|
1343
1352
|
sourceName,
|
|
1344
1353
|
txId: tx.tx_id,
|
|
@@ -1618,12 +1627,14 @@ import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contract
|
|
|
1618
1627
|
import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
|
|
1619
1628
|
import {
|
|
1620
1629
|
isByoSubgraph,
|
|
1630
|
+
recordLiveError,
|
|
1621
1631
|
recordLiveProgress,
|
|
1622
1632
|
recordSubgraphProcessed,
|
|
1623
1633
|
resolveSubgraphDb,
|
|
1634
|
+
rewindLiveProgress,
|
|
1624
1635
|
updateSubgraphStatus
|
|
1625
1636
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
1626
|
-
import { logger as
|
|
1637
|
+
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
1627
1638
|
import { sql as sql3 } from "kysely";
|
|
1628
1639
|
|
|
1629
1640
|
// src/runtime/block-source.ts
|
|
@@ -1631,7 +1642,7 @@ import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
|
|
|
1631
1642
|
import {
|
|
1632
1643
|
IndexHttpClient
|
|
1633
1644
|
} from "@secondlayer/shared/index-http";
|
|
1634
|
-
import { logger as
|
|
1645
|
+
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
1635
1646
|
|
|
1636
1647
|
// src/runtime/batch-loader.ts
|
|
1637
1648
|
async function loadBlockRange(db, fromHeight, toHeight) {
|
|
@@ -1955,7 +1966,7 @@ class FallbackBlockSource {
|
|
|
1955
1966
|
try {
|
|
1956
1967
|
return await this.primary.getTip();
|
|
1957
1968
|
} catch (err) {
|
|
1958
|
-
|
|
1969
|
+
logger4.warn("block source primary getTip failed — using DB tap", {
|
|
1959
1970
|
error: err instanceof Error ? err.message : String(err)
|
|
1960
1971
|
});
|
|
1961
1972
|
return this.fallback.getTip();
|
|
@@ -1965,7 +1976,7 @@ class FallbackBlockSource {
|
|
|
1965
1976
|
try {
|
|
1966
1977
|
return await this.primary.loadBlockRange(fromHeight, toHeight);
|
|
1967
1978
|
} catch (err) {
|
|
1968
|
-
|
|
1979
|
+
logger4.warn("block source primary loadBlockRange failed — using DB tap", {
|
|
1969
1980
|
from: fromHeight,
|
|
1970
1981
|
to: toHeight,
|
|
1971
1982
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -1997,7 +2008,7 @@ function resolveBlockSource(subgraph) {
|
|
|
1997
2008
|
return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph), canSparseScan(subgraph) ? sparseProbeTargets(subgraph) : undefined, needsTransactionData(subgraph)), postgresBlockSource);
|
|
1998
2009
|
}
|
|
1999
2010
|
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
|
|
2000
|
-
|
|
2011
|
+
logger4.debug("Subgraph not streams-index eligible, using DB tap", {
|
|
2001
2012
|
subgraph: subgraph.name
|
|
2002
2013
|
});
|
|
2003
2014
|
}
|
|
@@ -2006,7 +2017,7 @@ function resolveBlockSource(subgraph) {
|
|
|
2006
2017
|
|
|
2007
2018
|
// src/runtime/outbox-emit.ts
|
|
2008
2019
|
import { createHash as createHash2 } from "node:crypto";
|
|
2009
|
-
import { logger as
|
|
2020
|
+
import { logger as logger5 } from "@secondlayer/shared/logger";
|
|
2010
2021
|
var loggedKillSwitch = false;
|
|
2011
2022
|
var OP_VERB = {
|
|
2012
2023
|
insert: "created",
|
|
@@ -2030,7 +2041,7 @@ function stableStringify(obj) {
|
|
|
2030
2041
|
async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, blockHeight) {
|
|
2031
2042
|
if (!isEmitOutboxEnabled()) {
|
|
2032
2043
|
if (!loggedKillSwitch) {
|
|
2033
|
-
|
|
2044
|
+
logger5.warn("SECONDLAYER_EMIT_OUTBOX=false — outbox emission bypassed");
|
|
2034
2045
|
loggedKillSwitch = true;
|
|
2035
2046
|
}
|
|
2036
2047
|
return 0;
|
|
@@ -2328,10 +2339,20 @@ class CursorRaceLostError extends Error {
|
|
|
2328
2339
|
this.name = "CursorRaceLostError";
|
|
2329
2340
|
}
|
|
2330
2341
|
}
|
|
2342
|
+
|
|
2343
|
+
class LiveCursorRaceLostError extends Error {
|
|
2344
|
+
constructor(subgraphName, height) {
|
|
2345
|
+
super(`live cursor race lost for ${subgraphName} at block ${height}`);
|
|
2346
|
+
this.name = "LiveCursorRaceLostError";
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2331
2349
|
function opCursorMode(opts) {
|
|
2332
2350
|
const ap = opts?.atomicProgress;
|
|
2333
2351
|
return ap && "operationId" in ap ? ap : undefined;
|
|
2334
2352
|
}
|
|
2353
|
+
function isReorgRewind(opts) {
|
|
2354
|
+
return opts?.reorgRewind === true;
|
|
2355
|
+
}
|
|
2335
2356
|
function statusMode(opts) {
|
|
2336
2357
|
const ap = opts?.atomicProgress;
|
|
2337
2358
|
return ap && "status" in ap ? ap : undefined;
|
|
@@ -2350,7 +2371,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
|
|
|
2350
2371
|
const delay = retryDelaysMs[attempt];
|
|
2351
2372
|
if (delay === undefined)
|
|
2352
2373
|
break;
|
|
2353
|
-
|
|
2374
|
+
logger6.warn("Block processing failed, retrying", {
|
|
2354
2375
|
subgraph: subgraphName,
|
|
2355
2376
|
blockHeight,
|
|
2356
2377
|
attempt: attempt + 1,
|
|
@@ -2382,7 +2403,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2382
2403
|
} else {
|
|
2383
2404
|
const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
|
|
2384
2405
|
if (!data) {
|
|
2385
|
-
|
|
2406
|
+
logger6.debug("Block not found or non-canonical for subgraph processing", {
|
|
2386
2407
|
subgraph: subgraphName,
|
|
2387
2408
|
blockHeight
|
|
2388
2409
|
});
|
|
@@ -2401,7 +2422,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2401
2422
|
result.matched = matched.length;
|
|
2402
2423
|
if (matched.length === 0) {
|
|
2403
2424
|
if (!opts?.skipProgressUpdate) {
|
|
2404
|
-
|
|
2425
|
+
if (isReorgRewind(opts)) {
|
|
2426
|
+
await rewindLiveProgress(targetDb, subgraphName, blockHeight);
|
|
2427
|
+
} else {
|
|
2428
|
+
await recordLiveProgress(targetDb, subgraphName, blockHeight);
|
|
2429
|
+
}
|
|
2405
2430
|
}
|
|
2406
2431
|
return result;
|
|
2407
2432
|
}
|
|
@@ -2420,13 +2445,23 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2420
2445
|
};
|
|
2421
2446
|
let handlerMs = 0;
|
|
2422
2447
|
let flushMs = 0;
|
|
2423
|
-
const applyProgress = async (tx, rr) => {
|
|
2448
|
+
const applyProgress = async (tx, rr, flushedWrites) => {
|
|
2424
2449
|
if (opts?.skipProgressUpdate)
|
|
2425
2450
|
return;
|
|
2451
|
+
const rewind = isReorgRewind(opts);
|
|
2426
2452
|
if (rr.errors > 0 && rr.processed === 0) {
|
|
2427
|
-
|
|
2453
|
+
if (rewind) {
|
|
2454
|
+
await updateSubgraphStatus(tx, subgraphName, "error", blockHeight);
|
|
2455
|
+
} else {
|
|
2456
|
+
await recordLiveError(tx, subgraphName, blockHeight);
|
|
2457
|
+
}
|
|
2458
|
+
} else if (rewind) {
|
|
2459
|
+
await rewindLiveProgress(tx, subgraphName, blockHeight);
|
|
2428
2460
|
} else {
|
|
2429
|
-
await recordLiveProgress(tx, subgraphName, blockHeight);
|
|
2461
|
+
const advanced = await recordLiveProgress(tx, subgraphName, blockHeight);
|
|
2462
|
+
if (!advanced && flushedWrites && !route.byo) {
|
|
2463
|
+
throw new LiveCursorRaceLostError(subgraphName, blockHeight);
|
|
2464
|
+
}
|
|
2430
2465
|
}
|
|
2431
2466
|
if (rr.processed > 0 || rr.errors > 0) {
|
|
2432
2467
|
const lastError = rr.errors > 0 ? `${rr.errors} error(s) at block ${blockHeight}` : undefined;
|
|
@@ -2475,13 +2510,15 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2475
2510
|
} else if (byoOm && manifest && manifest.count > 0) {
|
|
2476
2511
|
await advanceOperationCursor(tx, byoOm.operationId, blockHeight);
|
|
2477
2512
|
}
|
|
2478
|
-
await applyProgress(tx, runResult);
|
|
2513
|
+
await applyProgress(tx, runResult, manifest ? manifest.count > 0 : false);
|
|
2479
2514
|
});
|
|
2480
2515
|
} else {
|
|
2481
2516
|
try {
|
|
2482
2517
|
await targetDb.transaction().execute(async (tx) => {
|
|
2483
2518
|
const opMode = opCursorMode(opts);
|
|
2484
|
-
|
|
2519
|
+
const sm = statusMode(opts);
|
|
2520
|
+
const rewind = isReorgRewind(opts);
|
|
2521
|
+
if (sm) {
|
|
2485
2522
|
const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
|
|
2486
2523
|
if (row && Number(row.last_processed_block) >= blockHeight) {
|
|
2487
2524
|
result.skipped = true;
|
|
@@ -2493,6 +2530,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2493
2530
|
result.skipped = true;
|
|
2494
2531
|
return;
|
|
2495
2532
|
}
|
|
2533
|
+
} else if (!rewind) {
|
|
2534
|
+
const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
|
|
2535
|
+
if (row && Number(row.last_processed_block) >= blockHeight) {
|
|
2536
|
+
result.skipped = true;
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2496
2539
|
}
|
|
2497
2540
|
const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
|
|
2498
2541
|
const handlerStart = performance.now();
|
|
@@ -2510,7 +2553,6 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2510
2553
|
}
|
|
2511
2554
|
flushMs = performance.now() - flushStart;
|
|
2512
2555
|
}
|
|
2513
|
-
const sm = statusMode(opts);
|
|
2514
2556
|
if (sm && flushedWrites) {
|
|
2515
2557
|
await updateSubgraphStatus(tx, subgraphName, sm.status, blockHeight);
|
|
2516
2558
|
} else if (opMode && flushedWrites) {
|
|
@@ -2519,11 +2561,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2519
2561
|
throw new CursorRaceLostError(opMode.operationId, blockHeight);
|
|
2520
2562
|
}
|
|
2521
2563
|
}
|
|
2522
|
-
await applyProgress(tx, runResult);
|
|
2564
|
+
await applyProgress(tx, runResult, flushedWrites);
|
|
2523
2565
|
});
|
|
2524
2566
|
} catch (err) {
|
|
2525
|
-
if (err instanceof CursorRaceLostError) {
|
|
2526
|
-
|
|
2567
|
+
if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
|
|
2568
|
+
logger6.warn("cursor race lost — block already covered", {
|
|
2527
2569
|
subgraph: subgraphName,
|
|
2528
2570
|
blockHeight,
|
|
2529
2571
|
error: err.message
|
|
@@ -2535,11 +2577,13 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2535
2577
|
}
|
|
2536
2578
|
}
|
|
2537
2579
|
const totalMs = performance.now() - blockStart;
|
|
2538
|
-
result.
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2580
|
+
if (!result.skipped) {
|
|
2581
|
+
result.timing = {
|
|
2582
|
+
totalMs: Math.round(totalMs),
|
|
2583
|
+
handlerMs: Math.round(handlerMs),
|
|
2584
|
+
flushMs: Math.round(flushMs)
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2543
2587
|
if (blockHeight % 1000 === 0) {
|
|
2544
2588
|
try {
|
|
2545
2589
|
const tables = Object.keys(subgraph.schema);
|
|
@@ -2547,7 +2591,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2547
2591
|
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);
|
|
2548
2592
|
const count = Number(rows[0]?.count ?? 0);
|
|
2549
2593
|
if (count >= 1e7) {
|
|
2550
|
-
|
|
2594
|
+
logger6.warn("Subgraph table exceeds 10M rows (estimate)", {
|
|
2551
2595
|
subgraph: subgraphName,
|
|
2552
2596
|
table,
|
|
2553
2597
|
count
|
|
@@ -2555,7 +2599,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2555
2599
|
}
|
|
2556
2600
|
}
|
|
2557
2601
|
} catch (err) {
|
|
2558
|
-
|
|
2602
|
+
logger6.debug("Row count sample failed", {
|
|
2559
2603
|
subgraph: subgraphName,
|
|
2560
2604
|
error: err instanceof Error ? err.message : String(err)
|
|
2561
2605
|
});
|
|
@@ -2633,7 +2677,34 @@ class StatsAccumulator {
|
|
|
2633
2677
|
// src/runtime/catchup.ts
|
|
2634
2678
|
import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
|
|
2635
2679
|
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
2636
|
-
import { logger as
|
|
2680
|
+
import { logger as logger7 } from "@secondlayer/shared/logger";
|
|
2681
|
+
|
|
2682
|
+
// src/runtime/catchup-leader.ts
|
|
2683
|
+
import {
|
|
2684
|
+
SUBGRAPH_CATCHUP_LOCK_KEY,
|
|
2685
|
+
createPostgresLeaderBackend,
|
|
2686
|
+
withLeaderLock
|
|
2687
|
+
} from "@secondlayer/shared/leader";
|
|
2688
|
+
import { targetListenerUrl } from "@secondlayer/shared/queue/listener";
|
|
2689
|
+
var catchUpLeader = false;
|
|
2690
|
+
function isCatchUpLeader() {
|
|
2691
|
+
return catchUpLeader;
|
|
2692
|
+
}
|
|
2693
|
+
function startCatchUpLeader(opts = {}) {
|
|
2694
|
+
return withLeaderLock(SUBGRAPH_CATCHUP_LOCK_KEY, async () => {
|
|
2695
|
+
catchUpLeader = true;
|
|
2696
|
+
await opts.onAcquire?.();
|
|
2697
|
+
return () => {
|
|
2698
|
+
catchUpLeader = false;
|
|
2699
|
+
};
|
|
2700
|
+
}, {
|
|
2701
|
+
pollMs: opts.pollMs,
|
|
2702
|
+
heartbeatMs: opts.heartbeatMs,
|
|
2703
|
+
createBackend: opts.createBackend ?? (() => createPostgresLeaderBackend(targetListenerUrl()))
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// src/runtime/catchup.ts
|
|
2637
2708
|
var LOG_INTERVAL = 1000;
|
|
2638
2709
|
var STANDARD_CATCHUP_BATCH_CONFIG = {
|
|
2639
2710
|
defaultBatchSize: 500,
|
|
@@ -2740,7 +2811,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2740
2811
|
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
2741
2812
|
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
2742
2813
|
const totalBlocks = chainTip - lastProcessedBlock;
|
|
2743
|
-
|
|
2814
|
+
logger7.info("Subgraph catch-up starting", {
|
|
2744
2815
|
subgraph: subgraphName,
|
|
2745
2816
|
from: startBlock,
|
|
2746
2817
|
to: chainTip,
|
|
@@ -2755,9 +2826,16 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2755
2826
|
let prefetchedBatchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
2756
2827
|
let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
|
|
2757
2828
|
while (currentHeight <= chainTip) {
|
|
2829
|
+
if (!isCatchUpLeader()) {
|
|
2830
|
+
logger7.info("Lost catch-up leadership, stopping catch-up", {
|
|
2831
|
+
subgraph: subgraphName,
|
|
2832
|
+
processed
|
|
2833
|
+
});
|
|
2834
|
+
break;
|
|
2835
|
+
}
|
|
2758
2836
|
const currentRow = await getSubgraph(targetDb, subgraphName);
|
|
2759
2837
|
if (!currentRow || currentRow.status !== "active") {
|
|
2760
|
-
|
|
2838
|
+
logger7.info("Subgraph status changed, stopping catch-up", {
|
|
2761
2839
|
subgraph: subgraphName,
|
|
2762
2840
|
status: currentRow?.status ?? "deleted"
|
|
2763
2841
|
});
|
|
@@ -2786,7 +2864,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2786
2864
|
blockData = (await source.loadBlockRange(height, height)).get(height);
|
|
2787
2865
|
}
|
|
2788
2866
|
if (!blockData) {
|
|
2789
|
-
|
|
2867
|
+
logger7.warn("Block missing during catch-up, deferring to next tick", {
|
|
2790
2868
|
subgraph: subgraphName,
|
|
2791
2869
|
blockHeight: height
|
|
2792
2870
|
});
|
|
@@ -2806,12 +2884,12 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2806
2884
|
});
|
|
2807
2885
|
} catch (err) {
|
|
2808
2886
|
if (err instanceof ReorgEpochChangedError) {
|
|
2809
|
-
|
|
2887
|
+
logger7.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
|
|
2810
2888
|
stopCatchup = true;
|
|
2811
2889
|
break;
|
|
2812
2890
|
}
|
|
2813
2891
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2814
|
-
|
|
2892
|
+
logger7.error("Block processing failed persistently during catch-up", {
|
|
2815
2893
|
subgraph: subgraphName,
|
|
2816
2894
|
blockHeight: height,
|
|
2817
2895
|
error: errorMsg
|
|
@@ -2830,7 +2908,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2830
2908
|
}
|
|
2831
2909
|
}
|
|
2832
2910
|
if (processed % LOG_INTERVAL === 0) {
|
|
2833
|
-
|
|
2911
|
+
logger7.info("Subgraph catch-up progress", {
|
|
2834
2912
|
subgraph: subgraphName,
|
|
2835
2913
|
processed,
|
|
2836
2914
|
total: totalBlocks,
|
|
@@ -2846,7 +2924,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
2846
2924
|
currentHeight = batchEnd + 1;
|
|
2847
2925
|
}
|
|
2848
2926
|
await stats.flush(targetDb);
|
|
2849
|
-
|
|
2927
|
+
logger7.info("Subgraph catch-up complete", {
|
|
2850
2928
|
subgraph: subgraphName,
|
|
2851
2929
|
processed
|
|
2852
2930
|
});
|
|
@@ -2862,5 +2940,5 @@ export {
|
|
|
2862
2940
|
bumpReorgEpoch
|
|
2863
2941
|
};
|
|
2864
2942
|
|
|
2865
|
-
//# debugId=
|
|
2943
|
+
//# debugId=05204354946D588C64756E2164756E21
|
|
2866
2944
|
//# sourceMappingURL=catchup.js.map
|