@secondlayer/subgraphs 4.1.4 → 4.2.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 +91 -31
- package/dist/src/index.js +3319 -3067
- package/dist/src/index.js.map +11 -9
- package/dist/src/runtime/block-processor.d.ts +72 -23
- package/dist/src/runtime/block-processor.js +867 -400
- package/dist/src/runtime/block-processor.js.map +9 -6
- package/dist/src/runtime/catchup.d.ts +72 -23
- package/dist/src/runtime/catchup.js +878 -411
- package/dist/src/runtime/catchup.js.map +9 -6
- package/dist/src/runtime/processor.js +943 -475
- package/dist/src/runtime/processor.js.map +10 -7
- package/dist/src/runtime/reindex.d.ts +72 -23
- package/dist/src/runtime/reindex.js +911 -444
- package/dist/src/runtime/reindex.js.map +9 -6
- package/dist/src/runtime/reorg.d.ts +72 -23
- package/dist/src/runtime/reorg.js +890 -422
- package/dist/src/runtime/reorg.js.map +10 -7
- package/dist/src/runtime/replay.js +12 -6
- package/dist/src/runtime/replay.js.map +4 -4
- package/dist/src/runtime/runner.d.ts +72 -23
- package/dist/src/runtime/runner.js +757 -28
- package/dist/src/runtime/runner.js.map +9 -5
- package/dist/src/runtime/source-matcher.d.ts +80 -24
- package/dist/src/runtime/source-matcher.js +12 -5
- package/dist/src/runtime/source-matcher.js.map +3 -3
- package/dist/src/schema/index.d.ts +72 -23
- package/dist/src/schema/index.js +115 -1
- package/dist/src/schema/index.js.map +3 -3
- package/dist/src/service.js +983 -515
- package/dist/src/service.js.map +10 -7
- package/dist/src/testing/index.d.ts +195 -24
- package/dist/src/testing/index.js +1049 -30
- package/dist/src/testing/index.js.map +10 -5
- package/dist/src/triggers/index.d.ts +83 -24
- package/dist/src/triggers/index.js +5 -2
- package/dist/src/triggers/index.js.map +3 -3
- package/dist/src/types.d.ts +73 -24
- package/dist/src/validate.d.ts +72 -23
- package/dist/src/validate.js +115 -1
- package/dist/src/validate.js.map +3 -3
- package/package.json +2 -2
package/dist/src/service.js
CHANGED
|
@@ -942,15 +942,679 @@ function decodeFunctionArgs(args) {
|
|
|
942
942
|
return args.map(decodeClarityValue);
|
|
943
943
|
}
|
|
944
944
|
|
|
945
|
+
// src/runtime/source-matcher.ts
|
|
946
|
+
var patternCache = new Map;
|
|
947
|
+
function matchAny(value, pattern) {
|
|
948
|
+
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
949
|
+
}
|
|
950
|
+
function matchPattern(value, pattern) {
|
|
951
|
+
if (!pattern.includes("*"))
|
|
952
|
+
return value === pattern;
|
|
953
|
+
let re = patternCache.get(pattern);
|
|
954
|
+
if (!re) {
|
|
955
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
956
|
+
re = new RegExp(`^${regex}$`);
|
|
957
|
+
patternCache.set(pattern, re);
|
|
958
|
+
}
|
|
959
|
+
return re.test(value);
|
|
960
|
+
}
|
|
961
|
+
var EMPTY_SET = new Set;
|
|
962
|
+
function factoryAllows(filter, contractId, factoryContracts) {
|
|
963
|
+
if (!filter.factory)
|
|
964
|
+
return true;
|
|
965
|
+
if (!contractId)
|
|
966
|
+
return false;
|
|
967
|
+
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
968
|
+
}
|
|
969
|
+
function readPath(payload, path) {
|
|
970
|
+
let cur = payload;
|
|
971
|
+
for (const key of path.split(".")) {
|
|
972
|
+
if (cur === null || typeof cur !== "object")
|
|
973
|
+
return;
|
|
974
|
+
cur = cur[key];
|
|
975
|
+
}
|
|
976
|
+
return cur;
|
|
977
|
+
}
|
|
978
|
+
function traitAllows(filter, contractId, traitContracts) {
|
|
979
|
+
const trait = filter.trait;
|
|
980
|
+
if (!trait)
|
|
981
|
+
return true;
|
|
982
|
+
if (!contractId)
|
|
983
|
+
return false;
|
|
984
|
+
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
985
|
+
}
|
|
986
|
+
function assetContract(assetId) {
|
|
987
|
+
return assetId?.split("::")[0];
|
|
988
|
+
}
|
|
989
|
+
function printContractId(data) {
|
|
990
|
+
if (!data)
|
|
991
|
+
return;
|
|
992
|
+
const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
|
|
993
|
+
return id || undefined;
|
|
994
|
+
}
|
|
995
|
+
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
|
|
996
|
+
const results = [];
|
|
997
|
+
switch (filter.type) {
|
|
998
|
+
case "stx_transfer":
|
|
999
|
+
case "stx_mint":
|
|
1000
|
+
case "stx_burn":
|
|
1001
|
+
case "stx_lock": {
|
|
1002
|
+
const eventType = `${filter.type}_event`;
|
|
1003
|
+
for (const tx of transactions) {
|
|
1004
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1005
|
+
const matched = txEvents.filter((e) => e.type === eventType);
|
|
1006
|
+
if (matched.length === 0)
|
|
1007
|
+
continue;
|
|
1008
|
+
const filtered = matched.filter((e) => {
|
|
1009
|
+
const data = e.data;
|
|
1010
|
+
if (!data)
|
|
1011
|
+
return false;
|
|
1012
|
+
if ("sender" in filter && filter.sender) {
|
|
1013
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
if ("recipient" in filter && filter.recipient) {
|
|
1017
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
1021
|
+
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
1025
|
+
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
1026
|
+
if (amount < filter.minAmount)
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
1030
|
+
const amount = BigInt(data.amount ?? "0");
|
|
1031
|
+
if (amount > filter.maxAmount)
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
return true;
|
|
1035
|
+
});
|
|
1036
|
+
if (filtered.length > 0) {
|
|
1037
|
+
results.push({ tx, events: filtered });
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
case "ft_transfer":
|
|
1043
|
+
case "ft_mint":
|
|
1044
|
+
case "ft_burn": {
|
|
1045
|
+
const eventType = `${filter.type}_event`;
|
|
1046
|
+
for (const tx of transactions) {
|
|
1047
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1048
|
+
const matched = txEvents.filter((e) => {
|
|
1049
|
+
if (e.type !== eventType)
|
|
1050
|
+
return false;
|
|
1051
|
+
const data = e.data;
|
|
1052
|
+
if (!data)
|
|
1053
|
+
return false;
|
|
1054
|
+
if (filter.assetIdentifier) {
|
|
1055
|
+
const assetId = data.asset_identifier;
|
|
1056
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1060
|
+
return false;
|
|
1061
|
+
if ("sender" in filter && filter.sender) {
|
|
1062
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
if ("recipient" in filter && filter.recipient) {
|
|
1066
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
if (filter.minAmount !== undefined) {
|
|
1070
|
+
const amount = BigInt(data.amount ?? "0");
|
|
1071
|
+
if (amount < filter.minAmount)
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
return true;
|
|
1075
|
+
});
|
|
1076
|
+
if (matched.length > 0) {
|
|
1077
|
+
results.push({ tx, events: matched });
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
case "nft_transfer":
|
|
1083
|
+
case "nft_mint":
|
|
1084
|
+
case "nft_burn": {
|
|
1085
|
+
const eventType = `${filter.type}_event`;
|
|
1086
|
+
for (const tx of transactions) {
|
|
1087
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1088
|
+
const matched = txEvents.filter((e) => {
|
|
1089
|
+
if (e.type !== eventType)
|
|
1090
|
+
return false;
|
|
1091
|
+
const data = e.data;
|
|
1092
|
+
if (!data)
|
|
1093
|
+
return false;
|
|
1094
|
+
if (filter.assetIdentifier) {
|
|
1095
|
+
const assetId = data.asset_identifier;
|
|
1096
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1100
|
+
return false;
|
|
1101
|
+
if ("sender" in filter && filter.sender) {
|
|
1102
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
if ("recipient" in filter && filter.recipient) {
|
|
1106
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
return true;
|
|
1110
|
+
});
|
|
1111
|
+
if (matched.length > 0) {
|
|
1112
|
+
results.push({ tx, events: matched });
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
case "contract_call": {
|
|
1118
|
+
for (const tx of transactions) {
|
|
1119
|
+
if (tx.type !== "contract_call")
|
|
1120
|
+
continue;
|
|
1121
|
+
if (filter.contractId) {
|
|
1122
|
+
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
1126
|
+
continue;
|
|
1127
|
+
if (filter.functionName) {
|
|
1128
|
+
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
if (filter.caller) {
|
|
1132
|
+
if (!matchPattern(tx.sender, filter.caller))
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
1136
|
+
continue;
|
|
1137
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1138
|
+
results.push({ tx, events: txEvents });
|
|
1139
|
+
}
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
case "contract_deploy": {
|
|
1143
|
+
for (const tx of transactions) {
|
|
1144
|
+
if (tx.type !== "smart_contract")
|
|
1145
|
+
continue;
|
|
1146
|
+
if (filter.deployer) {
|
|
1147
|
+
if (!matchPattern(tx.sender, filter.deployer))
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (filter.contractName) {
|
|
1151
|
+
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
1152
|
+
if (!matchPattern(name, filter.contractName))
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1156
|
+
results.push({ tx, events: txEvents });
|
|
1157
|
+
}
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
case "print_event": {
|
|
1161
|
+
for (const tx of transactions) {
|
|
1162
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1163
|
+
const matched = txEvents.filter((e) => {
|
|
1164
|
+
if (e.type !== "smart_contract_event" && e.type !== "contract_event")
|
|
1165
|
+
return false;
|
|
1166
|
+
const data = e.data;
|
|
1167
|
+
if (!data)
|
|
1168
|
+
return false;
|
|
1169
|
+
if (data.topic !== "print")
|
|
1170
|
+
return false;
|
|
1171
|
+
const contractId = printContractId(data);
|
|
1172
|
+
if (filter.contractId) {
|
|
1173
|
+
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
if (!traitAllows(filter, contractId, traitContracts))
|
|
1177
|
+
return false;
|
|
1178
|
+
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
1179
|
+
return false;
|
|
1180
|
+
return true;
|
|
1181
|
+
});
|
|
1182
|
+
if (matched.length > 0) {
|
|
1183
|
+
results.push({ tx, events: matched });
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return results;
|
|
1190
|
+
}
|
|
1191
|
+
function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
|
|
1192
|
+
const eventsByTx = new Map;
|
|
1193
|
+
for (const event of events) {
|
|
1194
|
+
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
1195
|
+
list.push(event);
|
|
1196
|
+
eventsByTx.set(event.tx_id, list);
|
|
1197
|
+
}
|
|
1198
|
+
const seen = new Set;
|
|
1199
|
+
const results = [];
|
|
1200
|
+
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
1201
|
+
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
|
|
1202
|
+
for (const match of matches) {
|
|
1203
|
+
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
1204
|
+
if (!seen.has(dedupeKey)) {
|
|
1205
|
+
seen.add(dedupeKey);
|
|
1206
|
+
results.push({ ...match, sourceName });
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return results;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
945
1213
|
// src/runtime/runner.ts
|
|
946
1214
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
947
|
-
import { logger as
|
|
1215
|
+
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
948
1216
|
import {
|
|
949
1217
|
clarityValueToJS,
|
|
950
|
-
deserializeCV as
|
|
1218
|
+
deserializeCV as deserializeCV4,
|
|
951
1219
|
toCamelCase as toCamelCase2
|
|
952
1220
|
} from "@secondlayer/stacks/clarity";
|
|
953
1221
|
|
|
1222
|
+
// src/print-schema.ts
|
|
1223
|
+
import {
|
|
1224
|
+
deserializeCV as deserializeCV3
|
|
1225
|
+
} from "@secondlayer/stacks/clarity";
|
|
1226
|
+
function camelizeDataKey(str) {
|
|
1227
|
+
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1228
|
+
}
|
|
1229
|
+
function camelizeKeys(obj) {
|
|
1230
|
+
if (obj === null || obj === undefined)
|
|
1231
|
+
return obj;
|
|
1232
|
+
if (typeof obj !== "object")
|
|
1233
|
+
return obj;
|
|
1234
|
+
if (Array.isArray(obj))
|
|
1235
|
+
return obj.map(camelizeKeys);
|
|
1236
|
+
const result = {};
|
|
1237
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1238
|
+
result[camelizeDataKey(k)] = camelizeKeys(v);
|
|
1239
|
+
}
|
|
1240
|
+
return result;
|
|
1241
|
+
}
|
|
1242
|
+
function cvToTree(cv) {
|
|
1243
|
+
switch (cv.type) {
|
|
1244
|
+
case "uint":
|
|
1245
|
+
case "int":
|
|
1246
|
+
return { kind: cv.type };
|
|
1247
|
+
case "true":
|
|
1248
|
+
case "false":
|
|
1249
|
+
return { kind: "bool" };
|
|
1250
|
+
case "address":
|
|
1251
|
+
case "contract":
|
|
1252
|
+
return { kind: "principal" };
|
|
1253
|
+
case "buffer":
|
|
1254
|
+
return { kind: "buffer", len: cv.value.length / 2 };
|
|
1255
|
+
case "ascii":
|
|
1256
|
+
return { kind: "ascii", len: cv.value.length };
|
|
1257
|
+
case "utf8":
|
|
1258
|
+
return { kind: "utf8", len: new TextEncoder().encode(cv.value).length };
|
|
1259
|
+
case "none":
|
|
1260
|
+
return { kind: "optional", inner: null };
|
|
1261
|
+
case "some":
|
|
1262
|
+
return { kind: "optional", inner: cvToTree(cv.value) };
|
|
1263
|
+
case "ok":
|
|
1264
|
+
return { kind: "response", ok: cvToTree(cv.value), err: null };
|
|
1265
|
+
case "err":
|
|
1266
|
+
return { kind: "response", ok: null, err: cvToTree(cv.value) };
|
|
1267
|
+
case "list": {
|
|
1268
|
+
let inner = null;
|
|
1269
|
+
for (const el of cv.value) {
|
|
1270
|
+
const t = cvToTree(el);
|
|
1271
|
+
inner = inner ? unify(inner, t) : t;
|
|
1272
|
+
}
|
|
1273
|
+
return { kind: "list", inner };
|
|
1274
|
+
}
|
|
1275
|
+
case "tuple": {
|
|
1276
|
+
const entries = new Map;
|
|
1277
|
+
for (const [k, v] of Object.entries(cv.value)) {
|
|
1278
|
+
entries.set(k, { tree: cvToTree(v), present: 1 });
|
|
1279
|
+
}
|
|
1280
|
+
return { kind: "tuple", count: 1, entries };
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
function unifyNullable(a, b) {
|
|
1285
|
+
if (!a)
|
|
1286
|
+
return b;
|
|
1287
|
+
if (!b)
|
|
1288
|
+
return a;
|
|
1289
|
+
return unify(a, b);
|
|
1290
|
+
}
|
|
1291
|
+
var UNION_KIND_ORDER = {
|
|
1292
|
+
uint: 0,
|
|
1293
|
+
int: 1,
|
|
1294
|
+
bool: 2,
|
|
1295
|
+
principal: 3,
|
|
1296
|
+
buffer: 4,
|
|
1297
|
+
ascii: 5,
|
|
1298
|
+
utf8: 6,
|
|
1299
|
+
list: 7,
|
|
1300
|
+
tuple: 8,
|
|
1301
|
+
response: 9
|
|
1302
|
+
};
|
|
1303
|
+
function makeUnion(members) {
|
|
1304
|
+
const sorted = [...members].sort((a, b) => (UNION_KIND_ORDER[a.kind] ?? 99) - (UNION_KIND_ORDER[b.kind] ?? 99));
|
|
1305
|
+
return { kind: "union", members: sorted };
|
|
1306
|
+
}
|
|
1307
|
+
function unify(a, b) {
|
|
1308
|
+
if (a.kind === "optional" || b.kind === "optional") {
|
|
1309
|
+
const ai = a.kind === "optional" ? a.inner : a;
|
|
1310
|
+
const bi = b.kind === "optional" ? b.inner : b;
|
|
1311
|
+
return { kind: "optional", inner: unifyNullable(ai, bi) };
|
|
1312
|
+
}
|
|
1313
|
+
if (a.kind === "union")
|
|
1314
|
+
return unionAdd(a.members, b);
|
|
1315
|
+
if (b.kind === "union")
|
|
1316
|
+
return unionAdd(b.members, a);
|
|
1317
|
+
if (a.kind !== b.kind)
|
|
1318
|
+
return makeUnion([a, b]);
|
|
1319
|
+
switch (a.kind) {
|
|
1320
|
+
case "uint":
|
|
1321
|
+
case "int":
|
|
1322
|
+
case "bool":
|
|
1323
|
+
case "principal":
|
|
1324
|
+
return a;
|
|
1325
|
+
case "buffer":
|
|
1326
|
+
case "ascii":
|
|
1327
|
+
case "utf8":
|
|
1328
|
+
return { kind: a.kind, len: Math.max(a.len, b.len) };
|
|
1329
|
+
case "list":
|
|
1330
|
+
return {
|
|
1331
|
+
kind: "list",
|
|
1332
|
+
inner: unifyNullable(a.inner, b.inner)
|
|
1333
|
+
};
|
|
1334
|
+
case "response": {
|
|
1335
|
+
const rb = b;
|
|
1336
|
+
return {
|
|
1337
|
+
kind: "response",
|
|
1338
|
+
ok: unifyNullable(a.ok, rb.ok),
|
|
1339
|
+
err: unifyNullable(a.err, rb.err)
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
case "tuple": {
|
|
1343
|
+
const tb = b;
|
|
1344
|
+
const entries = new Map([...a.entries].map(([k, e]) => [k, { ...e }]));
|
|
1345
|
+
for (const [k, e] of tb.entries) {
|
|
1346
|
+
const existing = entries.get(k);
|
|
1347
|
+
entries.set(k, existing ? {
|
|
1348
|
+
tree: unify(existing.tree, e.tree),
|
|
1349
|
+
present: existing.present + e.present
|
|
1350
|
+
} : { ...e });
|
|
1351
|
+
}
|
|
1352
|
+
return { kind: "tuple", count: a.count + tb.count, entries };
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
function unionAdd(members, t) {
|
|
1357
|
+
if (t.kind === "union") {
|
|
1358
|
+
let acc = { kind: "union", members };
|
|
1359
|
+
for (const m of t.members)
|
|
1360
|
+
acc = unify(acc, m);
|
|
1361
|
+
return acc;
|
|
1362
|
+
}
|
|
1363
|
+
const next = [...members];
|
|
1364
|
+
for (let i = 0;i < next.length; i++) {
|
|
1365
|
+
const member = next[i];
|
|
1366
|
+
if (!member)
|
|
1367
|
+
continue;
|
|
1368
|
+
const merged = unify(member, t);
|
|
1369
|
+
if (merged.kind !== "union") {
|
|
1370
|
+
next[i] = merged;
|
|
1371
|
+
return makeUnion(next);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
next.push(t);
|
|
1375
|
+
return makeUnion(next);
|
|
1376
|
+
}
|
|
1377
|
+
function wrapOptional(t) {
|
|
1378
|
+
return t.kind === "optional" ? t : { kind: "optional", inner: t };
|
|
1379
|
+
}
|
|
1380
|
+
function renderClarity(t) {
|
|
1381
|
+
if (!t)
|
|
1382
|
+
return "?";
|
|
1383
|
+
switch (t.kind) {
|
|
1384
|
+
case "uint":
|
|
1385
|
+
case "int":
|
|
1386
|
+
case "bool":
|
|
1387
|
+
case "principal":
|
|
1388
|
+
return t.kind;
|
|
1389
|
+
case "buffer":
|
|
1390
|
+
return `(buff ${t.len})`;
|
|
1391
|
+
case "ascii":
|
|
1392
|
+
return `(string-ascii ${t.len})`;
|
|
1393
|
+
case "utf8":
|
|
1394
|
+
return `(string-utf8 ${t.len})`;
|
|
1395
|
+
case "optional":
|
|
1396
|
+
return `(optional ${renderClarity(t.inner)})`;
|
|
1397
|
+
case "list":
|
|
1398
|
+
return `(list ${renderClarity(t.inner)})`;
|
|
1399
|
+
case "response":
|
|
1400
|
+
return `(response ${renderClarity(t.ok)} ${renderClarity(t.err)})`;
|
|
1401
|
+
case "tuple": {
|
|
1402
|
+
const parts = [...t.entries].map(([k, e]) => {
|
|
1403
|
+
const tree = e.present < t.count ? wrapOptional(e.tree) : e.tree;
|
|
1404
|
+
return `(${k} ${renderClarity(tree)})`;
|
|
1405
|
+
});
|
|
1406
|
+
return `(tuple ${parts.join(" ")})`;
|
|
1407
|
+
}
|
|
1408
|
+
case "union":
|
|
1409
|
+
return t.members.map(renderClarity).join(" | ");
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function renderTs(t) {
|
|
1413
|
+
if (!t)
|
|
1414
|
+
return "unknown";
|
|
1415
|
+
switch (t.kind) {
|
|
1416
|
+
case "uint":
|
|
1417
|
+
case "int":
|
|
1418
|
+
return "bigint";
|
|
1419
|
+
case "bool":
|
|
1420
|
+
return "boolean";
|
|
1421
|
+
case "principal":
|
|
1422
|
+
case "buffer":
|
|
1423
|
+
case "ascii":
|
|
1424
|
+
case "utf8":
|
|
1425
|
+
return "string";
|
|
1426
|
+
case "optional":
|
|
1427
|
+
return t.inner ? `${renderTs(t.inner)} | null` : "unknown | null";
|
|
1428
|
+
case "list": {
|
|
1429
|
+
const inner = renderTs(t.inner);
|
|
1430
|
+
return inner.includes(" | ") ? `(${inner})[]` : `${inner}[]`;
|
|
1431
|
+
}
|
|
1432
|
+
case "response": {
|
|
1433
|
+
const sides = [...new Set([renderTs(t.ok), renderTs(t.err)])];
|
|
1434
|
+
return sides.join(" | ");
|
|
1435
|
+
}
|
|
1436
|
+
case "tuple": {
|
|
1437
|
+
const parts = [...t.entries].map(([k, e]) => {
|
|
1438
|
+
const opt = e.present < t.count ? "?" : "";
|
|
1439
|
+
return `${camelizeDataKey(k)}${opt}: ${renderTs(e.tree)}`;
|
|
1440
|
+
});
|
|
1441
|
+
return `{ ${parts.join("; ")} }`;
|
|
1442
|
+
}
|
|
1443
|
+
case "union":
|
|
1444
|
+
return [...new Set(t.members.map((m) => renderTs(m)))].join(" | ");
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
function toColumnType(t) {
|
|
1448
|
+
if (!t)
|
|
1449
|
+
return "jsonb";
|
|
1450
|
+
switch (t.kind) {
|
|
1451
|
+
case "uint":
|
|
1452
|
+
return "uint";
|
|
1453
|
+
case "int":
|
|
1454
|
+
return "int";
|
|
1455
|
+
case "bool":
|
|
1456
|
+
return "boolean";
|
|
1457
|
+
case "principal":
|
|
1458
|
+
return "principal";
|
|
1459
|
+
case "buffer":
|
|
1460
|
+
case "ascii":
|
|
1461
|
+
case "utf8":
|
|
1462
|
+
return "text";
|
|
1463
|
+
case "list":
|
|
1464
|
+
case "tuple":
|
|
1465
|
+
return "jsonb";
|
|
1466
|
+
case "optional":
|
|
1467
|
+
return toColumnType(t.inner);
|
|
1468
|
+
case "response":
|
|
1469
|
+
return t.ok ? toColumnType(t.ok) : "jsonb";
|
|
1470
|
+
case "union":
|
|
1471
|
+
return "jsonb";
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
var MAX_DECODED_PER_TOPIC_NEWEST = 75;
|
|
1475
|
+
var MAX_DECODED_PER_TOPIC_OLDEST = 25;
|
|
1476
|
+
function inferPrintTopics(samples) {
|
|
1477
|
+
const groups = new Map;
|
|
1478
|
+
for (const s of samples) {
|
|
1479
|
+
const group = groups.get(s.topic);
|
|
1480
|
+
if (group)
|
|
1481
|
+
group.push(s);
|
|
1482
|
+
else
|
|
1483
|
+
groups.set(s.topic, [s]);
|
|
1484
|
+
}
|
|
1485
|
+
const out = [];
|
|
1486
|
+
for (const [topic, rows] of groups) {
|
|
1487
|
+
let first = Number.POSITIVE_INFINITY;
|
|
1488
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
1489
|
+
for (const r of rows) {
|
|
1490
|
+
if (r.blockHeight < first)
|
|
1491
|
+
first = r.blockHeight;
|
|
1492
|
+
if (r.blockHeight > last)
|
|
1493
|
+
last = r.blockHeight;
|
|
1494
|
+
}
|
|
1495
|
+
const withHex = [...rows].filter((r) => r.rawHex !== null).sort((a, b) => b.blockHeight - a.blockHeight);
|
|
1496
|
+
const budget = MAX_DECODED_PER_TOPIC_NEWEST + MAX_DECODED_PER_TOPIC_OLDEST;
|
|
1497
|
+
const picked = withHex.length <= budget ? withHex : [
|
|
1498
|
+
...withHex.slice(0, MAX_DECODED_PER_TOPIC_NEWEST),
|
|
1499
|
+
...withHex.slice(-MAX_DECODED_PER_TOPIC_OLDEST)
|
|
1500
|
+
];
|
|
1501
|
+
const tuples = [];
|
|
1502
|
+
let decoded = 0;
|
|
1503
|
+
for (const p of picked) {
|
|
1504
|
+
try {
|
|
1505
|
+
const cv = deserializeCV3(p.rawHex);
|
|
1506
|
+
decoded++;
|
|
1507
|
+
if (cv.type === "tuple")
|
|
1508
|
+
tuples.push(cv);
|
|
1509
|
+
} catch {}
|
|
1510
|
+
}
|
|
1511
|
+
const nonTuple = decoded > 0 && tuples.length === 0;
|
|
1512
|
+
const fields = [];
|
|
1513
|
+
if (!nonTuple) {
|
|
1514
|
+
const stats = new Map;
|
|
1515
|
+
for (const t of tuples) {
|
|
1516
|
+
for (const [key, value] of Object.entries(t.value)) {
|
|
1517
|
+
if (key === "topic")
|
|
1518
|
+
continue;
|
|
1519
|
+
const tree = cvToTree(value);
|
|
1520
|
+
const existing = stats.get(key);
|
|
1521
|
+
if (existing) {
|
|
1522
|
+
existing.present++;
|
|
1523
|
+
existing.tree = unify(existing.tree, tree);
|
|
1524
|
+
if (value.type === "none")
|
|
1525
|
+
existing.noneCount++;
|
|
1526
|
+
if (value.type === "none" || value.type === "some") {
|
|
1527
|
+
existing.optionalSeen = true;
|
|
1528
|
+
}
|
|
1529
|
+
} else {
|
|
1530
|
+
stats.set(key, {
|
|
1531
|
+
tree,
|
|
1532
|
+
present: 1,
|
|
1533
|
+
noneCount: value.type === "none" ? 1 : 0,
|
|
1534
|
+
optionalSeen: value.type === "none" || value.type === "some"
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
for (const [name, st] of [...stats].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1540
|
+
const field = {
|
|
1541
|
+
name,
|
|
1542
|
+
camel_name: camelizeDataKey(name),
|
|
1543
|
+
clarity_type: renderClarity(st.tree),
|
|
1544
|
+
ts_type: renderTs(st.tree),
|
|
1545
|
+
column_type: toColumnType(st.tree),
|
|
1546
|
+
always_present: st.present === tuples.length
|
|
1547
|
+
};
|
|
1548
|
+
if (st.optionalSeen) {
|
|
1549
|
+
field.optional_some_rate = (st.present - st.noneCount) / st.present;
|
|
1550
|
+
}
|
|
1551
|
+
fields.push(field);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
out.push({
|
|
1555
|
+
topic,
|
|
1556
|
+
count: rows.length,
|
|
1557
|
+
first_height: first,
|
|
1558
|
+
last_height: last,
|
|
1559
|
+
non_tuple: nonTuple,
|
|
1560
|
+
fields
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
return out.sort((a, b) => b.count - a.count);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// src/runtime/materialize.ts
|
|
1567
|
+
function resolveColumn(col, event, tx, block) {
|
|
1568
|
+
if ("fromTx" in col) {
|
|
1569
|
+
return { ok: true, value: tx[col.fromTx] };
|
|
1570
|
+
}
|
|
1571
|
+
if ("fromBlock" in col) {
|
|
1572
|
+
return { ok: true, value: block[col.fromBlock] };
|
|
1573
|
+
}
|
|
1574
|
+
const data = event.data;
|
|
1575
|
+
const dataObj = data !== null && typeof data === "object" && !Array.isArray(data) ? data : undefined;
|
|
1576
|
+
if (dataObj && Object.prototype.hasOwnProperty.call(dataObj, col.from)) {
|
|
1577
|
+
return { ok: true, value: dataObj[col.from] };
|
|
1578
|
+
}
|
|
1579
|
+
if (col.from === "topic" && "topic" in event) {
|
|
1580
|
+
return { ok: true, value: event.topic };
|
|
1581
|
+
}
|
|
1582
|
+
if (Object.prototype.hasOwnProperty.call(event, col.from)) {
|
|
1583
|
+
return { ok: true, value: event[col.from] };
|
|
1584
|
+
}
|
|
1585
|
+
return { ok: false, missing: col.from };
|
|
1586
|
+
}
|
|
1587
|
+
function buildMaterializeRow(spec, event, ctx, tableColumns) {
|
|
1588
|
+
const row = {};
|
|
1589
|
+
for (const [colName, mapping] of Object.entries(spec.columns)) {
|
|
1590
|
+
const resolved = resolveColumn(mapping, event, ctx.tx, ctx.block);
|
|
1591
|
+
if (!resolved.ok) {
|
|
1592
|
+
const colDef = tableColumns[colName];
|
|
1593
|
+
if (colDef?.nullable) {
|
|
1594
|
+
row[colName] = null;
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
return {
|
|
1598
|
+
ok: false,
|
|
1599
|
+
reason: `required materialize field "${resolved.missing}" missing for column "${colName}"`
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
row[colName] = resolved.value ?? null;
|
|
1603
|
+
}
|
|
1604
|
+
return { ok: true, row };
|
|
1605
|
+
}
|
|
1606
|
+
function applyMaterializeInsert(spec, event, ctx, schema) {
|
|
1607
|
+
const table = schema[spec.table];
|
|
1608
|
+
if (!table) {
|
|
1609
|
+
return { ok: false, reason: `materialize table "${spec.table}" missing` };
|
|
1610
|
+
}
|
|
1611
|
+
const built = buildMaterializeRow(spec, event, ctx, table.columns);
|
|
1612
|
+
if (!built.ok)
|
|
1613
|
+
return built;
|
|
1614
|
+
ctx.insert(spec.table, built.row);
|
|
1615
|
+
return { ok: true };
|
|
1616
|
+
}
|
|
1617
|
+
|
|
954
1618
|
// src/runtime/print-validate.ts
|
|
955
1619
|
var COLUMN_TYPES = new Set([
|
|
956
1620
|
"text",
|
|
@@ -1030,24 +1694,62 @@ function validatePrintPayload(prints, topic, data) {
|
|
|
1030
1694
|
return matchesTopic(declared, data);
|
|
1031
1695
|
}
|
|
1032
1696
|
|
|
1697
|
+
// src/runtime/violations.ts
|
|
1698
|
+
import { getTargetDb } from "@secondlayer/shared/db";
|
|
1699
|
+
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
1700
|
+
import { sql as sql2 } from "kysely";
|
|
1701
|
+
var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
|
|
1702
|
+
function jsonSafe2(value) {
|
|
1703
|
+
if (typeof value === "bigint")
|
|
1704
|
+
return value.toString();
|
|
1705
|
+
if (value === null || value === undefined)
|
|
1706
|
+
return value;
|
|
1707
|
+
if (Array.isArray(value))
|
|
1708
|
+
return value.map(jsonSafe2);
|
|
1709
|
+
if (typeof value === "object") {
|
|
1710
|
+
const out = {};
|
|
1711
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1712
|
+
out[k] = jsonSafe2(v);
|
|
1713
|
+
}
|
|
1714
|
+
return out;
|
|
1715
|
+
}
|
|
1716
|
+
return value;
|
|
1717
|
+
}
|
|
1718
|
+
async function defaultRecordPrintViolation(input) {
|
|
1719
|
+
try {
|
|
1720
|
+
const db = getTargetDb();
|
|
1721
|
+
const sample = jsonSafe2(input.samplePayload);
|
|
1722
|
+
await db.insertInto("subgraph_violations").values({
|
|
1723
|
+
subgraph_name: input.subgraphName,
|
|
1724
|
+
source_name: input.sourceName,
|
|
1725
|
+
block_height: input.blockHeight,
|
|
1726
|
+
tx_id: input.txId,
|
|
1727
|
+
reason: input.reason,
|
|
1728
|
+
sample_payload: sample
|
|
1729
|
+
}).execute();
|
|
1730
|
+
await sql2`
|
|
1731
|
+
DELETE FROM subgraph_violations
|
|
1732
|
+
WHERE id IN (
|
|
1733
|
+
SELECT id FROM subgraph_violations
|
|
1734
|
+
WHERE subgraph_name = ${input.subgraphName}
|
|
1735
|
+
ORDER BY seen_at DESC
|
|
1736
|
+
OFFSET ${VIOLATIONS_CAP_PER_SUBGRAPH}
|
|
1737
|
+
)
|
|
1738
|
+
`.execute(db);
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
logger3.warn("Failed to record print violation", {
|
|
1741
|
+
subgraph: input.subgraphName,
|
|
1742
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
var recorder = defaultRecordPrintViolation;
|
|
1747
|
+
async function recordPrintViolation(input) {
|
|
1748
|
+
await recorder(input);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1033
1751
|
// src/runtime/runner.ts
|
|
1034
1752
|
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
1035
|
-
function camelCase(str) {
|
|
1036
|
-
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1037
|
-
}
|
|
1038
|
-
function camelizeKeys(obj) {
|
|
1039
|
-
if (obj === null || obj === undefined)
|
|
1040
|
-
return obj;
|
|
1041
|
-
if (typeof obj !== "object")
|
|
1042
|
-
return obj;
|
|
1043
|
-
if (Array.isArray(obj))
|
|
1044
|
-
return obj.map(camelizeKeys);
|
|
1045
|
-
const result = {};
|
|
1046
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
1047
|
-
result[camelCase(k)] = camelizeKeys(v);
|
|
1048
|
-
}
|
|
1049
|
-
return result;
|
|
1050
|
-
}
|
|
1051
1753
|
function decodeFunctionArgs2(args) {
|
|
1052
1754
|
let parsed = args;
|
|
1053
1755
|
if (typeof parsed === "string") {
|
|
@@ -1111,7 +1813,7 @@ function buildContractCallInput(filter, tx) {
|
|
|
1111
1813
|
return;
|
|
1112
1814
|
try {
|
|
1113
1815
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1114
|
-
input[toCamelCase2(arg.name)] = decodeArg(arg.type,
|
|
1816
|
+
input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV4(clean));
|
|
1115
1817
|
} catch {}
|
|
1116
1818
|
});
|
|
1117
1819
|
return input;
|
|
@@ -1234,7 +1936,7 @@ function buildEventPayload(filter, tx, event) {
|
|
|
1234
1936
|
const { topic: _, ...rest } = clarityObj ?? {};
|
|
1235
1937
|
const data = Object.keys(rest).length > 0 ? camelizeKeys(rest) : rawValue && typeof rawValue !== "object" ? rawValue : {};
|
|
1236
1938
|
return {
|
|
1237
|
-
contractId: decoded
|
|
1939
|
+
contractId: printContractId(decoded) ?? tx.contract_id ?? "",
|
|
1238
1940
|
topic,
|
|
1239
1941
|
data: data ?? {},
|
|
1240
1942
|
tx: txMeta
|
|
@@ -1272,386 +1974,151 @@ function buildEventPayload(filter, tx, event) {
|
|
|
1272
1974
|
};
|
|
1273
1975
|
}
|
|
1274
1976
|
}
|
|
1275
|
-
async function runHandlers(subgraph, matched, ctx, opts) {
|
|
1276
|
-
let processed = 0;
|
|
1277
|
-
let errors = 0;
|
|
1278
|
-
let skipped = 0;
|
|
1279
|
-
const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
|
|
1280
|
-
const filterLookup = new Map;
|
|
1281
|
-
if (!Array.isArray(subgraph.sources)) {
|
|
1282
|
-
for (const [name, filter] of Object.entries(subgraph.sources)) {
|
|
1283
|
-
filterLookup.set(name, filter);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
const units = [];
|
|
1287
|
-
for (const { tx, events, sourceName } of matched) {
|
|
1288
|
-
if (events.length === 0) {
|
|
1289
|
-
units.push({ tx, sourceName, event: null });
|
|
1290
|
-
} else {
|
|
1291
|
-
for (const event of events)
|
|
1292
|
-
units.push({ tx, sourceName, event });
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
1296
|
-
for (const { tx, event, sourceName } of units) {
|
|
1297
|
-
if (errors >= threshold) {
|
|
1298
|
-
logger3.error("Subgraph error threshold reached, skipping remaining events", {
|
|
1299
|
-
subgraph: subgraph.name,
|
|
1300
|
-
errors,
|
|
1301
|
-
threshold
|
|
1302
|
-
});
|
|
1303
|
-
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1304
|
-
}
|
|
1305
|
-
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
1306
|
-
if (!handler) {
|
|
1307
|
-
logger3.warn("No handler found for source", {
|
|
1308
|
-
subgraph: subgraph.name,
|
|
1309
|
-
sourceName,
|
|
1310
|
-
txId: tx.tx_id
|
|
1311
|
-
});
|
|
1312
|
-
continue;
|
|
1313
|
-
}
|
|
1314
|
-
ctx.setTx({
|
|
1315
|
-
txId: tx.tx_id,
|
|
1316
|
-
sender: tx.sender,
|
|
1317
|
-
type: tx.type,
|
|
1318
|
-
status: tx.status,
|
|
1319
|
-
contractId: tx.contract_id ?? null,
|
|
1320
|
-
functionName: tx.function_name ?? null
|
|
1321
|
-
});
|
|
1322
|
-
const filter = filterLookup.get(sourceName);
|
|
1323
|
-
const checkpoint = ctx.opsCheckpoint();
|
|
1324
|
-
try {
|
|
1325
|
-
let payload;
|
|
1326
|
-
if (event === null) {
|
|
1327
|
-
payload = filter ? buildEventPayload(filter, tx, null) : {
|
|
1328
|
-
tx: {
|
|
1329
|
-
txId: tx.tx_id,
|
|
1330
|
-
sender: tx.sender,
|
|
1331
|
-
type: tx.type,
|
|
1332
|
-
status: tx.status,
|
|
1333
|
-
contractId: tx.contract_id,
|
|
1334
|
-
functionName: tx.function_name
|
|
1335
|
-
}
|
|
1336
|
-
};
|
|
1337
|
-
} else if (filter) {
|
|
1338
|
-
payload = buildEventPayload(filter, tx, event);
|
|
1339
|
-
} else {
|
|
1340
|
-
const decoded = decodeEventData(event.data);
|
|
1341
|
-
payload = {
|
|
1342
|
-
...decoded,
|
|
1343
|
-
_eventId: event.id,
|
|
1344
|
-
_eventType: event.type,
|
|
1345
|
-
_eventIndex: event.event_index,
|
|
1346
|
-
tx: {
|
|
1347
|
-
txId: tx.tx_id,
|
|
1348
|
-
sender: tx.sender,
|
|
1349
|
-
type: tx.type,
|
|
1350
|
-
status: tx.status,
|
|
1351
|
-
contractId: tx.contract_id,
|
|
1352
|
-
functionName: tx.function_name
|
|
1353
|
-
}
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
|
|
1357
|
-
continue;
|
|
1358
|
-
}
|
|
1359
|
-
if (event !== null && filter?.type === "print_event" && filter.prints) {
|
|
1360
|
-
const printPayload = payload;
|
|
1361
|
-
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1362
|
-
if (!verdict.ok) {
|
|
1363
|
-
skipped++;
|
|
1364
|
-
logger3.warn("Print payload does not match declared prints schema", {
|
|
1365
|
-
subgraph: subgraph.name,
|
|
1366
|
-
sourceName,
|
|
1367
|
-
topic: printPayload.topic,
|
|
1368
|
-
txId: tx.tx_id,
|
|
1369
|
-
reason: verdict.reason
|
|
1370
|
-
});
|
|
1371
|
-
continue;
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
await handler(payload, ctx);
|
|
1375
|
-
processed++;
|
|
1376
|
-
} catch (err) {
|
|
1377
|
-
ctx.rollbackTo(checkpoint);
|
|
1378
|
-
errors++;
|
|
1379
|
-
logger3.error("Subgraph handler error", {
|
|
1380
|
-
subgraph: subgraph.name,
|
|
1381
|
-
sourceName,
|
|
1382
|
-
txId: tx.tx_id,
|
|
1383
|
-
...event !== null ? { eventId: event.id, eventType: event.type } : {},
|
|
1384
|
-
error: getErrorMessage(err)
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// src/runtime/source-matcher.ts
|
|
1392
|
-
var patternCache = new Map;
|
|
1393
|
-
function matchAny(value, pattern) {
|
|
1394
|
-
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
1395
|
-
}
|
|
1396
|
-
function matchPattern(value, pattern) {
|
|
1397
|
-
if (!pattern.includes("*"))
|
|
1398
|
-
return value === pattern;
|
|
1399
|
-
let re = patternCache.get(pattern);
|
|
1400
|
-
if (!re) {
|
|
1401
|
-
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
1402
|
-
re = new RegExp(`^${regex}$`);
|
|
1403
|
-
patternCache.set(pattern, re);
|
|
1404
|
-
}
|
|
1405
|
-
return re.test(value);
|
|
1406
|
-
}
|
|
1407
|
-
var EMPTY_SET = new Set;
|
|
1408
|
-
function factoryAllows(filter, contractId, factoryContracts) {
|
|
1409
|
-
if (!filter.factory)
|
|
1410
|
-
return true;
|
|
1411
|
-
if (!contractId)
|
|
1412
|
-
return false;
|
|
1413
|
-
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
1414
|
-
}
|
|
1415
|
-
function readPath(payload, path) {
|
|
1416
|
-
let cur = payload;
|
|
1417
|
-
for (const key of path.split(".")) {
|
|
1418
|
-
if (cur === null || typeof cur !== "object")
|
|
1419
|
-
return;
|
|
1420
|
-
cur = cur[key];
|
|
1421
|
-
}
|
|
1422
|
-
return cur;
|
|
1423
|
-
}
|
|
1424
|
-
function traitAllows(filter, contractId, traitContracts) {
|
|
1425
|
-
const trait = filter.trait;
|
|
1426
|
-
if (!trait)
|
|
1427
|
-
return true;
|
|
1428
|
-
if (!contractId)
|
|
1429
|
-
return false;
|
|
1430
|
-
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
1431
|
-
}
|
|
1432
|
-
function assetContract(assetId) {
|
|
1433
|
-
return assetId?.split("::")[0];
|
|
1434
|
-
}
|
|
1435
|
-
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
|
|
1436
|
-
const results = [];
|
|
1437
|
-
switch (filter.type) {
|
|
1438
|
-
case "stx_transfer":
|
|
1439
|
-
case "stx_mint":
|
|
1440
|
-
case "stx_burn":
|
|
1441
|
-
case "stx_lock": {
|
|
1442
|
-
const eventType = `${filter.type}_event`;
|
|
1443
|
-
for (const tx of transactions) {
|
|
1444
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1445
|
-
const matched = txEvents.filter((e) => e.type === eventType);
|
|
1446
|
-
if (matched.length === 0)
|
|
1447
|
-
continue;
|
|
1448
|
-
const filtered = matched.filter((e) => {
|
|
1449
|
-
const data = e.data;
|
|
1450
|
-
if (!data)
|
|
1451
|
-
return false;
|
|
1452
|
-
if ("sender" in filter && filter.sender) {
|
|
1453
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
1454
|
-
return false;
|
|
1455
|
-
}
|
|
1456
|
-
if ("recipient" in filter && filter.recipient) {
|
|
1457
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
1458
|
-
return false;
|
|
1459
|
-
}
|
|
1460
|
-
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
1461
|
-
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
1462
|
-
return false;
|
|
1463
|
-
}
|
|
1464
|
-
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
1465
|
-
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
1466
|
-
if (amount < filter.minAmount)
|
|
1467
|
-
return false;
|
|
1468
|
-
}
|
|
1469
|
-
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
1470
|
-
const amount = BigInt(data.amount ?? "0");
|
|
1471
|
-
if (amount > filter.maxAmount)
|
|
1472
|
-
return false;
|
|
1473
|
-
}
|
|
1474
|
-
return true;
|
|
1475
|
-
});
|
|
1476
|
-
if (filtered.length > 0) {
|
|
1477
|
-
results.push({ tx, events: filtered });
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
break;
|
|
1481
|
-
}
|
|
1482
|
-
case "ft_transfer":
|
|
1483
|
-
case "ft_mint":
|
|
1484
|
-
case "ft_burn": {
|
|
1485
|
-
const eventType = `${filter.type}_event`;
|
|
1486
|
-
for (const tx of transactions) {
|
|
1487
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1488
|
-
const matched = txEvents.filter((e) => {
|
|
1489
|
-
if (e.type !== eventType)
|
|
1490
|
-
return false;
|
|
1491
|
-
const data = e.data;
|
|
1492
|
-
if (!data)
|
|
1493
|
-
return false;
|
|
1494
|
-
if (filter.assetIdentifier) {
|
|
1495
|
-
const assetId = data.asset_identifier;
|
|
1496
|
-
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1497
|
-
return false;
|
|
1498
|
-
}
|
|
1499
|
-
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1500
|
-
return false;
|
|
1501
|
-
if ("sender" in filter && filter.sender) {
|
|
1502
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
1503
|
-
return false;
|
|
1504
|
-
}
|
|
1505
|
-
if ("recipient" in filter && filter.recipient) {
|
|
1506
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
1507
|
-
return false;
|
|
1508
|
-
}
|
|
1509
|
-
if (filter.minAmount !== undefined) {
|
|
1510
|
-
const amount = BigInt(data.amount ?? "0");
|
|
1511
|
-
if (amount < filter.minAmount)
|
|
1512
|
-
return false;
|
|
1513
|
-
}
|
|
1514
|
-
return true;
|
|
1515
|
-
});
|
|
1516
|
-
if (matched.length > 0) {
|
|
1517
|
-
results.push({ tx, events: matched });
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
break;
|
|
1977
|
+
async function runHandlers(subgraph, matched, ctx, opts) {
|
|
1978
|
+
let processed = 0;
|
|
1979
|
+
let errors = 0;
|
|
1980
|
+
let skipped = 0;
|
|
1981
|
+
const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
|
|
1982
|
+
const filterLookup = new Map;
|
|
1983
|
+
if (!Array.isArray(subgraph.sources)) {
|
|
1984
|
+
for (const [name, filter] of Object.entries(subgraph.sources)) {
|
|
1985
|
+
filterLookup.set(name, filter);
|
|
1521
1986
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1987
|
+
}
|
|
1988
|
+
const units = [];
|
|
1989
|
+
for (const { tx, events, sourceName } of matched) {
|
|
1990
|
+
if (events.length === 0) {
|
|
1991
|
+
units.push({ tx, sourceName, event: null });
|
|
1992
|
+
} else {
|
|
1993
|
+
for (const event of events)
|
|
1994
|
+
units.push({ tx, sourceName, event });
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
1998
|
+
for (const { tx, event, sourceName } of units) {
|
|
1999
|
+
if (errors >= threshold) {
|
|
2000
|
+
logger4.error("Subgraph error threshold reached, skipping remaining events", {
|
|
2001
|
+
subgraph: subgraph.name,
|
|
2002
|
+
errors,
|
|
2003
|
+
threshold
|
|
2004
|
+
});
|
|
2005
|
+
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
2006
|
+
}
|
|
2007
|
+
const filter = filterLookup.get(sourceName);
|
|
2008
|
+
const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
|
|
2009
|
+
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
2010
|
+
if (!handler && !materialize) {
|
|
2011
|
+
logger4.warn("No handler found for source", {
|
|
2012
|
+
subgraph: subgraph.name,
|
|
2013
|
+
sourceName,
|
|
2014
|
+
txId: tx.tx_id
|
|
2015
|
+
});
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
ctx.setTx({
|
|
2019
|
+
txId: tx.tx_id,
|
|
2020
|
+
sender: tx.sender,
|
|
2021
|
+
type: tx.type,
|
|
2022
|
+
status: tx.status,
|
|
2023
|
+
contractId: tx.contract_id ?? null,
|
|
2024
|
+
functionName: tx.function_name ?? null
|
|
2025
|
+
});
|
|
2026
|
+
const checkpoint = ctx.opsCheckpoint();
|
|
2027
|
+
try {
|
|
2028
|
+
let payload;
|
|
2029
|
+
if (event === null) {
|
|
2030
|
+
payload = filter ? buildEventPayload(filter, tx, null) : {
|
|
2031
|
+
tx: {
|
|
2032
|
+
txId: tx.tx_id,
|
|
2033
|
+
sender: tx.sender,
|
|
2034
|
+
type: tx.type,
|
|
2035
|
+
status: tx.status,
|
|
2036
|
+
contractId: tx.contract_id,
|
|
2037
|
+
functionName: tx.function_name
|
|
1544
2038
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
2039
|
+
};
|
|
2040
|
+
} else if (filter) {
|
|
2041
|
+
payload = buildEventPayload(filter, tx, event);
|
|
2042
|
+
} else {
|
|
2043
|
+
const decoded = decodeEventData(event.data);
|
|
2044
|
+
payload = {
|
|
2045
|
+
...decoded,
|
|
2046
|
+
_eventId: event.id,
|
|
2047
|
+
_eventType: event.type,
|
|
2048
|
+
_eventIndex: event.event_index,
|
|
2049
|
+
tx: {
|
|
2050
|
+
txId: tx.tx_id,
|
|
2051
|
+
sender: tx.sender,
|
|
2052
|
+
type: tx.type,
|
|
2053
|
+
status: tx.status,
|
|
2054
|
+
contractId: tx.contract_id,
|
|
2055
|
+
functionName: tx.function_name
|
|
1548
2056
|
}
|
|
1549
|
-
|
|
1550
|
-
});
|
|
1551
|
-
if (matched.length > 0) {
|
|
1552
|
-
results.push({ tx, events: matched });
|
|
1553
|
-
}
|
|
2057
|
+
};
|
|
1554
2058
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
case "contract_call": {
|
|
1558
|
-
for (const tx of transactions) {
|
|
1559
|
-
if (tx.type !== "contract_call")
|
|
1560
|
-
continue;
|
|
1561
|
-
if (filter.contractId) {
|
|
1562
|
-
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
1563
|
-
continue;
|
|
1564
|
-
}
|
|
1565
|
-
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
1566
|
-
continue;
|
|
1567
|
-
if (filter.functionName) {
|
|
1568
|
-
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
1569
|
-
continue;
|
|
1570
|
-
}
|
|
1571
|
-
if (filter.caller) {
|
|
1572
|
-
if (!matchPattern(tx.sender, filter.caller))
|
|
1573
|
-
continue;
|
|
1574
|
-
}
|
|
1575
|
-
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
1576
|
-
continue;
|
|
1577
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1578
|
-
results.push({ tx, events: txEvents });
|
|
2059
|
+
if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
|
|
2060
|
+
continue;
|
|
1579
2061
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
2062
|
+
if (event !== null && filter?.type === "print_event" && filter.prints) {
|
|
2063
|
+
const printPayload = payload;
|
|
2064
|
+
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
2065
|
+
if (!verdict.ok) {
|
|
2066
|
+
skipped++;
|
|
2067
|
+
logger4.warn("Print payload does not match declared prints schema", {
|
|
2068
|
+
subgraph: subgraph.name,
|
|
2069
|
+
sourceName,
|
|
2070
|
+
topic: printPayload.topic,
|
|
2071
|
+
txId: tx.tx_id,
|
|
2072
|
+
reason: verdict.reason
|
|
2073
|
+
});
|
|
2074
|
+
recordPrintViolation({
|
|
2075
|
+
subgraphName: subgraph.name,
|
|
2076
|
+
sourceName,
|
|
2077
|
+
blockHeight: ctx.block.height,
|
|
2078
|
+
txId: tx.tx_id,
|
|
2079
|
+
reason: verdict.reason,
|
|
2080
|
+
samplePayload: {
|
|
2081
|
+
topic: printPayload.topic ?? "",
|
|
2082
|
+
data: printPayload.data ?? {}
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
1585
2085
|
continue;
|
|
1586
|
-
if (filter.deployer) {
|
|
1587
|
-
if (!matchPattern(tx.sender, filter.deployer))
|
|
1588
|
-
continue;
|
|
1589
|
-
}
|
|
1590
|
-
if (filter.contractName) {
|
|
1591
|
-
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
1592
|
-
if (!matchPattern(name, filter.contractName))
|
|
1593
|
-
continue;
|
|
1594
2086
|
}
|
|
1595
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1596
|
-
results.push({ tx, events: txEvents });
|
|
1597
2087
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
if (data.topic !== "print")
|
|
1610
|
-
return false;
|
|
1611
|
-
const printContractId = data.contract_identifier ?? data.contract_id;
|
|
1612
|
-
if (filter.contractId) {
|
|
1613
|
-
if (!printContractId || !matchAny(printContractId, filter.contractId))
|
|
1614
|
-
return false;
|
|
1615
|
-
}
|
|
1616
|
-
if (!traitAllows(filter, printContractId, traitContracts))
|
|
1617
|
-
return false;
|
|
1618
|
-
if (!factoryAllows(filter, printContractId, factoryContracts))
|
|
1619
|
-
return false;
|
|
1620
|
-
return true;
|
|
1621
|
-
});
|
|
1622
|
-
if (matched.length > 0) {
|
|
1623
|
-
results.push({ tx, events: matched });
|
|
2088
|
+
if (materialize) {
|
|
2089
|
+
const result = applyMaterializeInsert(materialize, payload, ctx, subgraph.schema);
|
|
2090
|
+
if (!result.ok) {
|
|
2091
|
+
skipped++;
|
|
2092
|
+
logger4.warn("Materialize skipped: required field missing", {
|
|
2093
|
+
subgraph: subgraph.name,
|
|
2094
|
+
sourceName,
|
|
2095
|
+
txId: tx.tx_id,
|
|
2096
|
+
reason: result.reason
|
|
2097
|
+
});
|
|
2098
|
+
continue;
|
|
1624
2099
|
}
|
|
2100
|
+
processed++;
|
|
2101
|
+
} else if (handler) {
|
|
2102
|
+
await handler(payload, ctx);
|
|
2103
|
+
processed++;
|
|
1625
2104
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
eventsByTx.set(event.tx_id, list);
|
|
1637
|
-
}
|
|
1638
|
-
const seen = new Set;
|
|
1639
|
-
const results = [];
|
|
1640
|
-
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
1641
|
-
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
|
|
1642
|
-
for (const match of matches) {
|
|
1643
|
-
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
1644
|
-
if (!seen.has(dedupeKey)) {
|
|
1645
|
-
seen.add(dedupeKey);
|
|
1646
|
-
results.push({ ...match, sourceName });
|
|
1647
|
-
}
|
|
2105
|
+
} catch (err) {
|
|
2106
|
+
ctx.rollbackTo(checkpoint);
|
|
2107
|
+
errors++;
|
|
2108
|
+
logger4.error("Subgraph handler error", {
|
|
2109
|
+
subgraph: subgraph.name,
|
|
2110
|
+
sourceName,
|
|
2111
|
+
txId: tx.tx_id,
|
|
2112
|
+
...event !== null ? { eventId: event.id, eventType: event.type } : {},
|
|
2113
|
+
error: getErrorMessage(err)
|
|
2114
|
+
});
|
|
1648
2115
|
}
|
|
1649
2116
|
}
|
|
1650
|
-
return
|
|
2117
|
+
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1651
2118
|
}
|
|
1652
2119
|
|
|
1653
2120
|
// src/runtime/block-processor.ts
|
|
1654
|
-
import { getTargetDb } from "@secondlayer/shared/db";
|
|
2121
|
+
import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
|
|
1655
2122
|
import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
|
|
1656
2123
|
import { recordGapBatch } from "@secondlayer/shared/db/queries/subgraph-gaps";
|
|
1657
2124
|
import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
|
|
@@ -1662,15 +2129,15 @@ import {
|
|
|
1662
2129
|
rewindLiveProgress,
|
|
1663
2130
|
updateSubgraphStatus
|
|
1664
2131
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
1665
|
-
import { logger as
|
|
1666
|
-
import { sql as
|
|
2132
|
+
import { logger as logger8 } from "@secondlayer/shared/logger";
|
|
2133
|
+
import { sql as sql4 } from "kysely";
|
|
1667
2134
|
|
|
1668
2135
|
// src/runtime/block-source.ts
|
|
1669
2136
|
import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
|
|
1670
2137
|
import {
|
|
1671
2138
|
createInternalIndexHttpClient
|
|
1672
2139
|
} from "@secondlayer/shared/index-http";
|
|
1673
|
-
import { logger as
|
|
2140
|
+
import { logger as logger5 } from "@secondlayer/shared/logger";
|
|
1674
2141
|
|
|
1675
2142
|
// src/runtime/batch-loader.ts
|
|
1676
2143
|
async function loadBlockRange(db, fromHeight, toHeight) {
|
|
@@ -1990,7 +2457,7 @@ function reconstructEvent(e) {
|
|
|
1990
2457
|
type: "contract_event",
|
|
1991
2458
|
data: {
|
|
1992
2459
|
topic: e.payload.topic,
|
|
1993
|
-
|
|
2460
|
+
contract_id: e.contract_id,
|
|
1994
2461
|
value: e.payload.value,
|
|
1995
2462
|
raw_value: e.payload.raw_value
|
|
1996
2463
|
}
|
|
@@ -2157,7 +2624,7 @@ class FallbackBlockSource {
|
|
|
2157
2624
|
try {
|
|
2158
2625
|
return await this.primary.getTip();
|
|
2159
2626
|
} catch (err) {
|
|
2160
|
-
|
|
2627
|
+
logger5.warn("block source primary getTip failed — using DB tap", {
|
|
2161
2628
|
error: err instanceof Error ? err.message : String(err)
|
|
2162
2629
|
});
|
|
2163
2630
|
return this.fallback.getTip();
|
|
@@ -2167,7 +2634,7 @@ class FallbackBlockSource {
|
|
|
2167
2634
|
try {
|
|
2168
2635
|
return await this.primary.loadBlockRange(fromHeight, toHeight);
|
|
2169
2636
|
} catch (err) {
|
|
2170
|
-
|
|
2637
|
+
logger5.warn("block source primary loadBlockRange failed — using DB tap", {
|
|
2171
2638
|
from: fromHeight,
|
|
2172
2639
|
to: toHeight,
|
|
2173
2640
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -2204,7 +2671,7 @@ function resolveBlockSource(subgraph) {
|
|
|
2204
2671
|
return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph), canSparseScan(subgraph) ? sparseProbeTargets(subgraph) : undefined, needsTransactionData(subgraph)), postgresBlockSource);
|
|
2205
2672
|
}
|
|
2206
2673
|
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
|
|
2207
|
-
|
|
2674
|
+
logger5.debug("Subgraph not streams-index eligible, using DB tap", {
|
|
2208
2675
|
subgraph: subgraph.name
|
|
2209
2676
|
});
|
|
2210
2677
|
}
|
|
@@ -2213,7 +2680,7 @@ function resolveBlockSource(subgraph) {
|
|
|
2213
2680
|
|
|
2214
2681
|
// src/runtime/outbox-emit.ts
|
|
2215
2682
|
import { createHash as createHash2 } from "node:crypto";
|
|
2216
|
-
import { logger as
|
|
2683
|
+
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
2217
2684
|
var loggedKillSwitch = false;
|
|
2218
2685
|
var OP_VERB = {
|
|
2219
2686
|
insert: "created",
|
|
@@ -2237,7 +2704,7 @@ function stableStringify(obj) {
|
|
|
2237
2704
|
async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, blockHeight) {
|
|
2238
2705
|
if (!isEmitOutboxEnabled()) {
|
|
2239
2706
|
if (!loggedKillSwitch) {
|
|
2240
|
-
|
|
2707
|
+
logger6.warn("SECONDLAYER_EMIT_OUTBOX=false — outbox emission bypassed");
|
|
2241
2708
|
loggedKillSwitch = true;
|
|
2242
2709
|
}
|
|
2243
2710
|
return 0;
|
|
@@ -2282,7 +2749,7 @@ function sandboxEnabled(subgraph) {
|
|
|
2282
2749
|
// src/runtime/sandbox/host.ts
|
|
2283
2750
|
import { tmpdir } from "node:os";
|
|
2284
2751
|
import { fileURLToPath } from "node:url";
|
|
2285
|
-
import { logger as
|
|
2752
|
+
import { logger as logger7 } from "@secondlayer/shared/logger";
|
|
2286
2753
|
|
|
2287
2754
|
// src/runtime/sandbox/bundle.ts
|
|
2288
2755
|
import esbuild from "esbuild";
|
|
@@ -2362,7 +2829,7 @@ function spawnSandboxProcess() {
|
|
|
2362
2829
|
break;
|
|
2363
2830
|
const text = decoder.decode(value, { stream: true });
|
|
2364
2831
|
if (text.trim()) {
|
|
2365
|
-
|
|
2832
|
+
logger7.warn("Sandbox subprocess stderr", { stderr: text.trim() });
|
|
2366
2833
|
}
|
|
2367
2834
|
}
|
|
2368
2835
|
} catch {}
|
|
@@ -2420,7 +2887,7 @@ async function ensureSandboxProcess(subgraphName, version, handlerCode) {
|
|
|
2420
2887
|
try {
|
|
2421
2888
|
await initSandboxProcess(existing.proc, subgraphName, version, handlerCode);
|
|
2422
2889
|
existing.version = version;
|
|
2423
|
-
|
|
2890
|
+
logger7.info("Sandbox subprocess handler reloaded", {
|
|
2424
2891
|
subgraph: subgraphName,
|
|
2425
2892
|
version
|
|
2426
2893
|
});
|
|
@@ -2428,7 +2895,7 @@ async function ensureSandboxProcess(subgraphName, version, handlerCode) {
|
|
|
2428
2895
|
} catch (err) {
|
|
2429
2896
|
existing.proc.kill();
|
|
2430
2897
|
pool.delete(subgraphName);
|
|
2431
|
-
|
|
2898
|
+
logger7.warn("Sandbox subprocess re-init failed; respawning", {
|
|
2432
2899
|
subgraph: subgraphName,
|
|
2433
2900
|
error: err instanceof Error ? err.message : String(err)
|
|
2434
2901
|
});
|
|
@@ -2511,7 +2978,7 @@ async function runBlockThroughProcess(entry, subgraphName, hostCtx, block, match
|
|
|
2511
2978
|
return result;
|
|
2512
2979
|
} catch (err) {
|
|
2513
2980
|
evictSandboxWorker(subgraphName);
|
|
2514
|
-
|
|
2981
|
+
logger7.warn("Sandbox subprocess run failed; subprocess evicted", {
|
|
2515
2982
|
subgraph: subgraphName,
|
|
2516
2983
|
blockHeight: block.height,
|
|
2517
2984
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -2582,7 +3049,7 @@ async function answerRead(hostCtx, msg) {
|
|
|
2582
3049
|
|
|
2583
3050
|
// src/runtime/subscription-state.ts
|
|
2584
3051
|
import { listSubscriptions } from "@secondlayer/shared/db/queries/subscriptions";
|
|
2585
|
-
import { sql as
|
|
3052
|
+
import { sql as sql3 } from "kysely";
|
|
2586
3053
|
|
|
2587
3054
|
// src/runtime/emitter-matcher.ts
|
|
2588
3055
|
function isPrimitive(v) {
|
|
@@ -2746,7 +3213,7 @@ class SubscriptionMatcher {
|
|
|
2746
3213
|
// src/runtime/subscription-state.ts
|
|
2747
3214
|
var matcher = new SubscriptionMatcher;
|
|
2748
3215
|
async function refreshMatcher(db) {
|
|
2749
|
-
const rows = await
|
|
3216
|
+
const rows = await sql3`
|
|
2750
3217
|
SELECT * FROM subscriptions WHERE status = 'active'
|
|
2751
3218
|
`.execute(db);
|
|
2752
3219
|
matcher.setAll(rows.rows);
|
|
@@ -2768,7 +3235,7 @@ async function resolveRoute(subgraphName, targetDb) {
|
|
|
2768
3235
|
version: row?.version ?? ""
|
|
2769
3236
|
};
|
|
2770
3237
|
routeCache.set(subgraphName, route);
|
|
2771
|
-
|
|
3238
|
+
logger8.info("subgraph handler dispatch path resolved", {
|
|
2772
3239
|
subgraph: subgraphName,
|
|
2773
3240
|
path: sandboxEnabled({ sandbox_workers: route.sandboxWorkers }) ? "sandbox" : "in-process"
|
|
2774
3241
|
});
|
|
@@ -2792,8 +3259,8 @@ async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, tx
|
|
|
2792
3259
|
for (const [discoveringSource, factory] of factories) {
|
|
2793
3260
|
const known = new Set;
|
|
2794
3261
|
try {
|
|
2795
|
-
const rows = await
|
|
2796
|
-
SELECT address FROM ${
|
|
3262
|
+
const rows = await sql4`
|
|
3263
|
+
SELECT address FROM ${sql4.raw(`"${schemaName}"."_factory_addresses"`)}
|
|
2797
3264
|
WHERE source_name = ${discoveringSource} AND block_height <= ${blockHeight}
|
|
2798
3265
|
`.execute(db);
|
|
2799
3266
|
for (const row of rows.rows)
|
|
@@ -2821,8 +3288,8 @@ async function persistFactoryDiscoveries(schemaName, db, blockHeight, discovered
|
|
|
2821
3288
|
if (discovered.length === 0)
|
|
2822
3289
|
return;
|
|
2823
3290
|
for (const { sourceName, address } of discovered) {
|
|
2824
|
-
await
|
|
2825
|
-
INSERT INTO ${
|
|
3291
|
+
await sql4`
|
|
3292
|
+
INSERT INTO ${sql4.raw(`"${schemaName}"."_factory_addresses"`)}
|
|
2826
3293
|
(source_name, address, block_height)
|
|
2827
3294
|
VALUES (${sourceName}, ${address}, ${blockHeight})
|
|
2828
3295
|
ON CONFLICT (source_name, address) DO NOTHING
|
|
@@ -2882,7 +3349,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
|
|
|
2882
3349
|
const delay = retryDelaysMs[attempt];
|
|
2883
3350
|
if (delay === undefined)
|
|
2884
3351
|
break;
|
|
2885
|
-
|
|
3352
|
+
logger8.warn("Block processing failed, retrying", {
|
|
2886
3353
|
subgraph: subgraphName,
|
|
2887
3354
|
blockHeight,
|
|
2888
3355
|
attempt: attempt + 1,
|
|
@@ -2895,7 +3362,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
|
|
|
2895
3362
|
throw lastError;
|
|
2896
3363
|
}
|
|
2897
3364
|
async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
2898
|
-
const targetDb =
|
|
3365
|
+
const targetDb = getTargetDb2();
|
|
2899
3366
|
const blockStart = performance.now();
|
|
2900
3367
|
const result = {
|
|
2901
3368
|
blockHeight,
|
|
@@ -2914,7 +3381,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2914
3381
|
} else {
|
|
2915
3382
|
const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
|
|
2916
3383
|
if (!data) {
|
|
2917
|
-
|
|
3384
|
+
logger8.debug("Block not found or non-canonical for subgraph processing", {
|
|
2918
3385
|
subgraph: subgraphName,
|
|
2919
3386
|
blockHeight
|
|
2920
3387
|
});
|
|
@@ -2980,7 +3447,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2980
3447
|
{ start: blockHeight, end: blockHeight, reason: "processing_error" }
|
|
2981
3448
|
]);
|
|
2982
3449
|
} else {
|
|
2983
|
-
|
|
3450
|
+
logger8.warn("Block errors not recorded as a gap — no subgraphId", {
|
|
2984
3451
|
subgraph: subgraphName,
|
|
2985
3452
|
blockHeight,
|
|
2986
3453
|
errors: rr.errors
|
|
@@ -3053,7 +3520,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
3053
3520
|
});
|
|
3054
3521
|
} catch (err) {
|
|
3055
3522
|
if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
|
|
3056
|
-
|
|
3523
|
+
logger8.warn("cursor race lost — block already covered", {
|
|
3057
3524
|
subgraph: subgraphName,
|
|
3058
3525
|
blockHeight,
|
|
3059
3526
|
error: err.message
|
|
@@ -3078,10 +3545,10 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
3078
3545
|
try {
|
|
3079
3546
|
const tables = Object.keys(subgraph.schema);
|
|
3080
3547
|
for (const table of tables) {
|
|
3081
|
-
const { rows } = await
|
|
3548
|
+
const { rows } = await sql4.raw(`SELECT n_live_tup AS count FROM pg_stat_user_tables WHERE schemaname = '${schemaName}' AND relname = '${table}'`).execute(route.dataDb);
|
|
3082
3549
|
const count = Number(rows[0]?.count ?? 0);
|
|
3083
3550
|
if (count >= 1e7) {
|
|
3084
|
-
|
|
3551
|
+
logger8.warn("Subgraph table exceeds 10M rows (estimate)", {
|
|
3085
3552
|
subgraph: subgraphName,
|
|
3086
3553
|
table,
|
|
3087
3554
|
count
|
|
@@ -3089,13 +3556,13 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
3089
3556
|
}
|
|
3090
3557
|
}
|
|
3091
3558
|
} catch (err) {
|
|
3092
|
-
|
|
3559
|
+
logger8.debug("Row count sample failed", {
|
|
3093
3560
|
subgraph: subgraphName,
|
|
3094
3561
|
error: err instanceof Error ? err.message : String(err)
|
|
3095
3562
|
});
|
|
3096
3563
|
}
|
|
3097
3564
|
if (journalEnabled(opts)) {
|
|
3098
|
-
await
|
|
3565
|
+
await sql4.raw(`DELETE FROM "${schemaName}"."_journal" WHERE "block_height" < ${blockHeight - JOURNAL_RETENTION_BLOCKS}`).execute(route.dataDb).catch(() => {});
|
|
3099
3566
|
}
|
|
3100
3567
|
}
|
|
3101
3568
|
return result;
|
|
@@ -3165,9 +3632,9 @@ class StatsAccumulator {
|
|
|
3165
3632
|
}
|
|
3166
3633
|
|
|
3167
3634
|
// src/runtime/catchup.ts
|
|
3168
|
-
import { getTargetDb as
|
|
3635
|
+
import { getTargetDb as getTargetDb3, sql as sql5 } from "@secondlayer/shared/db";
|
|
3169
3636
|
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
3170
|
-
import { logger as
|
|
3637
|
+
import { logger as logger9 } from "@secondlayer/shared/logger";
|
|
3171
3638
|
|
|
3172
3639
|
// src/runtime/catchup-leader.ts
|
|
3173
3640
|
import {
|
|
@@ -3297,8 +3764,8 @@ async function withSubgraphBlockLock(name, fn) {
|
|
|
3297
3764
|
}
|
|
3298
3765
|
}
|
|
3299
3766
|
async function withSubgraphBlockAdvisoryLock(name, fn) {
|
|
3300
|
-
return
|
|
3301
|
-
await
|
|
3767
|
+
return getTargetDb3().transaction().execute(async (lockTx) => {
|
|
3768
|
+
await sql5`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-block:${name}`}))`.execute(lockTx);
|
|
3302
3769
|
return fn();
|
|
3303
3770
|
});
|
|
3304
3771
|
}
|
|
@@ -3359,7 +3826,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3359
3826
|
catchingUp.add(subgraphName);
|
|
3360
3827
|
try {
|
|
3361
3828
|
const source = resolveBlockSource(subgraph);
|
|
3362
|
-
const targetDb =
|
|
3829
|
+
const targetDb = getTargetDb3();
|
|
3363
3830
|
const subgraphRow = await getSubgraph(targetDb, subgraphName);
|
|
3364
3831
|
if (!subgraphRow)
|
|
3365
3832
|
return 0;
|
|
@@ -3371,7 +3838,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3371
3838
|
if (usesDecodedIndexPlane(subgraph)) {
|
|
3372
3839
|
const bound = await boundSourceTip(rawTip, decoderNamesForSubgraph(subgraph));
|
|
3373
3840
|
if (!bound.ok) {
|
|
3374
|
-
|
|
3841
|
+
logger9.warn("Subgraph catch-up stalled: missing decoder checkpoint", {
|
|
3375
3842
|
event: "subgraph_catchup_decoder_stall",
|
|
3376
3843
|
subgraph: subgraphName,
|
|
3377
3844
|
missing: bound.missing
|
|
@@ -3379,7 +3846,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3379
3846
|
return 0;
|
|
3380
3847
|
}
|
|
3381
3848
|
if (bound.floor !== null && bound.floor < rawTip) {
|
|
3382
|
-
|
|
3849
|
+
logger9.debug("Subgraph catch-up tip bounded by decoder progress", {
|
|
3383
3850
|
event: "subgraph_catchup_decoder_floor",
|
|
3384
3851
|
subgraph: subgraphName,
|
|
3385
3852
|
rawTip,
|
|
@@ -3394,7 +3861,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3394
3861
|
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
3395
3862
|
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
3396
3863
|
const totalBlocks = chainTip - lastProcessedBlock;
|
|
3397
|
-
|
|
3864
|
+
logger9.info("Subgraph catch-up starting", {
|
|
3398
3865
|
subgraph: subgraphName,
|
|
3399
3866
|
from: startBlock,
|
|
3400
3867
|
to: chainTip,
|
|
@@ -3410,7 +3877,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3410
3877
|
let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
|
|
3411
3878
|
while (currentHeight <= chainTip) {
|
|
3412
3879
|
if (!isCatchUpLeader()) {
|
|
3413
|
-
|
|
3880
|
+
logger9.info("Lost catch-up leadership, stopping catch-up", {
|
|
3414
3881
|
subgraph: subgraphName,
|
|
3415
3882
|
processed
|
|
3416
3883
|
});
|
|
@@ -3418,7 +3885,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3418
3885
|
}
|
|
3419
3886
|
const currentRow = await getSubgraph(targetDb, subgraphName);
|
|
3420
3887
|
if (!currentRow || currentRow.status !== "active") {
|
|
3421
|
-
|
|
3888
|
+
logger9.info("Subgraph status changed, stopping catch-up", {
|
|
3422
3889
|
subgraph: subgraphName,
|
|
3423
3890
|
status: currentRow?.status ?? "deleted"
|
|
3424
3891
|
});
|
|
@@ -3447,7 +3914,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3447
3914
|
blockData = (await source.loadBlockRange(height, height)).get(height);
|
|
3448
3915
|
}
|
|
3449
3916
|
if (!blockData) {
|
|
3450
|
-
|
|
3917
|
+
logger9.warn("Block missing during catch-up, deferring to next tick", {
|
|
3451
3918
|
subgraph: subgraphName,
|
|
3452
3919
|
blockHeight: height
|
|
3453
3920
|
});
|
|
@@ -3468,12 +3935,12 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3468
3935
|
});
|
|
3469
3936
|
} catch (err) {
|
|
3470
3937
|
if (err instanceof ReorgEpochChangedError) {
|
|
3471
|
-
|
|
3938
|
+
logger9.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
|
|
3472
3939
|
stopCatchup = true;
|
|
3473
3940
|
break;
|
|
3474
3941
|
}
|
|
3475
3942
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3476
|
-
|
|
3943
|
+
logger9.error("Block processing failed persistently during catch-up", {
|
|
3477
3944
|
subgraph: subgraphName,
|
|
3478
3945
|
blockHeight: height,
|
|
3479
3946
|
error: errorMsg
|
|
@@ -3492,7 +3959,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3492
3959
|
}
|
|
3493
3960
|
}
|
|
3494
3961
|
if (processed % LOG_INTERVAL === 0) {
|
|
3495
|
-
|
|
3962
|
+
logger9.info("Subgraph catch-up progress", {
|
|
3496
3963
|
subgraph: subgraphName,
|
|
3497
3964
|
processed,
|
|
3498
3965
|
total: totalBlocks,
|
|
@@ -3508,7 +3975,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3508
3975
|
currentHeight = batchEnd + 1;
|
|
3509
3976
|
}
|
|
3510
3977
|
await stats.flush(targetDb);
|
|
3511
|
-
|
|
3978
|
+
logger9.info("Subgraph catch-up complete", {
|
|
3512
3979
|
subgraph: subgraphName,
|
|
3513
3980
|
processed
|
|
3514
3981
|
});
|
|
@@ -3520,7 +3987,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3520
3987
|
|
|
3521
3988
|
// src/runtime/reindex.ts
|
|
3522
3989
|
import { getErrorMessage as getErrorMessage3 } from "@secondlayer/shared";
|
|
3523
|
-
import { getRawClient, getTargetDb as
|
|
3990
|
+
import { getRawClient, getTargetDb as getTargetDb4 } from "@secondlayer/shared/db";
|
|
3524
3991
|
import {
|
|
3525
3992
|
recordGapBatch as recordGapBatch2,
|
|
3526
3993
|
resolveGaps
|
|
@@ -3533,11 +4000,11 @@ import {
|
|
|
3533
4000
|
recordSubgraphProcessed as recordSubgraphProcessed2,
|
|
3534
4001
|
updateSubgraphStatus as updateSubgraphStatus2
|
|
3535
4002
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
3536
|
-
import { logger as
|
|
4003
|
+
import { logger as logger11 } from "@secondlayer/shared/logger";
|
|
3537
4004
|
|
|
3538
4005
|
// src/runtime/reindex-notify.ts
|
|
3539
4006
|
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
3540
|
-
import { logger as
|
|
4007
|
+
import { logger as logger10 } from "@secondlayer/shared/logger";
|
|
3541
4008
|
async function notifyReindexComplete(db, subgraphName, stats) {
|
|
3542
4009
|
try {
|
|
3543
4010
|
const subgraph = await db.selectFrom("subgraphs").select(["account_id"]).where("name", "=", subgraphName).executeTakeFirst();
|
|
@@ -3548,7 +4015,7 @@ async function notifyReindexComplete(db, subgraphName, stats) {
|
|
|
3548
4015
|
return;
|
|
3549
4016
|
const resendKey = process.env.RESEND_API_KEY;
|
|
3550
4017
|
if (!resendKey) {
|
|
3551
|
-
|
|
4018
|
+
logger10.warn("RESEND_API_KEY unset — skipping reindex-complete email", {
|
|
3552
4019
|
subgraph: subgraphName
|
|
3553
4020
|
});
|
|
3554
4021
|
return;
|
|
@@ -3570,14 +4037,14 @@ async function notifyReindexComplete(db, subgraphName, stats) {
|
|
|
3570
4037
|
});
|
|
3571
4038
|
if (!res.ok) {
|
|
3572
4039
|
const text = await res.text().catch(() => "");
|
|
3573
|
-
|
|
4040
|
+
logger10.warn("reindex-complete email failed", {
|
|
3574
4041
|
subgraph: subgraphName,
|
|
3575
4042
|
status: res.status,
|
|
3576
4043
|
body: text.slice(0, 200)
|
|
3577
4044
|
});
|
|
3578
4045
|
}
|
|
3579
4046
|
} catch (err) {
|
|
3580
|
-
|
|
4047
|
+
logger10.warn("reindex-complete email threw", {
|
|
3581
4048
|
subgraph: subgraphName,
|
|
3582
4049
|
error: getErrorMessage2(err)
|
|
3583
4050
|
});
|
|
@@ -3646,7 +4113,7 @@ function coalesceFailedBlocks(blocks) {
|
|
|
3646
4113
|
}
|
|
3647
4114
|
async function processBlockRange(def, opts) {
|
|
3648
4115
|
const source = resolveBlockSource(def);
|
|
3649
|
-
const targetDb =
|
|
4116
|
+
const targetDb = getTargetDb4();
|
|
3650
4117
|
const subgraphName = def.name;
|
|
3651
4118
|
const { fromBlock, toBlock, status } = opts;
|
|
3652
4119
|
const totalBlocks = toBlock - fromBlock + 1;
|
|
@@ -3689,7 +4156,7 @@ async function processBlockRange(def, opts) {
|
|
|
3689
4156
|
while (currentHeight <= toBlock) {
|
|
3690
4157
|
if (opts.signal?.aborted) {
|
|
3691
4158
|
aborted = true;
|
|
3692
|
-
|
|
4159
|
+
logger11.info("Block processing aborted", {
|
|
3693
4160
|
subgraph: subgraphName,
|
|
3694
4161
|
currentBlock: currentHeight,
|
|
3695
4162
|
reason: String(opts.signal.reason ?? "unknown")
|
|
@@ -3740,7 +4207,7 @@ async function processBlockRange(def, opts) {
|
|
|
3740
4207
|
result = mayWrite ? await withSubgraphBlockWriteLock(subgraphName, runBlock) : await runBlock();
|
|
3741
4208
|
} catch (err) {
|
|
3742
4209
|
const errorMsg = getErrorMessage3(err);
|
|
3743
|
-
|
|
4210
|
+
logger11.error("Block processing failed persistently", {
|
|
3744
4211
|
subgraph: subgraphName,
|
|
3745
4212
|
blockHeight: height,
|
|
3746
4213
|
error: errorMsg
|
|
@@ -3786,7 +4253,7 @@ async function processBlockRange(def, opts) {
|
|
|
3786
4253
|
lastProgressFlushAt = now;
|
|
3787
4254
|
}
|
|
3788
4255
|
if (blocksProcessed % LOG_INTERVAL2 === 0) {
|
|
3789
|
-
|
|
4256
|
+
logger11.info(`${status === "reindexing" ? "Reindex" : "Backfill"} progress`, {
|
|
3790
4257
|
subgraph: subgraphName,
|
|
3791
4258
|
processed: blocksProcessed,
|
|
3792
4259
|
total: totalBlocks,
|
|
@@ -3806,7 +4273,7 @@ async function processBlockRange(def, opts) {
|
|
|
3806
4273
|
if (batchFailedBlocks.length > 0 && opts.subgraphId) {
|
|
3807
4274
|
const gaps = coalesceFailedBlocks(batchFailedBlocks);
|
|
3808
4275
|
await recordGapBatch2(targetDb, opts.subgraphId, subgraphName, gaps).catch((err) => {
|
|
3809
|
-
|
|
4276
|
+
logger11.warn("Failed to record subgraph gaps", {
|
|
3810
4277
|
subgraph: subgraphName,
|
|
3811
4278
|
error: err instanceof Error ? err.message : String(err)
|
|
3812
4279
|
});
|
|
@@ -3823,7 +4290,7 @@ async function processBlockRange(def, opts) {
|
|
|
3823
4290
|
} else {
|
|
3824
4291
|
await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
|
|
3825
4292
|
}
|
|
3826
|
-
|
|
4293
|
+
logger11.info("Sparse skip", {
|
|
3827
4294
|
subgraph: subgraphName,
|
|
3828
4295
|
from: batchEnd + 1,
|
|
3829
4296
|
to: jumpTo - 1,
|
|
@@ -3857,7 +4324,7 @@ async function resolveBlockRange(source, def, opts) {
|
|
|
3857
4324
|
}
|
|
3858
4325
|
const bound = await boundSourceTip(rawTip, decoderNamesForSubgraph(def));
|
|
3859
4326
|
if (!bound.ok) {
|
|
3860
|
-
|
|
4327
|
+
logger11.warn("Reindex stalled: missing decoder checkpoint", {
|
|
3861
4328
|
event: "subgraph_reindex_decoder_stall",
|
|
3862
4329
|
subgraph: def.name,
|
|
3863
4330
|
missing: bound.missing
|
|
@@ -3865,7 +4332,7 @@ async function resolveBlockRange(source, def, opts) {
|
|
|
3865
4332
|
throw new Error(`Reindex stalled: missing decoder checkpoint (${bound.missing.join(", ")})`);
|
|
3866
4333
|
}
|
|
3867
4334
|
if (bound.floor !== null && bound.floor < rawTip) {
|
|
3868
|
-
|
|
4335
|
+
logger11.debug("Reindex tip bounded by decoder progress", {
|
|
3869
4336
|
event: "subgraph_reindex_decoder_floor",
|
|
3870
4337
|
subgraph: def.name,
|
|
3871
4338
|
rawTip,
|
|
@@ -3880,16 +4347,16 @@ async function clearReindexMetadata(db, subgraphName) {
|
|
|
3880
4347
|
}
|
|
3881
4348
|
async function reindexSubgraph(def, opts) {
|
|
3882
4349
|
const source = resolveBlockSource(def);
|
|
3883
|
-
const targetDb =
|
|
4350
|
+
const targetDb = getTargetDb4();
|
|
3884
4351
|
const client = getRawClient("target");
|
|
3885
4352
|
const subgraphName = def.name;
|
|
3886
4353
|
const schemaName = opts?.schemaName ?? pgSchemaName(subgraphName);
|
|
3887
4354
|
await updateSubgraphStatus2(targetDb, subgraphName, "reindexing");
|
|
3888
|
-
|
|
4355
|
+
logger11.info("Reindex starting", { subgraph: subgraphName });
|
|
3889
4356
|
try {
|
|
3890
4357
|
const { fromBlock, toBlock } = await resolveBlockRange(source, def, opts);
|
|
3891
4358
|
if (fromBlock > toBlock) {
|
|
3892
|
-
|
|
4359
|
+
logger11.info("No blocks to reindex", {
|
|
3893
4360
|
subgraph: subgraphName,
|
|
3894
4361
|
fromBlock,
|
|
3895
4362
|
toBlock
|
|
@@ -3903,7 +4370,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3903
4370
|
await client.unsafe(stmt);
|
|
3904
4371
|
}
|
|
3905
4372
|
await updateSubgraphStatus2(targetDb, subgraphName, "reindexing", Math.max(0, fromBlock - 1));
|
|
3906
|
-
|
|
4373
|
+
logger11.info("Schema recreated for reindex", {
|
|
3907
4374
|
subgraph: subgraphName,
|
|
3908
4375
|
cursorResetTo: Math.max(0, fromBlock - 1)
|
|
3909
4376
|
});
|
|
@@ -3917,7 +4384,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3917
4384
|
last_error_at: null,
|
|
3918
4385
|
updated_at: new Date
|
|
3919
4386
|
}).where("name", "=", subgraphName).execute();
|
|
3920
|
-
|
|
4387
|
+
logger11.info("Reindexing blocks", {
|
|
3921
4388
|
subgraph: subgraphName,
|
|
3922
4389
|
fromBlock,
|
|
3923
4390
|
toBlock,
|
|
@@ -3939,9 +4406,9 @@ async function reindexSubgraph(def, opts) {
|
|
|
3939
4406
|
if (reason === "user-cancelled") {
|
|
3940
4407
|
await updateSubgraphStatus2(targetDb, subgraphName, "active");
|
|
3941
4408
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3942
|
-
|
|
4409
|
+
logger11.info("Reindex cancelled by user", { subgraph: subgraphName });
|
|
3943
4410
|
} else {
|
|
3944
|
-
|
|
4411
|
+
logger11.info("Reindex interrupted by shutdown, will resume", {
|
|
3945
4412
|
subgraph: subgraphName
|
|
3946
4413
|
});
|
|
3947
4414
|
}
|
|
@@ -3949,7 +4416,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3949
4416
|
}
|
|
3950
4417
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
3951
4418
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3952
|
-
|
|
4419
|
+
logger11.info("Reindex complete", {
|
|
3953
4420
|
subgraph: subgraphName,
|
|
3954
4421
|
blocks: result.blocksProcessed,
|
|
3955
4422
|
events: result.totalEventsProcessed,
|
|
@@ -3962,7 +4429,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3962
4429
|
});
|
|
3963
4430
|
return { processed: result.blocksProcessed };
|
|
3964
4431
|
} catch (err) {
|
|
3965
|
-
|
|
4432
|
+
logger11.error("Reindex failed", {
|
|
3966
4433
|
subgraph: subgraphName,
|
|
3967
4434
|
error: getErrorMessage3(err)
|
|
3968
4435
|
});
|
|
@@ -3971,7 +4438,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
3971
4438
|
}
|
|
3972
4439
|
}
|
|
3973
4440
|
async function resumeReindex(def, opts) {
|
|
3974
|
-
const targetDb =
|
|
4441
|
+
const targetDb = getTargetDb4();
|
|
3975
4442
|
const subgraphName = def.name;
|
|
3976
4443
|
const row = await targetDb.selectFrom("subgraphs").select([
|
|
3977
4444
|
"id",
|
|
@@ -3984,7 +4451,7 @@ async function resumeReindex(def, opts) {
|
|
|
3984
4451
|
const fromBlock = resolveReindexResumeBlock(row);
|
|
3985
4452
|
const toBlock = Number(row.reindex_to_block);
|
|
3986
4453
|
if (fromBlock == null) {
|
|
3987
|
-
|
|
4454
|
+
logger11.info("No reindex metadata, starting fresh reindex", {
|
|
3988
4455
|
subgraph: subgraphName
|
|
3989
4456
|
});
|
|
3990
4457
|
return reindexSubgraph(def, {
|
|
@@ -3993,12 +4460,12 @@ async function resumeReindex(def, opts) {
|
|
|
3993
4460
|
});
|
|
3994
4461
|
}
|
|
3995
4462
|
if (fromBlock > toBlock) {
|
|
3996
|
-
|
|
4463
|
+
logger11.info("Resume: no remaining blocks", { subgraph: subgraphName });
|
|
3997
4464
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
3998
4465
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
3999
4466
|
return { processed: 0 };
|
|
4000
4467
|
}
|
|
4001
|
-
|
|
4468
|
+
logger11.info("Resuming reindex", {
|
|
4002
4469
|
subgraph: subgraphName,
|
|
4003
4470
|
fromBlock,
|
|
4004
4471
|
toBlock,
|
|
@@ -4020,9 +4487,9 @@ async function resumeReindex(def, opts) {
|
|
|
4020
4487
|
if (reason === "user-cancelled") {
|
|
4021
4488
|
await updateSubgraphStatus2(targetDb, subgraphName, "active");
|
|
4022
4489
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
4023
|
-
|
|
4490
|
+
logger11.info("Resume cancelled by user", { subgraph: subgraphName });
|
|
4024
4491
|
} else {
|
|
4025
|
-
|
|
4492
|
+
logger11.info("Resume interrupted by shutdown, will resume again", {
|
|
4026
4493
|
subgraph: subgraphName
|
|
4027
4494
|
});
|
|
4028
4495
|
}
|
|
@@ -4030,13 +4497,13 @@ async function resumeReindex(def, opts) {
|
|
|
4030
4497
|
}
|
|
4031
4498
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
4032
4499
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
4033
|
-
|
|
4500
|
+
logger11.info("Resumed reindex complete", {
|
|
4034
4501
|
subgraph: subgraphName,
|
|
4035
4502
|
blocks: result.blocksProcessed
|
|
4036
4503
|
});
|
|
4037
4504
|
return { processed: result.blocksProcessed };
|
|
4038
4505
|
} catch (err) {
|
|
4039
|
-
|
|
4506
|
+
logger11.error("Resumed reindex failed", {
|
|
4040
4507
|
subgraph: subgraphName,
|
|
4041
4508
|
error: getErrorMessage3(err)
|
|
4042
4509
|
});
|
|
@@ -4045,9 +4512,9 @@ async function resumeReindex(def, opts) {
|
|
|
4045
4512
|
}
|
|
4046
4513
|
}
|
|
4047
4514
|
async function backfillSubgraph(def, opts) {
|
|
4048
|
-
const targetDb =
|
|
4515
|
+
const targetDb = getTargetDb4();
|
|
4049
4516
|
const subgraphName = def.name;
|
|
4050
|
-
|
|
4517
|
+
logger11.info("Backfill starting", {
|
|
4051
4518
|
subgraph: subgraphName,
|
|
4052
4519
|
from: opts.fromBlock,
|
|
4053
4520
|
to: opts.toBlock
|
|
@@ -4065,17 +4532,17 @@ async function backfillSubgraph(def, opts) {
|
|
|
4065
4532
|
signal: opts.signal
|
|
4066
4533
|
});
|
|
4067
4534
|
if (result.aborted) {
|
|
4068
|
-
|
|
4535
|
+
logger11.info("Backfill aborted", { subgraph: subgraphName });
|
|
4069
4536
|
return { processed: result.blocksProcessed };
|
|
4070
4537
|
}
|
|
4071
4538
|
const resolved = await resolveGaps(targetDb, subgraphName, opts.fromBlock, opts.toBlock).catch(() => 0);
|
|
4072
4539
|
if (resolved > 0) {
|
|
4073
|
-
|
|
4540
|
+
logger11.info("Resolved subgraph gaps via backfill", {
|
|
4074
4541
|
subgraph: subgraphName,
|
|
4075
4542
|
resolved
|
|
4076
4543
|
});
|
|
4077
4544
|
}
|
|
4078
|
-
|
|
4545
|
+
logger11.info("Backfill complete", {
|
|
4079
4546
|
subgraph: subgraphName,
|
|
4080
4547
|
blocks: result.blocksProcessed,
|
|
4081
4548
|
events: result.totalEventsProcessed,
|
|
@@ -4083,7 +4550,7 @@ async function backfillSubgraph(def, opts) {
|
|
|
4083
4550
|
});
|
|
4084
4551
|
return { processed: result.blocksProcessed };
|
|
4085
4552
|
} catch (err) {
|
|
4086
|
-
|
|
4553
|
+
logger11.error("Backfill failed", {
|
|
4087
4554
|
subgraph: subgraphName,
|
|
4088
4555
|
error: getErrorMessage3(err)
|
|
4089
4556
|
});
|
|
@@ -4093,16 +4560,16 @@ async function backfillSubgraph(def, opts) {
|
|
|
4093
4560
|
|
|
4094
4561
|
// src/runtime/reorg.ts
|
|
4095
4562
|
import { getErrorMessage as getErrorMessage4 } from "@secondlayer/shared";
|
|
4096
|
-
import { getRawClient as getRawClient2, getTargetDb as
|
|
4563
|
+
import { getRawClient as getRawClient2, getTargetDb as getTargetDb5 } from "@secondlayer/shared/db";
|
|
4097
4564
|
import { listSubgraphs } from "@secondlayer/shared/db/queries/subgraphs";
|
|
4098
|
-
import { logger as
|
|
4099
|
-
import { sql as
|
|
4565
|
+
import { logger as logger12 } from "@secondlayer/shared/logger";
|
|
4566
|
+
import { sql as sql6 } from "kysely";
|
|
4100
4567
|
async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
4101
|
-
const targetDb =
|
|
4568
|
+
const targetDb = getTargetDb5();
|
|
4102
4569
|
const activeSubgraphs = (await listSubgraphs(targetDb)).filter((v) => v.status === "active");
|
|
4103
4570
|
if (activeSubgraphs.length === 0)
|
|
4104
4571
|
return;
|
|
4105
|
-
|
|
4572
|
+
logger12.info("Propagating reorg to subgraphs", {
|
|
4106
4573
|
blockHeight,
|
|
4107
4574
|
subgraphCount: activeSubgraphs.length
|
|
4108
4575
|
});
|
|
@@ -4113,13 +4580,13 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
|
4113
4580
|
continue;
|
|
4114
4581
|
bumpReorgEpoch(sg.name);
|
|
4115
4582
|
await withSubgraphBlockLock(sg.name, async () => {
|
|
4116
|
-
await
|
|
4117
|
-
await
|
|
4583
|
+
await getTargetDb5().transaction().execute(async (lockTx) => {
|
|
4584
|
+
await sql6`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-reorg:${sg.name}`}))`.execute(lockTx);
|
|
4118
4585
|
await reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef);
|
|
4119
4586
|
});
|
|
4120
4587
|
});
|
|
4121
4588
|
} catch (err) {
|
|
4122
|
-
|
|
4589
|
+
logger12.error("Subgraph reorg handling failed", {
|
|
4123
4590
|
subgraph: sg.name,
|
|
4124
4591
|
blockHeight,
|
|
4125
4592
|
error: getErrorMessage4(err)
|
|
@@ -4128,7 +4595,7 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
|
|
|
4128
4595
|
}
|
|
4129
4596
|
}
|
|
4130
4597
|
async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
|
|
4131
|
-
const targetDb =
|
|
4598
|
+
const targetDb = getTargetDb5();
|
|
4132
4599
|
const schemaName = sg.schema_name ?? pgSchemaName(sg.name);
|
|
4133
4600
|
const client = getRawClient2("target");
|
|
4134
4601
|
const tableNames = Object.keys(schema);
|
|
@@ -4157,7 +4624,7 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
|
|
|
4157
4624
|
await tx.unsafe(`DELETE FROM "${schemaName}"."_journal" WHERE block_height >= $1`, [blockHeight]);
|
|
4158
4625
|
});
|
|
4159
4626
|
} else {
|
|
4160
|
-
|
|
4627
|
+
logger12.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
|
|
4161
4628
|
for (const tableName of tableNames) {
|
|
4162
4629
|
await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
|
|
4163
4630
|
}
|
|
@@ -4167,6 +4634,7 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
|
|
|
4167
4634
|
DELETE FROM "${schemaName}"."_factory_addresses" WHERE block_height >= ${Number(blockHeight)};
|
|
4168
4635
|
END IF;
|
|
4169
4636
|
END $$`);
|
|
4637
|
+
await targetDb.deleteFrom("subgraph_violations").where("subgraph_name", "=", sg.name).where("block_height", ">=", blockHeight).execute();
|
|
4170
4638
|
for (const [tableName, rows] of Object.entries(revertedByTable)) {
|
|
4171
4639
|
if (rows.length === 0)
|
|
4172
4640
|
continue;
|
|
@@ -4200,7 +4668,7 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
|
|
|
4200
4668
|
}).as("row_pk"),
|
|
4201
4669
|
eb2.val("pending").as("status")
|
|
4202
4670
|
]).where("subgraph_name", "=", sg.name).where("table_name", "=", tableName).where("status", "=", "active")).onConflict((oc) => oc.columns(["subscription_id", "dedup_key"]).doNothing()).execute().catch((err) => {
|
|
4203
|
-
|
|
4671
|
+
logger12.error("Failed to emit revert event for subscriptions", {
|
|
4204
4672
|
event: "reorg_revert_emit_dropped",
|
|
4205
4673
|
subgraph: sg.name,
|
|
4206
4674
|
table: tableName,
|
|
@@ -4209,14 +4677,14 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
|
|
|
4209
4677
|
});
|
|
4210
4678
|
});
|
|
4211
4679
|
}
|
|
4212
|
-
|
|
4680
|
+
logger12.info("Subgraph reorg cleanup done", {
|
|
4213
4681
|
subgraph: sg.name,
|
|
4214
4682
|
blockHeight,
|
|
4215
4683
|
tablesAffected: Object.keys(revertedByTable).length
|
|
4216
4684
|
});
|
|
4217
4685
|
const def = await loadSubgraphDef(sg);
|
|
4218
4686
|
await processBlock(def, sg.name, blockHeight, { reorgRewind: true });
|
|
4219
|
-
|
|
4687
|
+
logger12.info("Subgraph reorg reprocessed", {
|
|
4220
4688
|
subgraph: sg.name,
|
|
4221
4689
|
blockHeight
|
|
4222
4690
|
});
|
|
@@ -4228,7 +4696,7 @@ import { hostname } from "node:os";
|
|
|
4228
4696
|
import { resolve } from "node:path";
|
|
4229
4697
|
import { pathToFileURL } from "node:url";
|
|
4230
4698
|
import { getErrorMessage as getErrorMessage6 } from "@secondlayer/shared";
|
|
4231
|
-
import { getTargetDb as
|
|
4699
|
+
import { getTargetDb as getTargetDb6 } from "@secondlayer/shared/db";
|
|
4232
4700
|
import {
|
|
4233
4701
|
cancelSubgraphOperation,
|
|
4234
4702
|
claimSubgraphOperation,
|
|
@@ -4245,7 +4713,7 @@ import {
|
|
|
4245
4713
|
pgSchemaName as pgSchemaName2,
|
|
4246
4714
|
updateSubgraphStatus as updateSubgraphStatus3
|
|
4247
4715
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
4248
|
-
import { logger as
|
|
4716
|
+
import { logger as logger14 } from "@secondlayer/shared/logger";
|
|
4249
4717
|
import {
|
|
4250
4718
|
listen,
|
|
4251
4719
|
sourceListenerUrl,
|
|
@@ -4257,7 +4725,7 @@ import { getErrorMessage as getErrorMessage5 } from "@secondlayer/shared";
|
|
|
4257
4725
|
import {
|
|
4258
4726
|
createInternalIndexHttpClient as createInternalIndexHttpClient2
|
|
4259
4727
|
} from "@secondlayer/shared/index-http";
|
|
4260
|
-
import { logger as
|
|
4728
|
+
import { logger as logger13 } from "@secondlayer/shared/logger";
|
|
4261
4729
|
var POLL_MS = Number(process.env.SUBGRAPH_REORG_POLL_MS) || 15000;
|
|
4262
4730
|
var STARTUP_MARGIN_MS = 60 * 60 * 1000;
|
|
4263
4731
|
async function pollReorgsOnce(http2, cursor, onReorg, handled) {
|
|
@@ -4265,13 +4733,13 @@ async function pollReorgsOnce(http2, cursor, onReorg, handled) {
|
|
|
4265
4733
|
const sorted = [...reorgs].sort((a, b) => a.fork_point_height - b.fork_point_height);
|
|
4266
4734
|
for (const r of sorted) {
|
|
4267
4735
|
if (handled && r.id && handled.has(r.id)) {
|
|
4268
|
-
|
|
4736
|
+
logger13.info("Streams reorg already handled — skipping", {
|
|
4269
4737
|
forkPointHeight: r.fork_point_height,
|
|
4270
4738
|
reorgId: r.id
|
|
4271
4739
|
});
|
|
4272
4740
|
continue;
|
|
4273
4741
|
}
|
|
4274
|
-
|
|
4742
|
+
logger13.info("Streams reorg — rewinding", {
|
|
4275
4743
|
forkPointHeight: r.fork_point_height
|
|
4276
4744
|
});
|
|
4277
4745
|
await onReorg(r.fork_point_height);
|
|
@@ -4294,7 +4762,7 @@ function startStreamsReorgPoll(onReorg) {
|
|
|
4294
4762
|
try {
|
|
4295
4763
|
since = await pollReorgsOnce(http2, since, onReorg, handled);
|
|
4296
4764
|
} catch (err) {
|
|
4297
|
-
|
|
4765
|
+
logger13.error("Streams reorg poll failed", {
|
|
4298
4766
|
error: getErrorMessage5(err)
|
|
4299
4767
|
});
|
|
4300
4768
|
}
|
|
@@ -4302,7 +4770,7 @@ function startStreamsReorgPoll(onReorg) {
|
|
|
4302
4770
|
timer = setTimeout(tick, POLL_MS);
|
|
4303
4771
|
};
|
|
4304
4772
|
timer = setTimeout(tick, POLL_MS);
|
|
4305
|
-
|
|
4773
|
+
logger13.info("Streams reorg poll started", { pollMs: POLL_MS });
|
|
4306
4774
|
return () => {
|
|
4307
4775
|
running = false;
|
|
4308
4776
|
if (timer)
|
|
@@ -4333,7 +4801,7 @@ async function catchUpAll(subgraphs, db, concurrency) {
|
|
|
4333
4801
|
if (isHandlerNotFoundError(err)) {
|
|
4334
4802
|
await updateSubgraphStatus3(db, sg.name, "error");
|
|
4335
4803
|
}
|
|
4336
|
-
|
|
4804
|
+
logger14.error("Subgraph catch-up failed", {
|
|
4337
4805
|
subgraph: sg.name,
|
|
4338
4806
|
error: msg
|
|
4339
4807
|
});
|
|
@@ -4373,7 +4841,7 @@ async function loadSubgraphDefinition(sg) {
|
|
|
4373
4841
|
definitionCache.set(sg.name, def);
|
|
4374
4842
|
if (prevVersion && prevVersion !== sg.version) {
|
|
4375
4843
|
invalidateSubgraphRoute(sg.name);
|
|
4376
|
-
|
|
4844
|
+
logger14.info("Subgraph handler reloaded", {
|
|
4377
4845
|
subgraph: sg.name,
|
|
4378
4846
|
from: prevVersion,
|
|
4379
4847
|
to: sg.version
|
|
@@ -4392,7 +4860,7 @@ function cleanupCaches(active) {
|
|
|
4392
4860
|
}
|
|
4393
4861
|
}
|
|
4394
4862
|
async function synthesizeLegacyReindexOperations() {
|
|
4395
|
-
const db =
|
|
4863
|
+
const db = getTargetDb6();
|
|
4396
4864
|
const stale = (await listSubgraphs2(db)).filter((sg) => sg.status === "reindexing");
|
|
4397
4865
|
for (const sg of stale) {
|
|
4398
4866
|
const active = await findActiveSubgraphOperation(db, sg.id);
|
|
@@ -4407,7 +4875,7 @@ async function synthesizeLegacyReindexOperations() {
|
|
|
4407
4875
|
fromBlock: sg.reindex_from_block == null ? undefined : Number(sg.reindex_from_block),
|
|
4408
4876
|
toBlock: sg.reindex_to_block == null ? undefined : Number(sg.reindex_to_block)
|
|
4409
4877
|
});
|
|
4410
|
-
|
|
4878
|
+
logger14.info("Queued legacy reindex resume operation", {
|
|
4411
4879
|
subgraph: sg.name
|
|
4412
4880
|
});
|
|
4413
4881
|
} catch (err) {
|
|
@@ -4421,7 +4889,7 @@ async function runSubgraphOperation(operation, signal) {
|
|
|
4421
4889
|
if (operation.cancel_requested) {
|
|
4422
4890
|
return 0;
|
|
4423
4891
|
}
|
|
4424
|
-
const db =
|
|
4892
|
+
const db = getTargetDb6();
|
|
4425
4893
|
const subgraph = await db.selectFrom("subgraphs").selectAll().where("id", "=", operation.subgraph_id).executeTakeFirst();
|
|
4426
4894
|
if (!subgraph)
|
|
4427
4895
|
throw new Error(`Subgraph not found: ${operation.subgraph_id}`);
|
|
@@ -4460,13 +4928,13 @@ async function runSubgraphOperation(operation, signal) {
|
|
|
4460
4928
|
}
|
|
4461
4929
|
async function startSubgraphOperationRunner(opts) {
|
|
4462
4930
|
const concurrency = opts?.concurrency ?? DEFAULT_OPERATION_CONCURRENCY;
|
|
4463
|
-
const db =
|
|
4931
|
+
const db = getTargetDb6();
|
|
4464
4932
|
const lockedBy = `${hostname()}:${process.pid}:${randomUUID()}`;
|
|
4465
4933
|
const active = new Map;
|
|
4466
4934
|
const activeRuns = new Map;
|
|
4467
4935
|
let running = true;
|
|
4468
4936
|
let draining = false;
|
|
4469
|
-
|
|
4937
|
+
logger14.info("Starting subgraph operation runner", { concurrency, lockedBy });
|
|
4470
4938
|
try {
|
|
4471
4939
|
const stranded = await db.selectFrom("subgraphs").select(["id", "name", "account_id"]).where("status", "=", "reindexing").where("reindex_from_block", "is not", null).where("reindex_to_block", "is not", null).where(({ not, exists, selectFrom }) => not(exists(selectFrom("subgraph_operations").select("id").whereRef("subgraph_id", "=", "subgraphs.id").where("status", "in", ["queued", "running"])))).execute();
|
|
4472
4940
|
for (const row of stranded) {
|
|
@@ -4477,14 +4945,14 @@ async function startSubgraphOperationRunner(opts) {
|
|
|
4477
4945
|
accountId: row.account_id,
|
|
4478
4946
|
kind: "reindex"
|
|
4479
4947
|
});
|
|
4480
|
-
|
|
4948
|
+
logger14.info("Re-enqueued stranded reindex", { subgraph: row.name });
|
|
4481
4949
|
} catch (err) {
|
|
4482
4950
|
if (!isActiveSubgraphOperationConflict(err))
|
|
4483
4951
|
throw err;
|
|
4484
4952
|
}
|
|
4485
4953
|
}
|
|
4486
4954
|
} catch (err) {
|
|
4487
|
-
|
|
4955
|
+
logger14.warn("Stranded-reindex sweep failed", {
|
|
4488
4956
|
error: getErrorMessage6(err)
|
|
4489
4957
|
});
|
|
4490
4958
|
}
|
|
@@ -4495,7 +4963,7 @@ async function startSubgraphOperationRunner(opts) {
|
|
|
4495
4963
|
if (!running)
|
|
4496
4964
|
return;
|
|
4497
4965
|
heartbeatSubgraphOperation(db, operation.id, lockedBy).catch((err) => {
|
|
4498
|
-
|
|
4966
|
+
logger14.warn("Subgraph operation heartbeat failed", {
|
|
4499
4967
|
operationId: operation.id,
|
|
4500
4968
|
error: getErrorMessage6(err)
|
|
4501
4969
|
});
|
|
@@ -4507,7 +4975,7 @@ async function startSubgraphOperationRunner(opts) {
|
|
|
4507
4975
|
controller.abort("user-cancelled");
|
|
4508
4976
|
}
|
|
4509
4977
|
}).catch((err) => {
|
|
4510
|
-
|
|
4978
|
+
logger14.warn("Subgraph operation cancel poll failed", {
|
|
4511
4979
|
operationId: operation.id,
|
|
4512
4980
|
error: getErrorMessage6(err)
|
|
4513
4981
|
});
|
|
@@ -4524,14 +4992,14 @@ async function startSubgraphOperationRunner(opts) {
|
|
|
4524
4992
|
const reason = String(controller.signal.reason ?? "");
|
|
4525
4993
|
if (controller.signal.aborted && reason === "user-cancelled") {
|
|
4526
4994
|
await cancelSubgraphOperation(db, operation.id, lockedBy, processed);
|
|
4527
|
-
|
|
4995
|
+
logger14.info("Subgraph operation cancelled", {
|
|
4528
4996
|
operationId: operation.id,
|
|
4529
4997
|
subgraph: operation.subgraph_name
|
|
4530
4998
|
});
|
|
4531
4999
|
return;
|
|
4532
5000
|
}
|
|
4533
5001
|
if (controller.signal.aborted) {
|
|
4534
|
-
|
|
5002
|
+
logger14.info("Subgraph operation interrupted", {
|
|
4535
5003
|
operationId: operation.id,
|
|
4536
5004
|
subgraph: operation.subgraph_name,
|
|
4537
5005
|
reason
|
|
@@ -4539,7 +5007,7 @@ async function startSubgraphOperationRunner(opts) {
|
|
|
4539
5007
|
return;
|
|
4540
5008
|
}
|
|
4541
5009
|
await completeSubgraphOperation(db, operation.id, lockedBy, processed);
|
|
4542
|
-
|
|
5010
|
+
logger14.info("Subgraph operation completed", {
|
|
4543
5011
|
operationId: operation.id,
|
|
4544
5012
|
subgraph: operation.subgraph_name,
|
|
4545
5013
|
processed
|
|
@@ -4547,7 +5015,7 @@ async function startSubgraphOperationRunner(opts) {
|
|
|
4547
5015
|
} catch (err) {
|
|
4548
5016
|
const reason = String(controller.signal.reason ?? "");
|
|
4549
5017
|
if (controller.signal.aborted && reason === "shutdown") {
|
|
4550
|
-
|
|
5018
|
+
logger14.info("Subgraph operation interrupted by shutdown", {
|
|
4551
5019
|
operationId: operation.id,
|
|
4552
5020
|
subgraph: operation.subgraph_name
|
|
4553
5021
|
});
|
|
@@ -4558,7 +5026,7 @@ async function startSubgraphOperationRunner(opts) {
|
|
|
4558
5026
|
return;
|
|
4559
5027
|
}
|
|
4560
5028
|
await failSubgraphOperation(db, operation.id, lockedBy, getErrorMessage6(err), processed);
|
|
4561
|
-
|
|
5029
|
+
logger14.error("Subgraph operation failed", {
|
|
4562
5030
|
operationId: operation.id,
|
|
4563
5031
|
subgraph: operation.subgraph_name,
|
|
4564
5032
|
error: getErrorMessage6(err)
|
|
@@ -4605,20 +5073,20 @@ async function startSubgraphOperationRunner(opts) {
|
|
|
4605
5073
|
controller.abort("shutdown");
|
|
4606
5074
|
}
|
|
4607
5075
|
await Promise.allSettled(activeRuns.values());
|
|
4608
|
-
|
|
5076
|
+
logger14.info("Subgraph operation runner stopped");
|
|
4609
5077
|
};
|
|
4610
5078
|
}
|
|
4611
5079
|
async function startSubgraphProcessor(opts) {
|
|
4612
5080
|
const concurrency = opts?.concurrency ?? DEFAULT_CONCURRENCY;
|
|
4613
5081
|
let running = true;
|
|
4614
|
-
|
|
5082
|
+
logger14.info("Starting subgraph processor", { concurrency });
|
|
4615
5083
|
const stopOperations = await startSubgraphOperationRunner({
|
|
4616
5084
|
concurrency: Number.parseInt(process.env.SUBGRAPH_OPERATION_CONCURRENCY ?? String(DEFAULT_OPERATION_CONCURRENCY))
|
|
4617
5085
|
});
|
|
4618
5086
|
const runCatchUp = async () => {
|
|
4619
5087
|
if (!running || !isCatchUpLeader())
|
|
4620
5088
|
return;
|
|
4621
|
-
const db =
|
|
5089
|
+
const db = getTargetDb6();
|
|
4622
5090
|
const subgraphs = (await listSubgraphs2(db)).filter((v) => v.status === "active");
|
|
4623
5091
|
cleanupCaches(subgraphs);
|
|
4624
5092
|
await catchUpAll(subgraphs, db, concurrency);
|
|
@@ -4637,7 +5105,7 @@ async function startSubgraphProcessor(opts) {
|
|
|
4637
5105
|
await handleSubgraphReorg(blockHeight, loadSubgraphDefinition);
|
|
4638
5106
|
}
|
|
4639
5107
|
} catch (err) {
|
|
4640
|
-
|
|
5108
|
+
logger14.error("Subgraph reorg handling failed", {
|
|
4641
5109
|
error: getErrorMessage6(err)
|
|
4642
5110
|
});
|
|
4643
5111
|
}
|
|
@@ -4646,7 +5114,7 @@ async function startSubgraphProcessor(opts) {
|
|
|
4646
5114
|
runCatchUp();
|
|
4647
5115
|
}, POLL_INTERVAL_MS);
|
|
4648
5116
|
const stopStreamsReorgPoll = process.env.SUBGRAPH_SOURCE === "streams-index" ? startStreamsReorgPoll((forkHeight) => handleSubgraphReorg(forkHeight, loadSubgraphDefinition)) : undefined;
|
|
4649
|
-
|
|
5117
|
+
logger14.info("Subgraph processor ready");
|
|
4650
5118
|
return async () => {
|
|
4651
5119
|
running = false;
|
|
4652
5120
|
clearInterval(pollInterval);
|
|
@@ -4655,21 +5123,21 @@ async function startSubgraphProcessor(opts) {
|
|
|
4655
5123
|
await stopReorgListening();
|
|
4656
5124
|
stopStreamsReorgPoll?.();
|
|
4657
5125
|
await stopOperations();
|
|
4658
|
-
|
|
5126
|
+
logger14.info("Subgraph processor stopped");
|
|
4659
5127
|
};
|
|
4660
5128
|
}
|
|
4661
5129
|
|
|
4662
5130
|
// src/service.ts
|
|
4663
5131
|
import { assertDbSplit, getDb } from "@secondlayer/shared/db";
|
|
4664
|
-
import { logger as
|
|
4665
|
-
import { sql as
|
|
5132
|
+
import { logger as logger15 } from "@secondlayer/shared/logger";
|
|
5133
|
+
import { sql as sql7 } from "kysely";
|
|
4666
5134
|
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
4667
5135
|
var SERVICE_NAME = "subgraph-processor";
|
|
4668
5136
|
async function writeHeartbeat() {
|
|
4669
5137
|
try {
|
|
4670
|
-
await getDb().insertInto("service_heartbeats").values({ name: SERVICE_NAME }).onConflict((oc) => oc.column("name").doUpdateSet({ updated_at:
|
|
5138
|
+
await getDb().insertInto("service_heartbeats").values({ name: SERVICE_NAME }).onConflict((oc) => oc.column("name").doUpdateSet({ updated_at: sql7`now()` })).execute();
|
|
4671
5139
|
} catch (err) {
|
|
4672
|
-
|
|
5140
|
+
logger15.warn("subgraph-processor heartbeat write failed", {
|
|
4673
5141
|
error: err instanceof Error ? err.message : String(err)
|
|
4674
5142
|
});
|
|
4675
5143
|
}
|
|
@@ -4681,7 +5149,7 @@ var processor = await startSubgraphProcessor({
|
|
|
4681
5149
|
await writeHeartbeat();
|
|
4682
5150
|
var heartbeatInterval = setInterval(writeHeartbeat, HEARTBEAT_INTERVAL_MS2);
|
|
4683
5151
|
var shutdown = async () => {
|
|
4684
|
-
|
|
5152
|
+
logger15.info("Shutting down subgraph processor...");
|
|
4685
5153
|
clearInterval(heartbeatInterval);
|
|
4686
5154
|
await processor();
|
|
4687
5155
|
process.exit(0);
|
|
@@ -4689,5 +5157,5 @@ var shutdown = async () => {
|
|
|
4689
5157
|
process.on("SIGINT", shutdown);
|
|
4690
5158
|
process.on("SIGTERM", shutdown);
|
|
4691
5159
|
|
|
4692
|
-
//# debugId=
|
|
5160
|
+
//# debugId=DF841AAE92C1E98664756E2164756E21
|
|
4693
5161
|
//# sourceMappingURL=service.js.map
|