envio 2.16.1 → 2.17.1
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 -5
- package/src/Address.gen.ts +1 -1
- package/src/Address.res +1 -1
- package/src/ContractAddressingMap.res +115 -0
- package/src/Envio.gen.ts +8 -0
- package/src/Envio.res +12 -0
- package/src/ErrorHandling.res +61 -0
- package/src/EventUtils.res +87 -0
- package/src/Internal.gen.ts +1 -1
- package/src/Internal.res +10 -1
- package/src/LoadManager.res +174 -0
- package/src/Logging.res +179 -0
- package/src/Time.res +41 -0
- package/src/Types.ts +22 -0
- package/src/Utils.res +15 -0
- package/src/bindings/Express.res +10 -2
- package/src/bindings/NodeJs.res +81 -0
- package/src/db/EntityHistory.res +4 -4
- package/src/db/Table.res +5 -2
- package/src/sources/Fuel.res +37 -0
- package/src/sources/HyperFuel.res +260 -0
- package/src/sources/HyperFuel.resi +59 -0
- package/src/sources/HyperFuelClient.res +408 -0
- package/src/sources/HyperSync.res +349 -0
- package/src/sources/HyperSync.resi +69 -0
- package/src/sources/vendored-fuel-abi-coder.js +1847 -0
- package/src/bindings/OpaqueTypes.ts +0 -2
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
open Belt
|
|
2
|
+
|
|
3
|
+
module Log = {
|
|
4
|
+
type t = {
|
|
5
|
+
address: Address.t,
|
|
6
|
+
data: string,
|
|
7
|
+
topics: array<EvmTypes.Hex.t>,
|
|
8
|
+
logIndex: int,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let fieldNames = ["address", "data", "topics", "logIndex"]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type hyperSyncPage<'item> = {
|
|
15
|
+
items: array<'item>,
|
|
16
|
+
nextBlock: int,
|
|
17
|
+
archiveHeight: int,
|
|
18
|
+
rollbackGuard: option<HyperSyncClient.ResponseTypes.rollbackGuard>,
|
|
19
|
+
events: array<HyperSyncClient.ResponseTypes.event>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type logsQueryPageItem = {
|
|
23
|
+
log: Log.t,
|
|
24
|
+
block: Internal.eventBlock,
|
|
25
|
+
transaction: Internal.eventTransaction,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type logsQueryPage = hyperSyncPage<logsQueryPageItem>
|
|
29
|
+
|
|
30
|
+
type missingParams = {
|
|
31
|
+
queryName: string,
|
|
32
|
+
missingParams: array<string>,
|
|
33
|
+
}
|
|
34
|
+
type queryError = UnexpectedMissingParams(missingParams)
|
|
35
|
+
|
|
36
|
+
exception HyperSyncQueryError(queryError)
|
|
37
|
+
|
|
38
|
+
let queryErrorToExn = queryError => {
|
|
39
|
+
HyperSyncQueryError(queryError)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let queryErrorToMsq = (e: queryError): string => {
|
|
43
|
+
switch e {
|
|
44
|
+
| UnexpectedMissingParams({queryName, missingParams}) =>
|
|
45
|
+
`${queryName} query failed due to unexpected missing params on response:
|
|
46
|
+
${missingParams->Js.Array2.joinWith(", ")}`
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type queryResponse<'a> = result<'a, queryError>
|
|
51
|
+
let mapExn = (queryResponse: queryResponse<'a>) =>
|
|
52
|
+
switch queryResponse {
|
|
53
|
+
| Ok(v) => Ok(v)
|
|
54
|
+
| Error(err) => err->queryErrorToExn->Error
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module GetLogs = {
|
|
58
|
+
type error =
|
|
59
|
+
| UnexpectedMissingParams({missingParams: array<string>})
|
|
60
|
+
| WrongInstance
|
|
61
|
+
|
|
62
|
+
exception Error(error)
|
|
63
|
+
|
|
64
|
+
let makeRequestBody = (
|
|
65
|
+
~fromBlock,
|
|
66
|
+
~toBlockInclusive,
|
|
67
|
+
~addressesWithTopics,
|
|
68
|
+
~fieldSelection,
|
|
69
|
+
): HyperSyncClient.QueryTypes.query => {
|
|
70
|
+
fromBlock,
|
|
71
|
+
toBlockExclusive: ?switch toBlockInclusive {
|
|
72
|
+
| Some(toBlockInclusive) => Some(toBlockInclusive + 1)
|
|
73
|
+
| None => None
|
|
74
|
+
},
|
|
75
|
+
logs: addressesWithTopics,
|
|
76
|
+
fieldSelection,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let addMissingParams = (acc, fieldNames, returnedObj, ~prefix) => {
|
|
80
|
+
fieldNames->Array.forEach(fieldName => {
|
|
81
|
+
switch returnedObj
|
|
82
|
+
->(Utils.magic: 'a => Js.Dict.t<unknown>)
|
|
83
|
+
->Utils.Dict.dangerouslyGetNonOption(fieldName) {
|
|
84
|
+
| Some(_) => ()
|
|
85
|
+
| None => acc->Array.push(prefix ++ "." ++ fieldName)->ignore
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//Note this function can throw an error
|
|
91
|
+
let convertEvent = (
|
|
92
|
+
event: HyperSyncClient.ResponseTypes.event,
|
|
93
|
+
~nonOptionalBlockFieldNames,
|
|
94
|
+
~nonOptionalTransactionFieldNames,
|
|
95
|
+
): logsQueryPageItem => {
|
|
96
|
+
let missingParams = []
|
|
97
|
+
missingParams->addMissingParams(Log.fieldNames, event.log, ~prefix="log")
|
|
98
|
+
missingParams->addMissingParams(nonOptionalBlockFieldNames, event.block, ~prefix="block")
|
|
99
|
+
missingParams->addMissingParams(
|
|
100
|
+
nonOptionalTransactionFieldNames,
|
|
101
|
+
event.transaction,
|
|
102
|
+
~prefix="transaction",
|
|
103
|
+
)
|
|
104
|
+
if missingParams->Array.length > 0 {
|
|
105
|
+
raise(Error(UnexpectedMissingParams({missingParams: missingParams})))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
//Topics can be nullable and still need to be filtered
|
|
109
|
+
let logUnsanitized: Log.t = event.log->Utils.magic
|
|
110
|
+
let topics = event.log.topics->Option.getUnsafe->Array.keepMap(Js.Nullable.toOption)
|
|
111
|
+
let address = event.log.address->Option.getUnsafe
|
|
112
|
+
let log = {
|
|
113
|
+
...logUnsanitized,
|
|
114
|
+
topics,
|
|
115
|
+
address,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
{
|
|
119
|
+
log,
|
|
120
|
+
block: event.block->Utils.magic,
|
|
121
|
+
transaction: event.transaction->Utils.magic,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let convertResponse = (
|
|
126
|
+
res: HyperSyncClient.ResponseTypes.eventResponse,
|
|
127
|
+
~nonOptionalBlockFieldNames,
|
|
128
|
+
~nonOptionalTransactionFieldNames,
|
|
129
|
+
): logsQueryPage => {
|
|
130
|
+
let {nextBlock, archiveHeight, rollbackGuard} = res
|
|
131
|
+
let items =
|
|
132
|
+
res.data->Array.map(item =>
|
|
133
|
+
item->convertEvent(~nonOptionalBlockFieldNames, ~nonOptionalTransactionFieldNames)
|
|
134
|
+
)
|
|
135
|
+
let page: logsQueryPage = {
|
|
136
|
+
items,
|
|
137
|
+
nextBlock,
|
|
138
|
+
archiveHeight: archiveHeight->Option.getWithDefault(0), //Archive Height is only None if height is 0
|
|
139
|
+
events: res.data,
|
|
140
|
+
rollbackGuard,
|
|
141
|
+
}
|
|
142
|
+
page
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let query = async (
|
|
146
|
+
~client: HyperSyncClient.t,
|
|
147
|
+
~fromBlock,
|
|
148
|
+
~toBlock,
|
|
149
|
+
~logSelections: array<LogSelection.t>,
|
|
150
|
+
~fieldSelection,
|
|
151
|
+
~nonOptionalBlockFieldNames,
|
|
152
|
+
~nonOptionalTransactionFieldNames,
|
|
153
|
+
): logsQueryPage => {
|
|
154
|
+
let addressesWithTopics = logSelections->Array.flatMap(({addresses, topicSelections}) =>
|
|
155
|
+
topicSelections->Array.map(({topic0, topic1, topic2, topic3}) => {
|
|
156
|
+
let topics = HyperSyncClient.QueryTypes.makeTopicSelection(
|
|
157
|
+
~topic0,
|
|
158
|
+
~topic1,
|
|
159
|
+
~topic2,
|
|
160
|
+
~topic3,
|
|
161
|
+
)
|
|
162
|
+
HyperSyncClient.QueryTypes.makeLogSelection(~address=addresses, ~topics)
|
|
163
|
+
})
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
let query = makeRequestBody(
|
|
167
|
+
~fromBlock,
|
|
168
|
+
~toBlockInclusive=toBlock,
|
|
169
|
+
~addressesWithTopics,
|
|
170
|
+
~fieldSelection,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
let res = await client.getEvents(~query)
|
|
174
|
+
if res.nextBlock <= fromBlock {
|
|
175
|
+
// Might happen when /height response was from another instance of HyperSync
|
|
176
|
+
raise(Error(WrongInstance))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
res->convertResponse(~nonOptionalBlockFieldNames, ~nonOptionalTransactionFieldNames)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module BlockData = {
|
|
184
|
+
let makeRequestBody = (~fromBlock, ~toBlock): HyperSyncJsonApi.QueryTypes.postQueryBody => {
|
|
185
|
+
fromBlock,
|
|
186
|
+
toBlockExclusive: toBlock + 1,
|
|
187
|
+
fieldSelection: {
|
|
188
|
+
block: [Number, Hash, Timestamp],
|
|
189
|
+
},
|
|
190
|
+
includeAllBlocks: true,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let convertResponse = (res: HyperSyncJsonApi.ResponseTypes.queryResponse): queryResponse<
|
|
194
|
+
array<ReorgDetection.blockDataWithTimestamp>,
|
|
195
|
+
> => {
|
|
196
|
+
res.data
|
|
197
|
+
->Array.flatMap(item => {
|
|
198
|
+
item.blocks->Option.mapWithDefault([], blocks => {
|
|
199
|
+
blocks->Array.map(
|
|
200
|
+
block => {
|
|
201
|
+
switch block {
|
|
202
|
+
| {number: blockNumber, timestamp, hash: blockHash} =>
|
|
203
|
+
let blockTimestamp = timestamp->BigInt.toInt->Option.getExn
|
|
204
|
+
Ok(
|
|
205
|
+
(
|
|
206
|
+
{
|
|
207
|
+
blockTimestamp,
|
|
208
|
+
blockNumber,
|
|
209
|
+
blockHash,
|
|
210
|
+
}: ReorgDetection.blockDataWithTimestamp
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
| _ =>
|
|
214
|
+
let missingParams =
|
|
215
|
+
[
|
|
216
|
+
block.number->Utils.Option.mapNone("block.number"),
|
|
217
|
+
block.timestamp->Utils.Option.mapNone("block.timestamp"),
|
|
218
|
+
block.hash->Utils.Option.mapNone("block.hash"),
|
|
219
|
+
]->Array.keepMap(p => p)
|
|
220
|
+
|
|
221
|
+
Error(
|
|
222
|
+
UnexpectedMissingParams({
|
|
223
|
+
queryName: "query block data HyperSync",
|
|
224
|
+
missingParams,
|
|
225
|
+
}),
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
->Utils.Array.transposeResults
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let rec queryBlockData = async (
|
|
236
|
+
~serverUrl,
|
|
237
|
+
~apiToken,
|
|
238
|
+
~fromBlock,
|
|
239
|
+
~toBlock,
|
|
240
|
+
~logger,
|
|
241
|
+
): queryResponse<array<ReorgDetection.blockDataWithTimestamp>> => {
|
|
242
|
+
let body = makeRequestBody(~fromBlock, ~toBlock)
|
|
243
|
+
|
|
244
|
+
let logger = Logging.createChildFrom(
|
|
245
|
+
~logger,
|
|
246
|
+
~params={
|
|
247
|
+
"logType": "HyperSync get block hash query",
|
|
248
|
+
"fromBlock": fromBlock,
|
|
249
|
+
"toBlock": toBlock,
|
|
250
|
+
},
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
let maybeSuccessfulRes = switch await Time.retryAsyncWithExponentialBackOff(() =>
|
|
254
|
+
HyperSyncJsonApi.queryRoute->Rest.fetch(
|
|
255
|
+
{
|
|
256
|
+
"query": body,
|
|
257
|
+
"token": apiToken,
|
|
258
|
+
},
|
|
259
|
+
~client=Rest.client(serverUrl),
|
|
260
|
+
)
|
|
261
|
+
, ~logger) {
|
|
262
|
+
| exception _ => None
|
|
263
|
+
| res if res.nextBlock <= fromBlock => None
|
|
264
|
+
| res => Some(res)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// If the block is not found, retry the query. This can occur since replicas of hypersync might not hack caught up yet
|
|
268
|
+
switch maybeSuccessfulRes {
|
|
269
|
+
| None => {
|
|
270
|
+
let logger = Logging.createChild(~params={"url": serverUrl})
|
|
271
|
+
let delayMilliseconds = 100
|
|
272
|
+
logger->Logging.childInfo(
|
|
273
|
+
`Block #${fromBlock->Int.toString} not found in HyperSync. HyperSync has multiple instances and it's possible that they drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${delayMilliseconds->Int.toString}ms.`,
|
|
274
|
+
)
|
|
275
|
+
await Time.resolvePromiseAfterDelay(~delayMilliseconds)
|
|
276
|
+
await queryBlockData(~serverUrl, ~apiToken, ~fromBlock, ~toBlock, ~logger)
|
|
277
|
+
}
|
|
278
|
+
| Some(res) =>
|
|
279
|
+
switch res->convertResponse {
|
|
280
|
+
| Error(_) as err => err
|
|
281
|
+
| Ok(datas) if res.nextBlock <= toBlock => {
|
|
282
|
+
let restRes = await queryBlockData(
|
|
283
|
+
~serverUrl,
|
|
284
|
+
~apiToken,
|
|
285
|
+
~fromBlock=res.nextBlock,
|
|
286
|
+
~toBlock,
|
|
287
|
+
~logger,
|
|
288
|
+
)
|
|
289
|
+
restRes->Result.map(rest => datas->Array.concat(rest))
|
|
290
|
+
}
|
|
291
|
+
| Ok(_) as ok => ok
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let queryBlockDataMulti = async (~serverUrl, ~apiToken, ~blockNumbers, ~logger) => {
|
|
297
|
+
switch blockNumbers->Array.get(0) {
|
|
298
|
+
| None => Ok([])
|
|
299
|
+
| Some(firstBlock) => {
|
|
300
|
+
let fromBlock = ref(firstBlock)
|
|
301
|
+
let toBlock = ref(firstBlock)
|
|
302
|
+
let set = Utils.Set.make()
|
|
303
|
+
for idx in 0 to blockNumbers->Array.length - 1 {
|
|
304
|
+
let blockNumber = blockNumbers->Array.getUnsafe(idx)
|
|
305
|
+
if blockNumber < fromBlock.contents {
|
|
306
|
+
fromBlock := blockNumber
|
|
307
|
+
}
|
|
308
|
+
if blockNumber > toBlock.contents {
|
|
309
|
+
toBlock := blockNumber
|
|
310
|
+
}
|
|
311
|
+
set->Utils.Set.add(blockNumber)->ignore
|
|
312
|
+
}
|
|
313
|
+
if toBlock.contents - fromBlock.contents > 1000 {
|
|
314
|
+
Js.Exn.raiseError(
|
|
315
|
+
`Invalid block data request. Range of block numbers is too large. Max range is 1000. Requested range: ${fromBlock.contents->Int.toString}-${toBlock.contents->Int.toString}`,
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
let res = await queryBlockData(
|
|
319
|
+
~fromBlock=fromBlock.contents,
|
|
320
|
+
~toBlock=toBlock.contents,
|
|
321
|
+
~serverUrl,
|
|
322
|
+
~apiToken,
|
|
323
|
+
~logger,
|
|
324
|
+
)
|
|
325
|
+
let filtered = res->Result.map(datas => {
|
|
326
|
+
datas->Array.keep(data => set->Utils.Set.delete(data.blockNumber))
|
|
327
|
+
})
|
|
328
|
+
if set->Utils.Set.size > 0 {
|
|
329
|
+
Js.Exn.raiseError(
|
|
330
|
+
`Invalid response. Failed to get block data for block numbers: ${set
|
|
331
|
+
->Utils.Set.toArray
|
|
332
|
+
->Js.Array2.joinWith(", ")}`,
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
filtered
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let queryBlockData = (~serverUrl, ~apiToken, ~blockNumber, ~logger) =>
|
|
342
|
+
BlockData.queryBlockData(
|
|
343
|
+
~serverUrl,
|
|
344
|
+
~apiToken,
|
|
345
|
+
~fromBlock=blockNumber,
|
|
346
|
+
~toBlock=blockNumber,
|
|
347
|
+
~logger,
|
|
348
|
+
)->Promise.thenResolve(res => res->Result.map(res => res->Array.get(0)))
|
|
349
|
+
let queryBlockDataMulti = BlockData.queryBlockDataMulti
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
type hyperSyncPage<'item> = {
|
|
2
|
+
items: array<'item>,
|
|
3
|
+
nextBlock: int,
|
|
4
|
+
archiveHeight: int,
|
|
5
|
+
rollbackGuard: option<HyperSyncClient.ResponseTypes.rollbackGuard>,
|
|
6
|
+
events: array<HyperSyncClient.ResponseTypes.event>,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module Log: {
|
|
10
|
+
type t = {
|
|
11
|
+
address: Address.t,
|
|
12
|
+
data: string,
|
|
13
|
+
topics: array<EvmTypes.Hex.t>,
|
|
14
|
+
logIndex: int,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type logsQueryPageItem = {
|
|
19
|
+
log: Log.t,
|
|
20
|
+
block: Internal.eventBlock,
|
|
21
|
+
transaction: Internal.eventTransaction,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type logsQueryPage = hyperSyncPage<logsQueryPageItem>
|
|
25
|
+
|
|
26
|
+
type missingParams = {
|
|
27
|
+
queryName: string,
|
|
28
|
+
missingParams: array<string>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type queryError = UnexpectedMissingParams(missingParams)
|
|
32
|
+
|
|
33
|
+
let queryErrorToMsq: queryError => string
|
|
34
|
+
|
|
35
|
+
type queryResponse<'a> = result<'a, queryError>
|
|
36
|
+
|
|
37
|
+
module GetLogs: {
|
|
38
|
+
type error =
|
|
39
|
+
| UnexpectedMissingParams({missingParams: array<string>})
|
|
40
|
+
| WrongInstance
|
|
41
|
+
|
|
42
|
+
exception Error(error)
|
|
43
|
+
|
|
44
|
+
let query: (
|
|
45
|
+
~client: HyperSyncClient.t,
|
|
46
|
+
~fromBlock: int,
|
|
47
|
+
~toBlock: option<int>,
|
|
48
|
+
~logSelections: array<LogSelection.t>,
|
|
49
|
+
~fieldSelection: HyperSyncClient.QueryTypes.fieldSelection,
|
|
50
|
+
~nonOptionalBlockFieldNames: array<string>,
|
|
51
|
+
~nonOptionalTransactionFieldNames: array<string>,
|
|
52
|
+
) => promise<logsQueryPage>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let queryBlockData: (
|
|
56
|
+
~serverUrl: string,
|
|
57
|
+
~apiToken: string,
|
|
58
|
+
~blockNumber: int,
|
|
59
|
+
~logger: Pino.t,
|
|
60
|
+
) => promise<queryResponse<option<ReorgDetection.blockDataWithTimestamp>>>
|
|
61
|
+
|
|
62
|
+
let queryBlockDataMulti: (
|
|
63
|
+
~serverUrl: string,
|
|
64
|
+
~apiToken: string,
|
|
65
|
+
~blockNumbers: array<int>,
|
|
66
|
+
~logger: Pino.t,
|
|
67
|
+
) => promise<queryResponse<array<ReorgDetection.blockDataWithTimestamp>>>
|
|
68
|
+
|
|
69
|
+
let mapExn: queryResponse<'a> => result<'a, exn>
|