envio 3.5.0-alpha.1 → 3.5.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 (86) hide show
  1. package/index.d.ts +10 -6
  2. package/package.json +6 -6
  3. package/src/BatchProcessing.res +13 -0
  4. package/src/BatchProcessing.res.mjs +4 -0
  5. package/src/ChainFetching.res +5 -5
  6. package/src/ChainFetching.res.mjs +3 -4
  7. package/src/ChainState.res +38 -21
  8. package/src/ChainState.res.mjs +17 -24
  9. package/src/ChainState.resi +1 -5
  10. package/src/Change.res +3 -3
  11. package/src/Config.res +0 -4
  12. package/src/Config.res.mjs +0 -10
  13. package/src/Core.res +3 -0
  14. package/src/EntityId.res +15 -0
  15. package/src/EntityId.res.mjs +9 -0
  16. package/src/EventConfigBuilder.res +1 -58
  17. package/src/EventConfigBuilder.res.mjs +1 -33
  18. package/src/FetchState.res +345 -459
  19. package/src/FetchState.res.mjs +269 -430
  20. package/src/HandlerRegister.res +45 -0
  21. package/src/HandlerRegister.res.mjs +43 -0
  22. package/src/InMemoryStore.res +11 -4
  23. package/src/InMemoryTable.res +12 -10
  24. package/src/InMemoryTable.res.mjs +6 -5
  25. package/src/IndexerState.res +59 -2
  26. package/src/IndexerState.res.mjs +48 -3
  27. package/src/IndexerState.resi +2 -0
  28. package/src/Internal.res +17 -8
  29. package/src/LogSelection.res +2 -1
  30. package/src/Metrics.res +37 -6
  31. package/src/Metrics.res.mjs +5 -1
  32. package/src/Persistence.res +1 -1
  33. package/src/PgStorage.res +55 -22
  34. package/src/PgStorage.res.mjs +25 -17
  35. package/src/SimulateItems.res +2 -2
  36. package/src/TestIndexer.res +6 -5
  37. package/src/TestIndexer.res.mjs +3 -2
  38. package/src/UserContext.res +3 -3
  39. package/src/Writing.res +12 -2
  40. package/src/Writing.res.mjs +13 -2
  41. package/src/bindings/ClickHouse.res +5 -3
  42. package/src/bindings/ClickHouse.res.mjs +2 -5
  43. package/src/bindings/Vitest.res +22 -1
  44. package/src/bindings/Vitest.res.mjs +15 -0
  45. package/src/db/EntityHistory.res +16 -7
  46. package/src/db/EntityHistory.res.mjs +7 -6
  47. package/src/db/Table.res +40 -9
  48. package/src/db/Table.res.mjs +44 -6
  49. package/src/sources/AddressSet.res +48 -0
  50. package/src/sources/AddressSet.res.mjs +11 -0
  51. package/src/sources/AddressStore.res +152 -0
  52. package/src/sources/AddressStore.res.mjs +97 -0
  53. package/src/sources/EvmChain.res +3 -0
  54. package/src/sources/EvmChain.res.mjs +4 -2
  55. package/src/sources/EvmHyperSyncSource.res +6 -3
  56. package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
  57. package/src/sources/EvmRpcClient.res +6 -3
  58. package/src/sources/EvmRpcClient.res.mjs +3 -3
  59. package/src/sources/FuelHyperSync.res +4 -3
  60. package/src/sources/FuelHyperSync.res.mjs +3 -3
  61. package/src/sources/FuelHyperSync.resi +2 -1
  62. package/src/sources/FuelHyperSyncClient.res +12 -6
  63. package/src/sources/FuelHyperSyncClient.res.mjs +2 -2
  64. package/src/sources/FuelHyperSyncSource.res +7 -4
  65. package/src/sources/FuelHyperSyncSource.res.mjs +3 -3
  66. package/src/sources/HyperSync.res +6 -4
  67. package/src/sources/HyperSync.res.mjs +2 -3
  68. package/src/sources/HyperSync.resi +1 -1
  69. package/src/sources/HyperSyncClient.res +18 -9
  70. package/src/sources/HyperSyncClient.res.mjs +4 -4
  71. package/src/sources/RpcSource.res +15 -10
  72. package/src/sources/RpcSource.res.mjs +3 -4
  73. package/src/sources/SimulateSource.res +26 -11
  74. package/src/sources/SimulateSource.res.mjs +13 -5
  75. package/src/sources/Source.res +4 -2
  76. package/src/sources/SourceManager.res +2 -3
  77. package/src/sources/SourceManager.res.mjs +2 -3
  78. package/src/sources/Svm.res +1 -2
  79. package/src/sources/Svm.res.mjs +1 -1
  80. package/src/sources/SvmHyperSyncClient.res +12 -3
  81. package/src/sources/SvmHyperSyncClient.res.mjs +2 -2
  82. package/src/sources/SvmHyperSyncSource.res +10 -5
  83. package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
  84. package/src/IndexingAddresses.res +0 -151
  85. package/src/IndexingAddresses.res.mjs +0 -130
  86. package/src/IndexingAddresses.resi +0 -39
package/index.d.ts CHANGED
@@ -585,14 +585,18 @@ export type SvmOnSlotContext<Config extends IndexerConfigTypes = GlobalConfig> =
585
585
  BaseHandlerContext<Config, SvmChainIds<Config>>
586
586
  >;
587
587
 
588
+ /** The entity's `id` type. `ID!`/`String!` ids are `string`; `Int!` is `number`
589
+ * and `BigInt!` is `bigint`, so id-keyed operations accept the real scalar. */
590
+ type EntityId<Entity> = Entity extends { readonly id: infer Id } ? Id : string;
591
+
588
592
  /** Entity operations available in handler contexts. */
589
593
  type EntityOperations<Entity> = {
590
- readonly get: (id: string) => Promise<Entity | undefined>;
591
- readonly getOrThrow: (id: string, message?: string) => Promise<Entity>;
594
+ readonly get: (id: EntityId<Entity>) => Promise<Entity | undefined>;
595
+ readonly getOrThrow: (id: EntityId<Entity>, message?: string) => Promise<Entity>;
592
596
  readonly getWhere: (filter: GetWhereFilter<Entity>) => Promise<Entity[]>;
593
597
  readonly getOrCreate: (entity: Entity) => Promise<Entity>;
594
598
  readonly set: (entity: Entity) => void;
595
- readonly deleteUnsafe: (id: string) => void;
599
+ readonly deleteUnsafe: (id: EntityId<Entity>) => void;
596
600
  };
597
601
 
598
602
  /** Contract registration handle. */
@@ -1510,7 +1514,7 @@ type EntityChangeValue<Entity> = {
1510
1514
  /** Entities that were created or updated. */
1511
1515
  readonly sets?: readonly Entity[];
1512
1516
  /** IDs of entities that were deleted. */
1513
- readonly deleted?: readonly string[];
1517
+ readonly deleted?: readonly EntityId<Entity>[];
1514
1518
  };
1515
1519
 
1516
1520
  /** A dynamic contract address registration. */
@@ -1528,9 +1532,9 @@ type ConfigEntities<Config extends IndexerConfigTypes = GlobalConfig> =
1528
1532
  /** Entity operations available on test indexer for direct entity manipulation. */
1529
1533
  type TestIndexerEntityOperations<Entity> = {
1530
1534
  /** Get an entity by ID. Returns undefined if not found. */
1531
- readonly get: (id: string) => Promise<Entity | undefined>;
1535
+ readonly get: (id: EntityId<Entity>) => Promise<Entity | undefined>;
1532
1536
  /** Get an entity by ID or throw if not found. */
1533
- readonly getOrThrow: (id: string, message?: string) => Promise<Entity>;
1537
+ readonly getOrThrow: (id: EntityId<Entity>, message?: string) => Promise<Entity>;
1534
1538
  /** Get all entities. */
1535
1539
  readonly getAll: () => Promise<Entity[]>;
1536
1540
  /** Set (create or update) an entity. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.5.0-alpha.1",
3
+ "version": "3.5.0-alpha.2",
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.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"
72
+ "envio-linux-x64": "3.5.0-alpha.2",
73
+ "envio-linux-x64-musl": "3.5.0-alpha.2",
74
+ "envio-linux-arm64": "3.5.0-alpha.2",
75
+ "envio-darwin-x64": "3.5.0-alpha.2",
76
+ "envio-darwin-arm64": "3.5.0-alpha.2"
77
77
  }
78
78
  }
@@ -45,6 +45,19 @@ let rec startProcessing = async (state: IndexerState.t, ~scheduleFetch, ~schedul
45
45
  // Hand off now that no batch is in flight.
46
46
  if state->IndexerState.isResolvingReorg {
47
47
  scheduleRollback()
48
+ } else if (
49
+ !(state->IndexerState.isStopped) &&
50
+ // Only a genuine fetch stall when some chain still has blocks to bring in.
51
+ // If every chain has buffered up to its known head, the loop is idling at
52
+ // the tip waiting for new blocks, not bottlenecked on fetch.
53
+ !(
54
+ state
55
+ ->IndexerState.chainStates
56
+ ->Dict.valuesToArray
57
+ ->Array.every(ChainState.isFetchingAtHead)
58
+ )
59
+ ) {
60
+ state->IndexerState.markProcessingStalledOnFetch
48
61
  }
49
62
  }
50
63
  }
@@ -108,6 +108,10 @@ async function startProcessing(state, scheduleFetch, scheduleRollback) {
108
108
  IndexerState.endProcessing(state);
109
109
  if (IndexerState.isResolvingReorg(state)) {
110
110
  return scheduleRollback();
111
+ } else if (!IndexerState.isStopped(state) && !Object.values(IndexerState.chainStates(state)).every(ChainState.isFetchingAtHead)) {
112
+ return IndexerState.markProcessingStalledOnFetch(state);
113
+ } else {
114
+ return;
111
115
  }
112
116
  }
113
117
 
@@ -209,11 +209,11 @@ let rec onQueryResponse = async (
209
209
  // kick (eg from the processing loop quiescing) collapses into this one.
210
210
  scheduleRollback()
211
211
  | None =>
212
- // Drop over-fetched events (a merged partition returning an address before
213
- // its effectiveStartBlock, or a wildcard param referencing an address
214
- // registered after the log's block) before contract registration, so they
215
- // neither spawn dynamic contracts nor enter the buffer.
216
- let newItems = chainState->ChainState.filterByClientAddress(parsedQueueItems)
212
+ // Over-fetched events (a merged partition returning an address before its
213
+ // effectiveStartBlock, or a wildcard param referencing an address
214
+ // registered after the log's block) are already dropped by the source's
215
+ // native address gate, so everything here is indexable.
216
+ let newItems = parsedQueueItems
217
217
  let itemsWithContractRegister = []
218
218
  for idx in 0 to newItems->Array.length - 1 {
219
219
  let item = newItems->Array.getUnsafe(idx)
@@ -145,17 +145,16 @@ async function onQueryResponse(state, param, stateId, scheduleFetch, schedulePro
145
145
  IndexerState.beginReorg(state, chain, rollbackWithReorgDetectedBlockNumber);
146
146
  return scheduleRollback();
147
147
  }
148
- let newItems = ChainState.filterByClientAddress(chainState, parsedQueueItems);
149
148
  let itemsWithContractRegister = [];
150
- for (let idx = 0, idx_finish = newItems.length; idx < idx_finish; ++idx) {
151
- let item = newItems[idx];
149
+ for (let idx = 0, idx_finish = parsedQueueItems.length; idx < idx_finish; ++idx) {
150
+ let item = parsedQueueItems[idx];
152
151
  if (item.onEventRegistration.contractRegister !== undefined) {
153
152
  itemsWithContractRegister.push(item);
154
153
  }
155
154
  }
156
155
  let proceed = newItemsWithDcs => {
157
156
  if (!IndexerState.isStale(state, stateId)) {
158
- applyQueryResponse(state, chain, newItems, newItemsWithDcs, knownHeight, {
157
+ applyQueryResponse(state, chain, parsedQueueItems, newItemsWithDcs, knownHeight, {
159
158
  blockNumber: latestFetchedBlockNumber,
160
159
  blockTimestamp: latestFetchedBlockTimestamp
161
160
  }, query, transactionStore, blockStore);
@@ -7,9 +7,10 @@ type t = {
7
7
  // The registrations used to build this chain's sources and route native items.
8
8
  onEventRegistrations: array<Internal.onEventRegistration>,
9
9
  mutable fetchState: FetchState.t,
10
- // The chain-wide address index. Not `mutable`: the dict is mutated in place by
11
- // register/rollback, so the reference is stable across fetchState versions.
12
- indexingAddresses: IndexingAddresses.t,
10
+ // The chain-wide address index, kept in Rust. Not `mutable`: registration and
11
+ // rollback mutate it in place, so the handle is stable across fetchState
12
+ // versions — and it's the same handle this chain's source clients hold.
13
+ addressStore: AddressStore.t,
13
14
  sourceManager: SourceManager.t,
14
15
  chainConfig: Config.chain,
15
16
  mutable isProgressAtHead: bool,
@@ -83,7 +84,7 @@ let make = (
83
84
  ~chainConfig: Config.chain,
84
85
  ~fetchState: FetchState.t,
85
86
  ~onEventRegistrations=[],
86
- ~indexingAddresses: IndexingAddresses.t,
87
+ ~addressStore: AddressStore.t,
87
88
  ~sourceManager: SourceManager.t,
88
89
  ~reorgDetection: ReorgDetection.t,
89
90
  ~committedProgressBlockNumber: int,
@@ -102,7 +103,7 @@ let make = (
102
103
  logger,
103
104
  onEventRegistrations,
104
105
  fetchState,
105
- indexingAddresses,
106
+ addressStore,
106
107
  sourceManager,
107
108
  chainConfig,
108
109
  isProgressAtHead,
@@ -166,12 +167,18 @@ let makeInternal = (
166
167
  }
167
168
  })
168
169
 
169
- let contractConfigs = IndexingAddresses.makeContractConfigs(~onEventRegistrations)
170
- let indexingAddressIndex = IndexingAddresses.make(~contractConfigs, ~addresses=indexingAddresses)
170
+ let lowercaseAddresses = config.lowercaseAddresses
171
+ // Created before the fetch state and the sources: `make` registers this
172
+ // chain's addresses into it, and every source client holds the same handle.
173
+ let addressStore = AddressStore.make(
174
+ ~ecosystem=config.ecosystem.name,
175
+ ~shouldChecksum=!lowercaseAddresses,
176
+ ~contracts=AddressStore.contractsOf(~onEventRegistrations),
177
+ )
171
178
 
172
179
  let fetchState = FetchState.make(
173
180
  ~maxAddrInPartition=config.maxAddrInPartition,
174
- ~contractConfigs,
181
+ ~addressStore,
175
182
  ~addresses=indexingAddresses,
176
183
  ~progressBlockNumber,
177
184
  ~startBlock,
@@ -187,8 +194,11 @@ let makeInternal = (
187
194
  ),
188
195
  ~onBlockRegistrations,
189
196
  ~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.
197
+ // Fuel and SVM route through the address store like EVM does, so the
198
+ // client-side path works for them but their address counts are nowhere
199
+ // near the threshold, so switching would only trade a server-side filter
200
+ // that costs nothing today for an over-fetch. Keep them server-side until a
201
+ // chain actually needs it.
192
202
  ~clientFilterAddressThreshold=switch config.ecosystem.name {
193
203
  | Evm => Some(config.clientFilterAddressThreshold)
194
204
  | Fuel | Svm => None
@@ -205,7 +215,6 @@ let makeInternal = (
205
215
 
206
216
  // Create sources lazily here - this is where API token validation happens
207
217
  let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)
208
- let lowercaseAddresses = config.lowercaseAddresses
209
218
  let sources = switch chainConfig.sourceConfig {
210
219
  | Config.EvmSourceConfig({hypersync, rpcs}) =>
211
220
  let evmRpcs: array<EvmChain.rpc> = rpcs->Array.map((rpc): EvmChain.rpc => {
@@ -228,6 +237,7 @@ let makeInternal = (
228
237
  ~hyperSync=hypersync,
229
238
  ~rpcs=evmRpcs,
230
239
  ~lowercaseAddresses,
240
+ ~addressStore,
231
241
  )
232
242
  | Config.FuelSourceConfig({hypersync}) => [
233
243
  FuelHyperSyncSource.make({
@@ -235,6 +245,7 @@ let makeInternal = (
235
245
  endpointUrl: hypersync,
236
246
  apiToken: Env.envioApiToken,
237
247
  onEventRegistrations,
248
+ addressStore,
238
249
  }),
239
250
  ]
240
251
  | Config.SvmSourceConfig({hypersync, rpc}) =>
@@ -255,6 +266,7 @@ let makeInternal = (
255
266
  apiToken,
256
267
  onEventRegistrations,
257
268
  clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
269
+ addressStore,
258
270
  }),
259
271
  ]
260
272
  }
@@ -274,7 +286,7 @@ let makeInternal = (
274
286
  ~chainConfig,
275
287
  ~fetchState,
276
288
  ~onEventRegistrations,
277
- ~indexingAddresses=indexingAddressIndex,
289
+ ~addressStore,
278
290
  ~sourceManager=SourceManager.make(~sources, ~isRealtime, ~reducedPollingInterval?),
279
291
  ~reorgDetection=ReorgDetection.make(
280
292
  ~chainReorgCheckpoints,
@@ -415,7 +427,7 @@ let setRollbackTargetBlock = (cs: t, ~blockNumber) => cs.rollbackTargetBlock = S
415
427
  // rather than reaching into it.
416
428
  let knownHeight = (cs: t) => cs.fetchState.knownHeight
417
429
  let contractAddresses = (cs: t, ~contractName) =>
418
- cs.indexingAddresses->IndexingAddresses.getContractAddresses(~contractName)
430
+ cs.addressStore->AddressStore.contractAddresses(contractName)
419
431
  let bufferSize = (cs: t) => cs.fetchState->FetchState.bufferSize
420
432
  let bufferReadyCount = (cs: t) => cs.fetchState->FetchState.bufferReadyCount
421
433
  let getProgressPercentage = (cs: t) => cs.fetchState->FetchState.getProgressPercentage
@@ -706,6 +718,9 @@ let groupBatchItems = (items: array<Internal.item>, ~includeBlocks: bool): (
706
718
  }
707
719
  }
708
720
  }
721
+ // onBlock items build their block from the handler's own block number, not
722
+ // from the stores — which is what lets the sources keep only the blocks and
723
+ // transactions an event item references.
709
724
  | Internal.Block(_) => ()
710
725
  }
711
726
  )
@@ -802,15 +817,12 @@ let materializePageItems = async (
802
817
  ))
803
818
  }
804
819
 
805
- let filterByClientAddress = (cs: t, items: array<Internal.item>): array<Internal.item> =>
806
- items->FetchState.filterByClientAddress(~indexingAddresses=cs.indexingAddresses)
807
-
808
820
  let handleQueryResult = (
809
821
  cs: t,
810
822
  ~query: FetchState.query,
811
823
  ~newItems,
812
824
  ~newItemsWithDcs,
813
- ~latestFetchedBlock,
825
+ ~latestFetchedBlock: FetchState.blockNumberAndTimestamp,
814
826
  ~knownHeight,
815
827
  ~transactionStore as txPage: option<TransactionStore.t>,
816
828
  ~blockStore as blockPage: option<BlockStore.t>,
@@ -830,7 +842,12 @@ let handleQueryResult = (
830
842
  | [] => cs.fetchState
831
843
  | _ =>
832
844
  cs.fetchState->FetchState.registerDynamicContracts(
833
- ~indexingAddresses=cs.indexingAddresses,
845
+ ~addressStore=cs.addressStore,
846
+ // This response is applied below, after the addresses land. It was routed
847
+ // before they existed, so whatever it claims has to be inside the
848
+ // catch-up range — and an unbounded query can reach past the height that
849
+ // was known when it went out.
850
+ ~claimCeiling=Pervasives.max(knownHeight, latestFetchedBlock.blockNumber),
834
851
  newItemsWithDcs,
835
852
  )
836
853
  }
@@ -906,7 +923,7 @@ let toMetrics = (cs: t): Metrics.chainMetrics => {
906
923
  numBatchesFetched: 0,
907
924
  startBlock: cs.fetchState.startBlock,
908
925
  endBlock: cs.fetchState.endBlock,
909
- numAddresses: cs.indexingAddresses->IndexingAddresses.size,
926
+ numAddresses: cs.addressStore->AddressStore.size,
910
927
  isReady: cs->isReady,
911
928
  sourceBlockNumber: cs.fetchState.knownHeight,
912
929
  progressBlockNumber: cs.committedProgressBlockNumber,
@@ -1111,7 +1128,7 @@ let rollback = (
1111
1128
  }
1112
1129
  cs.fetchState =
1113
1130
  cs.fetchState->FetchState.rollback(
1114
- ~indexingAddresses=cs.indexingAddresses,
1131
+ ~addressStore=cs.addressStore,
1115
1132
  ~targetBlockNumber=newProgressBlockNumber,
1116
1133
  )
1117
1134
  cs.transactionStore->TransactionStore.rollback(newProgressBlockNumber)
@@ -1127,7 +1144,7 @@ let rollback = (
1127
1144
  )
1128
1145
  cs.fetchState =
1129
1146
  cs.fetchState->FetchState.rollback(
1130
- ~indexingAddresses=cs.indexingAddresses,
1147
+ ~addressStore=cs.addressStore,
1131
1148
  ~targetBlockNumber=rollbackTargetBlockNumber,
1132
1149
  )
1133
1150
  cs.transactionStore->TransactionStore.rollback(rollbackTargetBlockNumber)
@@ -11,6 +11,7 @@ import * as FieldMask from "./sources/FieldMask.res.mjs";
11
11
  import * as BlockStore from "./sources/BlockStore.res.mjs";
12
12
  import * as FetchState from "./FetchState.res.mjs";
13
13
  import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
14
+ import * as AddressStore from "./sources/AddressStore.res.mjs";
14
15
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
15
16
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
16
17
  import * as SourceManager from "./sources/SourceManager.res.mjs";
@@ -20,7 +21,6 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
20
21
  import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
21
22
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
22
23
  import * as TransactionStore from "./sources/TransactionStore.res.mjs";
23
- import * as IndexingAddresses from "./IndexingAddresses.res.mjs";
24
24
  import * as SvmHyperSyncSource from "./sources/SvmHyperSyncSource.res.mjs";
25
25
  import * as FuelHyperSyncSource from "./sources/FuelHyperSyncSource.res.mjs";
26
26
  import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
@@ -47,7 +47,7 @@ function validateOnEventRegistrations(chainId, registrations) {
47
47
  });
48
48
  }
49
49
 
50
- function make(chainConfig, fetchState, onEventRegistrationsOpt, indexingAddresses, sourceManager, reorgDetection, committedProgressBlockNumber, safeCheckpointTrackingOpt, numEventsProcessedOpt, timestampCaughtUpToHeadOrEndblockOpt, isProgressAtHeadOpt, transactionStoreOpt, chainDensityOpt, blockStoreOpt, reorgThresholdReadyToleranceOpt, logger) {
50
+ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, sourceManager, reorgDetection, committedProgressBlockNumber, safeCheckpointTrackingOpt, numEventsProcessedOpt, timestampCaughtUpToHeadOrEndblockOpt, isProgressAtHeadOpt, transactionStoreOpt, chainDensityOpt, blockStoreOpt, reorgThresholdReadyToleranceOpt, logger) {
51
51
  let onEventRegistrations = onEventRegistrationsOpt !== undefined ? onEventRegistrationsOpt : [];
52
52
  let safeCheckpointTracking = safeCheckpointTrackingOpt !== undefined ? Primitive_option.valFromOption(safeCheckpointTrackingOpt) : undefined;
53
53
  let numEventsProcessed = numEventsProcessedOpt !== undefined ? numEventsProcessedOpt : 0;
@@ -62,7 +62,7 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, indexingAddresse
62
62
  logger: logger,
63
63
  onEventRegistrations: onEventRegistrations,
64
64
  fetchState: fetchState,
65
- indexingAddresses: indexingAddresses,
65
+ addressStore: addressStore,
66
66
  sourceManager: sourceManager,
67
67
  chainConfig: chainConfig,
68
68
  isProgressAtHead: isProgressAtHead,
@@ -103,8 +103,8 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
103
103
  return Stdlib_JsError.throwWithMessage(`The start block for contract "` + contract.name + `" is less than the chain start block. This is not supported yet.`);
104
104
  }
105
105
  });
106
- let contractConfigs = IndexingAddresses.makeContractConfigs(onEventRegistrations);
107
- let indexingAddressIndex = IndexingAddresses.make(contractConfigs, indexingAddresses);
106
+ let lowercaseAddresses = config.lowercaseAddresses;
107
+ let addressStore = AddressStore.make(config.ecosystem.name, !lowercaseAddresses, AddressStore.contractsOf(onEventRegistrations));
108
108
  let match$1 = config.ecosystem.name;
109
109
  let tmp;
110
110
  switch (match$1) {
@@ -116,14 +116,13 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
116
116
  tmp = undefined;
117
117
  break;
118
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));
119
+ let fetchState = FetchState.make(startBlock, endBlock, onEventRegistrations, addressStore, 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));
120
120
  let chainReorgCheckpoints = Stdlib_Array.filterMap(reorgCheckpoints, reorgCheckpoint => {
121
121
  if (reorgCheckpoint.chain_id === chainConfig.id) {
122
122
  return reorgCheckpoint;
123
123
  }
124
124
  });
125
125
  let chain = ChainMap.Chain.makeUnsafe(chainConfig.id);
126
- let lowercaseAddresses = config.lowercaseAddresses;
127
126
  let sources = chainConfig.sourceConfig;
128
127
  let sources$1;
129
128
  switch (sources.TAG) {
@@ -140,14 +139,15 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
140
139
  headers: headers
141
140
  };
142
141
  });
143
- sources$1 = EvmChain.makeSources(chain, onEventRegistrations, sources.hypersync, evmRpcs, lowercaseAddresses);
142
+ sources$1 = EvmChain.makeSources(chain, onEventRegistrations, sources.hypersync, evmRpcs, lowercaseAddresses, addressStore);
144
143
  break;
145
144
  case "FuelSourceConfig" :
146
145
  sources$1 = [FuelHyperSyncSource.make({
147
146
  chain: chain,
148
147
  endpointUrl: sources.hypersync,
149
148
  apiToken: Env.envioApiToken,
150
- onEventRegistrations: onEventRegistrations
149
+ onEventRegistrations: onEventRegistrations,
150
+ addressStore: addressStore
151
151
  })];
152
152
  break;
153
153
  case "SvmSourceConfig" :
@@ -158,7 +158,8 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
158
158
  endpointUrl: hypersync,
159
159
  apiToken: Env.envioApiToken,
160
160
  onEventRegistrations: onEventRegistrations,
161
- clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis
161
+ clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
162
+ addressStore: addressStore
162
163
  })] : (
163
164
  rpc !== undefined ? [Svm.makeRPCSource(chain, rpc, undefined)] : Stdlib_JsError.throwWithMessage(`Chain ` + chain.toString() + ` has no SVM data source`)
164
165
  );
@@ -169,7 +170,7 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
169
170
  }
170
171
  let firstEventBlock$1 = fetchState.firstEventBlock;
171
172
  let chainDensity = firstEventBlock$1 !== undefined && progressBlockNumber > firstEventBlock$1 && numEventsProcessed > 0 ? numEventsProcessed / (progressBlockNumber - firstEventBlock$1 | 0) : undefined;
172
- return make(chainConfig, fetchState, onEventRegistrations, indexingAddressIndex, SourceManager.make(sources$1, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), ReorgDetection.make(chainReorgCheckpoints, maxReorgDepth, config.shouldRollbackOnReorg), progressBlockNumber, Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, Primitive_option.some(TransactionStore.make(config.ecosystem.name, !lowercaseAddresses)), Primitive_option.some(chainDensity), Primitive_option.some(BlockStore.make(config.ecosystem.name, !lowercaseAddresses)), config.reorgThresholdReadyTolerance, logger);
173
+ return make(chainConfig, fetchState, onEventRegistrations, addressStore, SourceManager.make(sources$1, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), ReorgDetection.make(chainReorgCheckpoints, maxReorgDepth, config.shouldRollbackOnReorg), progressBlockNumber, Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, Primitive_option.some(TransactionStore.make(config.ecosystem.name, !lowercaseAddresses)), Primitive_option.some(chainDensity), Primitive_option.some(BlockStore.make(config.ecosystem.name, !lowercaseAddresses)), config.reorgThresholdReadyTolerance, logger);
173
174
  }
174
175
 
175
176
  function makeFromConfig(chainConfig, config, registrationsByChainId, knownHeight) {
@@ -250,7 +251,7 @@ function knownHeight(cs) {
250
251
  }
251
252
 
252
253
  function contractAddresses(cs, contractName) {
253
- return IndexingAddresses.getContractAddresses(cs.indexingAddresses, contractName);
254
+ return cs.addressStore.contractAddresses(contractName);
254
255
  }
255
256
 
256
257
  function bufferSize(cs) {
@@ -541,10 +542,6 @@ async function materializePageItems(items, transactionStore, blockStore, ecosyst
541
542
  ]);
542
543
  }
543
544
 
544
- function filterByClientAddress(cs, items) {
545
- return FetchState.filterByClientAddress(items, cs.indexingAddresses);
546
- }
547
-
548
545
  function handleQueryResult(cs, query, newItems, newItemsWithDcs, latestFetchedBlock, knownHeight, txPage, blockPage) {
549
546
  if (txPage !== undefined) {
550
547
  cs.transactionStore.merge(Primitive_option.valFromOption(txPage));
@@ -552,7 +549,7 @@ function handleQueryResult(cs, query, newItems, newItemsWithDcs, latestFetchedBl
552
549
  if (blockPage !== undefined) {
553
550
  cs.blockStore.merge(Primitive_option.valFromOption(blockPage));
554
551
  }
555
- let fs = newItemsWithDcs.length !== 0 ? FetchState.registerDynamicContracts(cs.fetchState, cs.indexingAddresses, newItemsWithDcs) : cs.fetchState;
552
+ let fs = newItemsWithDcs.length !== 0 ? FetchState.registerDynamicContracts(cs.fetchState, cs.addressStore, Primitive_int.max(knownHeight, latestFetchedBlock.blockNumber), newItemsWithDcs) : cs.fetchState;
556
553
  cs.fetchState = FetchState.updateKnownHeight(FetchState.handleQueryResult(fs, query, latestFetchedBlock, newItems), knownHeight);
557
554
  cs.pendingBudget = Primitive_float.max(0, cs.pendingBudget - query.itemsEst);
558
555
  }
@@ -587,7 +584,6 @@ function setEndBlockToFirstEvent(cs, blockNumber) {
587
584
  startBlock: init.startBlock,
588
585
  endBlock: blockNumber,
589
586
  normalSelection: init.normalSelection,
590
- contractConfigs: init.contractConfigs,
591
587
  chainId: init.chainId,
592
588
  latestOnBlockBlockNumber: init.latestOnBlockBlockNumber,
593
589
  blockLag: init.blockLag,
@@ -606,7 +602,6 @@ function setEndBlockToFirstEvent(cs, blockNumber) {
606
602
  startBlock: init$1.startBlock,
607
603
  endBlock: blockNumber,
608
604
  normalSelection: init$1.normalSelection,
609
- contractConfigs: init$1.contractConfigs,
610
605
  chainId: init$1.chainId,
611
606
  latestOnBlockBlockNumber: init$1.latestOnBlockBlockNumber,
612
607
  blockLag: init$1.blockLag,
@@ -645,7 +640,7 @@ function toMetrics(cs) {
645
640
  numBatchesFetched: 0,
646
641
  startBlock: cs.fetchState.startBlock,
647
642
  endBlock: cs.fetchState.endBlock,
648
- numAddresses: IndexingAddresses.size(cs.indexingAddresses),
643
+ numAddresses: cs.addressStore.size(),
649
644
  isReady: cs.timestampCaughtUpToHeadOrEndblock !== undefined,
650
645
  sourceBlockNumber: cs.fetchState.knownHeight,
651
646
  progressBlockNumber: cs.committedProgressBlockNumber,
@@ -715,7 +710,6 @@ function applyBatchProgress(cs, batch, blockTimestampName) {
715
710
  startBlock: init.startBlock,
716
711
  endBlock: init.endBlock,
717
712
  normalSelection: init.normalSelection,
718
- contractConfigs: init.contractConfigs,
719
713
  chainId: init.chainId,
720
714
  latestOnBlockBlockNumber: init.latestOnBlockBlockNumber,
721
715
  blockLag: init.blockLag,
@@ -774,7 +768,7 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
774
768
  if (newProgressBlockNumber === undefined) {
775
769
  if (isReorgChain) {
776
770
  cs.reorgDetection = ReorgDetection.rollbackToValidBlockNumber(cs.reorgDetection, rollbackTargetBlockNumber);
777
- cs.fetchState = FetchState.rollback(cs.fetchState, cs.indexingAddresses, rollbackTargetBlockNumber);
771
+ cs.fetchState = FetchState.rollback(cs.fetchState, cs.addressStore, rollbackTargetBlockNumber);
778
772
  cs.transactionStore.rollback(rollbackTargetBlockNumber);
779
773
  cs.blockStore.rollback(rollbackTargetBlockNumber);
780
774
  cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, rollbackTargetBlockNumber);
@@ -792,7 +786,7 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
792
786
  if (safeCheckpointTracking !== undefined) {
793
787
  cs.safeCheckpointTracking = SafeCheckpointTracking.rollback(safeCheckpointTracking, newProgressBlockNumber);
794
788
  }
795
- cs.fetchState = FetchState.rollback(cs.fetchState, cs.indexingAddresses, newProgressBlockNumber);
789
+ cs.fetchState = FetchState.rollback(cs.fetchState, cs.addressStore, newProgressBlockNumber);
796
790
  cs.transactionStore.rollback(newProgressBlockNumber);
797
791
  cs.blockStore.rollback(newProgressBlockNumber);
798
792
  cs.committedProgressBlockNumber = newProgressBlockNumber;
@@ -842,7 +836,6 @@ export {
842
836
  isReady,
843
837
  isFetchingAtHead,
844
838
  isAtHeadWithoutEndBlock,
845
- filterByClientAddress,
846
839
  handleQueryResult,
847
840
  materializeBatchItems,
848
841
  materializePageItems,
@@ -9,7 +9,7 @@ let make: (
9
9
  ~chainConfig: Config.chain,
10
10
  ~fetchState: FetchState.t,
11
11
  ~onEventRegistrations: array<Internal.onEventRegistration>=?,
12
- ~indexingAddresses: IndexingAddresses.t,
12
+ ~addressStore: AddressStore.t,
13
13
  ~sourceManager: SourceManager.t,
14
14
  ~reorgDetection: ReorgDetection.t,
15
15
  ~committedProgressBlockNumber: int,
@@ -104,10 +104,6 @@ let isReady: t => bool
104
104
  let isFetchingAtHead: t => bool
105
105
  let isAtHeadWithoutEndBlock: t => bool
106
106
 
107
- // Drop over-fetched events an address-param filter rejects, before contract
108
- // registration runs on them. Uses the chain's current indexing addresses.
109
- let filterByClientAddress: (t, array<Internal.item>) => array<Internal.item>
110
-
111
107
  // State transitions. The chain state is mutated only through these.
112
108
  let handleQueryResult: (
113
109
  t,
package/src/Change.res CHANGED
@@ -1,9 +1,9 @@
1
1
  @tag("type")
2
2
  type t<'entity> =
3
- | @as("SET") Set({entityId: string, entity: 'entity, checkpointId: bigint})
4
- | @as("DELETE") Delete({entityId: string, checkpointId: bigint})
3
+ | @as("SET") Set({entityId: EntityId.t, entity: 'entity, checkpointId: bigint})
4
+ | @as("DELETE") Delete({entityId: EntityId.t, checkpointId: bigint})
5
5
 
6
6
  @get
7
- external getEntityId: t<'entity> => string = "entityId"
7
+ external getEntityId: t<'entity> => EntityId.t = "entityId"
8
8
  @get
9
9
  external getCheckpointId: t<'entity> => bigint = "checkpointId"
package/src/Config.res CHANGED
@@ -390,10 +390,6 @@ let getFieldTypeAndSchema = (prop, ~enumConfigsByName: dict<Table.enumConfig<Tab
390
390
  let enumConfig = enumConfigsByName->Dict.get(enumName)->Option.getOrThrow
391
391
  (Table.Enum({config: enumConfig}), enumConfig.schema->S.toUnknown)
392
392
  }
393
- | "entity" => {
394
- let entityName = prop["entity"]->Option.getOrThrow
395
- (Table.Entity({name: entityName}), S.string->S.toUnknown)
396
- }
397
393
  | other => JsError.throwWithMessage("Unknown field type in entity config: " ++ other)
398
394
  }
399
395
 
@@ -263,16 +263,6 @@ function getFieldTypeAndSchema(prop, enumConfigsByName) {
263
263
  Utils.Schema.dbDate
264
264
  ];
265
265
  break;
266
- case "entity" :
267
- let entityName = Stdlib_Option.getOrThrow(prop.entity, undefined);
268
- match = [
269
- {
270
- type: "Entity",
271
- name: entityName
272
- },
273
- S$RescriptSchema.string
274
- ];
275
- break;
276
266
  case "enum" :
277
267
  let enumName = Stdlib_Option.getOrThrow(prop.enum, undefined);
278
268
  let enumConfig = Stdlib_Option.getOrThrow(enumConfigsByName[enumName], undefined);
package/src/Core.res CHANGED
@@ -10,6 +10,7 @@ type svmHyperSyncClientCtor
10
10
  type fuelHyperSyncClientCtor
11
11
  type transactionStoreCtor
12
12
  type blockStoreCtor
13
+ type addressStoreCtor
13
14
  type fromUserApiOptions = {
14
15
  schema?: string,
15
16
  env?: dict<string>,
@@ -39,6 +40,8 @@ type addon = {
39
40
  transactionStore: transactionStoreCtor,
40
41
  @as("BlockStore")
41
42
  blockStore: blockStoreCtor,
43
+ @as("AddressStore")
44
+ addressStore: addressStoreCtor,
42
45
  // Ordered transaction-field names exposed for the field-code contract test
43
46
  // (the ReScript `transactionFields` arrays must match the Rust ordinals).
44
47
  evmTransactionFieldNames: unit => array<string>,
@@ -0,0 +1,15 @@
1
+ // Opaque representation of an entity id. The generated per-entity types expose
2
+ // the real scalar (string / int / bigint); the generic storage and in-memory
3
+ // layers work with any entity's id through this type without knowing which
4
+ // scalar backs it. The runtime value is always the real id, never a stringified
5
+ // form — `toKey` derives the string only where a JS object/dict key is needed.
6
+ type t
7
+
8
+ external unsafeOfAny: 'a => t = "%identity"
9
+ external unsafeToAny: t => 'a = "%identity"
10
+ external unsafeOfString: string => t = "%identity"
11
+
12
+ // Stringified id used as a JS object/dict key. `String` matches how JS coerces
13
+ // a value used as an object key, so an id indexes the same whether the raw
14
+ // value or its key form is used for lookup, across string/int/bigint.
15
+ let toKey: t => string = %raw(`String`)
@@ -0,0 +1,9 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let toKey = String;
5
+
6
+ export {
7
+ toKey,
8
+ }
9
+ /* toKey Not a pure module */