envio 3.0.0-alpha.2 → 3.0.0-alpha.3
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/evm.schema.json +44 -33
- package/fuel.schema.json +32 -21
- package/index.d.ts +1 -0
- package/package.json +7 -6
- package/src/Batch.res.mjs +1 -1
- package/src/Benchmark.res +394 -0
- package/src/Benchmark.res.mjs +398 -0
- package/src/ChainFetcher.res +459 -0
- package/src/ChainFetcher.res.mjs +281 -0
- package/src/ChainManager.res +179 -0
- package/src/ChainManager.res.mjs +139 -0
- package/src/Config.res +15 -1
- package/src/Config.res.mjs +27 -4
- package/src/Ecosystem.res +9 -124
- package/src/Ecosystem.res.mjs +19 -160
- package/src/Env.res +0 -1
- package/src/Env.res.mjs +0 -3
- package/src/Envio.gen.ts +9 -1
- package/src/Envio.res +12 -9
- package/src/EventProcessing.res +476 -0
- package/src/EventProcessing.res.mjs +341 -0
- package/src/FetchState.res +54 -29
- package/src/FetchState.res.mjs +62 -35
- package/src/GlobalState.res +1169 -0
- package/src/GlobalState.res.mjs +1196 -0
- package/src/Internal.res +2 -1
- package/src/LoadLayer.res +444 -0
- package/src/LoadLayer.res.mjs +296 -0
- package/src/LoadLayer.resi +32 -0
- package/src/Prometheus.res +8 -8
- package/src/Prometheus.res.mjs +10 -10
- package/src/ReorgDetection.res +6 -10
- package/src/ReorgDetection.res.mjs +6 -6
- package/src/UserContext.res +356 -0
- package/src/UserContext.res.mjs +238 -0
- package/src/bindings/DateFns.res +71 -0
- package/src/bindings/DateFns.res.mjs +22 -0
- package/src/sources/Evm.res +87 -0
- package/src/sources/Evm.res.mjs +105 -0
- package/src/sources/EvmChain.res +95 -0
- package/src/sources/EvmChain.res.mjs +61 -0
- package/src/sources/Fuel.res +19 -34
- package/src/sources/Fuel.res.mjs +34 -16
- package/src/sources/FuelSDK.res +37 -0
- package/src/sources/FuelSDK.res.mjs +29 -0
- package/src/sources/HyperFuel.res +2 -2
- package/src/sources/HyperFuel.resi +1 -1
- package/src/sources/HyperFuelClient.res +2 -2
- package/src/sources/HyperFuelSource.res +8 -8
- package/src/sources/HyperFuelSource.res.mjs +5 -5
- package/src/sources/HyperSyncSource.res +5 -5
- package/src/sources/HyperSyncSource.res.mjs +5 -5
- package/src/sources/RpcSource.res +4 -4
- package/src/sources/RpcSource.res.mjs +3 -3
- package/src/sources/Solana.res +59 -0
- package/src/sources/Solana.res.mjs +79 -0
- package/src/sources/Source.res +2 -2
- package/src/sources/SourceManager.res +24 -32
- package/src/sources/SourceManager.res.mjs +20 -20
- package/src/sources/SourceManager.resi +4 -5
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
open Belt
|
|
2
|
+
|
|
3
|
+
//A filter should return true if the event should be kept and isValid should return
|
|
4
|
+
//false when the filter should be removed/cleaned up
|
|
5
|
+
type processingFilter = {
|
|
6
|
+
filter: Internal.item => bool,
|
|
7
|
+
isValid: (~fetchState: FetchState.t) => bool,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type t = {
|
|
11
|
+
logger: Pino.t,
|
|
12
|
+
fetchState: FetchState.t,
|
|
13
|
+
sourceManager: SourceManager.t,
|
|
14
|
+
chainConfig: Config.chain,
|
|
15
|
+
isProgressAtHead: bool,
|
|
16
|
+
timestampCaughtUpToHeadOrEndblock: option<Js.Date.t>,
|
|
17
|
+
committedProgressBlockNumber: int,
|
|
18
|
+
firstEventBlockNumber: option<int>,
|
|
19
|
+
numEventsProcessed: int,
|
|
20
|
+
numBatchesFetched: int,
|
|
21
|
+
reorgDetection: ReorgDetection.t,
|
|
22
|
+
safeCheckpointTracking: option<SafeCheckpointTracking.t>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//CONSTRUCTION
|
|
26
|
+
let make = (
|
|
27
|
+
~chainConfig: Config.chain,
|
|
28
|
+
~dynamicContracts: array<Internal.indexingContract>,
|
|
29
|
+
~startBlock,
|
|
30
|
+
~endBlock,
|
|
31
|
+
~firstEventBlockNumber,
|
|
32
|
+
~progressBlockNumber,
|
|
33
|
+
~config: Config.t,
|
|
34
|
+
~registrations: EventRegister.registrations,
|
|
35
|
+
~targetBufferSize,
|
|
36
|
+
~logger,
|
|
37
|
+
~timestampCaughtUpToHeadOrEndblock,
|
|
38
|
+
~numEventsProcessed,
|
|
39
|
+
~numBatchesFetched,
|
|
40
|
+
~isInReorgThreshold,
|
|
41
|
+
~reorgCheckpoints: array<Internal.reorgCheckpoint>,
|
|
42
|
+
~maxReorgDepth,
|
|
43
|
+
): t => {
|
|
44
|
+
// We don't need the router itself, but only validation logic,
|
|
45
|
+
// since now event router is created for selection of events
|
|
46
|
+
// and validation doesn't work correctly in routers.
|
|
47
|
+
// Ideally to split it into two different parts.
|
|
48
|
+
let eventRouter = EventRouter.empty()
|
|
49
|
+
|
|
50
|
+
// Aggregate events we want to fetch
|
|
51
|
+
let contracts = []
|
|
52
|
+
let eventConfigs: array<Internal.eventConfig> = []
|
|
53
|
+
|
|
54
|
+
let notRegisteredEvents = []
|
|
55
|
+
|
|
56
|
+
chainConfig.contracts->Array.forEach(contract => {
|
|
57
|
+
let contractName = contract.name
|
|
58
|
+
|
|
59
|
+
contract.events->Array.forEach(eventConfig => {
|
|
60
|
+
let {isWildcard} = eventConfig
|
|
61
|
+
let hasContractRegister = eventConfig.contractRegister->Option.isSome
|
|
62
|
+
|
|
63
|
+
// Should validate the events
|
|
64
|
+
eventRouter->EventRouter.addOrThrow(
|
|
65
|
+
eventConfig.id,
|
|
66
|
+
(),
|
|
67
|
+
~contractName,
|
|
68
|
+
~chain=ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id),
|
|
69
|
+
~eventName=eventConfig.name,
|
|
70
|
+
~isWildcard,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// Filter out non-preRegistration events on preRegistration phase
|
|
74
|
+
// so we don't care about it in fetch state and workers anymore
|
|
75
|
+
let shouldBeIncluded = if config.enableRawEvents {
|
|
76
|
+
true
|
|
77
|
+
} else {
|
|
78
|
+
let isRegistered = hasContractRegister || eventConfig.handler->Option.isSome
|
|
79
|
+
if !isRegistered {
|
|
80
|
+
notRegisteredEvents->Array.push(eventConfig)
|
|
81
|
+
}
|
|
82
|
+
isRegistered
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if shouldBeIncluded {
|
|
86
|
+
eventConfigs->Array.push(eventConfig)
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
switch contract.startBlock {
|
|
91
|
+
| Some(startBlock) if startBlock < chainConfig.startBlock =>
|
|
92
|
+
Js.Exn.raiseError(
|
|
93
|
+
`The start block for contract "${contractName}" is less than the chain start block. This is not supported yet.`,
|
|
94
|
+
)
|
|
95
|
+
| _ => ()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
contract.addresses->Array.forEach(address => {
|
|
99
|
+
contracts->Array.push({
|
|
100
|
+
Internal.address,
|
|
101
|
+
contractName: contract.name,
|
|
102
|
+
startBlock: switch contract.startBlock {
|
|
103
|
+
| Some(startBlock) => startBlock
|
|
104
|
+
| None => chainConfig.startBlock
|
|
105
|
+
},
|
|
106
|
+
registrationBlock: None,
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
dynamicContracts->Array.forEach(dc => contracts->Array.push(dc))
|
|
112
|
+
|
|
113
|
+
if notRegisteredEvents->Utils.Array.notEmpty {
|
|
114
|
+
logger->Logging.childInfo(
|
|
115
|
+
`The event${if notRegisteredEvents->Array.length > 1 {
|
|
116
|
+
"s"
|
|
117
|
+
} else {
|
|
118
|
+
""
|
|
119
|
+
}} ${notRegisteredEvents
|
|
120
|
+
->Array.map(eventConfig => `${eventConfig.contractName}.${eventConfig.name}`)
|
|
121
|
+
->Js.Array2.joinWith(", ")} don't have an event handler and skipped for indexing.`,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let onBlockConfigs =
|
|
126
|
+
registrations.onBlockByChainId->Utils.Dict.dangerouslyGetNonOption(chainConfig.id->Int.toString)
|
|
127
|
+
switch onBlockConfigs {
|
|
128
|
+
| Some(onBlockConfigs) =>
|
|
129
|
+
// TODO: Move it to the EventRegister module
|
|
130
|
+
// so the error is thrown with better stack trace
|
|
131
|
+
onBlockConfigs->Array.forEach(onBlockConfig => {
|
|
132
|
+
if onBlockConfig.startBlock->Option.getWithDefault(startBlock) < startBlock {
|
|
133
|
+
Js.Exn.raiseError(
|
|
134
|
+
`The start block for onBlock handler "${onBlockConfig.name}" is less than the chain start block (${startBlock->Belt.Int.toString}). This is not supported yet.`,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
switch endBlock {
|
|
138
|
+
| Some(chainEndBlock) =>
|
|
139
|
+
if onBlockConfig.endBlock->Option.getWithDefault(chainEndBlock) > chainEndBlock {
|
|
140
|
+
Js.Exn.raiseError(
|
|
141
|
+
`The end block for onBlock handler "${onBlockConfig.name}" is greater than the chain end block (${chainEndBlock->Belt.Int.toString}). This is not supported yet.`,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
| None => ()
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
| None => ()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let fetchState = FetchState.make(
|
|
151
|
+
~maxAddrInPartition=config.maxAddrInPartition,
|
|
152
|
+
~contracts,
|
|
153
|
+
~progressBlockNumber,
|
|
154
|
+
~startBlock,
|
|
155
|
+
~endBlock,
|
|
156
|
+
~eventConfigs,
|
|
157
|
+
~targetBufferSize,
|
|
158
|
+
~knownHeight=0, // FIXME: Get it from db or fetch before creating FetchState
|
|
159
|
+
~chainId=chainConfig.id,
|
|
160
|
+
// FIXME: Shouldn't set with full history
|
|
161
|
+
~blockLag=Pervasives.max(
|
|
162
|
+
!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth,
|
|
163
|
+
Env.indexingBlockLag->Option.getWithDefault(0),
|
|
164
|
+
),
|
|
165
|
+
~onBlockConfigs?,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
let chainReorgCheckpoints = reorgCheckpoints->Array.keepMapU(reorgCheckpoint => {
|
|
169
|
+
if reorgCheckpoint.chainId === chainConfig.id {
|
|
170
|
+
Some(reorgCheckpoint)
|
|
171
|
+
} else {
|
|
172
|
+
None
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
{
|
|
177
|
+
logger,
|
|
178
|
+
chainConfig,
|
|
179
|
+
sourceManager: SourceManager.make(
|
|
180
|
+
~sources=chainConfig.sources,
|
|
181
|
+
~maxPartitionConcurrency=Env.maxPartitionConcurrency,
|
|
182
|
+
),
|
|
183
|
+
reorgDetection: ReorgDetection.make(
|
|
184
|
+
~chainReorgCheckpoints,
|
|
185
|
+
~maxReorgDepth,
|
|
186
|
+
~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
|
|
187
|
+
),
|
|
188
|
+
safeCheckpointTracking: SafeCheckpointTracking.make(
|
|
189
|
+
~maxReorgDepth,
|
|
190
|
+
~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
|
|
191
|
+
~chainReorgCheckpoints,
|
|
192
|
+
),
|
|
193
|
+
isProgressAtHead: false,
|
|
194
|
+
fetchState,
|
|
195
|
+
firstEventBlockNumber,
|
|
196
|
+
committedProgressBlockNumber: progressBlockNumber,
|
|
197
|
+
timestampCaughtUpToHeadOrEndblock,
|
|
198
|
+
numEventsProcessed,
|
|
199
|
+
numBatchesFetched,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let makeFromConfig = (chainConfig: Config.chain, ~config, ~registrations, ~targetBufferSize) => {
|
|
204
|
+
let logger = Logging.createChild(~params={"chainId": chainConfig.id})
|
|
205
|
+
|
|
206
|
+
make(
|
|
207
|
+
~chainConfig,
|
|
208
|
+
~config,
|
|
209
|
+
~registrations,
|
|
210
|
+
~startBlock=chainConfig.startBlock,
|
|
211
|
+
~endBlock=chainConfig.endBlock,
|
|
212
|
+
~reorgCheckpoints=[],
|
|
213
|
+
~maxReorgDepth=chainConfig.maxReorgDepth,
|
|
214
|
+
~firstEventBlockNumber=None,
|
|
215
|
+
~progressBlockNumber=-1,
|
|
216
|
+
~timestampCaughtUpToHeadOrEndblock=None,
|
|
217
|
+
~numEventsProcessed=0,
|
|
218
|
+
~numBatchesFetched=0,
|
|
219
|
+
~targetBufferSize,
|
|
220
|
+
~logger,
|
|
221
|
+
~dynamicContracts=[],
|
|
222
|
+
~isInReorgThreshold=false,
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* This function allows a chain fetcher to be created from metadata, in particular this is useful for restarting an indexer and making sure it fetches blocks from the same place.
|
|
228
|
+
*/
|
|
229
|
+
let makeFromDbState = async (
|
|
230
|
+
chainConfig: Config.chain,
|
|
231
|
+
~resumedChainState: Persistence.initialChainState,
|
|
232
|
+
~reorgCheckpoints,
|
|
233
|
+
~isInReorgThreshold,
|
|
234
|
+
~config,
|
|
235
|
+
~registrations,
|
|
236
|
+
~targetBufferSize,
|
|
237
|
+
) => {
|
|
238
|
+
let chainId = chainConfig.id
|
|
239
|
+
let logger = Logging.createChild(~params={"chainId": chainId})
|
|
240
|
+
|
|
241
|
+
Prometheus.ProgressEventsCount.set(~processedCount=resumedChainState.numEventsProcessed, ~chainId)
|
|
242
|
+
|
|
243
|
+
let progressBlockNumber =
|
|
244
|
+
// Can be -1 when not set
|
|
245
|
+
resumedChainState.progressBlockNumber >= 0
|
|
246
|
+
? resumedChainState.progressBlockNumber
|
|
247
|
+
: resumedChainState.startBlock - 1
|
|
248
|
+
|
|
249
|
+
make(
|
|
250
|
+
~dynamicContracts=resumedChainState.dynamicContracts,
|
|
251
|
+
~chainConfig,
|
|
252
|
+
~startBlock=resumedChainState.startBlock,
|
|
253
|
+
~endBlock=resumedChainState.endBlock,
|
|
254
|
+
~config,
|
|
255
|
+
~registrations,
|
|
256
|
+
~reorgCheckpoints,
|
|
257
|
+
~maxReorgDepth=resumedChainState.maxReorgDepth,
|
|
258
|
+
~firstEventBlockNumber=resumedChainState.firstEventBlockNumber,
|
|
259
|
+
~progressBlockNumber,
|
|
260
|
+
~timestampCaughtUpToHeadOrEndblock=Env.updateSyncTimeOnRestart
|
|
261
|
+
? None
|
|
262
|
+
: resumedChainState.timestampCaughtUpToHeadOrEndblock,
|
|
263
|
+
~numEventsProcessed=resumedChainState.numEventsProcessed,
|
|
264
|
+
~numBatchesFetched=0,
|
|
265
|
+
~logger,
|
|
266
|
+
~targetBufferSize,
|
|
267
|
+
~isInReorgThreshold,
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Helper function to get the configured start block for a contract from config
|
|
273
|
+
*/
|
|
274
|
+
let getContractStartBlock = (
|
|
275
|
+
config: Config.t,
|
|
276
|
+
~chain: ChainMap.Chain.t,
|
|
277
|
+
~contractName: string,
|
|
278
|
+
): option<int> => {
|
|
279
|
+
let chainConfig = config.chainMap->ChainMap.get(chain)
|
|
280
|
+
chainConfig.contracts
|
|
281
|
+
->Js.Array2.find(contract => contract.name === contractName)
|
|
282
|
+
->Option.flatMap(contract => contract.startBlock)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let runContractRegistersOrThrow = async (
|
|
286
|
+
~itemsWithContractRegister: array<Internal.item>,
|
|
287
|
+
~chain: ChainMap.Chain.t,
|
|
288
|
+
~config: Config.t,
|
|
289
|
+
) => {
|
|
290
|
+
let itemsWithDcs = []
|
|
291
|
+
|
|
292
|
+
let onRegister = (~item: Internal.item, ~contractAddress, ~contractName) => {
|
|
293
|
+
let eventItem = item->Internal.castUnsafeEventItem
|
|
294
|
+
let {blockNumber} = eventItem
|
|
295
|
+
|
|
296
|
+
// Use contract-specific start block if configured, otherwise fall back to registration block
|
|
297
|
+
let contractStartBlock = switch getContractStartBlock(config, ~chain, ~contractName) {
|
|
298
|
+
| Some(configuredStartBlock) => configuredStartBlock
|
|
299
|
+
| None => blockNumber
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let dc: Internal.indexingContract = {
|
|
303
|
+
address: contractAddress,
|
|
304
|
+
contractName,
|
|
305
|
+
startBlock: contractStartBlock,
|
|
306
|
+
registrationBlock: Some(blockNumber),
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
switch item->Internal.getItemDcs {
|
|
310
|
+
| None => {
|
|
311
|
+
item->Internal.setItemDcs([dc])
|
|
312
|
+
itemsWithDcs->Array.push(item)
|
|
313
|
+
}
|
|
314
|
+
| Some(dcs) => dcs->Array.push(dc)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let promises = []
|
|
319
|
+
for idx in 0 to itemsWithContractRegister->Array.length - 1 {
|
|
320
|
+
let item = itemsWithContractRegister->Array.getUnsafe(idx)
|
|
321
|
+
let eventItem = item->Internal.castUnsafeEventItem
|
|
322
|
+
let contractRegister = switch eventItem {
|
|
323
|
+
| {eventConfig: {contractRegister: Some(contractRegister)}} => contractRegister
|
|
324
|
+
| {eventConfig: {contractRegister: None, name: eventName}} =>
|
|
325
|
+
// Unexpected case, since we should pass only events with contract register to this function
|
|
326
|
+
Js.Exn.raiseError("Contract register is not set for event " ++ eventName)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let errorMessage = "Event contractRegister failed, please fix the error to keep the indexer running smoothly"
|
|
330
|
+
|
|
331
|
+
// Catch sync and async errors
|
|
332
|
+
try {
|
|
333
|
+
let params: UserContext.contractRegisterParams = {
|
|
334
|
+
item,
|
|
335
|
+
onRegister,
|
|
336
|
+
config,
|
|
337
|
+
isResolved: false,
|
|
338
|
+
}
|
|
339
|
+
let result = contractRegister(UserContext.getContractRegisterArgs(params))
|
|
340
|
+
|
|
341
|
+
// Even though `contractRegister` always returns a promise,
|
|
342
|
+
// in the ReScript type, but it might return a non-promise value for TS API.
|
|
343
|
+
if result->Promise.isCatchable {
|
|
344
|
+
promises->Array.push(
|
|
345
|
+
result
|
|
346
|
+
->Promise.thenResolve(r => {
|
|
347
|
+
params.isResolved = true
|
|
348
|
+
r
|
|
349
|
+
})
|
|
350
|
+
->Promise.catch(exn => {
|
|
351
|
+
params.isResolved = true
|
|
352
|
+
exn->ErrorHandling.mkLogAndRaise(~msg=errorMessage, ~logger=item->Logging.getItemLogger)
|
|
353
|
+
}),
|
|
354
|
+
)
|
|
355
|
+
} else {
|
|
356
|
+
params.isResolved = true
|
|
357
|
+
}
|
|
358
|
+
} catch {
|
|
359
|
+
| exn =>
|
|
360
|
+
exn->ErrorHandling.mkLogAndRaise(~msg=errorMessage, ~logger=item->Logging.getItemLogger)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if promises->Utils.Array.notEmpty {
|
|
365
|
+
let _ = await Promise.all(promises)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
itemsWithDcs
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let handleQueryResult = (
|
|
372
|
+
chainFetcher: t,
|
|
373
|
+
~query: FetchState.query,
|
|
374
|
+
~newItems,
|
|
375
|
+
~newItemsWithDcs,
|
|
376
|
+
~latestFetchedBlock,
|
|
377
|
+
~knownHeight,
|
|
378
|
+
) => {
|
|
379
|
+
let fs = switch newItemsWithDcs {
|
|
380
|
+
| [] => chainFetcher.fetchState
|
|
381
|
+
| _ => chainFetcher.fetchState->FetchState.registerDynamicContracts(newItemsWithDcs)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
fs
|
|
385
|
+
->FetchState.handleQueryResult(~query, ~latestFetchedBlock, ~newItems)
|
|
386
|
+
->Result.map(fs => {
|
|
387
|
+
...chainFetcher,
|
|
388
|
+
fetchState: fs->FetchState.updateKnownHeight(~knownHeight),
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
Gets the latest item on the front of the queue and returns updated fetcher
|
|
394
|
+
*/
|
|
395
|
+
let hasProcessedToEndblock = (self: t) => {
|
|
396
|
+
let {committedProgressBlockNumber, fetchState} = self
|
|
397
|
+
switch fetchState.endBlock {
|
|
398
|
+
| Some(endBlock) => committedProgressBlockNumber >= endBlock
|
|
399
|
+
| None => false
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let hasNoMoreEventsToProcess = (self: t) => {
|
|
404
|
+
self.fetchState->FetchState.bufferSize === 0
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let getHighestBlockBelowThreshold = (cf: t): int => {
|
|
408
|
+
let highestBlockBelowThreshold = cf.fetchState.knownHeight - cf.chainConfig.maxReorgDepth
|
|
409
|
+
highestBlockBelowThreshold < 0 ? 0 : highestBlockBelowThreshold
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
Finds the last known valid block number below the reorg block
|
|
414
|
+
If not found, returns the highest block below threshold
|
|
415
|
+
*/
|
|
416
|
+
let getLastKnownValidBlock = async (
|
|
417
|
+
chainFetcher: t,
|
|
418
|
+
~reorgBlockNumber: int,
|
|
419
|
+
//Parameter used for dependency injecting in tests
|
|
420
|
+
~getBlockHashes=(chainFetcher.sourceManager->SourceManager.getActiveSource).getBlockHashes,
|
|
421
|
+
) => {
|
|
422
|
+
// Improtant: It's important to not include the reorg detection block number
|
|
423
|
+
// because there might be different instances of the source
|
|
424
|
+
// with mismatching hashes between them.
|
|
425
|
+
// So we MUST always rollback the block number where we detected a reorg.
|
|
426
|
+
let scannedBlockNumbers =
|
|
427
|
+
chainFetcher.reorgDetection->ReorgDetection.getThresholdBlockNumbersBelowBlock(
|
|
428
|
+
~blockNumber=reorgBlockNumber,
|
|
429
|
+
~knownHeight=chainFetcher.fetchState.knownHeight,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
let getBlockHashes = blockNumbers => {
|
|
433
|
+
getBlockHashes(~blockNumbers, ~logger=chainFetcher.logger)->Promise.thenResolve(res =>
|
|
434
|
+
switch res {
|
|
435
|
+
| Ok(v) => v
|
|
436
|
+
| Error(exn) =>
|
|
437
|
+
exn->ErrorHandling.mkLogAndRaise(
|
|
438
|
+
~msg="Failed to fetch blockHashes for given blockNumbers during rollback",
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
switch scannedBlockNumbers {
|
|
445
|
+
| [] => chainFetcher->getHighestBlockBelowThreshold
|
|
446
|
+
| _ => {
|
|
447
|
+
let blockNumbersAndHashes = await getBlockHashes(scannedBlockNumbers)
|
|
448
|
+
|
|
449
|
+
switch chainFetcher.reorgDetection->ReorgDetection.getLatestValidScannedBlock(
|
|
450
|
+
~blockNumbersAndHashes,
|
|
451
|
+
) {
|
|
452
|
+
| Some(blockNumber) => blockNumber
|
|
453
|
+
| None => chainFetcher->getHighestBlockBelowThreshold
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
let isActivelyIndexing = (chainFetcher: t) => chainFetcher.fetchState->FetchState.isActivelyIndexing
|