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
package/src/tui/Tui.res.mjs
CHANGED
|
@@ -6,10 +6,10 @@ import * as Ink$1 from "ink";
|
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import * as SyncETA from "./components/SyncETA.res.mjs";
|
|
8
8
|
import * as TuiData from "./components/TuiData.res.mjs";
|
|
9
|
-
import * as ChainMap from "../ChainMap.res.mjs";
|
|
10
9
|
import * as Messages from "./components/Messages.res.mjs";
|
|
10
|
+
import * as ChainState from "../ChainState.res.mjs";
|
|
11
11
|
import * as FetchState from "../FetchState.res.mjs";
|
|
12
|
-
import * as
|
|
12
|
+
import * as IndexerState from "../IndexerState.res.mjs";
|
|
13
13
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
14
14
|
import InkBigText from "ink-big-text";
|
|
15
15
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
@@ -207,47 +207,47 @@ function Tui$App(props) {
|
|
|
207
207
|
clearInterval(intervalId);
|
|
208
208
|
};
|
|
209
209
|
}, [getState]);
|
|
210
|
-
let chains =
|
|
211
|
-
let numEventsProcessed =
|
|
212
|
-
let
|
|
213
|
-
let
|
|
214
|
-
let
|
|
215
|
-
let
|
|
216
|
-
let
|
|
217
|
-
|
|
218
|
-
|
|
210
|
+
let chains = Object.values(IndexerState.chainStates(state)).map(cs => {
|
|
211
|
+
let numEventsProcessed = ChainState.numEventsProcessed(cs);
|
|
212
|
+
let fetchState = ChainState.fetchState(cs);
|
|
213
|
+
let committedProgressBlockNumber = ChainState.committedProgressBlockNumber(cs);
|
|
214
|
+
let timestampCaughtUpToHeadOrEndblock = ChainState.timestampCaughtUpToHeadOrEndblock(cs);
|
|
215
|
+
let sourceManager = ChainState.sourceManager(cs);
|
|
216
|
+
let latestFetchedBlockNumber = Primitive_int.max(FetchState.bufferBlockNumber(fetchState), 0);
|
|
217
|
+
let hasProcessedToEndblock = ChainState.hasProcessedToEndblock(cs);
|
|
218
|
+
let knownHeight = hasProcessedToEndblock ? Stdlib_Option.getOr(fetchState.endBlock, fetchState.knownHeight) : fetchState.knownHeight;
|
|
219
|
+
let firstEventBlock = fetchState.firstEventBlock;
|
|
220
|
+
let progress = hasProcessedToEndblock ? ({
|
|
219
221
|
TAG: "Synced",
|
|
220
222
|
_0: {
|
|
221
223
|
firstEventBlockNumber: Stdlib_Option.getOr(firstEventBlock, 0),
|
|
222
|
-
latestProcessedBlock:
|
|
224
|
+
latestProcessedBlock: committedProgressBlockNumber,
|
|
223
225
|
numEventsProcessed: numEventsProcessed,
|
|
224
|
-
timestampCaughtUpToHeadOrEndblock: Stdlib_Option.getOr(
|
|
226
|
+
timestampCaughtUpToHeadOrEndblock: Stdlib_Option.getOr(timestampCaughtUpToHeadOrEndblock, new Date(Date.now()))
|
|
225
227
|
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
let match$1 = state.ctx.config.ecosystem.name;
|
|
228
|
+
}) : (
|
|
229
|
+
firstEventBlock !== undefined ? (
|
|
230
|
+
timestampCaughtUpToHeadOrEndblock !== undefined ? ({
|
|
231
|
+
TAG: "Synced",
|
|
232
|
+
_0: {
|
|
233
|
+
firstEventBlockNumber: firstEventBlock,
|
|
234
|
+
latestProcessedBlock: committedProgressBlockNumber,
|
|
235
|
+
numEventsProcessed: numEventsProcessed,
|
|
236
|
+
timestampCaughtUpToHeadOrEndblock: timestampCaughtUpToHeadOrEndblock
|
|
237
|
+
}
|
|
238
|
+
}) : ({
|
|
239
|
+
TAG: "Syncing",
|
|
240
|
+
_0: {
|
|
241
|
+
firstEventBlockNumber: firstEventBlock,
|
|
242
|
+
latestProcessedBlock: committedProgressBlockNumber,
|
|
243
|
+
numEventsProcessed: numEventsProcessed
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
) : "SearchingForEvents"
|
|
247
|
+
);
|
|
248
|
+
let match = IndexerState.config(state).ecosystem.name;
|
|
249
249
|
let tmp;
|
|
250
|
-
switch (match
|
|
250
|
+
switch (match) {
|
|
251
251
|
case "evm" :
|
|
252
252
|
case "fuel" :
|
|
253
253
|
tmp = "Block";
|
|
@@ -257,22 +257,22 @@ function Tui$App(props) {
|
|
|
257
257
|
break;
|
|
258
258
|
}
|
|
259
259
|
return {
|
|
260
|
-
chainId:
|
|
260
|
+
chainId: ChainState.chainConfig(cs).id.toString(),
|
|
261
261
|
eventsProcessed: numEventsProcessed,
|
|
262
|
-
progressBlock:
|
|
262
|
+
progressBlock: committedProgressBlockNumber < fetchState.startBlock ? fetchState.startBlock : committedProgressBlockNumber,
|
|
263
263
|
bufferBlock: latestFetchedBlockNumber,
|
|
264
|
-
sourceBlock:
|
|
265
|
-
startBlock:
|
|
266
|
-
endBlock:
|
|
267
|
-
firstEventBlockNumber:
|
|
268
|
-
poweredByHyperSync: SourceManager.getActiveSource(
|
|
264
|
+
sourceBlock: fetchState.knownHeight,
|
|
265
|
+
startBlock: fetchState.startBlock,
|
|
266
|
+
endBlock: fetchState.endBlock,
|
|
267
|
+
firstEventBlockNumber: fetchState.firstEventBlock,
|
|
268
|
+
poweredByHyperSync: SourceManager.getActiveSource(sourceManager).poweredByHyperSync,
|
|
269
269
|
progress: progress,
|
|
270
270
|
latestFetchedBlockNumber: latestFetchedBlockNumber,
|
|
271
271
|
knownHeight: knownHeight,
|
|
272
272
|
blockUnit: tmp,
|
|
273
|
-
rateLimitTimeMs: SourceManager.getRateLimitTimeMs(
|
|
274
|
-
isRateLimited: SourceManager.isRateLimited(
|
|
275
|
-
rateLimitResetInMs: SourceManager.getRateLimitResetInMs(
|
|
273
|
+
rateLimitTimeMs: SourceManager.getRateLimitTimeMs(sourceManager),
|
|
274
|
+
isRateLimited: SourceManager.isRateLimited(sourceManager),
|
|
275
|
+
rateLimitResetInMs: SourceManager.getRateLimitResetInMs(sourceManager)
|
|
276
276
|
};
|
|
277
277
|
});
|
|
278
278
|
let totalEventsProcessed = Stdlib_Array.reduce(chains, 0, (acc, chain) => acc + chain.eventsProcessed);
|
|
@@ -329,7 +329,7 @@ function Tui$App(props) {
|
|
|
329
329
|
tmp = null;
|
|
330
330
|
}
|
|
331
331
|
let defaultPassword = "testing";
|
|
332
|
-
let match$1 =
|
|
332
|
+
let match$1 = IndexerState.config(state).storage.clickhouse;
|
|
333
333
|
let match$2 = Env.ClickHouse.host();
|
|
334
334
|
return JsxRuntime.jsxs(Ink$1.Box, {
|
|
335
335
|
children: [
|
|
@@ -362,7 +362,7 @@ function Tui$App(props) {
|
|
|
362
362
|
}),
|
|
363
363
|
JsxRuntime.jsx(SyncETA.make, {
|
|
364
364
|
chains: chains,
|
|
365
|
-
indexerStartTime:
|
|
365
|
+
indexerStartTime: IndexerState.indexerStartTime(state)
|
|
366
366
|
}),
|
|
367
367
|
tmp,
|
|
368
368
|
JsxRuntime.jsx(Ink.Newline.make, {}),
|
|
@@ -383,7 +383,7 @@ function Tui$App(props) {
|
|
|
383
383
|
],
|
|
384
384
|
flexDirection: "row"
|
|
385
385
|
}),
|
|
386
|
-
|
|
386
|
+
IndexerState.config(state).isDev ? JsxRuntime.jsxs(Ink$1.Box, {
|
|
387
387
|
children: [
|
|
388
388
|
JsxRuntime.jsx(Ink$1.Text, {
|
|
389
389
|
children: "Dev Console: "
|
|
@@ -410,7 +410,7 @@ function Tui$App(props) {
|
|
|
410
410
|
flexDirection: "row"
|
|
411
411
|
}) : null,
|
|
412
412
|
JsxRuntime.jsx(Messages.make, {
|
|
413
|
-
config:
|
|
413
|
+
config: IndexerState.config(state)
|
|
414
414
|
})
|
|
415
415
|
],
|
|
416
416
|
flexDirection: "column"
|
package/src/ChainFetcher.res
DELETED
|
@@ -1,519 +0,0 @@
|
|
|
1
|
-
//A filter should return true if the event should be kept and isValid should return
|
|
2
|
-
//false when the filter should be removed/cleaned up
|
|
3
|
-
type processingFilter = {
|
|
4
|
-
filter: Internal.item => bool,
|
|
5
|
-
isValid: (~fetchState: FetchState.t) => bool,
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
type t = {
|
|
9
|
-
logger: Pino.t,
|
|
10
|
-
fetchState: FetchState.t,
|
|
11
|
-
sourceManager: SourceManager.t,
|
|
12
|
-
chainConfig: Config.chain,
|
|
13
|
-
isProgressAtHead: bool,
|
|
14
|
-
timestampCaughtUpToHeadOrEndblock: option<Date.t>,
|
|
15
|
-
committedProgressBlockNumber: int,
|
|
16
|
-
numEventsProcessed: float,
|
|
17
|
-
reorgDetection: ReorgDetection.t,
|
|
18
|
-
safeCheckpointTracking: option<SafeCheckpointTracking.t>,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
//CONSTRUCTION
|
|
22
|
-
let configAddresses = (chainConfig: Config.chain): array<Internal.indexingAddress> => {
|
|
23
|
-
let addresses = []
|
|
24
|
-
chainConfig.contracts->Array.forEach(contract => {
|
|
25
|
-
contract.addresses->Array.forEach(address => {
|
|
26
|
-
addresses->Array.push({
|
|
27
|
-
Internal.address,
|
|
28
|
-
contractName: contract.name,
|
|
29
|
-
registrationBlock: -1,
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
})
|
|
33
|
-
addresses
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let make = (
|
|
37
|
-
~chainConfig: Config.chain,
|
|
38
|
-
~indexingAddresses: array<Internal.indexingAddress>,
|
|
39
|
-
~startBlock,
|
|
40
|
-
~endBlock,
|
|
41
|
-
~firstEventBlock=None,
|
|
42
|
-
~progressBlockNumber,
|
|
43
|
-
~config: Config.t,
|
|
44
|
-
~registrations: HandlerRegister.registrations,
|
|
45
|
-
~targetBufferSize,
|
|
46
|
-
~logger,
|
|
47
|
-
~timestampCaughtUpToHeadOrEndblock,
|
|
48
|
-
~numEventsProcessed,
|
|
49
|
-
~isInReorgThreshold,
|
|
50
|
-
~isRealtime,
|
|
51
|
-
~reorgCheckpoints: array<Internal.reorgCheckpoint>,
|
|
52
|
-
~maxReorgDepth,
|
|
53
|
-
~knownHeight=0,
|
|
54
|
-
~reducedPollingInterval=?,
|
|
55
|
-
): t => {
|
|
56
|
-
// We don't need the router itself, but only validation logic,
|
|
57
|
-
// since now event router is created for selection of events
|
|
58
|
-
// and validation doesn't work correctly in routers.
|
|
59
|
-
// Ideally to split it into two different parts.
|
|
60
|
-
let eventRouter = EventRouter.empty()
|
|
61
|
-
|
|
62
|
-
// Aggregate events we want to fetch
|
|
63
|
-
let eventConfigs: array<Internal.eventConfig> = []
|
|
64
|
-
|
|
65
|
-
let notRegisteredEvents = []
|
|
66
|
-
|
|
67
|
-
chainConfig.contracts->Array.forEach(contract => {
|
|
68
|
-
let contractName = contract.name
|
|
69
|
-
|
|
70
|
-
contract.events->Array.forEach(eventConfig => {
|
|
71
|
-
let {isWildcard} = eventConfig
|
|
72
|
-
let hasContractRegister = eventConfig.contractRegister->Option.isSome
|
|
73
|
-
|
|
74
|
-
// Should validate the events
|
|
75
|
-
eventRouter->EventRouter.addOrThrow(
|
|
76
|
-
eventConfig.id,
|
|
77
|
-
(),
|
|
78
|
-
~contractName,
|
|
79
|
-
~chain=ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id),
|
|
80
|
-
~eventName=eventConfig.name,
|
|
81
|
-
~isWildcard,
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
// Filter out non-preRegistration events on preRegistration phase
|
|
85
|
-
// so we don't care about it in fetch state and workers anymore
|
|
86
|
-
let shouldBeIncluded = if config.enableRawEvents {
|
|
87
|
-
true
|
|
88
|
-
} else {
|
|
89
|
-
let isRegistered = hasContractRegister || eventConfig.handler->Option.isSome
|
|
90
|
-
if !isRegistered {
|
|
91
|
-
notRegisteredEvents->Array.push(eventConfig)
|
|
92
|
-
}
|
|
93
|
-
isRegistered
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Check if event has Static([]) filters (from a dynamic where
|
|
97
|
-
// callback returning `false` / SkipAll for this chain).
|
|
98
|
-
// If so, skip it entirely - it should never be fetched
|
|
99
|
-
let shouldSkip = try {
|
|
100
|
-
let getEventFiltersOrThrow = (
|
|
101
|
-
eventConfig->(Utils.magic: Internal.eventConfig => Internal.evmEventConfig)
|
|
102
|
-
).getEventFiltersOrThrow
|
|
103
|
-
|
|
104
|
-
// Check for non-evm chains
|
|
105
|
-
if (
|
|
106
|
-
getEventFiltersOrThrow->(Utils.magic: (ChainMap.Chain.t => Internal.eventFilters) => bool)
|
|
107
|
-
) {
|
|
108
|
-
switch getEventFiltersOrThrow(ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)) {
|
|
109
|
-
| Static([]) => true
|
|
110
|
-
| _ => false
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
false
|
|
114
|
-
}
|
|
115
|
-
} catch {
|
|
116
|
-
// Can throw when filter is invalid
|
|
117
|
-
// Don't skip an event in this case. Let it throw in a better place - source code
|
|
118
|
-
| _ => false
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if shouldBeIncluded && !shouldSkip {
|
|
122
|
-
eventConfigs->Array.push(eventConfig)
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
switch contract.startBlock {
|
|
127
|
-
| Some(startBlock) if startBlock < chainConfig.startBlock =>
|
|
128
|
-
JsError.throwWithMessage(
|
|
129
|
-
`The start block for contract "${contractName}" is less than the chain start block. This is not supported yet.`,
|
|
130
|
-
)
|
|
131
|
-
| _ => ()
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
if notRegisteredEvents->Utils.Array.notEmpty {
|
|
136
|
-
logger->Logging.childInfo(
|
|
137
|
-
`The event${if notRegisteredEvents->Array.length > 1 {
|
|
138
|
-
"s"
|
|
139
|
-
} else {
|
|
140
|
-
""
|
|
141
|
-
}} ${notRegisteredEvents
|
|
142
|
-
->Array.map(eventConfig => `${eventConfig.contractName}.${eventConfig.name}`)
|
|
143
|
-
->Array.joinUnsafe(", ")} don't have an event handler and skipped for indexing.`,
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
let onBlockConfigs =
|
|
148
|
-
registrations.onBlockByChainId->Utils.Dict.dangerouslyGetNonOption(chainConfig.id->Int.toString)
|
|
149
|
-
switch onBlockConfigs {
|
|
150
|
-
| Some(onBlockConfigs) =>
|
|
151
|
-
// TODO: Move it to the HandlerRegister module
|
|
152
|
-
// so the error is thrown with better stack trace
|
|
153
|
-
onBlockConfigs->Array.forEach(onBlockConfig => {
|
|
154
|
-
if onBlockConfig.startBlock->Option.getOr(startBlock) < startBlock {
|
|
155
|
-
JsError.throwWithMessage(
|
|
156
|
-
`The start block for onBlock handler "${onBlockConfig.name}" is less than the chain start block (${startBlock->Int.toString}). This is not supported yet.`,
|
|
157
|
-
)
|
|
158
|
-
}
|
|
159
|
-
switch endBlock {
|
|
160
|
-
| Some(chainEndBlock) =>
|
|
161
|
-
if onBlockConfig.endBlock->Option.getOr(chainEndBlock) > chainEndBlock {
|
|
162
|
-
JsError.throwWithMessage(
|
|
163
|
-
`The end block for onBlock handler "${onBlockConfig.name}" is greater than the chain end block (${chainEndBlock->Int.toString}). This is not supported yet.`,
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
| None => ()
|
|
167
|
-
}
|
|
168
|
-
})
|
|
169
|
-
| None => ()
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
let fetchState = FetchState.make(
|
|
173
|
-
~maxAddrInPartition=config.maxAddrInPartition,
|
|
174
|
-
~addresses=indexingAddresses,
|
|
175
|
-
~progressBlockNumber,
|
|
176
|
-
~startBlock,
|
|
177
|
-
~endBlock,
|
|
178
|
-
~eventConfigs,
|
|
179
|
-
~targetBufferSize,
|
|
180
|
-
~knownHeight,
|
|
181
|
-
~chainId=chainConfig.id,
|
|
182
|
-
// FIXME: Shouldn't set with full history
|
|
183
|
-
~blockLag=Pervasives.max(
|
|
184
|
-
!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth,
|
|
185
|
-
chainConfig.blockLag,
|
|
186
|
-
),
|
|
187
|
-
~onBlockConfigs?,
|
|
188
|
-
~firstEventBlock,
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
let chainReorgCheckpoints = reorgCheckpoints->Array.filterMap(reorgCheckpoint => {
|
|
192
|
-
if reorgCheckpoint.chainId === chainConfig.id {
|
|
193
|
-
Some(reorgCheckpoint)
|
|
194
|
-
} else {
|
|
195
|
-
None
|
|
196
|
-
}
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
// Create sources lazily here - this is where API token validation happens
|
|
200
|
-
let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)
|
|
201
|
-
let lowercaseAddresses = config.lowercaseAddresses
|
|
202
|
-
let sources = switch chainConfig.sourceConfig {
|
|
203
|
-
| Config.EvmSourceConfig({hypersync, rpcs}) =>
|
|
204
|
-
// Build Internal.evmContractConfig from contracts for EvmChain.makeSources
|
|
205
|
-
let evmContracts: array<Internal.evmContractConfig> = chainConfig.contracts->Array.map((
|
|
206
|
-
contract
|
|
207
|
-
): Internal.evmContractConfig => {
|
|
208
|
-
name: contract.name,
|
|
209
|
-
abi: contract.abi,
|
|
210
|
-
events: contract.events->(
|
|
211
|
-
Utils.magic: array<Internal.eventConfig> => array<Internal.evmEventConfig>
|
|
212
|
-
),
|
|
213
|
-
})
|
|
214
|
-
let evmRpcs: array<EvmChain.rpc> = rpcs->Array.map((rpc): EvmChain.rpc => {
|
|
215
|
-
let syncConfig = rpc.syncConfig
|
|
216
|
-
let ws = rpc.ws
|
|
217
|
-
{
|
|
218
|
-
url: rpc.url,
|
|
219
|
-
sourceFor: rpc.sourceFor,
|
|
220
|
-
?syncConfig,
|
|
221
|
-
?ws,
|
|
222
|
-
}
|
|
223
|
-
})
|
|
224
|
-
EvmChain.makeSources(
|
|
225
|
-
~chain,
|
|
226
|
-
~contracts=evmContracts,
|
|
227
|
-
~hyperSync=hypersync,
|
|
228
|
-
~rpcs=evmRpcs,
|
|
229
|
-
~lowercaseAddresses,
|
|
230
|
-
)
|
|
231
|
-
| Config.FuelSourceConfig({hypersync}) => [HyperFuelSource.make({chain, endpointUrl: hypersync})]
|
|
232
|
-
| Config.SvmSourceConfig({hypersync, rpc}) =>
|
|
233
|
-
switch (hypersync, rpc) {
|
|
234
|
-
| (None, None) =>
|
|
235
|
-
JsError.throwWithMessage(
|
|
236
|
-
`Chain ${chain->ChainMap.Chain.toChainId->Int.toString} has no SVM data source`,
|
|
237
|
-
)
|
|
238
|
-
| (None, Some(rpc)) => [Svm.makeRPCSource(~chain, ~rpc)]
|
|
239
|
-
| (Some(hypersyncUrl), _) =>
|
|
240
|
-
// HyperSync drives instruction sync. A configured RPC is ignored for now
|
|
241
|
-
// (RPC fallback isn't wired up yet).
|
|
242
|
-
let svmEventConfigs =
|
|
243
|
-
chainConfig.contracts
|
|
244
|
-
->Array.flatMap(contract => contract.events)
|
|
245
|
-
->(Utils.magic: array<Internal.eventConfig> => array<Internal.svmInstructionEventConfig>)
|
|
246
|
-
let apiToken = Env.envioApiToken
|
|
247
|
-
[
|
|
248
|
-
SvmHyperSyncSource.make({
|
|
249
|
-
chain,
|
|
250
|
-
endpointUrl: hypersyncUrl,
|
|
251
|
-
apiToken,
|
|
252
|
-
eventConfigs: svmEventConfigs,
|
|
253
|
-
clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
|
|
254
|
-
}),
|
|
255
|
-
]
|
|
256
|
-
}
|
|
257
|
-
// For tests: use ready-to-use sources directly
|
|
258
|
-
| Config.CustomSources(sources) => sources
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
{
|
|
262
|
-
logger,
|
|
263
|
-
chainConfig,
|
|
264
|
-
sourceManager: SourceManager.make(
|
|
265
|
-
~sources,
|
|
266
|
-
~maxPartitionConcurrency=Env.maxPartitionConcurrency,
|
|
267
|
-
~isRealtime,
|
|
268
|
-
~reducedPollingInterval?,
|
|
269
|
-
),
|
|
270
|
-
reorgDetection: ReorgDetection.make(
|
|
271
|
-
~chainReorgCheckpoints,
|
|
272
|
-
~maxReorgDepth,
|
|
273
|
-
~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
|
|
274
|
-
),
|
|
275
|
-
safeCheckpointTracking: SafeCheckpointTracking.make(
|
|
276
|
-
~maxReorgDepth,
|
|
277
|
-
~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
|
|
278
|
-
~chainReorgCheckpoints,
|
|
279
|
-
),
|
|
280
|
-
isProgressAtHead: false,
|
|
281
|
-
fetchState,
|
|
282
|
-
committedProgressBlockNumber: progressBlockNumber,
|
|
283
|
-
timestampCaughtUpToHeadOrEndblock,
|
|
284
|
-
numEventsProcessed,
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
let makeFromConfig = (
|
|
289
|
-
chainConfig: Config.chain,
|
|
290
|
-
~config,
|
|
291
|
-
~registrations,
|
|
292
|
-
~targetBufferSize,
|
|
293
|
-
~knownHeight,
|
|
294
|
-
) => {
|
|
295
|
-
let logger = Logging.createChild(~params={"chainId": chainConfig.id})
|
|
296
|
-
|
|
297
|
-
make(
|
|
298
|
-
~chainConfig,
|
|
299
|
-
~config,
|
|
300
|
-
~registrations,
|
|
301
|
-
~startBlock=chainConfig.startBlock,
|
|
302
|
-
~endBlock=chainConfig.endBlock,
|
|
303
|
-
~reorgCheckpoints=[],
|
|
304
|
-
~maxReorgDepth=chainConfig.maxReorgDepth,
|
|
305
|
-
~progressBlockNumber=-1,
|
|
306
|
-
~timestampCaughtUpToHeadOrEndblock=None,
|
|
307
|
-
~numEventsProcessed=0.,
|
|
308
|
-
~targetBufferSize,
|
|
309
|
-
~logger,
|
|
310
|
-
~indexingAddresses=configAddresses(chainConfig),
|
|
311
|
-
~isInReorgThreshold=false,
|
|
312
|
-
~isRealtime=false,
|
|
313
|
-
~knownHeight,
|
|
314
|
-
)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* 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.
|
|
319
|
-
*/
|
|
320
|
-
let makeFromDbState = (
|
|
321
|
-
chainConfig: Config.chain,
|
|
322
|
-
~resumedChainState: Persistence.initialChainState,
|
|
323
|
-
~reorgCheckpoints,
|
|
324
|
-
~isInReorgThreshold,
|
|
325
|
-
~isRealtime,
|
|
326
|
-
~config,
|
|
327
|
-
~registrations,
|
|
328
|
-
~targetBufferSize,
|
|
329
|
-
~reducedPollingInterval=?,
|
|
330
|
-
) => {
|
|
331
|
-
let chainId = chainConfig.id
|
|
332
|
-
let logger = Logging.createChild(~params={"chainId": chainId})
|
|
333
|
-
|
|
334
|
-
Prometheus.ProgressEventsCount.set(~processedCount=resumedChainState.numEventsProcessed, ~chainId)
|
|
335
|
-
|
|
336
|
-
let progressBlockNumber =
|
|
337
|
-
// Can be -1 when not set
|
|
338
|
-
resumedChainState.progressBlockNumber >= 0
|
|
339
|
-
? resumedChainState.progressBlockNumber
|
|
340
|
-
: resumedChainState.startBlock - 1
|
|
341
|
-
|
|
342
|
-
make(
|
|
343
|
-
~indexingAddresses=resumedChainState.indexingAddresses,
|
|
344
|
-
~chainConfig,
|
|
345
|
-
~startBlock=resumedChainState.startBlock,
|
|
346
|
-
~endBlock=resumedChainState.endBlock,
|
|
347
|
-
~config,
|
|
348
|
-
~registrations,
|
|
349
|
-
~reorgCheckpoints,
|
|
350
|
-
~maxReorgDepth=resumedChainState.maxReorgDepth,
|
|
351
|
-
~firstEventBlock=resumedChainState.firstEventBlockNumber,
|
|
352
|
-
~progressBlockNumber,
|
|
353
|
-
~timestampCaughtUpToHeadOrEndblock=Env.updateSyncTimeOnRestart
|
|
354
|
-
? None
|
|
355
|
-
: resumedChainState.timestampCaughtUpToHeadOrEndblock,
|
|
356
|
-
~numEventsProcessed=resumedChainState.numEventsProcessed,
|
|
357
|
-
~logger,
|
|
358
|
-
~targetBufferSize,
|
|
359
|
-
~isInReorgThreshold,
|
|
360
|
-
~isRealtime,
|
|
361
|
-
~knownHeight=resumedChainState.sourceBlockNumber,
|
|
362
|
-
~reducedPollingInterval?,
|
|
363
|
-
)
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
let runContractRegistersOrThrow = async (
|
|
367
|
-
~itemsWithContractRegister: array<Internal.item>,
|
|
368
|
-
~config: Config.t,
|
|
369
|
-
) => {
|
|
370
|
-
let itemsWithDcs = []
|
|
371
|
-
|
|
372
|
-
let onRegister = (~item: Internal.item, ~contractAddress, ~contractName) => {
|
|
373
|
-
let eventItem = item->Internal.castUnsafeEventItem
|
|
374
|
-
let {blockNumber} = eventItem
|
|
375
|
-
|
|
376
|
-
let dc: Internal.indexingAddress = {
|
|
377
|
-
address: contractAddress,
|
|
378
|
-
contractName,
|
|
379
|
-
registrationBlock: blockNumber,
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
switch item->Internal.getItemDcs {
|
|
383
|
-
| None => {
|
|
384
|
-
item->Internal.setItemDcs([dc])
|
|
385
|
-
itemsWithDcs->Array.push(item)
|
|
386
|
-
}
|
|
387
|
-
| Some(dcs) => dcs->Array.push(dc)
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
let promises = []
|
|
392
|
-
for idx in 0 to itemsWithContractRegister->Array.length - 1 {
|
|
393
|
-
let item = itemsWithContractRegister->Array.getUnsafe(idx)
|
|
394
|
-
let eventItem = item->Internal.castUnsafeEventItem
|
|
395
|
-
let contractRegister = switch eventItem {
|
|
396
|
-
| {eventConfig: {contractRegister: Some(contractRegister)}} => contractRegister
|
|
397
|
-
| {eventConfig: {contractRegister: None, name: eventName}} =>
|
|
398
|
-
// Unexpected case, since we should pass only events with contract register to this function
|
|
399
|
-
JsError.throwWithMessage("Contract register is not set for event " ++ eventName)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
let errorMessage = "Event contractRegister failed, please fix the error to keep the indexer running smoothly"
|
|
403
|
-
|
|
404
|
-
// Catch sync and async errors
|
|
405
|
-
try {
|
|
406
|
-
let params: UserContext.contractRegisterParams = {
|
|
407
|
-
item,
|
|
408
|
-
onRegister,
|
|
409
|
-
config,
|
|
410
|
-
isResolved: false,
|
|
411
|
-
}
|
|
412
|
-
let result = contractRegister(UserContext.getContractRegisterArgs(params))
|
|
413
|
-
|
|
414
|
-
// Even though `contractRegister` always returns a promise,
|
|
415
|
-
// in the ReScript type, but it might return a non-promise value for TS API.
|
|
416
|
-
if result->Utils.Promise.isCatchable {
|
|
417
|
-
promises->Array.push(
|
|
418
|
-
result
|
|
419
|
-
->Promise.thenResolve(r => {
|
|
420
|
-
params.isResolved = true
|
|
421
|
-
r
|
|
422
|
-
})
|
|
423
|
-
->Promise.catch(exn => {
|
|
424
|
-
params.isResolved = true
|
|
425
|
-
exn->ErrorHandling.mkLogAndRaise(~msg=errorMessage, ~logger=item->Logging.getItemLogger)
|
|
426
|
-
}),
|
|
427
|
-
)
|
|
428
|
-
} else {
|
|
429
|
-
params.isResolved = true
|
|
430
|
-
}
|
|
431
|
-
} catch {
|
|
432
|
-
| exn =>
|
|
433
|
-
exn->ErrorHandling.mkLogAndRaise(~msg=errorMessage, ~logger=item->Logging.getItemLogger)
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if promises->Utils.Array.notEmpty {
|
|
438
|
-
let _ = await Promise.all(promises)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
itemsWithDcs
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
let handleQueryResult = (
|
|
445
|
-
chainFetcher: t,
|
|
446
|
-
~query: FetchState.query,
|
|
447
|
-
~newItems,
|
|
448
|
-
~newItemsWithDcs,
|
|
449
|
-
~latestFetchedBlock,
|
|
450
|
-
~knownHeight,
|
|
451
|
-
) => {
|
|
452
|
-
let fs = switch newItemsWithDcs {
|
|
453
|
-
| [] => chainFetcher.fetchState
|
|
454
|
-
| _ => chainFetcher.fetchState->FetchState.registerDynamicContracts(newItemsWithDcs)
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
{
|
|
458
|
-
...chainFetcher,
|
|
459
|
-
fetchState: fs
|
|
460
|
-
->FetchState.handleQueryResult(~query, ~latestFetchedBlock, ~newItems)
|
|
461
|
-
->FetchState.updateKnownHeight(~knownHeight),
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
Gets the latest item on the front of the queue and returns updated fetcher
|
|
467
|
-
*/
|
|
468
|
-
let hasProcessedToEndblock = (self: t) => {
|
|
469
|
-
let {committedProgressBlockNumber, fetchState} = self
|
|
470
|
-
switch fetchState.endBlock {
|
|
471
|
-
| Some(endBlock) => committedProgressBlockNumber >= endBlock
|
|
472
|
-
| None => false
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
let hasNoMoreEventsToProcess = (self: t) => {
|
|
477
|
-
self.fetchState->FetchState.bufferSize === 0
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
let getHighestBlockBelowThreshold = (cf: t): int => {
|
|
481
|
-
let highestBlockBelowThreshold = cf.fetchState.knownHeight - cf.chainConfig.maxReorgDepth
|
|
482
|
-
highestBlockBelowThreshold < 0 ? 0 : highestBlockBelowThreshold
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
Finds the last known valid block number below the reorg block
|
|
487
|
-
If not found, returns the highest block below threshold
|
|
488
|
-
*/
|
|
489
|
-
let getLastKnownValidBlock = async (chainFetcher: t, ~reorgBlockNumber: int, ~isRealtime: bool) => {
|
|
490
|
-
// Don't include the reorg block itself — different source instances
|
|
491
|
-
// may have mismatching hashes at the head, so we always rollback
|
|
492
|
-
// the block where we detected the reorg.
|
|
493
|
-
let scannedBlockNumbers =
|
|
494
|
-
chainFetcher.reorgDetection->ReorgDetection.getThresholdBlockNumbersBelowBlock(
|
|
495
|
-
~blockNumber=reorgBlockNumber,
|
|
496
|
-
~knownHeight=chainFetcher.fetchState.knownHeight,
|
|
497
|
-
)
|
|
498
|
-
|
|
499
|
-
switch scannedBlockNumbers {
|
|
500
|
-
| [] => chainFetcher->getHighestBlockBelowThreshold
|
|
501
|
-
| _ => {
|
|
502
|
-
let blockNumbersAndHashes = await chainFetcher.sourceManager->SourceManager.getBlockHashes(
|
|
503
|
-
~blockNumbers=scannedBlockNumbers,
|
|
504
|
-
~isRealtime,
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
switch chainFetcher.reorgDetection->ReorgDetection.getLatestValidScannedBlock(
|
|
508
|
-
~blockNumbersAndHashes,
|
|
509
|
-
) {
|
|
510
|
-
| Some(blockNumber) => blockNumber
|
|
511
|
-
| None => chainFetcher->getHighestBlockBelowThreshold
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
let isActivelyIndexing = (chainFetcher: t) => chainFetcher.fetchState->FetchState.isActivelyIndexing
|
|
518
|
-
|
|
519
|
-
let isReady = (chainFetcher: t) => chainFetcher.timestampCaughtUpToHeadOrEndblock !== None
|