envio 3.15.0 → 3.16.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/README.md +40 -34
- package/evm.schema.json +11 -2
- package/fuel.schema.json +10 -1
- package/index.d.ts +6 -0
- 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/ChainState.res +48 -7
- package/src/ChainState.res.mjs +27 -11
- package/src/Clock.res +57 -0
- package/src/Clock.res.mjs +72 -0
- package/src/Config.res +10 -3
- package/src/Config.res.mjs +4 -2
- package/src/Core.res +4 -40
- 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/FetchState.res +157 -69
- package/src/FetchState.res.mjs +144 -49
- 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 +14 -8
- package/src/LoadLayer.res.mjs +8 -6
- package/src/Main.res.mjs +1 -1
- package/src/Persistence.res +3 -23
- package/src/Persistence.res.mjs +3 -3
- 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 +277 -948
- package/src/Storage.res.mjs +215 -707
- package/src/Supervisor.res +75 -56
- package/src/Supervisor.res.mjs +55 -26
- 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/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 +84 -77
- package/src/sources/SourceManager.res.mjs +17 -20
- package/src/sources/SourceManager.resi +3 -4
- package/src/sources/StartBlockResolver.res +1 -0
- package/src/sources/StartBlockResolver.res.mjs +3 -2
- package/src/sources/SvmHyperSyncSource.res +2 -2
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/svm.schema.json +11 -2
- 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 -67
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
|
@@ -950,55 +950,6 @@ module EffectCache = {
|
|
|
950
950
|
| CrossChain => cacheTablePrefix ++ effectName
|
|
951
951
|
| Chain(chainId) => `envio_${chainId->ChainId.toString}_effect_${effectName}`
|
|
952
952
|
}
|
|
953
|
-
|
|
954
|
-
// Only accepts a canonical decimal chain id ("7", not "007" or "1foo") —
|
|
955
|
-
// the schema's parser follows parseFloat semantics and accepts both.
|
|
956
|
-
let parseChainId = str =>
|
|
957
|
-
switch try Some(str->ChainId.normalizeOrThrow) catch {
|
|
958
|
-
| _ => None
|
|
959
|
-
} {
|
|
960
|
-
| Some(chainId) if chainId->ChainId.toString === str => Some(chainId)
|
|
961
|
-
| _ => None
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
let chainScopedRe = /^envio_([0-9]+)_effect_(.+)$/
|
|
965
|
-
let crossChainRe = /^envio_effect_(.+)$/
|
|
966
|
-
|
|
967
|
-
// Inverse of toTableName. Returns None for any table name that isn't a cache
|
|
968
|
-
// table. Chain-scoped is tried first: the `_effect_` separator keeps effect
|
|
969
|
-
// names that themselves start with digits unambiguous.
|
|
970
|
-
let fromTableName = (tableName): option<(string, chainScope)> =>
|
|
971
|
-
switch RegExp.exec(chainScopedRe, tableName) {
|
|
972
|
-
| Some(result) =>
|
|
973
|
-
switch (
|
|
974
|
-
RegExp.Result.matches(result)->Array.get(0),
|
|
975
|
-
RegExp.Result.matches(result)->Array.get(1),
|
|
976
|
-
) {
|
|
977
|
-
| (Some(Some(chainIdStr)), Some(Some(effectName))) =>
|
|
978
|
-
switch parseChainId(chainIdStr) {
|
|
979
|
-
| Some(chainId) => Some((effectName, Chain(chainId)))
|
|
980
|
-
| None => None
|
|
981
|
-
}
|
|
982
|
-
| _ => None
|
|
983
|
-
}
|
|
984
|
-
| None =>
|
|
985
|
-
switch RegExp.exec(crossChainRe, tableName) {
|
|
986
|
-
| Some(result) =>
|
|
987
|
-
switch RegExp.Result.matches(result)->Array.get(0) {
|
|
988
|
-
| Some(Some(effectName)) => Some((effectName, CrossChain))
|
|
989
|
-
| _ => None
|
|
990
|
-
}
|
|
991
|
-
| None => None
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// Relative posix path within .envio/cache. Chain-scoped caches live one
|
|
996
|
-
// directory level deep, named by chain id.
|
|
997
|
-
let toCachePath = (~effectName, ~scope) =>
|
|
998
|
-
switch scope {
|
|
999
|
-
| CrossChain => effectName ++ ".tsv"
|
|
1000
|
-
| Chain(chainId) => `${chainId->ChainId.toString}/${effectName}.tsv`
|
|
1001
|
-
}
|
|
1002
953
|
}
|
|
1003
954
|
|
|
1004
955
|
let cacheOutputSchema = S.json(~validate=false)->(Utils.magic: S.t<JSON.t> => S.t<effectOutput>)
|
package/src/Internal.res.mjs
CHANGED
|
@@ -240,87 +240,8 @@ function toTableName(effectName, scope) {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
function parseChainId(str) {
|
|
244
|
-
let chainId;
|
|
245
|
-
try {
|
|
246
|
-
chainId = Primitive_option.some(ChainId.normalizeOrThrow(str));
|
|
247
|
-
} catch (exn) {
|
|
248
|
-
chainId = undefined;
|
|
249
|
-
}
|
|
250
|
-
if (chainId === undefined) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
let chainId$1 = Primitive_option.valFromOption(chainId);
|
|
254
|
-
if (ChainId.toString(chainId$1) === str) {
|
|
255
|
-
return Primitive_option.some(chainId$1);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
let chainScopedRe = /^envio_([0-9]+)_effect_(.+)$/;
|
|
260
|
-
|
|
261
|
-
let crossChainRe = /^envio_effect_(.+)$/;
|
|
262
|
-
|
|
263
|
-
function fromTableName(tableName) {
|
|
264
|
-
let result = chainScopedRe.exec(tableName);
|
|
265
|
-
if (result == null) {
|
|
266
|
-
let result$1 = crossChainRe.exec(tableName);
|
|
267
|
-
if (result$1 == null) {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
let match = result$1.slice(1)[0];
|
|
271
|
-
if (match === undefined) {
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
let effectName = Primitive_option.valFromOption(match);
|
|
275
|
-
if (effectName !== undefined) {
|
|
276
|
-
return [
|
|
277
|
-
effectName,
|
|
278
|
-
"crossChain"
|
|
279
|
-
];
|
|
280
|
-
} else {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
let match$1 = result.slice(1)[0];
|
|
285
|
-
let match$2 = result.slice(1)[1];
|
|
286
|
-
if (match$1 === undefined) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
let chainIdStr = Primitive_option.valFromOption(match$1);
|
|
290
|
-
if (chainIdStr === undefined) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
if (match$2 === undefined) {
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
let effectName$1 = Primitive_option.valFromOption(match$2);
|
|
297
|
-
if (effectName$1 === undefined) {
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
let chainId = parseChainId(chainIdStr);
|
|
301
|
-
if (chainId !== undefined) {
|
|
302
|
-
return [
|
|
303
|
-
effectName$1,
|
|
304
|
-
Primitive_option.valFromOption(chainId)
|
|
305
|
-
];
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function toCachePath(effectName, scope) {
|
|
310
|
-
if (scope === "crossChain") {
|
|
311
|
-
return effectName + ".tsv";
|
|
312
|
-
} else {
|
|
313
|
-
return ChainId.toString(scope) + `/` + effectName + `.tsv`;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
243
|
let EffectCache = {
|
|
318
|
-
toTableName: toTableName
|
|
319
|
-
parseChainId: parseChainId,
|
|
320
|
-
chainScopedRe: chainScopedRe,
|
|
321
|
-
crossChainRe: crossChainRe,
|
|
322
|
-
fromTableName: fromTableName,
|
|
323
|
-
toCachePath: toCachePath
|
|
244
|
+
toTableName: toTableName
|
|
324
245
|
};
|
|
325
246
|
|
|
326
247
|
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
|
}
|
|
@@ -387,7 +393,7 @@ let loadByParsedFilter = (
|
|
|
387
393
|
entities->Array.forEach(entity => {
|
|
388
394
|
inMemTable->InMemoryTable.Entity.initValue(
|
|
389
395
|
~committedCheckpointId,
|
|
390
|
-
~
|
|
396
|
+
~entityId=entity.id->EntityId.unsafeOfString,
|
|
391
397
|
~entity=Some(entity),
|
|
392
398
|
)
|
|
393
399
|
})
|
package/src/LoadLayer.res.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Clock from "./Clock.res.mjs";
|
|
3
4
|
import * as Utils from "./Utils.res.mjs";
|
|
4
5
|
import * as Logger from "./Logger.res.mjs";
|
|
5
6
|
import * as ChainId from "./ChainId.res.mjs";
|
|
@@ -27,7 +28,8 @@ function scopeKeySuffix(scope) {
|
|
|
27
28
|
|
|
28
29
|
function loadById(loadManager, persistence, entityConfig, scope, indexerState, shouldGroup, entityId) {
|
|
29
30
|
let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
|
|
30
|
-
let
|
|
31
|
+
let entityIdToKey = inMemTable.entityIdToKey;
|
|
32
|
+
let change = inMemTable.latestEntityChangeById[entityIdToKey(entityId)];
|
|
31
33
|
if (change !== undefined) {
|
|
32
34
|
return Promise.resolve(InMemoryTable.Entity.mapChangeToEntity(change));
|
|
33
35
|
}
|
|
@@ -50,13 +52,13 @@ function loadById(loadManager, persistence, entityConfig, scope, indexerState, s
|
|
|
50
52
|
let entitiesMap = {};
|
|
51
53
|
for (let idx = 0, idx_finish = dbEntities.length; idx < idx_finish; ++idx) {
|
|
52
54
|
let entity = dbEntities[idx];
|
|
53
|
-
entitiesMap[entity.id] = entity;
|
|
55
|
+
entitiesMap[entityIdToKey(entity.id)] = entity;
|
|
54
56
|
}
|
|
55
57
|
let committedCheckpointId = IndexerState.committedCheckpointIdFor(indexerState, scope);
|
|
56
|
-
idsToLoad.forEach(entityId => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entityId, entitiesMap[entityId]));
|
|
58
|
+
idsToLoad.forEach(entityId => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entityId, entitiesMap[entityIdToKey(entityId)]));
|
|
57
59
|
return IndexerState.endStorageLoad(indexerState, timerRef, storageName, key, idsToLoad.length, dbEntities.length);
|
|
58
60
|
};
|
|
59
|
-
return LoadManager.call(loadManager, entityId, key, load,
|
|
61
|
+
return LoadManager.call(loadManager, entityId, key, load, entityIdToKey, shouldGroup, hash => hash in inMemTable.latestEntityChangeById, InMemoryTable.Entity.getUnsafe(inMemTable));
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
function callEffect(effect, arg, inMemTable, timerRef, onError) {
|
|
@@ -69,7 +71,7 @@ function executeWithRateLimit(effect, effectArgs, inMemTable, onError, isFromQue
|
|
|
69
71
|
let promises = [];
|
|
70
72
|
let state = inMemTable.rateLimitState;
|
|
71
73
|
if (state !== undefined) {
|
|
72
|
-
let now =
|
|
74
|
+
let now = state.clock.now();
|
|
73
75
|
if (now >= state.windowStartTime + state.durationMs) {
|
|
74
76
|
state.availableCalls = state.callsPerDuration;
|
|
75
77
|
state.windowStartTime = now;
|
|
@@ -98,7 +100,7 @@ function executeWithRateLimit(effect, effectArgs, inMemTable, onError, isFromQue
|
|
|
98
100
|
nextWindowPromise = p;
|
|
99
101
|
} else {
|
|
100
102
|
millisUntilReset.contents = state.windowStartTime + state.durationMs - now | 0;
|
|
101
|
-
let p$1 =
|
|
103
|
+
let p$1 = Clock.delay(state.clock, "effect rate limit window", millisUntilReset.contents);
|
|
102
104
|
state.nextWindowPromise = p$1;
|
|
103
105
|
nextWindowPromise = p$1;
|
|
104
106
|
}
|
package/src/Main.res.mjs
CHANGED
|
@@ -413,7 +413,7 @@ async function startIndexer(config, logger, persistence, resetOpt, isTestOpt, ex
|
|
|
413
413
|
Metrics.startRuntimeCollectors();
|
|
414
414
|
Server.startServer(logger, getMetrics, envioVersion, () => Persistence.getInitializedStorageOrThrow(persistence$1).dumpEffectCache().then(prim => {}), Metrics.collectRuntime, isDevelopmentMode);
|
|
415
415
|
}
|
|
416
|
-
let state = IndexerState.makeFromDbState(config$2, logger, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, Stdlib_Option.mapOr(Worker.config, false, worker => worker.holdRealtime), onError, undefined);
|
|
416
|
+
let state = IndexerState.makeFromDbState(config$2, logger, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, Stdlib_Option.mapOr(Worker.config, false, worker => worker.holdRealtime), onError, undefined, undefined);
|
|
417
417
|
if (shouldUseTui) {
|
|
418
418
|
Tui.start(config$2, logger, () => IndexerState.toMetrics(state));
|
|
419
419
|
}
|
package/src/Persistence.res
CHANGED
|
@@ -117,8 +117,6 @@ type storage = {
|
|
|
117
117
|
// carries a matching row — storage doesn't interpret it.
|
|
118
118
|
initialize: (
|
|
119
119
|
~chainConfigs: array<Config.chain>=?,
|
|
120
|
-
~entities: array<Internal.entityConfig>=?,
|
|
121
|
-
~enums: array<Table.enumConfig<Table.enum>>=?,
|
|
122
120
|
~contractMapping: ContractMapping.t,
|
|
123
121
|
~envioInfo: JSON.t,
|
|
124
122
|
) => promise<initialState>,
|
|
@@ -129,16 +127,11 @@ type storage = {
|
|
|
129
127
|
// Brings an initialized storage a chain it doesn't have yet: its row, its
|
|
130
128
|
// partitions and its config addresses, as `initialize` would have created
|
|
131
129
|
// them. The chains already there are left untouched.
|
|
132
|
-
addChain: (
|
|
133
|
-
~chainConfig: Config.chain,
|
|
134
|
-
~entities: array<Internal.entityConfig>,
|
|
135
|
-
~contractMapping: ContractMapping.t,
|
|
136
|
-
) => promise<unit>,
|
|
130
|
+
addChain: (~chainConfig: Config.chain, ~contractMapping: ContractMapping.t) => promise<unit>,
|
|
137
131
|
// `chainIds` is what this run drives. An isolated run resumes a subset of
|
|
138
132
|
// the stored chains, and ClickHouse's resume trims past each resumed chain's
|
|
139
133
|
// checkpoint, so chains a sibling process is still writing must stay out.
|
|
140
134
|
resumeInitialState: (
|
|
141
|
-
~entities: array<Internal.entityConfig>,
|
|
142
135
|
~chainIds: array<ChainId.t>,
|
|
143
136
|
~contractMapping: ContractMapping.t,
|
|
144
137
|
) => promise<initialState>,
|
|
@@ -159,19 +152,12 @@ type storage = {
|
|
|
159
152
|
// never runs `finalizeBackfill`: an index dropped or invalidated while it was
|
|
160
153
|
// down would otherwise never be rebuilt. Best-effort, and safe to run with
|
|
161
154
|
// indexing live.
|
|
162
|
-
ensureSchemaIndexes: (
|
|
163
|
-
~entities: array<Internal.entityConfig>,
|
|
164
|
-
~chainIds: array<ChainId.t>,
|
|
165
|
-
) => promise<unit>,
|
|
155
|
+
ensureSchemaIndexes: (~chainIds: array<ChainId.t>) => promise<unit>,
|
|
166
156
|
// Creates every index the schema promises for the given chains, then stamps
|
|
167
157
|
// `ready_at` on them. Called once those chains finish backfill. The indexes
|
|
168
158
|
// are committed one at a time so a failure part way through doesn't undo the
|
|
169
159
|
// ones already built; `ready_at` is only written once they all verify.
|
|
170
|
-
finalizeBackfill: (
|
|
171
|
-
~entities: array<Internal.entityConfig>,
|
|
172
|
-
~chainIds: array<ChainId.t>,
|
|
173
|
-
~readyAt: Date.t,
|
|
174
|
-
) => promise<unit>,
|
|
160
|
+
finalizeBackfill: (~chainIds: array<ChainId.t>, ~readyAt: Date.t) => promise<unit>,
|
|
175
161
|
// This is to download cache from the database to .envio/cache
|
|
176
162
|
dumpEffectCache: unit => promise<unit>,
|
|
177
163
|
reset: unit => promise<unit>,
|
|
@@ -190,7 +176,6 @@ type storage = {
|
|
|
190
176
|
getRollbackTargetCheckpoint: (
|
|
191
177
|
~reorgChainId: ChainId.t,
|
|
192
178
|
~lastKnownValidBlockNumber: int,
|
|
193
|
-
~sequence: CheckpointSequence.t,
|
|
194
179
|
) => promise<option<Internal.checkpointId>>,
|
|
195
180
|
// Get rollback progress diff
|
|
196
181
|
getRollbackProgressDiff: (
|
|
@@ -214,7 +199,6 @@ type storage = {
|
|
|
214
199
|
~batch: Batch.t,
|
|
215
200
|
~rollback: option<rollback>,
|
|
216
201
|
~config: Config.t,
|
|
217
|
-
~allEntities: array<Internal.entityConfig>,
|
|
218
202
|
~updatedEffectsCache: array<updatedEffectCache>,
|
|
219
203
|
~updatedEntities: array<updatedEntity>,
|
|
220
204
|
// Addresses this batch registered, with the checkpoint that covers them.
|
|
@@ -339,8 +323,6 @@ let init = {
|
|
|
339
323
|
throw(Refused(Config.setUpMeanwhileMessage))
|
|
340
324
|
}
|
|
341
325
|
let initialState = await persistence.storage.initialize(
|
|
342
|
-
~entities=persistence.allEntities,
|
|
343
|
-
~enums=persistence.allEnums,
|
|
344
326
|
~chainConfigs,
|
|
345
327
|
~contractMapping,
|
|
346
328
|
~envioInfo,
|
|
@@ -397,13 +379,11 @@ let init = {
|
|
|
397
379
|
)->Array.getUnsafe(0)
|
|
398
380
|
await persistence.storage.addChain(
|
|
399
381
|
~chainConfig,
|
|
400
|
-
~entities=persistence.allEntities,
|
|
401
382
|
~contractMapping=stored.contractMapping,
|
|
402
383
|
)
|
|
403
384
|
}
|
|
404
385
|
if resume {
|
|
405
386
|
let initialState = await persistence.storage.resumeInitialState(
|
|
406
|
-
~entities=persistence.allEntities,
|
|
407
387
|
~chainIds=chainConfigs->Array.map(chain => chain.id),
|
|
408
388
|
~contractMapping=stored.contractMapping,
|
|
409
389
|
)
|
package/src/Persistence.res.mjs
CHANGED
|
@@ -94,7 +94,7 @@ async function init(persistence, logger, chainConfigs, contractMapping, envioInf
|
|
|
94
94
|
Error: new Error()
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
-
let initialState = await persistence.storage.initialize(chainConfigs$1,
|
|
97
|
+
let initialState = await persistence.storage.initialize(chainConfigs$1, contractMapping, envioInfo);
|
|
98
98
|
Logger.info(logger, `The indexer storage is ready. Starting indexing!`, undefined);
|
|
99
99
|
persistence.storageStatus = {
|
|
100
100
|
TAG: "Ready",
|
|
@@ -136,11 +136,11 @@ async function init(persistence, logger, chainConfigs, contractMapping, envioInf
|
|
|
136
136
|
chainId: chainConfig.id
|
|
137
137
|
});
|
|
138
138
|
let chainConfig$1 = (await StartBlockResolver.resolveAllOrThrow([chainConfig], logger, lowercaseAddresses, startBlockRetry, sourceTiming))[0];
|
|
139
|
-
await persistence.storage.addChain(chainConfig$1,
|
|
139
|
+
await persistence.storage.addChain(chainConfig$1, stored.contractMapping);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
if (resume) {
|
|
143
|
-
let initialState$1 = await persistence.storage.resumeInitialState(
|
|
143
|
+
let initialState$1 = await persistence.storage.resumeInitialState(chainConfigs.map(chain => chain.id), stored.contractMapping);
|
|
144
144
|
persistence.storageStatus = {
|
|
145
145
|
TAG: "Ready",
|
|
146
146
|
_0: initialState$1
|
package/src/Rollback.res
CHANGED
|
@@ -130,7 +130,6 @@ and executeRollback = async (
|
|
|
130
130
|
switch await (state->IndexerState.persistence).storage.getRollbackTargetCheckpoint(
|
|
131
131
|
~reorgChainId=reorgChain,
|
|
132
132
|
~lastKnownValidBlockNumber=rollbackTargetBlockNumber,
|
|
133
|
-
~sequence=(state->IndexerState.config).checkpointSequence,
|
|
134
133
|
) {
|
|
135
134
|
| Some(checkpointId) => checkpointId
|
|
136
135
|
| None => 0n
|
package/src/Rollback.res.mjs
CHANGED
|
@@ -42,7 +42,7 @@ async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, sch
|
|
|
42
42
|
Logger.info(logger, "Started rollback on reorg", undefined);
|
|
43
43
|
ChainState.setRollbackTargetBlock(IndexerState.getChainState(state, reorgChain), rollbackTargetBlockNumber);
|
|
44
44
|
await Writing.flush(state);
|
|
45
|
-
let checkpointId = await IndexerState.persistence(state).storage.getRollbackTargetCheckpoint(reorgChain, rollbackTargetBlockNumber
|
|
45
|
+
let checkpointId = await IndexerState.persistence(state).storage.getRollbackTargetCheckpoint(reorgChain, rollbackTargetBlockNumber);
|
|
46
46
|
let rollbackTargetCheckpointId = checkpointId !== undefined ? checkpointId : 0n;
|
|
47
47
|
let config = IndexerState.config(state);
|
|
48
48
|
let next = RollbackFloors.make(config.checkpointSequence, ChainMap.keys(config.chainMap), reorgChain, rollbackTargetCheckpointId, rollbackTargetBlockNumber);
|
|
@@ -16,9 +16,7 @@ let make = (
|
|
|
16
16
|
) => {
|
|
17
17
|
if maxReorgDepth > 0 && shouldRollbackOnReorg {
|
|
18
18
|
let checkpointIds = Utils.Array.jsArrayCreate(chainReorgCheckpoints->Array.length)
|
|
19
|
-
let checkpointBlockNumbers = Utils.Array.jsArrayCreate(
|
|
20
|
-
chainReorgCheckpoints->Array.length,
|
|
21
|
-
)
|
|
19
|
+
let checkpointBlockNumbers = Utils.Array.jsArrayCreate(chainReorgCheckpoints->Array.length)
|
|
22
20
|
chainReorgCheckpoints->Array.forEachWithIndex((checkpoint, idx) => {
|
|
23
21
|
checkpointIds->Array.setUnsafe(idx, checkpoint.checkpointId)
|
|
24
22
|
checkpointBlockNumbers->Array.setUnsafe(idx, checkpoint.blockNumber)
|
|
@@ -36,7 +34,9 @@ let make = (
|
|
|
36
34
|
let getSafeCheckpointId = (safeCheckpointTracking: t, ~sourceBlockNumber: int): option<
|
|
37
35
|
Internal.checkpointId,
|
|
38
36
|
> => {
|
|
39
|
-
|
|
37
|
+
// The first block reorg detection compares is itself reorgable, so the safe
|
|
38
|
+
// block is the one below it.
|
|
39
|
+
let safeBlockNumber = sourceBlockNumber - safeCheckpointTracking.maxReorgDepth - 1
|
|
40
40
|
|
|
41
41
|
switch safeCheckpointTracking.checkpointIds {
|
|
42
42
|
| [] => None
|
|
@@ -20,7 +20,7 @@ function make(maxReorgDepth, shouldRollbackOnReorg, chainReorgCheckpoints) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function getSafeCheckpointId(safeCheckpointTracking, sourceBlockNumber) {
|
|
23
|
-
let safeBlockNumber = sourceBlockNumber - safeCheckpointTracking.maxReorgDepth | 0;
|
|
23
|
+
let safeBlockNumber = (sourceBlockNumber - safeCheckpointTracking.maxReorgDepth | 0) - 1 | 0;
|
|
24
24
|
let match = safeCheckpointTracking.checkpointIds;
|
|
25
25
|
if (match.length === 0) {
|
|
26
26
|
return;
|