@secondlayer/subgraphs 3.26.1 → 3.26.2
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 +339 -68
- package/dist/src/index.js.map +6 -4
- package/dist/src/runtime/catchup.d.ts +34 -1
- package/dist/src/runtime/catchup.js +14 -3
- package/dist/src/runtime/catchup.js.map +3 -3
- package/dist/src/runtime/processor.js +316 -305
- package/dist/src/runtime/processor.js.map +6 -6
- package/dist/src/runtime/reindex.js +314 -43
- package/dist/src/runtime/reindex.js.map +6 -4
- package/dist/src/runtime/reorg.js +14 -5
- package/dist/src/runtime/reorg.js.map +3 -3
- package/dist/src/service.js +318 -307
- package/dist/src/service.js.map +6 -6
- package/package.json +1 -1
|
@@ -3044,9 +3044,278 @@ class StatsAccumulator {
|
|
|
3044
3044
|
}
|
|
3045
3045
|
}
|
|
3046
3046
|
|
|
3047
|
+
// src/runtime/catchup.ts
|
|
3048
|
+
import { getTargetDb as getTargetDb2, sql as sql4 } from "@secondlayer/shared/db";
|
|
3049
|
+
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
3050
|
+
import { logger as logger8 } from "@secondlayer/shared/logger";
|
|
3051
|
+
|
|
3052
|
+
// src/runtime/catchup-leader.ts
|
|
3053
|
+
import {
|
|
3054
|
+
SUBGRAPH_CATCHUP_LOCK_KEY,
|
|
3055
|
+
createPostgresLeaderBackend,
|
|
3056
|
+
withLeaderLock
|
|
3057
|
+
} from "@secondlayer/shared/leader";
|
|
3058
|
+
import { targetListenerUrl } from "@secondlayer/shared/queue/listener";
|
|
3059
|
+
var catchUpLeader = false;
|
|
3060
|
+
function isCatchUpLeader() {
|
|
3061
|
+
return catchUpLeader;
|
|
3062
|
+
}
|
|
3063
|
+
function startCatchUpLeader(opts = {}) {
|
|
3064
|
+
return withLeaderLock(SUBGRAPH_CATCHUP_LOCK_KEY, async () => {
|
|
3065
|
+
catchUpLeader = true;
|
|
3066
|
+
await opts.onAcquire?.();
|
|
3067
|
+
return () => {
|
|
3068
|
+
catchUpLeader = false;
|
|
3069
|
+
};
|
|
3070
|
+
}, {
|
|
3071
|
+
pollMs: opts.pollMs,
|
|
3072
|
+
heartbeatMs: opts.heartbeatMs,
|
|
3073
|
+
createBackend: opts.createBackend ?? (() => createPostgresLeaderBackend(targetListenerUrl()))
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
// src/runtime/catchup.ts
|
|
3078
|
+
var LOG_INTERVAL = 1000;
|
|
3079
|
+
var STANDARD_CATCHUP_BATCH_CONFIG = {
|
|
3080
|
+
defaultBatchSize: 500,
|
|
3081
|
+
minBatchSize: 100,
|
|
3082
|
+
maxBatchSize: 1000,
|
|
3083
|
+
prefetch: true
|
|
3084
|
+
};
|
|
3085
|
+
var catchingUp = new Set;
|
|
3086
|
+
var subgraphBlockLockHeld = new Set;
|
|
3087
|
+
var subgraphBlockLockWaiters = new Map;
|
|
3088
|
+
var reorgEpoch = new Map;
|
|
3089
|
+
async function acquireSubgraphBlockLock(name) {
|
|
3090
|
+
if (!subgraphBlockLockHeld.has(name)) {
|
|
3091
|
+
subgraphBlockLockHeld.add(name);
|
|
3092
|
+
return () => releaseSubgraphBlockLock(name);
|
|
3093
|
+
}
|
|
3094
|
+
return new Promise((resolve) => {
|
|
3095
|
+
const waiters = subgraphBlockLockWaiters.get(name) ?? [];
|
|
3096
|
+
waiters.push(() => resolve(() => releaseSubgraphBlockLock(name)));
|
|
3097
|
+
subgraphBlockLockWaiters.set(name, waiters);
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
function releaseSubgraphBlockLock(name) {
|
|
3101
|
+
const waiters = subgraphBlockLockWaiters.get(name);
|
|
3102
|
+
if (waiters && waiters.length > 0) {
|
|
3103
|
+
const next = waiters.shift();
|
|
3104
|
+
if (waiters.length === 0)
|
|
3105
|
+
subgraphBlockLockWaiters.delete(name);
|
|
3106
|
+
next();
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
subgraphBlockLockHeld.delete(name);
|
|
3110
|
+
}
|
|
3111
|
+
async function withSubgraphBlockLock(name, fn) {
|
|
3112
|
+
const release = await acquireSubgraphBlockLock(name);
|
|
3113
|
+
try {
|
|
3114
|
+
return await fn();
|
|
3115
|
+
} finally {
|
|
3116
|
+
release();
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
async function withSubgraphBlockAdvisoryLock(name, fn) {
|
|
3120
|
+
return getTargetDb2().transaction().execute(async (lockTx) => {
|
|
3121
|
+
await sql4`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-block:${name}`}))`.execute(lockTx);
|
|
3122
|
+
return fn();
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
async function withSubgraphBlockWriteLock(name, fn) {
|
|
3126
|
+
return withSubgraphBlockLock(name, () => withSubgraphBlockAdvisoryLock(name, fn));
|
|
3127
|
+
}
|
|
3128
|
+
function bumpReorgEpoch(name) {
|
|
3129
|
+
reorgEpoch.set(name, (reorgEpoch.get(name) ?? 0) + 1);
|
|
3130
|
+
}
|
|
3131
|
+
function getReorgEpoch(name) {
|
|
3132
|
+
return reorgEpoch.get(name) ?? 0;
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
class ReorgEpochChangedError extends Error {
|
|
3136
|
+
constructor(name) {
|
|
3137
|
+
super(`reorg epoch changed for ${name} mid catch-up`);
|
|
3138
|
+
this.name = "ReorgEpochChangedError";
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
function parsePositiveInt(value) {
|
|
3142
|
+
if (value == null || value.trim() === "")
|
|
3143
|
+
return;
|
|
3144
|
+
const parsed = Number.parseInt(value, 10);
|
|
3145
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
3146
|
+
}
|
|
3147
|
+
function parseBoolean(value) {
|
|
3148
|
+
if (value == null || value.trim() === "")
|
|
3149
|
+
return;
|
|
3150
|
+
const normalized = value.trim().toLowerCase();
|
|
3151
|
+
if (normalized === "true")
|
|
3152
|
+
return true;
|
|
3153
|
+
if (normalized === "false")
|
|
3154
|
+
return false;
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
function resolveCatchupBatchConfig(env = process.env) {
|
|
3158
|
+
const base = STANDARD_CATCHUP_BATCH_CONFIG;
|
|
3159
|
+
const minBatchSize = parsePositiveInt(env.SUBGRAPH_CATCHUP_MIN_BATCH_SIZE) ?? base.minBatchSize;
|
|
3160
|
+
const maxBatchSize = parsePositiveInt(env.SUBGRAPH_CATCHUP_MAX_BATCH_SIZE) ?? base.maxBatchSize;
|
|
3161
|
+
const defaultBatchSize = parsePositiveInt(env.SUBGRAPH_CATCHUP_BATCH_SIZE) ?? base.defaultBatchSize;
|
|
3162
|
+
return {
|
|
3163
|
+
minBatchSize,
|
|
3164
|
+
maxBatchSize,
|
|
3165
|
+
defaultBatchSize: Math.min(Math.max(defaultBatchSize, minBatchSize), maxBatchSize),
|
|
3166
|
+
prefetch: parseBoolean(env.SUBGRAPH_CATCHUP_PREFETCH) ?? base.prefetch
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
function adjustBatchSize(current, avgEvents, config) {
|
|
3170
|
+
if (avgEvents > 50)
|
|
3171
|
+
return Math.max(Math.round(current * 0.5), config.minBatchSize);
|
|
3172
|
+
if (avgEvents < 10)
|
|
3173
|
+
return Math.min(Math.round(current * 1.5), config.maxBatchSize);
|
|
3174
|
+
return current;
|
|
3175
|
+
}
|
|
3176
|
+
async function catchUpSubgraph(subgraph, subgraphName) {
|
|
3177
|
+
if (catchingUp.has(subgraphName))
|
|
3178
|
+
return 0;
|
|
3179
|
+
catchingUp.add(subgraphName);
|
|
3180
|
+
try {
|
|
3181
|
+
const source = resolveBlockSource(subgraph);
|
|
3182
|
+
const targetDb = getTargetDb2();
|
|
3183
|
+
const subgraphRow = await getSubgraph(targetDb, subgraphName);
|
|
3184
|
+
if (!subgraphRow)
|
|
3185
|
+
return 0;
|
|
3186
|
+
const lastProcessedBlock = Number(subgraphRow.last_processed_block);
|
|
3187
|
+
const chainTip = await source.getTip();
|
|
3188
|
+
if (chainTip <= 0 || lastProcessedBlock >= chainTip)
|
|
3189
|
+
return 0;
|
|
3190
|
+
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
3191
|
+
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
3192
|
+
const totalBlocks = chainTip - lastProcessedBlock;
|
|
3193
|
+
logger8.info("Subgraph catch-up starting", {
|
|
3194
|
+
subgraph: subgraphName,
|
|
3195
|
+
from: startBlock,
|
|
3196
|
+
to: chainTip,
|
|
3197
|
+
blocks: totalBlocks
|
|
3198
|
+
});
|
|
3199
|
+
const stats = new StatsAccumulator(subgraphName, true);
|
|
3200
|
+
let processed = 0;
|
|
3201
|
+
const batchConfig = resolveCatchupBatchConfig();
|
|
3202
|
+
let batchSize = batchConfig.defaultBatchSize;
|
|
3203
|
+
let currentHeight = startBlock;
|
|
3204
|
+
const walkEpoch = getReorgEpoch(subgraphName);
|
|
3205
|
+
let prefetchedBatchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
3206
|
+
let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
|
|
3207
|
+
while (currentHeight <= chainTip) {
|
|
3208
|
+
if (!isCatchUpLeader()) {
|
|
3209
|
+
logger8.info("Lost catch-up leadership, stopping catch-up", {
|
|
3210
|
+
subgraph: subgraphName,
|
|
3211
|
+
processed
|
|
3212
|
+
});
|
|
3213
|
+
break;
|
|
3214
|
+
}
|
|
3215
|
+
const currentRow = await getSubgraph(targetDb, subgraphName);
|
|
3216
|
+
if (!currentRow || currentRow.status !== "active") {
|
|
3217
|
+
logger8.info("Subgraph status changed, stopping catch-up", {
|
|
3218
|
+
subgraph: subgraphName,
|
|
3219
|
+
status: currentRow?.status ?? "deleted"
|
|
3220
|
+
});
|
|
3221
|
+
break;
|
|
3222
|
+
}
|
|
3223
|
+
let batchEnd;
|
|
3224
|
+
let batch;
|
|
3225
|
+
if (nextBatchPromise) {
|
|
3226
|
+
batch = await nextBatchPromise;
|
|
3227
|
+
batchEnd = prefetchedBatchEnd;
|
|
3228
|
+
const nextStart = batchEnd + 1;
|
|
3229
|
+
if (nextStart <= chainTip) {
|
|
3230
|
+
prefetchedBatchEnd = Math.min(nextStart + batchSize - 1, chainTip);
|
|
3231
|
+
nextBatchPromise = source.loadBlockRange(nextStart, prefetchedBatchEnd);
|
|
3232
|
+
} else {
|
|
3233
|
+
nextBatchPromise = undefined;
|
|
3234
|
+
}
|
|
3235
|
+
} else {
|
|
3236
|
+
batchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
3237
|
+
batch = await source.loadBlockRange(currentHeight, batchEnd);
|
|
3238
|
+
}
|
|
3239
|
+
let stopCatchup = false;
|
|
3240
|
+
for (let height = currentHeight;height <= batchEnd; height++) {
|
|
3241
|
+
let blockData = batch.get(height);
|
|
3242
|
+
if (!blockData) {
|
|
3243
|
+
blockData = (await source.loadBlockRange(height, height)).get(height);
|
|
3244
|
+
}
|
|
3245
|
+
if (!blockData) {
|
|
3246
|
+
logger8.warn("Block missing during catch-up, deferring to next tick", {
|
|
3247
|
+
subgraph: subgraphName,
|
|
3248
|
+
blockHeight: height
|
|
3249
|
+
});
|
|
3250
|
+
stopCatchup = true;
|
|
3251
|
+
break;
|
|
3252
|
+
}
|
|
3253
|
+
const preloaded = blockData;
|
|
3254
|
+
let result;
|
|
3255
|
+
try {
|
|
3256
|
+
result = await withSubgraphBlockWriteLock(subgraphName, async () => {
|
|
3257
|
+
if (getReorgEpoch(subgraphName) !== walkEpoch) {
|
|
3258
|
+
throw new ReorgEpochChangedError(subgraphName);
|
|
3259
|
+
}
|
|
3260
|
+
return processBlockWithRetry(subgraph, subgraphName, height, {
|
|
3261
|
+
preloaded
|
|
3262
|
+
});
|
|
3263
|
+
});
|
|
3264
|
+
} catch (err) {
|
|
3265
|
+
if (err instanceof ReorgEpochChangedError) {
|
|
3266
|
+
logger8.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
|
|
3267
|
+
stopCatchup = true;
|
|
3268
|
+
break;
|
|
3269
|
+
}
|
|
3270
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3271
|
+
logger8.error("Block processing failed persistently during catch-up", {
|
|
3272
|
+
subgraph: subgraphName,
|
|
3273
|
+
blockHeight: height,
|
|
3274
|
+
error: errorMsg
|
|
3275
|
+
});
|
|
3276
|
+
const { updateSubgraphStatus: updateSubgraphStatus2, recordSubgraphProcessed: recordSubgraphProcessed2 } = await import("@secondlayer/shared/db/queries/subgraphs");
|
|
3277
|
+
await recordSubgraphProcessed2(targetDb, subgraphName, 0, 1, `catch-up halted at block ${height}: ${errorMsg}`).catch(() => {});
|
|
3278
|
+
await updateSubgraphStatus2(targetDb, subgraphName, "error").catch(() => {});
|
|
3279
|
+
stopCatchup = true;
|
|
3280
|
+
break;
|
|
3281
|
+
}
|
|
3282
|
+
processed++;
|
|
3283
|
+
if (result.timing) {
|
|
3284
|
+
stats.record(result.timing, result.processed);
|
|
3285
|
+
if (stats.shouldFlush()) {
|
|
3286
|
+
await stats.flush(targetDb);
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
if (processed % LOG_INTERVAL === 0) {
|
|
3290
|
+
logger8.info("Subgraph catch-up progress", {
|
|
3291
|
+
subgraph: subgraphName,
|
|
3292
|
+
processed,
|
|
3293
|
+
total: totalBlocks,
|
|
3294
|
+
currentBlock: height,
|
|
3295
|
+
pct: Math.round(processed / totalBlocks * 100)
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
if (stopCatchup)
|
|
3300
|
+
break;
|
|
3301
|
+
const avg = avgEventsPerBlock(batch);
|
|
3302
|
+
batchSize = adjustBatchSize(batchSize, avg, batchConfig);
|
|
3303
|
+
currentHeight = batchEnd + 1;
|
|
3304
|
+
}
|
|
3305
|
+
await stats.flush(targetDb);
|
|
3306
|
+
logger8.info("Subgraph catch-up complete", {
|
|
3307
|
+
subgraph: subgraphName,
|
|
3308
|
+
processed
|
|
3309
|
+
});
|
|
3310
|
+
return processed;
|
|
3311
|
+
} finally {
|
|
3312
|
+
catchingUp.delete(subgraphName);
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3047
3316
|
// src/runtime/reindex.ts
|
|
3048
3317
|
import { getErrorMessage as getErrorMessage3 } from "@secondlayer/shared";
|
|
3049
|
-
import { getRawClient, getTargetDb as
|
|
3318
|
+
import { getRawClient, getTargetDb as getTargetDb3 } from "@secondlayer/shared/db";
|
|
3050
3319
|
import {
|
|
3051
3320
|
recordGapBatch,
|
|
3052
3321
|
resolveGaps
|
|
@@ -3059,11 +3328,11 @@ import {
|
|
|
3059
3328
|
recordSubgraphProcessed as recordSubgraphProcessed2,
|
|
3060
3329
|
updateSubgraphStatus as updateSubgraphStatus2
|
|
3061
3330
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
3062
|
-
import { logger as
|
|
3331
|
+
import { logger as logger10 } from "@secondlayer/shared/logger";
|
|
3063
3332
|
|
|
3064
3333
|
// src/runtime/reindex-notify.ts
|
|
3065
3334
|
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
3066
|
-
import { logger as
|
|
3335
|
+
import { logger as logger9 } from "@secondlayer/shared/logger";
|
|
3067
3336
|
async function notifyReindexComplete(db, subgraphName, stats) {
|
|
3068
3337
|
try {
|
|
3069
3338
|
const subgraph = await db.selectFrom("subgraphs").select(["account_id"]).where("name", "=", subgraphName).executeTakeFirst();
|
|
@@ -3074,7 +3343,7 @@ async function notifyReindexComplete(db, subgraphName, stats) {
|
|
|
3074
3343
|
return;
|
|
3075
3344
|
const resendKey = process.env.RESEND_API_KEY;
|
|
3076
3345
|
if (!resendKey) {
|
|
3077
|
-
|
|
3346
|
+
logger9.warn("RESEND_API_KEY unset — skipping reindex-complete email", {
|
|
3078
3347
|
subgraph: subgraphName
|
|
3079
3348
|
});
|
|
3080
3349
|
return;
|
|
@@ -3096,14 +3365,14 @@ async function notifyReindexComplete(db, subgraphName, stats) {
|
|
|
3096
3365
|
});
|
|
3097
3366
|
if (!res.ok) {
|
|
3098
3367
|
const text = await res.text().catch(() => "");
|
|
3099
|
-
|
|
3368
|
+
logger9.warn("reindex-complete email failed", {
|
|
3100
3369
|
subgraph: subgraphName,
|
|
3101
3370
|
status: res.status,
|
|
3102
3371
|
body: text.slice(0, 200)
|
|
3103
3372
|
});
|
|
3104
3373
|
}
|
|
3105
3374
|
} catch (err) {
|
|
3106
|
-
|
|
3375
|
+
logger9.warn("reindex-complete email threw", {
|
|
3107
3376
|
subgraph: subgraphName,
|
|
3108
3377
|
error: getErrorMessage2(err)
|
|
3109
3378
|
});
|
|
@@ -3111,7 +3380,7 @@ async function notifyReindexComplete(db, subgraphName, stats) {
|
|
|
3111
3380
|
}
|
|
3112
3381
|
|
|
3113
3382
|
// src/runtime/reindex.ts
|
|
3114
|
-
var
|
|
3383
|
+
var LOG_INTERVAL2 = 1000;
|
|
3115
3384
|
var HEALTH_FLUSH_INTERVAL = 1000;
|
|
3116
3385
|
var PROGRESS_FLUSH_INTERVAL_MS = 5000;
|
|
3117
3386
|
var EMPTY_BATCH_HALT_THRESHOLD = 3;
|
|
@@ -3131,7 +3400,7 @@ function resolveReindexResumeBlock(row) {
|
|
|
3131
3400
|
const reindexFromBlock = Number(row.reindex_from_block);
|
|
3132
3401
|
return Math.max(lastProcessedBlock + 1, reindexFromBlock);
|
|
3133
3402
|
}
|
|
3134
|
-
function
|
|
3403
|
+
function parsePositiveInt2(value) {
|
|
3135
3404
|
if (value == null || value.trim() === "")
|
|
3136
3405
|
return;
|
|
3137
3406
|
const parsed = Number.parseInt(value, 10);
|
|
@@ -3139,9 +3408,9 @@ function parsePositiveInt(value) {
|
|
|
3139
3408
|
}
|
|
3140
3409
|
function resolveReindexBatchConfig(env = process.env) {
|
|
3141
3410
|
const base = STANDARD_REINDEX_BATCH_CONFIG;
|
|
3142
|
-
const minBatchSize =
|
|
3143
|
-
const maxBatchSize =
|
|
3144
|
-
const defaultBatchSize =
|
|
3411
|
+
const minBatchSize = parsePositiveInt2(env.SUBGRAPH_REINDEX_MIN_BATCH_SIZE) ?? base.minBatchSize;
|
|
3412
|
+
const maxBatchSize = parsePositiveInt2(env.SUBGRAPH_REINDEX_MAX_BATCH_SIZE) ?? base.maxBatchSize;
|
|
3413
|
+
const defaultBatchSize = parsePositiveInt2(env.SUBGRAPH_REINDEX_BATCH_SIZE) ?? base.defaultBatchSize;
|
|
3145
3414
|
return {
|
|
3146
3415
|
minBatchSize,
|
|
3147
3416
|
maxBatchSize,
|
|
@@ -3172,7 +3441,7 @@ function coalesceFailedBlocks(blocks) {
|
|
|
3172
3441
|
}
|
|
3173
3442
|
async function processBlockRange(def, opts) {
|
|
3174
3443
|
const source = resolveBlockSource(def);
|
|
3175
|
-
const targetDb =
|
|
3444
|
+
const targetDb = getTargetDb3();
|
|
3176
3445
|
const subgraphName = def.name;
|
|
3177
3446
|
const { fromBlock, toBlock, status } = opts;
|
|
3178
3447
|
const totalBlocks = toBlock - fromBlock + 1;
|
|
@@ -3215,7 +3484,7 @@ async function processBlockRange(def, opts) {
|
|
|
3215
3484
|
while (currentHeight <= toBlock) {
|
|
3216
3485
|
if (opts.signal?.aborted) {
|
|
3217
3486
|
aborted = true;
|
|
3218
|
-
|
|
3487
|
+
logger10.info("Block processing aborted", {
|
|
3219
3488
|
subgraph: subgraphName,
|
|
3220
3489
|
currentBlock: currentHeight,
|
|
3221
3490
|
reason: String(opts.signal.reason ?? "unknown")
|
|
@@ -3257,14 +3526,16 @@ async function processBlockRange(def, opts) {
|
|
|
3257
3526
|
}
|
|
3258
3527
|
let result;
|
|
3259
3528
|
try {
|
|
3260
|
-
|
|
3529
|
+
const mayWrite = blockData.txs.length > 0 || blockData.events.length > 0;
|
|
3530
|
+
const runBlock = () => processBlockWithRetry(def, subgraphName, height, {
|
|
3261
3531
|
skipProgressUpdate: true,
|
|
3262
3532
|
atomicProgress,
|
|
3263
3533
|
preloaded: blockData
|
|
3264
3534
|
});
|
|
3535
|
+
result = mayWrite ? await withSubgraphBlockWriteLock(subgraphName, runBlock) : await runBlock();
|
|
3265
3536
|
} catch (err) {
|
|
3266
3537
|
const errorMsg = getErrorMessage3(err);
|
|
3267
|
-
|
|
3538
|
+
logger10.error("Block processing failed persistently", {
|
|
3268
3539
|
subgraph: subgraphName,
|
|
3269
3540
|
blockHeight: height,
|
|
3270
3541
|
error: errorMsg
|
|
@@ -3309,8 +3580,8 @@ async function processBlockRange(def, opts) {
|
|
|
3309
3580
|
}
|
|
3310
3581
|
lastProgressFlushAt = now;
|
|
3311
3582
|
}
|
|
3312
|
-
if (blocksProcessed %
|
|
3313
|
-
|
|
3583
|
+
if (blocksProcessed % LOG_INTERVAL2 === 0) {
|
|
3584
|
+
logger10.info(`${status === "reindexing" ? "Reindex" : "Backfill"} progress`, {
|
|
3314
3585
|
subgraph: subgraphName,
|
|
3315
3586
|
processed: blocksProcessed,
|
|
3316
3587
|
total: totalBlocks,
|
|
@@ -3330,7 +3601,7 @@ async function processBlockRange(def, opts) {
|
|
|
3330
3601
|
if (batchFailedBlocks.length > 0 && opts.subgraphId) {
|
|
3331
3602
|
const gaps = coalesceFailedBlocks(batchFailedBlocks);
|
|
3332
3603
|
await recordGapBatch(targetDb, opts.subgraphId, subgraphName, gaps).catch((err) => {
|
|
3333
|
-
|
|
3604
|
+
logger10.warn("Failed to record subgraph gaps", {
|
|
3334
3605
|
subgraph: subgraphName,
|
|
3335
3606
|
error: err instanceof Error ? err.message : String(err)
|
|
3336
3607
|
});
|
|
@@ -3347,7 +3618,7 @@ async function processBlockRange(def, opts) {
|
|
|
3347
3618
|
} else {
|
|
3348
3619
|
await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
|
|
3349
3620
|
}
|
|
3350
|
-
|
|
3621
|
+
logger10.info("Sparse skip", {
|
|
3351
3622
|
subgraph: subgraphName,
|
|
3352
3623
|
from: batchEnd + 1,
|
|
3353
3624
|
to: jumpTo - 1,
|
|
@@ -3382,16 +3653,16 @@ async function clearReindexMetadata(db, subgraphName) {
|
|
|
3382
3653
|
}
|
|
3383
3654
|
async function reindexSubgraph(def, opts) {
|
|
3384
3655
|
const source = resolveBlockSource(def);
|
|
3385
|
-
const targetDb =
|
|
3656
|
+
const targetDb = getTargetDb3();
|
|
3386
3657
|
const client = getRawClient("target");
|
|
3387
3658
|
const subgraphName = def.name;
|
|
3388
3659
|
const schemaName = opts?.schemaName ?? pgSchemaName(subgraphName);
|
|
3389
3660
|
await updateSubgraphStatus2(targetDb, subgraphName, "reindexing");
|
|
3390
|
-
|
|
3661
|
+
logger10.info("Reindex starting", { subgraph: subgraphName });
|
|
3391
3662
|
try {
|
|
3392
3663
|
const { fromBlock, toBlock } = await resolveBlockRange(source, def, opts);
|
|
3393
3664
|
if (fromBlock > toBlock) {
|
|
3394
|
-
|
|
3665
|
+
logger10.info("No blocks to reindex", {
|
|
3395
3666
|
subgraph: subgraphName,
|
|
3396
3667
|
fromBlock,
|
|
3397
3668
|
toBlock
|
|
@@ -3405,7 +3676,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3405
3676
|
await client.unsafe(stmt);
|
|
3406
3677
|
}
|
|
3407
3678
|
await updateSubgraphStatus2(targetDb, subgraphName, "reindexing", Math.max(0, fromBlock - 1));
|
|
3408
|
-
|
|
3679
|
+
logger10.info("Schema recreated for reindex", {
|
|
3409
3680
|
subgraph: subgraphName,
|
|
3410
3681
|
cursorResetTo: Math.max(0, fromBlock - 1)
|
|
3411
3682
|
});
|
|
@@ -3419,7 +3690,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3419
3690
|
last_error_at: null,
|
|
3420
3691
|
updated_at: new Date
|
|
3421
3692
|
}).where("name", "=", subgraphName).execute();
|
|
3422
|
-
|
|
3693
|
+
logger10.info("Reindexing blocks", {
|
|
3423
3694
|
subgraph: subgraphName,
|
|
3424
3695
|
fromBlock,
|
|
3425
3696
|
toBlock,
|
|
@@ -3441,9 +3712,9 @@ async function reindexSubgraph(def, opts) {
|
|
|
3441
3712
|
if (reason === "user-cancelled") {
|
|
3442
3713
|
await updateSubgraphStatus2(targetDb, subgraphName, "active");
|
|
3443
3714
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3444
|
-
|
|
3715
|
+
logger10.info("Reindex cancelled by user", { subgraph: subgraphName });
|
|
3445
3716
|
} else {
|
|
3446
|
-
|
|
3717
|
+
logger10.info("Reindex interrupted by shutdown, will resume", {
|
|
3447
3718
|
subgraph: subgraphName
|
|
3448
3719
|
});
|
|
3449
3720
|
}
|
|
@@ -3451,7 +3722,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3451
3722
|
}
|
|
3452
3723
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
3453
3724
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3454
|
-
|
|
3725
|
+
logger10.info("Reindex complete", {
|
|
3455
3726
|
subgraph: subgraphName,
|
|
3456
3727
|
blocks: result.blocksProcessed,
|
|
3457
3728
|
events: result.totalEventsProcessed,
|
|
@@ -3464,7 +3735,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3464
3735
|
});
|
|
3465
3736
|
return { processed: result.blocksProcessed };
|
|
3466
3737
|
} catch (err) {
|
|
3467
|
-
|
|
3738
|
+
logger10.error("Reindex failed", {
|
|
3468
3739
|
subgraph: subgraphName,
|
|
3469
3740
|
error: getErrorMessage3(err)
|
|
3470
3741
|
});
|
|
@@ -3473,7 +3744,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3473
3744
|
}
|
|
3474
3745
|
}
|
|
3475
3746
|
async function resumeReindex(def, opts) {
|
|
3476
|
-
const targetDb =
|
|
3747
|
+
const targetDb = getTargetDb3();
|
|
3477
3748
|
const subgraphName = def.name;
|
|
3478
3749
|
const row = await targetDb.selectFrom("subgraphs").select([
|
|
3479
3750
|
"id",
|
|
@@ -3486,7 +3757,7 @@ async function resumeReindex(def, opts) {
|
|
|
3486
3757
|
const fromBlock = resolveReindexResumeBlock(row);
|
|
3487
3758
|
const toBlock = Number(row.reindex_to_block);
|
|
3488
3759
|
if (fromBlock == null) {
|
|
3489
|
-
|
|
3760
|
+
logger10.info("No reindex metadata, starting fresh reindex", {
|
|
3490
3761
|
subgraph: subgraphName
|
|
3491
3762
|
});
|
|
3492
3763
|
return reindexSubgraph(def, {
|
|
@@ -3495,12 +3766,12 @@ async function resumeReindex(def, opts) {
|
|
|
3495
3766
|
});
|
|
3496
3767
|
}
|
|
3497
3768
|
if (fromBlock > toBlock) {
|
|
3498
|
-
|
|
3769
|
+
logger10.info("Resume: no remaining blocks", { subgraph: subgraphName });
|
|
3499
3770
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
3500
3771
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3501
3772
|
return { processed: 0 };
|
|
3502
3773
|
}
|
|
3503
|
-
|
|
3774
|
+
logger10.info("Resuming reindex", {
|
|
3504
3775
|
subgraph: subgraphName,
|
|
3505
3776
|
fromBlock,
|
|
3506
3777
|
toBlock,
|
|
@@ -3522,9 +3793,9 @@ async function resumeReindex(def, opts) {
|
|
|
3522
3793
|
if (reason === "user-cancelled") {
|
|
3523
3794
|
await updateSubgraphStatus2(targetDb, subgraphName, "active");
|
|
3524
3795
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3525
|
-
|
|
3796
|
+
logger10.info("Resume cancelled by user", { subgraph: subgraphName });
|
|
3526
3797
|
} else {
|
|
3527
|
-
|
|
3798
|
+
logger10.info("Resume interrupted by shutdown, will resume again", {
|
|
3528
3799
|
subgraph: subgraphName
|
|
3529
3800
|
});
|
|
3530
3801
|
}
|
|
@@ -3532,13 +3803,13 @@ async function resumeReindex(def, opts) {
|
|
|
3532
3803
|
}
|
|
3533
3804
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
3534
3805
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3535
|
-
|
|
3806
|
+
logger10.info("Resumed reindex complete", {
|
|
3536
3807
|
subgraph: subgraphName,
|
|
3537
3808
|
blocks: result.blocksProcessed
|
|
3538
3809
|
});
|
|
3539
3810
|
return { processed: result.blocksProcessed };
|
|
3540
3811
|
} catch (err) {
|
|
3541
|
-
|
|
3812
|
+
logger10.error("Resumed reindex failed", {
|
|
3542
3813
|
subgraph: subgraphName,
|
|
3543
3814
|
error: getErrorMessage3(err)
|
|
3544
3815
|
});
|
|
@@ -3547,9 +3818,9 @@ async function resumeReindex(def, opts) {
|
|
|
3547
3818
|
}
|
|
3548
3819
|
}
|
|
3549
3820
|
async function backfillSubgraph(def, opts) {
|
|
3550
|
-
const targetDb =
|
|
3821
|
+
const targetDb = getTargetDb3();
|
|
3551
3822
|
const subgraphName = def.name;
|
|
3552
|
-
|
|
3823
|
+
logger10.info("Backfill starting", {
|
|
3553
3824
|
subgraph: subgraphName,
|
|
3554
3825
|
from: opts.fromBlock,
|
|
3555
3826
|
to: opts.toBlock
|
|
@@ -3567,17 +3838,17 @@ async function backfillSubgraph(def, opts) {
|
|
|
3567
3838
|
signal: opts.signal
|
|
3568
3839
|
});
|
|
3569
3840
|
if (result.aborted) {
|
|
3570
|
-
|
|
3841
|
+
logger10.info("Backfill aborted", { subgraph: subgraphName });
|
|
3571
3842
|
return { processed: result.blocksProcessed };
|
|
3572
3843
|
}
|
|
3573
3844
|
const resolved = await resolveGaps(targetDb, subgraphName, opts.fromBlock, opts.toBlock).catch(() => 0);
|
|
3574
3845
|
if (resolved > 0) {
|
|
3575
|
-
|
|
3846
|
+
logger10.info("Resolved subgraph gaps via backfill", {
|
|
3576
3847
|
subgraph: subgraphName,
|
|
3577
3848
|
resolved
|
|
3578
3849
|
});
|
|
3579
3850
|
}
|
|
3580
|
-
|
|
3851
|
+
logger10.info("Backfill complete", {
|
|
3581
3852
|
subgraph: subgraphName,
|
|
3582
3853
|
blocks: result.blocksProcessed,
|
|
3583
3854
|
events: result.totalEventsProcessed,
|
|
@@ -3585,7 +3856,7 @@ async function backfillSubgraph(def, opts) {
|
|
|
3585
3856
|
});
|
|
3586
3857
|
return { processed: result.blocksProcessed };
|
|
3587
3858
|
} catch (err) {
|
|
3588
|
-
|
|
3859
|
+
logger10.error("Backfill failed", {
|
|
3589
3860
|
subgraph: subgraphName,
|
|
3590
3861
|
error: getErrorMessage3(err)
|
|
3591
3862
|
});
|
|
@@ -3593,266 +3864,6 @@ async function backfillSubgraph(def, opts) {
|
|
|
3593
3864
|
}
|
|
3594
3865
|
}
|
|
3595
3866
|
|
|
3596
|
-
// src/runtime/catchup.ts
|
|
3597
|
-
import { getTargetDb as getTargetDb3 } from "@secondlayer/shared/db";
|
|
3598
|
-
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
3599
|
-
import { logger as logger10 } from "@secondlayer/shared/logger";
|
|
3600
|
-
|
|
3601
|
-
// src/runtime/catchup-leader.ts
|
|
3602
|
-
import {
|
|
3603
|
-
SUBGRAPH_CATCHUP_LOCK_KEY,
|
|
3604
|
-
createPostgresLeaderBackend,
|
|
3605
|
-
withLeaderLock
|
|
3606
|
-
} from "@secondlayer/shared/leader";
|
|
3607
|
-
import { targetListenerUrl } from "@secondlayer/shared/queue/listener";
|
|
3608
|
-
var catchUpLeader = false;
|
|
3609
|
-
function isCatchUpLeader() {
|
|
3610
|
-
return catchUpLeader;
|
|
3611
|
-
}
|
|
3612
|
-
function startCatchUpLeader(opts = {}) {
|
|
3613
|
-
return withLeaderLock(SUBGRAPH_CATCHUP_LOCK_KEY, async () => {
|
|
3614
|
-
catchUpLeader = true;
|
|
3615
|
-
await opts.onAcquire?.();
|
|
3616
|
-
return () => {
|
|
3617
|
-
catchUpLeader = false;
|
|
3618
|
-
};
|
|
3619
|
-
}, {
|
|
3620
|
-
pollMs: opts.pollMs,
|
|
3621
|
-
heartbeatMs: opts.heartbeatMs,
|
|
3622
|
-
createBackend: opts.createBackend ?? (() => createPostgresLeaderBackend(targetListenerUrl()))
|
|
3623
|
-
});
|
|
3624
|
-
}
|
|
3625
|
-
|
|
3626
|
-
// src/runtime/catchup.ts
|
|
3627
|
-
var LOG_INTERVAL2 = 1000;
|
|
3628
|
-
var STANDARD_CATCHUP_BATCH_CONFIG = {
|
|
3629
|
-
defaultBatchSize: 500,
|
|
3630
|
-
minBatchSize: 100,
|
|
3631
|
-
maxBatchSize: 1000,
|
|
3632
|
-
prefetch: true
|
|
3633
|
-
};
|
|
3634
|
-
var catchingUp = new Set;
|
|
3635
|
-
var subgraphBlockLockHeld = new Set;
|
|
3636
|
-
var subgraphBlockLockWaiters = new Map;
|
|
3637
|
-
var reorgEpoch = new Map;
|
|
3638
|
-
async function acquireSubgraphBlockLock(name) {
|
|
3639
|
-
if (!subgraphBlockLockHeld.has(name)) {
|
|
3640
|
-
subgraphBlockLockHeld.add(name);
|
|
3641
|
-
return () => releaseSubgraphBlockLock(name);
|
|
3642
|
-
}
|
|
3643
|
-
return new Promise((resolve) => {
|
|
3644
|
-
const waiters = subgraphBlockLockWaiters.get(name) ?? [];
|
|
3645
|
-
waiters.push(() => resolve(() => releaseSubgraphBlockLock(name)));
|
|
3646
|
-
subgraphBlockLockWaiters.set(name, waiters);
|
|
3647
|
-
});
|
|
3648
|
-
}
|
|
3649
|
-
function releaseSubgraphBlockLock(name) {
|
|
3650
|
-
const waiters = subgraphBlockLockWaiters.get(name);
|
|
3651
|
-
if (waiters && waiters.length > 0) {
|
|
3652
|
-
const next = waiters.shift();
|
|
3653
|
-
if (waiters.length === 0)
|
|
3654
|
-
subgraphBlockLockWaiters.delete(name);
|
|
3655
|
-
next();
|
|
3656
|
-
return;
|
|
3657
|
-
}
|
|
3658
|
-
subgraphBlockLockHeld.delete(name);
|
|
3659
|
-
}
|
|
3660
|
-
async function withSubgraphBlockLock(name, fn) {
|
|
3661
|
-
const release = await acquireSubgraphBlockLock(name);
|
|
3662
|
-
try {
|
|
3663
|
-
return await fn();
|
|
3664
|
-
} finally {
|
|
3665
|
-
release();
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
function bumpReorgEpoch(name) {
|
|
3669
|
-
reorgEpoch.set(name, (reorgEpoch.get(name) ?? 0) + 1);
|
|
3670
|
-
}
|
|
3671
|
-
function getReorgEpoch(name) {
|
|
3672
|
-
return reorgEpoch.get(name) ?? 0;
|
|
3673
|
-
}
|
|
3674
|
-
|
|
3675
|
-
class ReorgEpochChangedError extends Error {
|
|
3676
|
-
constructor(name) {
|
|
3677
|
-
super(`reorg epoch changed for ${name} mid catch-up`);
|
|
3678
|
-
this.name = "ReorgEpochChangedError";
|
|
3679
|
-
}
|
|
3680
|
-
}
|
|
3681
|
-
function parsePositiveInt2(value) {
|
|
3682
|
-
if (value == null || value.trim() === "")
|
|
3683
|
-
return;
|
|
3684
|
-
const parsed = Number.parseInt(value, 10);
|
|
3685
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
3686
|
-
}
|
|
3687
|
-
function parseBoolean(value) {
|
|
3688
|
-
if (value == null || value.trim() === "")
|
|
3689
|
-
return;
|
|
3690
|
-
const normalized = value.trim().toLowerCase();
|
|
3691
|
-
if (normalized === "true")
|
|
3692
|
-
return true;
|
|
3693
|
-
if (normalized === "false")
|
|
3694
|
-
return false;
|
|
3695
|
-
return;
|
|
3696
|
-
}
|
|
3697
|
-
function resolveCatchupBatchConfig(env = process.env) {
|
|
3698
|
-
const base = STANDARD_CATCHUP_BATCH_CONFIG;
|
|
3699
|
-
const minBatchSize = parsePositiveInt2(env.SUBGRAPH_CATCHUP_MIN_BATCH_SIZE) ?? base.minBatchSize;
|
|
3700
|
-
const maxBatchSize = parsePositiveInt2(env.SUBGRAPH_CATCHUP_MAX_BATCH_SIZE) ?? base.maxBatchSize;
|
|
3701
|
-
const defaultBatchSize = parsePositiveInt2(env.SUBGRAPH_CATCHUP_BATCH_SIZE) ?? base.defaultBatchSize;
|
|
3702
|
-
return {
|
|
3703
|
-
minBatchSize,
|
|
3704
|
-
maxBatchSize,
|
|
3705
|
-
defaultBatchSize: Math.min(Math.max(defaultBatchSize, minBatchSize), maxBatchSize),
|
|
3706
|
-
prefetch: parseBoolean(env.SUBGRAPH_CATCHUP_PREFETCH) ?? base.prefetch
|
|
3707
|
-
};
|
|
3708
|
-
}
|
|
3709
|
-
function adjustBatchSize(current, avgEvents, config) {
|
|
3710
|
-
if (avgEvents > 50)
|
|
3711
|
-
return Math.max(Math.round(current * 0.5), config.minBatchSize);
|
|
3712
|
-
if (avgEvents < 10)
|
|
3713
|
-
return Math.min(Math.round(current * 1.5), config.maxBatchSize);
|
|
3714
|
-
return current;
|
|
3715
|
-
}
|
|
3716
|
-
async function catchUpSubgraph(subgraph, subgraphName) {
|
|
3717
|
-
if (catchingUp.has(subgraphName))
|
|
3718
|
-
return 0;
|
|
3719
|
-
catchingUp.add(subgraphName);
|
|
3720
|
-
try {
|
|
3721
|
-
const source = resolveBlockSource(subgraph);
|
|
3722
|
-
const targetDb = getTargetDb3();
|
|
3723
|
-
const subgraphRow = await getSubgraph(targetDb, subgraphName);
|
|
3724
|
-
if (!subgraphRow)
|
|
3725
|
-
return 0;
|
|
3726
|
-
const lastProcessedBlock = Number(subgraphRow.last_processed_block);
|
|
3727
|
-
const chainTip = await source.getTip();
|
|
3728
|
-
if (chainTip <= 0 || lastProcessedBlock >= chainTip)
|
|
3729
|
-
return 0;
|
|
3730
|
-
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
3731
|
-
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
3732
|
-
const totalBlocks = chainTip - lastProcessedBlock;
|
|
3733
|
-
logger10.info("Subgraph catch-up starting", {
|
|
3734
|
-
subgraph: subgraphName,
|
|
3735
|
-
from: startBlock,
|
|
3736
|
-
to: chainTip,
|
|
3737
|
-
blocks: totalBlocks
|
|
3738
|
-
});
|
|
3739
|
-
const stats = new StatsAccumulator(subgraphName, true);
|
|
3740
|
-
let processed = 0;
|
|
3741
|
-
const batchConfig = resolveCatchupBatchConfig();
|
|
3742
|
-
let batchSize = batchConfig.defaultBatchSize;
|
|
3743
|
-
let currentHeight = startBlock;
|
|
3744
|
-
const walkEpoch = getReorgEpoch(subgraphName);
|
|
3745
|
-
let prefetchedBatchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
3746
|
-
let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
|
|
3747
|
-
while (currentHeight <= chainTip) {
|
|
3748
|
-
if (!isCatchUpLeader()) {
|
|
3749
|
-
logger10.info("Lost catch-up leadership, stopping catch-up", {
|
|
3750
|
-
subgraph: subgraphName,
|
|
3751
|
-
processed
|
|
3752
|
-
});
|
|
3753
|
-
break;
|
|
3754
|
-
}
|
|
3755
|
-
const currentRow = await getSubgraph(targetDb, subgraphName);
|
|
3756
|
-
if (!currentRow || currentRow.status !== "active") {
|
|
3757
|
-
logger10.info("Subgraph status changed, stopping catch-up", {
|
|
3758
|
-
subgraph: subgraphName,
|
|
3759
|
-
status: currentRow?.status ?? "deleted"
|
|
3760
|
-
});
|
|
3761
|
-
break;
|
|
3762
|
-
}
|
|
3763
|
-
let batchEnd;
|
|
3764
|
-
let batch;
|
|
3765
|
-
if (nextBatchPromise) {
|
|
3766
|
-
batch = await nextBatchPromise;
|
|
3767
|
-
batchEnd = prefetchedBatchEnd;
|
|
3768
|
-
const nextStart = batchEnd + 1;
|
|
3769
|
-
if (nextStart <= chainTip) {
|
|
3770
|
-
prefetchedBatchEnd = Math.min(nextStart + batchSize - 1, chainTip);
|
|
3771
|
-
nextBatchPromise = source.loadBlockRange(nextStart, prefetchedBatchEnd);
|
|
3772
|
-
} else {
|
|
3773
|
-
nextBatchPromise = undefined;
|
|
3774
|
-
}
|
|
3775
|
-
} else {
|
|
3776
|
-
batchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
3777
|
-
batch = await source.loadBlockRange(currentHeight, batchEnd);
|
|
3778
|
-
}
|
|
3779
|
-
let stopCatchup = false;
|
|
3780
|
-
for (let height = currentHeight;height <= batchEnd; height++) {
|
|
3781
|
-
let blockData = batch.get(height);
|
|
3782
|
-
if (!blockData) {
|
|
3783
|
-
blockData = (await source.loadBlockRange(height, height)).get(height);
|
|
3784
|
-
}
|
|
3785
|
-
if (!blockData) {
|
|
3786
|
-
logger10.warn("Block missing during catch-up, deferring to next tick", {
|
|
3787
|
-
subgraph: subgraphName,
|
|
3788
|
-
blockHeight: height
|
|
3789
|
-
});
|
|
3790
|
-
stopCatchup = true;
|
|
3791
|
-
break;
|
|
3792
|
-
}
|
|
3793
|
-
const preloaded = blockData;
|
|
3794
|
-
let result;
|
|
3795
|
-
try {
|
|
3796
|
-
result = await withSubgraphBlockLock(subgraphName, async () => {
|
|
3797
|
-
if (getReorgEpoch(subgraphName) !== walkEpoch) {
|
|
3798
|
-
throw new ReorgEpochChangedError(subgraphName);
|
|
3799
|
-
}
|
|
3800
|
-
return processBlockWithRetry(subgraph, subgraphName, height, {
|
|
3801
|
-
preloaded
|
|
3802
|
-
});
|
|
3803
|
-
});
|
|
3804
|
-
} catch (err) {
|
|
3805
|
-
if (err instanceof ReorgEpochChangedError) {
|
|
3806
|
-
logger10.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
|
|
3807
|
-
stopCatchup = true;
|
|
3808
|
-
break;
|
|
3809
|
-
}
|
|
3810
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3811
|
-
logger10.error("Block processing failed persistently during catch-up", {
|
|
3812
|
-
subgraph: subgraphName,
|
|
3813
|
-
blockHeight: height,
|
|
3814
|
-
error: errorMsg
|
|
3815
|
-
});
|
|
3816
|
-
const { updateSubgraphStatus: updateSubgraphStatus3, recordSubgraphProcessed: recordSubgraphProcessed3 } = await import("@secondlayer/shared/db/queries/subgraphs");
|
|
3817
|
-
await recordSubgraphProcessed3(targetDb, subgraphName, 0, 1, `catch-up halted at block ${height}: ${errorMsg}`).catch(() => {});
|
|
3818
|
-
await updateSubgraphStatus3(targetDb, subgraphName, "error").catch(() => {});
|
|
3819
|
-
stopCatchup = true;
|
|
3820
|
-
break;
|
|
3821
|
-
}
|
|
3822
|
-
processed++;
|
|
3823
|
-
if (result.timing) {
|
|
3824
|
-
stats.record(result.timing, result.processed);
|
|
3825
|
-
if (stats.shouldFlush()) {
|
|
3826
|
-
await stats.flush(targetDb);
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3829
|
-
if (processed % LOG_INTERVAL2 === 0) {
|
|
3830
|
-
logger10.info("Subgraph catch-up progress", {
|
|
3831
|
-
subgraph: subgraphName,
|
|
3832
|
-
processed,
|
|
3833
|
-
total: totalBlocks,
|
|
3834
|
-
currentBlock: height,
|
|
3835
|
-
pct: Math.round(processed / totalBlocks * 100)
|
|
3836
|
-
});
|
|
3837
|
-
}
|
|
3838
|
-
}
|
|
3839
|
-
if (stopCatchup)
|
|
3840
|
-
break;
|
|
3841
|
-
const avg = avgEventsPerBlock(batch);
|
|
3842
|
-
batchSize = adjustBatchSize(batchSize, avg, batchConfig);
|
|
3843
|
-
currentHeight = batchEnd + 1;
|
|
3844
|
-
}
|
|
3845
|
-
await stats.flush(targetDb);
|
|
3846
|
-
logger10.info("Subgraph catch-up complete", {
|
|
3847
|
-
subgraph: subgraphName,
|
|
3848
|
-
processed
|
|
3849
|
-
});
|
|
3850
|
-
return processed;
|
|
3851
|
-
} finally {
|
|
3852
|
-
catchingUp.delete(subgraphName);
|
|
3853
|
-
}
|
|
3854
|
-
}
|
|
3855
|
-
|
|
3856
3867
|
// src/runtime/reorg.ts
|
|
3857
3868
|
import { getErrorMessage as getErrorMessage4 } from "@secondlayer/shared";
|
|
3858
3869
|
import { getTargetDb as getTargetDb4 } from "@secondlayer/shared/db";
|
|
@@ -3861,7 +3872,7 @@ import {
|
|
|
3861
3872
|
resolveSubgraphRawClient
|
|
3862
3873
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
3863
3874
|
import { logger as logger11 } from "@secondlayer/shared/logger";
|
|
3864
|
-
import { sql as
|
|
3875
|
+
import { sql as sql5 } from "kysely";
|
|
3865
3876
|
async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
3866
3877
|
const targetDb = getTargetDb4();
|
|
3867
3878
|
const activeSubgraphs = (await listSubgraphs(targetDb)).filter((v) => v.status === "active");
|
|
@@ -3879,7 +3890,7 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
|
3879
3890
|
bumpReorgEpoch(sg.name);
|
|
3880
3891
|
await withSubgraphBlockLock(sg.name, async () => {
|
|
3881
3892
|
await getTargetDb4().transaction().execute(async (lockTx) => {
|
|
3882
|
-
await
|
|
3893
|
+
await sql5`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-reorg:${sg.name}`}))`.execute(lockTx);
|
|
3883
3894
|
await reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef);
|
|
3884
3895
|
});
|
|
3885
3896
|
});
|
|
@@ -4435,5 +4446,5 @@ export {
|
|
|
4435
4446
|
startSubgraphOperationRunner
|
|
4436
4447
|
};
|
|
4437
4448
|
|
|
4438
|
-
//# debugId=
|
|
4449
|
+
//# debugId=CE4F2387D634E83564756E2164756E21
|
|
4439
4450
|
//# sourceMappingURL=processor.js.map
|