envio 3.3.0-alpha.0 → 3.3.0-alpha.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.
Files changed (73) hide show
  1. package/package.json +6 -6
  2. package/src/Batch.res +0 -6
  3. package/src/Batch.res.mjs +0 -11
  4. package/src/BatchProcessing.res +2 -12
  5. package/src/BatchProcessing.res.mjs +2 -7
  6. package/src/ChainFetching.res +22 -13
  7. package/src/ChainFetching.res.mjs +13 -13
  8. package/src/ChainMetadata.res +1 -11
  9. package/src/ChainMetadata.res.mjs +1 -10
  10. package/src/ChainState.res +176 -2
  11. package/src/ChainState.res.mjs +153 -9
  12. package/src/ChainState.resi +53 -1
  13. package/src/Config.res +3 -6
  14. package/src/Config.res.mjs +2 -3
  15. package/src/Core.res +7 -0
  16. package/src/CrossChainState.res +99 -91
  17. package/src/CrossChainState.res.mjs +72 -57
  18. package/src/CrossChainState.resi +1 -9
  19. package/src/Ecosystem.res +6 -1
  20. package/src/Env.res +0 -7
  21. package/src/Env.res.mjs +0 -6
  22. package/src/Envio.res +3 -2
  23. package/src/EventConfigBuilder.res +19 -5
  24. package/src/EventConfigBuilder.res.mjs +6 -4
  25. package/src/EventProcessing.res +26 -0
  26. package/src/EventProcessing.res.mjs +20 -0
  27. package/src/FetchState.res +92 -22
  28. package/src/FetchState.res.mjs +87 -14
  29. package/src/IndexerLoop.res +2 -3
  30. package/src/IndexerLoop.res.mjs +1 -1
  31. package/src/IndexerState.res +0 -8
  32. package/src/IndexerState.res.mjs +4 -8
  33. package/src/IndexerState.resi +0 -4
  34. package/src/Internal.res +43 -4
  35. package/src/Internal.res.mjs +18 -0
  36. package/src/Main.res +3 -50
  37. package/src/Main.res.mjs +3 -24
  38. package/src/Prometheus.res +0 -11
  39. package/src/Prometheus.res.mjs +83 -98
  40. package/src/Rollback.res +1 -1
  41. package/src/Rollback.res.mjs +1 -1
  42. package/src/SimulateItems.res +3 -0
  43. package/src/SimulateItems.res.mjs +2 -1
  44. package/src/sources/Evm.res +52 -38
  45. package/src/sources/Evm.res.mjs +48 -36
  46. package/src/sources/Fuel.res +3 -1
  47. package/src/sources/Fuel.res.mjs +1 -0
  48. package/src/sources/HyperFuelSource.res +5 -0
  49. package/src/sources/HyperFuelSource.res.mjs +2 -0
  50. package/src/sources/HyperSync.res +4 -1
  51. package/src/sources/HyperSync.res.mjs +5 -3
  52. package/src/sources/HyperSync.resi +1 -0
  53. package/src/sources/HyperSyncClient.res +9 -78
  54. package/src/sources/HyperSyncClient.res.mjs +1 -22
  55. package/src/sources/HyperSyncSource.res +3 -4
  56. package/src/sources/HyperSyncSource.res.mjs +2 -1
  57. package/src/sources/RpcSource.res +7 -2
  58. package/src/sources/RpcSource.res.mjs +3 -1
  59. package/src/sources/SimulateSource.res +3 -1
  60. package/src/sources/SimulateSource.res.mjs +1 -0
  61. package/src/sources/Source.res +4 -0
  62. package/src/sources/SourceManager.res +9 -8
  63. package/src/sources/SourceManager.res.mjs +21 -26
  64. package/src/sources/SourceManager.resi +2 -3
  65. package/src/sources/Svm.res +25 -2
  66. package/src/sources/Svm.res.mjs +27 -2
  67. package/src/sources/SvmHyperSyncClient.res +3 -29
  68. package/src/sources/SvmHyperSyncSource.res +60 -84
  69. package/src/sources/SvmHyperSyncSource.res.mjs +59 -78
  70. package/src/sources/TransactionStore.res +111 -0
  71. package/src/sources/TransactionStore.res.mjs +77 -0
  72. package/src/tui/Tui.res +13 -16
  73. package/src/tui/Tui.res.mjs +12 -14
@@ -111,9 +111,8 @@ let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
111
111
  let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
112
112
  discriminator: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
113
113
  discriminatorByteLen: s.m(S$RescriptSchema.int),
114
- includeTransaction: s.m(S$RescriptSchema.bool),
114
+ transactionFields: s.m(S$RescriptSchema.array(Internal.svmTransactionFieldSchema)),
115
115
  includeLogs: s.m(S$RescriptSchema.bool),
116
- includeTokenBalances: s.m(S$RescriptSchema.bool),
117
116
  accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
118
117
  position: s.m(S$RescriptSchema.int),
119
118
  values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
@@ -518,7 +517,7 @@ function fromPublic(publicConfigJson) {
518
517
  position: af.position,
519
518
  values: af.values
520
519
  })));
521
- return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeTransaction, svm.includeLogs, svm.includeTokenBalances, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
520
+ return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeLogs, svm.transactionFields, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
522
521
  }
523
522
  });
524
523
  } else {
package/src/Core.res CHANGED
@@ -9,6 +9,7 @@ type evmRpcClientCtor
9
9
  type evmDecoderCtor
10
10
  type svmHypersyncClientCtor
11
11
  type hyperfuelClientCtor
12
+ type transactionStoreCtor
12
13
 
13
14
  type addon = {
14
15
  getConfigJson: (~configPath: Null.t<string>, ~directory: Null.t<string>) => string,
@@ -23,6 +24,12 @@ type addon = {
23
24
  svmHypersyncClient: svmHypersyncClientCtor,
24
25
  @as("HyperfuelClient")
25
26
  hyperfuelClient: hyperfuelClientCtor,
27
+ @as("TransactionStore")
28
+ transactionStore: transactionStoreCtor,
29
+ // Ordered transaction-field names exposed for the field-code contract test
30
+ // (the ReScript `transactionFields` arrays must match the Rust ordinals).
31
+ evmTransactionFieldNames: unit => array<string>,
32
+ svmTransactionFieldNames: unit => array<string>,
26
33
  }
27
34
 
28
35
  @module("node:module") external createRequire: string => {..} = "createRequire"
@@ -11,10 +11,6 @@ type t = {
11
11
  // True once every chain has caught up to head/endBlock. Monotonic during a run.
12
12
  mutable isRealtime: bool,
13
13
  mutable isInReorgThreshold: bool,
14
- // Indexer-wide caps on concurrent data-source queries, shared across all
15
- // chains. The realtime budget applies once every chain is at head.
16
- maxBackfillConcurrency: int,
17
- maxRealtimeConcurrency: int,
18
14
  // Indexer-wide fetch buffer pool (item count), shared across all chains.
19
15
  targetBufferSize: int,
20
16
  }
@@ -26,18 +22,10 @@ let calculateTargetBufferSize = () =>
26
22
  | None => 100_000
27
23
  }
28
24
 
29
- // The concurrency budget in force for the current phase.
30
- let maxConcurrency = (crossChainState: t) =>
31
- crossChainState.isRealtime
32
- ? crossChainState.maxRealtimeConcurrency
33
- : crossChainState.maxBackfillConcurrency
34
-
35
25
  let make = (
36
26
  ~chainStates,
37
27
  ~isInReorgThreshold,
38
28
  ~isRealtime,
39
- ~maxBackfillConcurrency=Env.maxBackfillConcurrency,
40
- ~maxRealtimeConcurrency=Env.maxRealtimeConcurrency,
41
29
  ~targetBufferSize=calculateTargetBufferSize(),
42
30
  ): t => {
43
31
  let crossChainState = {
@@ -45,11 +33,8 @@ let make = (
45
33
  chainIds: chainStates->Dict.valuesToArray->Array.map(cs => (cs->ChainState.chainConfig).id),
46
34
  isRealtime,
47
35
  isInReorgThreshold,
48
- maxBackfillConcurrency,
49
- maxRealtimeConcurrency,
50
36
  targetBufferSize,
51
37
  }
52
- Prometheus.IndexingMaxConcurrency.set(~maxConcurrency=crossChainState->maxConcurrency)
53
38
  Prometheus.IndexingTargetBufferSize.set(~targetBufferSize)
54
39
  crossChainState
55
40
  }
@@ -65,17 +50,6 @@ let chainStates = (crossChainState: t) => crossChainState.chainStates
65
50
  let isRealtime = (crossChainState: t) => crossChainState.isRealtime
66
51
  let isInReorgThreshold = (crossChainState: t) => crossChainState.isInReorgThreshold
67
52
 
68
- // Partition queries in flight across every chain — the live draw against
69
- // maxConcurrency.
70
- let inFlight = (crossChainState: t) => {
71
- let total = ref(0)
72
- for i in 0 to crossChainState.chainIds->Array.length - 1 {
73
- let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
74
- total := total.contents + cs->ChainState.sourceManager->SourceManager.inFlightCount
75
- }
76
- total.contents
77
- }
78
-
79
53
  // Ready-to-process items across every chain — the live draw against
80
54
  // targetBufferSize, which is a budget of processable events (items stuck behind
81
55
  // a gap don't count toward the goal of keeping ~targetBufferSize ready).
@@ -83,7 +57,7 @@ let totalReadyCount = (crossChainState: t) => {
83
57
  let total = ref(0)
84
58
  for i in 0 to crossChainState.chainIds->Array.length - 1 {
85
59
  let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
86
- total := total.contents + cs->ChainState.fetchState->FetchState.bufferReadyCount
60
+ total := total.contents + cs->ChainState.bufferReadyCount
87
61
  }
88
62
  total.contents
89
63
  }
@@ -91,9 +65,7 @@ let totalReadyCount = (crossChainState: t) => {
91
65
  // --- Derived (pure). ---
92
66
 
93
67
  let nextItemIsNone = (crossChainState: t): bool =>
94
- !Batch.hasReadyItem(
95
- crossChainState.chainStates->Dict.valuesToArray->Array.map(ChainState.fetchState),
96
- )
68
+ !(crossChainState.chainStates->Dict.valuesToArray->Array.some(ChainState.hasReadyItem))
97
69
 
98
70
  let getSafeCheckpointId = (crossChainState: t) => {
99
71
  let result: ref<option<bigint>> = ref(None)
@@ -105,7 +77,7 @@ let getSafeCheckpointId = (crossChainState: t) => {
105
77
  | Some(safeCheckpointTracking) =>
106
78
  let safeCheckpointId =
107
79
  safeCheckpointTracking->SafeCheckpointTracking.getSafeCheckpointId(
108
- ~sourceBlockNumber=(cs->ChainState.fetchState).knownHeight,
80
+ ~sourceBlockNumber=cs->ChainState.knownHeight,
109
81
  )
110
82
  switch result.contents {
111
83
  | None => result := Some(safeCheckpointId)
@@ -137,16 +109,9 @@ let createBatch = (
137
109
  // in an append-only ClickHouse insert.
138
110
  isRollback ? 1n : 0n,
139
111
  ),
140
- ~chainsBeforeBatch=crossChainState.chainStates->Utils.Dict.mapValues((
141
- cs
142
- ): Batch.chainBeforeBatch => {
143
- fetchState: cs->ChainState.fetchState,
144
- progressBlockNumber: cs->ChainState.committedProgressBlockNumber,
145
- totalEventsProcessed: cs->ChainState.numEventsProcessed,
146
- sourceBlockNumber: (cs->ChainState.fetchState).knownHeight,
147
- reorgDetection: cs->ChainState.reorgDetection,
148
- chainConfig: cs->ChainState.chainConfig,
149
- }),
112
+ ~chainsBeforeBatch=crossChainState.chainStates->Utils.Dict.mapValues(
113
+ ChainState.toChainBeforeBatch,
114
+ ),
150
115
  ~batchSizeTarget,
151
116
  )
152
117
  }
@@ -199,71 +164,114 @@ let applyBatchProgress = (crossChainState: t, ~batch: Batch.t) => {
199
164
  Prometheus.ProgressReady.setAllReady()
200
165
  }
201
166
 
202
- let wasRealtime = crossChainState.isRealtime
203
167
  crossChainState.isRealtime = crossChainState.isRealtime || allChainsReady.contents
204
- if !wasRealtime && crossChainState.isRealtime {
205
- // The realtime budget takes over now that every chain is at head.
206
- Prometheus.IndexingMaxConcurrency.set(~maxConcurrency=crossChainState->maxConcurrency)
207
- }
208
168
  }
209
169
 
210
170
  // --- Fetch control. ---
211
171
 
212
- // Chains ordered furthest-behind first, so the shared concurrency and buffer
213
- // pools go to the chains with the most backfill work before the rest.
172
+ // Chains ordered furthest-behind first, so the shared buffer pool goes to the
173
+ // chains with the most backfill work before the rest.
214
174
  let priorityOrder = (crossChainState: t) =>
215
175
  crossChainState.chainStates
216
176
  ->Dict.valuesToArray
217
177
  ->Array.toSorted((a, b) =>
218
- Float.compare(
219
- a->ChainState.fetchState->FetchState.getProgressPercentage,
220
- b->ChainState.fetchState->FetchState.getProgressPercentage,
221
- )
178
+ Float.compare(a->ChainState.getProgressPercentage, b->ChainState.getProgressPercentage)
222
179
  )
223
180
 
224
- // Dispatch a fetch tick across every chain in priority order, drawing from the
225
- // shared concurrency and buffer pools. Chains are visited in turn; fetchChain
226
- // bumps the in-flight count synchronously before it suspends, so a later chain
227
- // sees the slots an earlier one already claimed and the budget is honored
228
- // indexer-wide.
229
- //
230
- // bufferLimit is each chain's slice of the shared pool: it may grow its buffer
231
- // into whatever the other chains leave free, so a lone backfilling chain can use
232
- // the whole pool while head-following chains stay shallow.
181
+ // In-flight estimated items across every chain the live draw against
182
+ // targetBufferSize alongside totalReadyCount, so the pool isn't re-dispatched
183
+ // while queries are still being fetched.
184
+ let totalReservedSize = (crossChainState: t) => {
185
+ let total = ref(0.)
186
+ for i in 0 to crossChainState.chainIds->Array.length - 1 {
187
+ let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
188
+ total := total.contents +. cs->ChainState.pendingBudget
189
+ }
190
+ total.contents
191
+ }
192
+
193
+ // Furthest-behind first: order candidate queries by the chain progress % at
194
+ // their fromBlock, so the most behind ranges across all chains are admitted
195
+ // before the rest.
196
+ let compareByProgress = (a: FetchState.query, b: FetchState.query) =>
197
+ Float.compare(a.progress, b.progress)
198
+
199
+ // Dispatch a fetch tick across the whole indexer from one shared pool of
200
+ // ~targetBufferSize ready events. Every chain proposes its candidate queries
201
+ // (each carrying an estimated response size) against the full free budget; the
202
+ // candidates are then pooled, ordered by chain progress (furthest-behind first),
203
+ // and admitted until the budget is consumed. So the budget is split per query
204
+ // across chains rather than per chain — a chain that can only use a little
205
+ // leaves the rest for the others automatically.
233
206
  let checkAndFetch = async (
234
207
  crossChainState: t,
235
- ~fetchChain: (
236
- ~chain: ChainMap.Chain.t,
237
- ~concurrencyLimit: int,
238
- ~bufferLimit: int,
239
- ) => promise<unit>,
208
+ ~dispatchChain: (~chain: ChainMap.Chain.t, ~action: FetchState.nextQuery) => promise<unit>,
240
209
  ) => {
241
- let maxConcurrency = crossChainState->maxConcurrency
242
- // Pool is a budget of ready-to-process items, but the cap fed to getNextQuery
243
- // is still a buffer position (it tracks the sorted buffer, ready items first),
244
- // so add this chain's own buffer size back rather than its ready count. A chain
245
- // with a gap therefore gets extra headroom to fetch the not-ready overhang
246
- // while still aiming for its share of ~targetBufferSize ready events.
247
- let totalReady = crossChainState->totalReadyCount
248
- // Track the in-flight total as a running counter (summed once up front, then
249
- // adjusted by each chain's delta) instead of re-summing every chain — O(chains)
250
- // per tick rather than O(chains^2). fetchChain bumps the chain's count
251
- // synchronously, so the delta is observable right after the call.
252
- let inFlight = ref(crossChainState->inFlight)
253
- let priorityOrdered = crossChainState->priorityOrder
210
+ let remaining = Pervasives.max(
211
+ 0,
212
+ crossChainState.targetBufferSize -
213
+ crossChainState->totalReadyCount -
214
+ crossChainState->totalReservedSize->Float.toInt,
215
+ )
216
+
217
+ let chainIds = crossChainState.chainIds
218
+ let actionByChain = Dict.make()
219
+ // Candidate queries from every chain. Each query carries its chain id and the
220
+ // chain progress % at its fromBlock (the admission sort key), set here so the
221
+ // pool can be ordered without a side tuple per query.
222
+ let candidates = []
223
+ for i in 0 to chainIds->Array.length - 1 {
224
+ let chainId = chainIds->Array.getUnsafe(i)
225
+ let cs = crossChainState->getChainState(chainId)
226
+ switch cs->ChainState.getNextQuery(~budget=remaining) {
227
+ | (WaitingForNewBlock | NothingToQuery) as action =>
228
+ actionByChain->Utils.Dict.setByInt(chainId, action)
229
+ | Ready(queries) =>
230
+ // Default to NothingToQuery; replaced below if any candidate is admitted.
231
+ actionByChain->Utils.Dict.setByInt(chainId, FetchState.NothingToQuery)
232
+ queries->Array.forEach(query => {
233
+ query.chainId = chainId
234
+ query.progress = cs->ChainState.getProgressPercentageAt(~blockNumber=query.fromBlock)
235
+ candidates->Array.push(query)
236
+ })
237
+ }
238
+ }
239
+
240
+ candidates->Array.sort(compareByProgress)
241
+
242
+ // Admit furthest-behind first until the budget runs out. The condition is
243
+ // checked before each query, so as long as there's any budget left we admit
244
+ // the next one even when its estimate alone exceeds the remainder — otherwise a
245
+ // chain whose only query is bigger than the budget would never make progress.
246
+ let admittedByChain = Dict.make()
247
+ let running = ref(0.)
248
+ let remainingF = remaining->Int.toFloat
249
+ let idx = ref(0)
250
+ while running.contents < remainingF && idx.contents < candidates->Array.length {
251
+ let query = candidates->Array.getUnsafe(idx.contents)
252
+ admittedByChain->Utils.Dict.push(query.chainId->Int.toString, query)
253
+ running := running.contents +. query.estResponseSize
254
+ idx := idx.contents + 1
255
+ }
256
+ admittedByChain->Dict.forEachWithKey((queries, chainId) => {
257
+ actionByChain->Dict.set(chainId, FetchState.Ready(queries))
258
+ // Mark the admitted queries in flight and reserve their size against the
259
+ // shared budget; released as each response lands in handleQueryResult.
260
+ crossChainState
261
+ ->getChainState(chainId->Int.fromString->Option.getUnsafe)
262
+ ->ChainState.startFetchingQueries(~queries)
263
+ })
264
+
254
265
  let promises = []
255
- for i in 0 to priorityOrdered->Array.length - 1 {
256
- let cs = priorityOrdered->Array.getUnsafe(i)
257
- let chain = ChainMap.Chain.makeUnsafe(~chainId=(cs->ChainState.chainConfig).id)
258
- let sourceManager = cs->ChainState.sourceManager
259
- let concurrencyLimit = Pervasives.max(0, maxConcurrency - inFlight.contents)
260
- let bufferLimit =
261
- crossChainState.targetBufferSize -
262
- (totalReady -
263
- cs->ChainState.fetchState->FetchState.bufferSize)
264
- let inFlightBefore = sourceManager->SourceManager.inFlightCount
265
- promises->Array.push(fetchChain(~chain, ~concurrencyLimit, ~bufferLimit))
266
- inFlight := inFlight.contents + (sourceManager->SourceManager.inFlightCount - inFlightBefore)
266
+ for i in 0 to chainIds->Array.length - 1 {
267
+ let chainId = chainIds->Array.getUnsafe(i)
268
+ switch actionByChain->Utils.Dict.dangerouslyGetByIntNonOption(chainId) {
269
+ | Some(NothingToQuery)
270
+ | None => ()
271
+ | Some(action) =>
272
+ let chain = ChainMap.Chain.makeUnsafe(~chainId)
273
+ promises->Array.push(dispatchChain(~chain, ~action))
274
+ }
267
275
  }
268
276
  let _ = await promises->Promise.all
269
277
  }
@@ -6,10 +6,10 @@ import * as Utils from "./Utils.res.mjs";
6
6
  import * as Logging from "./Logging.res.mjs";
7
7
  import * as ChainMap from "./ChainMap.res.mjs";
8
8
  import * as ChainState from "./ChainState.res.mjs";
9
- import * as FetchState from "./FetchState.res.mjs";
10
9
  import * as Prometheus from "./Prometheus.res.mjs";
10
+ import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
11
+ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
11
12
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
12
- import * as SourceManager from "./sources/SourceManager.res.mjs";
13
13
  import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
14
14
  import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
15
15
 
@@ -21,17 +21,7 @@ function calculateTargetBufferSize() {
21
21
  }
22
22
  }
23
23
 
24
- function maxConcurrency(crossChainState) {
25
- if (crossChainState.isRealtime) {
26
- return crossChainState.maxRealtimeConcurrency;
27
- } else {
28
- return crossChainState.maxBackfillConcurrency;
29
- }
30
- }
31
-
32
- function make(chainStates, isInReorgThreshold, isRealtime, maxBackfillConcurrencyOpt, maxRealtimeConcurrencyOpt, targetBufferSizeOpt) {
33
- let maxBackfillConcurrency = maxBackfillConcurrencyOpt !== undefined ? maxBackfillConcurrencyOpt : Env.maxBackfillConcurrency;
34
- let maxRealtimeConcurrency = maxRealtimeConcurrencyOpt !== undefined ? maxRealtimeConcurrencyOpt : Env.maxRealtimeConcurrency;
24
+ function make(chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt) {
35
25
  let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : (
36
26
  Env.targetBufferSize !== undefined ? Env.targetBufferSize : 100000
37
27
  );
@@ -40,11 +30,8 @@ function make(chainStates, isInReorgThreshold, isRealtime, maxBackfillConcurrenc
40
30
  chainIds: Object.values(chainStates).map(cs => ChainState.chainConfig(cs).id),
41
31
  isRealtime: isRealtime,
42
32
  isInReorgThreshold: isInReorgThreshold,
43
- maxBackfillConcurrency: maxBackfillConcurrency,
44
- maxRealtimeConcurrency: maxRealtimeConcurrency,
45
33
  targetBufferSize: targetBufferSize
46
34
  };
47
- Prometheus.IndexingMaxConcurrency.set(maxConcurrency(crossChainState));
48
35
  Prometheus.IndexingTargetBufferSize.set(targetBufferSize);
49
36
  return crossChainState;
50
37
  }
@@ -61,26 +48,17 @@ function isInReorgThreshold(crossChainState) {
61
48
  return crossChainState.isInReorgThreshold;
62
49
  }
63
50
 
64
- function inFlight(crossChainState) {
65
- let total = 0;
66
- for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
67
- let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
68
- total = total + SourceManager.inFlightCount(ChainState.sourceManager(cs)) | 0;
69
- }
70
- return total;
71
- }
72
-
73
51
  function totalReadyCount(crossChainState) {
74
52
  let total = 0;
75
53
  for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
76
54
  let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
77
- total = total + FetchState.bufferReadyCount(ChainState.fetchState(cs)) | 0;
55
+ total = total + ChainState.bufferReadyCount(cs) | 0;
78
56
  }
79
57
  return total;
80
58
  }
81
59
 
82
60
  function nextItemIsNone(crossChainState) {
83
- return !Batch.hasReadyItem(Object.values(crossChainState.chainStates).map(ChainState.fetchState));
61
+ return !Object.values(crossChainState.chainStates).some(ChainState.hasReadyItem);
84
62
  }
85
63
 
86
64
  function getSafeCheckpointId(crossChainState) {
@@ -89,7 +67,7 @@ function getSafeCheckpointId(crossChainState) {
89
67
  let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
90
68
  let safeCheckpointTracking = ChainState.safeCheckpointTracking(cs);
91
69
  if (safeCheckpointTracking !== undefined) {
92
- let safeCheckpointId = SafeCheckpointTracking.getSafeCheckpointId(safeCheckpointTracking, ChainState.fetchState(cs).knownHeight);
70
+ let safeCheckpointId = SafeCheckpointTracking.getSafeCheckpointId(safeCheckpointTracking, ChainState.knownHeight(cs));
93
71
  let current = result;
94
72
  if (!(current !== undefined && safeCheckpointId >= current)) {
95
73
  result = safeCheckpointId;
@@ -105,14 +83,7 @@ function getSafeCheckpointId(crossChainState) {
105
83
  function createBatch(crossChainState, processedCheckpointId, batchSizeTarget, isRollback) {
106
84
  return Batch.make(processedCheckpointId + (
107
85
  isRollback ? 1n : 0n
108
- ), Utils.Dict.mapValues(crossChainState.chainStates, cs => ({
109
- fetchState: ChainState.fetchState(cs),
110
- reorgDetection: ChainState.reorgDetection(cs),
111
- progressBlockNumber: ChainState.committedProgressBlockNumber(cs),
112
- sourceBlockNumber: ChainState.fetchState(cs).knownHeight,
113
- totalEventsProcessed: ChainState.numEventsProcessed(cs),
114
- chainConfig: ChainState.chainConfig(cs)
115
- })), batchSizeTarget, crossChainState.isInReorgThreshold);
86
+ ), Utils.Dict.mapValues(crossChainState.chainStates, ChainState.toChainBeforeBatch), batchSizeTarget, crossChainState.isInReorgThreshold);
116
87
  }
117
88
 
118
89
  function enterReorgThreshold(crossChainState) {
@@ -148,32 +119,78 @@ function applyBatchProgress(crossChainState, batch) {
148
119
  if (allChainsReady) {
149
120
  Prometheus.ProgressReady.setAllReady();
150
121
  }
151
- let wasRealtime = crossChainState.isRealtime;
152
122
  crossChainState.isRealtime = crossChainState.isRealtime || allChainsReady;
153
- if (!wasRealtime && crossChainState.isRealtime) {
154
- return Prometheus.IndexingMaxConcurrency.set(maxConcurrency(crossChainState));
155
- }
156
123
  }
157
124
 
158
125
  function priorityOrder(crossChainState) {
159
- return Object.values(crossChainState.chainStates).toSorted((a, b) => Primitive_float.compare(FetchState.getProgressPercentage(ChainState.fetchState(a)), FetchState.getProgressPercentage(ChainState.fetchState(b))));
126
+ return Object.values(crossChainState.chainStates).toSorted((a, b) => Primitive_float.compare(ChainState.getProgressPercentage(a), ChainState.getProgressPercentage(b)));
127
+ }
128
+
129
+ function totalReservedSize(crossChainState) {
130
+ let total = 0;
131
+ for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
132
+ let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
133
+ total = total + ChainState.pendingBudget(cs);
134
+ }
135
+ return total;
160
136
  }
161
137
 
162
- async function checkAndFetch(crossChainState, fetchChain) {
163
- let maxConcurrency$1 = maxConcurrency(crossChainState);
164
- let totalReady = totalReadyCount(crossChainState);
165
- let inFlight$1 = inFlight(crossChainState);
166
- let priorityOrdered = priorityOrder(crossChainState);
138
+ function compareByProgress(a, b) {
139
+ return Primitive_float.compare(a.progress, b.progress);
140
+ }
141
+
142
+ async function checkAndFetch(crossChainState, dispatchChain) {
143
+ let remaining = Primitive_int.max(0, (crossChainState.targetBufferSize - totalReadyCount(crossChainState) | 0) - (totalReservedSize(crossChainState) | 0) | 0);
144
+ let chainIds = crossChainState.chainIds;
145
+ let actionByChain = {};
146
+ let candidates = [];
147
+ for (let i = 0, i_finish = chainIds.length; i < i_finish; ++i) {
148
+ let chainId = chainIds[i];
149
+ let cs = crossChainState.chainStates[chainId];
150
+ let action = ChainState.getNextQuery(cs, remaining);
151
+ if (typeof action !== "object") {
152
+ actionByChain[chainId] = action;
153
+ } else {
154
+ actionByChain[chainId] = "NothingToQuery";
155
+ action._0.forEach(query => {
156
+ query.chainId = chainId;
157
+ query.progress = ChainState.getProgressPercentageAt(cs, query.fromBlock);
158
+ candidates.push(query);
159
+ });
160
+ }
161
+ }
162
+ candidates.sort(compareByProgress);
163
+ let admittedByChain = {};
164
+ let running = 0;
165
+ let idx = 0;
166
+ while (running < remaining && idx < candidates.length) {
167
+ let query = candidates[idx];
168
+ Utils.Dict.push(admittedByChain, query.chainId.toString(), query);
169
+ running = running + query.estResponseSize;
170
+ idx = idx + 1 | 0;
171
+ };
172
+ Stdlib_Dict.forEachWithKey(admittedByChain, (queries, chainId) => {
173
+ actionByChain[chainId] = {
174
+ TAG: "Ready",
175
+ _0: queries
176
+ };
177
+ let chainId$1 = Stdlib_Int.fromString(chainId, undefined);
178
+ ChainState.startFetchingQueries(crossChainState.chainStates[chainId$1], queries);
179
+ });
167
180
  let promises = [];
168
- for (let i = 0, i_finish = priorityOrdered.length; i < i_finish; ++i) {
169
- let cs = priorityOrdered[i];
170
- let chain = ChainMap.Chain.makeUnsafe(ChainState.chainConfig(cs).id);
171
- let sourceManager = ChainState.sourceManager(cs);
172
- let concurrencyLimit = Primitive_int.max(0, maxConcurrency$1 - inFlight$1 | 0);
173
- let bufferLimit = crossChainState.targetBufferSize - (totalReady - FetchState.bufferSize(ChainState.fetchState(cs)) | 0) | 0;
174
- let inFlightBefore = SourceManager.inFlightCount(sourceManager);
175
- promises.push(fetchChain(chain, concurrencyLimit, bufferLimit));
176
- inFlight$1 = inFlight$1 + (SourceManager.inFlightCount(sourceManager) - inFlightBefore | 0) | 0;
181
+ for (let i$1 = 0, i_finish$1 = chainIds.length; i$1 < i_finish$1; ++i$1) {
182
+ let chainId$1 = chainIds[i$1];
183
+ let action$1 = actionByChain[chainId$1];
184
+ if (action$1 !== undefined) {
185
+ let exit = 0;
186
+ if (typeof action$1 === "object" || action$1 === "WaitingForNewBlock") {
187
+ exit = 1;
188
+ }
189
+ if (exit === 1) {
190
+ let chain = ChainMap.Chain.makeUnsafe(chainId$1);
191
+ promises.push(dispatchChain(chain, action$1));
192
+ }
193
+ }
177
194
  }
178
195
  await Promise.all(promises);
179
196
  }
@@ -184,8 +201,6 @@ export {
184
201
  chainStates,
185
202
  isRealtime,
186
203
  isInReorgThreshold,
187
- maxConcurrency,
188
- inFlight,
189
204
  nextItemIsNone,
190
205
  getSafeCheckpointId,
191
206
  createBatch,
@@ -9,8 +9,6 @@ let make: (
9
9
  ~chainStates: dict<ChainState.t>,
10
10
  ~isInReorgThreshold: bool,
11
11
  ~isRealtime: bool,
12
- ~maxBackfillConcurrency: int=?,
13
- ~maxRealtimeConcurrency: int=?,
14
12
  ~targetBufferSize: int=?,
15
13
  ) => t
16
14
 
@@ -18,8 +16,6 @@ let make: (
18
16
  let chainStates: t => dict<ChainState.t>
19
17
  let isRealtime: t => bool
20
18
  let isInReorgThreshold: t => bool
21
- let maxConcurrency: t => int
22
- let inFlight: t => int
23
19
 
24
20
  // Derived (pure).
25
21
  let nextItemIsNone: t => bool
@@ -39,9 +35,5 @@ let applyBatchProgress: (t, ~batch: Batch.t) => unit
39
35
  let priorityOrder: t => array<ChainState.t>
40
36
  let checkAndFetch: (
41
37
  t,
42
- ~fetchChain: (
43
- ~chain: ChainMap.Chain.t,
44
- ~concurrencyLimit: int,
45
- ~bufferLimit: int,
46
- ) => promise<unit>,
38
+ ~dispatchChain: (~chain: ChainMap.Chain.t, ~action: FetchState.nextQuery) => promise<unit>,
47
39
  ) => promise<unit>
package/src/Ecosystem.res CHANGED
@@ -33,8 +33,13 @@ type t = {
33
33
  loggers (see `getItemLogger`). */
34
34
  logger: Pino.t,
35
35
  /** Materialise the user-facing event handed to handlers and contract
36
- registration from an item's opaque payload. */
36
+ registration from an item's opaque payload. `event.transaction` is written
37
+ onto the payload at batch prep (HyperSync) or inline (RPC/simulate). */
37
38
  toEvent: Internal.eventItem => Internal.event,
39
+ /** Bitmask (as a float) of the transaction fields selected across the chain's
40
+ events — the set the store materialises at batch prep. `0.` when the
41
+ ecosystem carries transactions inline. */
42
+ transactionFieldMask: array<Internal.eventConfig> => float,
38
43
  /** Build the per-item child logger for an event item, with
39
44
  ecosystem-specific log fields (EVM/Fuel: contract/event/address; SVM:
40
45
  program/instruction/programId). Closes over the injected logger. */
package/src/Env.res CHANGED
@@ -8,13 +8,6 @@ let updateSyncTimeOnRestart =
8
8
  envSafe->EnvSafe.get("UPDATE_SYNC_TIME_ON_RESTART", S.bool, ~fallback=true)
9
9
  let targetBufferSize = envSafe->EnvSafe.get("ENVIO_INDEXING_MAX_BUFFER_SIZE", S.option(S.int))
10
10
  let maxAddrInPartition = envSafe->EnvSafe.get("MAX_PARTITION_SIZE", S.int, ~fallback=5_000)
11
- // Global caps on concurrent data-source queries across the whole indexer (all
12
- // chains share the budget), not per chain. Realtime allows far more since head
13
- // queries are small and many chains follow the head at once.
14
- let maxBackfillConcurrency =
15
- envSafe->EnvSafe.get("ENVIO_MAX_BACKFILL_CONCURRENCY", S.int, ~fallback=30)
16
- let maxRealtimeConcurrency =
17
- envSafe->EnvSafe.get("ENVIO_MAX_REALTIME_CONCURRENCY", S.int, ~fallback=200)
18
11
 
19
12
  // Target number of in-memory objects (uncommitted entity/effect changes plus
20
13
  // unwritten batch items) the store holds before processing waits for the write
package/src/Env.res.mjs CHANGED
@@ -19,10 +19,6 @@ let targetBufferSize = EnvSafe.get(envSafe, "ENVIO_INDEXING_MAX_BUFFER_SIZE", S$
19
19
 
20
20
  let maxAddrInPartition = EnvSafe.get(envSafe, "MAX_PARTITION_SIZE", S$RescriptSchema.int, undefined, 5000, undefined, undefined);
21
21
 
22
- let maxBackfillConcurrency = EnvSafe.get(envSafe, "ENVIO_MAX_BACKFILL_CONCURRENCY", S$RescriptSchema.int, undefined, 30, undefined, undefined);
23
-
24
- let maxRealtimeConcurrency = EnvSafe.get(envSafe, "ENVIO_MAX_REALTIME_CONCURRENCY", S$RescriptSchema.int, undefined, 200, undefined, undefined);
25
-
26
22
  let inMemoryObjectsTarget = EnvSafe.get(envSafe, "ENVIO_IN_MEMORY_OBJECTS_TARGET", S$RescriptSchema.int, undefined, 100000, undefined, undefined);
27
23
 
28
24
  let serverPort = EnvSafe.get(envSafe, "ENVIO_INDEXER_PORT", S$RescriptSchema.port(S$RescriptSchema.int, undefined), undefined, EnvSafe.get(envSafe, "METRICS_PORT", S$RescriptSchema.port(S$RescriptSchema.int, undefined), undefined, 9898, undefined, undefined), undefined, undefined);
@@ -224,8 +220,6 @@ export {
224
220
  updateSyncTimeOnRestart,
225
221
  targetBufferSize,
226
222
  maxAddrInPartition,
227
- maxBackfillConcurrency,
228
- maxRealtimeConcurrency,
229
223
  inMemoryObjectsTarget,
230
224
  serverPort,
231
225
  tuiEnvVar,
package/src/Envio.res CHANGED
@@ -48,6 +48,7 @@ type svmTokenBalance = {
48
48
  }
49
49
 
50
50
  type svmTransaction = {
51
+ transactionIndex?: int,
51
52
  signatures: array<string>,
52
53
  feePayer?: SvmTypes.Pubkey.t,
53
54
  success?: bool,
@@ -104,8 +105,8 @@ type svmInstruction = {
104
105
  d8?: string,
105
106
  /** Borsh-decoded params view. See [[svmInstructionParams]]. */
106
107
  params?: svmInstructionParams,
107
- /** Parent transaction. Absent when the per-instruction
108
- `include_transaction` flag is `false`. */
108
+ /** Parent transaction. Carries only the fields selected via
109
+ `field_selection.transaction_fields`; absent when none are selected. */
109
110
  transaction?: svmTransaction,
110
111
  /** Program log entries scoped to this instruction. Absent when the
111
112
  per-instruction `include_logs` flag is `false`. */