envio 3.9.0 → 3.10.0-subgraph

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.
Files changed (143) hide show
  1. package/evm.schema.json +26 -0
  2. package/fuel.schema.json +26 -0
  3. package/index.d.ts +41 -8
  4. package/package.json +6 -7
  5. package/src/Batch.res +9 -0
  6. package/src/ChainState.res +35 -39
  7. package/src/ChainState.res.mjs +14 -11
  8. package/src/ChainState.resi +15 -7
  9. package/src/Config.res +61 -44
  10. package/src/Config.res.mjs +56 -19
  11. package/src/Core.res +19 -0
  12. package/src/Core.res.mjs +11 -0
  13. package/src/CrossChainState.res +15 -23
  14. package/src/CrossChainState.res.mjs +10 -18
  15. package/src/CrossChainState.resi +4 -1
  16. package/src/Ecosystem.res +7 -2
  17. package/src/Envio.res +6 -4
  18. package/src/EventConfigBuilder.res +169 -26
  19. package/src/EventConfigBuilder.res.mjs +124 -15
  20. package/src/EventProcessing.res +6 -6
  21. package/src/EventProcessing.res.mjs +8 -6
  22. package/src/HandlerLoader.res +20 -8
  23. package/src/HandlerLoader.res.mjs +11 -2
  24. package/src/HandlerRegister.res +21 -4
  25. package/src/HandlerRegister.res.mjs +24 -5
  26. package/src/InMemoryStore.res +9 -7
  27. package/src/InMemoryStore.res.mjs +3 -3
  28. package/src/IndexerState.res +43 -6
  29. package/src/IndexerState.res.mjs +36 -7
  30. package/src/IndexerState.resi +4 -4
  31. package/src/Internal.res +16 -17
  32. package/src/Metrics.res +72 -6
  33. package/src/Metrics.res.mjs +30 -2
  34. package/src/Persistence.res +36 -30
  35. package/src/Persistence.res.mjs +1 -20
  36. package/src/PgStorage.res +104 -56
  37. package/src/PgStorage.res.mjs +57 -35
  38. package/src/PruneStaleHistory.res +85 -52
  39. package/src/PruneStaleHistory.res.mjs +63 -37
  40. package/src/Rollback.res +68 -45
  41. package/src/Rollback.res.mjs +38 -19
  42. package/src/RollbackCommit.res +7 -9
  43. package/src/RollbackCommit.res.mjs +5 -6
  44. package/src/SafeCheckpointTracking.res +11 -11
  45. package/src/SafeCheckpointTracking.res.mjs +7 -6
  46. package/src/SimulateItems.res +33 -18
  47. package/src/SimulateItems.res.mjs +26 -22
  48. package/src/Sink.res +38 -26
  49. package/src/Sink.res.mjs +21 -16
  50. package/src/TestIndexer.res +5 -5
  51. package/src/TestIndexer.res.mjs +1 -1
  52. package/src/UserContext.res +358 -51
  53. package/src/UserContext.res.mjs +271 -35
  54. package/src/Utils.res +170 -0
  55. package/src/Utils.res.mjs +153 -0
  56. package/src/Writing.res +2 -2
  57. package/src/Writing.res.mjs +1 -1
  58. package/src/bindings/ClickHouse.res +366 -698
  59. package/src/bindings/ClickHouse.res.mjs +371 -421
  60. package/src/bindings/ClickHouseSink.res +337 -0
  61. package/src/bindings/ClickHouseSink.res.mjs +246 -0
  62. package/src/bindings/EventSource.res +8 -2
  63. package/src/bindings/NodeJs.res +6 -0
  64. package/src/bindings/Vitest.res +23 -0
  65. package/src/bindings/Vitest.res.mjs +3 -0
  66. package/src/bindings/WebSocket.res +9 -10
  67. package/src/db/CheckpointBounds.res +53 -0
  68. package/src/db/CheckpointBounds.res.mjs +44 -0
  69. package/src/db/EntityFilter.res +36 -17
  70. package/src/db/EntityFilter.res.mjs +25 -14
  71. package/src/db/EntityHistory.res +47 -22
  72. package/src/db/EntityHistory.res.mjs +19 -12
  73. package/src/db/InternalTable.res +84 -35
  74. package/src/db/InternalTable.res.mjs +56 -23
  75. package/src/db/RollbackFloors.res +55 -0
  76. package/src/db/RollbackFloors.res.mjs +92 -0
  77. package/src/db/Table.res +22 -4
  78. package/src/db/Table.res.mjs +30 -9
  79. package/src/sources/Evm.res +2 -0
  80. package/src/sources/Evm.res.mjs +2 -0
  81. package/src/sources/EvmHyperSyncSource.res +3 -3
  82. package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
  83. package/src/sources/EvmRpcWs.res +124 -0
  84. package/src/sources/EvmRpcWs.res.mjs +117 -0
  85. package/src/sources/Fuel.res +2 -0
  86. package/src/sources/Fuel.res.mjs +2 -0
  87. package/src/sources/FuelHyperSyncSource.res +1 -1
  88. package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
  89. package/src/sources/HeightFeed.res +568 -0
  90. package/src/sources/HeightFeed.res.mjs +452 -0
  91. package/src/sources/HeightStream.res +257 -0
  92. package/src/sources/HeightStream.res.mjs +194 -0
  93. package/src/sources/HyperSync.res +5 -0
  94. package/src/sources/HyperSync.res.mjs +3 -0
  95. package/src/sources/HyperSync.resi +4 -0
  96. package/src/sources/HyperSyncSSE.res +49 -0
  97. package/src/sources/HyperSyncSSE.res.mjs +79 -0
  98. package/src/sources/RpcSource.res +1 -1
  99. package/src/sources/RpcSource.res.mjs +2 -2
  100. package/src/sources/Source.res +54 -1
  101. package/src/sources/Source.res.mjs +25 -0
  102. package/src/sources/SourceManager.res +220 -227
  103. package/src/sources/SourceManager.res.mjs +139 -164
  104. package/src/sources/SourceManager.resi +8 -0
  105. package/src/sources/Svm.res +2 -0
  106. package/src/sources/Svm.res.mjs +2 -0
  107. package/src/sources/SvmHyperSyncClient.res +70 -127
  108. package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
  109. package/src/sources/SvmHyperSyncSource.res +18 -15
  110. package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
  111. package/src/subgraph/blocks.ts +176 -0
  112. package/src/subgraph/calls.ts +217 -0
  113. package/src/subgraph/conformance.ts +102 -0
  114. package/src/subgraph/division.ts +133 -0
  115. package/src/subgraph/errors.ts +90 -0
  116. package/src/subgraph/graph-ts-types/VERSION +2 -0
  117. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  118. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  119. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  120. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  121. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  122. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  123. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  124. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  125. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  126. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  127. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  128. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  129. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  130. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  131. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  132. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  133. package/src/subgraph/graph-ts.ts +1948 -0
  134. package/src/subgraph/hosts.ts +148 -0
  135. package/src/subgraph/runtime.ts +863 -0
  136. package/src/subgraph/scope.ts +66 -0
  137. package/svm.schema.json +6 -81
  138. package/src/MemoryStorage.res +0 -729
  139. package/src/MemoryStorage.res.mjs +0 -586
  140. package/src/sources/HyperSyncHeightStream.res +0 -129
  141. package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
  142. package/src/sources/RpcWebSocketHeightStream.res +0 -175
  143. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
@@ -12,11 +12,43 @@ let maxEntitiesPerWrite = 5
12
12
  let forcedIntervalMultiplier = 5.
13
13
 
14
14
  type targets = {
15
- safeCheckpointId: Internal.checkpointId,
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
- ~safeCheckpointId,
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
- safeCheckpointId,
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
- switch state->IndexerState.getSafeCheckpointId {
76
- | None => None
77
- | Some(safeCheckpointId) =>
78
- Some(
79
- selectFrom(
80
- ~allEntities=(state->IndexerState.persistence).allEntities,
81
- ~lastPrunedAtMillis=state->IndexerState.lastPrunedAtMillis,
82
- ~writtenEntityNames,
83
- ~isRollback,
84
- ~nowMillis=Date.now(),
85
- ~intervalMillis=Env.ThrottleWrites.pruneStaleDataIntervalMillis->Int.toFloat,
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 pruneEntities = async (state: IndexerState.t, ~entities, ~safeCheckpointId) => {
125
+ let pruneEntity = async (
126
+ state: IndexerState.t,
127
+ ~entityConfig: Internal.entityConfig,
128
+ ~safeCheckpoints,
129
+ ) => {
96
130
  let persistence = state->IndexerState.persistence
97
- for idx in 0 to entities->Array.length - 1 {
98
- let entityConfig: Internal.entityConfig = entities->Array.getUnsafe(idx)
99
- let timeRef = Performance.now()
100
- // Recorded for failures too, so a failing prune retries on the same
101
- // interval instead of on every write.
102
- state->IndexerState.lastPrunedAtMillis->Dict.set(entityConfig.name, Date.now())
103
- switch await persistence.storage.pruneStaleEntityHistory(
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
- ~entityIndex=entityConfig.index,
106
- ~chainIdColumn=entityConfig.table->Table.getChainIdField->Option.map(Table.getPgDbFieldName),
107
- ~safeCheckpointId,
108
- ) {
109
- | () =>
110
- state->IndexerState.recordHistoryPrune(
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 pruneCheckpoints = async (state: IndexerState.t, ~safeCheckpointId) => {
127
- switch await (state->IndexerState.persistence).storage.pruneStaleCheckpoints(~safeCheckpointId) {
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={"safeCheckpointId": safeCheckpointId})->Logging.childErrorWithExn(
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({safeCheckpointId, concurrent}) if concurrent->Utils.Array.notEmpty =>
140
- await pruneCheckpoints(state, ~safeCheckpointId)
141
- await pruneEntities(state, ~entities=concurrent, ~safeCheckpointId)
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({safeCheckpointId, concurrent, forced}) if forced->Utils.Array.notEmpty =>
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, ~safeCheckpointId)
185
+ await pruneCheckpoints(state, ~safeCheckpoints)
153
186
  }
154
- await pruneEntities(state, ~entities=forced, ~safeCheckpointId)
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 selectFrom(allEntities, lastPrunedAtMillis, writtenEntityNames, isRollback, nowMillis, intervalMillis, safeCheckpointId) {
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
- safeCheckpointId: safeCheckpointId,
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 (!Config.shouldPruneHistory(config, IndexerState.isInReorgThreshold(state))) {
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 pruneEntities(state, entities, safeCheckpointId) {
91
+ async function pruneEntity(state, entityConfig, safeCheckpoints) {
65
92
  let persistence = IndexerState.persistence(state);
66
- for (let idx = 0, idx_finish = entities.length; idx < idx_finish; ++idx) {
67
- let entityConfig = entities[idx];
68
- let timeRef = Performance.now();
69
- IndexerState.lastPrunedAtMillis(state)[entityConfig.name] = Date.now();
70
- let exit = 0;
71
- let val;
72
- try {
73
- val = await persistence.storage.pruneStaleEntityHistory(entityConfig.name, entityConfig.index, Stdlib_Option.map(Table.getChainIdField(entityConfig.table), Table.getPgDbFieldName), safeCheckpointId);
74
- exit = 1;
75
- } catch (raw_exn) {
76
- let exn = Primitive_exceptions.internalToException(raw_exn);
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, safeCheckpointId) {
112
+ async function pruneCheckpoints(state, safeCheckpoints) {
89
113
  try {
90
- await IndexerState.persistence(state).storage.pruneStaleCheckpoints(safeCheckpointId);
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
- safeCheckpointId: safeCheckpointId
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 safeCheckpointId = targets.safeCheckpointId;
129
+ let safeCheckpoints = targets.safeCheckpoints;
106
130
  if (Utils.$$Array.notEmpty(concurrent)) {
107
- await pruneCheckpoints(state, safeCheckpointId);
108
- return await pruneEntities(state, concurrent, safeCheckpointId);
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 safeCheckpointId = targets.safeCheckpointId;
141
+ let safeCheckpoints = targets.safeCheckpoints;
118
142
  if (Utils.$$Array.notEmpty(forced)) {
119
143
  if (Utils.$$Array.isEmpty(targets.concurrent)) {
120
- await pruneCheckpoints(state, safeCheckpointId);
144
+ await pruneCheckpoints(state, safeCheckpoints);
121
145
  }
122
- return await pruneEntities(state, forced, safeCheckpointId);
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,
package/src/Rollback.res CHANGED
@@ -127,86 +127,105 @@ and executeRollback = async (
127
127
  }
128
128
  }
129
129
 
130
- let eventsProcessedDiffByChain = Dict.make()
131
- let newProgressBlockNumberPerChain = Dict.make()
130
+ // The diff computed here replaces a pending one rather than merging with it,
131
+ // so its deletes have to cover everything that one would have deleted. The
132
+ // flush above leaves a diff pending only when no batch has come along to
133
+ // carry it.
134
+ let floors = {
135
+ let next = if state->IndexerState.config->Config.isIsolatedMultichain {
136
+ RollbackFloors.isolated(
137
+ ~chainId=reorgChain,
138
+ ~floorCheckpointId=rollbackTargetCheckpointId,
139
+ ~forkBlockNumber=rollbackTargetBlockNumber,
140
+ )
141
+ } else {
142
+ RollbackFloors.global(
143
+ ~floorCheckpointId=rollbackTargetCheckpointId,
144
+ ~reorgChainId=reorgChain,
145
+ ~forkBlockNumber=rollbackTargetBlockNumber,
146
+ )
147
+ }
148
+ switch state->IndexerState.pendingRollback {
149
+ | None => next
150
+ | Some({floors: pending}) => RollbackFloors.merge(pending, next)
151
+ }
152
+ }
153
+
154
+ let progressDiffByChain: dict<ChainState.progressDiff> = Dict.make()
132
155
  let rollbackedProcessedEvents = ref(0.)
133
156
 
134
157
  {
135
158
  let rollbackProgressDiff = await (
136
159
  state->IndexerState.persistence
137
- ).storage.getRollbackProgressDiff(~rollbackTargetCheckpointId)
160
+ ).storage.getRollbackProgressDiff(~floors)
138
161
  for idx in 0 to rollbackProgressDiff->Array.length - 1 {
139
162
  let diff = rollbackProgressDiff->Array.getUnsafe(idx)
140
- eventsProcessedDiffByChain->ChainId.Dict.set(
141
- diff["chain_id"],
163
+ let chainId = diff["chain_id"]
164
+ let eventsProcessed = Float.fromString(diff["events_processed_diff"])->Option.getOrThrow
165
+ rollbackedProcessedEvents := rollbackedProcessedEvents.contents +. eventsProcessed
166
+ progressDiffByChain->ChainId.Dict.set(
167
+ chainId,
142
168
  {
143
- let eventsProcessedDiff =
144
- Float.fromString(diff["events_processed_diff"])->Option.getOrThrow
145
- rollbackedProcessedEvents := rollbackedProcessedEvents.contents +. eventsProcessedDiff
146
- eventsProcessedDiff
147
- },
148
- )
149
- newProgressBlockNumberPerChain->ChainId.Dict.set(
150
- diff["chain_id"],
151
- if rollbackTargetCheckpointId === 0n && diff["chain_id"] === reorgChain {
152
- Pervasives.min(diff["new_progress_block_number"], rollbackTargetBlockNumber)
153
- } else {
154
- diff["new_progress_block_number"]
169
+ blockNumber: diff["new_progress_block_number"],
170
+ eventsProcessed,
155
171
  },
156
172
  )
157
173
  }
158
174
  }
159
175
 
160
- let rolledBackChains = []
176
+ // Where the rollback leaves each chain it moved, written with the diff: the
177
+ // batch that carries it may belong to a chain the rollback never touched.
178
+ let rolledBackChains: array<InternalTable.Chains.progressedChain> = []
161
179
  let rolledBackAddresses = []
162
180
  state
163
181
  ->IndexerState.chainStates
164
182
  ->Utils.Dict.forEach(cs => {
165
183
  let chainId = (cs->ChainState.chainConfig).id
166
184
  let fromBlock = cs->ChainState.committedProgressBlockNumber
167
- let killedAddresses =
168
- cs->ChainState.rollback(
169
- ~newProgressBlockNumber=newProgressBlockNumberPerChain->ChainId.Dict.dangerouslyGetNonOption(
170
- chainId,
171
- ),
172
- ~eventsProcessedDiff=eventsProcessedDiffByChain->ChainId.Dict.dangerouslyGetNonOption(
173
- chainId,
174
- ),
175
- ~rollbackTargetBlockNumber,
176
- ~isReorgChain=chainId === reorgChain,
177
- )
185
+ let progressDiff = progressDiffByChain->ChainId.Dict.dangerouslyGetNonOption(chainId)
186
+ let killedAddresses = cs->ChainState.rollback(
187
+ ~rolledBackTo=switch (progressDiff, floors->RollbackFloors.forkBlockNumber(chainId)) {
188
+ | (Some(progressDiff), forkBlockNumber) =>
189
+ RecomputedProgress({
190
+ ...progressDiff,
191
+ blockNumber: forkBlockNumber->Option.mapOr(progressDiff.blockNumber, forkBlockNumber =>
192
+ Pervasives.min(progressDiff.blockNumber, forkBlockNumber)
193
+ ),
194
+ })
195
+ // Nothing of this chain's is above its floor, so there are no checkpoints
196
+ // to recompute from.
197
+ | (None, Some(forkBlockNumber)) => ForkBlock(forkBlockNumber)
198
+ | (None, None) => Untouched
199
+ },
200
+ )
178
201
  rolledBackAddresses->Array.pushMany(killedAddresses)->ignore
179
202
  let toBlock = cs->ChainState.committedProgressBlockNumber
180
203
  if fromBlock !== toBlock {
181
204
  rolledBackChains
182
205
  ->Array.push({
206
+ chainId,
207
+ progressBlockNumber: toBlock,
208
+ sourceBlockNumber: cs->ChainState.knownHeight,
209
+ totalEventsProcessed: cs->ChainState.numEventsProcessed,
210
+ })
211
+ ->ignore
212
+ logger->Logging.childInfo({
213
+ "msg": "Rollbacked",
183
214
  "chainId": chainId,
184
215
  "fromBlock": fromBlock,
185
216
  "toBlock": toBlock,
186
- "rollbackedEvents": eventsProcessedDiffByChain
187
- ->ChainId.Dict.dangerouslyGetNonOption(chainId)
188
- ->Option.getOr(0.),
217
+ "rollbackedEvents": progressDiff->Option.mapOr(0., diff => diff.eventsProcessed),
189
218
  })
190
- ->ignore
191
219
  }
192
220
  })
193
221
 
194
222
  let diff = await state->InMemoryStore.prepareRollbackDiff(
195
- ~rollbackTargetCheckpointId,
223
+ ~floors,
196
224
  ~rollbackDiffCheckpointId=state->IndexerState.committedCheckpointId->BigInt.add(1n),
197
- ~progressBlockNumberByChainId=newProgressBlockNumberPerChain,
225
+ ~progressedChains=rolledBackChains,
198
226
  ~rolledBackAddresses,
199
227
  )
200
228
 
201
- rolledBackChains->Array.forEach(rolledBack => {
202
- logger->Logging.childInfo({
203
- "msg": "Rollbacked",
204
- "chainId": rolledBack["chainId"],
205
- "fromBlock": rolledBack["fromBlock"],
206
- "toBlock": rolledBack["toBlock"],
207
- "rollbackedEvents": rolledBack["rollbackedEvents"],
208
- })
209
- })
210
229
  logger->Logging.childTrace({
211
230
  "msg": "Rollback entity changes",
212
231
  "deleted": diff["deletedEntities"],
@@ -217,7 +236,11 @@ and executeRollback = async (
217
236
  ~rollbackedProcessedEvents=rollbackedProcessedEvents.contents,
218
237
  )
219
238
 
220
- state->IndexerState.completeRollback(~eventsProcessedDiffByChain)
239
+ state->IndexerState.completeRollback(
240
+ ~eventsProcessedDiffByChain=progressDiffByChain->Utils.Dict.mapValues(diff =>
241
+ diff.eventsProcessed
242
+ ),
243
+ )
221
244
  scheduleFetch()
222
245
  scheduleProcessing()
223
246
  }
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "./Utils.res.mjs";
4
+ import * as Config from "./Config.res.mjs";
4
5
  import * as Logging from "./Logging.res.mjs";
5
6
  import * as Writing from "./Writing.res.mjs";
6
7
  import * as ChainState from "./ChainState.res.mjs";
@@ -12,6 +13,7 @@ import * as InMemoryStore from "./InMemoryStore.res.mjs";
12
13
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
13
14
  import * as SourceManager from "./sources/SourceManager.res.mjs";
14
15
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
16
+ import * as RollbackFloors from "./db/RollbackFloors.res.mjs";
15
17
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
16
18
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
17
19
 
@@ -42,50 +44,67 @@ async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, sch
42
44
  await Writing.flush(state);
43
45
  let checkpointId = await IndexerState.persistence(state).storage.getRollbackTargetCheckpoint(reorgChain, rollbackTargetBlockNumber);
44
46
  let rollbackTargetCheckpointId = checkpointId !== undefined ? checkpointId : 0n;
45
- let eventsProcessedDiffByChain = {};
46
- let newProgressBlockNumberPerChain = {};
47
+ let next = Config.isIsolatedMultichain(IndexerState.config(state)) ? RollbackFloors.isolated(reorgChain, rollbackTargetCheckpointId, rollbackTargetBlockNumber) : RollbackFloors.global(rollbackTargetCheckpointId, reorgChain, rollbackTargetBlockNumber);
48
+ let match = IndexerState.pendingRollback(state);
49
+ let floors = match !== undefined ? RollbackFloors.merge(match.floors, next) : next;
50
+ let progressDiffByChain = {};
47
51
  let rollbackedProcessedEvents = 0;
48
- let rollbackProgressDiff = await IndexerState.persistence(state).storage.getRollbackProgressDiff(rollbackTargetCheckpointId);
52
+ let rollbackProgressDiff = await IndexerState.persistence(state).storage.getRollbackProgressDiff(floors);
49
53
  for (let idx = 0, idx_finish = rollbackProgressDiff.length; idx < idx_finish; ++idx) {
50
54
  let diff = rollbackProgressDiff[idx];
51
- let eventsProcessedDiff = Stdlib_Option.getOrThrow(Stdlib_Float.fromString(diff.events_processed_diff), undefined);
52
- rollbackedProcessedEvents = rollbackedProcessedEvents + eventsProcessedDiff;
53
- eventsProcessedDiffByChain[diff.chain_id] = eventsProcessedDiff;
54
- newProgressBlockNumberPerChain[diff.chain_id] = rollbackTargetCheckpointId === 0n && diff.chain_id === reorgChain ? Primitive_int.min(diff.new_progress_block_number, rollbackTargetBlockNumber) : diff.new_progress_block_number;
55
+ let chainId = diff.chain_id;
56
+ let eventsProcessed = Stdlib_Option.getOrThrow(Stdlib_Float.fromString(diff.events_processed_diff), undefined);
57
+ rollbackedProcessedEvents = rollbackedProcessedEvents + eventsProcessed;
58
+ progressDiffByChain[chainId] = {
59
+ blockNumber: diff.new_progress_block_number,
60
+ eventsProcessed: eventsProcessed
61
+ };
55
62
  }
56
63
  let rolledBackChains = [];
57
64
  let rolledBackAddresses = [];
58
65
  Utils.Dict.forEach(IndexerState.chainStates(state), cs => {
59
66
  let chainId = ChainState.chainConfig(cs).id;
60
67
  let fromBlock = ChainState.committedProgressBlockNumber(cs);
61
- let killedAddresses = ChainState.rollback(cs, newProgressBlockNumberPerChain[chainId], eventsProcessedDiffByChain[chainId], rollbackTargetBlockNumber, chainId === reorgChain);
68
+ let progressDiff = progressDiffByChain[chainId];
69
+ let match = RollbackFloors.forkBlockNumber(floors, chainId);
70
+ let killedAddresses = ChainState.rollback(cs, progressDiff !== undefined ? ({
71
+ TAG: "RecomputedProgress",
72
+ _0: {
73
+ blockNumber: Stdlib_Option.mapOr(match, progressDiff.blockNumber, forkBlockNumber => Primitive_int.min(progressDiff.blockNumber, forkBlockNumber)),
74
+ eventsProcessed: progressDiff.eventsProcessed
75
+ }
76
+ }) : (
77
+ match !== undefined ? ({
78
+ TAG: "ForkBlock",
79
+ _0: match
80
+ }) : "Untouched"
81
+ ));
62
82
  rolledBackAddresses.push(...killedAddresses);
63
83
  let toBlock = ChainState.committedProgressBlockNumber(cs);
64
84
  if (fromBlock !== toBlock) {
65
85
  rolledBackChains.push({
86
+ chainId: chainId,
87
+ progressBlockNumber: toBlock,
88
+ sourceBlockNumber: ChainState.knownHeight(cs),
89
+ totalEventsProcessed: ChainState.numEventsProcessed(cs)
90
+ });
91
+ return Logging.childInfo(logger, {
92
+ msg: "Rollbacked",
66
93
  chainId: chainId,
67
94
  fromBlock: fromBlock,
68
95
  toBlock: toBlock,
69
- rollbackedEvents: Stdlib_Option.getOr(eventsProcessedDiffByChain[chainId], 0)
96
+ rollbackedEvents: Stdlib_Option.mapOr(progressDiff, 0, diff => diff.eventsProcessed)
70
97
  });
71
- return;
72
98
  }
73
99
  });
74
- let diff$1 = await InMemoryStore.prepareRollbackDiff(state, rollbackTargetCheckpointId, IndexerState.committedCheckpointId(state) + 1n, newProgressBlockNumberPerChain, rolledBackAddresses);
75
- rolledBackChains.forEach(rolledBack => Logging.childInfo(logger, {
76
- msg: "Rollbacked",
77
- chainId: rolledBack.chainId,
78
- fromBlock: rolledBack.fromBlock,
79
- toBlock: rolledBack.toBlock,
80
- rollbackedEvents: rolledBack.rollbackedEvents
81
- }));
100
+ let diff$1 = await InMemoryStore.prepareRollbackDiff(state, floors, IndexerState.committedCheckpointId(state) + 1n, rolledBackChains, rolledBackAddresses);
82
101
  Logging.childTrace(logger, {
83
102
  msg: "Rollback entity changes",
84
103
  deleted: diff$1.deletedEntities,
85
104
  upserted: diff$1.setEntities
86
105
  });
87
106
  IndexerState.recordRollbackSuccess(state, Performance.secondsSince(startTime), rollbackedProcessedEvents);
88
- IndexerState.completeRollback(state, eventsProcessedDiffByChain);
107
+ IndexerState.completeRollback(state, Utils.Dict.mapValues(progressDiffByChain, diff => diff.eventsProcessed));
89
108
  scheduleFetch();
90
109
  return scheduleProcessing();
91
110
  }
@@ -20,15 +20,13 @@ let register = (callback: callback) => {
20
20
  }
21
21
  }
22
22
 
23
- // Fired after a rollback diff is durably written, once per affected chain.
24
- // `progressBlockNumberByChainId` is the last valid block per chain, taken from
25
- // the in-memory store's rollback object. A throwing callback bubbles to the
26
- // write loop's onError, crashing the indexer like a failed write.
27
- let fire = async (~progressBlockNumberByChainId: dict<int>) => {
28
- let _ = await progressBlockNumberByChainId
29
- ->Dict.toArray
30
- ->Array.flatMap(((chainIdKey, rollbackToBlock)) => {
31
- let args = {chainId: chainIdKey->ChainId.normalizeOrThrow, rollbackToBlock}
23
+ // Fired after a rollback diff is durably written, once per affected chain, with
24
+ // the last valid block the rollback left that chain at. A throwing callback
25
+ // bubbles to the write loop's onError, crashing the indexer like a failed write.
26
+ let fire = async (~progressedChains: array<InternalTable.Chains.progressedChain>) => {
27
+ let _ = await progressedChains
28
+ ->Array.flatMap(({chainId, progressBlockNumber}) => {
29
+ let args = {chainId, rollbackToBlock: progressBlockNumber}
32
30
  callbacks->Array.map(callback => callback(args))
33
31
  })
34
32
  ->Promise.all