@secondlayer/subgraphs 3.19.2 → 3.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2862,6 +2862,52 @@ var STANDARD_CATCHUP_BATCH_CONFIG = {
2862
2862
  prefetch: true
2863
2863
  };
2864
2864
  var catchingUp = new Set;
2865
+ var subgraphBlockLockHeld = new Set;
2866
+ var subgraphBlockLockWaiters = new Map;
2867
+ var reorgEpoch = new Map;
2868
+ async function acquireSubgraphBlockLock(name) {
2869
+ if (!subgraphBlockLockHeld.has(name)) {
2870
+ subgraphBlockLockHeld.add(name);
2871
+ return () => releaseSubgraphBlockLock(name);
2872
+ }
2873
+ return new Promise((resolve) => {
2874
+ const waiters = subgraphBlockLockWaiters.get(name) ?? [];
2875
+ waiters.push(() => resolve(() => releaseSubgraphBlockLock(name)));
2876
+ subgraphBlockLockWaiters.set(name, waiters);
2877
+ });
2878
+ }
2879
+ function releaseSubgraphBlockLock(name) {
2880
+ const waiters = subgraphBlockLockWaiters.get(name);
2881
+ if (waiters && waiters.length > 0) {
2882
+ const next = waiters.shift();
2883
+ if (waiters.length === 0)
2884
+ subgraphBlockLockWaiters.delete(name);
2885
+ next();
2886
+ return;
2887
+ }
2888
+ subgraphBlockLockHeld.delete(name);
2889
+ }
2890
+ async function withSubgraphBlockLock(name, fn) {
2891
+ const release = await acquireSubgraphBlockLock(name);
2892
+ try {
2893
+ return await fn();
2894
+ } finally {
2895
+ release();
2896
+ }
2897
+ }
2898
+ function bumpReorgEpoch(name) {
2899
+ reorgEpoch.set(name, (reorgEpoch.get(name) ?? 0) + 1);
2900
+ }
2901
+ function getReorgEpoch(name) {
2902
+ return reorgEpoch.get(name) ?? 0;
2903
+ }
2904
+
2905
+ class ReorgEpochChangedError extends Error {
2906
+ constructor(name) {
2907
+ super(`reorg epoch changed for ${name} mid catch-up`);
2908
+ this.name = "ReorgEpochChangedError";
2909
+ }
2910
+ }
2865
2911
  function parsePositiveInt2(value) {
2866
2912
  if (value == null || value.trim() === "")
2867
2913
  return;
@@ -2925,6 +2971,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
2925
2971
  const batchConfig = resolveCatchupBatchConfig();
2926
2972
  let batchSize = batchConfig.defaultBatchSize;
2927
2973
  let currentHeight = startBlock;
2974
+ const walkEpoch = getReorgEpoch(subgraphName);
2928
2975
  let prefetchedBatchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
2929
2976
  let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
2930
2977
  while (currentHeight <= chainTip) {
@@ -2966,12 +3013,23 @@ async function catchUpSubgraph(subgraph, subgraphName) {
2966
3013
  stopCatchup = true;
2967
3014
  break;
2968
3015
  }
3016
+ const preloaded = blockData;
2969
3017
  let result;
2970
3018
  try {
2971
- result = await processBlockWithRetry(subgraph, subgraphName, height, {
2972
- preloaded: blockData
3019
+ result = await withSubgraphBlockLock(subgraphName, async () => {
3020
+ if (getReorgEpoch(subgraphName) !== walkEpoch) {
3021
+ throw new ReorgEpochChangedError(subgraphName);
3022
+ }
3023
+ return processBlockWithRetry(subgraph, subgraphName, height, {
3024
+ preloaded
3025
+ });
2973
3026
  });
2974
3027
  } catch (err) {
3028
+ if (err instanceof ReorgEpochChangedError) {
3029
+ logger8.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
3030
+ stopCatchup = true;
3031
+ break;
3032
+ }
2975
3033
  const errorMsg = err instanceof Error ? err.message : String(err);
2976
3034
  logger8.error("Block processing failed persistently during catch-up", {
2977
3035
  subgraph: subgraphName,
@@ -3040,85 +3098,88 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
3040
3098
  const schema = sg.definition.schema;
3041
3099
  if (!schema)
3042
3100
  continue;
3043
- const schemaName = sg.schema_name ?? pgSchemaName(sg.name);
3044
- const client = resolveSubgraphRawClient(sg);
3045
- const tableNames = Object.keys(schema);
3046
- const revertedByTable = {};
3047
- for (const tableName of tableNames) {
3048
- const rows = await client.unsafe(`SELECT * FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1 LIMIT 1000`, [blockHeight]);
3049
- if (rows.length > 0)
3050
- revertedByTable[tableName] = rows;
3051
- }
3052
- const hasJournal = (await client.unsafe(`SELECT to_regclass('"${schemaName}"."_journal"') AS r`))[0]?.r;
3053
- if (hasJournal) {
3054
- await client.begin(async (tx) => {
3101
+ bumpReorgEpoch(sg.name);
3102
+ await withSubgraphBlockLock(sg.name, async () => {
3103
+ const schemaName = sg.schema_name ?? pgSchemaName(sg.name);
3104
+ const client = resolveSubgraphRawClient(sg);
3105
+ const tableNames = Object.keys(schema);
3106
+ const revertedByTable = {};
3107
+ for (const tableName of tableNames) {
3108
+ const rows = await client.unsafe(`SELECT * FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1 LIMIT 1000`, [blockHeight]);
3109
+ if (rows.length > 0)
3110
+ revertedByTable[tableName] = rows;
3111
+ }
3112
+ const hasJournal = (await client.unsafe(`SELECT to_regclass('"${schemaName}"."_journal"') AS r`))[0]?.r;
3113
+ if (hasJournal) {
3114
+ await client.begin(async (tx) => {
3115
+ for (const tableName of tableNames) {
3116
+ const earliest = `
3117
+ SELECT DISTINCT ON (row_key) row_key, prev_row
3118
+ FROM "${schemaName}"."_journal"
3119
+ WHERE block_height >= $1 AND table_name = $2
3120
+ ORDER BY row_key, _jid ASC`;
3121
+ await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" t USING (${earliest}) e WHERE to_jsonb(t.*) @> e.row_key`, [blockHeight, tableName]);
3122
+ await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
3123
+ await tx.unsafe(`INSERT INTO "${schemaName}"."${tableName}"
3124
+ SELECT r.* FROM (${earliest}) e
3125
+ CROSS JOIN LATERAL jsonb_populate_record(NULL::"${schemaName}"."${tableName}", e.prev_row) r
3126
+ WHERE e.prev_row IS NOT NULL`, [blockHeight, tableName]);
3127
+ }
3128
+ await tx.unsafe(`DELETE FROM "${schemaName}"."_journal" WHERE block_height >= $1`, [blockHeight]);
3129
+ });
3130
+ } else {
3131
+ logger9.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
3055
3132
  for (const tableName of tableNames) {
3056
- const earliest = `
3057
- SELECT DISTINCT ON (row_key) row_key, prev_row
3058
- FROM "${schemaName}"."_journal"
3059
- WHERE block_height >= $1 AND table_name = $2
3060
- ORDER BY row_key, _jid ASC`;
3061
- await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" t USING (${earliest}) e WHERE to_jsonb(t.*) @> e.row_key`, [blockHeight, tableName]);
3062
- await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
3063
- await tx.unsafe(`INSERT INTO "${schemaName}"."${tableName}"
3064
- SELECT r.* FROM (${earliest}) e
3065
- CROSS JOIN LATERAL jsonb_populate_record(NULL::"${schemaName}"."${tableName}", e.prev_row) r
3066
- WHERE e.prev_row IS NOT NULL`, [blockHeight, tableName]);
3133
+ await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
3067
3134
  }
3068
- await tx.unsafe(`DELETE FROM "${schemaName}"."_journal" WHERE block_height >= $1`, [blockHeight]);
3069
- });
3070
- } else {
3071
- logger9.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
3072
- for (const tableName of tableNames) {
3073
- await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
3074
3135
  }
3075
- }
3076
- for (const [tableName, rows] of Object.entries(revertedByTable)) {
3077
- if (rows.length === 0)
3078
- continue;
3079
- await targetDb.insertInto("subscription_outbox").columns([
3080
- "subscription_id",
3081
- "event_type",
3082
- "payload",
3083
- "dedup_key",
3084
- "row_pk",
3085
- "status"
3086
- ]).expression((eb) => eb.selectFrom("subscriptions").select((eb2) => [
3087
- "id as subscription_id",
3088
- eb2.val(`${sg.name}.${tableName}.reverted`).as("event_type"),
3089
- eb2.val(JSON.stringify({
3090
- subgraph: sg.name,
3091
- table: tableName,
3092
- reorgFromHeight: blockHeight,
3093
- rows
3094
- })).as("payload"),
3095
- eb2.val(`reorg:${sg.name}:${tableName}:${blockHeight}`).as("dedup_key"),
3096
- eb2.val({
3097
- subgraph: sg.name,
3098
- table: tableName,
3099
- reorgRoot: blockHeight
3100
- }).as("row_pk"),
3101
- eb2.val("pending").as("status")
3102
- ]).where("subgraph_name", "=", sg.name).where("table_name", "=", tableName).where("status", "=", "active")).onConflict((oc) => oc.column("dedup_key").doNothing()).execute().catch((err) => {
3103
- logger9.error("Failed to emit revert event for subscriptions", {
3104
- event: "reorg_revert_emit_dropped",
3105
- subgraph: sg.name,
3106
- table: tableName,
3107
- blockHeight,
3108
- error: getErrorMessage4(err)
3136
+ for (const [tableName, rows] of Object.entries(revertedByTable)) {
3137
+ if (rows.length === 0)
3138
+ continue;
3139
+ await targetDb.insertInto("subscription_outbox").columns([
3140
+ "subscription_id",
3141
+ "event_type",
3142
+ "payload",
3143
+ "dedup_key",
3144
+ "row_pk",
3145
+ "status"
3146
+ ]).expression((eb) => eb.selectFrom("subscriptions").select((eb2) => [
3147
+ "id as subscription_id",
3148
+ eb2.val(`${sg.name}.${tableName}.reverted`).as("event_type"),
3149
+ eb2.val(JSON.stringify({
3150
+ subgraph: sg.name,
3151
+ table: tableName,
3152
+ reorgFromHeight: blockHeight,
3153
+ rows
3154
+ })).as("payload"),
3155
+ eb2.val(`reorg:${sg.name}:${tableName}:${blockHeight}`).as("dedup_key"),
3156
+ eb2.val({
3157
+ subgraph: sg.name,
3158
+ table: tableName,
3159
+ reorgRoot: blockHeight
3160
+ }).as("row_pk"),
3161
+ eb2.val("pending").as("status")
3162
+ ]).where("subgraph_name", "=", sg.name).where("table_name", "=", tableName).where("status", "=", "active")).onConflict((oc) => oc.column("dedup_key").doNothing()).execute().catch((err) => {
3163
+ logger9.error("Failed to emit revert event for subscriptions", {
3164
+ event: "reorg_revert_emit_dropped",
3165
+ subgraph: sg.name,
3166
+ table: tableName,
3167
+ blockHeight,
3168
+ error: getErrorMessage4(err)
3169
+ });
3109
3170
  });
3171
+ }
3172
+ logger9.info("Subgraph reorg cleanup done", {
3173
+ subgraph: sg.name,
3174
+ blockHeight,
3175
+ tablesAffected: Object.keys(revertedByTable).length
3176
+ });
3177
+ const def = await loadSubgraphDef(sg);
3178
+ await processBlock(def, sg.name, blockHeight);
3179
+ logger9.info("Subgraph reorg reprocessed", {
3180
+ subgraph: sg.name,
3181
+ blockHeight
3110
3182
  });
3111
- }
3112
- logger9.info("Subgraph reorg cleanup done", {
3113
- subgraph: sg.name,
3114
- blockHeight,
3115
- tablesAffected: Object.keys(revertedByTable).length
3116
- });
3117
- const def = await loadSubgraphDef(sg);
3118
- await processBlock(def, sg.name, blockHeight);
3119
- logger9.info("Subgraph reorg reprocessed", {
3120
- subgraph: sg.name,
3121
- blockHeight
3122
3183
  });
3123
3184
  } catch (err) {
3124
3185
  logger9.error("Subgraph reorg handling failed", {
@@ -3592,5 +3653,5 @@ export {
3592
3653
  startSubgraphOperationRunner
3593
3654
  };
3594
3655
 
3595
- //# debugId=55EF8F5DD7627C2A64756E2164756E21
3656
+ //# debugId=18F19302D77EAAC164756E2164756E21
3596
3657
  //# sourceMappingURL=processor.js.map