envio 2.21.0 → 2.21.2

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.
@@ -83,6 +83,7 @@ module MakeSafePromMetric = (
83
83
  let handleInt: (t<'a>, ~labels: 'a, ~value: int) => unit
84
84
  let handleFloat: (t<'a>, ~labels: 'a, ~value: float) => unit
85
85
  let increment: (t<'a>, ~labels: 'a) => unit
86
+ let incrementMany: (t<'a>, ~labels: 'a, ~value: int) => unit
86
87
  } => {
87
88
  type t<'a> = {metric: M.t, labelSchema: S.t<'a>}
88
89
 
@@ -121,6 +122,13 @@ module MakeSafePromMetric = (
121
122
  ->M.labels(labels->S.reverseConvertToJsonOrThrow(labelSchema))
122
123
  ->Obj.magic
123
124
  )["inc"]()
125
+
126
+ let incrementMany = ({metric, labelSchema}: t<'a>, ~labels: 'a, ~value) =>
127
+ (
128
+ metric
129
+ ->M.labels(labels->S.reverseConvertToJsonOrThrow(labelSchema))
130
+ ->Obj.magic
131
+ )["inc"](value)
124
132
  }
125
133
 
126
134
  module SafeCounter = MakeSafePromMetric({
@@ -335,7 +343,7 @@ module IndexingMaxConcurrency = {
335
343
  module IndexingConcurrency = {
336
344
  let gauge = SafeGauge.makeOrThrow(
337
345
  ~name="envio_indexing_concurrency",
338
- ~help="The current number of concurrent queries to the chain data-source.",
346
+ ~help="The number of executing concurrent queries to the chain data-source.",
339
347
  ~labelSchema=chainIdLabelsSchema,
340
348
  )
341
349
 
@@ -344,6 +352,42 @@ module IndexingConcurrency = {
344
352
  }
345
353
  }
346
354
 
355
+ module IndexingPartitions = {
356
+ let gauge = SafeGauge.makeOrThrow(
357
+ ~name="envio_indexing_partitions",
358
+ ~help="The number of partitions used to split fetching logic by addresses and block ranges.",
359
+ ~labelSchema=chainIdLabelsSchema,
360
+ )
361
+
362
+ let set = (~partitionsCount, ~chainId) => {
363
+ gauge->SafeGauge.handleInt(~labels=chainId, ~value=partitionsCount)
364
+ }
365
+ }
366
+
367
+ module IndexingIdleTime = {
368
+ let counter = SafeCounter.makeOrThrow(
369
+ ~name="envio_indexing_idle_time",
370
+ ~help="The number of milliseconds the indexer source syncing has been idle. A high value may indicate the source sync is a bottleneck.",
371
+ ~labelSchema=chainIdLabelsSchema,
372
+ )
373
+ }
374
+
375
+ module IndexingSourceWaitingTime = {
376
+ let counter = SafeCounter.makeOrThrow(
377
+ ~name="envio_indexing_source_waiting_time",
378
+ ~help="The number of milliseconds the indexer has been waiting for new blocks.",
379
+ ~labelSchema=chainIdLabelsSchema,
380
+ )
381
+ }
382
+
383
+ module IndexingQueryTime = {
384
+ let counter = SafeCounter.makeOrThrow(
385
+ ~name="envio_indexing_query_time",
386
+ ~help="The number of milliseconds spent performing queries to the chain data-source.",
387
+ ~labelSchema=chainIdLabelsSchema,
388
+ )
389
+ }
390
+
347
391
  module IndexingBufferSize = {
348
392
  let gauge = SafeGauge.makeOrThrow(
349
393
  ~name="envio_indexing_buffer_size",
@@ -15,8 +15,8 @@ type blockData = {
15
15
  external generalizeBlockDataWithTimestamp: blockDataWithTimestamp => blockData = "%identity"
16
16
 
17
17
  type reorgGuard = {
18
- lastBlockScannedData: blockData,
19
- firstBlockParentNumberAndHash: option<blockData>,
18
+ rangeLastBlock: blockData,
19
+ prevRangeLastBlock: option<blockData>,
20
20
  }
21
21
 
22
22
  type reorgDetected = {
@@ -36,22 +36,31 @@ let reorgDetectedToLogParams = (reorgDetected: reorgDetected, ~shouldRollbackOnR
36
36
  }
37
37
  }
38
38
 
39
+ type reorgResult = NoReorg | ReorgDetected(reorgDetected)
40
+ type validBlockError = NotFound | AlreadyReorgedHashes
41
+ type validBlockResult = result<blockDataWithTimestamp, validBlockError>
42
+
39
43
  module LastBlockScannedHashes: {
40
44
  type t
41
45
  /**Instantiat t with existing data*/
42
- let makeWithData: (array<blockData>, ~confirmedBlockThreshold: int) => t
46
+ let makeWithData: (
47
+ array<blockData>,
48
+ ~confirmedBlockThreshold: int,
49
+ ~detectedReorgBlock: blockData=?,
50
+ ) => t
43
51
 
44
52
  /**Instantiat empty t with no block data*/
45
53
  let empty: (~confirmedBlockThreshold: int) => t
46
54
 
47
- /** Registers a new reorg guard, prunes unnened data and returns the updated data
48
- or an error if a reorg has occured
49
- */
55
+ /** Registers a new reorg guard, prunes unneeded data, and returns the updated state.
56
+ * Resets internal state if shouldRollbackOnReorg is false (detect-only mode)
57
+ */
50
58
  let registerReorgGuard: (
51
59
  t,
52
60
  ~reorgGuard: reorgGuard,
53
61
  ~currentBlockHeight: int,
54
- ) => result<t, reorgDetected>
62
+ ~shouldRollbackOnReorg: bool,
63
+ ) => (t, reorgResult)
55
64
 
56
65
  /**
57
66
  Returns the latest block data which matches block number and hashes in the provided array
@@ -61,7 +70,7 @@ module LastBlockScannedHashes: {
61
70
  t,
62
71
  ~blockNumbersAndHashes: array<blockDataWithTimestamp>,
63
72
  ~currentBlockHeight: int,
64
- ) => option<blockDataWithTimestamp>
73
+ ) => validBlockResult
65
74
 
66
75
  let getThresholdBlockNumbers: (t, ~currentBlockHeight: int) => array<int>
67
76
 
@@ -76,9 +85,14 @@ module LastBlockScannedHashes: {
76
85
  // A hash map of recent blockdata by block number to make comparison checks
77
86
  // for reorgs.
78
87
  dataByBlockNumber: dict<blockData>,
88
+ // The latest block which detected a reorg
89
+ // and should never be valid.
90
+ // We keep track of this to avoid responses
91
+ // with the stale data from other data-source instances.
92
+ detectedReorgBlock: option<blockData>,
79
93
  }
80
94
 
81
- let makeWithData = (blocks, ~confirmedBlockThreshold) => {
95
+ let makeWithData = (blocks, ~confirmedBlockThreshold, ~detectedReorgBlock=?) => {
82
96
  let dataByBlockNumber = Js.Dict.empty()
83
97
 
84
98
  blocks->Belt.Array.forEach(block => {
@@ -88,12 +102,14 @@ module LastBlockScannedHashes: {
88
102
  {
89
103
  confirmedBlockThreshold,
90
104
  dataByBlockNumber,
105
+ detectedReorgBlock,
91
106
  }
92
107
  }
93
108
  //Instantiates empty LastBlockHashes
94
109
  let empty = (~confirmedBlockThreshold) => {
95
110
  confirmedBlockThreshold,
96
111
  dataByBlockNumber: Js.Dict.empty(),
112
+ detectedReorgBlock: None,
97
113
  }
98
114
 
99
115
  let getDataByBlockNumberCopyInThreshold = (
@@ -122,33 +138,33 @@ module LastBlockScannedHashes: {
122
138
  {confirmedBlockThreshold} as self: t,
123
139
  ~reorgGuard: reorgGuard,
124
140
  ~currentBlockHeight,
141
+ ~shouldRollbackOnReorg,
125
142
  ) => {
126
143
  let dataByBlockNumberCopyInThreshold =
127
144
  self->getDataByBlockNumberCopyInThreshold(~currentBlockHeight)
128
145
 
129
- let {lastBlockScannedData, firstBlockParentNumberAndHash} = reorgGuard
146
+ let {rangeLastBlock, prevRangeLastBlock} = reorgGuard
130
147
 
131
148
  let maybeReorgDetected = switch dataByBlockNumberCopyInThreshold->Utils.Dict.dangerouslyGetNonOption(
132
- lastBlockScannedData.blockNumber->Int.toString,
149
+ rangeLastBlock.blockNumber->Int.toString,
133
150
  ) {
134
- | Some(scannedBlock) if scannedBlock.blockHash !== lastBlockScannedData.blockHash =>
151
+ | Some(scannedBlock) if scannedBlock.blockHash !== rangeLastBlock.blockHash =>
135
152
  Some({
136
- receivedBlock: lastBlockScannedData,
153
+ receivedBlock: rangeLastBlock,
137
154
  scannedBlock,
138
155
  })
139
156
  | _ =>
140
- switch firstBlockParentNumberAndHash {
157
+ switch prevRangeLastBlock {
141
158
  //If parentHash is None, then it's the genesis block (no reorg)
142
159
  //Need to check that parentHash matches because of the dynamic contracts
143
160
  | None => None
144
- | Some(firstBlockParentNumberAndHash) =>
161
+ | Some(prevRangeLastBlock) =>
145
162
  switch dataByBlockNumberCopyInThreshold->Utils.Dict.dangerouslyGetNonOption(
146
- firstBlockParentNumberAndHash.blockNumber->Int.toString,
163
+ prevRangeLastBlock.blockNumber->Int.toString,
147
164
  ) {
148
- | Some(scannedBlock)
149
- if scannedBlock.blockHash !== firstBlockParentNumberAndHash.blockHash =>
165
+ | Some(scannedBlock) if scannedBlock.blockHash !== prevRangeLastBlock.blockHash =>
150
166
  Some({
151
- receivedBlock: firstBlockParentNumberAndHash,
167
+ receivedBlock: prevRangeLastBlock,
152
168
  scannedBlock,
153
169
  })
154
170
  | _ => None
@@ -157,25 +173,37 @@ module LastBlockScannedHashes: {
157
173
  }
158
174
 
159
175
  switch maybeReorgDetected {
160
- | Some(reorgDetected) => Error(reorgDetected)
176
+ | Some(reorgDetected) => (
177
+ shouldRollbackOnReorg
178
+ ? {
179
+ ...self,
180
+ detectedReorgBlock: Some(reorgDetected.scannedBlock),
181
+ }
182
+ : empty(~confirmedBlockThreshold),
183
+ ReorgDetected(reorgDetected),
184
+ )
161
185
  | None => {
162
186
  dataByBlockNumberCopyInThreshold->Js.Dict.set(
163
- lastBlockScannedData.blockNumber->Int.toString,
164
- lastBlockScannedData,
187
+ rangeLastBlock.blockNumber->Int.toString,
188
+ rangeLastBlock,
165
189
  )
166
- switch firstBlockParentNumberAndHash {
190
+ switch prevRangeLastBlock {
167
191
  | None => ()
168
- | Some(firstBlockParentNumberAndHash) =>
192
+ | Some(prevRangeLastBlock) =>
169
193
  dataByBlockNumberCopyInThreshold->Js.Dict.set(
170
- firstBlockParentNumberAndHash.blockNumber->Int.toString,
171
- firstBlockParentNumberAndHash,
194
+ prevRangeLastBlock.blockNumber->Int.toString,
195
+ prevRangeLastBlock,
172
196
  )
173
197
  }
174
198
 
175
- Ok({
176
- confirmedBlockThreshold,
177
- dataByBlockNumber: dataByBlockNumberCopyInThreshold,
178
- })
199
+ (
200
+ {
201
+ confirmedBlockThreshold,
202
+ dataByBlockNumber: dataByBlockNumberCopyInThreshold,
203
+ detectedReorgBlock: None,
204
+ },
205
+ NoReorg,
206
+ )
179
207
  }
180
208
  }
181
209
  }
@@ -186,39 +214,58 @@ module LastBlockScannedHashes: {
186
214
  ~currentBlockHeight,
187
215
  ) => {
188
216
  let verifiedDataByBlockNumber = Js.Dict.empty()
189
- blockNumbersAndHashes->Array.forEach(blockData => {
217
+ for idx in 0 to blockNumbersAndHashes->Array.length - 1 {
218
+ let blockData = blockNumbersAndHashes->Array.getUnsafe(idx)
190
219
  verifiedDataByBlockNumber->Js.Dict.set(blockData.blockNumber->Int.toString, blockData)
191
- })
220
+ }
192
221
 
193
- let dataByBlockNumber = self->getDataByBlockNumberCopyInThreshold(~currentBlockHeight)
194
- // Js engine automatically orders numeric object keys
195
- let ascBlockNumberKeys = dataByBlockNumber->Js.Dict.keys
222
+ let isAlreadyReorgedResponse = switch self.detectedReorgBlock {
223
+ | Some(detectedReorgBlock) =>
224
+ switch verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(
225
+ detectedReorgBlock.blockNumber->Int.toString,
226
+ ) {
227
+ | Some(verifiedBlockData) => verifiedBlockData.blockHash === detectedReorgBlock.blockHash
228
+ | None => false
229
+ }
230
+ | None => false
231
+ }
196
232
 
197
- let getPrevScannedBlock = idx =>
198
- ascBlockNumberKeys
199
- ->Belt.Array.get(idx - 1)
200
- ->Option.flatMap(key => {
201
- // We should already validate that the block number is verified at the point
202
- verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(key)
203
- })
233
+ if isAlreadyReorgedResponse {
234
+ Error(AlreadyReorgedHashes)
235
+ } else {
236
+ let dataByBlockNumber = self->getDataByBlockNumberCopyInThreshold(~currentBlockHeight)
237
+ // Js engine automatically orders numeric object keys
238
+ let ascBlockNumberKeys = dataByBlockNumber->Js.Dict.keys
239
+
240
+ let getPrevScannedBlock = idx =>
241
+ switch ascBlockNumberKeys
242
+ ->Belt.Array.get(idx - 1)
243
+ ->Option.flatMap(key => {
244
+ // We should already validate that the block number is verified at the point
245
+ verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(key)
246
+ }) {
247
+ | Some(data) => Ok(data)
248
+ | None => Error(NotFound)
249
+ }
204
250
 
205
- let rec loop = idx => {
206
- switch ascBlockNumberKeys->Belt.Array.get(idx) {
207
- | Some(blockNumberKey) =>
208
- let scannedBlock = dataByBlockNumber->Js.Dict.unsafeGet(blockNumberKey)
209
- switch verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(blockNumberKey) {
210
- | None =>
211
- Js.Exn.raiseError(
212
- `Unexpected case. Couldn't find verified hash for block number ${blockNumberKey}`,
213
- )
214
- | Some(verifiedBlockData) if verifiedBlockData.blockHash === scannedBlock.blockHash =>
215
- loop(idx + 1)
216
- | Some(_) => getPrevScannedBlock(idx)
251
+ let rec loop = idx => {
252
+ switch ascBlockNumberKeys->Belt.Array.get(idx) {
253
+ | Some(blockNumberKey) =>
254
+ let scannedBlock = dataByBlockNumber->Js.Dict.unsafeGet(blockNumberKey)
255
+ switch verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(blockNumberKey) {
256
+ | None =>
257
+ Js.Exn.raiseError(
258
+ `Unexpected case. Couldn't find verified hash for block number ${blockNumberKey}`,
259
+ )
260
+ | Some(verifiedBlockData) if verifiedBlockData.blockHash === scannedBlock.blockHash =>
261
+ loop(idx + 1)
262
+ | Some(_) => getPrevScannedBlock(idx)
263
+ }
264
+ | None => getPrevScannedBlock(idx)
217
265
  }
218
- | None => getPrevScannedBlock(idx)
219
266
  }
267
+ loop(0)
220
268
  }
221
- loop(0)
222
269
  }
223
270
 
224
271
  /**
@@ -254,6 +301,7 @@ module LastBlockScannedHashes: {
254
301
  {
255
302
  confirmedBlockThreshold,
256
303
  dataByBlockNumber: newDataByBlockNumber,
304
+ detectedReorgBlock: None,
257
305
  }
258
306
  }
259
307
 
@@ -0,0 +1,59 @@
1
+ /**
2
+ A set of stats for logging about the block range fetch
3
+ */
4
+ type blockRangeFetchStats = {
5
+ @as("total time elapsed (ms)") totalTimeElapsed: int,
6
+ @as("parsing time (ms)") parsingTimeElapsed?: int,
7
+ @as("page fetch time (ms)") pageFetchTime?: int,
8
+ }
9
+
10
+ /**
11
+ Thes response returned from a block range fetch
12
+ */
13
+ type blockRangeFetchResponse = {
14
+ currentBlockHeight: int,
15
+ reorgGuard: ReorgDetection.reorgGuard,
16
+ parsedQueueItems: array<Internal.eventItem>,
17
+ fromBlockQueried: int,
18
+ latestFetchedBlockNumber: int,
19
+ latestFetchedBlockTimestamp: int,
20
+ stats: blockRangeFetchStats,
21
+ }
22
+
23
+ type getItemsRetry =
24
+ | WithSuggestedToBlock({toBlock: int})
25
+ | WithBackoff({message: string, backoffMillis: int})
26
+
27
+ type getItemsError =
28
+ | UnsupportedSelection({message: string})
29
+ | FailedGettingFieldSelection({exn: exn, blockNumber: int, logIndex: int, message: string})
30
+ | FailedParsingItems({exn: exn, blockNumber: int, logIndex: int, message: string})
31
+ | FailedGettingItems({exn: exn, attemptedToBlock: int, retry: getItemsRetry})
32
+
33
+ exception GetItemsError(getItemsError)
34
+
35
+ type sourceFor = Sync | Fallback
36
+ type t = {
37
+ name: string,
38
+ sourceFor: sourceFor,
39
+ chain: ChainMap.Chain.t,
40
+ poweredByHyperSync: bool,
41
+ /* Frequency (in ms) used when polling for new events on this network. */
42
+ pollingInterval: int,
43
+ getBlockHashes: (
44
+ ~blockNumbers: array<int>,
45
+ ~logger: Pino.t,
46
+ ) => promise<result<array<ReorgDetection.blockDataWithTimestamp>, exn>>,
47
+ getHeightOrThrow: unit => promise<int>,
48
+ getItemsOrThrow: (
49
+ ~fromBlock: int,
50
+ ~toBlock: option<int>,
51
+ ~addressesByContractName: dict<array<Address.t>>,
52
+ ~indexingContracts: dict<FetchState.indexingContract>,
53
+ ~currentBlockHeight: int,
54
+ ~partitionId: string,
55
+ ~selection: FetchState.selection,
56
+ ~retry: int,
57
+ ~logger: Pino.t,
58
+ ) => promise<blockRangeFetchResponse>,
59
+ }