envio 3.9.0 → 3.10.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/evm.schema.json +26 -0
- package/fuel.schema.json +26 -0
- package/index.d.ts +35 -3
- package/package.json +6 -7
- package/src/Batch.res +9 -0
- package/src/ChainState.res +35 -39
- package/src/ChainState.res.mjs +14 -11
- package/src/ChainState.resi +15 -7
- package/src/Config.res +52 -44
- package/src/Config.res.mjs +51 -17
- package/src/Core.res +4 -0
- package/src/CrossChainState.res +15 -23
- package/src/CrossChainState.res.mjs +10 -18
- package/src/CrossChainState.resi +4 -1
- package/src/Ecosystem.res +7 -2
- package/src/Envio.res +6 -4
- package/src/EventConfigBuilder.res +169 -26
- package/src/EventConfigBuilder.res.mjs +124 -15
- package/src/HandlerRegister.res +21 -4
- package/src/HandlerRegister.res.mjs +24 -5
- package/src/InMemoryStore.res +9 -7
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/IndexerState.res +43 -6
- package/src/IndexerState.res.mjs +36 -7
- package/src/IndexerState.resi +4 -4
- package/src/Internal.res +16 -17
- package/src/Metrics.res +72 -6
- package/src/Metrics.res.mjs +30 -2
- package/src/Persistence.res +36 -30
- package/src/Persistence.res.mjs +1 -20
- package/src/PgStorage.res +104 -56
- package/src/PgStorage.res.mjs +57 -35
- package/src/PruneStaleHistory.res +85 -52
- package/src/PruneStaleHistory.res.mjs +63 -37
- package/src/Rollback.res +68 -45
- package/src/Rollback.res.mjs +38 -19
- package/src/RollbackCommit.res +7 -9
- package/src/RollbackCommit.res.mjs +5 -6
- package/src/SafeCheckpointTracking.res +11 -11
- package/src/SafeCheckpointTracking.res.mjs +7 -6
- package/src/SimulateItems.res +33 -18
- package/src/SimulateItems.res.mjs +26 -22
- package/src/Sink.res +38 -26
- package/src/Sink.res.mjs +21 -16
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +1 -1
- package/src/Utils.res +170 -0
- package/src/Utils.res.mjs +153 -0
- package/src/Writing.res +2 -2
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +366 -698
- package/src/bindings/ClickHouse.res.mjs +371 -421
- package/src/bindings/ClickHouseSink.res +337 -0
- package/src/bindings/ClickHouseSink.res.mjs +246 -0
- package/src/bindings/EventSource.res +8 -2
- package/src/bindings/NodeJs.res +6 -0
- package/src/bindings/Vitest.res +23 -0
- package/src/bindings/Vitest.res.mjs +3 -0
- package/src/bindings/WebSocket.res +9 -10
- package/src/db/CheckpointBounds.res +53 -0
- package/src/db/CheckpointBounds.res.mjs +44 -0
- package/src/db/EntityFilter.res +36 -17
- package/src/db/EntityFilter.res.mjs +25 -14
- package/src/db/EntityHistory.res +47 -22
- package/src/db/EntityHistory.res.mjs +19 -12
- package/src/db/InternalTable.res +84 -35
- package/src/db/InternalTable.res.mjs +56 -23
- package/src/db/RollbackFloors.res +55 -0
- package/src/db/RollbackFloors.res.mjs +92 -0
- package/src/db/Table.res +22 -4
- package/src/db/Table.res.mjs +30 -9
- package/src/sources/Evm.res +2 -0
- package/src/sources/Evm.res.mjs +2 -0
- package/src/sources/EvmHyperSyncSource.res +3 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcWs.res +124 -0
- package/src/sources/EvmRpcWs.res.mjs +117 -0
- package/src/sources/Fuel.res +2 -0
- package/src/sources/Fuel.res.mjs +2 -0
- package/src/sources/FuelHyperSyncSource.res +1 -1
- package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
- package/src/sources/HeightFeed.res +568 -0
- package/src/sources/HeightFeed.res.mjs +452 -0
- package/src/sources/HeightStream.res +257 -0
- package/src/sources/HeightStream.res.mjs +194 -0
- package/src/sources/HyperSync.res +5 -0
- package/src/sources/HyperSync.res.mjs +3 -0
- package/src/sources/HyperSync.resi +4 -0
- package/src/sources/HyperSyncSSE.res +49 -0
- package/src/sources/HyperSyncSSE.res.mjs +79 -0
- package/src/sources/RpcSource.res +1 -1
- package/src/sources/RpcSource.res.mjs +2 -2
- package/src/sources/Source.res +54 -1
- package/src/sources/Source.res.mjs +25 -0
- package/src/sources/SourceManager.res +220 -227
- package/src/sources/SourceManager.res.mjs +139 -164
- package/src/sources/SourceManager.resi +8 -0
- package/src/sources/Svm.res +2 -0
- package/src/sources/Svm.res.mjs +2 -0
- package/src/sources/SvmHyperSyncClient.res +70 -127
- package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
- package/src/sources/SvmHyperSyncSource.res +18 -15
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/svm.schema.json +1 -75
- package/src/MemoryStorage.res +0 -729
- package/src/MemoryStorage.res.mjs +0 -586
- package/src/sources/HyperSyncHeightStream.res +0 -129
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
- package/src/sources/RpcWebSocketHeightStream.res +0 -175
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
package/src/PgStorage.res.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
|
29
29
|
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
30
30
|
import * as ContractMapping from "./ContractMapping.res.mjs";
|
|
31
31
|
import * as IndexDefinition from "./db/IndexDefinition.res.mjs";
|
|
32
|
+
import * as CheckpointBounds from "./db/CheckpointBounds.res.mjs";
|
|
32
33
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
33
34
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
34
35
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
@@ -207,7 +208,7 @@ function getEntityHistory(entityConfig) {
|
|
|
207
208
|
checkpointIdField,
|
|
208
209
|
actionField
|
|
209
210
|
]), undefined);
|
|
210
|
-
let setChangeSchema = EntityHistory.makeSetUpdateSchema(Table.getIdSchema(entityConfig.table), getRowSchema(entityConfig));
|
|
211
|
+
let setChangeSchema = EntityHistory.makeSetUpdateSchema(Table.getIdSchema(entityConfig.table), undefined, getRowSchema(entityConfig));
|
|
211
212
|
let cache_setChangeSchemaRows = S$RescriptSchema.array(setChangeSchema);
|
|
212
213
|
let cache$1 = {
|
|
213
214
|
table: table,
|
|
@@ -296,7 +297,7 @@ function makeFilterCondition(filter, table, params) {
|
|
|
296
297
|
let serializeParamOrThrow = (queryField, fieldName, fieldValue, isArray) => {
|
|
297
298
|
let param;
|
|
298
299
|
try {
|
|
299
|
-
param = S$RescriptSchema.
|
|
300
|
+
param = S$RescriptSchema.reverseConvertOrThrow(fieldValue, isArray ? queryField.arrayFieldSchema : queryField.fieldSchema);
|
|
300
301
|
} catch (raw_exn) {
|
|
301
302
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
302
303
|
throw {
|
|
@@ -410,12 +411,20 @@ VALUES` + placeholders + (
|
|
|
410
411
|
function makeTableBatchSetQuery(pgSchema, table, itemSchema, chainIdModeOpt) {
|
|
411
412
|
let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
|
|
412
413
|
let match = Table.toSqlParams(table, itemSchema, pgSchema, chainIdMode);
|
|
414
|
+
let byteaColumnIndexes = match.byteaColumnIndexes;
|
|
413
415
|
let isRawEvents = table.tableName === InternalTable.RawEvents.table.tableName;
|
|
414
416
|
let isHistoryUpdate = table.tableName.startsWith(EntityHistory.historyTablePrefix);
|
|
415
417
|
if ((isRawEvents || !match.hasArrayField) && !isHistoryUpdate) {
|
|
416
418
|
return {
|
|
417
419
|
query: makeInsertUnnestSetQuery(pgSchema, table, itemSchema, isRawEvents, chainIdMode),
|
|
418
|
-
convertOrThrow: S$RescriptSchema.compile(S$RescriptSchema.unnest(match.dbSchema),
|
|
420
|
+
convertOrThrow: S$RescriptSchema.compile(S$RescriptSchema.preprocess(S$RescriptSchema.unnest(match.dbSchema), param => ({
|
|
421
|
+
s: columns => {
|
|
422
|
+
byteaColumnIndexes.forEach(index => {
|
|
423
|
+
columns[index] = Utils.Bytes.toPgArrayLiteral(columns[index]);
|
|
424
|
+
});
|
|
425
|
+
return columns;
|
|
426
|
+
}
|
|
427
|
+
})), "Output", "Input", "Sync", false),
|
|
419
428
|
isInsertValues: false
|
|
420
429
|
};
|
|
421
430
|
} else {
|
|
@@ -681,7 +690,7 @@ function executeSet(sql, items, dbFunction) {
|
|
|
681
690
|
async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, setQueryCache, updatedEffectsCache, updatedEntities, registeredAddresses, sinkPromise, chainMetaData, escapeTables) {
|
|
682
691
|
try {
|
|
683
692
|
let chainIdMode = config.chainIdMode;
|
|
684
|
-
let shouldSaveHistory = Config.shouldSaveHistory(config, isInReorgThreshold);
|
|
693
|
+
let shouldSaveHistory = Config.shouldSaveHistory(config, isInReorgThreshold, undefined);
|
|
685
694
|
let specificError = {
|
|
686
695
|
contents: undefined
|
|
687
696
|
};
|
|
@@ -723,6 +732,7 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
|
|
|
723
732
|
let idsToDelete = [];
|
|
724
733
|
let scopeChainId;
|
|
725
734
|
scopeChainId = scope === "crossChain" ? undefined : Primitive_option.some(scope);
|
|
735
|
+
let shouldSaveHistory = Config.shouldSaveHistory(config, isInReorgThreshold, scopeChainId);
|
|
726
736
|
let match = Table.getChainIdField(entityConfig.table);
|
|
727
737
|
let changes$1;
|
|
728
738
|
if (match !== undefined && scopeChainId !== undefined) {
|
|
@@ -835,11 +845,15 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
|
|
|
835
845
|
});
|
|
836
846
|
let rollbackTables;
|
|
837
847
|
if (rollback !== undefined) {
|
|
848
|
+
let progressedChains = rollback.progressedChains;
|
|
838
849
|
let rolledBackAddresses = rollback.rolledBackAddresses;
|
|
839
|
-
let
|
|
850
|
+
let floors = rollback.floors;
|
|
840
851
|
rollbackTables = sql => {
|
|
841
|
-
let promises = allEntities.filter(entityConfig => entityConfig.storage.postgres).map(entityConfig => EntityHistory.rollback(sql, pgSchema, entityConfig.name, entityConfig.index,
|
|
842
|
-
promises.push(InternalTable.Checkpoints.rollback(sql, pgSchema,
|
|
852
|
+
let promises = allEntities.filter(entityConfig => entityConfig.storage.postgres).map(entityConfig => EntityHistory.rollback(sql, pgSchema, entityConfig.name, entityConfig.index, Table.getPgChainIdColumn(entityConfig.table), floors));
|
|
853
|
+
promises.push(InternalTable.Checkpoints.rollback(sql, pgSchema, floors));
|
|
854
|
+
if (Utils.$$Array.notEmpty(progressedChains)) {
|
|
855
|
+
promises.push(InternalTable.Chains.setProgressedChains(sql, pgSchema, progressedChains));
|
|
856
|
+
}
|
|
843
857
|
if (Utils.$$Array.notEmpty(rolledBackAddresses)) {
|
|
844
858
|
promises.push(InternalTable.EnvioAddresses.$$delete(sql, pgSchema, rolledBackAddresses, chainIdMode));
|
|
845
859
|
}
|
|
@@ -917,41 +931,45 @@ function rollbackKeyColumns(entityConfig) {
|
|
|
917
931
|
}
|
|
918
932
|
}
|
|
919
933
|
|
|
920
|
-
function makeGetRollbackPreTargetRowsQuery(entityConfig, pgSchema) {
|
|
934
|
+
function makeGetRollbackPreTargetRowsQuery(entityConfig, pgSchema, floors) {
|
|
921
935
|
let dataFieldNames = Stdlib_Array.filterMap(entityConfig.table.fields, fieldOrDerived => {
|
|
922
936
|
if (fieldOrDerived.TAG === "Field") {
|
|
923
937
|
return Table.getPgDbFieldName(fieldOrDerived._0);
|
|
924
938
|
}
|
|
925
939
|
});
|
|
926
|
-
let dataFieldsCommaSeparated = dataFieldNames.map(name => `"` + name + `"`).join(", ");
|
|
927
940
|
let historyTableName = EntityHistory.historyTableName(entityConfig.name, entityConfig.index);
|
|
941
|
+
let tableRef = `"` + historyTableName + `"`;
|
|
942
|
+
let dataFieldsCommaSeparated = dataFieldNames.map(name => tableRef + `."` + name + `"`).join(", ");
|
|
928
943
|
let keyColumns = rollbackKeyColumns(entityConfig);
|
|
929
|
-
let keyColumnsCommaSeparated = keyColumns.map(c =>
|
|
930
|
-
let keyMatch = keyColumns.map(c => `h."` + c + `" =
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
944
|
+
let keyColumnsCommaSeparated = keyColumns.map(c => tableRef + `."` + c + `"`).join(", ");
|
|
945
|
+
let keyMatch = keyColumns.map(c => `h."` + c + `" = ` + tableRef + `."` + c + `"`).join(" AND ");
|
|
946
|
+
let bounds = CheckpointBounds.sql(floors.floors, Table.getPgChainIdColumn(entityConfig.table), tableRef);
|
|
947
|
+
return `SELECT DISTINCT ON (` + keyColumnsCommaSeparated + `) ` + dataFieldsCommaSeparated + `, ` + tableRef + `."` + EntityHistory.changeFieldName + `"
|
|
948
|
+
FROM "` + pgSchema + `"."` + historyTableName + `"` + bounds.join + `
|
|
949
|
+
WHERE ` + tableRef + `."` + EntityHistory.checkpointIdFieldName + `" <= ` + bounds.checkpointId + `
|
|
934
950
|
AND EXISTS (
|
|
935
951
|
SELECT 1
|
|
936
952
|
FROM "` + pgSchema + `"."` + historyTableName + `" h
|
|
937
953
|
WHERE ` + keyMatch + `
|
|
938
|
-
AND h."` + EntityHistory.checkpointIdFieldName + `" >
|
|
954
|
+
AND h."` + EntityHistory.checkpointIdFieldName + `" > ` + bounds.checkpointId + `
|
|
939
955
|
)
|
|
940
|
-
ORDER BY ` + keyColumnsCommaSeparated + `, "` + EntityHistory.checkpointIdFieldName + `" DESC`;
|
|
956
|
+
ORDER BY ` + keyColumnsCommaSeparated + `, ` + tableRef + `."` + EntityHistory.checkpointIdFieldName + `" DESC`;
|
|
941
957
|
}
|
|
942
958
|
|
|
943
|
-
function makeGetRollbackRemovedIdsQuery(entityConfig, pgSchema) {
|
|
959
|
+
function makeGetRollbackRemovedIdsQuery(entityConfig, pgSchema, floors) {
|
|
944
960
|
let historyTableName = EntityHistory.historyTableName(entityConfig.name, entityConfig.index);
|
|
961
|
+
let tableRef = `"` + historyTableName + `"`;
|
|
945
962
|
let keyColumns = rollbackKeyColumns(entityConfig);
|
|
946
|
-
let keyMatch = keyColumns.map(c => `h."` + c + `" =
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
963
|
+
let keyMatch = keyColumns.map(c => `h."` + c + `" = ` + tableRef + `."` + c + `"`).join(" AND ");
|
|
964
|
+
let bounds = CheckpointBounds.sql(floors.floors, Table.getPgChainIdColumn(entityConfig.table), tableRef);
|
|
965
|
+
return `SELECT DISTINCT ` + keyColumns.map(c => tableRef + `."` + c + `"`).join(", ") + `
|
|
966
|
+
FROM "` + pgSchema + `"."` + historyTableName + `"` + bounds.join + `
|
|
967
|
+
WHERE ` + tableRef + `."` + EntityHistory.checkpointIdFieldName + `" > ` + bounds.checkpointId + `
|
|
950
968
|
AND NOT EXISTS (
|
|
951
969
|
SELECT 1
|
|
952
970
|
FROM "` + pgSchema + `"."` + historyTableName + `" h
|
|
953
971
|
WHERE ` + keyMatch + `
|
|
954
|
-
AND h."` + EntityHistory.checkpointIdFieldName + `" <=
|
|
972
|
+
AND h."` + EntityHistory.checkpointIdFieldName + `" <= ` + bounds.checkpointId + `
|
|
955
973
|
)`;
|
|
956
974
|
}
|
|
957
975
|
|
|
@@ -1113,13 +1131,12 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1113
1131
|
let entities = entitiesOpt !== undefined ? entitiesOpt : [];
|
|
1114
1132
|
let enums = enumsOpt !== undefined ? enumsOpt : [];
|
|
1115
1133
|
let pgEntities = entities.filter(e => e.storage.postgres);
|
|
1116
|
-
let chEntities = entities.filter(e => e.storage.clickhouse);
|
|
1117
1134
|
let schemaTableNames = await sql.unsafe(makeSchemaTableNamesQuery(pgSchema));
|
|
1118
1135
|
if (Utils.$$Array.notEmpty(schemaTableNames) && !schemaTableNames.some(table => table.table_name === InternalTable.Chains.table.tableName ? true : table.table_name === "event_sync_state")) {
|
|
1119
1136
|
Stdlib_JsError.throwWithMessage(`Cannot run Envio migrations on PostgreSQL schema "` + pgSchema + `" because it contains non-Envio tables. Running migrations would delete all data in this schema.\n\nTo resolve this:\n1. If you want to use this schema, first backup any important data, then drop it with: "pnpm envio local db-migrate down"\n2. Or specify a different schema name by setting the "ENVIO_PG_SCHEMA" environment variable\n3. Or manually drop the schema in your database if you're certain the data is not needed.`);
|
|
1120
1137
|
}
|
|
1121
1138
|
if (sink !== undefined) {
|
|
1122
|
-
await sink.initialize(
|
|
1139
|
+
await sink.initialize(entities);
|
|
1123
1140
|
}
|
|
1124
1141
|
let queries = makeInitializeTransaction(pgSchema, pgUser, isHasuraEnabled, chainConfigs, pgEntities, enums, Utils.$$Array.isEmpty(schemaTableNames), true, chainIdMode);
|
|
1125
1142
|
let rowsByChain = chainConfigs.map(chainConfig => ChainState.configStorageRows(chainConfig, ecosystem, contractMapping));
|
|
@@ -1398,7 +1415,7 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1398
1415
|
return Logging.errorWithExn(Utils.prettifyExn(exn$1), `Failed to dump cache.`);
|
|
1399
1416
|
}
|
|
1400
1417
|
};
|
|
1401
|
-
let resumeInitialState = async () => {
|
|
1418
|
+
let resumeInitialState = async (entities, throwIfIncompatible) => {
|
|
1402
1419
|
let match = await Promise.all([
|
|
1403
1420
|
restoreEffectCache(false),
|
|
1404
1421
|
InternalTable.Chains.getInitialState(sql, pgSchema).then(rawInitialStates => rawInitialStates.map(rawInitialState => ({
|
|
@@ -1435,6 +1452,10 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1435
1452
|
})
|
|
1436
1453
|
]);
|
|
1437
1454
|
let match$1 = match[4];
|
|
1455
|
+
let storedContractMapping = match$1[1];
|
|
1456
|
+
let storedEnvioInfo = match$1[0];
|
|
1457
|
+
let chains = match[1];
|
|
1458
|
+
throwIfIncompatible(storedEnvioInfo, storedContractMapping);
|
|
1438
1459
|
await reloadIndexCatalog();
|
|
1439
1460
|
let checkpointId = BigInt(match[2][0].id);
|
|
1440
1461
|
let reorgCheckpoints = match[3].map(raw => ({
|
|
@@ -1444,14 +1465,14 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1444
1465
|
block_hash: raw.block_hash
|
|
1445
1466
|
}));
|
|
1446
1467
|
if (sink !== undefined) {
|
|
1447
|
-
await sink.resume(checkpointId);
|
|
1468
|
+
await sink.resume(checkpointId, chains, entities);
|
|
1448
1469
|
}
|
|
1449
1470
|
return {
|
|
1450
1471
|
cleanRun: false,
|
|
1451
|
-
contractMapping:
|
|
1452
|
-
envioInfo:
|
|
1472
|
+
contractMapping: storedContractMapping,
|
|
1473
|
+
envioInfo: storedEnvioInfo,
|
|
1453
1474
|
cache: match[0],
|
|
1454
|
-
chains:
|
|
1475
|
+
chains: chains,
|
|
1455
1476
|
checkpointId: checkpointId,
|
|
1456
1477
|
reorgCheckpoints: reorgCheckpoints
|
|
1457
1478
|
};
|
|
@@ -1461,14 +1482,15 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1461
1482
|
return await sql.unsafe(query);
|
|
1462
1483
|
};
|
|
1463
1484
|
let setChainMeta = chainsData => InternalTable.Chains.setMeta(sql, pgSchema, chainsData).then(param => (undefined));
|
|
1464
|
-
let pruneStaleCheckpoints =
|
|
1465
|
-
let pruneStaleEntityHistory = (entityName, entityIndex, chainIdColumn,
|
|
1485
|
+
let pruneStaleCheckpoints = safeCheckpoints => InternalTable.Checkpoints.pruneStaleCheckpoints(sql, pgSchema, safeCheckpoints);
|
|
1486
|
+
let pruneStaleEntityHistory = (entityName, entityIndex, chainIdColumn, safeCheckpoints) => EntityHistory.pruneStaleEntityHistory(sql, entityName, entityIndex, pgSchema, chainIdColumn, safeCheckpoints);
|
|
1466
1487
|
let getRollbackTargetCheckpoint = (reorgChainId, lastKnownValidBlockNumber) => InternalTable.Checkpoints.getRollbackTargetCheckpoint(sql, pgSchema, reorgChainId, lastKnownValidBlockNumber);
|
|
1467
|
-
let getRollbackProgressDiff =
|
|
1468
|
-
let getRollbackData = async (entityConfig,
|
|
1488
|
+
let getRollbackProgressDiff = floors => InternalTable.Checkpoints.getRollbackProgressDiff(sql, pgSchema, floors);
|
|
1489
|
+
let getRollbackData = async (entityConfig, floors) => {
|
|
1490
|
+
let params = CheckpointBounds.params(floors.floors);
|
|
1469
1491
|
let match = await Promise.all([
|
|
1470
|
-
sql.unsafe(makeGetRollbackRemovedIdsQuery(entityConfig, pgSchema
|
|
1471
|
-
sql.unsafe(makeGetRollbackPreTargetRowsQuery(entityConfig, pgSchema
|
|
1492
|
+
sql.unsafe(makeGetRollbackRemovedIdsQuery(entityConfig, pgSchema, floors), params, {prepare: true}),
|
|
1493
|
+
sql.unsafe(makeGetRollbackPreTargetRowsQuery(entityConfig, pgSchema, floors), params, {prepare: true})
|
|
1472
1494
|
]);
|
|
1473
1495
|
let chainIdSchema = rollbackChainIdSchema(entityConfig.table);
|
|
1474
1496
|
let scopeOf = row => {
|
|
@@ -12,11 +12,43 @@ let maxEntitiesPerWrite = 5
|
|
|
12
12
|
let forcedIntervalMultiplier = 5.
|
|
13
13
|
|
|
14
14
|
type targets = {
|
|
15
|
-
|
|
15
|
+
safeCheckpoints: CheckpointBounds.t,
|
|
16
16
|
concurrent: array<Internal.entityConfig>,
|
|
17
17
|
forced: array<Internal.entityConfig>,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Where each chain's history may be pruned back to, or None while nowhere can.
|
|
21
|
+
//
|
|
22
|
+
// A rollback only ever deletes a chain's rows down to that chain's own floor,
|
|
23
|
+
// which is set by its own reorg and so never reaches below its own safe
|
|
24
|
+
// checkpoint. Each chain can therefore prune to its own, and a chain with
|
|
25
|
+
// nothing safe yet holds only itself back.
|
|
26
|
+
//
|
|
27
|
+
// Unless the schema has a cross-chain entity: a reorg on any chain then rolls
|
|
28
|
+
// every chain back to one checkpoint, which can sit below another chain's safe
|
|
29
|
+
// point, so the lowest is the only bound every chain agrees on.
|
|
30
|
+
let selectSafeCheckpoints = (state: IndexerState.t) => {
|
|
31
|
+
let byChain = state->IndexerState.getSafeCheckpointIdByChain
|
|
32
|
+
let safe =
|
|
33
|
+
byChain->Array.filterMap(((chainId, checkpointId)) =>
|
|
34
|
+
checkpointId->Option.map(checkpointId => (chainId, checkpointId))
|
|
35
|
+
)
|
|
36
|
+
if state->IndexerState.config->Config.isIsolatedMultichain {
|
|
37
|
+
safe->Utils.Array.notEmpty ? Some(CheckpointBounds.PerChain(safe)) : None
|
|
38
|
+
} else if safe->Array.length === byChain->Array.length {
|
|
39
|
+
let (_, lowest) = safe->Array.getUnsafe(0)
|
|
40
|
+
Some(
|
|
41
|
+
CheckpointBounds.EveryChain(
|
|
42
|
+
safe->Array.reduce(lowest, (lowest, (_, checkpointId)) =>
|
|
43
|
+
Pervasives.min(lowest, checkpointId)
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
} else {
|
|
48
|
+
None
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
20
52
|
let selectFrom = (
|
|
21
53
|
~allEntities: array<Internal.entityConfig>,
|
|
22
54
|
~lastPrunedAtMillis: dict<float>,
|
|
@@ -24,7 +56,7 @@ let selectFrom = (
|
|
|
24
56
|
~isRollback,
|
|
25
57
|
~nowMillis,
|
|
26
58
|
~intervalMillis,
|
|
27
|
-
~
|
|
59
|
+
~safeCheckpoints,
|
|
28
60
|
) => {
|
|
29
61
|
let byOldestPrune = ((a, _), (b, _)) => a -. b
|
|
30
62
|
let toEntities = candidates => candidates->Array.map(((_, entityConfig)) => entityConfig)
|
|
@@ -60,7 +92,7 @@ let selectFrom = (
|
|
|
60
92
|
}
|
|
61
93
|
|
|
62
94
|
{
|
|
63
|
-
|
|
95
|
+
safeCheckpoints,
|
|
64
96
|
concurrent: sortedConcurrent->Array.slice(~start=0, ~end=maxEntitiesPerWrite)->toEntities,
|
|
65
97
|
forced: forcedCandidates
|
|
66
98
|
->Array.toSorted(byOldestPrune)
|
|
@@ -72,62 +104,63 @@ let selectFrom = (
|
|
|
72
104
|
let select = (state: IndexerState.t, ~writtenEntityNames, ~isRollback) => {
|
|
73
105
|
let config = state->IndexerState.config
|
|
74
106
|
if config->Config.shouldPruneHistory(~isInReorgThreshold=state->IndexerState.isInReorgThreshold) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
~safeCheckpointId,
|
|
87
|
-
),
|
|
107
|
+
state
|
|
108
|
+
->selectSafeCheckpoints
|
|
109
|
+
->Option.map(safeCheckpoints =>
|
|
110
|
+
selectFrom(
|
|
111
|
+
~allEntities=(state->IndexerState.persistence).allEntities,
|
|
112
|
+
~lastPrunedAtMillis=state->IndexerState.lastPrunedAtMillis,
|
|
113
|
+
~writtenEntityNames,
|
|
114
|
+
~isRollback,
|
|
115
|
+
~nowMillis=Date.now(),
|
|
116
|
+
~intervalMillis=Env.ThrottleWrites.pruneStaleDataIntervalMillis->Int.toFloat,
|
|
117
|
+
~safeCheckpoints,
|
|
88
118
|
)
|
|
89
|
-
|
|
119
|
+
)
|
|
90
120
|
} else {
|
|
91
121
|
None
|
|
92
122
|
}
|
|
93
123
|
}
|
|
94
124
|
|
|
95
|
-
let
|
|
125
|
+
let pruneEntity = async (
|
|
126
|
+
state: IndexerState.t,
|
|
127
|
+
~entityConfig: Internal.entityConfig,
|
|
128
|
+
~safeCheckpoints,
|
|
129
|
+
) => {
|
|
96
130
|
let persistence = state->IndexerState.persistence
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
131
|
+
let timeRef = Performance.now()
|
|
132
|
+
// Recorded for failures too, so a failing prune retries on the same
|
|
133
|
+
// interval instead of on every write.
|
|
134
|
+
state->IndexerState.lastPrunedAtMillis->Dict.set(entityConfig.name, Date.now())
|
|
135
|
+
switch await persistence.storage.pruneStaleEntityHistory(
|
|
136
|
+
~entityName=entityConfig.name,
|
|
137
|
+
~entityIndex=entityConfig.index,
|
|
138
|
+
~chainIdColumn=entityConfig.table->Table.getPgChainIdColumn,
|
|
139
|
+
~safeCheckpoints,
|
|
140
|
+
) {
|
|
141
|
+
| () =>
|
|
142
|
+
state->IndexerState.recordHistoryPrune(
|
|
143
|
+
~timeSeconds=Performance.secondsSince(timeRef),
|
|
104
144
|
~entityName=entityConfig.name,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
~timeSeconds=Performance.secondsSince(timeRef),
|
|
112
|
-
~entityName=entityConfig.name,
|
|
113
|
-
)
|
|
114
|
-
| exception exn =>
|
|
115
|
-
// Pruning is cleanup; a failure must not fail the write loop.
|
|
116
|
-
Logging.createChild(
|
|
117
|
-
~params={
|
|
118
|
-
"entityName": entityConfig.name,
|
|
119
|
-
"safeCheckpointId": safeCheckpointId,
|
|
120
|
-
},
|
|
121
|
-
)->Logging.childErrorWithExn(exn->Utils.prettifyExn, `Failed to prune stale entity history`)
|
|
122
|
-
}
|
|
145
|
+
)
|
|
146
|
+
| exception exn =>
|
|
147
|
+
// Pruning is cleanup; a failure must not fail the write loop.
|
|
148
|
+
Logging.createChild(
|
|
149
|
+
~params={"entityName": entityConfig.name, "safeCheckpoints": safeCheckpoints},
|
|
150
|
+
)->Logging.childErrorWithExn(exn->Utils.prettifyExn, `Failed to prune stale entity history`)
|
|
123
151
|
}
|
|
124
152
|
}
|
|
125
153
|
|
|
126
|
-
let
|
|
127
|
-
|
|
154
|
+
let pruneEntities = (state: IndexerState.t, ~entities, ~safeCheckpoints) =>
|
|
155
|
+
entities->Utils.Array.awaitEach(entityConfig =>
|
|
156
|
+
state->pruneEntity(~entityConfig, ~safeCheckpoints)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
let pruneCheckpoints = async (state: IndexerState.t, ~safeCheckpoints) => {
|
|
160
|
+
switch await (state->IndexerState.persistence).storage.pruneStaleCheckpoints(~safeCheckpoints) {
|
|
128
161
|
| () => ()
|
|
129
162
|
| exception exn =>
|
|
130
|
-
Logging.createChild(~params={"
|
|
163
|
+
Logging.createChild(~params={"safeCheckpoints": safeCheckpoints})->Logging.childErrorWithExn(
|
|
131
164
|
exn->Utils.prettifyExn,
|
|
132
165
|
`Failed to prune stale checkpoints`,
|
|
133
166
|
)
|
|
@@ -136,22 +169,22 @@ let pruneCheckpoints = async (state: IndexerState.t, ~safeCheckpointId) => {
|
|
|
136
169
|
|
|
137
170
|
let runConcurrent = async (state: IndexerState.t, ~targets) => {
|
|
138
171
|
switch targets {
|
|
139
|
-
| Some({
|
|
140
|
-
await pruneCheckpoints(state, ~
|
|
141
|
-
await pruneEntities(state, ~entities=concurrent, ~
|
|
172
|
+
| Some({safeCheckpoints, concurrent}) if concurrent->Utils.Array.notEmpty =>
|
|
173
|
+
await pruneCheckpoints(state, ~safeCheckpoints)
|
|
174
|
+
await pruneEntities(state, ~entities=concurrent, ~safeCheckpoints)
|
|
142
175
|
| Some(_) | None => ()
|
|
143
176
|
}
|
|
144
177
|
}
|
|
145
178
|
|
|
146
179
|
let runForced = async (state: IndexerState.t, ~targets) => {
|
|
147
180
|
switch targets {
|
|
148
|
-
| Some({
|
|
181
|
+
| Some({safeCheckpoints, concurrent, forced}) if forced->Utils.Array.notEmpty =>
|
|
149
182
|
// When nothing ran concurrently (eg a rollback write), checkpoint pruning
|
|
150
183
|
// lands here, after the write, so it never overlaps a rollback transaction.
|
|
151
184
|
if concurrent->Utils.Array.isEmpty {
|
|
152
|
-
await pruneCheckpoints(state, ~
|
|
185
|
+
await pruneCheckpoints(state, ~safeCheckpoints)
|
|
153
186
|
}
|
|
154
|
-
await pruneEntities(state, ~entities=forced, ~
|
|
187
|
+
await pruneEntities(state, ~entities=forced, ~safeCheckpoints)
|
|
155
188
|
| Some(_) | None => ()
|
|
156
189
|
}
|
|
157
190
|
}
|
|
@@ -7,10 +7,41 @@ import * as Config from "./Config.res.mjs";
|
|
|
7
7
|
import * as Logging from "./Logging.res.mjs";
|
|
8
8
|
import * as Performance from "./bindings/Performance.res.mjs";
|
|
9
9
|
import * as IndexerState from "./IndexerState.res.mjs";
|
|
10
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
10
11
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
12
|
+
import * as Primitive_bigint from "@rescript/runtime/lib/es6/Primitive_bigint.js";
|
|
11
13
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
12
14
|
|
|
13
|
-
function
|
|
15
|
+
function selectSafeCheckpoints(state) {
|
|
16
|
+
let byChain = IndexerState.getSafeCheckpointIdByChain(state);
|
|
17
|
+
let safe = Stdlib_Array.filterMap(byChain, param => {
|
|
18
|
+
let chainId = param[0];
|
|
19
|
+
return Stdlib_Option.map(param[1], checkpointId => [
|
|
20
|
+
chainId,
|
|
21
|
+
checkpointId
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
if (Config.isIsolatedMultichain(IndexerState.config(state))) {
|
|
25
|
+
if (Utils.$$Array.notEmpty(safe)) {
|
|
26
|
+
return {
|
|
27
|
+
TAG: "PerChain",
|
|
28
|
+
_0: safe
|
|
29
|
+
};
|
|
30
|
+
} else {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (safe.length !== byChain.length) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
let match = safe[0];
|
|
38
|
+
return {
|
|
39
|
+
TAG: "EveryChain",
|
|
40
|
+
_0: Stdlib_Array.reduce(safe, match[1], (lowest, param) => Primitive_bigint.min(lowest, param[1]))
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function selectFrom(allEntities, lastPrunedAtMillis, writtenEntityNames, isRollback, nowMillis, intervalMillis, safeCheckpoints) {
|
|
14
45
|
let byOldestPrune = (param, param$1) => param[0] - param$1[0];
|
|
15
46
|
let toEntities = candidates => candidates.map(param => param[1]);
|
|
16
47
|
let concurrentCandidates = [];
|
|
@@ -44,7 +75,7 @@ function selectFrom(allEntities, lastPrunedAtMillis, writtenEntityNames, isRollb
|
|
|
44
75
|
}
|
|
45
76
|
}
|
|
46
77
|
return {
|
|
47
|
-
|
|
78
|
+
safeCheckpoints: safeCheckpoints,
|
|
48
79
|
concurrent: toEntities(sortedConcurrent.slice(0, 5)),
|
|
49
80
|
forced: toEntities(forcedCandidates.toSorted(byOldestPrune).slice(0, 5))
|
|
50
81
|
};
|
|
@@ -52,47 +83,40 @@ function selectFrom(allEntities, lastPrunedAtMillis, writtenEntityNames, isRollb
|
|
|
52
83
|
|
|
53
84
|
function select(state, writtenEntityNames, isRollback) {
|
|
54
85
|
let config = IndexerState.config(state);
|
|
55
|
-
if (
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
let safeCheckpointId = IndexerState.getSafeCheckpointId(state);
|
|
59
|
-
if (safeCheckpointId !== undefined) {
|
|
60
|
-
return selectFrom(IndexerState.persistence(state).allEntities, IndexerState.lastPrunedAtMillis(state), writtenEntityNames, isRollback, Date.now(), Env.ThrottleWrites.pruneStaleDataIntervalMillis, safeCheckpointId);
|
|
86
|
+
if (Config.shouldPruneHistory(config, IndexerState.isInReorgThreshold(state))) {
|
|
87
|
+
return Stdlib_Option.map(selectSafeCheckpoints(state), safeCheckpoints => selectFrom(IndexerState.persistence(state).allEntities, IndexerState.lastPrunedAtMillis(state), writtenEntityNames, isRollback, Date.now(), Env.ThrottleWrites.pruneStaleDataIntervalMillis, safeCheckpoints));
|
|
61
88
|
}
|
|
62
89
|
}
|
|
63
90
|
|
|
64
|
-
async function
|
|
91
|
+
async function pruneEntity(state, entityConfig, safeCheckpoints) {
|
|
65
92
|
let persistence = IndexerState.persistence(state);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Logging.childErrorWithExn(Logging.createChild({
|
|
78
|
-
entityName: entityConfig.name,
|
|
79
|
-
safeCheckpointId: safeCheckpointId
|
|
80
|
-
}), Utils.prettifyExn(exn), `Failed to prune stale entity history`);
|
|
81
|
-
}
|
|
82
|
-
if (exit === 1) {
|
|
83
|
-
IndexerState.recordHistoryPrune(state, entityConfig.name, Performance.secondsSince(timeRef));
|
|
84
|
-
}
|
|
93
|
+
let timeRef = Performance.now();
|
|
94
|
+
IndexerState.lastPrunedAtMillis(state)[entityConfig.name] = Date.now();
|
|
95
|
+
let val;
|
|
96
|
+
try {
|
|
97
|
+
val = await persistence.storage.pruneStaleEntityHistory(entityConfig.name, entityConfig.index, Table.getPgChainIdColumn(entityConfig.table), safeCheckpoints);
|
|
98
|
+
} catch (raw_exn) {
|
|
99
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
100
|
+
return Logging.childErrorWithExn(Logging.createChild({
|
|
101
|
+
entityName: entityConfig.name,
|
|
102
|
+
safeCheckpoints: safeCheckpoints
|
|
103
|
+
}), Utils.prettifyExn(exn), `Failed to prune stale entity history`);
|
|
85
104
|
}
|
|
105
|
+
return IndexerState.recordHistoryPrune(state, entityConfig.name, Performance.secondsSince(timeRef));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function pruneEntities(state, entities, safeCheckpoints) {
|
|
109
|
+
return Utils.$$Array.awaitEach(entities, entityConfig => pruneEntity(state, entityConfig, safeCheckpoints));
|
|
86
110
|
}
|
|
87
111
|
|
|
88
|
-
async function pruneCheckpoints(state,
|
|
112
|
+
async function pruneCheckpoints(state, safeCheckpoints) {
|
|
89
113
|
try {
|
|
90
|
-
await IndexerState.persistence(state).storage.pruneStaleCheckpoints(
|
|
114
|
+
await IndexerState.persistence(state).storage.pruneStaleCheckpoints(safeCheckpoints);
|
|
91
115
|
return;
|
|
92
116
|
} catch (raw_exn) {
|
|
93
117
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
94
118
|
return Logging.childErrorWithExn(Logging.createChild({
|
|
95
|
-
|
|
119
|
+
safeCheckpoints: safeCheckpoints
|
|
96
120
|
}), Utils.prettifyExn(exn), `Failed to prune stale checkpoints`);
|
|
97
121
|
}
|
|
98
122
|
}
|
|
@@ -102,10 +126,10 @@ async function runConcurrent(state, targets) {
|
|
|
102
126
|
return;
|
|
103
127
|
}
|
|
104
128
|
let concurrent = targets.concurrent;
|
|
105
|
-
let
|
|
129
|
+
let safeCheckpoints = targets.safeCheckpoints;
|
|
106
130
|
if (Utils.$$Array.notEmpty(concurrent)) {
|
|
107
|
-
await pruneCheckpoints(state,
|
|
108
|
-
return await pruneEntities(state, concurrent,
|
|
131
|
+
await pruneCheckpoints(state, safeCheckpoints);
|
|
132
|
+
return await pruneEntities(state, concurrent, safeCheckpoints);
|
|
109
133
|
}
|
|
110
134
|
}
|
|
111
135
|
|
|
@@ -114,12 +138,12 @@ async function runForced(state, targets) {
|
|
|
114
138
|
return;
|
|
115
139
|
}
|
|
116
140
|
let forced = targets.forced;
|
|
117
|
-
let
|
|
141
|
+
let safeCheckpoints = targets.safeCheckpoints;
|
|
118
142
|
if (Utils.$$Array.notEmpty(forced)) {
|
|
119
143
|
if (Utils.$$Array.isEmpty(targets.concurrent)) {
|
|
120
|
-
await pruneCheckpoints(state,
|
|
144
|
+
await pruneCheckpoints(state, safeCheckpoints);
|
|
121
145
|
}
|
|
122
|
-
return await pruneEntities(state, forced,
|
|
146
|
+
return await pruneEntities(state, forced, safeCheckpoints);
|
|
123
147
|
}
|
|
124
148
|
}
|
|
125
149
|
|
|
@@ -130,8 +154,10 @@ let forcedIntervalMultiplier = 5;
|
|
|
130
154
|
export {
|
|
131
155
|
maxEntitiesPerWrite,
|
|
132
156
|
forcedIntervalMultiplier,
|
|
157
|
+
selectSafeCheckpoints,
|
|
133
158
|
selectFrom,
|
|
134
159
|
select,
|
|
160
|
+
pruneEntity,
|
|
135
161
|
pruneEntities,
|
|
136
162
|
pruneCheckpoints,
|
|
137
163
|
runConcurrent,
|