@secondlayer/subgraphs 0.5.7 → 0.7.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 +17 -5
- package/dist/src/index.js +467 -191
- package/dist/src/index.js.map +16 -15
- package/dist/src/runtime/block-processor.d.ts +9 -1
- package/dist/src/runtime/block-processor.js +216 -144
- package/dist/src/runtime/block-processor.js.map +9 -9
- package/dist/src/runtime/catchup.d.ts +2 -2
- package/dist/src/runtime/catchup.js +366 -160
- package/dist/src/runtime/catchup.js.map +12 -11
- package/dist/src/runtime/clarity.js.map +2 -2
- package/dist/src/runtime/context.d.ts +4 -2
- package/dist/src/runtime/context.js +79 -36
- package/dist/src/runtime/context.js.map +3 -3
- package/dist/src/runtime/processor.js +384 -166
- package/dist/src/runtime/processor.js.map +14 -13
- package/dist/src/runtime/reindex.d.ts +13 -1
- package/dist/src/runtime/reindex.js +459 -189
- package/dist/src/runtime/reindex.js.map +13 -12
- package/dist/src/runtime/reorg.js +229 -148
- package/dist/src/runtime/reorg.js.map +10 -10
- package/dist/src/runtime/runner.d.ts +4 -2
- 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 +1 -1
- package/dist/src/schema/index.js +9 -3
- package/dist/src/schema/index.js.map +5 -5
- package/dist/src/service.js +385 -167
- package/dist/src/service.js.map +15 -14
- package/dist/src/templates.js.map +2 -2
- package/dist/src/types.js.map +2 -2
- package/dist/src/validate.js +2 -2
- package/dist/src/validate.js.map +3 -3
- 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) {
|
|
@@ -122,53 +135,83 @@ class SubgraphContext {
|
|
|
122
135
|
}
|
|
123
136
|
return opsToFlush.length;
|
|
124
137
|
}
|
|
138
|
+
prepareInsert(op) {
|
|
139
|
+
const upsertKeys = op.data._upsert_keys;
|
|
140
|
+
const data = { ...op.data };
|
|
141
|
+
delete data._upsert_keys;
|
|
142
|
+
delete data._upsert_fallback_keys;
|
|
143
|
+
delete data._upsert_fallback_set;
|
|
144
|
+
data._block_height = this.block.height;
|
|
145
|
+
data._tx_id = this._tx.txId;
|
|
146
|
+
data._created_at = "NOW()";
|
|
147
|
+
const cols = Object.keys(data);
|
|
148
|
+
cols.forEach(validateColumnName);
|
|
149
|
+
const vals = cols.map((c) => data[c] === "NOW()" ? "NOW()" : escapeLiteral(data[c]));
|
|
150
|
+
const batchKey = `${op.table}:${[...cols].sort().join(",")}:${upsertKeys ? [...upsertKeys].sort().join(",") : ""}`;
|
|
151
|
+
return { data, cols, vals, upsertKeys, batchKey };
|
|
152
|
+
}
|
|
125
153
|
buildStatements(ops) {
|
|
126
154
|
const statements = [];
|
|
155
|
+
let currentBatch = null;
|
|
156
|
+
let currentBatchKey = "";
|
|
157
|
+
const flushInsertBatch = () => {
|
|
158
|
+
if (!currentBatch)
|
|
159
|
+
return;
|
|
160
|
+
const qualifiedTable = `"${this.pgSchemaName}"."${currentBatch.table}"`;
|
|
161
|
+
const colList = currentBatch.cols.map((c) => `"${c}"`).join(", ");
|
|
162
|
+
let rows = currentBatch.rows;
|
|
163
|
+
if (currentBatch.upsertKeys && currentBatch.upsertKeys.length > 0) {
|
|
164
|
+
const keyIndices = currentBatch.upsertKeys.map((k) => currentBatch.cols.indexOf(k));
|
|
165
|
+
const seen = new Map;
|
|
166
|
+
for (let i = 0;i < rows.length; i++) {
|
|
167
|
+
const key = keyIndices.map((ki) => rows[i][ki]).join("\x00");
|
|
168
|
+
seen.set(key, i);
|
|
169
|
+
}
|
|
170
|
+
if (seen.size < rows.length) {
|
|
171
|
+
rows = Array.from(seen.values()).map((i) => rows[i]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const valuesList = rows.map((r) => `(${r.join(", ")})`).join(", ");
|
|
175
|
+
let stmt = `INSERT INTO ${qualifiedTable} (${colList}) VALUES ${valuesList}`;
|
|
176
|
+
if (currentBatch.upsertKeys && currentBatch.upsertKeys.length > 0) {
|
|
177
|
+
const updateCols = currentBatch.cols.filter((c) => !currentBatch.upsertKeys.includes(c) && !c.startsWith("_"));
|
|
178
|
+
if (updateCols.length > 0) {
|
|
179
|
+
const setClauses = updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`);
|
|
180
|
+
stmt += ` ON CONFLICT (${currentBatch.upsertKeys.map((k) => `"${k}"`).join(", ")}) DO UPDATE SET ${setClauses.join(", ")}`;
|
|
181
|
+
} else {
|
|
182
|
+
stmt += ` ON CONFLICT (${currentBatch.upsertKeys.map((k) => `"${k}"`).join(", ")}) DO NOTHING`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
statements.push(stmt);
|
|
186
|
+
currentBatch = null;
|
|
187
|
+
currentBatchKey = "";
|
|
188
|
+
};
|
|
127
189
|
for (const op of ops) {
|
|
128
190
|
const qualifiedTable = `"${this.pgSchemaName}"."${op.table}"`;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
delete data._upsert_fallback_set;
|
|
138
|
-
data._block_height = this.block.height;
|
|
139
|
-
data._tx_id = this._tx.txId;
|
|
140
|
-
data._created_at = "NOW()";
|
|
141
|
-
const cols = Object.keys(data);
|
|
142
|
-
cols.forEach(validateColumnName);
|
|
143
|
-
const vals = cols.map((c) => data[c] === "NOW()" ? "NOW()" : escapeLiteral(data[c]));
|
|
144
|
-
let stmt = `INSERT INTO ${qualifiedTable} (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${vals.join(", ")})`;
|
|
145
|
-
if (upsertKeys && upsertKeys.length > 0) {
|
|
146
|
-
const updateCols = cols.filter((c) => !upsertKeys.includes(c) && !c.startsWith("_"));
|
|
147
|
-
if (updateCols.length > 0) {
|
|
148
|
-
const setClauses = updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`);
|
|
149
|
-
stmt += ` ON CONFLICT (${upsertKeys.map((k) => `"${k}"`).join(", ")}) DO UPDATE SET ${setClauses.join(", ")}`;
|
|
150
|
-
} else {
|
|
151
|
-
stmt += ` ON CONFLICT (${upsertKeys.map((k) => `"${k}"`).join(", ")}) DO NOTHING`;
|
|
152
|
-
}
|
|
153
|
-
} else if (fallbackKeys && fallbackSet) {}
|
|
154
|
-
statements.push(stmt);
|
|
155
|
-
break;
|
|
191
|
+
if (op.kind === "insert") {
|
|
192
|
+
const { cols, vals, upsertKeys, batchKey } = this.prepareInsert(op);
|
|
193
|
+
if (batchKey === currentBatchKey && currentBatch) {
|
|
194
|
+
currentBatch.rows.push(vals);
|
|
195
|
+
} else {
|
|
196
|
+
flushInsertBatch();
|
|
197
|
+
currentBatch = { table: op.table, cols, rows: [vals], upsertKeys };
|
|
198
|
+
currentBatchKey = batchKey;
|
|
156
199
|
}
|
|
157
|
-
|
|
200
|
+
} else {
|
|
201
|
+
flushInsertBatch();
|
|
202
|
+
if (op.kind === "update") {
|
|
158
203
|
const setEntries = Object.entries(op.set);
|
|
159
204
|
setEntries.forEach(([k]) => validateColumnName(k));
|
|
160
205
|
const setClauses = setEntries.map(([k, v]) => `"${k}" = ${escapeLiteral(v)}`);
|
|
161
206
|
const { clause } = buildWhereClause(op.data);
|
|
162
207
|
statements.push(`UPDATE ${qualifiedTable} SET ${setClauses.join(", ")} WHERE ${clause}`);
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
case "delete": {
|
|
208
|
+
} else if (op.kind === "delete") {
|
|
166
209
|
const { clause } = buildWhereClause(op.data);
|
|
167
210
|
statements.push(`DELETE FROM ${qualifiedTable} WHERE ${clause}`);
|
|
168
|
-
break;
|
|
169
211
|
}
|
|
170
212
|
}
|
|
171
213
|
}
|
|
214
|
+
flushInsertBatch();
|
|
172
215
|
return statements;
|
|
173
216
|
}
|
|
174
217
|
validateTable(table) {
|
|
@@ -197,95 +240,6 @@ function buildWhereClause(where) {
|
|
|
197
240
|
return { clause: parts.join(" AND "), values: [] };
|
|
198
241
|
}
|
|
199
242
|
|
|
200
|
-
// src/runtime/source-matcher.ts
|
|
201
|
-
function matchPattern(value, pattern) {
|
|
202
|
-
if (!pattern.includes("*"))
|
|
203
|
-
return value === pattern;
|
|
204
|
-
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
205
|
-
return new RegExp(`^${regex}$`).test(value);
|
|
206
|
-
}
|
|
207
|
-
function matchSource(source, transactions, eventsByTx) {
|
|
208
|
-
const results = [];
|
|
209
|
-
const key = sourceKey(source);
|
|
210
|
-
for (const tx of transactions) {
|
|
211
|
-
if (source.type) {
|
|
212
|
-
if (!matchPattern(tx.type, source.type))
|
|
213
|
-
continue;
|
|
214
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
215
|
-
let matchedEvents = txEvents;
|
|
216
|
-
if (source.minAmount !== undefined) {
|
|
217
|
-
const amountEvents = matchedEvents.filter((e) => {
|
|
218
|
-
const data = e.data;
|
|
219
|
-
const rawAmount = data?.amount;
|
|
220
|
-
if (rawAmount === undefined)
|
|
221
|
-
return false;
|
|
222
|
-
const amount = BigInt(rawAmount);
|
|
223
|
-
return amount >= source.minAmount;
|
|
224
|
-
});
|
|
225
|
-
if (amountEvents.length === 0)
|
|
226
|
-
continue;
|
|
227
|
-
matchedEvents = amountEvents;
|
|
228
|
-
}
|
|
229
|
-
results.push({ tx, events: matchedEvents, sourceKey: key });
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
if (source.contract) {
|
|
233
|
-
const txContractMatch = tx.contract_id && matchPattern(tx.contract_id, source.contract);
|
|
234
|
-
if (source.function && tx.function_name) {
|
|
235
|
-
if (!matchPattern(tx.function_name, source.function))
|
|
236
|
-
continue;
|
|
237
|
-
} else if (source.function && !tx.function_name) {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
241
|
-
let matchedEvents = txEvents;
|
|
242
|
-
if (!txContractMatch) {
|
|
243
|
-
matchedEvents = txEvents.filter((e) => {
|
|
244
|
-
const data = e.data;
|
|
245
|
-
const contractIdentifier = data?.contract_identifier;
|
|
246
|
-
return contractIdentifier && matchPattern(contractIdentifier, source.contract);
|
|
247
|
-
});
|
|
248
|
-
if (matchedEvents.length === 0)
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
if (source.event) {
|
|
252
|
-
matchedEvents = matchedEvents.filter((e) => {
|
|
253
|
-
if (matchPattern(e.type, source.event))
|
|
254
|
-
return true;
|
|
255
|
-
const data = e.data;
|
|
256
|
-
const topic = data?.topic;
|
|
257
|
-
return topic ? matchPattern(topic, source.event) : false;
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
if (txContractMatch || matchedEvents.length > 0) {
|
|
261
|
-
results.push({ tx, events: matchedEvents, sourceKey: key });
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return results;
|
|
266
|
-
}
|
|
267
|
-
function matchSources(sources, transactions, events) {
|
|
268
|
-
const eventsByTx = new Map;
|
|
269
|
-
for (const event of events) {
|
|
270
|
-
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
271
|
-
list.push(event);
|
|
272
|
-
eventsByTx.set(event.tx_id, list);
|
|
273
|
-
}
|
|
274
|
-
const seen = new Set;
|
|
275
|
-
const results = [];
|
|
276
|
-
for (const source of sources) {
|
|
277
|
-
const matches = matchSource(source, transactions, eventsByTx);
|
|
278
|
-
for (const match of matches) {
|
|
279
|
-
const dedupeKey = `${match.tx.tx_id}:${match.sourceKey}`;
|
|
280
|
-
if (!seen.has(dedupeKey)) {
|
|
281
|
-
seen.add(dedupeKey);
|
|
282
|
-
results.push(match);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return results;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
243
|
// src/runtime/clarity.ts
|
|
290
244
|
import { cvToJSON, deserializeCV } from "@secondlayer/stacks/clarity";
|
|
291
245
|
function decodeClarityValue(hex) {
|
|
@@ -318,8 +272,8 @@ function decodeFunctionArgs(args) {
|
|
|
318
272
|
}
|
|
319
273
|
|
|
320
274
|
// src/runtime/runner.ts
|
|
321
|
-
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
322
275
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
276
|
+
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
323
277
|
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
324
278
|
function resolveHandler(handlers, key) {
|
|
325
279
|
return handlers[key] ?? handlers["*"] ?? null;
|
|
@@ -331,7 +285,11 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
331
285
|
for (const { tx, events, sourceKey: sourceKey2 } of matched) {
|
|
332
286
|
const handler = resolveHandler(subgraph.handlers, sourceKey2);
|
|
333
287
|
if (!handler) {
|
|
334
|
-
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
|
+
});
|
|
335
293
|
continue;
|
|
336
294
|
}
|
|
337
295
|
ctx.setTx({
|
|
@@ -408,16 +366,109 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
408
366
|
return { processed, errors };
|
|
409
367
|
}
|
|
410
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
|
+
|
|
411
458
|
// src/runtime/block-processor.ts
|
|
412
|
-
import { sql as sql2 } from "kysely";
|
|
413
459
|
import { getDb } from "@secondlayer/shared/db";
|
|
460
|
+
import {
|
|
461
|
+
recordSubgraphProcessed,
|
|
462
|
+
updateSubgraphStatus
|
|
463
|
+
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
414
464
|
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
415
|
-
import {
|
|
465
|
+
import { sql as sql2 } from "kysely";
|
|
416
466
|
|
|
417
467
|
// src/schema/utils.ts
|
|
418
468
|
import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
|
|
419
469
|
|
|
420
470
|
// src/runtime/block-processor.ts
|
|
471
|
+
var schemaNameCache = new Map;
|
|
421
472
|
async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
422
473
|
const db = getDb();
|
|
423
474
|
const blockStart = performance.now();
|
|
@@ -428,19 +479,32 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
428
479
|
errors: 0,
|
|
429
480
|
skipped: false
|
|
430
481
|
};
|
|
431
|
-
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
482
|
+
let block, txs, evts;
|
|
483
|
+
if (opts?.preloaded) {
|
|
484
|
+
block = opts.preloaded.block;
|
|
485
|
+
txs = opts.preloaded.txs;
|
|
486
|
+
evts = opts.preloaded.events;
|
|
487
|
+
} else {
|
|
488
|
+
block = await db.selectFrom("blocks").selectAll().where("height", "=", blockHeight).executeTakeFirst();
|
|
489
|
+
if (!block) {
|
|
490
|
+
logger3.warn("Block not found for subgraph processing", {
|
|
491
|
+
subgraph: subgraphName,
|
|
492
|
+
blockHeight
|
|
493
|
+
});
|
|
494
|
+
result.skipped = true;
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
if (!block.canonical) {
|
|
498
|
+
logger3.debug("Skipping non-canonical block", {
|
|
499
|
+
subgraph: subgraphName,
|
|
500
|
+
blockHeight
|
|
501
|
+
});
|
|
502
|
+
result.skipped = true;
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
txs = await db.selectFrom("transactions").selectAll().where("block_height", "=", blockHeight).execute();
|
|
506
|
+
evts = await db.selectFrom("events").selectAll().where("block_height", "=", blockHeight).execute();
|
|
441
507
|
}
|
|
442
|
-
const txs = await db.selectFrom("transactions").selectAll().where("block_height", "=", blockHeight).execute();
|
|
443
|
-
const evts = await db.selectFrom("events").selectAll().where("block_height", "=", blockHeight).execute();
|
|
444
508
|
const matched = matchSources(subgraph.sources, txs, evts);
|
|
445
509
|
result.matched = matched.length;
|
|
446
510
|
if (matched.length === 0) {
|
|
@@ -449,8 +513,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
449
513
|
}
|
|
450
514
|
return result;
|
|
451
515
|
}
|
|
452
|
-
|
|
453
|
-
|
|
516
|
+
let schemaName = schemaNameCache.get(subgraphName);
|
|
517
|
+
if (!schemaName) {
|
|
518
|
+
const subgraphRecord = await db.selectFrom("subgraphs").select("schema_name").where("name", "=", subgraphName).executeTakeFirst();
|
|
519
|
+
schemaName = subgraphRecord?.schema_name ?? pgSchemaName(subgraphName);
|
|
520
|
+
schemaNameCache.set(subgraphName, schemaName);
|
|
521
|
+
}
|
|
454
522
|
const blockMeta = {
|
|
455
523
|
height: block.height,
|
|
456
524
|
hash: block.hash,
|
|
@@ -499,7 +567,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
499
567
|
const { rows } = await sql2.raw(`SELECT COUNT(*) as count FROM "${schemaName}"."${table}"`).execute(db);
|
|
500
568
|
const count = Number(rows[0].count);
|
|
501
569
|
if (count >= 1e7) {
|
|
502
|
-
logger3.warn("Subgraph table exceeds 10M rows", {
|
|
570
|
+
logger3.warn("Subgraph table exceeds 10M rows", {
|
|
571
|
+
subgraph: subgraphName,
|
|
572
|
+
table,
|
|
573
|
+
count
|
|
574
|
+
});
|
|
503
575
|
}
|
|
504
576
|
}
|
|
505
577
|
} catch {}
|
|
@@ -508,17 +580,20 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
508
580
|
}
|
|
509
581
|
|
|
510
582
|
// src/runtime/reorg.ts
|
|
583
|
+
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
511
584
|
import { getDb as getDb2, getRawClient } from "@secondlayer/shared/db";
|
|
512
585
|
import { listSubgraphs } from "@secondlayer/shared/db/queries/subgraphs";
|
|
513
586
|
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
514
|
-
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
515
587
|
async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
516
588
|
const db = getDb2();
|
|
517
589
|
const client = getRawClient();
|
|
518
590
|
const activeSubgraphs = (await listSubgraphs(db)).filter((v) => v.status === "active");
|
|
519
591
|
if (activeSubgraphs.length === 0)
|
|
520
592
|
return;
|
|
521
|
-
logger4.info("Propagating reorg to subgraphs", {
|
|
593
|
+
logger4.info("Propagating reorg to subgraphs", {
|
|
594
|
+
blockHeight,
|
|
595
|
+
subgraphCount: activeSubgraphs.length
|
|
596
|
+
});
|
|
522
597
|
for (const sg of activeSubgraphs) {
|
|
523
598
|
try {
|
|
524
599
|
const schema = sg.definition.schema;
|
|
@@ -528,10 +603,16 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
|
528
603
|
for (const tableName of Object.keys(schema)) {
|
|
529
604
|
await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" = $1`, [blockHeight]);
|
|
530
605
|
}
|
|
531
|
-
logger4.info("Subgraph reorg cleanup done", {
|
|
606
|
+
logger4.info("Subgraph reorg cleanup done", {
|
|
607
|
+
subgraph: sg.name,
|
|
608
|
+
blockHeight
|
|
609
|
+
});
|
|
532
610
|
const def = await loadSubgraphDef(sg.handler_path);
|
|
533
611
|
await processBlock(def, sg.name, blockHeight);
|
|
534
|
-
logger4.info("Subgraph reorg reprocessed", {
|
|
612
|
+
logger4.info("Subgraph reorg reprocessed", {
|
|
613
|
+
subgraph: sg.name,
|
|
614
|
+
blockHeight
|
|
615
|
+
});
|
|
535
616
|
} catch (err) {
|
|
536
617
|
logger4.error("Subgraph reorg handling failed", {
|
|
537
618
|
subgraph: sg.name,
|
|
@@ -545,5 +626,5 @@ export {
|
|
|
545
626
|
handleSubgraphReorg
|
|
546
627
|
};
|
|
547
628
|
|
|
548
|
-
//# debugId=
|
|
629
|
+
//# debugId=9A297975E56BD47664756E2164756E21
|
|
549
630
|
//# sourceMappingURL=reorg.js.map
|