envio 3.11.0 → 3.12.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/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/ChainState.res +72 -19
- package/src/ChainState.res.mjs +50 -12
- package/src/ChainState.resi +5 -0
- package/src/Config.res +58 -37
- package/src/Config.res.mjs +63 -53
- package/src/CrossChainState.res +49 -52
- 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 +14 -0
- package/src/FetchState.res.mjs +14 -0
- 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/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 +11 -4
- package/src/LoadLayer.res.mjs +8 -5
- 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 +194 -118
- package/src/PgStorage.res.mjs +132 -72
- 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 +5 -5
- package/src/TestIndexer.res.mjs +7 -6
- package/src/UserContext.res +7 -4
- package/src/UserContext.res.mjs +5 -2
- package/src/Utils.res +9 -0
- package/src/Utils.res.mjs +9 -0
- 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/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/sources/BlockStore.res +3 -0
- package/src/sources/EvmHyperSyncSource.res +3 -1
- package/src/sources/EvmHyperSyncSource.res.mjs +2 -1
- package/src/sources/HyperSync.res +2 -0
- package/src/sources/HyperSync.res.mjs +1 -0
- package/src/sources/HyperSync.resi +1 -0
- package/src/sources/HyperSyncClient.res +3 -0
- package/src/sources/RequestStat.res +8 -1
- package/src/sources/Source.res +4 -3
- package/src/sources/SourceManager.res +30 -10
- package/src/sources/SourceManager.res.mjs +24 -7
- package/src/sources/SourceManager.resi +2 -0
- 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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// How checkpoint ids are handed out. A schema with a cross-chain entity has
|
|
2
|
+
// rows any chain's reorg can reach, so its checkpoints have to be comparable
|
|
3
|
+
// across chains and come from one shared counter. Without one, no chain can
|
|
4
|
+
// touch another's rows, and each gets a counter of its own — which is what lets
|
|
5
|
+
// a rollback, a prune or a resume name one chain without saying anything about
|
|
6
|
+
// the others.
|
|
7
|
+
type t =
|
|
8
|
+
| SharedAcrossChains
|
|
9
|
+
| PerChain
|
|
10
|
+
|
|
11
|
+
let fromEntities = (entities: array<Internal.entityConfig>) =>
|
|
12
|
+
entities->Array.some(entityConfig => entityConfig.crossChain) ? SharedAcrossChains : PerChain
|
|
13
|
+
|
|
14
|
+
// Where a chain stands: under one shared counter that is wherever the counter
|
|
15
|
+
// got to, whichever chain moved it last.
|
|
16
|
+
let position = (sequence: t, frontier: Frontier.t, ~chainId) =>
|
|
17
|
+
switch sequence {
|
|
18
|
+
| SharedAcrossChains => frontier->Frontier.max
|
|
19
|
+
| PerChain => frontier->Frontier.get(chainId)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// The committed (or processed) id a scope's rows compare against. A chain scope
|
|
23
|
+
// reads its own. A cross-chain scope spans every chain: under one shared
|
|
24
|
+
// sequence the highest id is exactly how far the scope has got, while per-chain
|
|
25
|
+
// ids aren't comparable across chains, so only the lowest is an id every chain
|
|
26
|
+
// has passed.
|
|
27
|
+
let forScope = (sequence: t, frontier: Frontier.t, ~scope: Internal.chainScope) =>
|
|
28
|
+
switch scope {
|
|
29
|
+
| Chain(chainId) => frontier->Frontier.get(chainId)
|
|
30
|
+
| CrossChain =>
|
|
31
|
+
switch sequence {
|
|
32
|
+
| SharedAcrossChains => frontier->Frontier.max
|
|
33
|
+
| PerChain => frontier->Frontier.min
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The diff id a scope's rows carry after a rollback, taken from the frontier the
|
|
38
|
+
// rollback stamped them with. A per-chain rollback names only the chains it
|
|
39
|
+
// moved: a sibling it left alone has no diff row, and so no id to compare
|
|
40
|
+
// against.
|
|
41
|
+
let findForScope = (sequence: t, frontier: Frontier.t, ~scope: Internal.chainScope): option<
|
|
42
|
+
Internal.checkpointId,
|
|
43
|
+
> =>
|
|
44
|
+
switch scope {
|
|
45
|
+
| Chain(chainId) => frontier->Frontier.find(chainId)
|
|
46
|
+
| CrossChain => Some(sequence->forScope(frontier, ~scope))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Hands out the ids of one batch, starting from where the frontier left each
|
|
50
|
+
// chain. Under `SharedAcrossChains` the ids come from a single run of the counter, so they
|
|
51
|
+
// interleave across chains in allocation order; under `PerChain` each chain
|
|
52
|
+
// continues its own.
|
|
53
|
+
type cursor = {sequence: t, frontier: Frontier.t}
|
|
54
|
+
|
|
55
|
+
let cursor = (sequence: t, ~frontier: Frontier.t) => {sequence, frontier: frontier->Frontier.copy}
|
|
56
|
+
|
|
57
|
+
let next = (cursor, ~chainId): Internal.checkpointId => {
|
|
58
|
+
let checkpointId = cursor.sequence->position(cursor.frontier, ~chainId)->BigInt.add(1n)
|
|
59
|
+
cursor.frontier->Frontier.set(chainId, checkpointId)
|
|
60
|
+
checkpointId
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The checkpoint id each chain's rows are compared against in a query,
|
|
64
|
+
// together with the sequence that decides how the comparison is rendered.
|
|
65
|
+
type checkpointBoundsByChain = {sequence: t, byChain: Frontier.t}
|
|
66
|
+
|
|
67
|
+
// The pieces a query splices in to read its bound as `checkpointId`.
|
|
68
|
+
type sql = {
|
|
69
|
+
join: string,
|
|
70
|
+
using: string,
|
|
71
|
+
usingMatch: string,
|
|
72
|
+
checkpointId: string,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The chain ids are cast to the widest integer type rather than the column's
|
|
76
|
+
// own: nothing indexes the join, so the values only have to compare.
|
|
77
|
+
let relation = `unnest($1::${(Postgres.BigInt :> string)}[],$2::${(Postgres.BigInt :> string)}[]) AS envio_bounds(chain_id, checkpoint_id)`
|
|
78
|
+
|
|
79
|
+
let sql = (
|
|
80
|
+
bounds: checkpointBoundsByChain,
|
|
81
|
+
~chainIdColumn: option<string>,
|
|
82
|
+
~tableRef: string,
|
|
83
|
+
): sql =>
|
|
84
|
+
switch (bounds.sequence, chainIdColumn) {
|
|
85
|
+
| (SharedAcrossChains, _) => {join: "", using: "", usingMatch: "", checkpointId: "$1"}
|
|
86
|
+
| (PerChain, Some(column)) =>
|
|
87
|
+
let chainMatch = `envio_bounds.chain_id = ${tableRef}."${column}"`
|
|
88
|
+
{
|
|
89
|
+
join: ` JOIN ${relation} ON ${chainMatch}`,
|
|
90
|
+
using: ` USING ${relation}`,
|
|
91
|
+
usingMatch: ` AND ${chainMatch}`,
|
|
92
|
+
checkpointId: "envio_bounds.checkpoint_id",
|
|
93
|
+
}
|
|
94
|
+
| (PerChain, None) =>
|
|
95
|
+
JsError.throwWithMessage(
|
|
96
|
+
"Internal error: per-chain checkpoint bounds can't bound a table with no chain-id column. Only a schema whose entities are all per-chain has them.",
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Bound in the order `sql` reads them: under one shared sequence the lowest of
|
|
101
|
+
// the ids as `$1` (every chain is held to it), otherwise the chains and their
|
|
102
|
+
// own ids as the two parallel arrays the relation unnests.
|
|
103
|
+
let params = (bounds: checkpointBoundsByChain): unknown =>
|
|
104
|
+
switch bounds.sequence {
|
|
105
|
+
| SharedAcrossChains =>
|
|
106
|
+
[bounds.byChain->Frontier.min->BigInt.toString]->(Utils.magic: array<string> => unknown)
|
|
107
|
+
| PerChain =>
|
|
108
|
+
bounds.byChain->Frontier.unnestParams->(Utils.magic: Frontier.unnestParams => unknown)
|
|
109
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Frontier from "./Frontier.res.mjs";
|
|
4
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
5
|
+
|
|
6
|
+
function fromEntities(entities) {
|
|
7
|
+
if (entities.some(entityConfig => entityConfig.crossChain)) {
|
|
8
|
+
return "SharedAcrossChains";
|
|
9
|
+
} else {
|
|
10
|
+
return "PerChain";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function position(sequence, frontier, chainId) {
|
|
15
|
+
if (sequence === "SharedAcrossChains") {
|
|
16
|
+
return Frontier.max(frontier);
|
|
17
|
+
} else {
|
|
18
|
+
return Frontier.get(frontier, chainId);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function forScope(sequence, frontier, scope) {
|
|
23
|
+
if (scope === "crossChain") {
|
|
24
|
+
if (sequence === "SharedAcrossChains") {
|
|
25
|
+
return Frontier.max(frontier);
|
|
26
|
+
} else {
|
|
27
|
+
return Frontier.min(frontier);
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
return Frontier.get(frontier, scope);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function findForScope(sequence, frontier, scope) {
|
|
35
|
+
if (scope === "crossChain") {
|
|
36
|
+
return forScope(sequence, frontier, scope);
|
|
37
|
+
} else {
|
|
38
|
+
return Frontier.find(frontier, scope);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function cursor(sequence, frontier) {
|
|
43
|
+
return {
|
|
44
|
+
sequence: sequence,
|
|
45
|
+
frontier: Frontier.copy(frontier)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function next(cursor, chainId) {
|
|
50
|
+
let checkpointId = position(cursor.sequence, cursor.frontier, chainId) + 1n;
|
|
51
|
+
Frontier.set(cursor.frontier, chainId, checkpointId);
|
|
52
|
+
return checkpointId;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let relation = `unnest($1::` + "BIGINT" + `[],$2::` + "BIGINT" + `[]) AS envio_bounds(chain_id, checkpoint_id)`;
|
|
56
|
+
|
|
57
|
+
function sql(bounds, chainIdColumn, tableRef) {
|
|
58
|
+
let match = bounds.sequence;
|
|
59
|
+
if (match === "SharedAcrossChains") {
|
|
60
|
+
return {
|
|
61
|
+
join: "",
|
|
62
|
+
using: "",
|
|
63
|
+
usingMatch: "",
|
|
64
|
+
checkpointId: "$1"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (chainIdColumn === undefined) {
|
|
68
|
+
return Stdlib_JsError.throwWithMessage("Internal error: per-chain checkpoint bounds can't bound a table with no chain-id column. Only a schema whose entities are all per-chain has them.");
|
|
69
|
+
}
|
|
70
|
+
let chainMatch = `envio_bounds.chain_id = ` + tableRef + `."` + chainIdColumn + `"`;
|
|
71
|
+
return {
|
|
72
|
+
join: ` JOIN ` + relation + ` ON ` + chainMatch,
|
|
73
|
+
using: ` USING ` + relation,
|
|
74
|
+
usingMatch: ` AND ` + chainMatch,
|
|
75
|
+
checkpointId: "envio_bounds.checkpoint_id"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function params(bounds) {
|
|
80
|
+
let match = bounds.sequence;
|
|
81
|
+
if (match === "SharedAcrossChains") {
|
|
82
|
+
return [Frontier.min(bounds.byChain).toString()];
|
|
83
|
+
} else {
|
|
84
|
+
return Frontier.unnestParams(bounds.byChain);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
fromEntities,
|
|
90
|
+
position,
|
|
91
|
+
forScope,
|
|
92
|
+
findForScope,
|
|
93
|
+
cursor,
|
|
94
|
+
next,
|
|
95
|
+
relation,
|
|
96
|
+
sql,
|
|
97
|
+
params,
|
|
98
|
+
}
|
|
99
|
+
/* Frontier Not a pure module */
|
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 */
|