envio 3.11.0 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +3 -0
- package/package.json +6 -6
- package/src/Batch.res +111 -78
- package/src/Batch.res.mjs +53 -46
- package/src/BatchProcessing.res +1 -11
- package/src/BatchProcessing.res.mjs +1 -4
- package/src/ChainFetching.res +7 -3
- package/src/ChainFetching.res.mjs +1 -1
- package/src/ChainState.res +82 -20
- package/src/ChainState.res.mjs +51 -13
- package/src/ChainState.resi +6 -1
- package/src/Config.res +58 -37
- package/src/Config.res.mjs +63 -53
- package/src/CrossChainState.res +50 -53
- package/src/CrossChainState.res.mjs +25 -23
- package/src/CrossChainState.resi +6 -13
- package/src/EffectState.res +13 -4
- package/src/EffectState.resi +7 -1
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +6 -6
- package/src/FetchState.res +65 -46
- package/src/FetchState.res.mjs +68 -36
- package/src/FinalizeBackfill.res +16 -12
- package/src/FinalizeBackfill.res.mjs +3 -3
- package/src/HistoryPolicy.res +46 -0
- package/src/HistoryPolicy.res.mjs +50 -0
- package/src/InMemoryStore.res +30 -32
- package/src/InMemoryStore.res.mjs +33 -28
- package/src/InMemoryTable.res +155 -67
- package/src/InMemoryTable.res.mjs +151 -60
- package/src/IndexerState.res +66 -45
- package/src/IndexerState.res.mjs +57 -29
- package/src/IndexerState.resi +8 -13
- package/src/Internal.res +4 -2
- package/src/LoadLayer.res +68 -38
- package/src/LoadLayer.res.mjs +50 -64
- package/src/LoadLayer.resi +1 -1
- package/src/Main.res +25 -24
- package/src/Main.res.mjs +24 -25
- package/src/Metrics.res +49 -1
- package/src/Metrics.res.mjs +26 -3
- package/src/Persistence.res +55 -16
- package/src/Persistence.res.mjs +18 -3
- package/src/PgStorage.res +286 -180
- package/src/PgStorage.res.mjs +209 -122
- package/src/PruneStaleHistory.res +9 -15
- package/src/PruneStaleHistory.res.mjs +11 -20
- package/src/Rollback.res +9 -14
- package/src/Rollback.res.mjs +5 -3
- package/src/Sink.res +7 -5
- package/src/Sink.res.mjs +4 -4
- package/src/Staging.res +298 -0
- package/src/Staging.res.mjs +257 -0
- package/src/TestIndexer.res +14 -30
- package/src/TestIndexer.res.mjs +11 -9
- package/src/UserContext.res +20 -36
- package/src/UserContext.res.mjs +6 -9
- package/src/Utils.res +10 -4
- package/src/Utils.res.mjs +16 -16
- package/src/Writing.res +34 -20
- package/src/Writing.res.mjs +15 -20
- package/src/bindings/ClickHouse.res +41 -34
- package/src/bindings/ClickHouse.res.mjs +36 -30
- package/src/bindings/ClickHouseSink.res +45 -186
- package/src/bindings/ClickHouseSink.res.mjs +21 -174
- package/src/db/CheckpointSequence.res +109 -0
- package/src/db/CheckpointSequence.res.mjs +99 -0
- package/src/db/EntityFilter.res +487 -275
- package/src/db/EntityFilter.res.mjs +557 -309
- package/src/db/EntityHistory.res +6 -5
- package/src/db/EntityHistory.res.mjs +5 -5
- package/src/db/Frontier.res +86 -0
- package/src/db/Frontier.res.mjs +126 -0
- package/src/db/InternalTable.res +145 -27
- package/src/db/InternalTable.res.mjs +115 -29
- package/src/db/RollbackFloors.res +34 -41
- package/src/db/RollbackFloors.res.mjs +27 -71
- package/src/db/Table.res +21 -6
- package/src/db/Table.res.mjs +13 -4
- package/src/sources/BlockStore.res +8 -0
- package/src/sources/EvmHyperSyncSource.res +5 -1
- package/src/sources/EvmHyperSyncSource.res.mjs +4 -3
- package/src/sources/FuelHyperSyncSource.res +1 -0
- package/src/sources/FuelHyperSyncSource.res.mjs +1 -1
- package/src/sources/HyperSync.res +6 -0
- package/src/sources/HyperSync.res.mjs +5 -2
- package/src/sources/HyperSync.resi +2 -0
- package/src/sources/HyperSyncClient.res +6 -0
- package/src/sources/HyperSyncSSE.res +1 -1
- package/src/sources/HyperSyncSSE.res.mjs +4 -10
- package/src/sources/RequestStat.res +8 -1
- package/src/sources/RpcSource.res +1 -0
- package/src/sources/RpcSource.res.mjs +1 -1
- package/src/sources/SimulateSource.res +1 -0
- package/src/sources/SimulateSource.res.mjs +1 -1
- package/src/sources/Source.res +11 -3
- package/src/sources/SourceManager.res +34 -13
- package/src/sources/SourceManager.res.mjs +26 -9
- package/src/sources/SourceManager.resi +2 -0
- package/src/sources/SvmHyperSyncClient.res +5 -0
- package/src/sources/SvmHyperSyncSource.res +3 -0
- package/src/sources/SvmHyperSyncSource.res.mjs +4 -2
- package/src/tui/Tui.res +56 -143
- package/src/tui/Tui.res.mjs +70 -166
- package/src/tui/components/TuiData.res +61 -16
- package/src/tui/components/TuiData.res.mjs +53 -15
- package/svm.schema.json +0 -7
- package/src/db/CheckpointBounds.res +0 -53
- package/src/db/CheckpointBounds.res.mjs +0 -44
package/src/db/EntityHistory.res
CHANGED
|
@@ -105,12 +105,12 @@ let makePruneStaleEntityHistoryQuery = (
|
|
|
105
105
|
~entityIndex,
|
|
106
106
|
~pgSchema,
|
|
107
107
|
~chainIdColumn,
|
|
108
|
-
~safeCheckpoints:
|
|
108
|
+
~safeCheckpoints: CheckpointSequence.checkpointBoundsByChain,
|
|
109
109
|
) => {
|
|
110
110
|
let historyTableRef = `"${pgSchema}"."${historyTableName(~entityName, ~entityIndex)}"`
|
|
111
111
|
let keyColumns = makeKeyColumns(~chainIdColumn)
|
|
112
112
|
let anchorKeys = keyColumns->Array.map(column => `t.${column}`)->Array.joinUnsafe(", ")
|
|
113
|
-
let bounds = safeCheckpoints->
|
|
113
|
+
let bounds = safeCheckpoints->CheckpointSequence.sql(~chainIdColumn, ~tableRef="t")
|
|
114
114
|
|
|
115
115
|
// Whether a key still has a row above the safe checkpoint is an aggregate
|
|
116
116
|
// over the same groups as the anchor, so it's computed in the one pass
|
|
@@ -151,7 +151,7 @@ let pruneStaleEntityHistory = (
|
|
|
151
151
|
~chainIdColumn,
|
|
152
152
|
~safeCheckpoints,
|
|
153
153
|
),
|
|
154
|
-
safeCheckpoints->
|
|
154
|
+
safeCheckpoints->CheckpointSequence.params,
|
|
155
155
|
)
|
|
156
156
|
|
|
157
157
|
// If an entity doesn't have a history before the update
|
|
@@ -224,11 +224,12 @@ let rollback = (
|
|
|
224
224
|
~floors: RollbackFloors.t,
|
|
225
225
|
) => {
|
|
226
226
|
let historyTableRef = `"${pgSchema}"."${historyTableName(~entityName, ~entityIndex)}"`
|
|
227
|
-
let bounds =
|
|
227
|
+
let bounds =
|
|
228
|
+
floors.checkpointBounds->CheckpointSequence.sql(~chainIdColumn, ~tableRef=historyTableRef)
|
|
228
229
|
sql
|
|
229
230
|
->Postgres.preparedUnsafe(
|
|
230
231
|
`DELETE FROM ${historyTableRef}${bounds.using} WHERE "${checkpointIdFieldName}" > ${bounds.checkpointId}${bounds.usingMatch};`,
|
|
231
|
-
floors.
|
|
232
|
+
floors.checkpointBounds->CheckpointSequence.params,
|
|
232
233
|
)
|
|
233
234
|
->Utils.Promise.ignoreValue
|
|
234
235
|
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import * as Table from "./Table.res.mjs";
|
|
4
4
|
import * as ChainId from "../ChainId.res.mjs";
|
|
5
5
|
import * as Stdlib_BigInt from "@rescript/runtime/lib/es6/Stdlib_BigInt.js";
|
|
6
|
-
import * as CheckpointBounds from "./CheckpointBounds.res.mjs";
|
|
7
6
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
8
7
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
8
|
+
import * as CheckpointSequence from "./CheckpointSequence.res.mjs";
|
|
9
9
|
|
|
10
10
|
let variants = [
|
|
11
11
|
"SET",
|
|
@@ -90,7 +90,7 @@ function makePruneStaleEntityHistoryQuery(entityName, entityIndex, pgSchema, cha
|
|
|
90
90
|
let historyTableRef = `"` + pgSchema + `"."` + historyTableName(entityName, entityIndex) + `"`;
|
|
91
91
|
let keyColumns = makeKeyColumns(chainIdColumn);
|
|
92
92
|
let anchorKeys = keyColumns.map(column => `t.` + column).join(", ");
|
|
93
|
-
let bounds =
|
|
93
|
+
let bounds = CheckpointSequence.sql(safeCheckpoints, chainIdColumn, "t");
|
|
94
94
|
return `WITH anchors AS (
|
|
95
95
|
SELECT ` + anchorKeys + `,
|
|
96
96
|
MAX(t.` + checkpointIdFieldName + `) FILTER (WHERE t.` + checkpointIdFieldName + ` <= ` + bounds.checkpointId + `) AS keep_checkpoint_id,
|
|
@@ -107,7 +107,7 @@ WHERE ` + makeKeyMatch(chainIdColumn, "d", "a") + `
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function pruneStaleEntityHistory(sql, entityName, entityIndex, pgSchema, chainIdColumn, safeCheckpoints) {
|
|
110
|
-
return sql.unsafe(makePruneStaleEntityHistoryQuery(entityName, entityIndex, pgSchema, chainIdColumn, safeCheckpoints),
|
|
110
|
+
return sql.unsafe(makePruneStaleEntityHistoryQuery(entityName, entityIndex, pgSchema, chainIdColumn, safeCheckpoints), CheckpointSequence.params(safeCheckpoints), {prepare: true});
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
function makeBackfillHistoryQuery(pgSchema, entityName, entityIndex, idPgType, chainIdColumn, chainId) {
|
|
@@ -137,8 +137,8 @@ function backfillHistory(sql, pgSchema, table, entityIndex, chainId, ids) {
|
|
|
137
137
|
|
|
138
138
|
function rollback(sql, pgSchema, entityName, entityIndex, chainIdColumn, floors) {
|
|
139
139
|
let historyTableRef = `"` + pgSchema + `"."` + historyTableName(entityName, entityIndex) + `"`;
|
|
140
|
-
let bounds =
|
|
141
|
-
return sql.unsafe(`DELETE FROM ` + historyTableRef + bounds.using + ` WHERE "` + checkpointIdFieldName + `" > ` + bounds.checkpointId + bounds.usingMatch + `;`,
|
|
140
|
+
let bounds = CheckpointSequence.sql(floors.checkpointBounds, chainIdColumn, historyTableRef);
|
|
141
|
+
return sql.unsafe(`DELETE FROM ` + historyTableRef + bounds.using + ` WHERE "` + checkpointIdFieldName + `" > ` + bounds.checkpointId + bounds.usingMatch + `;`, CheckpointSequence.params(floors.checkpointBounds), {prepare: true});
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
let checkpointIdFieldType = "UInt64";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Where each chain stands in its checkpoint sequence.
|
|
2
|
+
type t = dict<Internal.checkpointId>
|
|
3
|
+
|
|
4
|
+
let empty = (): t => Dict.make()
|
|
5
|
+
|
|
6
|
+
let fromEntries = (entries: array<(ChainId.t, Internal.checkpointId)>): t => {
|
|
7
|
+
let frontier = Dict.make()
|
|
8
|
+
entries->Array.forEach(((chainId, checkpointId)) =>
|
|
9
|
+
frontier->ChainId.Dict.set(chainId, checkpointId)
|
|
10
|
+
)
|
|
11
|
+
frontier
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let copy = (frontier: t): t => frontier->Dict.copy
|
|
15
|
+
|
|
16
|
+
// A chain the frontier doesn't name has committed nothing yet, which is what
|
|
17
|
+
// the initial checkpoint id means.
|
|
18
|
+
let get = (frontier: t, chainId): Internal.checkpointId =>
|
|
19
|
+
switch frontier->ChainId.Dict.dangerouslyGetNonOption(chainId) {
|
|
20
|
+
| Some(checkpointId) => checkpointId
|
|
21
|
+
| None => Internal.initialCheckpointId
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Whether the frontier names the chain at all, as opposed to `get`'s reading of
|
|
25
|
+
// an absent chain as the initial id. A rollback frontier names only the chains
|
|
26
|
+
// it moved, and "no id" is not the same answer as "id zero" there.
|
|
27
|
+
let find = (frontier: t, chainId): option<Internal.checkpointId> =>
|
|
28
|
+
frontier->ChainId.Dict.dangerouslyGetNonOption(chainId)
|
|
29
|
+
|
|
30
|
+
let set = (frontier: t, chainId, checkpointId) => frontier->ChainId.Dict.set(chainId, checkpointId)
|
|
31
|
+
|
|
32
|
+
let entries = (frontier: t): array<(ChainId.t, Internal.checkpointId)> =>
|
|
33
|
+
frontier
|
|
34
|
+
->Dict.toArray
|
|
35
|
+
->Array.map(((key, checkpointId)) => (key->ChainId.normalizeOrThrow, checkpointId))
|
|
36
|
+
|
|
37
|
+
let chainIds = (frontier: t): array<ChainId.t> =>
|
|
38
|
+
frontier->Dict.keysToArray->Array.map(ChainId.normalizeOrThrow)
|
|
39
|
+
|
|
40
|
+
// The two parallel arrays an `unnest($1, $2) AS ...(chain_id, checkpoint_id)`
|
|
41
|
+
// relation reads positionally.
|
|
42
|
+
type unnestParams = (array<ChainId.t>, array<string>)
|
|
43
|
+
|
|
44
|
+
let unnestParams = (frontier: t): unnestParams => {
|
|
45
|
+
let chainIds = []
|
|
46
|
+
let checkpointIds = []
|
|
47
|
+
frontier->Utils.Dict.forEachWithKey((checkpointId, key) => {
|
|
48
|
+
chainIds->Array.push(key->ChainId.normalizeOrThrow)
|
|
49
|
+
checkpointIds->Array.push(checkpointId->BigInt.toString)
|
|
50
|
+
})
|
|
51
|
+
(chainIds, checkpointIds)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
%%private(
|
|
55
|
+
let fold = (frontier: t, pick) =>
|
|
56
|
+
frontier
|
|
57
|
+
->Dict.valuesToArray
|
|
58
|
+
->Array.reduce(None, (acc, checkpointId) =>
|
|
59
|
+
switch acc {
|
|
60
|
+
| None => Some(checkpointId)
|
|
61
|
+
| Some(picked) => Some(pick(picked, checkpointId))
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// A frontier naming no chain has committed nothing anywhere.
|
|
67
|
+
let max = (frontier: t) =>
|
|
68
|
+
frontier->fold(Pervasives.max)->Option.getOr(Internal.initialCheckpointId)
|
|
69
|
+
let min = (frontier: t) =>
|
|
70
|
+
frontier->fold(Pervasives.min)->Option.getOr(Internal.initialCheckpointId)
|
|
71
|
+
|
|
72
|
+
let mergeMin = (a: t, b: t): t => Utils.Dict.mergeWith(a, b, Pervasives.min)
|
|
73
|
+
let mergeMax = (a: t, b: t): t => Utils.Dict.mergeWith(a, b, Pervasives.max)
|
|
74
|
+
|
|
75
|
+
let equals = (a: t, b: t) => {
|
|
76
|
+
let same = (a: t, b: t) =>
|
|
77
|
+
a
|
|
78
|
+
->Dict.toArray
|
|
79
|
+
->Array.every(((key, checkpointId)) =>
|
|
80
|
+
switch b->Utils.Dict.dangerouslyGetNonOption(key) {
|
|
81
|
+
| Some(other) => other == checkpointId
|
|
82
|
+
| None => checkpointId == Internal.initialCheckpointId
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
same(a, b) && same(b, a)
|
|
86
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Utils from "../Utils.res.mjs";
|
|
4
|
+
import * as ChainId from "../ChainId.res.mjs";
|
|
5
|
+
import * as Internal from "../Internal.res.mjs";
|
|
6
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
7
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
|
+
import * as Primitive_bigint from "@rescript/runtime/lib/es6/Primitive_bigint.js";
|
|
9
|
+
|
|
10
|
+
function empty() {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fromEntries(entries) {
|
|
15
|
+
let frontier = {};
|
|
16
|
+
entries.forEach(param => {
|
|
17
|
+
frontier[param[0]] = param[1];
|
|
18
|
+
});
|
|
19
|
+
return frontier;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function copy(frontier) {
|
|
23
|
+
return Object.assign({}, frontier);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function get(frontier, chainId) {
|
|
27
|
+
let checkpointId = frontier[chainId];
|
|
28
|
+
if (checkpointId !== undefined) {
|
|
29
|
+
return checkpointId;
|
|
30
|
+
} else {
|
|
31
|
+
return Internal.initialCheckpointId;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function find(frontier, chainId) {
|
|
36
|
+
return frontier[chainId];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function set(frontier, chainId, checkpointId) {
|
|
40
|
+
frontier[chainId] = checkpointId;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function entries(frontier) {
|
|
44
|
+
return Object.entries(frontier).map(param => [
|
|
45
|
+
ChainId.normalizeOrThrow(param[0]),
|
|
46
|
+
param[1]
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function chainIds(frontier) {
|
|
51
|
+
return Object.keys(frontier).map(ChainId.normalizeOrThrow);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function unnestParams(frontier) {
|
|
55
|
+
let chainIds = [];
|
|
56
|
+
let checkpointIds = [];
|
|
57
|
+
Utils.Dict.forEachWithKey(frontier, (checkpointId, key) => {
|
|
58
|
+
chainIds.push(ChainId.normalizeOrThrow(key));
|
|
59
|
+
checkpointIds.push(checkpointId.toString());
|
|
60
|
+
});
|
|
61
|
+
return [
|
|
62
|
+
chainIds,
|
|
63
|
+
checkpointIds
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function fold(frontier, pick) {
|
|
68
|
+
return Stdlib_Array.reduce(Object.values(frontier), undefined, (acc, checkpointId) => {
|
|
69
|
+
if (acc !== undefined) {
|
|
70
|
+
return pick(acc, checkpointId);
|
|
71
|
+
} else {
|
|
72
|
+
return checkpointId;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function max(frontier) {
|
|
78
|
+
return Stdlib_Option.getOr(fold(frontier, Primitive_bigint.max), Internal.initialCheckpointId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function min(frontier) {
|
|
82
|
+
return Stdlib_Option.getOr(fold(frontier, Primitive_bigint.min), Internal.initialCheckpointId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function mergeMin(a, b) {
|
|
86
|
+
return Utils.Dict.mergeWith(a, b, Primitive_bigint.min);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function mergeMax(a, b) {
|
|
90
|
+
return Utils.Dict.mergeWith(a, b, Primitive_bigint.max);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function equals(a, b) {
|
|
94
|
+
let same = (a, b) => Object.entries(a).every(param => {
|
|
95
|
+
let checkpointId = param[1];
|
|
96
|
+
let other = b[param[0]];
|
|
97
|
+
if (other !== undefined) {
|
|
98
|
+
return other === checkpointId;
|
|
99
|
+
} else {
|
|
100
|
+
return checkpointId === Internal.initialCheckpointId;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
if (same(a, b)) {
|
|
104
|
+
return same(b, a);
|
|
105
|
+
} else {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
empty,
|
|
112
|
+
fromEntries,
|
|
113
|
+
copy,
|
|
114
|
+
get,
|
|
115
|
+
find,
|
|
116
|
+
set,
|
|
117
|
+
entries,
|
|
118
|
+
chainIds,
|
|
119
|
+
unnestParams,
|
|
120
|
+
max,
|
|
121
|
+
min,
|
|
122
|
+
mergeMin,
|
|
123
|
+
mergeMax,
|
|
124
|
+
equals,
|
|
125
|
+
}
|
|
126
|
+
/* Utils Not a pure module */
|
package/src/db/InternalTable.res
CHANGED
|
@@ -170,6 +170,7 @@ FROM "${pgSchema}"."${table.tableName}";`
|
|
|
170
170
|
module Chains = {
|
|
171
171
|
type progressFields = [
|
|
172
172
|
| #progress_block
|
|
173
|
+
| #progress_block_time
|
|
173
174
|
| #events_processed
|
|
174
175
|
| #source_block
|
|
175
176
|
]
|
|
@@ -186,6 +187,7 @@ module Chains = {
|
|
|
186
187
|
| #buffer_block
|
|
187
188
|
| #ready_at
|
|
188
189
|
| #_is_hyper_sync
|
|
190
|
+
| #checkpoint_id
|
|
189
191
|
]
|
|
190
192
|
|
|
191
193
|
let fields: array<field> = [
|
|
@@ -198,19 +200,16 @@ module Chains = {
|
|
|
198
200
|
#first_event_block,
|
|
199
201
|
#buffer_block,
|
|
200
202
|
#progress_block,
|
|
203
|
+
#progress_block_time,
|
|
201
204
|
#ready_at,
|
|
202
205
|
#events_processed,
|
|
203
206
|
#_is_hyper_sync,
|
|
207
|
+
#checkpoint_id,
|
|
204
208
|
]
|
|
205
209
|
|
|
206
210
|
type metaFields = {
|
|
207
211
|
@as("first_event_block")
|
|
208
|
-
firstEventBlockNumber: Null.t<
|
|
209
|
-
// Push id first (for WHERE clause)
|
|
210
|
-
|
|
211
|
-
// Then push all updateable field values (for SET clause)
|
|
212
|
-
int,
|
|
213
|
-
>,
|
|
212
|
+
firstEventBlockNumber: Null.t<int>,
|
|
214
213
|
@as("buffer_block") latestFetchedBlockNumber: int,
|
|
215
214
|
@as("ready_at")
|
|
216
215
|
timestampCaughtUpToHeadOrEndblock: Null.t<Date.t>,
|
|
@@ -225,7 +224,9 @@ module Chains = {
|
|
|
225
224
|
@as("max_reorg_depth") maxReorgDepth: int,
|
|
226
225
|
@as("source_block") blockHeight: int,
|
|
227
226
|
@as("progress_block") progressBlockNumber: int,
|
|
227
|
+
@as("progress_block_time") progressBlockTime: Null.t<Date.t>,
|
|
228
228
|
@as("events_processed") numEventsProcessed: float,
|
|
229
|
+
@as("checkpoint_id") checkpointId: Internal.checkpointId,
|
|
229
230
|
...metaFields,
|
|
230
231
|
}
|
|
231
232
|
|
|
@@ -265,6 +266,22 @@ module Chains = {
|
|
|
265
266
|
mkField((#_is_hyper_sync: field :> string), Boolean, ~fieldSchema=S.bool),
|
|
266
267
|
// Fully processed block number
|
|
267
268
|
mkField((#progress_block: field :> string), Int32, ~fieldSchema=S.int),
|
|
269
|
+
// When the block in progress_block was produced, so that `now() -
|
|
270
|
+
// progress_block_time` is how far behind chain time the indexer is. Null
|
|
271
|
+
// while the source hasn't reported that block's header.
|
|
272
|
+
mkField(
|
|
273
|
+
(#progress_block_time: field :> string),
|
|
274
|
+
Date,
|
|
275
|
+
~fieldSchema=S.null(Utils.Schema.dbDate),
|
|
276
|
+
~isNullable,
|
|
277
|
+
),
|
|
278
|
+
// The last checkpoint id the chain has committed. Kept here rather than
|
|
279
|
+
// read off the checkpoints table: checkpoint rows are only written while
|
|
280
|
+
// a rollback could reach them, but an append-only sink holds a row for
|
|
281
|
+
// every id ever handed out and resolves current state through them, so a
|
|
282
|
+
// resume has to continue the sequence even where no checkpoint row backs
|
|
283
|
+
// it.
|
|
284
|
+
mkField((#checkpoint_id: field :> string), UInt64, ~fieldSchema=S.bigint),
|
|
268
285
|
],
|
|
269
286
|
)
|
|
270
287
|
|
|
@@ -280,8 +297,10 @@ module Chains = {
|
|
|
280
297
|
latestFetchedBlockNumber: -1,
|
|
281
298
|
timestampCaughtUpToHeadOrEndblock: Null.null,
|
|
282
299
|
progressBlockNumber: -1,
|
|
300
|
+
progressBlockTime: Null.null,
|
|
283
301
|
isHyperSync: false,
|
|
284
302
|
numEventsProcessed: 0.,
|
|
303
|
+
checkpointId: Internal.initialCheckpointId,
|
|
285
304
|
}
|
|
286
305
|
}
|
|
287
306
|
|
|
@@ -358,8 +377,11 @@ WHERE "${(#id: field :> string)}" = $2
|
|
|
358
377
|
timestampCaughtUpToHeadOrEndblock: Null.t<Date.t>,
|
|
359
378
|
numEventsProcessed: float,
|
|
360
379
|
progressBlockNumber: int,
|
|
380
|
+
progressBlockTime: Null.t<Date.t>,
|
|
361
381
|
addressRows: AddressRows.seedRows,
|
|
362
382
|
sourceBlockNumber: int,
|
|
383
|
+
// BIGINT, which the driver hands back as a string.
|
|
384
|
+
checkpointId: string,
|
|
363
385
|
}
|
|
364
386
|
|
|
365
387
|
let makeGetInitialStateQuery = (~pgSchema) => {
|
|
@@ -371,7 +393,9 @@ WHERE "${(#id: field :> string)}" = $2
|
|
|
371
393
|
"${(#ready_at: field :> string)}" as "timestampCaughtUpToHeadOrEndblock",
|
|
372
394
|
"${(#events_processed: field :> string)}"::float8 as "numEventsProcessed",
|
|
373
395
|
"${(#progress_block: field :> string)}" as "progressBlockNumber",
|
|
374
|
-
"${(#
|
|
396
|
+
"${(#progress_block_time: field :> string)}" as "progressBlockTime",
|
|
397
|
+
"${(#source_block: field :> string)}" as "sourceBlockNumber",
|
|
398
|
+
"${(#checkpoint_id: field :> string)}"::TEXT as "checkpointId"
|
|
375
399
|
FROM "${pgSchema}"."${table.tableName}";`
|
|
376
400
|
}
|
|
377
401
|
|
|
@@ -403,7 +427,20 @@ FROM "${pgSchema}"."${table.tableName}";`
|
|
|
403
427
|
})
|
|
404
428
|
}
|
|
405
429
|
|
|
406
|
-
|
|
430
|
+
// Block timestamps are unix seconds everywhere inside the indexer - that's
|
|
431
|
+
// what a block header carries - and a timestamp in storage, where `now() -
|
|
432
|
+
// progress_block_time` is the query the column exists for.
|
|
433
|
+
let blockTimeToDb = (blockTime: option<int>) =>
|
|
434
|
+
blockTime->Option.map(seconds => Date.fromTime(seconds->Int.toFloat *. 1000.))->Null.fromOption
|
|
435
|
+
let blockTimeFromDb = (date: Null.t<Date.t>) =>
|
|
436
|
+
date->Null.toOption->Option.map(date => (date->Date.getTime /. 1000.)->Float.toInt)
|
|
437
|
+
|
|
438
|
+
let progressFields: array<progressFields> = [
|
|
439
|
+
#progress_block,
|
|
440
|
+
#progress_block_time,
|
|
441
|
+
#events_processed,
|
|
442
|
+
#source_block,
|
|
443
|
+
]
|
|
407
444
|
|
|
408
445
|
let makeProgressFieldsUpdateQuery = (~pgSchema) => {
|
|
409
446
|
let setClauses = Array.mapWithIndex(progressFields, (field, index) => {
|
|
@@ -444,6 +481,7 @@ WHERE "id" = $1;`
|
|
|
444
481
|
type progressedChain = {
|
|
445
482
|
chainId: ChainId.t,
|
|
446
483
|
progressBlockNumber: int,
|
|
484
|
+
progressBlockTime: option<int>,
|
|
447
485
|
sourceBlockNumber: int,
|
|
448
486
|
totalEventsProcessed: float,
|
|
449
487
|
}
|
|
@@ -463,6 +501,8 @@ WHERE "id" = $1;`
|
|
|
463
501
|
->Array.push(
|
|
464
502
|
switch field {
|
|
465
503
|
| #progress_block => data.progressBlockNumber->(Utils.magic: int => unknown)
|
|
504
|
+
| #progress_block_time =>
|
|
505
|
+
data.progressBlockTime->blockTimeToDb->(Utils.magic: Null.t<Date.t> => unknown)
|
|
466
506
|
| #events_processed => data.totalEventsProcessed->(Utils.magic: float => unknown)
|
|
467
507
|
| #source_block => data.sourceBlockNumber->(Utils.magic: int => unknown)
|
|
468
508
|
},
|
|
@@ -475,6 +515,31 @@ WHERE "id" = $1;`
|
|
|
475
515
|
|
|
476
516
|
Promise.all(promises)->Utils.Promise.ignoreValue
|
|
477
517
|
}
|
|
518
|
+
|
|
519
|
+
let makeSetCheckpointFrontierQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
|
|
520
|
+
let chainIdArrayType = chainIdArrayType(~pgSchema, ~chainIdMode)
|
|
521
|
+
`UPDATE "${pgSchema}"."${table.tableName}"
|
|
522
|
+
SET "${(#checkpoint_id: field :> string)}" = envio_frontier.checkpoint_id
|
|
523
|
+
FROM unnest($1::${chainIdArrayType},$2::${(BigInt: Postgres.columnType :> string)}[]) AS envio_frontier(chain_id, checkpoint_id)
|
|
524
|
+
WHERE "${table.tableName}"."${(#id: field :> string)}" = envio_frontier.chain_id;`
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// The chains the write moved, in one statement and in the batch's own
|
|
528
|
+
// transaction — so a chain's stored id can never outlive the rows it covers,
|
|
529
|
+
// nor lag behind them.
|
|
530
|
+
let setCheckpointFrontier = (
|
|
531
|
+
sql,
|
|
532
|
+
~pgSchema,
|
|
533
|
+
~frontier: Frontier.t,
|
|
534
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
535
|
+
) => {
|
|
536
|
+
sql
|
|
537
|
+
->Postgres.preparedUnsafe(
|
|
538
|
+
makeSetCheckpointFrontierQuery(~pgSchema, ~chainIdMode),
|
|
539
|
+
frontier->Frontier.unnestParams->(Utils.magic: Frontier.unnestParams => unknown),
|
|
540
|
+
)
|
|
541
|
+
->Utils.Promise.ignoreValue
|
|
542
|
+
}
|
|
478
543
|
}
|
|
479
544
|
|
|
480
545
|
module EnvioInfo = {
|
|
@@ -556,31 +621,58 @@ module Checkpoints = {
|
|
|
556
621
|
|
|
557
622
|
let initialCheckpointId = 0n
|
|
558
623
|
|
|
624
|
+
// The checkpoint a rollback's diff rows are stamped with. It never reaches
|
|
625
|
+
// Postgres — there the diff is written straight to the entity table — but an
|
|
626
|
+
// append-only sink resolves current state through the checkpoints, so without
|
|
627
|
+
// one of these the diff sits above the frontier while the rows it supersedes
|
|
628
|
+
// sit below it, and the orphaned values are what a reader sees.
|
|
629
|
+
type diffCheckpoint = {
|
|
630
|
+
chainId: ChainId.t,
|
|
631
|
+
checkpointId: Internal.checkpointId,
|
|
632
|
+
// Where the rollback left the chain. At or below its stored progress, so a
|
|
633
|
+
// resume counts the row as covered rather than as something to trim back to.
|
|
634
|
+
blockNumber: int,
|
|
635
|
+
}
|
|
636
|
+
|
|
559
637
|
// One definition per column, carrying what each storage needs: the field
|
|
560
638
|
// itself, the type ClickHouse gives it where that differs from Postgres, and
|
|
561
|
-
// where a batch keeps the column's values.
|
|
639
|
+
// where a batch — or a rollback diff — keeps the column's values.
|
|
562
640
|
type column = {
|
|
563
641
|
field: fieldOrDerived,
|
|
564
642
|
clickHouseFieldType: fieldType,
|
|
565
643
|
valuesOf: Batch.t => array<unknown>,
|
|
644
|
+
diffValuesOf: array<diffCheckpoint> => array<unknown>,
|
|
566
645
|
}
|
|
567
646
|
|
|
647
|
+
// The chain leads the key: ids are only unique within a chain, and every
|
|
648
|
+
// query that narrows to one chain reads a contiguous run of it.
|
|
568
649
|
let columns: array<column> = [
|
|
569
650
|
{
|
|
570
|
-
field: mkField(
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
651
|
+
field: mkField(
|
|
652
|
+
(#chain_id: field :> string),
|
|
653
|
+
ChainId,
|
|
654
|
+
~fieldSchema=ChainId.schema,
|
|
655
|
+
~isPrimaryKey,
|
|
656
|
+
),
|
|
576
657
|
clickHouseFieldType: ChainId,
|
|
577
658
|
valuesOf: batch =>
|
|
578
659
|
batch.checkpointChainIds->(Utils.magic: array<ChainId.t> => array<unknown>),
|
|
660
|
+
diffValuesOf: diffs =>
|
|
661
|
+
diffs->Array.map(diff => diff.chainId->(Utils.magic: ChainId.t => unknown)),
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
field: mkField((#id: field :> string), UInt64, ~fieldSchema=S.bigint, ~isPrimaryKey),
|
|
665
|
+
clickHouseFieldType: UInt64,
|
|
666
|
+
valuesOf: batch => batch.checkpointIds->(Utils.magic: array<bigint> => array<unknown>),
|
|
667
|
+
diffValuesOf: diffs =>
|
|
668
|
+
diffs->Array.map(diff => diff.checkpointId->(Utils.magic: bigint => unknown)),
|
|
579
669
|
},
|
|
580
670
|
{
|
|
581
671
|
field: mkField((#block_number: field :> string), Int32, ~fieldSchema=S.int),
|
|
582
672
|
clickHouseFieldType: Int32,
|
|
583
673
|
valuesOf: batch => batch.checkpointBlockNumbers->(Utils.magic: array<int> => array<unknown>),
|
|
674
|
+
diffValuesOf: diffs =>
|
|
675
|
+
diffs->Array.map(diff => diff.blockNumber->(Utils.magic: int => unknown)),
|
|
584
676
|
},
|
|
585
677
|
{
|
|
586
678
|
field: mkField(
|
|
@@ -592,6 +684,8 @@ module Checkpoints = {
|
|
|
592
684
|
clickHouseFieldType: String,
|
|
593
685
|
valuesOf: batch =>
|
|
594
686
|
batch.checkpointBlockHashes->(Utils.magic: array<Null.t<string>> => array<unknown>),
|
|
687
|
+
diffValuesOf: diffs =>
|
|
688
|
+
diffs->Array.map(_ => Null.Null->(Utils.magic: Null.t<string> => unknown)),
|
|
595
689
|
},
|
|
596
690
|
{
|
|
597
691
|
field: mkField((#events_processed: field :> string), Int32, ~fieldSchema=S.int),
|
|
@@ -600,10 +694,34 @@ module Checkpoints = {
|
|
|
600
694
|
clickHouseFieldType: UInt64,
|
|
601
695
|
valuesOf: batch =>
|
|
602
696
|
batch.checkpointEventsProcessed->(Utils.magic: array<int> => array<unknown>),
|
|
697
|
+
diffValuesOf: diffs => diffs->Array.map(_ => 0->(Utils.magic: int => unknown)),
|
|
603
698
|
},
|
|
604
699
|
]
|
|
605
700
|
|
|
606
|
-
let
|
|
701
|
+
let tableName = "envio_checkpoints"
|
|
702
|
+
|
|
703
|
+
let table = mkTable(tableName, ~fields=columns->Array.map(({field}) => field))
|
|
704
|
+
|
|
705
|
+
// Where each chain counts its own ids the chain has to be part of the key,
|
|
706
|
+
// and every bound a rollback or a prune applies names it. Under one shared
|
|
707
|
+
// sequence the id is unique by itself and those bounds are id ranges with no
|
|
708
|
+
// chain in them — which a key led by the chain can't serve.
|
|
709
|
+
let globalTable = mkTable(
|
|
710
|
+
tableName,
|
|
711
|
+
~fields=columns->Array.map(({field: column}) =>
|
|
712
|
+
switch column {
|
|
713
|
+
| Table.Field(f) if f.fieldName === (#chain_id: field :> string) =>
|
|
714
|
+
Table.Field({...f, isPrimaryKey: false})
|
|
715
|
+
| column => column
|
|
716
|
+
}
|
|
717
|
+
),
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
let tableFor = (sequence: CheckpointSequence.t) =>
|
|
721
|
+
switch sequence {
|
|
722
|
+
| SharedAcrossChains => globalTable
|
|
723
|
+
| PerChain => table
|
|
724
|
+
}
|
|
607
725
|
|
|
608
726
|
let makeGetReorgCheckpointsQuery = (~pgSchema): string => {
|
|
609
727
|
// The safe_block checkpoint itself is included, so it can be used for safe
|
|
@@ -640,10 +758,6 @@ WHERE cp."${(#block_hash: field :> string)}" IS NOT NULL
|
|
|
640
758
|
ORDER BY cp."${(#id: field :> string)}";`
|
|
641
759
|
}
|
|
642
760
|
|
|
643
|
-
let makeCommitedCheckpointIdQuery = (~pgSchema) => {
|
|
644
|
-
`SELECT COALESCE(MAX(${(#id: field :> string)}), ${initialCheckpointId->BigInt.toString}) AS id FROM "${pgSchema}"."${table.tableName}";`
|
|
645
|
-
}
|
|
646
|
-
|
|
647
761
|
let makeInsertCheckpointQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
|
|
648
762
|
let chainIdArrayType = chainIdArrayType(~pgSchema, ~chainIdMode)
|
|
649
763
|
`INSERT INTO "${pgSchema}"."${table.tableName}" ("${(#id: field :> string)}", "${(#chain_id: field :> string)}", "${(#block_number: field :> string)}", "${(#block_hash: field :> string)}", "${(#events_processed: field :> string)}")
|
|
@@ -687,18 +801,21 @@ SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${chai
|
|
|
687
801
|
|
|
688
802
|
let rollback = (sql, ~pgSchema, ~floors: RollbackFloors.t) => {
|
|
689
803
|
let tableRef = `"${pgSchema}"."${table.tableName}"`
|
|
690
|
-
let bounds = floors.
|
|
804
|
+
let bounds = floors.checkpointBounds->CheckpointSequence.sql(~chainIdColumn, ~tableRef)
|
|
691
805
|
sql
|
|
692
806
|
->Postgres.preparedUnsafe(
|
|
693
807
|
`DELETE FROM ${tableRef}${bounds.using} WHERE "${(#id: field :> string)}" > ${bounds.checkpointId}${bounds.usingMatch};`,
|
|
694
|
-
floors.
|
|
808
|
+
floors.checkpointBounds->CheckpointSequence.params,
|
|
695
809
|
)
|
|
696
810
|
->Utils.Promise.ignoreValue
|
|
697
811
|
}
|
|
698
812
|
|
|
699
|
-
let makePruneStaleCheckpointsQuery = (
|
|
813
|
+
let makePruneStaleCheckpointsQuery = (
|
|
814
|
+
~pgSchema,
|
|
815
|
+
~safeCheckpoints: CheckpointSequence.checkpointBoundsByChain,
|
|
816
|
+
) => {
|
|
700
817
|
let tableRef = `"${pgSchema}"."${table.tableName}"`
|
|
701
|
-
let bounds = safeCheckpoints->
|
|
818
|
+
let bounds = safeCheckpoints->CheckpointSequence.sql(~chainIdColumn, ~tableRef)
|
|
702
819
|
`DELETE FROM ${tableRef}${bounds.using} WHERE "${(#id: field :> string)}" < ${bounds.checkpointId}${bounds.usingMatch};`
|
|
703
820
|
}
|
|
704
821
|
|
|
@@ -706,7 +823,7 @@ SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${chai
|
|
|
706
823
|
sql
|
|
707
824
|
->Postgres.preparedUnsafe(
|
|
708
825
|
makePruneStaleCheckpointsQuery(~pgSchema, ~safeCheckpoints),
|
|
709
|
-
safeCheckpoints->
|
|
826
|
+
safeCheckpoints->CheckpointSequence.params,
|
|
710
827
|
)
|
|
711
828
|
->Utils.Promise.ignoreValue
|
|
712
829
|
|
|
@@ -738,7 +855,7 @@ LIMIT 1;`
|
|
|
738
855
|
}
|
|
739
856
|
|
|
740
857
|
let makeGetRollbackProgressDiffQuery = (~pgSchema, ~floors: RollbackFloors.t) => {
|
|
741
|
-
let bounds = floors.
|
|
858
|
+
let bounds = floors.checkpointBounds->CheckpointSequence.sql(~chainIdColumn, ~tableRef="t")
|
|
742
859
|
`SELECT
|
|
743
860
|
t."${(#chain_id: field :> string)}"::float8 as "${(#chain_id: field :> string)}",
|
|
744
861
|
SUM(t."${(#events_processed: field :> string)}") as events_processed_diff,
|
|
@@ -752,7 +869,7 @@ GROUP BY t."${(#chain_id: field :> string)}";`
|
|
|
752
869
|
sql
|
|
753
870
|
->Postgres.preparedUnsafe(
|
|
754
871
|
makeGetRollbackProgressDiffQuery(~pgSchema, ~floors),
|
|
755
|
-
floors.
|
|
872
|
+
floors.checkpointBounds->CheckpointSequence.params,
|
|
756
873
|
)
|
|
757
874
|
->(
|
|
758
875
|
Utils.magic: promise<unknown> => promise<
|
|
@@ -816,6 +933,7 @@ SELECT
|
|
|
816
933
|
"${(#start_block: Chains.field :> string)}" AS "startBlock",
|
|
817
934
|
"${(#end_block: Chains.field :> string)}" AS "endBlock",
|
|
818
935
|
"${(#progress_block: Chains.field :> string)}" AS "progressBlock",
|
|
936
|
+
"${(#progress_block_time: Chains.field :> string)}" AS "progressBlockTime",
|
|
819
937
|
"${(#buffer_block: Chains.field :> string)}" AS "bufferBlock",
|
|
820
938
|
"${(#first_event_block: Chains.field :> string)}" AS "firstEventBlock",
|
|
821
939
|
"${(#events_processed: Chains.field :> string)}"::float4 AS "eventsProcessed",
|