envio 3.1.2 → 3.2.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.
Files changed (134) hide show
  1. package/evm.schema.json +83 -11
  2. package/fuel.schema.json +83 -11
  3. package/index.d.ts +184 -3
  4. package/package.json +6 -6
  5. package/src/Batch.res +2 -2
  6. package/src/ChainFetcher.res +27 -3
  7. package/src/ChainFetcher.res.mjs +17 -3
  8. package/src/ChainManager.res +163 -0
  9. package/src/ChainManager.res.mjs +136 -0
  10. package/src/Config.res +213 -30
  11. package/src/Config.res.mjs +102 -41
  12. package/src/Core.res +16 -10
  13. package/src/Ecosystem.res +0 -3
  14. package/src/Env.res +2 -2
  15. package/src/Env.res.mjs +2 -2
  16. package/src/Envio.res +101 -2
  17. package/src/Envio.res.mjs +2 -3
  18. package/src/EventConfigBuilder.res +87 -0
  19. package/src/EventConfigBuilder.res.mjs +53 -0
  20. package/src/EventUtils.res +2 -2
  21. package/src/FetchState.res +63 -67
  22. package/src/FetchState.res.mjs +44 -42
  23. package/src/GlobalState.res +219 -363
  24. package/src/GlobalState.res.mjs +314 -491
  25. package/src/GlobalStateManager.res +49 -59
  26. package/src/GlobalStateManager.res.mjs +5 -4
  27. package/src/GlobalStateManager.resi +1 -1
  28. package/src/HandlerLoader.res +18 -2
  29. package/src/HandlerLoader.res.mjs +16 -34
  30. package/src/HandlerRegister.res +9 -9
  31. package/src/HandlerRegister.res.mjs +9 -9
  32. package/src/Hasura.res +102 -32
  33. package/src/Hasura.res.mjs +88 -34
  34. package/src/InMemoryStore.res +10 -1
  35. package/src/InMemoryStore.res.mjs +4 -1
  36. package/src/InMemoryTable.res +83 -136
  37. package/src/InMemoryTable.res.mjs +57 -86
  38. package/src/Internal.res +70 -5
  39. package/src/Internal.res.mjs +2 -8
  40. package/src/LazyLoader.res +2 -2
  41. package/src/LazyLoader.res.mjs +3 -3
  42. package/src/LoadLayer.res +47 -60
  43. package/src/LoadLayer.res.mjs +28 -50
  44. package/src/LoadLayer.resi +2 -5
  45. package/src/LogSelection.res +90 -21
  46. package/src/LogSelection.res.mjs +72 -21
  47. package/src/Logging.res +1 -1
  48. package/src/Main.res +61 -2
  49. package/src/Main.res.mjs +37 -1
  50. package/src/Persistence.res +3 -16
  51. package/src/PgStorage.res +125 -114
  52. package/src/PgStorage.res.mjs +112 -95
  53. package/src/Ports.res +5 -0
  54. package/src/Ports.res.mjs +9 -0
  55. package/src/Prometheus.res +3 -3
  56. package/src/Prometheus.res.mjs +4 -4
  57. package/src/ReorgDetection.res +4 -4
  58. package/src/ReorgDetection.res.mjs +4 -5
  59. package/src/SafeCheckpointTracking.res +16 -16
  60. package/src/SafeCheckpointTracking.res.mjs +2 -2
  61. package/src/SimulateItems.res +10 -14
  62. package/src/SimulateItems.res.mjs +5 -2
  63. package/src/Sink.res +1 -1
  64. package/src/Sink.res.mjs +1 -2
  65. package/src/SvmTypes.res +9 -0
  66. package/src/SvmTypes.res.mjs +14 -0
  67. package/src/TestIndexer.res +35 -68
  68. package/src/TestIndexer.res.mjs +17 -48
  69. package/src/TestIndexerProxyStorage.res +23 -23
  70. package/src/TestIndexerProxyStorage.res.mjs +12 -15
  71. package/src/Throttler.res +2 -2
  72. package/src/Time.res +2 -2
  73. package/src/Time.res.mjs +2 -2
  74. package/src/UserContext.res +19 -118
  75. package/src/UserContext.res.mjs +10 -66
  76. package/src/Utils.res +15 -15
  77. package/src/Utils.res.mjs +7 -8
  78. package/src/adapters/MarkBatchProcessedAdapter.res +5 -0
  79. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +14 -0
  80. package/src/bindings/BigDecimal.res +1 -1
  81. package/src/bindings/BigDecimal.res.mjs +2 -2
  82. package/src/bindings/ClickHouse.res +8 -6
  83. package/src/bindings/ClickHouse.res.mjs +5 -5
  84. package/src/bindings/Hrtime.res +1 -1
  85. package/src/bindings/Pino.res +2 -2
  86. package/src/bindings/Pino.res.mjs +3 -4
  87. package/src/db/EntityFilter.res +410 -0
  88. package/src/db/EntityFilter.res.mjs +424 -0
  89. package/src/db/EntityHistory.res +1 -1
  90. package/src/db/EntityHistory.res.mjs +1 -1
  91. package/src/db/InternalTable.res +10 -10
  92. package/src/db/InternalTable.res.mjs +41 -45
  93. package/src/db/Schema.res +2 -2
  94. package/src/db/Schema.res.mjs +3 -3
  95. package/src/db/Table.res +106 -22
  96. package/src/db/Table.res.mjs +84 -35
  97. package/src/sources/EventRouter.res +67 -2
  98. package/src/sources/EventRouter.res.mjs +45 -3
  99. package/src/sources/Evm.res +0 -7
  100. package/src/sources/Evm.res.mjs +0 -15
  101. package/src/sources/EvmChain.res +1 -1
  102. package/src/sources/EvmChain.res.mjs +1 -2
  103. package/src/sources/EvmRpcClient.res +42 -0
  104. package/src/sources/EvmRpcClient.res.mjs +64 -0
  105. package/src/sources/Fuel.res +0 -7
  106. package/src/sources/Fuel.res.mjs +0 -15
  107. package/src/sources/HyperFuelSource.res +5 -4
  108. package/src/sources/HyperFuelSource.res.mjs +2 -2
  109. package/src/sources/HyperSyncClient.res +9 -5
  110. package/src/sources/HyperSyncClient.res.mjs +2 -2
  111. package/src/sources/HyperSyncHeightStream.res +2 -2
  112. package/src/sources/HyperSyncHeightStream.res.mjs +2 -2
  113. package/src/sources/HyperSyncSource.res +12 -11
  114. package/src/sources/HyperSyncSource.res.mjs +6 -6
  115. package/src/sources/Rpc.res +1 -5
  116. package/src/sources/Rpc.res.mjs +1 -9
  117. package/src/sources/RpcSource.res +57 -21
  118. package/src/sources/RpcSource.res.mjs +47 -20
  119. package/src/sources/RpcWebSocketHeightStream.res +1 -1
  120. package/src/sources/SourceManager.res +3 -2
  121. package/src/sources/SourceManager.res.mjs +1 -1
  122. package/src/sources/Svm.res +3 -10
  123. package/src/sources/Svm.res.mjs +4 -18
  124. package/src/sources/SvmHyperSyncClient.res +265 -0
  125. package/src/sources/SvmHyperSyncClient.res.mjs +28 -0
  126. package/src/sources/SvmHyperSyncSource.res +638 -0
  127. package/src/sources/SvmHyperSyncSource.res.mjs +557 -0
  128. package/src/tui/Tui.res +9 -2
  129. package/src/tui/Tui.res.mjs +18 -3
  130. package/src/tui/components/BufferedProgressBar.res +2 -2
  131. package/src/tui/components/TuiData.res +3 -0
  132. package/svm.schema.json +523 -14
  133. package/src/TableIndices.res +0 -115
  134. package/src/TableIndices.res.mjs +0 -144
@@ -309,6 +309,39 @@ let resolveFieldSelection = (
309
309
  (selectedBlockFields, selectedTransactionFields)
310
310
  }
311
311
 
312
+ // ============== Client-side address filter ==============
313
+
314
+ let compileAddressFilter: string => (
315
+ Internal.event,
316
+ int,
317
+ dict<Internal.indexingContract>,
318
+ ) => bool = %raw(`function (body) {
319
+ return new Function("event", "blockNumber", "indexingAddresses", body);
320
+ }`)
321
+
322
+ // Body of the client-side address filter for a DNF of address-filtered param
323
+ // names (OR of AND-groups): keep the event only if some group's params are all
324
+ // registered at or before the log's block. The DNF is fixed here, so it's
325
+ // unrolled into one boolean expression — no per-event closure, loop, or array.
326
+ // `None` when there's no address-param filter. Exposed for snapshotting.
327
+ let buildAddressFilterBody = (groups: array<array<string>>): option<string> => {
328
+ switch groups {
329
+ | [] => None
330
+ | _ =>
331
+ let leaf = name =>
332
+ `(ic = indexingAddresses[p[${JSON.stringify(
333
+ JSON.String(name),
334
+ )}]]) !== undefined && ic.effectiveStartBlock <= blockNumber`
335
+ let groupExprs =
336
+ groups->Array.map(group => "(" ++ group->Array.map(leaf)->Array.join(" && ") ++ ")")
337
+ Some("var p = event.params, ic; return " ++ groupExprs->Array.join(" || ") ++ ";")
338
+ }
339
+ }
340
+
341
+ let buildAddressFilter = (groups: array<array<string>>): option<
342
+ (Internal.event, int, dict<Internal.indexingContract>) => bool,
343
+ > => buildAddressFilterBody(groups)->Option.map(compileAddressFilter)
344
+
312
345
  // ============== Build complete EVM event config ==============
313
346
 
314
347
  let buildEvmEventConfig = (
@@ -334,6 +367,7 @@ let buildEvmEventConfig = (
334
367
  let {
335
368
  getEventFiltersOrThrow,
336
369
  filterByAddresses,
370
+ addressFilterParamGroups,
337
371
  startBlock: whereStartBlock,
338
372
  } = LogSelection.parseEventFiltersOrThrow(
339
373
  ~eventFilters,
@@ -374,6 +408,7 @@ let buildEvmEventConfig = (
374
408
  simulateParamsSchema: buildSimulateParamsSchema(params),
375
409
  getEventFiltersOrThrow,
376
410
  filterByAddresses,
411
+ clientAddressFilter: ?buildAddressFilter(addressFilterParamGroups),
377
412
  dependsOnAddresses: !isWildcard || filterByAddresses,
378
413
  startBlock: resolvedStartBlock,
379
414
  selectedBlockFields,
@@ -386,6 +421,58 @@ let buildEvmEventConfig = (
386
421
 
387
422
  // ============== Build Fuel event config ==============
388
423
 
424
+ let buildSvmInstructionEventConfig = (
425
+ ~contractName: string,
426
+ ~instructionName: string,
427
+ ~programId: SvmTypes.Pubkey.t,
428
+ ~discriminator: option<string>,
429
+ ~discriminatorByteLen: int,
430
+ ~includeTransaction: bool,
431
+ ~includeLogs: bool,
432
+ ~includeTokenBalances: bool,
433
+ ~accountFilters: array<Internal.svmAccountFilterGroup>,
434
+ ~isInner: option<bool>,
435
+ ~isWildcard: bool,
436
+ ~handler: option<Internal.handler>,
437
+ ~contractRegister: option<Internal.contractRegister>,
438
+ ~accounts: array<string>=[],
439
+ ~args: JSON.t=JSON.Null,
440
+ ~definedTypes: JSON.t=JSON.Null,
441
+ ~startBlock: option<int>=?,
442
+ ): Internal.svmInstructionEventConfig => {
443
+ let paramsSchema =
444
+ S.json(~validate=false)
445
+ ->Utils.Schema.coerceToJsonPgType
446
+ ->(Utils.magic: S.t<JSON.t> => S.t<Internal.eventParams>)
447
+ {
448
+ id: switch discriminator {
449
+ | Some(d) => d
450
+ | None => "none"
451
+ },
452
+ name: instructionName,
453
+ contractName,
454
+ isWildcard,
455
+ handler,
456
+ contractRegister,
457
+ paramsRawEventSchema: paramsSchema,
458
+ simulateParamsSchema: paramsSchema,
459
+ filterByAddresses: false,
460
+ dependsOnAddresses: !isWildcard,
461
+ startBlock,
462
+ programId,
463
+ discriminator,
464
+ discriminatorByteLen,
465
+ includeTransaction,
466
+ includeLogs,
467
+ includeTokenBalances,
468
+ accountFilters,
469
+ isInner,
470
+ accounts,
471
+ args,
472
+ definedTypes,
473
+ }
474
+ }
475
+
389
476
  let buildFuelEventConfig = (
390
477
  ~contractName: string,
391
478
  ~eventName: string,
@@ -251,6 +251,23 @@ function resolveFieldSelection(blockFields, transactionFields, globalBlockFields
251
251
  ];
252
252
  }
253
253
 
254
+ let compileAddressFilter = (function (body) {
255
+ return new Function("event", "blockNumber", "indexingAddresses", body);
256
+ });
257
+
258
+ function buildAddressFilterBody(groups) {
259
+ if (groups.length === 0) {
260
+ return;
261
+ }
262
+ let leaf = name => `(ic = indexingAddresses[p[` + JSON.stringify(name) + `]]) !== undefined && ic.effectiveStartBlock <= blockNumber`;
263
+ let groupExprs = groups.map(group => "(" + group.map(leaf).join(" && ") + ")");
264
+ return "var p = event.params, ic; return " + groupExprs.join(" || ") + ";";
265
+ }
266
+
267
+ function buildAddressFilter(groups) {
268
+ return Stdlib_Option.map(buildAddressFilterBody(groups), compileAddressFilter);
269
+ }
270
+
254
271
  function buildEvmEventConfig(contractName, eventName, sighash, params, isWildcard, handler, contractRegister, eventFilters, probeChainId, onEventBlockFilterSchema, blockFields, transactionFields, startBlock, globalBlockFieldsSetOpt, globalTransactionFieldsSetOpt) {
255
272
  let globalBlockFieldsSet = globalBlockFieldsSetOpt !== undefined ? Primitive_option.valFromOption(globalBlockFieldsSetOpt) : new Set();
256
273
  let globalTransactionFieldsSet = globalTransactionFieldsSetOpt !== undefined ? Primitive_option.valFromOption(globalTransactionFieldsSetOpt) : new Set();
@@ -274,6 +291,7 @@ function buildEvmEventConfig(contractName, eventName, sighash, params, isWildcar
274
291
  isWildcard: isWildcard,
275
292
  filterByAddresses: filterByAddresses,
276
293
  dependsOnAddresses: !isWildcard || filterByAddresses,
294
+ clientAddressFilter: Stdlib_Option.map(buildAddressFilterBody(match.addressFilterParamGroups), compileAddressFilter),
277
295
  handler: handler,
278
296
  contractRegister: contractRegister,
279
297
  paramsRawEventSchema: buildParamsSchema(params),
@@ -288,6 +306,37 @@ function buildEvmEventConfig(contractName, eventName, sighash, params, isWildcar
288
306
  };
289
307
  }
290
308
 
309
+ function buildSvmInstructionEventConfig(contractName, instructionName, programId, discriminator, discriminatorByteLen, includeTransaction, includeLogs, includeTokenBalances, accountFilters, isInner, isWildcard, handler, contractRegister, accountsOpt, argsOpt, definedTypesOpt, startBlock) {
310
+ let accounts = accountsOpt !== undefined ? accountsOpt : [];
311
+ let args = argsOpt !== undefined ? argsOpt : null;
312
+ let definedTypes = definedTypesOpt !== undefined ? definedTypesOpt : null;
313
+ let paramsSchema = Utils.Schema.coerceToJsonPgType(S$RescriptSchema.json(false));
314
+ return {
315
+ id: discriminator !== undefined ? discriminator : "none",
316
+ name: instructionName,
317
+ contractName: contractName,
318
+ isWildcard: isWildcard,
319
+ filterByAddresses: false,
320
+ dependsOnAddresses: !isWildcard,
321
+ handler: handler,
322
+ contractRegister: contractRegister,
323
+ paramsRawEventSchema: paramsSchema,
324
+ simulateParamsSchema: paramsSchema,
325
+ startBlock: startBlock,
326
+ programId: programId,
327
+ discriminator: discriminator,
328
+ discriminatorByteLen: discriminatorByteLen,
329
+ includeTransaction: includeTransaction,
330
+ includeLogs: includeLogs,
331
+ includeTokenBalances: includeTokenBalances,
332
+ accountFilters: accountFilters,
333
+ isInner: isInner,
334
+ accounts: accounts,
335
+ args: args,
336
+ definedTypes: definedTypes
337
+ };
338
+ }
339
+
291
340
  function buildFuelEventConfig(contractName, eventName, kind, sighash, rawAbi, isWildcard, handler, contractRegister, startBlock) {
292
341
  let fuelKind;
293
342
  switch (kind) {
@@ -362,7 +411,11 @@ export {
362
411
  buildTopicGetter,
363
412
  alwaysIncludedBlockFields,
364
413
  resolveFieldSelection,
414
+ compileAddressFilter,
415
+ buildAddressFilterBody,
416
+ buildAddressFilter,
365
417
  buildEvmEventConfig,
418
+ buildSvmInstructionEventConfig,
366
419
  buildFuelEventConfig,
367
420
  }
368
421
  /* paramMetaSchema Not a pure module */
@@ -45,8 +45,8 @@ let packEventIndex = (~blockNumber, ~logIndex) => {
45
45
  // let logIndexMask = 65535->BigInt.fromInt
46
46
  // let logIndex = packedEventIndex->BigInt.bitwiseAnd(logIndexMask)
47
47
  // {
48
- // blockNumber: blockNumber->BigInt.toString->Belt.Int.fromString->Belt.Option.getUnsafe,
49
- // logIndex: logIndex->BigInt.toString->Belt.Int.fromString->Belt.Option.getUnsafe,
48
+ // blockNumber: blockNumber->BigInt.toString->Int.fromString->Option.getUnsafe,
49
+ // logIndex: logIndex->BigInt.toString->Int.fromString->Option.getUnsafe,
50
50
  // }
51
51
  // }
52
52
 
@@ -1,9 +1,6 @@
1
- type contractConfig = {filterByAddresses: bool, startBlock: option<int>}
1
+ type contractConfig = {startBlock: option<int>}
2
2
 
3
- type indexingAddress = {
4
- ...Internal.indexingAddress,
5
- effectiveStartBlock: int,
6
- }
3
+ type indexingAddress = Internal.indexingContract
7
4
 
8
5
  let deriveEffectiveStartBlock = (~registrationBlock: int, ~contractStartBlock: option<int>) => {
9
6
  Pervasives.max(Pervasives.max(registrationBlock, 0), contractStartBlock->Option.getOr(0))
@@ -336,7 +333,7 @@ module OptimizedPartitions = {
336
333
  let _ = newPartitions->Array.sort(ascSortFn)
337
334
 
338
335
  let partitionsCount = newPartitions->Array.length
339
- let idsInAscOrder = Belt.Array.makeUninitializedUnsafe(partitionsCount)
336
+ let idsInAscOrder = Utils.Array.jsArrayCreate(partitionsCount)
340
337
  let entities = Dict.make()
341
338
  for idx in 0 to partitionsCount - 1 {
342
339
  let p = newPartitions->Array.getUnsafe(idx)
@@ -639,7 +636,7 @@ let updateInternal = (
639
636
  let maxBlockNumber = switch switch mutItemsRef.contents {
640
637
  | Some(mutItems) => mutItems
641
638
  | None => fetchState.buffer
642
- }->Belt.Array.get(fetchState.targetBufferSize - 1) {
639
+ }->Array.get(fetchState.targetBufferSize - 1) {
643
640
  | Some(item) => item->Internal.getItemBlockNumber
644
641
  | None =>
645
642
  switch optimizedPartitions->OptimizedPartitions.getLatestFullyFetchedBlock {
@@ -756,7 +753,6 @@ Returns OptimizedPartitions.t directly.
756
753
  */
757
754
  let createPartitionsFromIndexingAddresses = (
758
755
  ~registeringContractsByContract: dict<dict<indexingAddress>>,
759
- ~contractConfigs: dict<contractConfig>,
760
756
  ~dynamicContracts: Utils.Set.t<string>,
761
757
  ~normalSelection: selection,
762
758
  ~maxAddrInPartition: int,
@@ -778,9 +774,6 @@ OptimizedPartitions.t => {
778
774
  let addresses =
779
775
  registeringContracts->Dict.keysToArray->(Utils.magic: array<string> => array<Address.t>)
780
776
 
781
- // Can unsafely get it, because we already filtered out the contracts
782
- // that don't have any events to fetch
783
- let contractConfig = contractConfigs->Dict.getUnsafe(contractName)
784
777
  let isDynamic = dynamicContracts->Utils.Set.has(contractName)
785
778
  let partitions = isDynamic ? dynamicPartitions : nonDynamicPartitions
786
779
 
@@ -802,30 +795,23 @@ OptimizedPartitions.t => {
802
795
  let maybeNextStartBlockKey =
803
796
  ascKeys->Array.getUnsafe(idx + 1)->(Utils.magic: string => option<string>)
804
797
 
805
- // For this case we can't filter out events earlier than contract registration
806
- // on the client side, so we need to keep the old logic of creating
807
- // a partition for every block range, so there are no irrelevant events
808
- let shouldAllocateNewPartition = if contractConfig.filterByAddresses {
809
- true
810
- } else {
811
- switch maybeNextStartBlockKey {
812
- | None => true
813
- | Some(nextStartBlockKey) => {
814
- let nextStartBlock = nextStartBlockKey->Int.fromString->Option.getUnsafe
815
- let shouldJoinCurrentStartBlock =
816
- nextStartBlock - startBlockRef.contents < OptimizedPartitions.tooFarBlockRange
817
-
818
- // If dynamic contract registration are close to eachother
819
- // and it's possible to use dc.effectiveStartBlock to filter out events on client side
820
- // then we can optimize the number of partitions,
821
- // by putting dcs with different startBlocks in the same partition
822
- if shouldJoinCurrentStartBlock {
823
- addressesRef :=
824
- addressesRef.contents->Array.concat(byStartBlock->Dict.getUnsafe(nextStartBlockKey))
825
- false
826
- } else {
827
- true
828
- }
798
+ let shouldAllocateNewPartition = switch maybeNextStartBlockKey {
799
+ | None => true
800
+ | Some(nextStartBlockKey) => {
801
+ let nextStartBlock = nextStartBlockKey->Int.fromString->Option.getUnsafe
802
+ let shouldJoinCurrentStartBlock =
803
+ nextStartBlock - startBlockRef.contents < OptimizedPartitions.tooFarBlockRange
804
+
805
+ // Addresses with different start blocks within range share a partition;
806
+ // events before each address's effectiveStartBlock are dropped on the
807
+ // client side (EventRouter for the srcAddress, the event's
808
+ // clientAddressFilter for address-valued params).
809
+ if shouldJoinCurrentStartBlock {
810
+ addressesRef :=
811
+ addressesRef.contents->Array.concat(byStartBlock->Dict.getUnsafe(nextStartBlockKey))
812
+ false
813
+ } else {
814
+ true
829
815
  }
830
816
  }
831
817
  }
@@ -907,15 +893,10 @@ OptimizedPartitions.t => {
907
893
  }
908
894
  }
909
895
 
910
- let nextContractName =
911
- nextP.addressesByContractName->Dict.keysToArray->Utils.Array.firstUnsafe
912
- let hasFilterByAddresses = (
913
- contractConfigs->Dict.getUnsafe(nextContractName)
914
- ).filterByAddresses
915
896
  let isTooFar = currentPBlock + OptimizedPartitions.tooFarBlockRange < nextPBlock
916
897
 
917
- if isTooFar || hasFilterByAddresses {
918
- // Too far or address-filtered: mergeBlock on current, merge addresses into next
898
+ if isTooFar {
899
+ // Too far: mergeBlock on current, merge addresses into next
919
900
  mergedNonDynamic
920
901
  ->Array.push({
921
902
  ...currentP,
@@ -927,7 +908,7 @@ OptimizedPartitions.t => {
927
908
  addressesByContractName: mergedAddresses,
928
909
  }
929
910
  } else {
930
- // Close and not address-filtered: push next's addresses into current
911
+ // Close: push next's addresses into current
931
912
  currentPRef := {
932
913
  ...currentP,
933
914
  addressesByContractName: mergedAddresses,
@@ -968,12 +949,15 @@ let registerDynamicContracts = (
968
949
  let indexingAddresses = fetchState.indexingAddresses
969
950
  let registeringContractsByContract: dict<dict<indexingAddress>> = Dict.make()
970
951
  let earliestRegisteringEventBlockNumber = ref(%raw(`Infinity`))
971
- let hasDCWithFilterByAddresses = ref(false)
972
952
  // Addresses registered for contracts without matching events. These are not
973
953
  // added to partitions, but they are tracked on fetchState.indexingAddresses
974
954
  // so that later conflicting registrations are detected, and are persisted
975
955
  // to envio_addresses so they can be picked up on restart with updated config.
976
956
  let noEventsAddresses: dict<indexingAddress> = Dict.make()
957
+ // Batch-level view of all addresses registered so far (across contracts,
958
+ // including no-events ones), so two contracts registering the same address
959
+ // within one batch conflict the same way as against indexingAddresses.
960
+ let registeringAddresses: dict<indexingAddress> = Dict.make()
977
961
 
978
962
  for itemIdx in 0 to items->Array.length - 1 {
979
963
  let item = items->Array.getUnsafe(itemIdx)
@@ -987,7 +971,7 @@ let registerDynamicContracts = (
987
971
  let shouldRemove = ref(false)
988
972
 
989
973
  switch fetchState.contractConfigs->Utils.Dict.dangerouslyGetNonOption(dc.contractName) {
990
- | Some({filterByAddresses, startBlock: contractStartBlock}) =>
974
+ | Some({startBlock: contractStartBlock}) =>
991
975
  let dcWithStartBlock: indexingAddress = {
992
976
  address: dc.address,
993
977
  contractName: dc.contractName,
@@ -1021,9 +1005,7 @@ let registerDynamicContracts = (
1021
1005
  }
1022
1006
  shouldRemove := true
1023
1007
  | None =>
1024
- let registeringContracts =
1025
- registeringContractsByContract->Utils.Dict.getOrInsertEmptyDict(dc.contractName)
1026
- let shouldUpdate = switch registeringContracts->Utils.Dict.dangerouslyGetNonOption(
1008
+ let shouldUpdate = switch registeringAddresses->Utils.Dict.dangerouslyGetNonOption(
1027
1009
  dc.address->Address.toString,
1028
1010
  ) {
1029
1011
  | Some(registeringContract) if registeringContract.contractName != dc.contractName =>
@@ -1036,9 +1018,7 @@ let registerDynamicContracts = (
1036
1018
  // FIXME: This unsafely relies on the asc order of the items
1037
1019
  // which is 99% true, but there were cases when the source ordering was wrong
1038
1020
  false
1039
- | None =>
1040
- hasDCWithFilterByAddresses := hasDCWithFilterByAddresses.contents || filterByAddresses
1041
- true
1021
+ | None => true
1042
1022
  }
1043
1023
  if shouldUpdate {
1044
1024
  earliestRegisteringEventBlockNumber :=
@@ -1046,7 +1026,10 @@ let registerDynamicContracts = (
1046
1026
  earliestRegisteringEventBlockNumber.contents,
1047
1027
  dcWithStartBlock.effectiveStartBlock,
1048
1028
  )
1049
- registeringContracts->Dict.set(dc.address->Address.toString, dcWithStartBlock)
1029
+ registeringContractsByContract
1030
+ ->Utils.Dict.getOrInsertEmptyDict(dc.contractName)
1031
+ ->Dict.set(dc.address->Address.toString, dcWithStartBlock)
1032
+ registeringAddresses->Dict.set(dc.address->Address.toString, dcWithStartBlock)
1050
1033
  } else {
1051
1034
  shouldRemove := true
1052
1035
  }
@@ -1073,11 +1056,14 @@ let registerDynamicContracts = (
1073
1056
  }
1074
1057
  shouldRemove := true
1075
1058
  | None =>
1076
- switch noEventsAddresses->Utils.Dict.dangerouslyGetNonOption(
1059
+ switch registeringAddresses->Utils.Dict.dangerouslyGetNonOption(
1077
1060
  dc.address->Address.toString,
1078
1061
  ) {
1079
- | Some(_) =>
1080
- // Already queued for persistence by an earlier item in this batch.
1062
+ | Some(existingContract) =>
1063
+ if existingContract.contractName != dc.contractName {
1064
+ fetchState->warnDifferentContractType(~existingContract, ~dc=dcAsIndexingAddress)
1065
+ }
1066
+ // Otherwise already queued for persistence by an earlier item in this batch.
1081
1067
  shouldRemove := true
1082
1068
  | None =>
1083
1069
  let logger = Logging.createChild(
@@ -1092,6 +1078,7 @@ let registerDynamicContracts = (
1092
1078
  // skip partition registration since there's nothing to fetch.
1093
1079
  logger->Logging.childWarn(`Persisting contract registration without fetching: Contract doesn't have any events to fetch. It'll be picked up on restart if you add events for the contract.`)
1094
1080
  noEventsAddresses->Dict.set(dc.address->Address.toString, dcAsIndexingAddress)
1081
+ registeringAddresses->Dict.set(dc.address->Address.toString, dcAsIndexingAddress)
1095
1082
  }
1096
1083
  }
1097
1084
  }
@@ -1209,7 +1196,6 @@ let registerDynamicContracts = (
1209
1196
 
1210
1197
  let optimizedPartitions = createPartitionsFromIndexingAddresses(
1211
1198
  ~registeringContractsByContract,
1212
- ~contractConfigs=fetchState.contractConfigs,
1213
1199
  ~dynamicContracts=dynamicContractsRef.contents,
1214
1200
  ~normalSelection=fetchState.normalSelection,
1215
1201
  ~maxAddrInPartition=fetchState.optimizedPartitions.maxAddrInPartition,
@@ -1236,6 +1222,20 @@ let handleQueryResult = (
1236
1222
  ~latestFetchedBlock: blockNumberAndTimestamp,
1237
1223
  ~newItems,
1238
1224
  ): t => {
1225
+ // Drop events an address-param filter rejects. A merged partition may
1226
+ // over-fetch a wildcard event whose indexed address param references an
1227
+ // address registered after the log's block; `clientAddressFilter` is the
1228
+ // param-level analogue of EventRouter's srcAddress effectiveStartBlock check.
1229
+ let newItems = newItems->Array.filter(item =>
1230
+ switch item {
1231
+ | Internal.Event({eventConfig, event, blockNumber}) =>
1232
+ switch eventConfig.clientAddressFilter {
1233
+ | Some(filter) => filter(event, blockNumber, fetchState.indexingAddresses)
1234
+ | None => true
1235
+ }
1236
+ | _ => true
1237
+ }
1238
+ )
1239
1239
  fetchState->updateInternal(
1240
1240
  ~optimizedPartitions=fetchState.optimizedPartitions->OptimizedPartitions.handleQueryResponse(
1241
1241
  ~query,
@@ -1508,7 +1508,7 @@ let getNextQuery = (
1508
1508
  }
1509
1509
 
1510
1510
  let hasReadyItem = ({buffer} as fetchState: t) => {
1511
- switch buffer->Belt.Array.get(0) {
1511
+ switch buffer->Array.get(0) {
1512
1512
  | Some(item) => item->Internal.getItemBlockNumber <= fetchState->bufferBlockNumber
1513
1513
  | None => false
1514
1514
  }
@@ -1519,7 +1519,7 @@ let getReadyItemsCount = (fetchState: t, ~targetSize: int, ~fromItem) => {
1519
1519
  let acc = ref(0)
1520
1520
  let isFinished = ref(false)
1521
1521
  while !isFinished.contents {
1522
- switch fetchState.buffer->Belt.Array.get(fromItem + acc.contents) {
1522
+ switch fetchState.buffer->Array.get(fromItem + acc.contents) {
1523
1523
  | Some(item) =>
1524
1524
  let itemBlockNumber = item->Internal.getItemBlockNumber
1525
1525
  if itemBlockNumber <= readyBlockNumber.contents {
@@ -1563,15 +1563,14 @@ let make = (
1563
1563
  let normalEventConfigs = []
1564
1564
  let contractNamesWithNormalEvents = Utils.Set.make()
1565
1565
  let indexingAddresses = Dict.make()
1566
- let contractConfigs = Dict.make()
1566
+ let contractConfigs: dict<contractConfig> = Dict.make()
1567
1567
 
1568
1568
  eventConfigs->Array.forEach(ec => {
1569
1569
  switch contractConfigs->Utils.Dict.dangerouslyGetNonOption(ec.contractName) {
1570
- | Some({filterByAddresses, startBlock}) =>
1570
+ | Some({startBlock}) =>
1571
1571
  contractConfigs->Dict.set(
1572
1572
  ec.contractName,
1573
1573
  {
1574
- filterByAddresses: filterByAddresses || ec.filterByAddresses,
1575
1574
  startBlock: switch (startBlock, ec.startBlock) {
1576
1575
  | (Some(a), Some(b)) => Some(Pervasives.min(a, b))
1577
1576
  | (Some(_) as s, None) | (None, Some(_) as s) => s
@@ -1583,7 +1582,6 @@ let make = (
1583
1582
  contractConfigs->Dict.set(
1584
1583
  ec.contractName,
1585
1584
  {
1586
- filterByAddresses: ec.filterByAddresses,
1587
1585
  startBlock: ec.startBlock,
1588
1586
  },
1589
1587
  )
@@ -1663,7 +1661,6 @@ let make = (
1663
1661
 
1664
1662
  let optimizedPartitions = createPartitionsFromIndexingAddresses(
1665
1663
  ~registeringContractsByContract,
1666
- ~contractConfigs,
1667
1664
  ~dynamicContracts,
1668
1665
  ~normalSelection,
1669
1666
  ~maxAddrInPartition,
@@ -1866,7 +1863,6 @@ let rollback = (fetchState: t, ~targetBlockNumber) => {
1866
1863
  // Step 3: Recreate partitions from deleted partition addresses
1867
1864
  let optimizedPartitions = createPartitionsFromIndexingAddresses(
1868
1865
  ~registeringContractsByContract,
1869
- ~contractConfigs=fetchState.contractConfigs,
1870
1866
  ~dynamicContracts=fetchState.optimizedPartitions.dynamicContracts,
1871
1867
  ~normalSelection=fetchState.normalSelection,
1872
1868
  ~maxAddrInPartition=fetchState.optimizedPartitions.maxAddrInPartition,
@@ -1951,7 +1947,7 @@ let isReadyToEnterReorgThreshold = ({endBlock, blockLag, buffer, knownHeight} as
1951
1947
 
1952
1948
  let sortForUnorderedBatch = {
1953
1949
  let hasFullBatch = ({buffer} as fetchState: t, ~batchSizeTarget) => {
1954
- switch buffer->Belt.Array.get(batchSizeTarget - 1) {
1950
+ switch buffer->Array.get(batchSizeTarget - 1) {
1955
1951
  | Some(item) => item->Internal.getItemBlockNumber <= fetchState->bufferBlockNumber
1956
1952
  | None => false
1957
1953
  }
@@ -1966,7 +1962,7 @@ let sortForUnorderedBatch = {
1966
1962
  if totalRange <= 0 {
1967
1963
  0.
1968
1964
  } else {
1969
- let progress = switch fetchState.buffer->Belt.Array.get(0) {
1965
+ let progress = switch fetchState.buffer->Array.get(0) {
1970
1966
  | Some(item) => item->Internal.getItemBlockNumber - firstEventBlock
1971
1967
  | None => fetchState->bufferBlockNumber - firstEventBlock
1972
1968
  }
@@ -2001,7 +1997,7 @@ let sortForUnorderedBatch = {
2001
1997
 
2002
1998
  let getProgressBlockNumberAt = ({buffer} as fetchState: t, ~index) => {
2003
1999
  let bufferBlockNumber = fetchState->bufferBlockNumber
2004
- switch buffer->Belt.Array.get(index) {
2000
+ switch buffer->Array.get(index) {
2005
2001
  | Some(item) if bufferBlockNumber >= item->Internal.getItemBlockNumber =>
2006
2002
  item->Internal.getItemBlockNumber - 1
2007
2003
  | _ => bufferBlockNumber