envio 3.15.0-subgraph → 3.16.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.
- package/README.md +40 -34
- package/evm.schema.json +3 -3
- package/fuel.schema.json +2 -2
- package/index.d.ts +3 -3
- package/licenses/README.md +1 -1
- package/package.json +8 -8
- package/src/Batch.res.mjs +5 -5
- package/src/BatchProcessing.res +1 -0
- package/src/BatchProcessing.res.mjs +1 -0
- package/src/ChainFetching.res +1 -6
- package/src/ChainFetching.res.mjs +1 -1
- package/src/ChainState.res +48 -7
- package/src/ChainState.res.mjs +21 -8
- package/src/Clock.res +57 -0
- package/src/Clock.res.mjs +72 -0
- package/src/Config.res +16 -4
- package/src/Config.res.mjs +5 -3
- package/src/Core.res +4 -40
- package/src/CrossChainState.res +10 -6
- package/src/CrossChainState.res.mjs +1 -1
- package/src/EffectState.res +10 -2
- package/src/EffectState.res.mjs +8 -4
- package/src/EffectState.resi +3 -1
- package/src/EntityId.res +2 -7
- package/src/EntityId.res.mjs +1 -8
- package/src/EntityTables.res +4 -1
- package/src/EntityTables.res.mjs +1 -1
- package/src/Env.res +4 -0
- package/src/Env.res.mjs +5 -0
- package/src/FetchState.res +157 -69
- package/src/FetchState.res.mjs +112 -47
- package/src/FinalizeBackfill.res +2 -4
- package/src/FinalizeBackfill.res.mjs +3 -4
- package/src/InMemoryTable.res +17 -16
- package/src/InMemoryTable.res.mjs +14 -15
- package/src/IndexerState.res +28 -4
- package/src/IndexerState.res.mjs +19 -6
- package/src/IndexerState.resi +4 -0
- package/src/Internal.res +0 -49
- package/src/Internal.res.mjs +1 -80
- package/src/LoadLayer.res +17 -10
- package/src/LoadLayer.res.mjs +11 -8
- package/src/Main.res.mjs +1 -1
- package/src/Persistence.res +3 -23
- package/src/Persistence.res.mjs +3 -3
- package/src/PreloadLookahead.res +42 -24
- package/src/PreloadLookahead.res.mjs +51 -30
- package/src/Rollback.res +0 -1
- package/src/Rollback.res.mjs +1 -1
- package/src/SafeCheckpointTracking.res +4 -4
- package/src/SafeCheckpointTracking.res.mjs +1 -1
- package/src/StagedRows.res +106 -0
- package/src/StagedRows.res.mjs +102 -0
- package/src/Staging.res +32 -14
- package/src/Staging.res.mjs +17 -20
- package/src/Storage.res +278 -973
- package/src/Storage.res.mjs +218 -720
- package/src/Supervisor.res +75 -56
- package/src/Supervisor.res.mjs +55 -26
- package/src/SyncWrites.res +24 -18
- package/src/SyncWrites.res.mjs +11 -11
- package/src/TestIndexer.res +22 -27
- package/src/TestIndexer.res.mjs +20 -17
- package/src/Throttler.res +15 -8
- package/src/Throttler.res.mjs +9 -6
- package/src/Throttler.resi +1 -1
- package/src/UserContext.res +13 -4
- package/src/UserContext.res.mjs +5 -5
- package/src/Utils.res +0 -6
- package/src/Utils.res.mjs +0 -5
- package/src/Writing.res +0 -1
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +0 -322
- package/src/bindings/ClickHouse.res.mjs +0 -338
- package/src/bindings/StorageClient.res +107 -174
- package/src/bindings/StorageClient.res.mjs +19 -8
- package/src/bindings/Vitest.res +0 -26
- package/src/bindings/Vitest.res.mjs +0 -3
- package/src/db/Table.res +24 -150
- package/src/db/Table.res.mjs +38 -272
- package/src/sources/ChainSources.res +2 -0
- package/src/sources/ChainSources.res.mjs +2 -2
- package/src/sources/EvmChain.res +3 -0
- package/src/sources/EvmChain.res.mjs +4 -2
- package/src/sources/EvmHyperSyncSource.res +2 -2
- package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
- package/src/sources/EvmRpcClient.res +13 -3
- package/src/sources/EvmRpcClient.res.mjs +4 -3
- package/src/sources/EvmRpcWs.res +4 -3
- package/src/sources/EvmRpcWs.res.mjs +2 -2
- package/src/sources/HeightFeed.res +31 -10
- package/src/sources/HeightFeed.res.mjs +10 -6
- package/src/sources/HeightStream.res +32 -17
- package/src/sources/HeightStream.res.mjs +10 -10
- package/src/sources/HyperSyncSSE.res +4 -6
- package/src/sources/HyperSyncSSE.res.mjs +2 -2
- package/src/sources/RpcSource.res +10 -1
- package/src/sources/RpcSource.res.mjs +4 -3
- package/src/sources/Source.res +3 -0
- package/src/sources/SourceManager.res +85 -104
- package/src/sources/SourceManager.res.mjs +18 -37
- package/src/sources/SourceManager.resi +3 -5
- package/src/sources/StartBlockResolver.res +1 -0
- package/src/sources/StartBlockResolver.res.mjs +4 -3
- package/src/sources/SvmHyperSyncSource.res +2 -2
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/src/subgraph/graph-ts.ts +87 -28
- package/src/subgraph/runtime.ts +0 -1
- package/src/subgraph/scope.ts +0 -1
- package/svm.schema.json +3 -3
- package/src/PgWriting.res +0 -36
- package/src/PgWriting.res.mjs +0 -34
- package/src/db/IndexDefinition.res +0 -47
- package/src/db/IndexDefinition.res.mjs +0 -60
|
@@ -27,7 +27,7 @@ async function runOnce(state) {
|
|
|
27
27
|
let persistence = IndexerState.persistence(state);
|
|
28
28
|
let storage = Persistence.getInitializedStorageOrThrow(persistence);
|
|
29
29
|
let readyAt = new Date();
|
|
30
|
-
await storage.finalizeBackfill(
|
|
30
|
+
await storage.finalizeBackfill(chainIds, readyAt);
|
|
31
31
|
return IndexerState.markReady(state, readyAt);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -42,9 +42,8 @@ function run(state) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function repairSchemaIndexes(state) {
|
|
45
|
-
let
|
|
46
|
-
|
|
47
|
-
return storage.ensureSchemaIndexes(persistence.allEntities, CrossChainState.chainIds(IndexerState.crossChainState(state)));
|
|
45
|
+
let storage = Persistence.getInitializedStorageOrThrow(IndexerState.persistence(state));
|
|
46
|
+
return storage.ensureSchemaIndexes(CrossChainState.chainIds(IndexerState.crossChainState(state)));
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
export {
|
package/src/InMemoryTable.res
CHANGED
|
@@ -22,6 +22,8 @@ module Entity = {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
type t = {
|
|
25
|
+
// Keys every id dict and Set below; see Table.table.
|
|
26
|
+
entityIdToKey: EntityId.t => string,
|
|
25
27
|
latestEntityChangeById: dict<Change.t<Internal.entity>>,
|
|
26
28
|
// Recorded changes (new latest ids + prevEntityChanges pushes), tracked
|
|
27
29
|
// manually so InMemoryStore can gauge size without scanning every dict.
|
|
@@ -45,12 +47,9 @@ module Entity = {
|
|
|
45
47
|
mutable hasIndexes: bool,
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
// string/int/bigint, so it's stringified to a stable key for in-memory
|
|
50
|
-
// indexing.
|
|
51
|
-
let getEntityIdUnsafe = (entity: Internal.entity): string =>
|
|
50
|
+
let getEntityKeyUnsafe = (self: t, entity: Internal.entity): string =>
|
|
52
51
|
switch (entity->(Utils.magic: Internal.entity => {"id": option<EntityId.t>}))["id"] {
|
|
53
|
-
| Some(id) => id
|
|
52
|
+
| Some(id) => self.entityIdToKey(id)
|
|
54
53
|
| None => JsError.throwWithMessage("Property 'id' does not exist on expected entity object")
|
|
55
54
|
}
|
|
56
55
|
|
|
@@ -62,7 +61,8 @@ module Entity = {
|
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
let make = (): t => {
|
|
64
|
+
let make = (~entityIdToKey): t => {
|
|
65
|
+
entityIdToKey,
|
|
66
66
|
latestEntityChangeById: Dict.make(),
|
|
67
67
|
changesCount: 0.,
|
|
68
68
|
dirtyIds: Utils.Set.make(),
|
|
@@ -141,10 +141,8 @@ module Entity = {
|
|
|
141
141
|
self.hasIndexes = false
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
let updateIndexes = (self: t, ~entity: Internal.entity) =>
|
|
144
|
+
let updateIndexes = (self: t, ~entityId: string, ~entity: Internal.entity) =>
|
|
145
145
|
if self.hasIndexes {
|
|
146
|
-
let entityId = entity->getEntityIdUnsafe
|
|
147
|
-
|
|
148
146
|
switch self.indexesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
149
147
|
| None => ()
|
|
150
148
|
| Some(indexes) =>
|
|
@@ -185,7 +183,7 @@ module Entity = {
|
|
|
185
183
|
}
|
|
186
184
|
|
|
187
185
|
let set = (inMemTable: t, ~committedCheckpointId, change: Change.t<Internal.entity>) => {
|
|
188
|
-
let entityKey = change->Change.getEntityId
|
|
186
|
+
let entityKey = inMemTable.entityIdToKey(change->Change.getEntityId)
|
|
189
187
|
switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(entityKey) {
|
|
190
188
|
| Some(prev) =>
|
|
191
189
|
let prevCheckpointId = prev->Change.getCheckpointId
|
|
@@ -200,8 +198,8 @@ module Entity = {
|
|
|
200
198
|
}
|
|
201
199
|
|
|
202
200
|
switch change {
|
|
203
|
-
| Set({entity}) => inMemTable->updateIndexes(~entity)
|
|
204
|
-
| Delete(
|
|
201
|
+
| Set({entity}) => inMemTable->updateIndexes(~entityId=entityKey, ~entity)
|
|
202
|
+
| Delete(_) => inMemTable->deleteEntityFromIndexes(~entityId=entityKey)
|
|
205
203
|
}
|
|
206
204
|
inMemTable.latestEntityChangeById->Dict.set(entityKey, change)
|
|
207
205
|
if change->Change.getCheckpointId > committedCheckpointId {
|
|
@@ -214,11 +212,14 @@ module Entity = {
|
|
|
214
212
|
let initValue = (
|
|
215
213
|
inMemTable: t,
|
|
216
214
|
~committedCheckpointId,
|
|
217
|
-
~
|
|
215
|
+
~entityId: EntityId.t,
|
|
218
216
|
~entity: option<Internal.entity>,
|
|
219
217
|
) =>
|
|
220
|
-
if
|
|
221
|
-
|
|
218
|
+
if (
|
|
219
|
+
inMemTable.latestEntityChangeById
|
|
220
|
+
->Utils.Dict.dangerouslyGetNonOption(inMemTable.entityIdToKey(entityId))
|
|
221
|
+
->Option.isNone
|
|
222
|
+
) {
|
|
222
223
|
let change: Change.t<Internal.entity> = switch entity {
|
|
223
224
|
| Some(entity) => Set({entityId, entity, checkpointId: Internal.loadedFromDbCheckpointId})
|
|
224
225
|
| None => Delete({entityId, checkpointId: Internal.loadedFromDbCheckpointId})
|
|
@@ -312,7 +313,7 @@ module Entity = {
|
|
|
312
313
|
switch change->mapChangeToEntity {
|
|
313
314
|
| Some(entity) =>
|
|
314
315
|
if index.matcher(entity) {
|
|
315
|
-
inMemTable->addToIndex(~index, ~entityId=entity
|
|
316
|
+
inMemTable->addToIndex(~index, ~entityId=inMemTable->getEntityKeyUnsafe(entity))
|
|
316
317
|
}
|
|
317
318
|
| None => ()
|
|
318
319
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Utils from "./Utils.res.mjs";
|
|
4
|
-
import * as EntityId from "./EntityId.res.mjs";
|
|
5
4
|
import * as Internal from "./Internal.res.mjs";
|
|
6
5
|
import * as EntityFilter from "./db/EntityFilter.res.mjs";
|
|
7
6
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
@@ -9,10 +8,10 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
9
8
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
10
9
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
11
10
|
|
|
12
|
-
function
|
|
11
|
+
function getEntityKeyUnsafe(self, entity) {
|
|
13
12
|
let id = entity.id;
|
|
14
13
|
if (id !== undefined) {
|
|
15
|
-
return
|
|
14
|
+
return self.entityIdToKey(Primitive_option.valFromOption(id));
|
|
16
15
|
} else {
|
|
17
16
|
return Stdlib_JsError.throwWithMessage("Property 'id' does not exist on expected entity object");
|
|
18
17
|
}
|
|
@@ -28,8 +27,9 @@ function addToIndex(self, index, entityId) {
|
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
function make() {
|
|
30
|
+
function make(entityIdToKey) {
|
|
32
31
|
return {
|
|
32
|
+
entityIdToKey: entityIdToKey,
|
|
33
33
|
latestEntityChangeById: {},
|
|
34
34
|
changesCount: 0,
|
|
35
35
|
dirtyIds: new Set(),
|
|
@@ -99,11 +99,10 @@ function dropCommittedChanges(self, committedCheckpointId, keepLoadedFromDb) {
|
|
|
99
99
|
self.hasIndexes = false;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
function updateIndexes(self, entity) {
|
|
102
|
+
function updateIndexes(self, entityId, entity) {
|
|
103
103
|
if (!self.hasIndexes) {
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
|
-
let entityId = getEntityIdUnsafe(entity);
|
|
107
106
|
let indexes = self.indexesByEntityId[entityId];
|
|
108
107
|
if (indexes !== undefined) {
|
|
109
108
|
let indexes$1 = Primitive_option.valFromOption(indexes);
|
|
@@ -144,7 +143,7 @@ function deleteEntityFromIndexes(self, entityId) {
|
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
function set(inMemTable, committedCheckpointId, change) {
|
|
147
|
-
let entityKey =
|
|
146
|
+
let entityKey = inMemTable.entityIdToKey(change.entityId);
|
|
148
147
|
let prev = inMemTable.latestEntityChangeById[entityKey];
|
|
149
148
|
if (prev !== undefined) {
|
|
150
149
|
let prevCheckpointId = prev.checkpointId;
|
|
@@ -156,9 +155,9 @@ function set(inMemTable, committedCheckpointId, change) {
|
|
|
156
155
|
inMemTable.changesCount = inMemTable.changesCount + 1;
|
|
157
156
|
}
|
|
158
157
|
if (change.type === "SET") {
|
|
159
|
-
updateIndexes(inMemTable, change.entity);
|
|
158
|
+
updateIndexes(inMemTable, entityKey, change.entity);
|
|
160
159
|
} else {
|
|
161
|
-
deleteEntityFromIndexes(inMemTable,
|
|
160
|
+
deleteEntityFromIndexes(inMemTable, entityKey);
|
|
162
161
|
}
|
|
163
162
|
inMemTable.latestEntityChangeById[entityKey] = change;
|
|
164
163
|
if (change.checkpointId > committedCheckpointId) {
|
|
@@ -167,18 +166,18 @@ function set(inMemTable, committedCheckpointId, change) {
|
|
|
167
166
|
}
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
function initValue(inMemTable, committedCheckpointId,
|
|
171
|
-
if (!Stdlib_Option.isNone(inMemTable.latestEntityChangeById[
|
|
169
|
+
function initValue(inMemTable, committedCheckpointId, entityId, entity) {
|
|
170
|
+
if (!Stdlib_Option.isNone(inMemTable.latestEntityChangeById[inMemTable.entityIdToKey(entityId)])) {
|
|
172
171
|
return;
|
|
173
172
|
}
|
|
174
173
|
let change = entity !== undefined ? ({
|
|
175
174
|
type: "SET",
|
|
176
|
-
entityId:
|
|
175
|
+
entityId: entityId,
|
|
177
176
|
entity: entity,
|
|
178
177
|
checkpointId: Internal.loadedFromDbCheckpointId
|
|
179
178
|
}) : ({
|
|
180
179
|
type: "DELETE",
|
|
181
|
-
entityId:
|
|
180
|
+
entityId: entityId,
|
|
182
181
|
checkpointId: Internal.loadedFromDbCheckpointId
|
|
183
182
|
});
|
|
184
183
|
set(inMemTable, committedCheckpointId, change);
|
|
@@ -285,7 +284,7 @@ function addEmptyIndex(inMemTable, filter, table) {
|
|
|
285
284
|
Utils.Dict.forEach(inMemTable.latestEntityChangeById, change => {
|
|
286
285
|
let entity = mapChangeToEntity(change);
|
|
287
286
|
if (entity !== undefined && index_matcher(entity)) {
|
|
288
|
-
return addToIndex(inMemTable, index,
|
|
287
|
+
return addToIndex(inMemTable, index, getEntityKeyUnsafe(inMemTable, entity));
|
|
289
288
|
}
|
|
290
289
|
});
|
|
291
290
|
}
|
|
@@ -321,7 +320,7 @@ function tryIndexFromLoadedValues(inMemTable, filter, table) {
|
|
|
321
320
|
}
|
|
322
321
|
|
|
323
322
|
let Entity = {
|
|
324
|
-
|
|
323
|
+
getEntityKeyUnsafe: getEntityKeyUnsafe,
|
|
325
324
|
addToIndex: addToIndex,
|
|
326
325
|
make: make,
|
|
327
326
|
snapshotChanges: snapshotChanges,
|
package/src/IndexerState.res
CHANGED
|
@@ -108,6 +108,12 @@ type t = {
|
|
|
108
108
|
// in-process test runner injects a callback that resolves its run promise
|
|
109
109
|
// instead, so a caught-up run doesn't kill the test process.
|
|
110
110
|
onExit: option<unit => unit>,
|
|
111
|
+
// Told, in the processing pass that gets it there, that a run holding its
|
|
112
|
+
// realtime transitions has gone as far as it can on its own. The test
|
|
113
|
+
// runner's stand-in supervisor releases from it, so the release lands at a
|
|
114
|
+
// point in the indexer's own progress rather than on a timer.
|
|
115
|
+
onArrivedAtHead: option<unit => unit>,
|
|
116
|
+
mutable reportedArrivalAtHead: bool,
|
|
111
117
|
// Set once on any fatal error. Every loop checks it to stop iterating and
|
|
112
118
|
// every launch skips when it's set, so a single failure quiesces the indexer.
|
|
113
119
|
mutable isStopped: bool,
|
|
@@ -157,10 +163,13 @@ let make = (
|
|
|
157
163
|
~exitAfterFirstEventBlock=false,
|
|
158
164
|
~onError: ErrorHandling.t => unit,
|
|
159
165
|
~onExit=?,
|
|
166
|
+
~onArrivedAtHead=?,
|
|
167
|
+
~clock=Clock.real,
|
|
160
168
|
) => {
|
|
161
169
|
let chainMetaThrottler = {
|
|
162
170
|
let intervalMillis = Env.ThrottleWrites.chainMetadataIntervalMillis
|
|
163
171
|
Throttler.make(
|
|
172
|
+
~clock,
|
|
164
173
|
~intervalMillis,
|
|
165
174
|
~logger=logger->Logger.withFields({
|
|
166
175
|
"context": "Throttler for chain metadata writes",
|
|
@@ -175,7 +184,7 @@ let make = (
|
|
|
175
184
|
persistence,
|
|
176
185
|
allEntities: persistence.allEntities,
|
|
177
186
|
entities: EntityTables.make(persistence.allEntities->EntityTables.crossChain),
|
|
178
|
-
effectState: EffectState.make(),
|
|
187
|
+
effectState: EffectState.make(~clock, ()),
|
|
179
188
|
rollback: None,
|
|
180
189
|
committedFrontier,
|
|
181
190
|
processedFrontier: committedFrontier->Frontier.copy,
|
|
@@ -205,6 +214,8 @@ let make = (
|
|
|
205
214
|
exitAfterFirstEventBlock,
|
|
206
215
|
onError,
|
|
207
216
|
onExit,
|
|
217
|
+
onArrivedAtHead,
|
|
218
|
+
reportedArrivalAtHead: false,
|
|
208
219
|
isStopped: false,
|
|
209
220
|
epoch: 0,
|
|
210
221
|
simulateDeadInputTracker: SimulateDeadInputTracker.makeFromConfig(config),
|
|
@@ -225,12 +236,13 @@ let make = (
|
|
|
225
236
|
}
|
|
226
237
|
|
|
227
238
|
// Check if progress is past the reorg threshold (safe block).
|
|
228
|
-
// A chain is in reorg threshold
|
|
229
|
-
// This matches the
|
|
239
|
+
// A chain is in reorg threshold once its progress passes the safe block,
|
|
240
|
+
// sourceBlockNumber - maxReorgDepth - 1. This matches the reorg checkpoints
|
|
241
|
+
// query in packages/cli/src/postgres/internal.rs.
|
|
230
242
|
let isProgressInReorgThreshold = (~progressBlockNumber, ~sourceBlockNumber, ~maxReorgDepth) => {
|
|
231
243
|
maxReorgDepth > 0 &&
|
|
232
244
|
sourceBlockNumber > 0 &&
|
|
233
|
-
progressBlockNumber > sourceBlockNumber - maxReorgDepth
|
|
245
|
+
progressBlockNumber > sourceBlockNumber - maxReorgDepth - 1
|
|
234
246
|
}
|
|
235
247
|
|
|
236
248
|
let makeFromDbState = (
|
|
@@ -249,6 +261,7 @@ let makeFromDbState = (
|
|
|
249
261
|
~holdRealtime=false,
|
|
250
262
|
~onError,
|
|
251
263
|
~onExit=?,
|
|
264
|
+
~onArrivedAtHead=?,
|
|
252
265
|
) => {
|
|
253
266
|
let isInReorgThreshold = if initialState.cleanRun {
|
|
254
267
|
false
|
|
@@ -303,6 +316,8 @@ let makeFromDbState = (
|
|
|
303
316
|
~exitAfterFirstEventBlock,
|
|
304
317
|
~onError,
|
|
305
318
|
~onExit?,
|
|
319
|
+
~onArrivedAtHead?,
|
|
320
|
+
~clock=?sourceTiming->Option.flatMap(timing => timing.clock),
|
|
306
321
|
)
|
|
307
322
|
state.crossChainState->CrossChainState.markCaughtUpOnResume
|
|
308
323
|
initialState.cache->Utils.Dict.forEach(({effectName, count, scope}) => {
|
|
@@ -559,6 +574,15 @@ let isHoldingRealtime = (state: t) => state.crossChainState->CrossChainState.isH
|
|
|
559
574
|
|
|
560
575
|
let hasArrivedAtHead = (state: t) => state.crossChainState->CrossChainState.hasArrivedAtHead
|
|
561
576
|
|
|
577
|
+
let reportArrivalAtHead = (state: t) =>
|
|
578
|
+
switch state.onArrivedAtHead {
|
|
579
|
+
| Some(onArrivedAtHead)
|
|
580
|
+
if !state.reportedArrivalAtHead && state->isHoldingRealtime && state->hasArrivedAtHead =>
|
|
581
|
+
state.reportedArrivalAtHead = true
|
|
582
|
+
onArrivedAtHead()
|
|
583
|
+
| _ => ()
|
|
584
|
+
}
|
|
585
|
+
|
|
562
586
|
let releaseRealtime = (state: t) => {
|
|
563
587
|
state.crossChainState->CrossChainState.releaseRealtime
|
|
564
588
|
// Every chain is parked at the head with no batch coming, so nothing would
|
package/src/IndexerState.res.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Env from "./Env.res.mjs";
|
|
4
4
|
import * as Batch from "./Batch.res.mjs";
|
|
5
|
+
import * as Clock from "./Clock.res.mjs";
|
|
5
6
|
import * as Utils from "./Utils.res.mjs";
|
|
6
7
|
import * as Config from "./Config.res.mjs";
|
|
7
8
|
import * as Logger from "./Logger.res.mjs";
|
|
@@ -26,25 +27,26 @@ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js
|
|
|
26
27
|
import * as CheckpointSequence from "./db/CheckpointSequence.res.mjs";
|
|
27
28
|
import * as SimulateDeadInputTracker from "./SimulateDeadInputTracker.res.mjs";
|
|
28
29
|
|
|
29
|
-
function make(config, logger, persistence, chainStates, isRealtime, targetBufferSizeOpt, holdRealtimeOpt, committedFrontierOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
|
|
30
|
+
function make(config, logger, persistence, chainStates, isRealtime, targetBufferSizeOpt, holdRealtimeOpt, committedFrontierOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit, onArrivedAtHead, clockOpt) {
|
|
30
31
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
|
|
31
32
|
let holdRealtime = holdRealtimeOpt !== undefined ? holdRealtimeOpt : false;
|
|
32
33
|
let committedFrontier = committedFrontierOpt !== undefined ? committedFrontierOpt : Frontier.empty();
|
|
33
34
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
34
35
|
let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
|
|
35
36
|
let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
|
|
37
|
+
let clock = clockOpt !== undefined ? clockOpt : Clock.real;
|
|
36
38
|
let intervalMillis = Env.ThrottleWrites.chainMetadataIntervalMillis;
|
|
37
39
|
let chainMetaThrottler = Throttler.make(intervalMillis, Logger.withFields(logger, {
|
|
38
40
|
context: "Throttler for chain metadata writes",
|
|
39
41
|
intervalMillis: intervalMillis
|
|
40
|
-
}));
|
|
42
|
+
}), clock);
|
|
41
43
|
return {
|
|
42
44
|
config: config,
|
|
43
45
|
logger: logger,
|
|
44
46
|
persistence: persistence,
|
|
45
47
|
allEntities: persistence.allEntities,
|
|
46
48
|
entities: EntityTables.make(EntityTables.crossChain(persistence.allEntities)),
|
|
47
|
-
effectState: EffectState.make(),
|
|
49
|
+
effectState: EffectState.make(clock, undefined),
|
|
48
50
|
rollback: undefined,
|
|
49
51
|
committedFrontier: committedFrontier,
|
|
50
52
|
processedFrontier: Frontier.copy(committedFrontier),
|
|
@@ -68,6 +70,8 @@ function make(config, logger, persistence, chainStates, isRealtime, targetBuffer
|
|
|
68
70
|
exitAfterFirstEventBlock: exitAfterFirstEventBlock,
|
|
69
71
|
onError: onError,
|
|
70
72
|
onExit: onExit,
|
|
73
|
+
onArrivedAtHead: onArrivedAtHead,
|
|
74
|
+
reportedArrivalAtHead: false,
|
|
71
75
|
isStopped: false,
|
|
72
76
|
epoch: 0,
|
|
73
77
|
scheduleProcessing: () => {},
|
|
@@ -88,7 +92,7 @@ function make(config, logger, persistence, chainStates, isRealtime, targetBuffer
|
|
|
88
92
|
};
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
function makeFromDbState(config, logger, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, sourceTiming, targetBufferSizeOpt, holdRealtimeOpt, onError, onExit) {
|
|
95
|
+
function makeFromDbState(config, logger, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, sourceTiming, targetBufferSizeOpt, holdRealtimeOpt, onError, onExit, onArrivedAtHead) {
|
|
92
96
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
93
97
|
let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
|
|
94
98
|
let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
|
|
@@ -99,7 +103,7 @@ function makeFromDbState(config, logger, persistence, initialState, registration
|
|
|
99
103
|
let sourceBlockNumber = resumedChainState.sourceBlockNumber;
|
|
100
104
|
let maxReorgDepth = resumedChainState.maxReorgDepth;
|
|
101
105
|
if (maxReorgDepth > 0 && sourceBlockNumber > 0) {
|
|
102
|
-
return progressBlockNumber > (sourceBlockNumber - maxReorgDepth | 0);
|
|
106
|
+
return progressBlockNumber > ((sourceBlockNumber - maxReorgDepth | 0) - 1 | 0);
|
|
103
107
|
} else {
|
|
104
108
|
return false;
|
|
105
109
|
}
|
|
@@ -111,7 +115,7 @@ function makeFromDbState(config, logger, persistence, initialState, registration
|
|
|
111
115
|
let chainConfig = ChainMap.get(config.chainMap, chainId);
|
|
112
116
|
chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, initialState.contractMapping, registrationsByChainId, logger, sourceTiming);
|
|
113
117
|
});
|
|
114
|
-
let state = make(config, logger, persistence, chainStates, isRealtime, targetBufferSize, holdRealtime, initialState.checkpointFrontier, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
|
|
118
|
+
let state = make(config, logger, persistence, chainStates, isRealtime, targetBufferSize, holdRealtime, initialState.checkpointFrontier, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit, onArrivedAtHead, Stdlib_Option.flatMap(sourceTiming, timing => timing.clock));
|
|
115
119
|
CrossChainState.markCaughtUpOnResume(state.crossChainState);
|
|
116
120
|
Utils.Dict.forEach(initialState.cache, param => {
|
|
117
121
|
let count = param.count;
|
|
@@ -401,6 +405,14 @@ function hasArrivedAtHead(state) {
|
|
|
401
405
|
return CrossChainState.hasArrivedAtHead(state.crossChainState);
|
|
402
406
|
}
|
|
403
407
|
|
|
408
|
+
function reportArrivalAtHead(state) {
|
|
409
|
+
let onArrivedAtHead = state.onArrivedAtHead;
|
|
410
|
+
if (onArrivedAtHead !== undefined && !state.reportedArrivalAtHead && CrossChainState.isHoldingRealtime(state.crossChainState) && CrossChainState.hasArrivedAtHead(state.crossChainState)) {
|
|
411
|
+
state.reportedArrivalAtHead = true;
|
|
412
|
+
return onArrivedAtHead();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
404
416
|
function releaseRealtime(state) {
|
|
405
417
|
CrossChainState.releaseRealtime(state.crossChainState);
|
|
406
418
|
state.scheduleProcessing();
|
|
@@ -917,6 +929,7 @@ export {
|
|
|
917
929
|
bindScheduleProcessing,
|
|
918
930
|
isHoldingRealtime,
|
|
919
931
|
hasArrivedAtHead,
|
|
932
|
+
reportArrivalAtHead,
|
|
920
933
|
releaseRealtime,
|
|
921
934
|
markReady,
|
|
922
935
|
reportFinished,
|
package/src/IndexerState.resi
CHANGED
|
@@ -24,6 +24,8 @@ let make: (
|
|
|
24
24
|
~exitAfterFirstEventBlock: bool=?,
|
|
25
25
|
~onError: ErrorHandling.t => unit,
|
|
26
26
|
~onExit: unit => unit=?,
|
|
27
|
+
~onArrivedAtHead: unit => unit=?,
|
|
28
|
+
~clock: Clock.t=?,
|
|
27
29
|
) => t
|
|
28
30
|
|
|
29
31
|
let makeFromDbState: (
|
|
@@ -40,6 +42,7 @@ let makeFromDbState: (
|
|
|
40
42
|
~holdRealtime: bool=?,
|
|
41
43
|
~onError: ErrorHandling.t => unit,
|
|
42
44
|
~onExit: unit => unit=?,
|
|
45
|
+
~onArrivedAtHead: unit => unit=?,
|
|
43
46
|
) => t
|
|
44
47
|
|
|
45
48
|
let unexpectedErrorMsg: string
|
|
@@ -106,6 +109,7 @@ let shouldFinalizeIndexes: t => bool
|
|
|
106
109
|
let bindScheduleProcessing: (t, unit => unit) => unit
|
|
107
110
|
let isHoldingRealtime: t => bool
|
|
108
111
|
let hasArrivedAtHead: t => bool
|
|
112
|
+
let reportArrivalAtHead: t => unit
|
|
109
113
|
// The supervisor's go-ahead for a process driving part of a split run.
|
|
110
114
|
let releaseRealtime: t => unit
|
|
111
115
|
let markReady: (t, ~readyAt: Date.t) => unit
|
package/src/Internal.res
CHANGED
|
@@ -1011,55 +1011,6 @@ module EffectCache = {
|
|
|
1011
1011
|
| CrossChain => cacheTablePrefix ++ effectName
|
|
1012
1012
|
| Chain(chainId) => `envio_${chainId->ChainId.toString}_effect_${effectName}`
|
|
1013
1013
|
}
|
|
1014
|
-
|
|
1015
|
-
// Only accepts a canonical decimal chain id ("7", not "007" or "1foo") —
|
|
1016
|
-
// the schema's parser follows parseFloat semantics and accepts both.
|
|
1017
|
-
let parseChainId = str =>
|
|
1018
|
-
switch try Some(str->ChainId.normalizeOrThrow) catch {
|
|
1019
|
-
| _ => None
|
|
1020
|
-
} {
|
|
1021
|
-
| Some(chainId) if chainId->ChainId.toString === str => Some(chainId)
|
|
1022
|
-
| _ => None
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
let chainScopedRe = /^envio_([0-9]+)_effect_(.+)$/
|
|
1026
|
-
let crossChainRe = /^envio_effect_(.+)$/
|
|
1027
|
-
|
|
1028
|
-
// Inverse of toTableName. Returns None for any table name that isn't a cache
|
|
1029
|
-
// table. Chain-scoped is tried first: the `_effect_` separator keeps effect
|
|
1030
|
-
// names that themselves start with digits unambiguous.
|
|
1031
|
-
let fromTableName = (tableName): option<(string, chainScope)> =>
|
|
1032
|
-
switch RegExp.exec(chainScopedRe, tableName) {
|
|
1033
|
-
| Some(result) =>
|
|
1034
|
-
switch (
|
|
1035
|
-
RegExp.Result.matches(result)->Array.get(0),
|
|
1036
|
-
RegExp.Result.matches(result)->Array.get(1),
|
|
1037
|
-
) {
|
|
1038
|
-
| (Some(Some(chainIdStr)), Some(Some(effectName))) =>
|
|
1039
|
-
switch parseChainId(chainIdStr) {
|
|
1040
|
-
| Some(chainId) => Some((effectName, Chain(chainId)))
|
|
1041
|
-
| None => None
|
|
1042
|
-
}
|
|
1043
|
-
| _ => None
|
|
1044
|
-
}
|
|
1045
|
-
| None =>
|
|
1046
|
-
switch RegExp.exec(crossChainRe, tableName) {
|
|
1047
|
-
| Some(result) =>
|
|
1048
|
-
switch RegExp.Result.matches(result)->Array.get(0) {
|
|
1049
|
-
| Some(Some(effectName)) => Some((effectName, CrossChain))
|
|
1050
|
-
| _ => None
|
|
1051
|
-
}
|
|
1052
|
-
| None => None
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// Relative posix path within .envio/cache. Chain-scoped caches live one
|
|
1057
|
-
// directory level deep, named by chain id.
|
|
1058
|
-
let toCachePath = (~effectName, ~scope) =>
|
|
1059
|
-
switch scope {
|
|
1060
|
-
| CrossChain => effectName ++ ".tsv"
|
|
1061
|
-
| Chain(chainId) => `${chainId->ChainId.toString}/${effectName}.tsv`
|
|
1062
|
-
}
|
|
1063
1014
|
}
|
|
1064
1015
|
|
|
1065
1016
|
let cacheOutputSchema = S.json(~validate=false)->(Utils.magic: S.t<JSON.t> => S.t<effectOutput>)
|
package/src/Internal.res.mjs
CHANGED
|
@@ -271,87 +271,8 @@ function toTableName(effectName, scope) {
|
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
function parseChainId(str) {
|
|
275
|
-
let chainId;
|
|
276
|
-
try {
|
|
277
|
-
chainId = Primitive_option.some(ChainId.normalizeOrThrow(str));
|
|
278
|
-
} catch (exn) {
|
|
279
|
-
chainId = undefined;
|
|
280
|
-
}
|
|
281
|
-
if (chainId === undefined) {
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
let chainId$1 = Primitive_option.valFromOption(chainId);
|
|
285
|
-
if (ChainId.toString(chainId$1) === str) {
|
|
286
|
-
return Primitive_option.some(chainId$1);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
let chainScopedRe = /^envio_([0-9]+)_effect_(.+)$/;
|
|
291
|
-
|
|
292
|
-
let crossChainRe = /^envio_effect_(.+)$/;
|
|
293
|
-
|
|
294
|
-
function fromTableName(tableName) {
|
|
295
|
-
let result = chainScopedRe.exec(tableName);
|
|
296
|
-
if (result == null) {
|
|
297
|
-
let result$1 = crossChainRe.exec(tableName);
|
|
298
|
-
if (result$1 == null) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
let match = result$1.slice(1)[0];
|
|
302
|
-
if (match === undefined) {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
let effectName = Primitive_option.valFromOption(match);
|
|
306
|
-
if (effectName !== undefined) {
|
|
307
|
-
return [
|
|
308
|
-
effectName,
|
|
309
|
-
"crossChain"
|
|
310
|
-
];
|
|
311
|
-
} else {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
let match$1 = result.slice(1)[0];
|
|
316
|
-
let match$2 = result.slice(1)[1];
|
|
317
|
-
if (match$1 === undefined) {
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
let chainIdStr = Primitive_option.valFromOption(match$1);
|
|
321
|
-
if (chainIdStr === undefined) {
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (match$2 === undefined) {
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
let effectName$1 = Primitive_option.valFromOption(match$2);
|
|
328
|
-
if (effectName$1 === undefined) {
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
let chainId = parseChainId(chainIdStr);
|
|
332
|
-
if (chainId !== undefined) {
|
|
333
|
-
return [
|
|
334
|
-
effectName$1,
|
|
335
|
-
Primitive_option.valFromOption(chainId)
|
|
336
|
-
];
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function toCachePath(effectName, scope) {
|
|
341
|
-
if (scope === "crossChain") {
|
|
342
|
-
return effectName + ".tsv";
|
|
343
|
-
} else {
|
|
344
|
-
return ChainId.toString(scope) + `/` + effectName + `.tsv`;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
274
|
let EffectCache = {
|
|
349
|
-
toTableName: toTableName
|
|
350
|
-
parseChainId: parseChainId,
|
|
351
|
-
chainScopedRe: chainScopedRe,
|
|
352
|
-
crossChainRe: crossChainRe,
|
|
353
|
-
fromTableName: fromTableName,
|
|
354
|
-
toCachePath: toCachePath
|
|
275
|
+
toTableName: toTableName
|
|
355
276
|
};
|
|
356
277
|
|
|
357
278
|
let cacheOutputSchema = S$RescriptSchema.json(false);
|
package/src/LoadLayer.res
CHANGED
|
@@ -16,7 +16,12 @@ let loadById = (
|
|
|
16
16
|
~entityId,
|
|
17
17
|
) => {
|
|
18
18
|
let inMemTable = indexerState->InMemoryStore.getInMemTable(~entityConfig, ~scope)
|
|
19
|
-
|
|
19
|
+
// The ids here are typed as strings but hold whatever scalar the id is.
|
|
20
|
+
let entityIdToKey =
|
|
21
|
+
inMemTable.entityIdToKey->(Utils.magic: (EntityId.t => string) => string => string)
|
|
22
|
+
switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(
|
|
23
|
+
entityIdToKey(entityId),
|
|
24
|
+
) {
|
|
20
25
|
// A row already in memory is final, so even a grouped call skips the load
|
|
21
26
|
// manager: grouping it would only resolve it a tick later to the same value.
|
|
22
27
|
| Some(change) => Promise.resolve(change->InMemoryTable.Entity.mapChangeToEntity)
|
|
@@ -53,14 +58,14 @@ let loadById = (
|
|
|
53
58
|
//Set the entity in the in memory store
|
|
54
59
|
for idx in 0 to dbEntities->Array.length - 1 {
|
|
55
60
|
let entity = dbEntities->Array.getUnsafe(idx)
|
|
56
|
-
entitiesMap->Dict.set(entity.id, entity)
|
|
61
|
+
entitiesMap->Dict.set(entityIdToKey(entity.id), entity)
|
|
57
62
|
}
|
|
58
63
|
let committedCheckpointId = indexerState->IndexerState.committedCheckpointIdFor(~scope)
|
|
59
64
|
idsToLoad->Array.forEach(entityId => {
|
|
60
65
|
inMemTable->InMemoryTable.Entity.initValue(
|
|
61
66
|
~committedCheckpointId,
|
|
62
|
-
~
|
|
63
|
-
~entity=entitiesMap->Utils.Dict.dangerouslyGetNonOption(entityId),
|
|
67
|
+
~entityId=entityId->EntityId.unsafeOfString,
|
|
68
|
+
~entity=entitiesMap->Utils.Dict.dangerouslyGetNonOption(entityIdToKey(entityId)),
|
|
64
69
|
)
|
|
65
70
|
})
|
|
66
71
|
|
|
@@ -77,7 +82,7 @@ let loadById = (
|
|
|
77
82
|
~key,
|
|
78
83
|
~load,
|
|
79
84
|
~shouldGroup,
|
|
80
|
-
~hasher=
|
|
85
|
+
~hasher=entityIdToKey,
|
|
81
86
|
~getUnsafeInMemory=inMemTable->InMemoryTable.Entity.getUnsafe,
|
|
82
87
|
~hasInMemory=hash => inMemTable.latestEntityChangeById->Dict.has(hash),
|
|
83
88
|
~input=entityId,
|
|
@@ -140,7 +145,7 @@ let rec executeWithRateLimit = (
|
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
| Some(state) =>
|
|
143
|
-
let now =
|
|
148
|
+
let now = state.clock.now()
|
|
144
149
|
|
|
145
150
|
// Check if we need to reset the window
|
|
146
151
|
if now >= state.windowStartTime +. state.durationMs->Int.toFloat {
|
|
@@ -188,7 +193,8 @@ let rec executeWithRateLimit = (
|
|
|
188
193
|
| None =>
|
|
189
194
|
millisUntilReset :=
|
|
190
195
|
(state.windowStartTime +. state.durationMs->Int.toFloat -. now)->Float.toInt
|
|
191
|
-
let p =
|
|
196
|
+
let p =
|
|
197
|
+
state.clock->Clock.delay(~label="effect rate limit window", millisUntilReset.contents)
|
|
192
198
|
state.nextWindowPromise = Some(p)
|
|
193
199
|
p
|
|
194
200
|
}
|
|
@@ -355,8 +361,9 @@ let getByIdInMemory = (
|
|
|
355
361
|
~entityId: string,
|
|
356
362
|
) => {
|
|
357
363
|
let inMemTable = indexerState->InMemoryStore.getInMemTable(~entityConfig, ~scope)
|
|
358
|
-
inMemTable.
|
|
359
|
-
|
|
364
|
+
let entityKey = inMemTable.entityIdToKey(entityId->EntityId.unsafeOfString)
|
|
365
|
+
inMemTable.latestEntityChangeById->Dict.has(entityKey)
|
|
366
|
+
? Some((inMemTable->InMemoryTable.Entity.getUnsafe)(entityKey))
|
|
360
367
|
: None
|
|
361
368
|
}
|
|
362
369
|
|
|
@@ -425,7 +432,7 @@ let loadByParsedFilter = (
|
|
|
425
432
|
entities->Array.forEach(entity => {
|
|
426
433
|
inMemTable->InMemoryTable.Entity.initValue(
|
|
427
434
|
~committedCheckpointId,
|
|
428
|
-
~
|
|
435
|
+
~entityId=entity.id->EntityId.unsafeOfString,
|
|
429
436
|
~entity=Some(entity),
|
|
430
437
|
)
|
|
431
438
|
})
|