@secondlayer/subgraphs 3.5.0 → 3.6.1
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 +327 -113
- package/dist/src/index.js.map +12 -10
- package/dist/src/runtime/block-processor.js +292 -30
- package/dist/src/runtime/block-processor.js.map +8 -5
- package/dist/src/runtime/catchup.js +306 -91
- package/dist/src/runtime/catchup.js.map +9 -7
- package/dist/src/runtime/processor.js +433 -171
- package/dist/src/runtime/processor.js.map +14 -11
- package/dist/src/runtime/reindex.js +325 -111
- package/dist/src/runtime/reindex.js.map +10 -8
- package/dist/src/runtime/reorg.js +298 -36
- package/dist/src/runtime/reorg.js.map +8 -5
- package/dist/src/runtime/runner.d.ts +6 -1
- package/dist/src/runtime/runner.js +10 -7
- package/dist/src/runtime/runner.js.map +3 -3
- package/dist/src/schema/index.js.map +1 -1
- package/dist/src/service.js +436 -174
- package/dist/src/service.js.map +13 -10
- package/package.json +2 -2
|
@@ -518,21 +518,21 @@ function buildEventPayload(filter, tx, event) {
|
|
|
518
518
|
return {
|
|
519
519
|
sender: decoded.sender,
|
|
520
520
|
recipient: decoded.recipient,
|
|
521
|
-
tokenId: decoded.value,
|
|
521
|
+
tokenId: decoded.raw_value ?? decoded.value,
|
|
522
522
|
assetIdentifier: decoded.asset_identifier,
|
|
523
523
|
tx: txMeta
|
|
524
524
|
};
|
|
525
525
|
case "nft_mint":
|
|
526
526
|
return {
|
|
527
527
|
recipient: decoded.recipient,
|
|
528
|
-
tokenId: decoded.value,
|
|
528
|
+
tokenId: decoded.raw_value ?? decoded.value,
|
|
529
529
|
assetIdentifier: decoded.asset_identifier,
|
|
530
530
|
tx: txMeta
|
|
531
531
|
};
|
|
532
532
|
case "nft_burn":
|
|
533
533
|
return {
|
|
534
534
|
sender: decoded.sender,
|
|
535
|
-
tokenId: decoded.value,
|
|
535
|
+
tokenId: decoded.raw_value ?? decoded.value,
|
|
536
536
|
assetIdentifier: decoded.asset_identifier,
|
|
537
537
|
tx: txMeta
|
|
538
538
|
};
|
|
@@ -564,8 +564,8 @@ function buildEventPayload(filter, tx, event) {
|
|
|
564
564
|
tx: txMeta
|
|
565
565
|
};
|
|
566
566
|
case "print_event": {
|
|
567
|
-
const
|
|
568
|
-
const rawValue =
|
|
567
|
+
const rawHex = event.data?.raw_value;
|
|
568
|
+
const rawValue = typeof rawHex === "string" && rawHex.startsWith("0x") ? decodeClarityValue(rawHex) : decoded.value;
|
|
569
569
|
const clarityObj = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue) ? rawValue : null;
|
|
570
570
|
const topic = clarityObj?.topic ? String(clarityObj.topic) : decoded.topic ?? "";
|
|
571
571
|
const { topic: _, ...rest } = clarityObj ?? {};
|
|
@@ -578,9 +578,11 @@ function buildEventPayload(filter, tx, event) {
|
|
|
578
578
|
};
|
|
579
579
|
}
|
|
580
580
|
case "contract_call": {
|
|
581
|
+
const ccRawHex = event.data?.raw_value;
|
|
582
|
+
const normalized = typeof ccRawHex === "string" && ccRawHex.startsWith("0x") ? { ...decoded, value: decodeClarityValue(ccRawHex) } : decoded;
|
|
581
583
|
const input = buildContractCallInput(filter, tx);
|
|
582
584
|
return {
|
|
583
|
-
...
|
|
585
|
+
...normalized,
|
|
584
586
|
type: "contract_call",
|
|
585
587
|
_eventType: event.type,
|
|
586
588
|
contractId: tx.contract_id ?? "",
|
|
@@ -949,10 +951,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
|
|
|
949
951
|
}
|
|
950
952
|
|
|
951
953
|
// src/runtime/block-processor.ts
|
|
952
|
-
import {
|
|
953
|
-
getSourceDb,
|
|
954
|
-
getTargetDb
|
|
955
|
-
} from "@secondlayer/shared/db";
|
|
954
|
+
import { getTargetDb } from "@secondlayer/shared/db";
|
|
956
955
|
import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
|
|
957
956
|
import {
|
|
958
957
|
isByoSubgraph,
|
|
@@ -960,15 +959,286 @@ import {
|
|
|
960
959
|
resolveSubgraphDb,
|
|
961
960
|
updateSubgraphStatus
|
|
962
961
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
963
|
-
import { logger as
|
|
962
|
+
import { logger as logger5 } from "@secondlayer/shared/logger";
|
|
964
963
|
import { sql as sql3 } from "kysely";
|
|
965
964
|
|
|
966
965
|
// src/schema/utils.ts
|
|
967
966
|
import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
|
|
968
967
|
|
|
968
|
+
// src/runtime/block-source.ts
|
|
969
|
+
import { getSourceDb } from "@secondlayer/shared/db";
|
|
970
|
+
import { IndexHttpClient } from "@secondlayer/shared/index-http";
|
|
971
|
+
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
972
|
+
|
|
973
|
+
// src/runtime/batch-loader.ts
|
|
974
|
+
async function loadBlockRange(db, fromHeight, toHeight) {
|
|
975
|
+
const [blocks, txs, events] = await Promise.all([
|
|
976
|
+
db.selectFrom("blocks").selectAll().where("height", ">=", fromHeight).where("height", "<=", toHeight).where("canonical", "=", true).execute(),
|
|
977
|
+
db.selectFrom("transactions").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute(),
|
|
978
|
+
db.selectFrom("events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute()
|
|
979
|
+
]);
|
|
980
|
+
const txsByHeight = new Map;
|
|
981
|
+
for (const tx of txs) {
|
|
982
|
+
const h = Number(tx.block_height);
|
|
983
|
+
const list = txsByHeight.get(h) ?? [];
|
|
984
|
+
list.push(tx);
|
|
985
|
+
txsByHeight.set(h, list);
|
|
986
|
+
}
|
|
987
|
+
const eventsByHeight = new Map;
|
|
988
|
+
for (const evt of events) {
|
|
989
|
+
const h = Number(evt.block_height);
|
|
990
|
+
const list = eventsByHeight.get(h) ?? [];
|
|
991
|
+
list.push(evt);
|
|
992
|
+
eventsByHeight.set(h, list);
|
|
993
|
+
}
|
|
994
|
+
const result = new Map;
|
|
995
|
+
for (const block of blocks) {
|
|
996
|
+
const h = Number(block.height);
|
|
997
|
+
result.set(h, {
|
|
998
|
+
block,
|
|
999
|
+
txs: txsByHeight.get(h) ?? [],
|
|
1000
|
+
events: eventsByHeight.get(h) ?? []
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
return result;
|
|
1004
|
+
}
|
|
1005
|
+
function avgEventsPerBlock(batch) {
|
|
1006
|
+
if (batch.size === 0)
|
|
1007
|
+
return 0;
|
|
1008
|
+
let totalEvents = 0;
|
|
1009
|
+
for (const data of batch.values()) {
|
|
1010
|
+
totalEvents += data.events.length;
|
|
1011
|
+
}
|
|
1012
|
+
return totalEvents / batch.size;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/runtime/reconstruct.ts
|
|
1016
|
+
function isoToUnixSeconds(iso) {
|
|
1017
|
+
if (!iso)
|
|
1018
|
+
return 0;
|
|
1019
|
+
return Math.floor(new Date(iso).getTime() / 1000);
|
|
1020
|
+
}
|
|
1021
|
+
function reconstructBlock(b) {
|
|
1022
|
+
return {
|
|
1023
|
+
height: b.block_height,
|
|
1024
|
+
hash: b.block_hash,
|
|
1025
|
+
parent_hash: b.parent_hash,
|
|
1026
|
+
burn_block_height: b.burn_block_height,
|
|
1027
|
+
burn_block_hash: b.burn_block_hash,
|
|
1028
|
+
timestamp: isoToUnixSeconds(b.block_time),
|
|
1029
|
+
canonical: true,
|
|
1030
|
+
created_at: new Date(0)
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
function reconstructTransaction(t) {
|
|
1034
|
+
return {
|
|
1035
|
+
tx_id: t.tx_id,
|
|
1036
|
+
block_height: t.block_height,
|
|
1037
|
+
tx_index: t.tx_index,
|
|
1038
|
+
type: t.tx_type,
|
|
1039
|
+
sender: t.sender,
|
|
1040
|
+
status: t.status,
|
|
1041
|
+
contract_id: t.contract_call?.contract_id ?? t.smart_contract?.contract_id ?? null,
|
|
1042
|
+
function_name: t.contract_call?.function_name ?? null,
|
|
1043
|
+
function_args: t.contract_call?.function_args_hex ?? [],
|
|
1044
|
+
raw_result: t.contract_call?.result_hex ?? null,
|
|
1045
|
+
raw_tx: "",
|
|
1046
|
+
created_at: new Date(0)
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function reconstructEvent(e) {
|
|
1050
|
+
const base = {
|
|
1051
|
+
id: `${e.tx_id}#${e.event_index}`,
|
|
1052
|
+
tx_id: e.tx_id,
|
|
1053
|
+
block_height: e.block_height,
|
|
1054
|
+
event_index: e.event_index,
|
|
1055
|
+
created_at: new Date(0)
|
|
1056
|
+
};
|
|
1057
|
+
switch (e.event_type) {
|
|
1058
|
+
case "ft_transfer":
|
|
1059
|
+
case "ft_mint":
|
|
1060
|
+
case "ft_burn":
|
|
1061
|
+
return {
|
|
1062
|
+
...base,
|
|
1063
|
+
type: `${e.event_type}_event`,
|
|
1064
|
+
data: {
|
|
1065
|
+
asset_identifier: e.asset_identifier,
|
|
1066
|
+
sender: e.sender,
|
|
1067
|
+
recipient: e.recipient,
|
|
1068
|
+
amount: e.amount
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
case "nft_transfer":
|
|
1072
|
+
case "nft_mint":
|
|
1073
|
+
case "nft_burn":
|
|
1074
|
+
return {
|
|
1075
|
+
...base,
|
|
1076
|
+
type: `${e.event_type}_event`,
|
|
1077
|
+
data: {
|
|
1078
|
+
asset_identifier: e.asset_identifier,
|
|
1079
|
+
sender: e.sender,
|
|
1080
|
+
recipient: e.recipient,
|
|
1081
|
+
raw_value: e.value
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
case "stx_transfer":
|
|
1085
|
+
case "stx_mint":
|
|
1086
|
+
case "stx_burn":
|
|
1087
|
+
return {
|
|
1088
|
+
...base,
|
|
1089
|
+
type: `${e.event_type}_event`,
|
|
1090
|
+
data: {
|
|
1091
|
+
sender: e.sender,
|
|
1092
|
+
recipient: e.recipient,
|
|
1093
|
+
amount: e.amount,
|
|
1094
|
+
...e.event_type === "stx_transfer" ? { memo: e.memo ?? "" } : {}
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
case "stx_lock":
|
|
1098
|
+
return {
|
|
1099
|
+
...base,
|
|
1100
|
+
type: "stx_lock_event",
|
|
1101
|
+
data: {
|
|
1102
|
+
locked_address: e.sender,
|
|
1103
|
+
locked_amount: e.amount,
|
|
1104
|
+
unlock_height: e.payload.unlock_height
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
case "print":
|
|
1108
|
+
return {
|
|
1109
|
+
...base,
|
|
1110
|
+
type: "contract_event",
|
|
1111
|
+
data: {
|
|
1112
|
+
topic: e.payload.topic,
|
|
1113
|
+
contract_identifier: e.contract_id,
|
|
1114
|
+
value: e.payload.value,
|
|
1115
|
+
raw_value: e.payload.raw_value
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/runtime/block-source.ts
|
|
1122
|
+
class PostgresBlockSource {
|
|
1123
|
+
async getTip() {
|
|
1124
|
+
const progress = await getSourceDb().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
|
|
1125
|
+
return progress ? Number(progress.highest_seen_block) : 0;
|
|
1126
|
+
}
|
|
1127
|
+
loadBlockRange(fromHeight, toHeight) {
|
|
1128
|
+
return loadBlockRange(getSourceDb(), fromHeight, toHeight);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
var EVENT_FILTER_TO_INDEX_TYPE = {
|
|
1132
|
+
stx_transfer: "stx_transfer",
|
|
1133
|
+
stx_mint: "stx_mint",
|
|
1134
|
+
stx_burn: "stx_burn",
|
|
1135
|
+
stx_lock: "stx_lock",
|
|
1136
|
+
ft_transfer: "ft_transfer",
|
|
1137
|
+
ft_mint: "ft_mint",
|
|
1138
|
+
ft_burn: "ft_burn",
|
|
1139
|
+
nft_transfer: "nft_transfer",
|
|
1140
|
+
nft_mint: "nft_mint",
|
|
1141
|
+
nft_burn: "nft_burn",
|
|
1142
|
+
print_event: "print"
|
|
1143
|
+
};
|
|
1144
|
+
var TX_SOURCE_TYPES = new Set(["contract_call", "contract_deploy"]);
|
|
1145
|
+
var ALL_INDEX_EVENT_TYPES = [
|
|
1146
|
+
...new Set(Object.values(EVENT_FILTER_TO_INDEX_TYPE))
|
|
1147
|
+
];
|
|
1148
|
+
function sourceFilters(subgraph) {
|
|
1149
|
+
const sources = subgraph.sources;
|
|
1150
|
+
return Array.isArray(sources) ? sources : Object.values(sources);
|
|
1151
|
+
}
|
|
1152
|
+
function referencedIndexEventTypes(subgraph) {
|
|
1153
|
+
const filters = sourceFilters(subgraph);
|
|
1154
|
+
if (filters.some((f) => TX_SOURCE_TYPES.has(f.type))) {
|
|
1155
|
+
return ALL_INDEX_EVENT_TYPES;
|
|
1156
|
+
}
|
|
1157
|
+
const types = new Set;
|
|
1158
|
+
for (const f of filters) {
|
|
1159
|
+
const t = EVENT_FILTER_TO_INDEX_TYPE[f.type];
|
|
1160
|
+
if (t)
|
|
1161
|
+
types.add(t);
|
|
1162
|
+
}
|
|
1163
|
+
return [...types];
|
|
1164
|
+
}
|
|
1165
|
+
function isStreamsIndexEligible(subgraph) {
|
|
1166
|
+
if (Array.isArray(subgraph.sources))
|
|
1167
|
+
return false;
|
|
1168
|
+
const filters = sourceFilters(subgraph);
|
|
1169
|
+
if (filters.length === 0)
|
|
1170
|
+
return false;
|
|
1171
|
+
for (const f of filters) {
|
|
1172
|
+
const known = EVENT_FILTER_TO_INDEX_TYPE[f.type] || TX_SOURCE_TYPES.has(f.type);
|
|
1173
|
+
if (!known)
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
return true;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
class PublicApiBlockSource {
|
|
1180
|
+
http;
|
|
1181
|
+
eventTypes;
|
|
1182
|
+
constructor(http, eventTypes) {
|
|
1183
|
+
this.http = http;
|
|
1184
|
+
this.eventTypes = eventTypes;
|
|
1185
|
+
}
|
|
1186
|
+
getTip() {
|
|
1187
|
+
return this.http.getIndexTip();
|
|
1188
|
+
}
|
|
1189
|
+
async loadBlockRange(fromHeight, toHeight) {
|
|
1190
|
+
const [blocks, txs, eventLists] = await Promise.all([
|
|
1191
|
+
this.http.walkBlocks(fromHeight, toHeight),
|
|
1192
|
+
this.http.walkTransactions(fromHeight, toHeight),
|
|
1193
|
+
Promise.all(this.eventTypes.map((t) => this.http.walkEvents(t, fromHeight, toHeight)))
|
|
1194
|
+
]);
|
|
1195
|
+
const map = new Map;
|
|
1196
|
+
for (const b of blocks) {
|
|
1197
|
+
map.set(b.block_height, {
|
|
1198
|
+
block: reconstructBlock(b),
|
|
1199
|
+
txs: [],
|
|
1200
|
+
events: []
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
for (const t of txs) {
|
|
1204
|
+
map.get(t.block_height)?.txs.push(reconstructTransaction(t));
|
|
1205
|
+
}
|
|
1206
|
+
for (const list of eventLists) {
|
|
1207
|
+
for (const e of list) {
|
|
1208
|
+
map.get(e.block_height)?.events.push(reconstructEvent(e));
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
for (const bd of map.values()) {
|
|
1212
|
+
bd.txs.sort((a, b) => a.tx_index - b.tx_index);
|
|
1213
|
+
bd.events.sort((a, b) => a.event_index - b.event_index);
|
|
1214
|
+
}
|
|
1215
|
+
return map;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
var postgresBlockSource = new PostgresBlockSource;
|
|
1219
|
+
function buildHttpClient() {
|
|
1220
|
+
const baseUrl = process.env.SUBGRAPH_INDEX_API_URL ?? process.env.STREAMS_API_URL ?? "http://api:3800";
|
|
1221
|
+
return new IndexHttpClient({
|
|
1222
|
+
indexBaseUrl: baseUrl,
|
|
1223
|
+
streamsBaseUrl: baseUrl,
|
|
1224
|
+
streamsApiKey: process.env.STREAMS_INTERNAL_API_KEY ?? "sk-sl_streams_l2_internal"
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
function resolveBlockSource(subgraph) {
|
|
1228
|
+
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph && isStreamsIndexEligible(subgraph)) {
|
|
1229
|
+
return new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph));
|
|
1230
|
+
}
|
|
1231
|
+
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
|
|
1232
|
+
logger3.debug("Subgraph not streams-index eligible, using DB tap", {
|
|
1233
|
+
subgraph: subgraph.name
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
return postgresBlockSource;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
969
1239
|
// src/runtime/outbox-emit.ts
|
|
970
1240
|
import { createHash } from "node:crypto";
|
|
971
|
-
import { logger as
|
|
1241
|
+
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
972
1242
|
var loggedKillSwitch = false;
|
|
973
1243
|
function isEmitOutboxEnabled() {
|
|
974
1244
|
return process.env.SECONDLAYER_EMIT_OUTBOX !== "false";
|
|
@@ -987,7 +1257,7 @@ function stableStringify(obj) {
|
|
|
987
1257
|
async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, blockHeight) {
|
|
988
1258
|
if (!isEmitOutboxEnabled()) {
|
|
989
1259
|
if (!loggedKillSwitch) {
|
|
990
|
-
|
|
1260
|
+
logger4.warn("SECONDLAYER_EMIT_OUTBOX=false — outbox emission bypassed");
|
|
991
1261
|
loggedKillSwitch = true;
|
|
992
1262
|
}
|
|
993
1263
|
return 0;
|
|
@@ -1228,7 +1498,6 @@ async function resolveTraitContracts(subgraph, blockHeight, db) {
|
|
|
1228
1498
|
return resolved;
|
|
1229
1499
|
}
|
|
1230
1500
|
async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
1231
|
-
const sourceDb = getSourceDb();
|
|
1232
1501
|
const targetDb = getTargetDb();
|
|
1233
1502
|
const blockStart = performance.now();
|
|
1234
1503
|
const result = {
|
|
@@ -1246,25 +1515,18 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
1246
1515
|
txs = opts.preloaded.txs;
|
|
1247
1516
|
evts = opts.preloaded.events;
|
|
1248
1517
|
} else {
|
|
1249
|
-
|
|
1250
|
-
if (!
|
|
1251
|
-
|
|
1252
|
-
subgraph: subgraphName,
|
|
1253
|
-
blockHeight
|
|
1254
|
-
});
|
|
1255
|
-
result.skipped = true;
|
|
1256
|
-
return result;
|
|
1257
|
-
}
|
|
1258
|
-
if (!block.canonical) {
|
|
1259
|
-
logger4.debug("Skipping non-canonical block", {
|
|
1518
|
+
const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
|
|
1519
|
+
if (!data) {
|
|
1520
|
+
logger5.debug("Block not found or non-canonical for subgraph processing", {
|
|
1260
1521
|
subgraph: subgraphName,
|
|
1261
1522
|
blockHeight
|
|
1262
1523
|
});
|
|
1263
1524
|
result.skipped = true;
|
|
1264
1525
|
return result;
|
|
1265
1526
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1527
|
+
block = data.block;
|
|
1528
|
+
txs = data.txs;
|
|
1529
|
+
evts = data.events;
|
|
1268
1530
|
}
|
|
1269
1531
|
const traitContracts = await resolveTraitContracts(subgraph, blockHeight, targetDb);
|
|
1270
1532
|
const matched = matchSources(subgraph.sources, txs, evts, traitContracts);
|
|
@@ -1355,7 +1617,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
1355
1617
|
const { rows } = await sql3.raw(`SELECT n_live_tup AS count FROM pg_stat_user_tables WHERE schemaname = '${schemaName}' AND relname = '${table}'`).execute(route.dataDb);
|
|
1356
1618
|
const count = Number(rows[0]?.count ?? 0);
|
|
1357
1619
|
if (count >= 1e7) {
|
|
1358
|
-
|
|
1620
|
+
logger5.warn("Subgraph table exceeds 10M rows (estimate)", {
|
|
1359
1621
|
subgraph: subgraphName,
|
|
1360
1622
|
table,
|
|
1361
1623
|
count
|
|
@@ -1363,7 +1625,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
1363
1625
|
}
|
|
1364
1626
|
}
|
|
1365
1627
|
} catch (err) {
|
|
1366
|
-
|
|
1628
|
+
logger5.debug("Row count sample failed", {
|
|
1367
1629
|
subgraph: subgraphName,
|
|
1368
1630
|
error: err instanceof Error ? err.message : String(err)
|
|
1369
1631
|
});
|
|
@@ -1436,56 +1698,12 @@ class StatsAccumulator {
|
|
|
1436
1698
|
}
|
|
1437
1699
|
|
|
1438
1700
|
// src/runtime/catchup.ts
|
|
1439
|
-
import {
|
|
1701
|
+
import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
|
|
1440
1702
|
import {
|
|
1441
1703
|
recordGapBatch
|
|
1442
1704
|
} from "@secondlayer/shared/db/queries/subgraph-gaps";
|
|
1443
1705
|
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
1444
|
-
import { logger as
|
|
1445
|
-
|
|
1446
|
-
// src/runtime/batch-loader.ts
|
|
1447
|
-
async function loadBlockRange(db, fromHeight, toHeight) {
|
|
1448
|
-
const [blocks, txs, events] = await Promise.all([
|
|
1449
|
-
db.selectFrom("blocks").selectAll().where("height", ">=", fromHeight).where("height", "<=", toHeight).where("canonical", "=", true).execute(),
|
|
1450
|
-
db.selectFrom("transactions").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute(),
|
|
1451
|
-
db.selectFrom("events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute()
|
|
1452
|
-
]);
|
|
1453
|
-
const txsByHeight = new Map;
|
|
1454
|
-
for (const tx of txs) {
|
|
1455
|
-
const h = Number(tx.block_height);
|
|
1456
|
-
const list = txsByHeight.get(h) ?? [];
|
|
1457
|
-
list.push(tx);
|
|
1458
|
-
txsByHeight.set(h, list);
|
|
1459
|
-
}
|
|
1460
|
-
const eventsByHeight = new Map;
|
|
1461
|
-
for (const evt of events) {
|
|
1462
|
-
const h = Number(evt.block_height);
|
|
1463
|
-
const list = eventsByHeight.get(h) ?? [];
|
|
1464
|
-
list.push(evt);
|
|
1465
|
-
eventsByHeight.set(h, list);
|
|
1466
|
-
}
|
|
1467
|
-
const result = new Map;
|
|
1468
|
-
for (const block of blocks) {
|
|
1469
|
-
const h = Number(block.height);
|
|
1470
|
-
result.set(h, {
|
|
1471
|
-
block,
|
|
1472
|
-
txs: txsByHeight.get(h) ?? [],
|
|
1473
|
-
events: eventsByHeight.get(h) ?? []
|
|
1474
|
-
});
|
|
1475
|
-
}
|
|
1476
|
-
return result;
|
|
1477
|
-
}
|
|
1478
|
-
function avgEventsPerBlock(batch) {
|
|
1479
|
-
if (batch.size === 0)
|
|
1480
|
-
return 0;
|
|
1481
|
-
let totalEvents = 0;
|
|
1482
|
-
for (const data of batch.values()) {
|
|
1483
|
-
totalEvents += data.events.length;
|
|
1484
|
-
}
|
|
1485
|
-
return totalEvents / batch.size;
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
// src/runtime/catchup.ts
|
|
1706
|
+
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
1489
1707
|
var LOG_INTERVAL = 1000;
|
|
1490
1708
|
var STANDARD_CATCHUP_BATCH_CONFIG = {
|
|
1491
1709
|
defaultBatchSize: 500,
|
|
@@ -1556,22 +1774,19 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
1556
1774
|
return 0;
|
|
1557
1775
|
catchingUp.add(subgraphName);
|
|
1558
1776
|
try {
|
|
1559
|
-
const
|
|
1777
|
+
const source = resolveBlockSource(subgraph);
|
|
1560
1778
|
const targetDb = getTargetDb2();
|
|
1561
1779
|
const subgraphRow = await getSubgraph(targetDb, subgraphName);
|
|
1562
1780
|
if (!subgraphRow)
|
|
1563
1781
|
return 0;
|
|
1564
1782
|
const lastProcessedBlock = Number(subgraphRow.last_processed_block);
|
|
1565
|
-
const
|
|
1566
|
-
if (
|
|
1567
|
-
return 0;
|
|
1568
|
-
const chainTip = Number(progress.highest_seen_block);
|
|
1569
|
-
if (lastProcessedBlock >= chainTip)
|
|
1783
|
+
const chainTip = await source.getTip();
|
|
1784
|
+
if (chainTip <= 0 || lastProcessedBlock >= chainTip)
|
|
1570
1785
|
return 0;
|
|
1571
1786
|
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
1572
1787
|
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
1573
1788
|
const totalBlocks = chainTip - lastProcessedBlock;
|
|
1574
|
-
|
|
1789
|
+
logger6.info("Subgraph catch-up starting", {
|
|
1575
1790
|
subgraph: subgraphName,
|
|
1576
1791
|
from: startBlock,
|
|
1577
1792
|
to: chainTip,
|
|
@@ -1583,11 +1798,11 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
1583
1798
|
let batchSize = batchConfig.defaultBatchSize;
|
|
1584
1799
|
let currentHeight = startBlock;
|
|
1585
1800
|
let prefetchedBatchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
1586
|
-
let nextBatchPromise = batchConfig.prefetch ? loadBlockRange(
|
|
1801
|
+
let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
|
|
1587
1802
|
while (currentHeight <= chainTip) {
|
|
1588
1803
|
const currentRow = await getSubgraph(targetDb, subgraphName);
|
|
1589
1804
|
if (!currentRow || currentRow.status !== "active") {
|
|
1590
|
-
|
|
1805
|
+
logger6.info("Subgraph status changed, stopping catch-up", {
|
|
1591
1806
|
subgraph: subgraphName,
|
|
1592
1807
|
status: currentRow?.status ?? "deleted"
|
|
1593
1808
|
});
|
|
@@ -1601,13 +1816,13 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
1601
1816
|
const nextStart = batchEnd + 1;
|
|
1602
1817
|
if (nextStart <= chainTip) {
|
|
1603
1818
|
prefetchedBatchEnd = Math.min(nextStart + batchSize - 1, chainTip);
|
|
1604
|
-
nextBatchPromise = loadBlockRange(
|
|
1819
|
+
nextBatchPromise = source.loadBlockRange(nextStart, prefetchedBatchEnd);
|
|
1605
1820
|
} else {
|
|
1606
1821
|
nextBatchPromise = undefined;
|
|
1607
1822
|
}
|
|
1608
1823
|
} else {
|
|
1609
1824
|
batchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
|
|
1610
|
-
batch = await loadBlockRange(
|
|
1825
|
+
batch = await source.loadBlockRange(currentHeight, batchEnd);
|
|
1611
1826
|
}
|
|
1612
1827
|
const batchFailedBlocks = [];
|
|
1613
1828
|
for (let height = currentHeight;height <= batchEnd; height++) {
|
|
@@ -1623,7 +1838,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
1623
1838
|
preloaded: blockData
|
|
1624
1839
|
});
|
|
1625
1840
|
} catch (err) {
|
|
1626
|
-
|
|
1841
|
+
logger6.error("Block processing error during catch-up", {
|
|
1627
1842
|
subgraph: subgraphName,
|
|
1628
1843
|
blockHeight: height,
|
|
1629
1844
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -1642,7 +1857,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
1642
1857
|
}
|
|
1643
1858
|
}
|
|
1644
1859
|
if (processed % LOG_INTERVAL === 0) {
|
|
1645
|
-
|
|
1860
|
+
logger6.info("Subgraph catch-up progress", {
|
|
1646
1861
|
subgraph: subgraphName,
|
|
1647
1862
|
processed,
|
|
1648
1863
|
total: totalBlocks,
|
|
@@ -1654,7 +1869,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
1654
1869
|
if (batchFailedBlocks.length > 0) {
|
|
1655
1870
|
const gaps = coalesceGaps(batchFailedBlocks);
|
|
1656
1871
|
await recordGapBatch(targetDb, subgraphRow.id, subgraphName, gaps).catch((err) => {
|
|
1657
|
-
|
|
1872
|
+
logger6.warn("Failed to record subgraph gaps", {
|
|
1658
1873
|
subgraph: subgraphName,
|
|
1659
1874
|
error: err instanceof Error ? err.message : String(err)
|
|
1660
1875
|
});
|
|
@@ -1665,7 +1880,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
1665
1880
|
currentHeight = batchEnd + 1;
|
|
1666
1881
|
}
|
|
1667
1882
|
await stats.flush(targetDb);
|
|
1668
|
-
|
|
1883
|
+
logger6.info("Subgraph catch-up complete", {
|
|
1669
1884
|
subgraph: subgraphName,
|
|
1670
1885
|
processed
|
|
1671
1886
|
});
|
|
@@ -1679,5 +1894,5 @@ export {
|
|
|
1679
1894
|
catchUpSubgraph
|
|
1680
1895
|
};
|
|
1681
1896
|
|
|
1682
|
-
//# debugId=
|
|
1897
|
+
//# debugId=2911A2929B5325F464756E2164756E21
|
|
1683
1898
|
//# sourceMappingURL=catchup.js.map
|