envio 2.21.1 → 2.21.3-rc

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "v2.21.1",
3
+ "version": "v2.21.3-rc",
4
4
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
5
5
  "bin": "./bin.js",
6
6
  "main": "./index.js",
@@ -25,10 +25,10 @@
25
25
  },
26
26
  "homepage": "https://envio.dev",
27
27
  "optionalDependencies": {
28
- "envio-linux-x64": "v2.21.1",
29
- "envio-linux-arm64": "v2.21.1",
30
- "envio-darwin-x64": "v2.21.1",
31
- "envio-darwin-arm64": "v2.21.1"
28
+ "envio-linux-x64": "v2.21.3-rc",
29
+ "envio-linux-arm64": "v2.21.3-rc",
30
+ "envio-darwin-x64": "v2.21.3-rc",
31
+ "envio-darwin-arm64": "v2.21.3-rc"
32
32
  },
33
33
  "dependencies": {
34
34
  "@envio-dev/hypersync-client": "0.6.5",
@@ -940,14 +940,14 @@ let queueItemBlockNumber = (queueItem: queueItem) => {
940
940
  let queueItemIsInReorgThreshold = (
941
941
  queueItem: queueItem,
942
942
  ~currentBlockHeight,
943
- ~heighestBlockBelowThreshold,
943
+ ~highestBlockBelowThreshold,
944
944
  ) => {
945
945
  if currentBlockHeight === 0 {
946
946
  false
947
947
  } else {
948
948
  switch queueItem {
949
- | Item(_) => queueItem->queueItemBlockNumber > heighestBlockBelowThreshold
950
- | NoItem(_) => queueItem->queueItemBlockNumber > heighestBlockBelowThreshold
949
+ | Item(_) => queueItem->queueItemBlockNumber > highestBlockBelowThreshold
950
+ | NoItem(_) => queueItem->queueItemBlockNumber > highestBlockBelowThreshold
951
951
  }
952
952
  }
953
953
  }
@@ -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({
@@ -356,6 +364,30 @@ module IndexingPartitions = {
356
364
  }
357
365
  }
358
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
+
359
391
  module IndexingBufferSize = {
360
392
  let gauge = SafeGauge.makeOrThrow(
361
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
 
@@ -1,5 +1,7 @@
1
1
  open Belt
2
2
 
3
+ type sourceManagerStatus = Idle | WaitingForNewBlock | Querieng
4
+
3
5
  // Ideally the ChainFetcher name suits this better
4
6
  // But currently the ChainFetcher module is immutable
5
7
  // and handles both processing and fetching.
@@ -7,6 +9,8 @@ open Belt
7
9
  // with a mutable state for easier reasoning and testing.
8
10
  type t = {
9
11
  sources: Utils.Set.t<Source.t>,
12
+ mutable statusStart: Hrtime.timeRef,
13
+ mutable status: sourceManagerStatus,
10
14
  maxPartitionConcurrency: int,
11
15
  newBlockFallbackStallTimeout: int,
12
16
  stalledPollingInterval: int,
@@ -66,7 +70,23 @@ let make = (
66
70
  newBlockFallbackStallTimeout,
67
71
  stalledPollingInterval,
68
72
  getHeightRetryInterval,
73
+ statusStart: Hrtime.makeTimer(),
74
+ status: Idle,
75
+ }
76
+ }
77
+
78
+ let trackNewStatus = (sourceManager: t, ~newStatus) => {
79
+ let promCounter = switch newStatus {
80
+ | Idle => Prometheus.IndexingIdleTime.counter
81
+ | WaitingForNewBlock => Prometheus.IndexingSourceWaitingTime.counter
82
+ | Querieng => Prometheus.IndexingQueryTime.counter
69
83
  }
84
+ promCounter->Prometheus.SafeCounter.incrementMany(
85
+ ~labels=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
86
+ ~value=sourceManager.statusStart->Hrtime.timeSince->Hrtime.toMillis->Hrtime.intFromMillis,
87
+ )
88
+ sourceManager.statusStart = Hrtime.makeTimer()
89
+ sourceManager.status = newStatus
70
90
  }
71
91
 
72
92
  let fetchNext = async (
@@ -96,10 +116,12 @@ let fetchNext = async (
96
116
  | Some(waitingStateId) if waitingStateId >= stateId => ()
97
117
  | Some(_) // Case for the prev state before a rollback
98
118
  | None =>
119
+ sourceManager->trackNewStatus(~newStatus=WaitingForNewBlock)
99
120
  sourceManager.waitingForNewBlockStateId = Some(stateId)
100
121
  let currentBlockHeight = await waitForNewBlock(~currentBlockHeight)
101
122
  switch sourceManager.waitingForNewBlockStateId {
102
123
  | Some(waitingStateId) if waitingStateId === stateId => {
124
+ sourceManager->trackNewStatus(~newStatus=Idle)
103
125
  sourceManager.waitingForNewBlockStateId = None
104
126
  onNewBlock(~currentBlockHeight)
105
127
  }
@@ -115,6 +137,7 @@ let fetchNext = async (
115
137
  ~concurrency=sourceManager.fetchingPartitionsCount,
116
138
  ~chainId=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
117
139
  )
140
+ sourceManager->trackNewStatus(~newStatus=Querieng)
118
141
  let _ =
119
142
  await queries
120
143
  ->Array.map(q => {
@@ -125,6 +148,9 @@ let fetchNext = async (
125
148
  ~concurrency=sourceManager.fetchingPartitionsCount,
126
149
  ~chainId=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
127
150
  )
151
+ if sourceManager.fetchingPartitionsCount === 0 {
152
+ sourceManager->trackNewStatus(~newStatus=Idle)
153
+ }
128
154
  })
129
155
  promise
130
156
  })