envio 3.4.0 → 3.5.0-alpha.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.4.0",
3
+ "version": "3.5.0-alpha.1",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -69,10 +69,10 @@
69
69
  "tsx": "4.21.0"
70
70
  },
71
71
  "optionalDependencies": {
72
- "envio-linux-x64": "3.4.0",
73
- "envio-linux-x64-musl": "3.4.0",
74
- "envio-linux-arm64": "3.4.0",
75
- "envio-darwin-x64": "3.4.0",
76
- "envio-darwin-arm64": "3.4.0"
72
+ "envio-linux-x64": "3.5.0-alpha.1",
73
+ "envio-linux-x64-musl": "3.5.0-alpha.1",
74
+ "envio-linux-arm64": "3.5.0-alpha.1",
75
+ "envio-darwin-x64": "3.5.0-alpha.1",
76
+ "envio-darwin-arm64": "3.5.0-alpha.1"
77
77
  }
78
78
  }
@@ -187,6 +187,12 @@ let makeInternal = (
187
187
  ),
188
188
  ~onBlockRegistrations,
189
189
  ~firstEventBlock,
190
+ // Only EVM sources (HyperSync + RPC) honor client-side address filtering so
191
+ // far, so switching contracts to it is gated to EVM chains.
192
+ ~clientFilterAddressThreshold=switch config.ecosystem.name {
193
+ | Evm => Some(config.clientFilterAddressThreshold)
194
+ | Fuel | Svm => None
195
+ },
190
196
  )
191
197
 
192
198
  let chainReorgCheckpoints = reorgCheckpoints->Array.filterMap(reorgCheckpoint => {
@@ -541,13 +547,7 @@ let frontierProgress = (cs: t) =>
541
547
  // maxTargetBlock set to the most-behind chain's progress mapped onto this
542
548
  // chain, so a chain with budget can't run further ahead than the chain the
543
549
  // whole pool is prioritizing.
544
- let getNextQuery = (
545
- cs: t,
546
- ~chainTargetItems: float,
547
- ~chunkItemsMultiplier=1.,
548
- ~itemsTargetFloor=0,
549
- ~maxTargetBlock=?,
550
- ) => {
550
+ let getNextQuery = (cs: t, ~chainTargetItems: float, ~maxTargetBlock=?) => {
551
551
  let chainTargetBlock = cs->targetBlock(~chainTargetItems)
552
552
  let chainTargetBlock = switch maxTargetBlock {
553
553
  | Some(maxTargetBlock) => Pervasives.min(chainTargetBlock, maxTargetBlock)
@@ -561,10 +561,6 @@ let getNextQuery = (
561
561
  // of being held by an oversized probe.
562
562
  let chainTargetItems = switch cs->effectiveDensity {
563
563
  | Some(density) if density > 0. =>
564
- // No extra headroom here: the budget is reserved in honest itemsEst units,
565
- // and truncation safety lives in the itemsTarget server cap (sized with
566
- // chunkItemsMultiplier at query creation) — multiplying the budget cap too
567
- // would compound the two and hold budget away from other chains.
568
564
  let rangeCost =
569
565
  density *. (chainTargetBlock - cs.fetchState->FetchState.bufferBlockNumber)->Int.toFloat
570
566
  Pervasives.min(chainTargetItems, Math.ceil(rangeCost) +. cs.pendingBudget)
@@ -572,12 +568,7 @@ let getNextQuery = (
572
568
  // budget to the cold-chain cap, so it's used as-is.
573
569
  | _ => chainTargetItems
574
570
  }
575
- cs.fetchState->FetchState.getNextQuery(
576
- ~chainTargetBlock,
577
- ~chainTargetItems,
578
- ~chunkItemsMultiplier,
579
- ~itemsTargetFloor,
580
- )
571
+ cs.fetchState->FetchState.getNextQuery(~chainTargetBlock, ~chainTargetItems)
581
572
  }
582
573
 
583
574
  // Run a fetch tick for this chain against its sources, feeding the owned fetch
@@ -105,7 +105,18 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
105
105
  });
106
106
  let contractConfigs = IndexingAddresses.makeContractConfigs(onEventRegistrations);
107
107
  let indexingAddressIndex = IndexingAddresses.make(contractConfigs, indexingAddresses);
108
- let fetchState = FetchState.make(startBlock, endBlock, onEventRegistrations, contractConfigs, indexingAddresses, config.maxAddrInPartition, chainConfig.id, (config.batchSize << 1), knownHeight, progressBlockNumber, match.onBlockRegistrations, Primitive_int.max(!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth, chainConfig.blockLag), Primitive_option.some(firstEventBlock));
108
+ let match$1 = config.ecosystem.name;
109
+ let tmp;
110
+ switch (match$1) {
111
+ case "evm" :
112
+ tmp = config.clientFilterAddressThreshold;
113
+ break;
114
+ case "fuel" :
115
+ case "svm" :
116
+ tmp = undefined;
117
+ break;
118
+ }
119
+ let fetchState = FetchState.make(startBlock, endBlock, onEventRegistrations, contractConfigs, indexingAddresses, config.maxAddrInPartition, chainConfig.id, (config.batchSize << 1), knownHeight, progressBlockNumber, match.onBlockRegistrations, Primitive_int.max(!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth, chainConfig.blockLag), Primitive_option.some(firstEventBlock), Primitive_option.some(tmp));
109
120
  let chainReorgCheckpoints = Stdlib_Array.filterMap(reorgCheckpoints, reorgCheckpoint => {
110
121
  if (reorgCheckpoint.chain_id === chainConfig.id) {
111
122
  return reorgCheckpoint;
@@ -348,9 +359,7 @@ function frontierProgress(cs) {
348
359
  }
349
360
  }
350
361
 
351
- function getNextQuery(cs, chainTargetItems, chunkItemsMultiplierOpt, itemsTargetFloorOpt, maxTargetBlock) {
352
- let chunkItemsMultiplier = chunkItemsMultiplierOpt !== undefined ? chunkItemsMultiplierOpt : 1;
353
- let itemsTargetFloor = itemsTargetFloorOpt !== undefined ? itemsTargetFloorOpt : 0;
362
+ function getNextQuery(cs, chainTargetItems, maxTargetBlock) {
354
363
  let chainTargetBlock = targetBlock(cs, chainTargetItems);
355
364
  let chainTargetBlock$1 = maxTargetBlock !== undefined ? Primitive_int.min(chainTargetBlock, maxTargetBlock) : chainTargetBlock;
356
365
  let density = effectiveDensity(cs);
@@ -361,7 +370,7 @@ function getNextQuery(cs, chainTargetItems, chunkItemsMultiplierOpt, itemsTarget
361
370
  } else {
362
371
  chainTargetItems$1 = chainTargetItems;
363
372
  }
364
- return FetchState.getNextQuery(cs.fetchState, chainTargetBlock$1, chainTargetItems$1, chunkItemsMultiplier, itemsTargetFloor);
373
+ return FetchState.getNextQuery(cs.fetchState, chainTargetBlock$1, chainTargetItems$1);
365
374
  }
366
375
 
367
376
  function dispatch(cs, executeQuery, waitForNewBlock, onNewBlock, action, stateId) {
@@ -586,7 +595,8 @@ function setEndBlockToFirstEvent(cs, blockNumber) {
586
595
  maxOnBlockBufferSize: init.maxOnBlockBufferSize,
587
596
  onBlockRegistrations: init.onBlockRegistrations,
588
597
  knownHeight: init.knownHeight,
589
- firstEventBlock: init.firstEventBlock
598
+ firstEventBlock: init.firstEventBlock,
599
+ clientFilterAddressThreshold: init.clientFilterAddressThreshold
590
600
  };
591
601
  return;
592
602
  }
@@ -604,7 +614,8 @@ function setEndBlockToFirstEvent(cs, blockNumber) {
604
614
  maxOnBlockBufferSize: init$1.maxOnBlockBufferSize,
605
615
  onBlockRegistrations: init$1.onBlockRegistrations,
606
616
  knownHeight: init$1.knownHeight,
607
- firstEventBlock: init$1.firstEventBlock
617
+ firstEventBlock: init$1.firstEventBlock,
618
+ clientFilterAddressThreshold: init$1.clientFilterAddressThreshold
608
619
  };
609
620
  }
610
621
 
@@ -712,7 +723,8 @@ function applyBatchProgress(cs, batch, blockTimestampName) {
712
723
  maxOnBlockBufferSize: init.maxOnBlockBufferSize,
713
724
  onBlockRegistrations: init.onBlockRegistrations,
714
725
  knownHeight: init.knownHeight,
715
- firstEventBlock: firstEventBlock
726
+ firstEventBlock: firstEventBlock,
727
+ clientFilterAddressThreshold: init.clientFilterAddressThreshold
716
728
  };
717
729
  }
718
730
  }
@@ -80,13 +80,7 @@ let hasReadyItem: t => bool
80
80
  let targetBlock: (t, ~chainTargetItems: float) => int
81
81
  let blockAtProgress: (t, ~progress: float) => int
82
82
  let frontierProgress: t => float
83
- let getNextQuery: (
84
- t,
85
- ~chainTargetItems: float,
86
- ~chunkItemsMultiplier: float=?,
87
- ~itemsTargetFloor: int=?,
88
- ~maxTargetBlock: int=?,
89
- ) => FetchState.nextQuery
83
+ let getNextQuery: (t, ~chainTargetItems: float, ~maxTargetBlock: int=?) => FetchState.nextQuery
90
84
  let dispatch: (
91
85
  t,
92
86
  ~executeQuery: FetchState.query => promise<unit>,
package/src/Config.res CHANGED
@@ -78,6 +78,9 @@ type t = {
78
78
  ecosystem: Ecosystem.t,
79
79
  enableRawEvents: bool,
80
80
  maxAddrInPartition: int,
81
+ // Per-contract registered-address count past which a dynamic contract switches
82
+ // to client-side filtering. Overridable in tests.
83
+ clientFilterAddressThreshold: int,
81
84
  batchSize: int,
82
85
  // Slack (in blocks) below the lagged head within which a chain still counts as
83
86
  // ready to enter the reorg threshold, absorbing head advances between catch-up
@@ -995,10 +998,14 @@ let fromPublic = (publicConfigJson: JSON.t) => {
995
998
 
996
999
  let allEntities = userEntities->Array.concat([EnvioAddresses.entityConfig])
997
1000
 
1001
+ // Keyed by the capitalized entity name to match the handler-context
1002
+ // accessor (`context.Pool_snapshots`) the generated types expose, while
1003
+ // entityConfig.name stays the original schema name used for the physical
1004
+ // Postgres/ClickHouse tables.
998
1005
  let userEntitiesByName =
999
1006
  userEntities
1000
1007
  ->Array.map(entityConfig => {
1001
- (entityConfig.name, entityConfig)
1008
+ (entityConfig.name->Utils.String.capitalize, entityConfig)
1002
1009
  })
1003
1010
  ->Dict.fromArray
1004
1011
 
@@ -1029,6 +1036,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1029
1036
  enableRawEvents: publicConfig["rawEvents"]->Option.getOr(false),
1030
1037
  ecosystem,
1031
1038
  maxAddrInPartition,
1039
+ clientFilterAddressThreshold: Env.clientFilterAddressThreshold,
1032
1040
  batchSize: publicConfig["fullBatchSize"]->Option.getOr(5000),
1033
1041
  reorgThresholdReadyTolerance: 100,
1034
1042
  lowercaseAddresses,
@@ -704,7 +704,7 @@ function fromPublic(publicConfigJson) {
704
704
  let userEntities = parseEntitiesFromJson(Stdlib_Option.getOr(publicConfig.entities, []), enumConfigsByName, globalStorage);
705
705
  let allEntities = userEntities.concat([entityConfig]);
706
706
  let userEntitiesByName = Object.fromEntries(userEntities.map(entityConfig => [
707
- entityConfig.name,
707
+ Utils.$$String.capitalize(entityConfig.name),
708
708
  entityConfig
709
709
  ]));
710
710
  let contractHandlers = publicContractsConfig !== undefined ? Object.entries(publicContractsConfig).map(param => ({
@@ -724,6 +724,7 @@ function fromPublic(publicConfigJson) {
724
724
  ecosystem: ecosystem,
725
725
  enableRawEvents: Stdlib_Option.getOr(publicConfig.rawEvents, false),
726
726
  maxAddrInPartition: Env.maxAddrInPartition,
727
+ clientFilterAddressThreshold: Env.clientFilterAddressThreshold,
727
728
  batchSize: Stdlib_Option.getOr(publicConfig.fullBatchSize, 5000),
728
729
  reorgThresholdReadyTolerance: 100,
729
730
  lowercaseAddresses: lowercaseAddresses,
@@ -249,19 +249,6 @@ let checkAndFetch = async (
249
249
  // while it takes its first measurements. Its probe is one admission unit.
250
250
  let coldChainBudget = minimumAdmissionBudget
251
251
 
252
- // Chunk reservations get headroom over the density estimate so a
253
- // denser-than-expected range doesn't truncate at the server cap; realtime
254
- // gets more since a forced catch-up query there costs a head-poll roundtrip.
255
- let chunkItemsMultiplier = crossChainState.isRealtime ? 3. : 1.5
256
-
257
- // Server-cap floor for bounded queries: their block range is already the
258
- // hard bound on the response, so a low density estimate shrinking the cap
259
- // below this only buys self-truncated responses. Splitting the target pool
260
- // across a chain's concurrency slots keeps the worst case — every in-flight
261
- // bounded query returning a full floored response at once — at ~one buffer
262
- // target.
263
- let itemsTargetFloor = crossChainState.targetBufferSize / FetchState.maxChainConcurrency
264
-
265
252
  let prioritizedChainStates = crossChainState->priorityOrder
266
253
 
267
254
  // Alignment anchor: the first known-height chain in priority order — which,
@@ -308,12 +295,7 @@ let checkAndFetch = async (
308
295
  Some(cs->ChainState.blockAtProgress(~progress=progress +. 0.1))
309
296
  | _ => None
310
297
  }
311
- switch cs->ChainState.getNextQuery(
312
- ~chainTargetItems,
313
- ~chunkItemsMultiplier,
314
- ~itemsTargetFloor,
315
- ~maxTargetBlock?,
316
- ) {
298
+ switch cs->ChainState.getNextQuery(~chainTargetItems, ~maxTargetBlock?) {
317
299
  | WaitingForNewBlock as action => actionByChain->Utils.Dict.setByInt(chainId, action)
318
300
  | NothingToQuery =>
319
301
  // A chain below its head can emit no query when its budget went to
@@ -333,7 +315,7 @@ let checkAndFetch = async (
333
315
  {
334
316
  "fromBlock": query.fromBlock,
335
317
  "targetBlock": query.toBlock,
336
- "targetEvents": query.itemsTarget,
318
+ "targetEvents": query.itemsEst,
337
319
  },
338
320
  )
339
321
  )
@@ -6,9 +6,7 @@ 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 Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
11
- import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
12
10
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
13
11
  import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
14
12
  import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
@@ -147,8 +145,6 @@ async function checkAndFetch(crossChainState, dispatchChain) {
147
145
  contents: Primitive_float.max(0, targetBudget - totalReadyCount(crossChainState) - totalReservedSize(crossChainState))
148
146
  };
149
147
  let minimumAdmissionBudget = targetBudget * 0.1;
150
- let chunkItemsMultiplier = crossChainState.isRealtime ? 3 : 1.5;
151
- let itemsTargetFloor = Primitive_int.div(crossChainState.targetBufferSize, FetchState.maxChainConcurrency);
152
148
  let prioritizedChainStates = priorityOrder(crossChainState);
153
149
  let alignment = crossChainState.isRealtime ? undefined : Stdlib_Option.map(prioritizedChainStates.find(cs => ChainState.knownHeight(cs) !== 0), cs => [
154
150
  ChainState.chainConfig(cs).id,
@@ -170,7 +166,7 @@ async function checkAndFetch(crossChainState, dispatchChain) {
170
166
  isCold ? Primitive_float.min(remaining.contents, minimumAdmissionBudget) : remaining.contents
171
167
  ) + ChainState.pendingBudget(cs);
172
168
  let maxTargetBlock = alignment !== undefined && alignment[0] !== chainId ? ChainState.blockAtProgress(cs, alignment[1] + 0.1) : undefined;
173
- let action = ChainState.getNextQuery(cs, chainTargetItems, chunkItemsMultiplier, itemsTargetFloor, maxTargetBlock);
169
+ let action = ChainState.getNextQuery(cs, chainTargetItems, maxTargetBlock);
174
170
  if (typeof action !== "object") {
175
171
  if (action === "WaitingForNewBlock") {
176
172
  actionByChain[chainId] = action;
@@ -186,7 +182,7 @@ async function checkAndFetch(crossChainState, dispatchChain) {
186
182
  partitions[query.partitionId] = {
187
183
  fromBlock: query.fromBlock,
188
184
  targetBlock: query.toBlock,
189
- targetEvents: query.itemsTarget
185
+ targetEvents: query.itemsEst
190
186
  };
191
187
  });
192
188
  Logging.trace({
package/src/Env.res CHANGED
@@ -9,6 +9,22 @@ let updateSyncTimeOnRestart =
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
11
 
12
+ // Most parallel in-flight queries a single chain may have at once, across all
13
+ // its partitions (consumed as FetchState.maxChainConcurrency).
14
+ let maxChainConcurrency = 100
15
+
16
+ // Switch a single contract to client-side address filtering
17
+ // once its registered address count crosses this threshold. Keeping addresses
18
+ // server-side spreads the contract across ceil(count / maxAddrInPartition)
19
+ // partitions, each holding an in-flight query slot; capping a contract at half
20
+ // the chain's concurrency budget stops one busy contract from monopolising them.
21
+ let clientFilterAddressThreshold =
22
+ envSafe->EnvSafe.get(
23
+ "ENVIO_CLIENT_FILTER_ADDRESS_THRESHOLD",
24
+ S.int,
25
+ ~fallback=maxAddrInPartition * maxChainConcurrency / 2,
26
+ )
27
+
12
28
  // Target number of in-memory objects (uncommitted entity/effect changes plus
13
29
  // unwritten batch items) the store holds before processing waits for the write
14
30
  // cycle to catch up.
package/src/Env.res.mjs CHANGED
@@ -19,6 +19,8 @@ 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 clientFilterAddressThreshold = EnvSafe.get(envSafe, "ENVIO_CLIENT_FILTER_ADDRESS_THRESHOLD", S$RescriptSchema.int, undefined, (maxAddrInPartition * 100 | 0) / 2 | 0, undefined, undefined);
23
+
22
24
  let inMemoryObjectsTarget = EnvSafe.get(envSafe, "ENVIO_IN_MEMORY_OBJECTS_TARGET", S$RescriptSchema.int, undefined, 100000, undefined, undefined);
23
25
 
24
26
  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);
@@ -216,10 +218,14 @@ let ThrottleWrites = {
216
218
 
217
219
  EnvSafe.close(envSafe);
218
220
 
221
+ let maxChainConcurrency = 100;
222
+
219
223
  export {
220
224
  updateSyncTimeOnRestart,
221
225
  targetBufferSize,
222
226
  maxAddrInPartition,
227
+ maxChainConcurrency,
228
+ clientFilterAddressThreshold,
223
229
  inMemoryObjectsTarget,
224
230
  serverPort,
225
231
  tuiEnvVar,