@secondlayer/subgraphs 3.21.0 → 3.23.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 +238 -17
- package/dist/src/index.js +389 -52
- package/dist/src/index.js.map +12 -11
- package/dist/src/runtime/block-processor.d.ts +111 -10
- package/dist/src/runtime/block-processor.js +263 -31
- package/dist/src/runtime/block-processor.js.map +9 -8
- package/dist/src/runtime/catchup.d.ts +111 -10
- package/dist/src/runtime/catchup.js +263 -31
- package/dist/src/runtime/catchup.js.map +9 -8
- package/dist/src/runtime/context.d.ts +111 -11
- package/dist/src/runtime/context.js +166 -12
- package/dist/src/runtime/context.js.map +7 -6
- package/dist/src/runtime/processor.js +272 -35
- package/dist/src/runtime/processor.js.map +10 -9
- package/dist/src/runtime/reindex.d.ts +111 -10
- package/dist/src/runtime/reindex.js +263 -31
- package/dist/src/runtime/reindex.js.map +9 -8
- package/dist/src/runtime/reorg.d.ts +111 -10
- package/dist/src/runtime/reorg.js +268 -31
- package/dist/src/runtime/reorg.js.map +10 -9
- package/dist/src/runtime/replay.d.ts +3 -3
- package/dist/src/runtime/replay.js +30 -6
- package/dist/src/runtime/replay.js.map +4 -4
- package/dist/src/runtime/runner.d.ts +191 -17
- package/dist/src/runtime/source-matcher.d.ts +68 -8
- package/dist/src/runtime/source-matcher.js +30 -6
- package/dist/src/runtime/source-matcher.js.map +3 -3
- package/dist/src/runtime/stats.d.ts +3 -3
- package/dist/src/schema/index.d.ts +114 -13
- package/dist/src/schema/index.js +133 -20
- package/dist/src/schema/index.js.map +5 -5
- package/dist/src/service.js +272 -35
- package/dist/src/service.js.map +10 -9
- package/dist/src/testing/index.d.ts +667 -0
- package/dist/src/testing/index.js +1463 -0
- package/dist/src/testing/index.js.map +17 -0
- package/dist/src/triggers/index.d.ts +57 -6
- package/dist/src/types.d.ts +112 -11
- package/dist/src/validate.d.ts +129 -11
- package/dist/src/validate.js +117 -18
- package/dist/src/validate.js.map +3 -3
- package/package.json +9 -3
|
@@ -100,6 +100,18 @@ function emitJournalDDL(schemaName) {
|
|
|
100
100
|
`CREATE INDEX IF NOT EXISTS idx_${schemaName}_journal_height ON ${schemaName}._journal (block_height)`
|
|
101
101
|
];
|
|
102
102
|
}
|
|
103
|
+
function emitFactoryDDL(schemaName) {
|
|
104
|
+
return [
|
|
105
|
+
`CREATE TABLE IF NOT EXISTS ${schemaName}._factory_addresses (
|
|
106
|
+
source_name TEXT NOT NULL,
|
|
107
|
+
address TEXT NOT NULL,
|
|
108
|
+
block_height BIGINT NOT NULL,
|
|
109
|
+
_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
110
|
+
PRIMARY KEY (source_name, address)
|
|
111
|
+
)`,
|
|
112
|
+
`CREATE INDEX IF NOT EXISTS idx_${schemaName}_factory_height ON ${schemaName}._factory_addresses (block_height)`
|
|
113
|
+
];
|
|
114
|
+
}
|
|
103
115
|
function emitForeignKeyDDL(schemaName, tableName, tableDef) {
|
|
104
116
|
return (tableDef.relations ?? []).map((rel) => {
|
|
105
117
|
const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
|
|
@@ -118,6 +130,9 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
118
130
|
statements.push(...emitTableDDL(schemaName, tableName, tableDef));
|
|
119
131
|
}
|
|
120
132
|
statements.push(...emitJournalDDL(schemaName));
|
|
133
|
+
if (Object.values(def.sources ?? {}).some((src) => src.factory !== undefined)) {
|
|
134
|
+
statements.push(...emitFactoryDDL(schemaName));
|
|
135
|
+
}
|
|
121
136
|
for (const [tableName, tableDef] of Object.entries(def.schema)) {
|
|
122
137
|
statements.push(...emitForeignKeyDDL(schemaName, tableName, tableDef));
|
|
123
138
|
}
|
|
@@ -130,6 +145,139 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
130
145
|
return { statements, hash };
|
|
131
146
|
}
|
|
132
147
|
|
|
148
|
+
// src/runtime/chain-read.ts
|
|
149
|
+
import { getSourceDb } from "@secondlayer/shared/db";
|
|
150
|
+
import { http, createPublicClient } from "@secondlayer/stacks";
|
|
151
|
+
import {
|
|
152
|
+
ContractResponseError,
|
|
153
|
+
buildFunctionArgs,
|
|
154
|
+
isResponseOutput,
|
|
155
|
+
readContract
|
|
156
|
+
} from "@secondlayer/stacks/actions";
|
|
157
|
+
import {
|
|
158
|
+
clarityValueToJSUntyped,
|
|
159
|
+
deserializeCV,
|
|
160
|
+
normalizeAbi,
|
|
161
|
+
serializeCV,
|
|
162
|
+
toCamelCase
|
|
163
|
+
} from "@secondlayer/stacks/clarity";
|
|
164
|
+
function readContractAt(ctx, contractId, abi, options) {
|
|
165
|
+
return ctx.client.contract(contractId, abi, options);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class ChainReadError extends Error {
|
|
169
|
+
name = "ChainReadError";
|
|
170
|
+
}
|
|
171
|
+
function nodeClient(rpcUrl) {
|
|
172
|
+
return createPublicClient({
|
|
173
|
+
transport: http(rpcUrl, { retryCount: 0, timeout: 1e4 })
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
class ConcurrencyGate {
|
|
178
|
+
limit;
|
|
179
|
+
active = 0;
|
|
180
|
+
queue = [];
|
|
181
|
+
constructor(limit) {
|
|
182
|
+
this.limit = limit;
|
|
183
|
+
}
|
|
184
|
+
async run(fn) {
|
|
185
|
+
if (this.active >= this.limit) {
|
|
186
|
+
await new Promise((resolve) => this.queue.push(resolve));
|
|
187
|
+
}
|
|
188
|
+
this.active++;
|
|
189
|
+
try {
|
|
190
|
+
return await fn();
|
|
191
|
+
} finally {
|
|
192
|
+
this.active--;
|
|
193
|
+
this.queue.shift()?.();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
var DEFAULT_MAX_CONCURRENT_READS = 4;
|
|
198
|
+
function maxConcurrentReads() {
|
|
199
|
+
const raw = process.env.SUBGRAPH_CHAIN_READ_CONCURRENCY;
|
|
200
|
+
const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
201
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT_READS;
|
|
202
|
+
}
|
|
203
|
+
var gate = new ConcurrencyGate(maxConcurrentReads());
|
|
204
|
+
function hashArgs(args) {
|
|
205
|
+
const serialized = args.map((arg) => serializeCV(arg)).join(",");
|
|
206
|
+
return Bun.hash(serialized).toString(16);
|
|
207
|
+
}
|
|
208
|
+
function findFunction(abi, name) {
|
|
209
|
+
const fn = abi.functions.find((f) => f.name === name);
|
|
210
|
+
if (!fn) {
|
|
211
|
+
throw new ChainReadError(`No function "${name}" in the supplied ABI.`);
|
|
212
|
+
}
|
|
213
|
+
if (fn.access !== "read-only") {
|
|
214
|
+
throw new ChainReadError(`"${name}" is ${fn.access}, not read-only — a handler cannot call it.`);
|
|
215
|
+
}
|
|
216
|
+
return fn;
|
|
217
|
+
}
|
|
218
|
+
function createChainReadClient(ctx) {
|
|
219
|
+
const readValue = async (contractId, abi, functionName, args, options) => {
|
|
220
|
+
const rpcUrl = ctx.rpcUrl ?? process.env.STACKS_NODE_RPC_URL;
|
|
221
|
+
if (!rpcUrl) {
|
|
222
|
+
throw new ChainReadError("ctx.client needs a Stacks node: set STACKS_NODE_RPC_URL on the subgraph processor.");
|
|
223
|
+
}
|
|
224
|
+
const constant = options.cache === "contract-constant";
|
|
225
|
+
if (!constant && !ctx.indexBlockHash) {
|
|
226
|
+
throw new ChainReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a read cannot be pinned to it. Reading at the node's tip would make this handler non-deterministic (the same reindex would produce different rows). Re-ingest the block, or pass cache: "contract-constant" if the value genuinely cannot change.`);
|
|
227
|
+
}
|
|
228
|
+
const fn = findFunction(abi, functionName);
|
|
229
|
+
const clarityArgs = buildFunctionArgs(fn, args);
|
|
230
|
+
const db = ctx.db ?? getSourceDb();
|
|
231
|
+
const argsHash = hashArgs(clarityArgs);
|
|
232
|
+
const pinnedTo = constant ? null : ctx.indexBlockHash;
|
|
233
|
+
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
|
+
const resultHex = cached?.result_hex ?? await gate.run(async () => {
|
|
235
|
+
const value2 = await readContract(nodeClient(rpcUrl), {
|
|
236
|
+
contract: contractId,
|
|
237
|
+
functionName: fn.name,
|
|
238
|
+
args: clarityArgs,
|
|
239
|
+
tip: ctx.indexBlockHash ?? undefined
|
|
240
|
+
});
|
|
241
|
+
return serializeCV(value2);
|
|
242
|
+
});
|
|
243
|
+
if (!cached) {
|
|
244
|
+
await db.insertInto("chain_read_cache").values({
|
|
245
|
+
contract_id: contractId,
|
|
246
|
+
function_name: fn.name,
|
|
247
|
+
args_hash: argsHash,
|
|
248
|
+
index_block_hash: pinnedTo,
|
|
249
|
+
block_height: constant ? null : ctx.blockHeight,
|
|
250
|
+
result_hex: resultHex
|
|
251
|
+
}).onConflict((oc) => oc.doNothing()).execute();
|
|
252
|
+
}
|
|
253
|
+
const value = deserializeCV(resultHex);
|
|
254
|
+
const js = clarityValueToJSUntyped(fn.outputs, value);
|
|
255
|
+
if (isResponseOutput(fn.outputs)) {
|
|
256
|
+
const response = js;
|
|
257
|
+
if ("ok" in response)
|
|
258
|
+
return response.ok;
|
|
259
|
+
throw new ContractResponseError(`${contractId}::${fn.name} returned an err response.`, response.err);
|
|
260
|
+
}
|
|
261
|
+
return js;
|
|
262
|
+
};
|
|
263
|
+
return {
|
|
264
|
+
contract(contractId, rawAbi, options = {}) {
|
|
265
|
+
const abi = normalizeAbi(rawAbi);
|
|
266
|
+
const camelToKebab = new Map;
|
|
267
|
+
for (const fn of abi.functions) {
|
|
268
|
+
camelToKebab.set(toCamelCase(fn.name), fn.name);
|
|
269
|
+
}
|
|
270
|
+
const read = new Proxy({}, {
|
|
271
|
+
get(_target, prop) {
|
|
272
|
+
const fnName = camelToKebab.get(prop) ?? prop;
|
|
273
|
+
return (args = {}) => readValue(contractId, abi, fnName, args, options);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
return { read };
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
133
281
|
// src/runtime/context.ts
|
|
134
282
|
var JOURNAL_RETENTION_BLOCKS = 300;
|
|
135
283
|
var journalEnsured = new Set;
|
|
@@ -148,6 +296,7 @@ class SubgraphContext {
|
|
|
148
296
|
ops = [];
|
|
149
297
|
byo;
|
|
150
298
|
journal;
|
|
299
|
+
_client;
|
|
151
300
|
constructor(db, pgSchemaName2, subgraphSchema, block, tx, byo = false, journal = false) {
|
|
152
301
|
this.db = db;
|
|
153
302
|
this.pgSchemaName = pgSchemaName2;
|
|
@@ -157,6 +306,13 @@ class SubgraphContext {
|
|
|
157
306
|
this.byo = byo;
|
|
158
307
|
this.journal = journal;
|
|
159
308
|
}
|
|
309
|
+
get client() {
|
|
310
|
+
this._client ??= createChainReadClient({
|
|
311
|
+
blockHeight: this.block.height,
|
|
312
|
+
indexBlockHash: this.block.indexBlockHash
|
|
313
|
+
});
|
|
314
|
+
return this._client;
|
|
315
|
+
}
|
|
160
316
|
get tx() {
|
|
161
317
|
return this._tx;
|
|
162
318
|
}
|
|
@@ -262,23 +418,21 @@ class SubgraphContext {
|
|
|
262
418
|
formatUnits(value, decimals) {
|
|
263
419
|
return formatUnits(value, decimals);
|
|
264
420
|
}
|
|
265
|
-
async
|
|
266
|
-
this.validateTable(table);
|
|
421
|
+
async readRows(table, where, limit) {
|
|
267
422
|
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
268
423
|
const { clause } = buildWhereClause(where);
|
|
269
|
-
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} LIMIT
|
|
424
|
+
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
|
|
270
425
|
const { rows } = await sql.raw(query).execute(this.db);
|
|
271
|
-
|
|
272
|
-
|
|
426
|
+
return rows.map((r) => this.coerceRow(table, r));
|
|
427
|
+
}
|
|
428
|
+
async findOne(table, where) {
|
|
429
|
+
this.validateTable(table);
|
|
430
|
+
const row = (await this.readRows(table, where, 1))[0] ?? null;
|
|
431
|
+
return this.overlayOne(table, where, row);
|
|
273
432
|
}
|
|
274
433
|
async findMany(table, where) {
|
|
275
434
|
this.validateTable(table);
|
|
276
|
-
|
|
277
|
-
const { clause } = buildWhereClause(where);
|
|
278
|
-
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}`;
|
|
279
|
-
const { rows } = await sql.raw(query).execute(this.db);
|
|
280
|
-
const dbRows = rows.map((r) => this.coerceRow(table, r));
|
|
281
|
-
return this.overlayMany(table, where, dbRows);
|
|
435
|
+
return this.overlayMany(table, where, await this.readRows(table, where));
|
|
282
436
|
}
|
|
283
437
|
overlayOne(table, where, dbRow) {
|
|
284
438
|
if (this.ops.length === 0)
|
|
@@ -720,11 +874,11 @@ function buildWhereClause(where) {
|
|
|
720
874
|
}
|
|
721
875
|
|
|
722
876
|
// src/runtime/clarity.ts
|
|
723
|
-
import { cvToValue, deserializeCV } from "@secondlayer/stacks/clarity";
|
|
877
|
+
import { cvToValue, deserializeCV as deserializeCV2 } from "@secondlayer/stacks/clarity";
|
|
724
878
|
function decodeClarityValue(hex) {
|
|
725
879
|
try {
|
|
726
880
|
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
727
|
-
const cv =
|
|
881
|
+
const cv = deserializeCV2(cleanHex);
|
|
728
882
|
return cvToValue(cv);
|
|
729
883
|
} catch {
|
|
730
884
|
return hex;
|
|
@@ -755,8 +909,8 @@ import { getErrorMessage } from "@secondlayer/shared";
|
|
|
755
909
|
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
756
910
|
import {
|
|
757
911
|
clarityValueToJS,
|
|
758
|
-
deserializeCV as
|
|
759
|
-
toCamelCase
|
|
912
|
+
deserializeCV as deserializeCV3,
|
|
913
|
+
toCamelCase as toCamelCase2
|
|
760
914
|
} from "@secondlayer/stacks/clarity";
|
|
761
915
|
|
|
762
916
|
// src/runtime/print-validate.ts
|
|
@@ -919,7 +1073,7 @@ function buildContractCallInput(filter, tx) {
|
|
|
919
1073
|
return;
|
|
920
1074
|
try {
|
|
921
1075
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
922
|
-
input[
|
|
1076
|
+
input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV3(clean));
|
|
923
1077
|
} catch {}
|
|
924
1078
|
});
|
|
925
1079
|
return input;
|
|
@@ -1198,6 +1352,9 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1198
1352
|
|
|
1199
1353
|
// src/runtime/source-matcher.ts
|
|
1200
1354
|
var patternCache = new Map;
|
|
1355
|
+
function matchAny(value, pattern) {
|
|
1356
|
+
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
1357
|
+
}
|
|
1201
1358
|
function matchPattern(value, pattern) {
|
|
1202
1359
|
if (!pattern.includes("*"))
|
|
1203
1360
|
return value === pattern;
|
|
@@ -1210,6 +1367,22 @@ function matchPattern(value, pattern) {
|
|
|
1210
1367
|
return re.test(value);
|
|
1211
1368
|
}
|
|
1212
1369
|
var EMPTY_SET = new Set;
|
|
1370
|
+
function factoryAllows(filter, contractId, factoryContracts) {
|
|
1371
|
+
if (!filter.factory)
|
|
1372
|
+
return true;
|
|
1373
|
+
if (!contractId)
|
|
1374
|
+
return false;
|
|
1375
|
+
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
1376
|
+
}
|
|
1377
|
+
function readPath(payload, path) {
|
|
1378
|
+
let cur = payload;
|
|
1379
|
+
for (const key of path.split(".")) {
|
|
1380
|
+
if (cur === null || typeof cur !== "object")
|
|
1381
|
+
return;
|
|
1382
|
+
cur = cur[key];
|
|
1383
|
+
}
|
|
1384
|
+
return cur;
|
|
1385
|
+
}
|
|
1213
1386
|
function traitAllows(filter, contractId, traitContracts) {
|
|
1214
1387
|
const trait = filter.trait;
|
|
1215
1388
|
if (!trait)
|
|
@@ -1221,7 +1394,7 @@ function traitAllows(filter, contractId, traitContracts) {
|
|
|
1221
1394
|
function assetContract(assetId) {
|
|
1222
1395
|
return assetId?.split("::")[0];
|
|
1223
1396
|
}
|
|
1224
|
-
function matchFilter(filter, transactions, eventsByTx, traitContracts) {
|
|
1397
|
+
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
|
|
1225
1398
|
const results = [];
|
|
1226
1399
|
switch (filter.type) {
|
|
1227
1400
|
case "stx_transfer":
|
|
@@ -1348,9 +1521,11 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
|
|
|
1348
1521
|
if (tx.type !== "contract_call")
|
|
1349
1522
|
continue;
|
|
1350
1523
|
if (filter.contractId) {
|
|
1351
|
-
if (!tx.contract_id || !
|
|
1524
|
+
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
1352
1525
|
continue;
|
|
1353
1526
|
}
|
|
1527
|
+
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
1528
|
+
continue;
|
|
1354
1529
|
if (filter.functionName) {
|
|
1355
1530
|
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
1356
1531
|
continue;
|
|
@@ -1397,11 +1572,13 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
|
|
|
1397
1572
|
return false;
|
|
1398
1573
|
const printContractId = data.contract_identifier ?? data.contract_id;
|
|
1399
1574
|
if (filter.contractId) {
|
|
1400
|
-
if (!printContractId || !
|
|
1575
|
+
if (!printContractId || !matchAny(printContractId, filter.contractId))
|
|
1401
1576
|
return false;
|
|
1402
1577
|
}
|
|
1403
1578
|
if (!traitAllows(filter, printContractId, traitContracts))
|
|
1404
1579
|
return false;
|
|
1580
|
+
if (!factoryAllows(filter, printContractId, factoryContracts))
|
|
1581
|
+
return false;
|
|
1405
1582
|
return true;
|
|
1406
1583
|
});
|
|
1407
1584
|
if (matched.length > 0) {
|
|
@@ -1413,7 +1590,7 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
|
|
|
1413
1590
|
}
|
|
1414
1591
|
return results;
|
|
1415
1592
|
}
|
|
1416
|
-
function matchSources(sources, transactions, events, traitContracts = new Map) {
|
|
1593
|
+
function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
|
|
1417
1594
|
const eventsByTx = new Map;
|
|
1418
1595
|
for (const event of events) {
|
|
1419
1596
|
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
@@ -1423,7 +1600,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
|
|
|
1423
1600
|
const seen = new Set;
|
|
1424
1601
|
const results = [];
|
|
1425
1602
|
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
1426
|
-
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts);
|
|
1603
|
+
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
|
|
1427
1604
|
for (const match of matches) {
|
|
1428
1605
|
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
1429
1606
|
if (!seen.has(dedupeKey)) {
|
|
@@ -1450,7 +1627,7 @@ import { logger as logger5 } from "@secondlayer/shared/logger";
|
|
|
1450
1627
|
import { sql as sql3 } from "kysely";
|
|
1451
1628
|
|
|
1452
1629
|
// src/runtime/block-source.ts
|
|
1453
|
-
import { getSourceDb } from "@secondlayer/shared/db";
|
|
1630
|
+
import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
|
|
1454
1631
|
import {
|
|
1455
1632
|
IndexHttpClient
|
|
1456
1633
|
} from "@secondlayer/shared/index-http";
|
|
@@ -1511,6 +1688,7 @@ function reconstructBlock(b) {
|
|
|
1511
1688
|
parent_hash: b.parent_hash,
|
|
1512
1689
|
burn_block_height: b.burn_block_height,
|
|
1513
1690
|
burn_block_hash: b.burn_block_hash,
|
|
1691
|
+
index_block_hash: b.index_block_hash ?? null,
|
|
1514
1692
|
timestamp: isoToUnixSeconds(b.block_time),
|
|
1515
1693
|
canonical: true,
|
|
1516
1694
|
created_at: new Date(0)
|
|
@@ -1645,11 +1823,11 @@ function sparseProbeTargets(subgraph) {
|
|
|
1645
1823
|
|
|
1646
1824
|
class PostgresBlockSource {
|
|
1647
1825
|
async getTip() {
|
|
1648
|
-
const progress = await
|
|
1826
|
+
const progress = await getSourceDb2().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
|
|
1649
1827
|
return progress ? Number(progress.highest_seen_block) : 0;
|
|
1650
1828
|
}
|
|
1651
1829
|
loadBlockRange(fromHeight, toHeight) {
|
|
1652
|
-
return loadBlockRange(
|
|
1830
|
+
return loadBlockRange(getSourceDb2(), fromHeight, toHeight);
|
|
1653
1831
|
}
|
|
1654
1832
|
}
|
|
1655
1833
|
var EVENT_FILTER_TO_INDEX_TYPE = {
|
|
@@ -1718,8 +1896,8 @@ class PublicApiBlockSource {
|
|
|
1718
1896
|
eventTypes;
|
|
1719
1897
|
probeTargets;
|
|
1720
1898
|
needsTransactions;
|
|
1721
|
-
constructor(
|
|
1722
|
-
this.http =
|
|
1899
|
+
constructor(http2, eventTypes, probeTargets, needsTransactions = true) {
|
|
1900
|
+
this.http = http2;
|
|
1723
1901
|
this.eventTypes = eventTypes;
|
|
1724
1902
|
this.probeTargets = probeTargets;
|
|
1725
1903
|
this.needsTransactions = needsTransactions;
|
|
@@ -2078,6 +2256,57 @@ async function resolveRoute(subgraphName, targetDb) {
|
|
|
2078
2256
|
function invalidateSubgraphRoute(subgraphName) {
|
|
2079
2257
|
routeCache.delete(subgraphName);
|
|
2080
2258
|
}
|
|
2259
|
+
async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, txs, evts) {
|
|
2260
|
+
const factories = new Map;
|
|
2261
|
+
for (const source of Object.values(subgraph.sources)) {
|
|
2262
|
+
const factory = source.factory;
|
|
2263
|
+
if (factory)
|
|
2264
|
+
factories.set(factory.from, factory);
|
|
2265
|
+
}
|
|
2266
|
+
const resolved = new Map;
|
|
2267
|
+
const discovered = [];
|
|
2268
|
+
if (factories.size === 0)
|
|
2269
|
+
return { resolved, discovered };
|
|
2270
|
+
for (const [discoveringSource, factory] of factories) {
|
|
2271
|
+
const known = new Set;
|
|
2272
|
+
try {
|
|
2273
|
+
const rows = await sql3`
|
|
2274
|
+
SELECT address FROM ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
|
|
2275
|
+
WHERE source_name = ${discoveringSource} AND block_height <= ${blockHeight}
|
|
2276
|
+
`.execute(db);
|
|
2277
|
+
for (const row of rows.rows)
|
|
2278
|
+
known.add(row.address);
|
|
2279
|
+
} catch {}
|
|
2280
|
+
const discovering = subgraph.sources[factory.from];
|
|
2281
|
+
if (discovering) {
|
|
2282
|
+
const matches = matchSources({ [factory.from]: discovering }, txs, evts, new Map, new Map);
|
|
2283
|
+
for (const match of matches) {
|
|
2284
|
+
for (const event of match.events ?? []) {
|
|
2285
|
+
const payload = buildEventPayload(discovering, match.tx, event);
|
|
2286
|
+
const value = readPath(payload, factory.field);
|
|
2287
|
+
if (typeof value === "string" && value.length > 0 && !known.has(value)) {
|
|
2288
|
+
known.add(value);
|
|
2289
|
+
discovered.push({ sourceName: discoveringSource, address: value });
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
resolved.set(discoveringSource, known);
|
|
2295
|
+
}
|
|
2296
|
+
return { resolved, discovered };
|
|
2297
|
+
}
|
|
2298
|
+
async function persistFactoryDiscoveries(schemaName, db, blockHeight, discovered) {
|
|
2299
|
+
if (discovered.length === 0)
|
|
2300
|
+
return;
|
|
2301
|
+
for (const { sourceName, address } of discovered) {
|
|
2302
|
+
await sql3`
|
|
2303
|
+
INSERT INTO ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
|
|
2304
|
+
(source_name, address, block_height)
|
|
2305
|
+
VALUES (${sourceName}, ${address}, ${blockHeight})
|
|
2306
|
+
ON CONFLICT (source_name, address) DO NOTHING
|
|
2307
|
+
`.execute(db);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2081
2310
|
async function resolveTraitContracts(subgraph, blockHeight, db) {
|
|
2082
2311
|
const traits = new Set;
|
|
2083
2312
|
for (const source of Object.values(subgraph.sources)) {
|
|
@@ -2164,8 +2393,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2164
2393
|
txs = data.txs;
|
|
2165
2394
|
evts = data.events;
|
|
2166
2395
|
}
|
|
2396
|
+
const route = await resolveRoute(subgraphName, targetDb);
|
|
2397
|
+
const schemaName = route.schemaName;
|
|
2167
2398
|
const traitContracts = await resolveTraitContracts(subgraph, blockHeight, targetDb);
|
|
2168
|
-
const
|
|
2399
|
+
const { resolved: factoryContracts, discovered } = await resolveFactoryContracts(subgraph, blockHeight, schemaName, targetDb, txs, evts);
|
|
2400
|
+
const matched = matchSources(subgraph.sources, txs, evts, traitContracts, factoryContracts);
|
|
2169
2401
|
result.matched = matched.length;
|
|
2170
2402
|
if (matched.length === 0) {
|
|
2171
2403
|
if (!opts?.skipProgressUpdate) {
|
|
@@ -2173,13 +2405,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2173
2405
|
}
|
|
2174
2406
|
return result;
|
|
2175
2407
|
}
|
|
2176
|
-
const route = await resolveRoute(subgraphName, targetDb);
|
|
2177
|
-
const schemaName = route.schemaName;
|
|
2178
2408
|
const blockMeta = {
|
|
2179
2409
|
height: block.height,
|
|
2180
2410
|
hash: block.hash,
|
|
2181
2411
|
timestamp: block.timestamp,
|
|
2182
|
-
burnBlockHeight: block.burn_block_height
|
|
2412
|
+
burnBlockHeight: block.burn_block_height,
|
|
2413
|
+
indexBlockHash: block.index_block_hash
|
|
2183
2414
|
};
|
|
2184
2415
|
const initialTx = {
|
|
2185
2416
|
txId: "",
|
|
@@ -2232,6 +2463,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2232
2463
|
});
|
|
2233
2464
|
result.processed = runResult.processed;
|
|
2234
2465
|
result.errors = runResult.errors;
|
|
2466
|
+
await persistFactoryDiscoveries(schemaName, route.dataDb, blockHeight, discovered);
|
|
2235
2467
|
await targetDb.transaction().execute(async (tx) => {
|
|
2236
2468
|
if (manifest && manifest.count > 0) {
|
|
2237
2469
|
await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
|
|
@@ -3230,6 +3462,11 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
|
3230
3462
|
await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
|
|
3231
3463
|
}
|
|
3232
3464
|
}
|
|
3465
|
+
await client.unsafe(`DO $$ BEGIN
|
|
3466
|
+
IF to_regclass('"${schemaName}"."_factory_addresses"') IS NOT NULL THEN
|
|
3467
|
+
DELETE FROM "${schemaName}"."_factory_addresses" WHERE block_height >= ${Number(blockHeight)};
|
|
3468
|
+
END IF;
|
|
3469
|
+
END $$`);
|
|
3233
3470
|
for (const [tableName, rows] of Object.entries(revertedByTable)) {
|
|
3234
3471
|
if (rows.length === 0)
|
|
3235
3472
|
continue;
|
|
@@ -3356,8 +3593,8 @@ import { IndexHttpClient as IndexHttpClient2 } from "@secondlayer/shared/index-h
|
|
|
3356
3593
|
import { logger as logger10 } from "@secondlayer/shared/logger";
|
|
3357
3594
|
var POLL_MS = Number(process.env.SUBGRAPH_REORG_POLL_MS) || 15000;
|
|
3358
3595
|
var STARTUP_MARGIN_MS = 60 * 60 * 1000;
|
|
3359
|
-
async function pollReorgsOnce(
|
|
3360
|
-
const { reorgs, next_since } = await
|
|
3596
|
+
async function pollReorgsOnce(http2, cursor, onReorg, handled) {
|
|
3597
|
+
const { reorgs, next_since } = await http2.listReorgs(cursor);
|
|
3361
3598
|
const sorted = [...reorgs].sort((a, b) => a.fork_point_height - b.fork_point_height);
|
|
3362
3599
|
for (const r of sorted) {
|
|
3363
3600
|
if (handled && r.id && handled.has(r.id)) {
|
|
@@ -3378,7 +3615,7 @@ async function pollReorgsOnce(http, cursor, onReorg, handled) {
|
|
|
3378
3615
|
}
|
|
3379
3616
|
function startStreamsReorgPoll(onReorg) {
|
|
3380
3617
|
const baseUrl = process.env.SUBGRAPH_INDEX_API_URL ?? process.env.STREAMS_API_URL ?? "http://api:3800";
|
|
3381
|
-
const
|
|
3618
|
+
const http2 = new IndexHttpClient2({
|
|
3382
3619
|
indexBaseUrl: baseUrl,
|
|
3383
3620
|
streamsBaseUrl: baseUrl,
|
|
3384
3621
|
streamsApiKey: process.env.STREAMS_INTERNAL_API_KEY ?? "sk-sl_streams_decode_internal"
|
|
@@ -3393,7 +3630,7 @@ function startStreamsReorgPoll(onReorg) {
|
|
|
3393
3630
|
if (handled.size > 1e4)
|
|
3394
3631
|
handled.clear();
|
|
3395
3632
|
try {
|
|
3396
|
-
since = await pollReorgsOnce(
|
|
3633
|
+
since = await pollReorgsOnce(http2, since, onReorg, handled);
|
|
3397
3634
|
} catch (err) {
|
|
3398
3635
|
logger10.error("Streams reorg poll failed", {
|
|
3399
3636
|
error: getErrorMessage5(err)
|
|
@@ -3768,5 +4005,5 @@ export {
|
|
|
3768
4005
|
startSubgraphOperationRunner
|
|
3769
4006
|
};
|
|
3770
4007
|
|
|
3771
|
-
//# debugId=
|
|
4008
|
+
//# debugId=72D00DA8ABE4D8A264756E2164756E21
|
|
3772
4009
|
//# sourceMappingURL=processor.js.map
|