envio 3.2.1 → 3.3.0-alpha.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/package.json +6 -7
- package/src/Batch.res +12 -15
- package/src/Batch.res.mjs +4 -5
- package/src/BatchProcessing.res +199 -0
- package/src/BatchProcessing.res.mjs +125 -0
- package/src/ChainFetching.res +375 -0
- package/src/ChainFetching.res.mjs +206 -0
- package/src/ChainMetadata.res +27 -0
- package/src/ChainMetadata.res.mjs +27 -0
- package/src/ChainState.res +618 -0
- package/src/ChainState.res.mjs +461 -0
- package/src/ChainState.resi +85 -0
- package/src/Config.res +13 -4
- package/src/Config.res.mjs +8 -3
- package/src/ContractRegisterContext.res +106 -0
- package/src/ContractRegisterContext.res.mjs +76 -0
- package/src/Core.res +3 -0
- package/src/CrossChainState.res +269 -0
- package/src/CrossChainState.res.mjs +197 -0
- package/src/CrossChainState.resi +47 -0
- package/src/Ecosystem.res +58 -0
- package/src/Ecosystem.res.mjs +43 -1
- package/src/Env.res +7 -2
- package/src/Env.res.mjs +5 -2
- package/src/EventConfigBuilder.res +2 -2
- package/src/EventProcessing.res +60 -46
- package/src/EventProcessing.res.mjs +33 -32
- package/src/EventUtils.res +0 -4
- package/src/EventUtils.res.mjs +0 -4
- package/src/ExitOnCaughtUp.res +10 -0
- package/src/ExitOnCaughtUp.res.mjs +22 -0
- package/src/FetchState.res +124 -62
- package/src/FetchState.res.mjs +159 -93
- package/src/InMemoryStore.res +36 -451
- package/src/InMemoryStore.res.mjs +20 -369
- package/src/IndexerLoop.res +44 -0
- package/src/IndexerLoop.res.mjs +46 -0
- package/src/IndexerState.res +544 -0
- package/src/IndexerState.res.mjs +547 -0
- package/src/IndexerState.resi +132 -0
- package/src/Internal.res +29 -8
- package/src/LoadLayer.res +20 -18
- package/src/LoadLayer.res.mjs +14 -12
- package/src/LoadLayer.resi +6 -3
- package/src/Logging.res +11 -44
- package/src/Logging.res.mjs +10 -42
- package/src/Main.res +54 -79
- package/src/Main.res.mjs +44 -56
- package/src/PgStorage.res +8 -71
- package/src/PgStorage.res.mjs +3 -47
- package/src/Prometheus.res +7 -8
- package/src/Prometheus.res.mjs +7 -4
- package/src/PruneStaleHistory.res +45 -0
- package/src/PruneStaleHistory.res.mjs +46 -0
- package/src/RawEvent.res +73 -0
- package/src/RawEvent.res.mjs +51 -0
- package/src/Rollback.res +204 -0
- package/src/Rollback.res.mjs +118 -0
- package/src/SimulateItems.res +12 -10
- package/src/SimulateItems.res.mjs +1 -1
- package/src/UserContext.res +26 -114
- package/src/UserContext.res.mjs +15 -78
- package/src/Writing.res +242 -0
- package/src/Writing.res.mjs +215 -0
- package/src/db/InternalTable.res +14 -27
- package/src/sources/Evm.res +40 -1
- package/src/sources/Evm.res.mjs +94 -84
- package/src/sources/Fuel.res +40 -1
- package/src/sources/Fuel.res.mjs +36 -26
- package/src/sources/HyperFuel.res +41 -120
- package/src/sources/HyperFuel.res.mjs +42 -80
- package/src/sources/HyperFuel.resi +1 -24
- package/src/sources/HyperFuelClient.res +16 -297
- package/src/sources/HyperFuelClient.res.mjs +5 -4
- package/src/sources/HyperFuelSource.res +33 -8
- package/src/sources/HyperFuelSource.res.mjs +56 -10
- package/src/sources/HyperSyncSource.res +5 -3
- package/src/sources/HyperSyncSource.res.mjs +1 -1
- package/src/sources/RpcSource.res +2 -2
- package/src/sources/RpcSource.res.mjs +1 -1
- package/src/sources/SourceManager.res +8 -18
- package/src/sources/SourceManager.res.mjs +8 -5
- package/src/sources/SourceManager.resi +4 -1
- package/src/sources/Svm.res +18 -1
- package/src/sources/Svm.res.mjs +30 -22
- package/src/sources/SvmHyperSyncSource.res +1 -1
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/src/tui/Tui.res +38 -36
- package/src/tui/Tui.res.mjs +51 -51
- package/src/ChainFetcher.res +0 -519
- package/src/ChainFetcher.res.mjs +0 -314
- package/src/ChainManager.res +0 -358
- package/src/ChainManager.res.mjs +0 -291
- package/src/Ctx.res +0 -6
- package/src/Ctx.res.mjs +0 -2
- package/src/GlobalState.res +0 -1056
- package/src/GlobalState.res.mjs +0 -1086
- package/src/GlobalStateManager.res +0 -57
- package/src/GlobalStateManager.res.mjs +0 -68
- package/src/GlobalStateManager.resi +0 -7
- package/src/Ports.res +0 -5
- package/src/Ports.res.mjs +0 -9
- package/src/adapters/MarkBatchProcessedAdapter.res +0 -5
- package/src/adapters/MarkBatchProcessedAdapter.res.mjs +0 -14
- package/src/sources/EnvioApiClient.res +0 -15
- package/src/sources/EnvioApiClient.res.mjs +0 -24
package/src/GlobalState.res
DELETED
|
@@ -1,1056 +0,0 @@
|
|
|
1
|
-
type chain = ChainMap.Chain.t
|
|
2
|
-
type rollbackState =
|
|
3
|
-
| NoRollback
|
|
4
|
-
| ReorgDetected({chain: chain, blockNumber: int})
|
|
5
|
-
| FindingReorgDepth
|
|
6
|
-
| FoundReorgDepth({chain: chain, rollbackTargetBlockNumber: int})
|
|
7
|
-
| RollbackReady({eventsProcessedDiffByChain: dict<float>})
|
|
8
|
-
|
|
9
|
-
module WriteThrottlers = {
|
|
10
|
-
type t = {pruneStaleEntityHistory: Throttler.t}
|
|
11
|
-
let make = (): t => {
|
|
12
|
-
let pruneStaleEntityHistory = {
|
|
13
|
-
let intervalMillis = Env.ThrottleWrites.pruneStaleDataIntervalMillis
|
|
14
|
-
let logger = Logging.createChild(
|
|
15
|
-
~params={
|
|
16
|
-
"context": "Throttler for pruning stale entity history data",
|
|
17
|
-
"intervalMillis": intervalMillis,
|
|
18
|
-
},
|
|
19
|
-
)
|
|
20
|
-
Throttler.make(~intervalMillis, ~logger)
|
|
21
|
-
}
|
|
22
|
-
{pruneStaleEntityHistory: pruneStaleEntityHistory}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type t = {
|
|
27
|
-
ctx: Ctx.t,
|
|
28
|
-
chainManager: ChainManager.t,
|
|
29
|
-
rollbackState: rollbackState,
|
|
30
|
-
indexerStartTime: Date.t,
|
|
31
|
-
writeThrottlers: WriteThrottlers.t,
|
|
32
|
-
loadManager: LoadManager.t,
|
|
33
|
-
keepProcessAlive: bool,
|
|
34
|
-
exitAfterFirstEventBlock: bool,
|
|
35
|
-
// The single fatal-error handler ErrorExit delegates to.
|
|
36
|
-
onError: ErrorHandling.t => unit,
|
|
37
|
-
//Initialized as 0, increments, when rollbacks occur to invalidate
|
|
38
|
-
//responses based on the wrong stateId
|
|
39
|
-
id: int,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let make = (
|
|
43
|
-
~ctx: Ctx.t,
|
|
44
|
-
~chainManager: ChainManager.t,
|
|
45
|
-
~isDevelopmentMode=false,
|
|
46
|
-
~shouldUseTui=false,
|
|
47
|
-
~exitAfterFirstEventBlock=false,
|
|
48
|
-
~onError: ErrorHandling.t => unit,
|
|
49
|
-
) => {
|
|
50
|
-
{
|
|
51
|
-
ctx,
|
|
52
|
-
chainManager,
|
|
53
|
-
indexerStartTime: Date.make(),
|
|
54
|
-
rollbackState: NoRollback,
|
|
55
|
-
writeThrottlers: WriteThrottlers.make(),
|
|
56
|
-
loadManager: LoadManager.make(),
|
|
57
|
-
keepProcessAlive: isDevelopmentMode || shouldUseTui,
|
|
58
|
-
exitAfterFirstEventBlock,
|
|
59
|
-
onError,
|
|
60
|
-
id: 0,
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let getId = self => self.id
|
|
65
|
-
let setChainManager = (self, chainManager) => {
|
|
66
|
-
...self,
|
|
67
|
-
chainManager,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let isPreparingRollback = state =>
|
|
71
|
-
switch state.rollbackState {
|
|
72
|
-
| NoRollback
|
|
73
|
-
| // We already updated fetch states here
|
|
74
|
-
// so we treat it as not rolling back
|
|
75
|
-
RollbackReady(_) => false
|
|
76
|
-
| FindingReorgDepth
|
|
77
|
-
| ReorgDetected(_)
|
|
78
|
-
| FoundReorgDepth(_) => true
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
type partitionQueryResponse = {
|
|
82
|
-
chain: chain,
|
|
83
|
-
response: Source.blockRangeFetchResponse,
|
|
84
|
-
query: FetchState.query,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
type shouldExit = ExitWithSuccess | ExitWithError(string) | NoExit
|
|
88
|
-
|
|
89
|
-
// Need to dispatch an action for every async operation
|
|
90
|
-
// to get access to the latest state.
|
|
91
|
-
type action =
|
|
92
|
-
// After a response is received, we validate it with the new state
|
|
93
|
-
// if there's no reorg to continue processing the response.
|
|
94
|
-
| ValidatePartitionQueryResponse(partitionQueryResponse)
|
|
95
|
-
// This should be a separate action from ValidatePartitionQueryResponse
|
|
96
|
-
// because when processing the response, there might be an async contract registration.
|
|
97
|
-
// So after it's finished we dispatch the submit action to get the latest fetch state.
|
|
98
|
-
| SubmitPartitionQueryResponse({
|
|
99
|
-
newItems: array<Internal.item>,
|
|
100
|
-
newItemsWithDcs: array<Internal.item>,
|
|
101
|
-
knownHeight: int,
|
|
102
|
-
latestFetchedBlock: FetchState.blockNumberAndTimestamp,
|
|
103
|
-
query: FetchState.query,
|
|
104
|
-
chain: chain,
|
|
105
|
-
})
|
|
106
|
-
| FinishWaitingForNewBlock({chain: chain, knownHeight: int})
|
|
107
|
-
| EventBatchProcessed({batch: Batch.t})
|
|
108
|
-
| StartFindingReorgDepth
|
|
109
|
-
| FindReorgDepth({chain: chain, rollbackTargetBlockNumber: int})
|
|
110
|
-
| EnterReorgThreshold
|
|
111
|
-
| UpdateQueues({
|
|
112
|
-
progressedChainsById: dict<Batch.chainAfterBatch>,
|
|
113
|
-
// Needed to prevent overwriting the blockLag
|
|
114
|
-
// set by EnterReorgThreshold
|
|
115
|
-
shouldEnterReorgThreshold: bool,
|
|
116
|
-
})
|
|
117
|
-
| SuccessExit
|
|
118
|
-
| ErrorExit(ErrorHandling.t)
|
|
119
|
-
| SetRollbackState({
|
|
120
|
-
rollbackedChainManager: ChainManager.t,
|
|
121
|
-
eventsProcessedDiffByChain: dict<float>,
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
type queryChain = CheckAllChains | Chain(chain)
|
|
125
|
-
type task =
|
|
126
|
-
| NextQuery(queryChain)
|
|
127
|
-
| ProcessPartitionQueryResponse(partitionQueryResponse)
|
|
128
|
-
| ProcessEventBatch
|
|
129
|
-
| UpdateChainMetaDataAndCheckForExit(shouldExit)
|
|
130
|
-
| Rollback
|
|
131
|
-
| PruneStaleEntityHistory
|
|
132
|
-
|
|
133
|
-
let updateChainMetadataTable = (cm: ChainManager.t, ~inMemoryStore: InMemoryStore.t) => {
|
|
134
|
-
let chainsData: dict<InternalTable.Chains.metaFields> = Dict.make()
|
|
135
|
-
|
|
136
|
-
cm.chainFetchers
|
|
137
|
-
->ChainMap.values
|
|
138
|
-
->Array.forEach(cf => {
|
|
139
|
-
chainsData->Dict.set(
|
|
140
|
-
cf.chainConfig.id->Int.toString,
|
|
141
|
-
{
|
|
142
|
-
firstEventBlockNumber: cf.fetchState.firstEventBlock->Null.fromOption,
|
|
143
|
-
isHyperSync: (cf.sourceManager->SourceManager.getActiveSource).poweredByHyperSync,
|
|
144
|
-
latestFetchedBlockNumber: cf.fetchState->FetchState.bufferBlockNumber,
|
|
145
|
-
timestampCaughtUpToHeadOrEndblock: cf.timestampCaughtUpToHeadOrEndblock->Null.fromOption,
|
|
146
|
-
},
|
|
147
|
-
)
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
// Staged; the cycle folds the stale diff into the next batch write.
|
|
151
|
-
inMemoryStore->InMemoryStore.setChainMeta(chainsData)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
let validatePartitionQueryResponse = (
|
|
155
|
-
state,
|
|
156
|
-
{chain, response} as partitionQueryResponse: partitionQueryResponse,
|
|
157
|
-
) => {
|
|
158
|
-
let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
|
|
159
|
-
let {
|
|
160
|
-
parsedQueueItems,
|
|
161
|
-
latestFetchedBlockNumber,
|
|
162
|
-
stats,
|
|
163
|
-
knownHeight,
|
|
164
|
-
blockHashes,
|
|
165
|
-
fromBlockQueried,
|
|
166
|
-
} = response
|
|
167
|
-
|
|
168
|
-
if knownHeight > chainFetcher.fetchState.knownHeight {
|
|
169
|
-
Prometheus.SourceHeight.set(
|
|
170
|
-
~blockNumber=knownHeight,
|
|
171
|
-
~chainId=chainFetcher.chainConfig.id,
|
|
172
|
-
// The knownHeight from response won't necessarily
|
|
173
|
-
// belong to the currently active source.
|
|
174
|
-
// But for simplicity, assume it does.
|
|
175
|
-
~sourceName=(chainFetcher.sourceManager->SourceManager.getActiveSource).name,
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
Prometheus.FetchingBlockRange.increment(
|
|
180
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
181
|
-
~totalTimeElapsed=stats.totalTimeElapsed,
|
|
182
|
-
~parsingTimeElapsed=stats.parsingTimeElapsed->Option.getOr(0.),
|
|
183
|
-
~numEvents=parsedQueueItems->Array.length,
|
|
184
|
-
~blockRangeSize=latestFetchedBlockNumber - fromBlockQueried + 1,
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
let (updatedReorgDetection, reorgResult: ReorgDetection.reorgResult) =
|
|
188
|
-
chainFetcher.reorgDetection->ReorgDetection.registerReorgGuard(~blockHashes, ~knownHeight)
|
|
189
|
-
|
|
190
|
-
let updatedChainFetcher = {
|
|
191
|
-
...chainFetcher,
|
|
192
|
-
reorgDetection: updatedReorgDetection,
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
let nextState = {
|
|
196
|
-
...state,
|
|
197
|
-
chainManager: {
|
|
198
|
-
...state.chainManager,
|
|
199
|
-
chainFetchers: state.chainManager.chainFetchers->ChainMap.set(chain, updatedChainFetcher),
|
|
200
|
-
},
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
let rollbackWithReorgDetectedBlockNumber = switch reorgResult {
|
|
204
|
-
| ReorgDetected(reorgDetected) => {
|
|
205
|
-
chainFetcher.logger->Logging.childInfo(
|
|
206
|
-
reorgDetected->ReorgDetection.reorgDetectedToLogParams(
|
|
207
|
-
~shouldRollbackOnReorg=state.ctx.config.shouldRollbackOnReorg,
|
|
208
|
-
),
|
|
209
|
-
)
|
|
210
|
-
Prometheus.ReorgCount.increment(~chain)
|
|
211
|
-
Prometheus.ReorgDetectionBlockNumber.set(
|
|
212
|
-
~blockNumber=reorgDetected.scannedBlock.blockNumber,
|
|
213
|
-
~chain,
|
|
214
|
-
)
|
|
215
|
-
if state.ctx.config.shouldRollbackOnReorg {
|
|
216
|
-
Some(reorgDetected.scannedBlock.blockNumber)
|
|
217
|
-
} else {
|
|
218
|
-
None
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
| NoReorg => None
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
switch rollbackWithReorgDetectedBlockNumber {
|
|
225
|
-
| None => (nextState, [ProcessPartitionQueryResponse(partitionQueryResponse)])
|
|
226
|
-
| Some(reorgDetectedBlockNumber) => {
|
|
227
|
-
let chainManager = switch state.rollbackState {
|
|
228
|
-
| RollbackReady({eventsProcessedDiffByChain}) => {
|
|
229
|
-
...state.chainManager,
|
|
230
|
-
// Restore event counters for ALL chains, not just the reorg chain.
|
|
231
|
-
// The previous rollback subtracted from all chains' counters,
|
|
232
|
-
// but was never committed to DB. So we must undo the subtraction
|
|
233
|
-
// for every chain before the new rollback subtracts again.
|
|
234
|
-
chainFetchers: state.chainManager.chainFetchers->ChainMap.mapWithKey((
|
|
235
|
-
c,
|
|
236
|
-
chainFetcher,
|
|
237
|
-
) => {
|
|
238
|
-
switch eventsProcessedDiffByChain->Utils.Dict.dangerouslyGetByIntNonOption(
|
|
239
|
-
c->ChainMap.Chain.toChainId,
|
|
240
|
-
) {
|
|
241
|
-
| Some(eventsProcessedDiff) => {
|
|
242
|
-
...chainFetcher,
|
|
243
|
-
// Since we detected a reorg, until rollback wasn't completed in the db
|
|
244
|
-
// We return the events processed counter to the pre-rollback value,
|
|
245
|
-
// to decrease it once more for the new rollback.
|
|
246
|
-
numEventsProcessed: chainFetcher.numEventsProcessed +. eventsProcessedDiff,
|
|
247
|
-
}
|
|
248
|
-
| None => chainFetcher
|
|
249
|
-
}
|
|
250
|
-
}),
|
|
251
|
-
}
|
|
252
|
-
| _ => state.chainManager
|
|
253
|
-
}
|
|
254
|
-
(
|
|
255
|
-
{
|
|
256
|
-
...nextState,
|
|
257
|
-
id: nextState.id + 1,
|
|
258
|
-
chainManager: {
|
|
259
|
-
...chainManager,
|
|
260
|
-
chainFetchers: chainManager.chainFetchers->ChainMap.map(chainFetcher => {
|
|
261
|
-
...chainFetcher,
|
|
262
|
-
// TODO: It's not optimal to abort pending queries for all chains,
|
|
263
|
-
// this is how it always worked, but we should consider a better approach.
|
|
264
|
-
fetchState: chainFetcher.fetchState->FetchState.resetPendingQueries,
|
|
265
|
-
}),
|
|
266
|
-
},
|
|
267
|
-
rollbackState: ReorgDetected({
|
|
268
|
-
chain,
|
|
269
|
-
blockNumber: reorgDetectedBlockNumber,
|
|
270
|
-
}),
|
|
271
|
-
},
|
|
272
|
-
[Rollback],
|
|
273
|
-
)
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
let submitPartitionQueryResponse = (
|
|
279
|
-
state,
|
|
280
|
-
~newItems,
|
|
281
|
-
~newItemsWithDcs,
|
|
282
|
-
~knownHeight,
|
|
283
|
-
~latestFetchedBlock,
|
|
284
|
-
~query,
|
|
285
|
-
~chain,
|
|
286
|
-
) => {
|
|
287
|
-
let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
|
|
288
|
-
|
|
289
|
-
let updatedChainFetcher =
|
|
290
|
-
chainFetcher->ChainFetcher.handleQueryResult(
|
|
291
|
-
~query,
|
|
292
|
-
~latestFetchedBlock,
|
|
293
|
-
~newItems,
|
|
294
|
-
~newItemsWithDcs,
|
|
295
|
-
~knownHeight,
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
// In auto-exit mode, set endBlock to the first event's block when events arrive.
|
|
299
|
-
// Also update if a partition returns events at an earlier block than current endBlock.
|
|
300
|
-
let updatedChainFetcher = if state.exitAfterFirstEventBlock && newItems->Array.length > 0 {
|
|
301
|
-
let firstEventBlock = newItems->Array.getUnsafe(0)->Internal.getItemBlockNumber
|
|
302
|
-
switch updatedChainFetcher.fetchState.endBlock {
|
|
303
|
-
| None => {
|
|
304
|
-
...updatedChainFetcher,
|
|
305
|
-
fetchState: {...updatedChainFetcher.fetchState, endBlock: Some(firstEventBlock)},
|
|
306
|
-
}
|
|
307
|
-
| Some(currentEndBlock) if firstEventBlock < currentEndBlock => {
|
|
308
|
-
...updatedChainFetcher,
|
|
309
|
-
fetchState: {...updatedChainFetcher.fetchState, endBlock: Some(firstEventBlock)},
|
|
310
|
-
}
|
|
311
|
-
| Some(_) => updatedChainFetcher
|
|
312
|
-
}
|
|
313
|
-
} else {
|
|
314
|
-
updatedChainFetcher
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if !chainFetcher.isProgressAtHead && updatedChainFetcher.isProgressAtHead {
|
|
318
|
-
updatedChainFetcher.logger->Logging.childInfo("All events have been fetched")
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
let nextState = {
|
|
322
|
-
...state,
|
|
323
|
-
chainManager: {
|
|
324
|
-
...state.chainManager,
|
|
325
|
-
chainFetchers: state.chainManager.chainFetchers->ChainMap.set(chain, updatedChainFetcher),
|
|
326
|
-
},
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
(
|
|
330
|
-
nextState,
|
|
331
|
-
[UpdateChainMetaDataAndCheckForExit(NoExit), ProcessEventBatch, NextQuery(Chain(chain))],
|
|
332
|
-
)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
let processPartitionQueryResponse = async (
|
|
336
|
-
state,
|
|
337
|
-
{chain, response, query}: partitionQueryResponse,
|
|
338
|
-
~dispatchAction,
|
|
339
|
-
) => {
|
|
340
|
-
let {
|
|
341
|
-
parsedQueueItems,
|
|
342
|
-
latestFetchedBlockNumber,
|
|
343
|
-
knownHeight,
|
|
344
|
-
latestFetchedBlockTimestamp,
|
|
345
|
-
} = response
|
|
346
|
-
|
|
347
|
-
let itemsWithContractRegister = []
|
|
348
|
-
let newItems = []
|
|
349
|
-
|
|
350
|
-
for idx in 0 to parsedQueueItems->Array.length - 1 {
|
|
351
|
-
let item = parsedQueueItems->Array.getUnsafe(idx)
|
|
352
|
-
let eventItem = item->Internal.castUnsafeEventItem
|
|
353
|
-
if eventItem.eventConfig.contractRegister !== None {
|
|
354
|
-
itemsWithContractRegister->Array.push(item)
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// TODO: Don't really need to keep it in the queue
|
|
358
|
-
// when there's no handler (besides raw_events, processed counter, and dcsToStore consuming)
|
|
359
|
-
newItems->Array.push(item)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
let newItemsWithDcs = switch itemsWithContractRegister {
|
|
363
|
-
| [] as empty => empty
|
|
364
|
-
| _ =>
|
|
365
|
-
await ChainFetcher.runContractRegistersOrThrow(
|
|
366
|
-
~itemsWithContractRegister,
|
|
367
|
-
~config=state.ctx.config,
|
|
368
|
-
)
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
dispatchAction(
|
|
372
|
-
SubmitPartitionQueryResponse({
|
|
373
|
-
newItems,
|
|
374
|
-
newItemsWithDcs,
|
|
375
|
-
knownHeight,
|
|
376
|
-
latestFetchedBlock: {
|
|
377
|
-
blockNumber: latestFetchedBlockNumber,
|
|
378
|
-
blockTimestamp: latestFetchedBlockTimestamp,
|
|
379
|
-
},
|
|
380
|
-
chain,
|
|
381
|
-
query,
|
|
382
|
-
}),
|
|
383
|
-
)
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
let updateChainFetcher = (chainFetcherUpdate, ~state, ~chain) => {
|
|
387
|
-
(
|
|
388
|
-
{
|
|
389
|
-
...state,
|
|
390
|
-
chainManager: {
|
|
391
|
-
...state.chainManager,
|
|
392
|
-
chainFetchers: state.chainManager.chainFetchers->ChainMap.update(chain, chainFetcherUpdate),
|
|
393
|
-
},
|
|
394
|
-
},
|
|
395
|
-
[],
|
|
396
|
-
)
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
let onEnterReorgThreshold = (~state: t) => {
|
|
400
|
-
Logging.info("Reorg threshold reached")
|
|
401
|
-
Prometheus.ReorgThreshold.set(~isInReorgThreshold=true)
|
|
402
|
-
|
|
403
|
-
let chainFetchers = state.chainManager.chainFetchers->ChainMap.map(chainFetcher => {
|
|
404
|
-
{
|
|
405
|
-
...chainFetcher,
|
|
406
|
-
fetchState: chainFetcher.fetchState->FetchState.updateInternal(
|
|
407
|
-
~blockLag=chainFetcher.chainConfig.blockLag,
|
|
408
|
-
),
|
|
409
|
-
}
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
{
|
|
413
|
-
...state,
|
|
414
|
-
chainManager: {
|
|
415
|
-
...state.chainManager,
|
|
416
|
-
chainFetchers,
|
|
417
|
-
isInReorgThreshold: true,
|
|
418
|
-
},
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
let injectedActionReducer = (~markBatchProcessed: Ports.MarkBatchProcessed.t) =>
|
|
423
|
-
(state: t, action: action) => {
|
|
424
|
-
switch action {
|
|
425
|
-
| FinishWaitingForNewBlock({chain, knownHeight}) => {
|
|
426
|
-
let updatedChainFetchers = state.chainManager.chainFetchers->ChainMap.update(
|
|
427
|
-
chain,
|
|
428
|
-
chainFetcher => {
|
|
429
|
-
let updatedFetchState =
|
|
430
|
-
chainFetcher.fetchState->FetchState.updateKnownHeight(~knownHeight)
|
|
431
|
-
if updatedFetchState !== chainFetcher.fetchState {
|
|
432
|
-
{
|
|
433
|
-
...chainFetcher,
|
|
434
|
-
fetchState: updatedFetchState,
|
|
435
|
-
}
|
|
436
|
-
} else {
|
|
437
|
-
chainFetcher
|
|
438
|
-
}
|
|
439
|
-
},
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
let isBelowReorgThreshold =
|
|
443
|
-
!state.chainManager.isInReorgThreshold && state.ctx.config.shouldRollbackOnReorg
|
|
444
|
-
let shouldEnterReorgThreshold =
|
|
445
|
-
isBelowReorgThreshold &&
|
|
446
|
-
updatedChainFetchers
|
|
447
|
-
->ChainMap.values
|
|
448
|
-
->Array.every(chainFetcher => {
|
|
449
|
-
chainFetcher.fetchState->FetchState.isReadyToEnterReorgThreshold
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
let state = {
|
|
453
|
-
...state,
|
|
454
|
-
chainManager: {
|
|
455
|
-
...state.chainManager,
|
|
456
|
-
chainFetchers: updatedChainFetchers,
|
|
457
|
-
},
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Attempt ProcessEventBatch in case if we have block handlers to run
|
|
461
|
-
if shouldEnterReorgThreshold {
|
|
462
|
-
(onEnterReorgThreshold(~state), [NextQuery(CheckAllChains), ProcessEventBatch])
|
|
463
|
-
} else {
|
|
464
|
-
(state, [NextQuery(Chain(chain)), ProcessEventBatch])
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
| ValidatePartitionQueryResponse(partitionQueryResponse) =>
|
|
468
|
-
state->validatePartitionQueryResponse(partitionQueryResponse)
|
|
469
|
-
| SubmitPartitionQueryResponse({
|
|
470
|
-
newItems,
|
|
471
|
-
newItemsWithDcs,
|
|
472
|
-
knownHeight,
|
|
473
|
-
latestFetchedBlock,
|
|
474
|
-
query,
|
|
475
|
-
chain,
|
|
476
|
-
}) =>
|
|
477
|
-
state->submitPartitionQueryResponse(
|
|
478
|
-
~newItems,
|
|
479
|
-
~newItemsWithDcs,
|
|
480
|
-
~knownHeight,
|
|
481
|
-
~latestFetchedBlock,
|
|
482
|
-
~query,
|
|
483
|
-
~chain,
|
|
484
|
-
)
|
|
485
|
-
| EventBatchProcessed({batch}) =>
|
|
486
|
-
let maybePruneEntityHistory =
|
|
487
|
-
state.ctx.config->Config.shouldPruneHistory(
|
|
488
|
-
~isInReorgThreshold=state.chainManager.isInReorgThreshold,
|
|
489
|
-
)
|
|
490
|
-
? [PruneStaleEntityHistory]
|
|
491
|
-
: []
|
|
492
|
-
|
|
493
|
-
markBatchProcessed()
|
|
494
|
-
|
|
495
|
-
let state = {
|
|
496
|
-
...state,
|
|
497
|
-
// Can safely reset rollback state, since overwrite is not possible.
|
|
498
|
-
// If rollback is pending, the EventBatchProcessed will be handled by the invalid action reducer instead.
|
|
499
|
-
rollbackState: NoRollback,
|
|
500
|
-
chainManager: state.chainManager->ChainManager.updateProgressedChains(~batch),
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
let shouldExit = EventProcessing.allChainsEventsProcessedToEndblock(
|
|
504
|
-
state.chainManager.chainFetchers,
|
|
505
|
-
)
|
|
506
|
-
? {
|
|
507
|
-
Logging.info("All chains are caught up to end blocks.")
|
|
508
|
-
|
|
509
|
-
// Keep the indexer process running when in development mode (for Dev Console)
|
|
510
|
-
// or when TUI is enabled (for display)
|
|
511
|
-
if state.keepProcessAlive {
|
|
512
|
-
NoExit
|
|
513
|
-
} else {
|
|
514
|
-
ExitWithSuccess
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
: if (
|
|
518
|
-
// In auto-exit mode, error if all chains reached head with no events found
|
|
519
|
-
state.exitAfterFirstEventBlock &&
|
|
520
|
-
state.chainManager.chainFetchers
|
|
521
|
-
->ChainMap.values
|
|
522
|
-
->Array.every(cf => cf.isProgressAtHead && cf.fetchState.endBlock->Option.isNone)
|
|
523
|
-
) {
|
|
524
|
-
ExitWithError(
|
|
525
|
-
"No events found between startBlock and chain head. Cannot auto-detect endBlock.",
|
|
526
|
-
)
|
|
527
|
-
} else {
|
|
528
|
-
NoExit
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// On exit, stop dispatching ProcessEventBatch: the flush is async and would
|
|
532
|
-
// otherwise keep processing further batches while it runs.
|
|
533
|
-
let tasks = switch shouldExit {
|
|
534
|
-
| ExitWithSuccess
|
|
535
|
-
| ExitWithError(_) => [UpdateChainMetaDataAndCheckForExit(shouldExit)]
|
|
536
|
-
| NoExit =>
|
|
537
|
-
[UpdateChainMetaDataAndCheckForExit(shouldExit), ProcessEventBatch]->Array.concat(
|
|
538
|
-
maybePruneEntityHistory,
|
|
539
|
-
)
|
|
540
|
-
}
|
|
541
|
-
(state, tasks)
|
|
542
|
-
|
|
543
|
-
| StartFindingReorgDepth => ({...state, rollbackState: FindingReorgDepth}, [])
|
|
544
|
-
| FindReorgDepth({chain, rollbackTargetBlockNumber}) => (
|
|
545
|
-
{
|
|
546
|
-
...state,
|
|
547
|
-
rollbackState: FoundReorgDepth({
|
|
548
|
-
chain,
|
|
549
|
-
rollbackTargetBlockNumber,
|
|
550
|
-
}),
|
|
551
|
-
},
|
|
552
|
-
[Rollback],
|
|
553
|
-
)
|
|
554
|
-
| EnterReorgThreshold => (onEnterReorgThreshold(~state), [NextQuery(CheckAllChains)])
|
|
555
|
-
| UpdateQueues({progressedChainsById, shouldEnterReorgThreshold}) =>
|
|
556
|
-
let chainFetchers = state.chainManager.chainFetchers->ChainMap.mapWithKey((chain, cf) => {
|
|
557
|
-
let fs = switch progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
|
|
558
|
-
chain->ChainMap.Chain.toChainId,
|
|
559
|
-
) {
|
|
560
|
-
| Some(chainAfterBatch) => chainAfterBatch.fetchState
|
|
561
|
-
| None => cf.fetchState
|
|
562
|
-
}
|
|
563
|
-
{
|
|
564
|
-
...cf,
|
|
565
|
-
fetchState: shouldEnterReorgThreshold
|
|
566
|
-
? fs->FetchState.updateInternal(~blockLag=cf.chainConfig.blockLag)
|
|
567
|
-
: fs,
|
|
568
|
-
}
|
|
569
|
-
})
|
|
570
|
-
|
|
571
|
-
let chainManager = {
|
|
572
|
-
...state.chainManager,
|
|
573
|
-
chainFetchers,
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
(
|
|
577
|
-
{
|
|
578
|
-
...state,
|
|
579
|
-
chainManager,
|
|
580
|
-
},
|
|
581
|
-
[NextQuery(CheckAllChains)],
|
|
582
|
-
)
|
|
583
|
-
| SetRollbackState({rollbackedChainManager, eventsProcessedDiffByChain}) => (
|
|
584
|
-
{
|
|
585
|
-
...state,
|
|
586
|
-
rollbackState: RollbackReady({
|
|
587
|
-
eventsProcessedDiffByChain: eventsProcessedDiffByChain,
|
|
588
|
-
}),
|
|
589
|
-
chainManager: rollbackedChainManager,
|
|
590
|
-
},
|
|
591
|
-
[NextQuery(CheckAllChains), ProcessEventBatch],
|
|
592
|
-
)
|
|
593
|
-
| SuccessExit => {
|
|
594
|
-
Logging.info("Exiting with success")
|
|
595
|
-
NodeJs.process->NodeJs.exitWithCode(Success)
|
|
596
|
-
(state, [])
|
|
597
|
-
}
|
|
598
|
-
| ErrorExit(errHandler) =>
|
|
599
|
-
state.onError(errHandler)
|
|
600
|
-
(state, [])
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
let injectedInvalidatedActionReducer = (
|
|
605
|
-
~markBatchProcessed: Ports.MarkBatchProcessed.t,
|
|
606
|
-
~actionReducer: (t, action) => (t, array<task>),
|
|
607
|
-
) =>
|
|
608
|
-
(state: t, action: action) =>
|
|
609
|
-
switch action {
|
|
610
|
-
| EventBatchProcessed({batch}) if state->isPreparingRollback =>
|
|
611
|
-
Logging.info("Finished processing batch before rollback, actioning rollback")
|
|
612
|
-
markBatchProcessed()
|
|
613
|
-
(
|
|
614
|
-
{
|
|
615
|
-
...state,
|
|
616
|
-
chainManager: state.chainManager->ChainManager.updateProgressedChains(~batch),
|
|
617
|
-
},
|
|
618
|
-
[Rollback],
|
|
619
|
-
)
|
|
620
|
-
| ErrorExit(_) => actionReducer(state, action)
|
|
621
|
-
| _ =>
|
|
622
|
-
Logging.trace({
|
|
623
|
-
"msg": "Invalidated action discarded",
|
|
624
|
-
"action": action->S.convertOrThrow(Utils.Schema.variantTag),
|
|
625
|
-
})
|
|
626
|
-
(state, [])
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
let checkAndFetchForChain = (
|
|
630
|
-
//Used for dependency injection for tests
|
|
631
|
-
~waitForNewBlock,
|
|
632
|
-
~executeQuery,
|
|
633
|
-
//required args
|
|
634
|
-
~state,
|
|
635
|
-
~dispatchAction,
|
|
636
|
-
) =>
|
|
637
|
-
async chain => {
|
|
638
|
-
let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
|
|
639
|
-
if !isPreparingRollback(state) {
|
|
640
|
-
let {fetchState} = chainFetcher
|
|
641
|
-
let isRealtime = state.chainManager.isRealtime
|
|
642
|
-
|
|
643
|
-
// Only affects the WaitingForNewBlock branch of fetchNext, where
|
|
644
|
-
// there's nothing to fetch. During backfill any such chain is idle.
|
|
645
|
-
let reducedPolling = !isRealtime
|
|
646
|
-
|
|
647
|
-
await chainFetcher.sourceManager->SourceManager.fetchNext(
|
|
648
|
-
~fetchState,
|
|
649
|
-
~waitForNewBlock=(~knownHeight) =>
|
|
650
|
-
chainFetcher.sourceManager->waitForNewBlock(~knownHeight, ~isRealtime, ~reducedPolling),
|
|
651
|
-
~onNewBlock=(~knownHeight) =>
|
|
652
|
-
dispatchAction(FinishWaitingForNewBlock({chain, knownHeight})),
|
|
653
|
-
~executeQuery=async query => {
|
|
654
|
-
try {
|
|
655
|
-
let response = await chainFetcher.sourceManager->executeQuery(
|
|
656
|
-
~query,
|
|
657
|
-
~knownHeight=fetchState.knownHeight,
|
|
658
|
-
~isRealtime,
|
|
659
|
-
)
|
|
660
|
-
dispatchAction(ValidatePartitionQueryResponse({chain, response, query}))
|
|
661
|
-
} catch {
|
|
662
|
-
| exn => dispatchAction(ErrorExit(exn->ErrorHandling.make))
|
|
663
|
-
}
|
|
664
|
-
},
|
|
665
|
-
~stateId=state.id,
|
|
666
|
-
)
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
let injectedTaskReducer = (
|
|
671
|
-
//Used for dependency injection for tests
|
|
672
|
-
~waitForNewBlock,
|
|
673
|
-
~executeQuery,
|
|
674
|
-
~getLastKnownValidBlock,
|
|
675
|
-
) =>
|
|
676
|
-
async (
|
|
677
|
-
//required args
|
|
678
|
-
state: t,
|
|
679
|
-
task: task,
|
|
680
|
-
~dispatchAction,
|
|
681
|
-
) => {
|
|
682
|
-
switch task {
|
|
683
|
-
| ProcessPartitionQueryResponse(partitionQueryResponse) =>
|
|
684
|
-
state->processPartitionQueryResponse(partitionQueryResponse, ~dispatchAction)->Promise.ignore
|
|
685
|
-
| PruneStaleEntityHistory =>
|
|
686
|
-
let runPrune = async () => {
|
|
687
|
-
switch state.chainManager->ChainManager.getSafeCheckpointId {
|
|
688
|
-
| None => ()
|
|
689
|
-
| Some(safeCheckpointId) =>
|
|
690
|
-
await state.ctx.persistence.storage.pruneStaleCheckpoints(~safeCheckpointId)
|
|
691
|
-
|
|
692
|
-
for idx in 0 to state.ctx.persistence.allEntities->Array.length - 1 {
|
|
693
|
-
if idx !== 0 {
|
|
694
|
-
// Add some delay between entities
|
|
695
|
-
// To unblock the pg client if it's needed for something else
|
|
696
|
-
await Utils.delay(1000)
|
|
697
|
-
}
|
|
698
|
-
let entityConfig = state.ctx.persistence.allEntities->Array.getUnsafe(idx)
|
|
699
|
-
let timeRef = Hrtime.makeTimer()
|
|
700
|
-
try {
|
|
701
|
-
let () = await state.ctx.persistence.storage.pruneStaleEntityHistory(
|
|
702
|
-
~entityName=entityConfig.name,
|
|
703
|
-
~entityIndex=entityConfig.index,
|
|
704
|
-
~safeCheckpointId,
|
|
705
|
-
)
|
|
706
|
-
} catch {
|
|
707
|
-
| exn =>
|
|
708
|
-
exn->ErrorHandling.mkLogAndRaise(
|
|
709
|
-
~msg=`Failed to prune stale entity history`,
|
|
710
|
-
~logger=Logging.createChild(
|
|
711
|
-
~params={
|
|
712
|
-
"entityName": entityConfig.name,
|
|
713
|
-
"safeCheckpointId": safeCheckpointId,
|
|
714
|
-
},
|
|
715
|
-
),
|
|
716
|
-
)
|
|
717
|
-
}
|
|
718
|
-
Prometheus.RollbackHistoryPrune.increment(
|
|
719
|
-
~timeSeconds=Hrtime.timeSince(timeRef)->Hrtime.toSecondsFloat,
|
|
720
|
-
~entityName=entityConfig.name,
|
|
721
|
-
)
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
state.writeThrottlers.pruneStaleEntityHistory->Throttler.schedule(runPrune)
|
|
726
|
-
|
|
727
|
-
| UpdateChainMetaDataAndCheckForExit(shouldExit) =>
|
|
728
|
-
let {chainManager} = state
|
|
729
|
-
switch shouldExit {
|
|
730
|
-
| ExitWithSuccess =>
|
|
731
|
-
updateChainMetadataTable(chainManager, ~inMemoryStore=state.ctx.inMemoryStore)
|
|
732
|
-
await state.ctx.inMemoryStore->InMemoryStore.flush
|
|
733
|
-
dispatchAction(SuccessExit)
|
|
734
|
-
| ExitWithError(msg) =>
|
|
735
|
-
dispatchAction(ErrorExit(ErrorHandling.make(JsError.throwWithMessage(msg))))
|
|
736
|
-
| NoExit => updateChainMetadataTable(chainManager, ~inMemoryStore=state.ctx.inMemoryStore)
|
|
737
|
-
}
|
|
738
|
-
| NextQuery(chainCheck) =>
|
|
739
|
-
let fetchForChain = checkAndFetchForChain(
|
|
740
|
-
~waitForNewBlock,
|
|
741
|
-
~executeQuery,
|
|
742
|
-
~state,
|
|
743
|
-
~dispatchAction,
|
|
744
|
-
)
|
|
745
|
-
|
|
746
|
-
switch chainCheck {
|
|
747
|
-
| Chain(chain) => await chain->fetchForChain
|
|
748
|
-
| CheckAllChains =>
|
|
749
|
-
//Mapping from the states chainManager so we can construct tests that don't use
|
|
750
|
-
//all chains
|
|
751
|
-
let _ = await state.chainManager.chainFetchers
|
|
752
|
-
->ChainMap.keys
|
|
753
|
-
->Array.map(fetchForChain(_))
|
|
754
|
-
->Promise.all
|
|
755
|
-
}
|
|
756
|
-
| ProcessEventBatch =>
|
|
757
|
-
if !state.ctx.inMemoryStore.isProcessing && !isPreparingRollback(state) {
|
|
758
|
-
let isRollbackBatch = switch state.rollbackState {
|
|
759
|
-
| RollbackReady(_) => true
|
|
760
|
-
| _ => false
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
let batch =
|
|
764
|
-
state.chainManager->ChainManager.createBatch(
|
|
765
|
-
~processedCheckpointId=state.ctx.inMemoryStore.processedCheckpointId,
|
|
766
|
-
~batchSizeTarget=state.ctx.config.batchSize,
|
|
767
|
-
~isRollback=isRollbackBatch,
|
|
768
|
-
)
|
|
769
|
-
|
|
770
|
-
let progressedChainsById = batch.progressedChainsById
|
|
771
|
-
|
|
772
|
-
let isBelowReorgThreshold =
|
|
773
|
-
!state.chainManager.isInReorgThreshold && state.ctx.config.shouldRollbackOnReorg
|
|
774
|
-
let shouldEnterReorgThreshold =
|
|
775
|
-
isBelowReorgThreshold &&
|
|
776
|
-
state.chainManager.chainFetchers
|
|
777
|
-
->ChainMap.values
|
|
778
|
-
->Array.every(chainFetcher => {
|
|
779
|
-
let fetchState = switch progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
|
|
780
|
-
chainFetcher.fetchState.chainId,
|
|
781
|
-
) {
|
|
782
|
-
| Some(chainAfterBatch) => chainAfterBatch.fetchState
|
|
783
|
-
| None => chainFetcher.fetchState
|
|
784
|
-
}
|
|
785
|
-
fetchState->FetchState.isReadyToEnterReorgThreshold
|
|
786
|
-
})
|
|
787
|
-
|
|
788
|
-
if shouldEnterReorgThreshold {
|
|
789
|
-
dispatchAction(EnterReorgThreshold)
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
if progressedChainsById->Utils.Dict.isEmpty {
|
|
793
|
-
// When resuming from persisted state, all events may already be processed.
|
|
794
|
-
// Log the same completion message and handle exit just like EventBatchProcessed does.
|
|
795
|
-
if EventProcessing.allChainsEventsProcessedToEndblock(state.chainManager.chainFetchers) {
|
|
796
|
-
Logging.info("All chains are caught up to end blocks.")
|
|
797
|
-
if !state.keepProcessAlive {
|
|
798
|
-
updateChainMetadataTable(state.chainManager, ~inMemoryStore=state.ctx.inMemoryStore)
|
|
799
|
-
await state.ctx.inMemoryStore->InMemoryStore.flush
|
|
800
|
-
dispatchAction(SuccessExit)
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
} else {
|
|
804
|
-
let inMemoryStore = state.ctx.inMemoryStore
|
|
805
|
-
inMemoryStore.isProcessing = true
|
|
806
|
-
dispatchAction(UpdateQueues({progressedChainsById, shouldEnterReorgThreshold}))
|
|
807
|
-
|
|
808
|
-
inMemoryStore->InMemoryStore.setBatchDcs(~batch)
|
|
809
|
-
|
|
810
|
-
switch await EventProcessing.processEventBatch(
|
|
811
|
-
~batch,
|
|
812
|
-
~inMemoryStore,
|
|
813
|
-
~loadManager=state.loadManager,
|
|
814
|
-
~ctx=state.ctx,
|
|
815
|
-
~chainFetchers=state.chainManager.chainFetchers,
|
|
816
|
-
) {
|
|
817
|
-
| exception exn =>
|
|
818
|
-
//All casese should be handled/caught before this with better user messaging.
|
|
819
|
-
//This is just a safety in case something unexpected happens
|
|
820
|
-
let errHandler =
|
|
821
|
-
exn->ErrorHandling.make(
|
|
822
|
-
~msg="A top level unexpected error occurred during processing",
|
|
823
|
-
)
|
|
824
|
-
dispatchAction(ErrorExit(errHandler))
|
|
825
|
-
| res =>
|
|
826
|
-
switch res {
|
|
827
|
-
| Ok() => dispatchAction(EventBatchProcessed({batch: batch}))
|
|
828
|
-
| Error(errHandler) => dispatchAction(ErrorExit(errHandler))
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
| Rollback =>
|
|
834
|
-
//If it isn't processing a batch currently continue with rollback otherwise wait for current batch to finish processing
|
|
835
|
-
switch state {
|
|
836
|
-
| {rollbackState: NoRollback | RollbackReady(_)} =>
|
|
837
|
-
JsError.throwWithMessage("Internal error: Rollback initiated with invalid state")
|
|
838
|
-
| {rollbackState: ReorgDetected({chain, blockNumber: reorgBlockNumber})} => {
|
|
839
|
-
let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
|
|
840
|
-
|
|
841
|
-
dispatchAction(StartFindingReorgDepth)
|
|
842
|
-
let rollbackTargetBlockNumber = await chainFetcher->getLastKnownValidBlock(
|
|
843
|
-
~reorgBlockNumber,
|
|
844
|
-
~isRealtime=state.chainManager.isRealtime,
|
|
845
|
-
)
|
|
846
|
-
|
|
847
|
-
chainFetcher.sourceManager->SourceManager.onReorg(
|
|
848
|
-
~rollbackTargetBlock=rollbackTargetBlockNumber,
|
|
849
|
-
)
|
|
850
|
-
|
|
851
|
-
dispatchAction(FindReorgDepth({chain, rollbackTargetBlockNumber}))
|
|
852
|
-
}
|
|
853
|
-
// We can come to this case when event batch finished processing
|
|
854
|
-
// while we are still finding the reorg depth
|
|
855
|
-
// Do nothing here, just wait for reorg depth to be found
|
|
856
|
-
| {rollbackState: FindingReorgDepth} => ()
|
|
857
|
-
| {rollbackState: FoundReorgDepth(_)} if state.ctx.inMemoryStore.isProcessing =>
|
|
858
|
-
Logging.info("Waiting for batch to finish processing before executing rollback")
|
|
859
|
-
| {rollbackState: FoundReorgDepth({chain: reorgChain, rollbackTargetBlockNumber})} =>
|
|
860
|
-
let startTime = Hrtime.makeTimer()
|
|
861
|
-
|
|
862
|
-
let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(reorgChain)
|
|
863
|
-
|
|
864
|
-
let logger = Logging.createChildFrom(
|
|
865
|
-
~logger=chainFetcher.logger,
|
|
866
|
-
~params={
|
|
867
|
-
"action": "Rollback",
|
|
868
|
-
"reorgChain": reorgChain,
|
|
869
|
-
"targetBlockNumber": rollbackTargetBlockNumber,
|
|
870
|
-
},
|
|
871
|
-
)
|
|
872
|
-
logger->Logging.childInfo("Started rollback on reorg")
|
|
873
|
-
Prometheus.RollbackTargetBlockNumber.set(
|
|
874
|
-
~blockNumber=rollbackTargetBlockNumber,
|
|
875
|
-
~chain=reorgChain,
|
|
876
|
-
)
|
|
877
|
-
|
|
878
|
-
let reorgChainId = reorgChain->ChainMap.Chain.toChainId
|
|
879
|
-
|
|
880
|
-
// Finish pending batch writes first: the target checkpoint, the progress
|
|
881
|
-
// diff and the rollback diff below must all be computed from the same db
|
|
882
|
-
// state. Otherwise an in-flight batch lands after the progress reads and
|
|
883
|
-
// its entity changes get reverted without the chain progress being
|
|
884
|
-
// rolled back, so the events are never reprocessed.
|
|
885
|
-
await state.ctx.inMemoryStore->InMemoryStore.flush
|
|
886
|
-
|
|
887
|
-
let rollbackTargetCheckpointId = {
|
|
888
|
-
switch await state.ctx.persistence.storage.getRollbackTargetCheckpoint(
|
|
889
|
-
~reorgChainId,
|
|
890
|
-
~lastKnownValidBlockNumber=rollbackTargetBlockNumber,
|
|
891
|
-
) {
|
|
892
|
-
| Some(checkpointId) => checkpointId
|
|
893
|
-
| None => 0n
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
let eventsProcessedDiffByChain = Dict.make()
|
|
898
|
-
let newProgressBlockNumberPerChain = Dict.make()
|
|
899
|
-
let rollbackedProcessedEvents = ref(0.)
|
|
900
|
-
|
|
901
|
-
{
|
|
902
|
-
let rollbackProgressDiff = await state.ctx.persistence.storage.getRollbackProgressDiff(
|
|
903
|
-
~rollbackTargetCheckpointId,
|
|
904
|
-
)
|
|
905
|
-
for idx in 0 to rollbackProgressDiff->Array.length - 1 {
|
|
906
|
-
let diff = rollbackProgressDiff->Array.getUnsafe(idx)
|
|
907
|
-
eventsProcessedDiffByChain->Utils.Dict.setByInt(
|
|
908
|
-
diff["chain_id"],
|
|
909
|
-
{
|
|
910
|
-
let eventsProcessedDiff =
|
|
911
|
-
Float.fromString(diff["events_processed_diff"])->Option.getOrThrow
|
|
912
|
-
rollbackedProcessedEvents :=
|
|
913
|
-
rollbackedProcessedEvents.contents +. eventsProcessedDiff
|
|
914
|
-
eventsProcessedDiff
|
|
915
|
-
},
|
|
916
|
-
)
|
|
917
|
-
newProgressBlockNumberPerChain->Utils.Dict.setByInt(
|
|
918
|
-
diff["chain_id"],
|
|
919
|
-
if rollbackTargetCheckpointId === 0n && diff["chain_id"] === reorgChainId {
|
|
920
|
-
Pervasives.min(diff["new_progress_block_number"], rollbackTargetBlockNumber)
|
|
921
|
-
} else {
|
|
922
|
-
diff["new_progress_block_number"]
|
|
923
|
-
},
|
|
924
|
-
)
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
let chainFetchers = state.chainManager.chainFetchers->ChainMap.mapWithKey((chain, cf) => {
|
|
929
|
-
switch newProgressBlockNumberPerChain->Utils.Dict.dangerouslyGetByIntNonOption(
|
|
930
|
-
chain->ChainMap.Chain.toChainId,
|
|
931
|
-
) {
|
|
932
|
-
| Some(newProgressBlockNumber) =>
|
|
933
|
-
let fetchState =
|
|
934
|
-
cf.fetchState->FetchState.rollback(~targetBlockNumber=newProgressBlockNumber)
|
|
935
|
-
let newTotalEventsProcessed =
|
|
936
|
-
cf.numEventsProcessed -.
|
|
937
|
-
eventsProcessedDiffByChain
|
|
938
|
-
->Utils.Dict.dangerouslyGetByIntNonOption(chain->ChainMap.Chain.toChainId)
|
|
939
|
-
->Option.getUnsafe
|
|
940
|
-
|
|
941
|
-
if cf.committedProgressBlockNumber !== newProgressBlockNumber {
|
|
942
|
-
Prometheus.ProgressBlockNumber.set(
|
|
943
|
-
~blockNumber=newProgressBlockNumber,
|
|
944
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
945
|
-
)
|
|
946
|
-
}
|
|
947
|
-
if cf.numEventsProcessed !== newTotalEventsProcessed {
|
|
948
|
-
Prometheus.ProgressEventsCount.set(
|
|
949
|
-
~processedCount=newTotalEventsProcessed,
|
|
950
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
951
|
-
)
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
{
|
|
955
|
-
...cf,
|
|
956
|
-
reorgDetection: chain == reorgChain
|
|
957
|
-
? cf.reorgDetection->ReorgDetection.rollbackToValidBlockNumber(
|
|
958
|
-
~blockNumber=rollbackTargetBlockNumber,
|
|
959
|
-
)
|
|
960
|
-
: cf.reorgDetection,
|
|
961
|
-
safeCheckpointTracking: switch cf.safeCheckpointTracking {
|
|
962
|
-
| Some(safeCheckpointTracking) =>
|
|
963
|
-
Some(
|
|
964
|
-
safeCheckpointTracking->SafeCheckpointTracking.rollback(
|
|
965
|
-
~targetBlockNumber=newProgressBlockNumber,
|
|
966
|
-
),
|
|
967
|
-
)
|
|
968
|
-
| None => None
|
|
969
|
-
},
|
|
970
|
-
fetchState,
|
|
971
|
-
committedProgressBlockNumber: newProgressBlockNumber,
|
|
972
|
-
numEventsProcessed: newTotalEventsProcessed,
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
| None =>
|
|
976
|
-
// Even without a progress diff entry, the reorg chain must have its
|
|
977
|
-
// reorgDetection and fetchState rolled back. Otherwise the stale block hash
|
|
978
|
-
// stays in dataByBlockNumber and the same reorg is re-detected on the next
|
|
979
|
-
// fetch, causing an infinite reorg→rollback loop.
|
|
980
|
-
if chain == reorgChain {
|
|
981
|
-
{
|
|
982
|
-
...cf,
|
|
983
|
-
reorgDetection: cf.reorgDetection->ReorgDetection.rollbackToValidBlockNumber(
|
|
984
|
-
~blockNumber=rollbackTargetBlockNumber,
|
|
985
|
-
),
|
|
986
|
-
fetchState: cf.fetchState->FetchState.rollback(
|
|
987
|
-
~targetBlockNumber=rollbackTargetBlockNumber,
|
|
988
|
-
),
|
|
989
|
-
committedProgressBlockNumber: Pervasives.min(
|
|
990
|
-
cf.committedProgressBlockNumber,
|
|
991
|
-
rollbackTargetBlockNumber,
|
|
992
|
-
),
|
|
993
|
-
}
|
|
994
|
-
} else {
|
|
995
|
-
cf
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
})
|
|
999
|
-
|
|
1000
|
-
let diff = await state.ctx.inMemoryStore->InMemoryStore.prepareRollbackDiff(
|
|
1001
|
-
~persistence=state.ctx.persistence,
|
|
1002
|
-
~rollbackTargetCheckpointId,
|
|
1003
|
-
~rollbackDiffCheckpointId=state.ctx.inMemoryStore.committedCheckpointId->BigInt.add(1n),
|
|
1004
|
-
~progressBlockNumberByChainId=newProgressBlockNumberPerChain,
|
|
1005
|
-
)
|
|
1006
|
-
|
|
1007
|
-
let chainManager = {
|
|
1008
|
-
...state.chainManager,
|
|
1009
|
-
chainFetchers,
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
logger->Logging.childTrace({
|
|
1013
|
-
"msg": "Finished rollback on reorg",
|
|
1014
|
-
"entityChanges": {
|
|
1015
|
-
"deleted": diff["deletedEntities"],
|
|
1016
|
-
"upserted": diff["setEntities"],
|
|
1017
|
-
},
|
|
1018
|
-
"rollbackedEvents": rollbackedProcessedEvents.contents,
|
|
1019
|
-
"beforeCheckpointId": state.ctx.inMemoryStore.committedCheckpointId,
|
|
1020
|
-
"targetCheckpointId": rollbackTargetCheckpointId,
|
|
1021
|
-
})
|
|
1022
|
-
Prometheus.RollbackSuccess.increment(
|
|
1023
|
-
~timeSeconds=Hrtime.timeSince(startTime)->Hrtime.toSecondsFloat,
|
|
1024
|
-
~rollbackedProcessedEvents=rollbackedProcessedEvents.contents,
|
|
1025
|
-
)
|
|
1026
|
-
|
|
1027
|
-
dispatchAction(
|
|
1028
|
-
SetRollbackState({
|
|
1029
|
-
rollbackedChainManager: chainManager,
|
|
1030
|
-
eventsProcessedDiffByChain,
|
|
1031
|
-
}),
|
|
1032
|
-
)
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
type reducers = {
|
|
1038
|
-
actionReducer: (t, action) => (t, array<task>),
|
|
1039
|
-
invalidatedActionReducer: (t, action) => (t, array<task>),
|
|
1040
|
-
taskReducer: (t, task, ~dispatchAction: action => unit) => promise<unit>,
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
let makeReducers = (
|
|
1044
|
-
~waitForNewBlock=SourceManager.waitForNewBlock,
|
|
1045
|
-
~executeQuery=SourceManager.executeQuery,
|
|
1046
|
-
~getLastKnownValidBlock=(chainFetcher, ~reorgBlockNumber, ~isRealtime) =>
|
|
1047
|
-
chainFetcher->ChainFetcher.getLastKnownValidBlock(~reorgBlockNumber, ~isRealtime),
|
|
1048
|
-
~markBatchProcessed: Ports.MarkBatchProcessed.t,
|
|
1049
|
-
) => {
|
|
1050
|
-
let actionReducer = injectedActionReducer(~markBatchProcessed)
|
|
1051
|
-
{
|
|
1052
|
-
actionReducer,
|
|
1053
|
-
invalidatedActionReducer: injectedInvalidatedActionReducer(~markBatchProcessed, ~actionReducer),
|
|
1054
|
-
taskReducer: injectedTaskReducer(~waitForNewBlock, ~executeQuery, ~getLastKnownValidBlock),
|
|
1055
|
-
}
|
|
1056
|
-
}
|