envio 3.2.0 → 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 +35 -0
- package/src/EventConfigBuilder.res.mjs +21 -0
- 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 +162 -113
- package/src/FetchState.res.mjs +181 -119
- package/src/HandlerLoader.res +6 -1
- package/src/HandlerLoader.res.mjs +13 -36
- 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 +44 -7
- package/src/LoadLayer.res +20 -18
- package/src/LoadLayer.res.mjs +14 -12
- package/src/LoadLayer.resi +6 -3
- package/src/LogSelection.res +86 -17
- package/src/LogSelection.res.mjs +67 -14
- 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/TestIndexer.res +34 -27
- package/src/TestIndexer.res.mjs +4 -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 +7 -5
- package/src/sources/HyperSyncSource.res.mjs +3 -3
- 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
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
// Fetch orchestration and query-response handling. Re-enters the loop only
|
|
2
|
+
// through the injected schedule* effects; everything else points at IndexerState
|
|
3
|
+
// (state + transitions) and leaf effect modules.
|
|
4
|
+
|
|
5
|
+
type partitionQueryResponse = {
|
|
6
|
+
chain: IndexerState.chain,
|
|
7
|
+
response: Source.blockRangeFetchResponse,
|
|
8
|
+
query: FetchState.query,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let runContractRegistersOrThrow = async (
|
|
12
|
+
~itemsWithContractRegister: array<Internal.item>,
|
|
13
|
+
~config: Config.t,
|
|
14
|
+
) => {
|
|
15
|
+
let itemsWithDcs = []
|
|
16
|
+
|
|
17
|
+
let onRegister = (~item: Internal.item, ~contractAddress, ~contractName) => {
|
|
18
|
+
let eventItem = item->Internal.castUnsafeEventItem
|
|
19
|
+
let {blockNumber} = eventItem
|
|
20
|
+
|
|
21
|
+
let dc: Internal.indexingAddress = {
|
|
22
|
+
address: contractAddress,
|
|
23
|
+
contractName,
|
|
24
|
+
registrationBlock: blockNumber,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
switch item->Internal.getItemDcs {
|
|
28
|
+
| None => {
|
|
29
|
+
item->Internal.setItemDcs([dc])
|
|
30
|
+
itemsWithDcs->Array.push(item)
|
|
31
|
+
}
|
|
32
|
+
| Some(dcs) => dcs->Array.push(dc)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let promises = []
|
|
37
|
+
for idx in 0 to itemsWithContractRegister->Array.length - 1 {
|
|
38
|
+
let item = itemsWithContractRegister->Array.getUnsafe(idx)
|
|
39
|
+
let eventItem = item->Internal.castUnsafeEventItem
|
|
40
|
+
let contractRegister = switch eventItem {
|
|
41
|
+
| {eventConfig: {contractRegister: Some(contractRegister)}} => contractRegister
|
|
42
|
+
| {eventConfig: {contractRegister: None, name: eventName}} =>
|
|
43
|
+
// Unexpected case, since we should pass only events with contract register to this function
|
|
44
|
+
JsError.throwWithMessage("Contract register is not set for event " ++ eventName)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let errorMessage = "Event contractRegister failed, please fix the error to keep the indexer running smoothly"
|
|
48
|
+
|
|
49
|
+
// Catch sync and async errors
|
|
50
|
+
try {
|
|
51
|
+
let params: ContractRegisterContext.contractRegisterParams = {
|
|
52
|
+
item,
|
|
53
|
+
onRegister,
|
|
54
|
+
config,
|
|
55
|
+
isResolved: false,
|
|
56
|
+
}
|
|
57
|
+
let result = contractRegister(ContractRegisterContext.getContractRegisterArgs(params))
|
|
58
|
+
|
|
59
|
+
// Even though `contractRegister` always returns a promise,
|
|
60
|
+
// in the ReScript type, but it might return a non-promise value for TS API.
|
|
61
|
+
if result->Utils.Promise.isCatchable {
|
|
62
|
+
promises->Array.push(
|
|
63
|
+
result
|
|
64
|
+
->Promise.thenResolve(r => {
|
|
65
|
+
params.isResolved = true
|
|
66
|
+
r
|
|
67
|
+
})
|
|
68
|
+
->Promise.catch(exn => {
|
|
69
|
+
params.isResolved = true
|
|
70
|
+
exn->ErrorHandling.mkLogAndRaise(
|
|
71
|
+
~msg=errorMessage,
|
|
72
|
+
~logger=Ecosystem.getItemLogger(item, ~ecosystem=config.ecosystem),
|
|
73
|
+
)
|
|
74
|
+
}),
|
|
75
|
+
)
|
|
76
|
+
} else {
|
|
77
|
+
params.isResolved = true
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
| exn =>
|
|
81
|
+
exn->ErrorHandling.mkLogAndRaise(
|
|
82
|
+
~msg=errorMessage,
|
|
83
|
+
~logger=Ecosystem.getItemLogger(item, ~ecosystem=config.ecosystem),
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if promises->Utils.Array.notEmpty {
|
|
89
|
+
let _ = await Promise.all(promises)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
itemsWithDcs
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let rec onQueryResponse = async (
|
|
96
|
+
state: IndexerState.t,
|
|
97
|
+
{chain, response, query}: partitionQueryResponse,
|
|
98
|
+
~stateId,
|
|
99
|
+
~scheduleFetch,
|
|
100
|
+
~scheduleProcessing,
|
|
101
|
+
~scheduleRollback,
|
|
102
|
+
) =>
|
|
103
|
+
if state->IndexerState.isStale(~stateId) {
|
|
104
|
+
()
|
|
105
|
+
} else {
|
|
106
|
+
let chainState = state->IndexerState.getChainState(~chain)
|
|
107
|
+
let {
|
|
108
|
+
parsedQueueItems,
|
|
109
|
+
latestFetchedBlockNumber,
|
|
110
|
+
latestFetchedBlockTimestamp,
|
|
111
|
+
stats,
|
|
112
|
+
knownHeight,
|
|
113
|
+
blockHashes,
|
|
114
|
+
fromBlockQueried,
|
|
115
|
+
} = response
|
|
116
|
+
|
|
117
|
+
if knownHeight > (chainState->ChainState.fetchState).knownHeight {
|
|
118
|
+
Prometheus.SourceHeight.set(
|
|
119
|
+
~blockNumber=knownHeight,
|
|
120
|
+
~chainId=(chainState->ChainState.chainConfig).id,
|
|
121
|
+
// The knownHeight from response won't necessarily
|
|
122
|
+
// belong to the currently active source.
|
|
123
|
+
// But for simplicity, assume it does.
|
|
124
|
+
~sourceName=(chainState->ChainState.sourceManager->SourceManager.getActiveSource).name,
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Prometheus.FetchingBlockRange.increment(
|
|
129
|
+
~chainId=chain->ChainMap.Chain.toChainId,
|
|
130
|
+
~totalTimeElapsed=stats.totalTimeElapsed,
|
|
131
|
+
~parsingTimeElapsed=stats.parsingTimeElapsed->Option.getOr(0.),
|
|
132
|
+
~numEvents=parsedQueueItems->Array.length,
|
|
133
|
+
~blockRangeSize=latestFetchedBlockNumber - fromBlockQueried + 1,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
let reorgResult = chainState->ChainState.registerReorgGuard(~blockHashes, ~knownHeight)
|
|
137
|
+
|
|
138
|
+
let rollbackWithReorgDetectedBlockNumber = switch reorgResult {
|
|
139
|
+
| ReorgDetected(reorgDetected) => {
|
|
140
|
+
chainState
|
|
141
|
+
->ChainState.logger
|
|
142
|
+
->Logging.childInfo(
|
|
143
|
+
reorgDetected->ReorgDetection.reorgDetectedToLogParams(
|
|
144
|
+
~shouldRollbackOnReorg=(state->IndexerState.config).shouldRollbackOnReorg,
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
Prometheus.ReorgCount.increment(~chain)
|
|
148
|
+
Prometheus.ReorgDetectionBlockNumber.set(
|
|
149
|
+
~blockNumber=reorgDetected.scannedBlock.blockNumber,
|
|
150
|
+
~chain,
|
|
151
|
+
)
|
|
152
|
+
if (state->IndexerState.config).shouldRollbackOnReorg {
|
|
153
|
+
Some(reorgDetected.scannedBlock.blockNumber)
|
|
154
|
+
} else {
|
|
155
|
+
None
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
| NoReorg => None
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
switch rollbackWithReorgDetectedBlockNumber {
|
|
162
|
+
| Some(reorgDetectedBlockNumber) =>
|
|
163
|
+
// Prepare every chain for the rollback: restore each events-processed
|
|
164
|
+
// counter (the previous, uncommitted rollback subtracted from all chains,
|
|
165
|
+
// so undo that before the new rollback subtracts again) and drop pending
|
|
166
|
+
// queries requested against the about-to-be-invalidated chain state.
|
|
167
|
+
let eventsProcessedDiffByChain = switch state->IndexerState.rollbackState {
|
|
168
|
+
| RollbackReady({eventsProcessedDiffByChain}) => Some(eventsProcessedDiffByChain)
|
|
169
|
+
| _ => None
|
|
170
|
+
}
|
|
171
|
+
state
|
|
172
|
+
->IndexerState.chainStates
|
|
173
|
+
->Utils.Dict.forEach(cs =>
|
|
174
|
+
cs->ChainState.prepareReorg(
|
|
175
|
+
~eventsProcessedDiff=switch eventsProcessedDiffByChain {
|
|
176
|
+
| Some(byChain) =>
|
|
177
|
+
byChain->Utils.Dict.dangerouslyGetByIntNonOption((cs->ChainState.chainConfig).id)
|
|
178
|
+
| None => None
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
state->IndexerState.beginReorg(~chain, ~blockNumber=reorgDetectedBlockNumber)
|
|
183
|
+
// Advances synchronously to FindingReorgDepth, so a concurrent rollback
|
|
184
|
+
// kick (eg from the processing loop quiescing) collapses into this one.
|
|
185
|
+
scheduleRollback()
|
|
186
|
+
| None =>
|
|
187
|
+
let itemsWithContractRegister = []
|
|
188
|
+
let newItems = []
|
|
189
|
+
for idx in 0 to parsedQueueItems->Array.length - 1 {
|
|
190
|
+
let item = parsedQueueItems->Array.getUnsafe(idx)
|
|
191
|
+
let eventItem = item->Internal.castUnsafeEventItem
|
|
192
|
+
if eventItem.eventConfig.contractRegister !== None {
|
|
193
|
+
itemsWithContractRegister->Array.push(item)
|
|
194
|
+
}
|
|
195
|
+
// TODO: Don't really need to keep it in the queue
|
|
196
|
+
// when there's no handler (besides raw_events, processed counter, and dcsToStore consuming)
|
|
197
|
+
newItems->Array.push(item)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Re-check staleness: contract registration is async, so the chain state
|
|
201
|
+
// may have rolled back by the time we apply the fetched items.
|
|
202
|
+
let proceed = (~newItemsWithDcs) =>
|
|
203
|
+
if !(state->IndexerState.isStale(~stateId)) {
|
|
204
|
+
applyQueryResponse(
|
|
205
|
+
state,
|
|
206
|
+
~chain,
|
|
207
|
+
~newItems,
|
|
208
|
+
~newItemsWithDcs,
|
|
209
|
+
~knownHeight,
|
|
210
|
+
~latestFetchedBlock={
|
|
211
|
+
FetchState.blockNumber: latestFetchedBlockNumber,
|
|
212
|
+
blockTimestamp: latestFetchedBlockTimestamp,
|
|
213
|
+
},
|
|
214
|
+
~query,
|
|
215
|
+
)
|
|
216
|
+
ChainMetadata.stage(state)
|
|
217
|
+
scheduleFetch()
|
|
218
|
+
scheduleProcessing()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
switch itemsWithContractRegister {
|
|
222
|
+
| [] => proceed(~newItemsWithDcs=[])
|
|
223
|
+
| _ =>
|
|
224
|
+
switch await runContractRegistersOrThrow(
|
|
225
|
+
~itemsWithContractRegister,
|
|
226
|
+
~config=state->IndexerState.config,
|
|
227
|
+
) {
|
|
228
|
+
| exception exn => IndexerState.errorExit(state, exn->ErrorHandling.make)
|
|
229
|
+
| newItemsWithDcs => proceed(~newItemsWithDcs)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
and applyQueryResponse = (
|
|
236
|
+
state: IndexerState.t,
|
|
237
|
+
~chain,
|
|
238
|
+
~newItems,
|
|
239
|
+
~newItemsWithDcs,
|
|
240
|
+
~knownHeight,
|
|
241
|
+
~latestFetchedBlock,
|
|
242
|
+
~query,
|
|
243
|
+
) => {
|
|
244
|
+
let chainState = state->IndexerState.getChainState(~chain)
|
|
245
|
+
let wasFetchingAtHead = chainState->ChainState.isFetchingAtHead
|
|
246
|
+
|
|
247
|
+
chainState->ChainState.handleQueryResult(
|
|
248
|
+
~query,
|
|
249
|
+
~latestFetchedBlock,
|
|
250
|
+
~newItems,
|
|
251
|
+
~newItemsWithDcs,
|
|
252
|
+
~knownHeight,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
// In auto-exit mode, set endBlock to the first event's block when events arrive.
|
|
256
|
+
if state->IndexerState.exitAfterFirstEventBlock && newItems->Array.length > 0 {
|
|
257
|
+
chainState->ChainState.setEndBlockToFirstEvent(
|
|
258
|
+
~blockNumber=newItems->Array.getUnsafe(0)->Internal.getItemBlockNumber,
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Log the backfill→head transition once: this response brought the fetch
|
|
263
|
+
// frontier to the head. Gated on !isReady so realtime re-catch-ups (a new
|
|
264
|
+
// block arrives, gets fetched) don't spam the log after the chain is synced.
|
|
265
|
+
if (
|
|
266
|
+
!wasFetchingAtHead &&
|
|
267
|
+
!(chainState->ChainState.isReady) &&
|
|
268
|
+
chainState->ChainState.isFetchingAtHead
|
|
269
|
+
) {
|
|
270
|
+
chainState->ChainState.logger->Logging.childInfo("All events have been fetched")
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let finishWaitingForNewBlock = (
|
|
275
|
+
state: IndexerState.t,
|
|
276
|
+
~chain,
|
|
277
|
+
~knownHeight,
|
|
278
|
+
~stateId,
|
|
279
|
+
~scheduleFetch,
|
|
280
|
+
~scheduleProcessing,
|
|
281
|
+
) =>
|
|
282
|
+
if state->IndexerState.isStale(~stateId) {
|
|
283
|
+
()
|
|
284
|
+
} else {
|
|
285
|
+
let chainState = state->IndexerState.getChainState(~chain)
|
|
286
|
+
chainState->ChainState.updateKnownHeight(~knownHeight)
|
|
287
|
+
|
|
288
|
+
let isBelowReorgThreshold =
|
|
289
|
+
!(state->IndexerState.isInReorgThreshold) &&
|
|
290
|
+
(state->IndexerState.config).shouldRollbackOnReorg
|
|
291
|
+
let shouldEnterReorgThreshold =
|
|
292
|
+
isBelowReorgThreshold &&
|
|
293
|
+
state
|
|
294
|
+
->IndexerState.chainStates
|
|
295
|
+
->Dict.valuesToArray
|
|
296
|
+
->Array.every(cs => {
|
|
297
|
+
cs->ChainState.fetchState->FetchState.isReadyToEnterReorgThreshold
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
// Kick processing in case there are block handlers to run.
|
|
301
|
+
if shouldEnterReorgThreshold {
|
|
302
|
+
IndexerState.enterReorgThreshold(state)
|
|
303
|
+
}
|
|
304
|
+
scheduleFetch()
|
|
305
|
+
scheduleProcessing()
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let fetchChain = async (
|
|
309
|
+
state: IndexerState.t,
|
|
310
|
+
chain,
|
|
311
|
+
~concurrencyLimit,
|
|
312
|
+
~bufferLimit,
|
|
313
|
+
~stateId,
|
|
314
|
+
~scheduleFetch,
|
|
315
|
+
~scheduleProcessing,
|
|
316
|
+
~scheduleRollback,
|
|
317
|
+
) => {
|
|
318
|
+
let chainState = state->IndexerState.getChainState(~chain)
|
|
319
|
+
if !(state->IndexerState.isResolvingReorg) && !(state->IndexerState.isStopped) {
|
|
320
|
+
let fetchState = chainState->ChainState.fetchState
|
|
321
|
+
let isRealtime = state->IndexerState.isRealtime
|
|
322
|
+
let sourceManager = chainState->ChainState.sourceManager
|
|
323
|
+
|
|
324
|
+
// Only affects the WaitingForNewBlock branch of fetchNext, where
|
|
325
|
+
// there's nothing to fetch. During backfill any such chain is idle.
|
|
326
|
+
let reducedPolling = !isRealtime
|
|
327
|
+
|
|
328
|
+
// Owns its error boundary: launch doesn't catch, so any failure here (the
|
|
329
|
+
// query, response handling, or fetchNext itself) must stop the indexer.
|
|
330
|
+
try {
|
|
331
|
+
await sourceManager->SourceManager.fetchNext(
|
|
332
|
+
~fetchState,
|
|
333
|
+
~waitForNewBlock=(~knownHeight) =>
|
|
334
|
+
sourceManager->SourceManager.waitForNewBlock(~knownHeight, ~isRealtime, ~reducedPolling),
|
|
335
|
+
~onNewBlock=(~knownHeight) =>
|
|
336
|
+
finishWaitingForNewBlock(
|
|
337
|
+
state,
|
|
338
|
+
~chain,
|
|
339
|
+
~knownHeight,
|
|
340
|
+
~stateId,
|
|
341
|
+
~scheduleFetch,
|
|
342
|
+
~scheduleProcessing,
|
|
343
|
+
),
|
|
344
|
+
~executeQuery=async query => {
|
|
345
|
+
// Caught here (not just by the outer try) so the query promise never
|
|
346
|
+
// rejects: fetchNext spins a side-chain off it that would otherwise
|
|
347
|
+
// become an unhandled rejection.
|
|
348
|
+
try {
|
|
349
|
+
let response = await sourceManager->SourceManager.executeQuery(
|
|
350
|
+
~query,
|
|
351
|
+
~knownHeight=fetchState.knownHeight,
|
|
352
|
+
~isRealtime,
|
|
353
|
+
)
|
|
354
|
+
await onQueryResponse(
|
|
355
|
+
state,
|
|
356
|
+
{chain, response, query},
|
|
357
|
+
~stateId,
|
|
358
|
+
~scheduleFetch,
|
|
359
|
+
~scheduleProcessing,
|
|
360
|
+
~scheduleRollback,
|
|
361
|
+
)
|
|
362
|
+
} catch {
|
|
363
|
+
| exn => IndexerState.errorExit(state, exn->ErrorHandling.make)
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
~concurrencyLimit,
|
|
367
|
+
~bufferLimit,
|
|
368
|
+
~stateId,
|
|
369
|
+
)
|
|
370
|
+
} catch {
|
|
371
|
+
| exn =>
|
|
372
|
+
IndexerState.errorExit(state, exn->ErrorHandling.make(~msg=IndexerState.unexpectedErrorMsg))
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as Logging from "./Logging.res.mjs";
|
|
5
|
+
import * as Ecosystem from "./Ecosystem.res.mjs";
|
|
6
|
+
import * as ChainState from "./ChainState.res.mjs";
|
|
7
|
+
import * as FetchState from "./FetchState.res.mjs";
|
|
8
|
+
import * as Prometheus from "./Prometheus.res.mjs";
|
|
9
|
+
import * as IndexerState from "./IndexerState.res.mjs";
|
|
10
|
+
import * as ChainMetadata from "./ChainMetadata.res.mjs";
|
|
11
|
+
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
12
|
+
import * as SourceManager from "./sources/SourceManager.res.mjs";
|
|
13
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
14
|
+
import * as ReorgDetection from "./ReorgDetection.res.mjs";
|
|
15
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
16
|
+
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
17
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
18
|
+
import * as ContractRegisterContext from "./ContractRegisterContext.res.mjs";
|
|
19
|
+
|
|
20
|
+
async function runContractRegistersOrThrow(itemsWithContractRegister, config) {
|
|
21
|
+
let itemsWithDcs = [];
|
|
22
|
+
let onRegister = (item, contractAddress, contractName) => {
|
|
23
|
+
let dc_registrationBlock = item.blockNumber;
|
|
24
|
+
let dc = {
|
|
25
|
+
address: contractAddress,
|
|
26
|
+
contractName: contractName,
|
|
27
|
+
registrationBlock: dc_registrationBlock
|
|
28
|
+
};
|
|
29
|
+
let dcs = item.dcs;
|
|
30
|
+
if (dcs !== undefined) {
|
|
31
|
+
dcs.push(dc);
|
|
32
|
+
} else {
|
|
33
|
+
item.dcs = [dc];
|
|
34
|
+
itemsWithDcs.push(item);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
let promises = [];
|
|
38
|
+
for (let idx = 0, idx_finish = itemsWithContractRegister.length; idx < idx_finish; ++idx) {
|
|
39
|
+
let item = itemsWithContractRegister[idx];
|
|
40
|
+
let match = item.eventConfig;
|
|
41
|
+
let contractRegister = match.contractRegister;
|
|
42
|
+
let contractRegister$1 = contractRegister !== undefined ? contractRegister : Stdlib_JsError.throwWithMessage("Contract register is not set for event " + match.name);
|
|
43
|
+
let errorMessage = "Event contractRegister failed, please fix the error to keep the indexer running smoothly";
|
|
44
|
+
try {
|
|
45
|
+
let params = {
|
|
46
|
+
item: item,
|
|
47
|
+
onRegister: onRegister,
|
|
48
|
+
config: config,
|
|
49
|
+
isResolved: false
|
|
50
|
+
};
|
|
51
|
+
let result = contractRegister$1(ContractRegisterContext.getContractRegisterArgs(params));
|
|
52
|
+
if (Utils.$$Promise.isCatchable(result)) {
|
|
53
|
+
promises.push(Stdlib_Promise.$$catch(result.then(r => {
|
|
54
|
+
params.isResolved = true;
|
|
55
|
+
|
|
56
|
+
}), exn => {
|
|
57
|
+
params.isResolved = true;
|
|
58
|
+
return ErrorHandling.mkLogAndRaise(Ecosystem.getItemLogger(item, config.ecosystem), errorMessage, exn);
|
|
59
|
+
}));
|
|
60
|
+
} else {
|
|
61
|
+
params.isResolved = true;
|
|
62
|
+
}
|
|
63
|
+
} catch (raw_exn) {
|
|
64
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
65
|
+
ErrorHandling.mkLogAndRaise(Ecosystem.getItemLogger(item, config.ecosystem), errorMessage, exn);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (Utils.$$Array.notEmpty(promises)) {
|
|
69
|
+
await Promise.all(promises);
|
|
70
|
+
}
|
|
71
|
+
return itemsWithDcs;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function applyQueryResponse(state, chain, newItems, newItemsWithDcs, knownHeight, latestFetchedBlock, query) {
|
|
75
|
+
let chainState = IndexerState.getChainState(state, chain);
|
|
76
|
+
let wasFetchingAtHead = ChainState.isFetchingAtHead(chainState);
|
|
77
|
+
ChainState.handleQueryResult(chainState, query, newItems, newItemsWithDcs, latestFetchedBlock, knownHeight);
|
|
78
|
+
if (IndexerState.exitAfterFirstEventBlock(state) && newItems.length !== 0) {
|
|
79
|
+
ChainState.setEndBlockToFirstEvent(chainState, newItems[0].blockNumber);
|
|
80
|
+
}
|
|
81
|
+
if (!wasFetchingAtHead && !ChainState.isReady(chainState) && ChainState.isFetchingAtHead(chainState)) {
|
|
82
|
+
return Logging.childInfo(ChainState.logger(chainState), "All events have been fetched");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function onQueryResponse(state, param, stateId, scheduleFetch, scheduleProcessing, scheduleRollback) {
|
|
87
|
+
let query = param.query;
|
|
88
|
+
let response = param.response;
|
|
89
|
+
let chain = param.chain;
|
|
90
|
+
if (IndexerState.isStale(state, stateId)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let chainState = IndexerState.getChainState(state, chain);
|
|
94
|
+
let stats = response.stats;
|
|
95
|
+
let latestFetchedBlockTimestamp = response.latestFetchedBlockTimestamp;
|
|
96
|
+
let latestFetchedBlockNumber = response.latestFetchedBlockNumber;
|
|
97
|
+
let parsedQueueItems = response.parsedQueueItems;
|
|
98
|
+
let knownHeight = response.knownHeight;
|
|
99
|
+
if (knownHeight > ChainState.fetchState(chainState).knownHeight) {
|
|
100
|
+
Prometheus.SourceHeight.set(SourceManager.getActiveSource(ChainState.sourceManager(chainState)).name, ChainState.chainConfig(chainState).id, knownHeight);
|
|
101
|
+
}
|
|
102
|
+
Prometheus.FetchingBlockRange.increment(chain, stats["total time elapsed (s)"], Stdlib_Option.getOr(stats["parsing time (s)"], 0), parsedQueueItems.length, (latestFetchedBlockNumber - response.fromBlockQueried | 0) + 1 | 0);
|
|
103
|
+
let reorgResult = ChainState.registerReorgGuard(chainState, response.blockHashes, knownHeight);
|
|
104
|
+
let rollbackWithReorgDetectedBlockNumber;
|
|
105
|
+
if (typeof reorgResult !== "object") {
|
|
106
|
+
rollbackWithReorgDetectedBlockNumber = undefined;
|
|
107
|
+
} else {
|
|
108
|
+
let reorgDetected = reorgResult._0;
|
|
109
|
+
Logging.childInfo(ChainState.logger(chainState), ReorgDetection.reorgDetectedToLogParams(reorgDetected, IndexerState.config(state).shouldRollbackOnReorg));
|
|
110
|
+
Prometheus.ReorgCount.increment(chain);
|
|
111
|
+
Prometheus.ReorgDetectionBlockNumber.set(reorgDetected.scannedBlock.blockNumber, chain);
|
|
112
|
+
rollbackWithReorgDetectedBlockNumber = IndexerState.config(state).shouldRollbackOnReorg ? reorgDetected.scannedBlock.blockNumber : undefined;
|
|
113
|
+
}
|
|
114
|
+
if (rollbackWithReorgDetectedBlockNumber !== undefined) {
|
|
115
|
+
let match = IndexerState.rollbackState(state);
|
|
116
|
+
let eventsProcessedDiffByChain;
|
|
117
|
+
eventsProcessedDiffByChain = typeof match !== "object" || match.TAG !== "RollbackReady" ? undefined : match.eventsProcessedDiffByChain;
|
|
118
|
+
Utils.Dict.forEach(IndexerState.chainStates(state), cs => ChainState.prepareReorg(cs, eventsProcessedDiffByChain !== undefined ? eventsProcessedDiffByChain[ChainState.chainConfig(cs).id] : undefined));
|
|
119
|
+
IndexerState.beginReorg(state, chain, rollbackWithReorgDetectedBlockNumber);
|
|
120
|
+
return scheduleRollback();
|
|
121
|
+
}
|
|
122
|
+
let itemsWithContractRegister = [];
|
|
123
|
+
let newItems = [];
|
|
124
|
+
for (let idx = 0, idx_finish = parsedQueueItems.length; idx < idx_finish; ++idx) {
|
|
125
|
+
let item = parsedQueueItems[idx];
|
|
126
|
+
if (item.eventConfig.contractRegister !== undefined) {
|
|
127
|
+
itemsWithContractRegister.push(item);
|
|
128
|
+
}
|
|
129
|
+
newItems.push(item);
|
|
130
|
+
}
|
|
131
|
+
let proceed = newItemsWithDcs => {
|
|
132
|
+
if (!IndexerState.isStale(state, stateId)) {
|
|
133
|
+
applyQueryResponse(state, chain, newItems, newItemsWithDcs, knownHeight, {
|
|
134
|
+
blockNumber: latestFetchedBlockNumber,
|
|
135
|
+
blockTimestamp: latestFetchedBlockTimestamp
|
|
136
|
+
}, query);
|
|
137
|
+
ChainMetadata.stage(state);
|
|
138
|
+
scheduleFetch();
|
|
139
|
+
return scheduleProcessing();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
if (itemsWithContractRegister.length === 0) {
|
|
143
|
+
return proceed([]);
|
|
144
|
+
}
|
|
145
|
+
let newItemsWithDcs;
|
|
146
|
+
try {
|
|
147
|
+
newItemsWithDcs = await runContractRegistersOrThrow(itemsWithContractRegister, IndexerState.config(state));
|
|
148
|
+
} catch (raw_exn) {
|
|
149
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
150
|
+
return IndexerState.errorExit(state, ErrorHandling.make(exn, undefined, undefined));
|
|
151
|
+
}
|
|
152
|
+
return proceed(newItemsWithDcs);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function finishWaitingForNewBlock(state, chain, knownHeight, stateId, scheduleFetch, scheduleProcessing) {
|
|
156
|
+
if (IndexerState.isStale(state, stateId)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
let chainState = IndexerState.getChainState(state, chain);
|
|
160
|
+
ChainState.updateKnownHeight(chainState, knownHeight);
|
|
161
|
+
let isBelowReorgThreshold = !IndexerState.isInReorgThreshold(state) && IndexerState.config(state).shouldRollbackOnReorg;
|
|
162
|
+
let shouldEnterReorgThreshold = isBelowReorgThreshold && Object.values(IndexerState.chainStates(state)).every(cs => FetchState.isReadyToEnterReorgThreshold(ChainState.fetchState(cs)));
|
|
163
|
+
if (shouldEnterReorgThreshold) {
|
|
164
|
+
IndexerState.enterReorgThreshold(state);
|
|
165
|
+
}
|
|
166
|
+
scheduleFetch();
|
|
167
|
+
scheduleProcessing();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function fetchChain(state, chain, concurrencyLimit, bufferLimit, stateId, scheduleFetch, scheduleProcessing, scheduleRollback) {
|
|
171
|
+
let chainState = IndexerState.getChainState(state, chain);
|
|
172
|
+
if (IndexerState.isResolvingReorg(state) || IndexerState.isStopped(state)) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
let fetchState = ChainState.fetchState(chainState);
|
|
176
|
+
let isRealtime = IndexerState.isRealtime(state);
|
|
177
|
+
let sourceManager = ChainState.sourceManager(chainState);
|
|
178
|
+
let reducedPolling = !isRealtime;
|
|
179
|
+
try {
|
|
180
|
+
return await SourceManager.fetchNext(sourceManager, fetchState, async query => {
|
|
181
|
+
try {
|
|
182
|
+
let response = await SourceManager.executeQuery(sourceManager, query, fetchState.knownHeight, isRealtime);
|
|
183
|
+
return await onQueryResponse(state, {
|
|
184
|
+
chain: chain,
|
|
185
|
+
response: response,
|
|
186
|
+
query: query
|
|
187
|
+
}, stateId, scheduleFetch, scheduleProcessing, scheduleRollback);
|
|
188
|
+
} catch (raw_exn) {
|
|
189
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
190
|
+
return IndexerState.errorExit(state, ErrorHandling.make(exn, undefined, undefined));
|
|
191
|
+
}
|
|
192
|
+
}, knownHeight => SourceManager.waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling), knownHeight => finishWaitingForNewBlock(state, chain, knownHeight, stateId, scheduleFetch, scheduleProcessing), concurrencyLimit, bufferLimit, stateId);
|
|
193
|
+
} catch (raw_exn) {
|
|
194
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
195
|
+
return IndexerState.errorExit(state, ErrorHandling.make(exn, undefined, IndexerState.unexpectedErrorMsg));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
runContractRegistersOrThrow,
|
|
201
|
+
onQueryResponse,
|
|
202
|
+
applyQueryResponse,
|
|
203
|
+
finishWaitingForNewBlock,
|
|
204
|
+
fetchChain,
|
|
205
|
+
}
|
|
206
|
+
/* Utils Not a pure module */
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Stage the per-chain metadata into the in-memory store. It's staged, not
|
|
2
|
+
// written: the next batch write folds the staged diff in.
|
|
3
|
+
let stage = (state: IndexerState.t) => {
|
|
4
|
+
let chainsData: dict<InternalTable.Chains.metaFields> = Dict.make()
|
|
5
|
+
|
|
6
|
+
state
|
|
7
|
+
->IndexerState.chainStates
|
|
8
|
+
->Dict.valuesToArray
|
|
9
|
+
->Array.forEach(cs => {
|
|
10
|
+
let fetchState = cs->ChainState.fetchState
|
|
11
|
+
chainsData->Dict.set(
|
|
12
|
+
(cs->ChainState.chainConfig).id->Int.toString,
|
|
13
|
+
{
|
|
14
|
+
firstEventBlockNumber: fetchState.firstEventBlock->Null.fromOption,
|
|
15
|
+
isHyperSync: (
|
|
16
|
+
cs->ChainState.sourceManager->SourceManager.getActiveSource
|
|
17
|
+
).poweredByHyperSync,
|
|
18
|
+
latestFetchedBlockNumber: fetchState->FetchState.bufferBlockNumber,
|
|
19
|
+
timestampCaughtUpToHeadOrEndblock: cs
|
|
20
|
+
->ChainState.timestampCaughtUpToHeadOrEndblock
|
|
21
|
+
->Null.fromOption,
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
state->Writing.setChainMeta(chainsData)
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Writing from "./Writing.res.mjs";
|
|
4
|
+
import * as ChainState from "./ChainState.res.mjs";
|
|
5
|
+
import * as FetchState from "./FetchState.res.mjs";
|
|
6
|
+
import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
|
|
7
|
+
import * as IndexerState from "./IndexerState.res.mjs";
|
|
8
|
+
import * as SourceManager from "./sources/SourceManager.res.mjs";
|
|
9
|
+
|
|
10
|
+
function stage(state) {
|
|
11
|
+
let chainsData = {};
|
|
12
|
+
Object.values(IndexerState.chainStates(state)).forEach(cs => {
|
|
13
|
+
let fetchState = ChainState.fetchState(cs);
|
|
14
|
+
chainsData[ChainState.chainConfig(cs).id.toString()] = {
|
|
15
|
+
first_event_block: Stdlib_Null.fromOption(fetchState.firstEventBlock),
|
|
16
|
+
buffer_block: FetchState.bufferBlockNumber(fetchState),
|
|
17
|
+
ready_at: Stdlib_Null.fromOption(ChainState.timestampCaughtUpToHeadOrEndblock(cs)),
|
|
18
|
+
_is_hyper_sync: SourceManager.getActiveSource(ChainState.sourceManager(cs)).poweredByHyperSync
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
Writing.setChainMeta(state, chainsData);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
stage,
|
|
26
|
+
}
|
|
27
|
+
/* Writing Not a pure module */
|