envio 3.5.0-alpha.3 → 3.5.0

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 (127) hide show
  1. package/package.json +6 -6
  2. package/src/Batch.res +8 -8
  3. package/src/Batch.res.mjs +4 -3
  4. package/src/BatchProcessing.res +17 -7
  5. package/src/BatchProcessing.res.mjs +8 -4
  6. package/src/ChainFetching.res +20 -19
  7. package/src/ChainFetching.res.mjs +14 -14
  8. package/src/ChainId.res +58 -0
  9. package/src/ChainId.res.mjs +49 -0
  10. package/src/ChainId.resi +27 -0
  11. package/src/ChainMap.res +12 -26
  12. package/src/ChainMap.res.mjs +5 -41
  13. package/src/ChainMap.resi +5 -20
  14. package/src/ChainMetadata.res +1 -1
  15. package/src/ChainMetadata.res.mjs +3 -2
  16. package/src/ChainState.res +35 -20
  17. package/src/ChainState.res.mjs +34 -11
  18. package/src/ChainState.resi +2 -0
  19. package/src/Config.res +41 -24
  20. package/src/Config.res.mjs +17 -14
  21. package/src/ContractRegisterContext.res +1 -1
  22. package/src/ContractRegisterContext.res.mjs +1 -1
  23. package/src/Core.res +1 -0
  24. package/src/CrossChainState.res +61 -16
  25. package/src/CrossChainState.res.mjs +37 -13
  26. package/src/CrossChainState.resi +3 -2
  27. package/src/Env.res +0 -3
  28. package/src/Env.res.mjs +0 -3
  29. package/src/EventConfigBuilder.res +1 -1
  30. package/src/EventProcessing.res +4 -4
  31. package/src/EventProcessing.res.mjs +5 -5
  32. package/src/FetchState.res +261 -89
  33. package/src/FetchState.res.mjs +187 -63
  34. package/src/FinalizeBackfill.res +33 -6
  35. package/src/FinalizeBackfill.res.mjs +19 -1
  36. package/src/HandlerRegister.res +8 -8
  37. package/src/HandlerRegister.res.mjs +5 -4
  38. package/src/HandlerRegister.resi +1 -1
  39. package/src/InMemoryStore.res +4 -0
  40. package/src/InMemoryStore.res.mjs +1 -1
  41. package/src/IndexerLoop.res +9 -2
  42. package/src/IndexerLoop.res.mjs +8 -1
  43. package/src/IndexerState.res +41 -24
  44. package/src/IndexerState.res.mjs +36 -17
  45. package/src/IndexerState.resi +9 -7
  46. package/src/Internal.res +19 -17
  47. package/src/Internal.res.mjs +18 -9
  48. package/src/LoadLayer.res +1 -1
  49. package/src/LoadLayer.res.mjs +2 -1
  50. package/src/LogSelection.res +2 -2
  51. package/src/Main.res +7 -11
  52. package/src/Main.res.mjs +4 -6
  53. package/src/Metrics.res +6 -6
  54. package/src/Metrics.res.mjs +4 -3
  55. package/src/Persistence.res +15 -7
  56. package/src/PgStorage.res +284 -258
  57. package/src/PgStorage.res.mjs +181 -208
  58. package/src/RawEvent.res +1 -2
  59. package/src/RawEvent.res.mjs +1 -1
  60. package/src/Rollback.res +18 -20
  61. package/src/Rollback.res.mjs +9 -9
  62. package/src/RollbackCommit.res +2 -2
  63. package/src/RollbackCommit.res.mjs +3 -3
  64. package/src/SafeCheckpointTracking.res +2 -2
  65. package/src/SimulateDeadInputTracker.res +17 -21
  66. package/src/SimulateDeadInputTracker.res.mjs +9 -8
  67. package/src/SimulateItems.res +8 -10
  68. package/src/SimulateItems.res.mjs +9 -10
  69. package/src/Sink.res +2 -2
  70. package/src/Sink.res.mjs +3 -2
  71. package/src/TestIndexer.res +11 -14
  72. package/src/TestIndexer.res.mjs +14 -10
  73. package/src/UserContext.res +2 -2
  74. package/src/UserContext.res.mjs +2 -1
  75. package/src/Utils.res +0 -4
  76. package/src/Utils.res.mjs +1 -6
  77. package/src/bindings/ClickHouse.res +20 -2
  78. package/src/bindings/ClickHouse.res.mjs +25 -14
  79. package/src/db/EntityHistory.res +1 -1
  80. package/src/db/IndexCatalog.res +248 -0
  81. package/src/db/IndexCatalog.res.mjs +222 -0
  82. package/src/db/IndexDefinition.res +131 -0
  83. package/src/db/IndexDefinition.res.mjs +138 -0
  84. package/src/db/IndexManager.res +145 -0
  85. package/src/db/IndexManager.res.mjs +130 -0
  86. package/src/db/InternalTable.res +50 -26
  87. package/src/db/InternalTable.res.mjs +32 -24
  88. package/src/db/Table.res +11 -1
  89. package/src/db/Table.res.mjs +9 -4
  90. package/src/sources/AddressSet.res +5 -4
  91. package/src/sources/AddressStore.res +25 -14
  92. package/src/sources/AddressStore.res.mjs +10 -12
  93. package/src/sources/Evm.res +2 -2
  94. package/src/sources/Evm.res.mjs +1 -1
  95. package/src/sources/EvmChain.res +3 -3
  96. package/src/sources/EvmChain.res.mjs +3 -3
  97. package/src/sources/EvmHyperSyncSource.res +7 -7
  98. package/src/sources/EvmHyperSyncSource.res.mjs +6 -6
  99. package/src/sources/Fuel.res +2 -2
  100. package/src/sources/Fuel.res.mjs +1 -1
  101. package/src/sources/FuelHyperSyncClient.res +3 -0
  102. package/src/sources/FuelHyperSyncClient.res.mjs +1 -0
  103. package/src/sources/FuelHyperSyncSource.res +5 -7
  104. package/src/sources/FuelHyperSyncSource.res.mjs +5 -5
  105. package/src/sources/HyperSync.resi +2 -2
  106. package/src/sources/HyperSyncClient.res +5 -0
  107. package/src/sources/HyperSyncClient.res.mjs +1 -0
  108. package/src/sources/RpcSource.res +13 -14
  109. package/src/sources/RpcSource.res.mjs +13 -12
  110. package/src/sources/SimulateSource.res +42 -15
  111. package/src/sources/SimulateSource.res.mjs +27 -9
  112. package/src/sources/Source.res +1 -5
  113. package/src/sources/SourceManager.res +12 -12
  114. package/src/sources/SourceManager.res.mjs +11 -10
  115. package/src/sources/SourceManager.resi +2 -2
  116. package/src/sources/Svm.res +4 -5
  117. package/src/sources/Svm.res.mjs +5 -4
  118. package/src/sources/SvmHyperSyncClient.res +3 -0
  119. package/src/sources/SvmHyperSyncClient.res.mjs +1 -0
  120. package/src/sources/SvmHyperSyncSource.res +4 -4
  121. package/src/sources/SvmHyperSyncSource.res.mjs +3 -3
  122. package/src/tui/Tui.res +1 -1
  123. package/src/tui/Tui.res.mjs +2 -1
  124. package/src/tui/components/CustomHooks.res +5 -4
  125. package/src/tui/components/CustomHooks.res.mjs +4 -2
  126. package/src/db/IndexRegistry.res +0 -219
  127. package/src/db/IndexRegistry.res.mjs +0 -195
@@ -0,0 +1,130 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Utils from "../Utils.res.mjs";
4
+ import * as IndexCatalog from "./IndexCatalog.res.mjs";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as IndexDefinition from "./IndexDefinition.res.mjs";
7
+
8
+ function make() {
9
+ return {
10
+ catalog: IndexCatalog.make(),
11
+ inflight: {},
12
+ tableQueue: {}
13
+ };
14
+ }
15
+
16
+ function reload(manager, rows) {
17
+ manager.catalog = IndexCatalog.fromRows(rows);
18
+ return manager.catalog;
19
+ }
20
+
21
+ function catalog(manager) {
22
+ return manager.catalog;
23
+ }
24
+
25
+ function isSatisfied(manager, definition, coverage) {
26
+ return Stdlib_Option.isSome(IndexCatalog.find(manager.catalog, definition, coverage));
27
+ }
28
+
29
+ function conflictError(entry, definition, pgSchema) {
30
+ return new Error(`Cannot create the index "` + entry.name + `" in schema "` + pgSchema + `" for ` + IndexDefinition.describe(definition) + `. A different index already holds that name and the indexer can't safely replace it: ` + Stdlib_Option.getOr(IndexCatalog.rejectReason(entry, definition, "Exact"), "it is unique") + `. Drop that index by hand, then restart the indexer.`);
31
+ }
32
+
33
+ function verificationError(name, definition, pgSchema, reason) {
34
+ return new Error(`The index "` + name + `" in schema "` + pgSchema + `" is not usable after its DDL ran: ` + reason + `. It was meant to cover ` + IndexDefinition.describe(definition) + `. Drop it by hand, then restart the indexer.`);
35
+ }
36
+
37
+ function prepare(manager, definition, coverage, pgSchema) {
38
+ let match = IndexCatalog.find(manager.catalog, definition, coverage);
39
+ if (match !== undefined) {
40
+ return;
41
+ }
42
+ let name = IndexDefinition.name(definition);
43
+ let create = IndexDefinition.makeCreateQuery(definition, pgSchema);
44
+ let entry = IndexCatalog.getByName(manager.catalog, name);
45
+ if (entry === undefined) {
46
+ return {
47
+ definition: definition,
48
+ name: name,
49
+ isRebuild: false,
50
+ queries: [create]
51
+ };
52
+ }
53
+ if (IndexCatalog.isExactly(entry, definition)) {
54
+ return {
55
+ definition: definition,
56
+ name: name,
57
+ isRebuild: true,
58
+ queries: [
59
+ IndexDefinition.makeDropQuery(pgSchema, name),
60
+ create
61
+ ]
62
+ };
63
+ }
64
+ throw conflictError(entry, definition, pgSchema);
65
+ }
66
+
67
+ function verifyOrThrow(prepared, rows, pgSchema) {
68
+ let name = prepared.name;
69
+ let definition = prepared.definition;
70
+ let entry = rows.map(IndexCatalog.fromRow).find(entry => entry.name === name);
71
+ if (entry !== undefined) {
72
+ let reason = IndexCatalog.rejectReason(entry, definition, "Exact");
73
+ if (reason !== undefined) {
74
+ throw verificationError(name, definition, pgSchema, reason);
75
+ }
76
+ return entry;
77
+ }
78
+ throw verificationError(name, definition, pgSchema, "PostgreSQL has no such index");
79
+ }
80
+
81
+ function record(manager, entry) {
82
+ IndexCatalog.set(manager.catalog, entry);
83
+ }
84
+
85
+ function resync(manager, name, rows) {
86
+ let entry = rows.map(IndexCatalog.fromRow).find(entry => entry.name === name);
87
+ if (entry !== undefined) {
88
+ return IndexCatalog.set(manager.catalog, entry);
89
+ } else {
90
+ return IndexCatalog.remove(manager.catalog, name);
91
+ }
92
+ }
93
+
94
+ function ensure(manager, definition, coverage, build) {
95
+ if (isSatisfied(manager, definition, coverage)) {
96
+ return Promise.resolve();
97
+ }
98
+ let key = IndexCatalog.coverageKey(coverage) + IndexDefinition.key(definition);
99
+ let promise = manager.inflight[key];
100
+ if (promise !== undefined) {
101
+ return promise;
102
+ }
103
+ let tail = manager.tableQueue[definition.tableName];
104
+ let tail$1 = tail !== undefined ? tail : Promise.resolve();
105
+ let promise$1 = tail$1.then(() => {
106
+ if (isSatisfied(manager, definition, coverage)) {
107
+ return Promise.resolve();
108
+ } else {
109
+ return build();
110
+ }
111
+ });
112
+ manager.inflight[key] = promise$1;
113
+ manager.tableQueue[definition.tableName] = Utils.$$Promise.silentCatch(promise$1);
114
+ return promise$1.finally(() => Utils.Dict.deleteInPlace(manager.inflight, key));
115
+ }
116
+
117
+ export {
118
+ make,
119
+ reload,
120
+ catalog,
121
+ isSatisfied,
122
+ conflictError,
123
+ verificationError,
124
+ prepare,
125
+ verifyOrThrow,
126
+ record,
127
+ resync,
128
+ ensure,
129
+ }
130
+ /* Utils Not a pure module */
@@ -56,7 +56,7 @@ module Chains = {
56
56
  }
57
57
 
58
58
  type t = {
59
- @as("id") id: int,
59
+ @as("id") id: ChainId.t,
60
60
  @as("start_block") startBlock: int,
61
61
  @as("end_block") endBlock: Null.t<int>,
62
62
  @as("max_reorg_depth") maxReorgDepth: int,
@@ -69,7 +69,7 @@ module Chains = {
69
69
  let table = mkTable(
70
70
  "envio_chains",
71
71
  ~fields=[
72
- mkField((#id: field :> string), Int32, ~fieldSchema=S.int, ~isPrimaryKey),
72
+ mkField((#id: field :> string), ChainId, ~fieldSchema=ChainId.schema, ~isPrimaryKey),
73
73
  // Values populated from config
74
74
  mkField((#start_block: field :> string), Int32, ~fieldSchema=S.int),
75
75
  mkField((#end_block: field :> string), Int32, ~fieldSchema=S.null(S.int), ~isNullable),
@@ -164,15 +164,24 @@ SET ${setClauses->Array.joinUnsafe(",\n ")}
164
164
  WHERE "${(#id: field :> string)}" = $1;`
165
165
  }
166
166
 
167
- // Written in the same transaction as the deferred schema indexes, so a chain
168
- // is never reported ready without the indexes the schema promises.
167
+ // Written only once every schema-defined index is verified, so a chain is
168
+ // never reported ready without the indexes the schema promises. One row at a
169
+ // time, like `setMeta`: the id column is INTEGER or BIGINT depending on the
170
+ // configured `ChainId.mode`, and a bare `= $2` needs no cast either way.
171
+ //
172
+ // `IS NULL` so a chain keeps the timestamp it first caught up at, matching the
173
+ // sticky in-memory `ChainState.markReady`. Without it a partial recovery (eg a
174
+ // chain added to an already-synced indexer) would restamp the ready chains in
175
+ // the database while their in-memory copies kept the old value — and the next
176
+ // chain-metadata write would push the stale value back over the committed one.
169
177
  let makeSetReadyAtQuery = (~pgSchema) =>
170
178
  `UPDATE "${pgSchema}"."${table.tableName}"
171
179
  SET "${(#ready_at: field :> string)}" = $1
172
- WHERE "${(#id: field :> string)}" = ANY($2::int[]);`
180
+ WHERE "${(#id: field :> string)}" = $2
181
+ AND "${(#ready_at: field :> string)}" IS NULL;`
173
182
 
174
183
  type rawInitialState = {
175
- id: int,
184
+ id: ChainId.t,
176
185
  startBlock: int,
177
186
  endBlock: Null.t<int>,
178
187
  maxReorgDepth: int,
@@ -198,7 +207,7 @@ FROM "${pgSchema}"."${table.tableName}";`
198
207
  }
199
208
 
200
209
  type rawIndexingAddress = {
201
- chainId: int,
210
+ chainId: ChainId.t,
202
211
  address: Address.t,
203
212
  contractName: string,
204
213
  registrationBlock: int,
@@ -231,7 +240,9 @@ FROM "${pgSchema}"."${EnvioAddresses.table.tableName}";`
231
240
 
232
241
  let indexingAddressesByChainId = Dict.make()
233
242
  rawIndexingAddresses->Array.forEach(row => {
234
- let key = row.chainId->Int.toString
243
+ // BIGINT chain ids come back as strings; normalizing here keeps the
244
+ // grouping key identical to the one derived from the chains rows below.
245
+ let key = row.chainId->ChainId.normalizeOrThrow->ChainId.toString
235
246
  let addresses = switch indexingAddressesByChainId->Dict.get(key) {
236
247
  | Some(addresses) => addresses
237
248
  | None =>
@@ -249,10 +260,14 @@ FROM "${pgSchema}"."${EnvioAddresses.table.tableName}";`
249
260
  })
250
261
 
251
262
  rawInitialStates->Array.map(rawInitialState => {
252
- ...rawInitialState,
253
- indexingAddresses: indexingAddressesByChainId
254
- ->Dict.get(rawInitialState.id->Int.toString)
255
- ->Option.getOr([]),
263
+ let id = rawInitialState.id->ChainId.normalizeOrThrow
264
+ {
265
+ ...rawInitialState,
266
+ id,
267
+ indexingAddresses: indexingAddressesByChainId
268
+ ->Dict.get(id->ChainId.toString)
269
+ ->Option.getOr([]),
270
+ }
256
271
  })
257
272
  }
258
273
 
@@ -295,7 +310,7 @@ WHERE "id" = $1;`
295
310
  }
296
311
 
297
312
  type progressedChain = {
298
- chainId: int,
313
+ chainId: ChainId.t,
299
314
  progressBlockNumber: int,
300
315
  sourceBlockNumber: int,
301
316
  totalEventsProcessed: float,
@@ -309,7 +324,7 @@ WHERE "id" = $1;`
309
324
  progressedChains->Array.forEach(data => {
310
325
  let params = []
311
326
 
312
- params->Array.push(data.chainId->(Utils.magic: int => unknown))->ignore
327
+ params->Array.push(data.chainId->(Utils.magic: ChainId.t => unknown))->ignore
313
328
 
314
329
  progressFields->Array.forEach(field => {
315
330
  params
@@ -393,7 +408,7 @@ module Checkpoints = {
393
408
  type t = {
394
409
  id: bigint,
395
410
  @as("chain_id")
396
- chainId: int,
411
+ chainId: ChainId.t,
397
412
  @as("block_number")
398
413
  blockNumber: int,
399
414
  @as("block_hash")
@@ -405,7 +420,7 @@ module Checkpoints = {
405
420
  // Schema for parsing DB results where BIGINT columns come back as strings
406
421
  let dbSchema = S.object(s => {
407
422
  id: s.field("id", Utils.BigInt.schema),
408
- chainId: s.field("chain_id", S.int),
423
+ chainId: s.field("chain_id", ChainId.schema),
409
424
  blockNumber: s.field("block_number", S.int),
410
425
  blockHash: s.field(
411
426
  "block_hash",
@@ -423,7 +438,7 @@ module Checkpoints = {
423
438
  "envio_checkpoints",
424
439
  ~fields=[
425
440
  mkField((#id: field :> string), UInt64, ~fieldSchema=S.bigint, ~isPrimaryKey),
426
- mkField((#chain_id: field :> string), Int32, ~fieldSchema=S.int),
441
+ mkField((#chain_id: field :> string), ChainId, ~fieldSchema=ChainId.schema),
427
442
  mkField((#block_number: field :> string), Int32, ~fieldSchema=S.int),
428
443
  mkField((#block_hash: field :> string), String, ~fieldSchema=S.null(S.string), ~isNullable),
429
444
  mkField((#events_processed: field :> string), Int32, ~fieldSchema=S.int),
@@ -460,9 +475,17 @@ WHERE cp."${(#block_hash: field :> string)}" IS NOT NULL
460
475
  `SELECT COALESCE(MAX(${(#id: field :> string)}), ${initialCheckpointId->BigInt.toString}) AS id FROM "${pgSchema}"."${table.tableName}";`
461
476
  }
462
477
 
463
- let makeInsertCheckpointQuery = (~pgSchema) => {
478
+ let makeInsertCheckpointQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
479
+ let chainIdArrayType = Table.getPgFieldType(
480
+ ~fieldType=ChainId,
481
+ ~pgSchema,
482
+ ~isArray=true,
483
+ ~isNumericArrayAsText=false,
484
+ ~isNullable=false,
485
+ ~chainIdMode,
486
+ )
464
487
  `INSERT INTO "${pgSchema}"."${table.tableName}" ("${(#id: field :> string)}", "${(#chain_id: field :> string)}", "${(#block_number: field :> string)}", "${(#block_hash: field :> string)}", "${(#events_processed: field :> string)}")
465
- SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${(Integer: Postgres.columnType :> string)}[],$3::${(Integer: Postgres.columnType :> string)}[],$4::${(Text: Postgres.columnType :> string)}[],$5::${(Integer: Postgres.columnType :> string)}[]);`
488
+ SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${chainIdArrayType},$3::${(Integer: Postgres.columnType :> string)}[],$4::${(Text: Postgres.columnType :> string)}[],$5::${(Integer: Postgres.columnType :> string)}[]);`
466
489
  }
467
490
 
468
491
  let insert = (
@@ -473,8 +496,9 @@ SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${(Int
473
496
  ~checkpointBlockNumbers,
474
497
  ~checkpointBlockHashes,
475
498
  ~checkpointEventsProcessed,
499
+ ~chainIdMode: ChainId.mode=Int32,
476
500
  ) => {
477
- let query = makeInsertCheckpointQuery(~pgSchema)
501
+ let query = makeInsertCheckpointQuery(~pgSchema, ~chainIdMode)
478
502
 
479
503
  // Convert bigint arrays to string arrays for postgres driver compatibility
480
504
  let checkpointIdStrings = checkpointIds->Utils.BigInt.arrayToStringArray
@@ -489,7 +513,7 @@ SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${(Int
489
513
  checkpointEventsProcessed,
490
514
  )->(
491
515
  Utils.magic: (
492
- (array<string>, array<int>, array<int>, array<Null.t<string>>, array<int>)
516
+ (array<string>, array<ChainId.t>, array<int>, array<Null.t<string>>, array<int>)
493
517
  ) => unknown
494
518
  ),
495
519
  )
@@ -530,7 +554,7 @@ LIMIT 1;`
530
554
  let getRollbackTargetCheckpoint = (
531
555
  sql,
532
556
  ~pgSchema,
533
- ~reorgChainId: int,
557
+ ~reorgChainId: ChainId.t,
534
558
  ~lastKnownValidBlockNumber: int,
535
559
  ) => {
536
560
  let rawResult: promise<array<{"id": string}>> =
@@ -547,7 +571,7 @@ LIMIT 1;`
547
571
 
548
572
  let makeGetRollbackProgressDiffQuery = (~pgSchema) => {
549
573
  `SELECT
550
- "${(#chain_id: field :> string)}",
574
+ "${(#chain_id: field :> string)}"::float8 as "${(#chain_id: field :> string)}",
551
575
  SUM("${(#events_processed: field :> string)}") as events_processed_diff,
552
576
  MIN("${(#block_number: field :> string)}") - 1 as new_progress_block_number
553
577
  FROM "${pgSchema}"."${table.tableName}"
@@ -568,7 +592,7 @@ GROUP BY "${(#chain_id: field :> string)}";`
568
592
  ->(
569
593
  Utils.magic: promise<unknown> => promise<
570
594
  array<{
571
- "chain_id": int,
595
+ "chain_id": ChainId.t,
572
596
  "events_processed_diff": string,
573
597
  "new_progress_block_number": int,
574
598
  }>,
@@ -581,7 +605,7 @@ module RawEvents = {
581
605
  type t = Internal.rawEvent
582
606
 
583
607
  let schema = S.schema((s): t => {
584
- chain_id: s.matches(S.int),
608
+ chain_id: s.matches(ChainId.schema),
585
609
  event_id: s.matches(S.bigint),
586
610
  event_name: s.matches(S.string),
587
611
  contract_name: s.matches(S.string),
@@ -598,7 +622,7 @@ module RawEvents = {
598
622
  let table = mkTable(
599
623
  "raw_events",
600
624
  ~fields=[
601
- mkField("chain_id", Int32, ~fieldSchema=S.int),
625
+ mkField("chain_id", ChainId, ~fieldSchema=ChainId.schema),
602
626
  mkField("event_id", UInt64, ~fieldSchema=S.bigint),
603
627
  mkField("event_name", String, ~fieldSchema=S.string),
604
628
  mkField("contract_name", String, ~fieldSchema=S.string),
@@ -4,6 +4,7 @@ import * as Table from "./Table.res.mjs";
4
4
  import * as Utils from "../Utils.res.mjs";
5
5
  import * as Config from "../Config.res.mjs";
6
6
  import * as Address from "../Address.res.mjs";
7
+ import * as ChainId from "../ChainId.res.mjs";
7
8
  import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
8
9
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
10
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
@@ -25,7 +26,7 @@ let fields = [
25
26
  ];
26
27
 
27
28
  let table = Table.mkTable("envio_chains", undefined, [
28
- Table.mkField("id", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, true, undefined, undefined, undefined, undefined, undefined),
29
+ Table.mkField("id", "ChainId", ChainId.schema, undefined, undefined, undefined, true, undefined, undefined, undefined, undefined, undefined),
29
30
  Table.mkField("start_block", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
30
31
  Table.mkField("end_block", "Int32", S$RescriptSchema.$$null(S$RescriptSchema.int), undefined, undefined, true, undefined, undefined, undefined, undefined, undefined, undefined),
31
32
  Table.mkField("max_reorg_depth", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
@@ -104,7 +105,8 @@ WHERE "` + "id" + `" = $1;`;
104
105
  function makeSetReadyAtQuery(pgSchema) {
105
106
  return `UPDATE "` + pgSchema + `"."` + table.tableName + `"
106
107
  SET "` + "ready_at" + `" = $1
107
- WHERE "` + "id" + `" = ANY($2::int[]);`;
108
+ WHERE "` + "id" + `" = $2
109
+ AND "` + "ready_at" + `" IS NULL;`;
108
110
  }
109
111
 
110
112
  function makeGetInitialStateQuery(pgSchema) {
@@ -135,7 +137,7 @@ async function getInitialState(sql, pgSchema) {
135
137
  ]);
136
138
  let indexingAddressesByChainId = {};
137
139
  match[1].forEach(row => {
138
- let key = row.chainId.toString();
140
+ let key = ChainId.toString(ChainId.normalizeOrThrow(row.chainId));
139
141
  let addresses = indexingAddressesByChainId[key];
140
142
  let addresses$1;
141
143
  if (addresses !== undefined) {
@@ -151,18 +153,21 @@ async function getInitialState(sql, pgSchema) {
151
153
  registrationBlock: row.registrationBlock
152
154
  });
153
155
  });
154
- return match[0].map(rawInitialState => ({
155
- id: rawInitialState.id,
156
- startBlock: rawInitialState.startBlock,
157
- endBlock: rawInitialState.endBlock,
158
- maxReorgDepth: rawInitialState.maxReorgDepth,
159
- firstEventBlockNumber: rawInitialState.firstEventBlockNumber,
160
- timestampCaughtUpToHeadOrEndblock: rawInitialState.timestampCaughtUpToHeadOrEndblock,
161
- numEventsProcessed: rawInitialState.numEventsProcessed,
162
- progressBlockNumber: rawInitialState.progressBlockNumber,
163
- indexingAddresses: Stdlib_Option.getOr(indexingAddressesByChainId[rawInitialState.id.toString()], []),
164
- sourceBlockNumber: rawInitialState.sourceBlockNumber
165
- }));
156
+ return match[0].map(rawInitialState => {
157
+ let id = ChainId.normalizeOrThrow(rawInitialState.id);
158
+ return {
159
+ id: id,
160
+ startBlock: rawInitialState.startBlock,
161
+ endBlock: rawInitialState.endBlock,
162
+ maxReorgDepth: rawInitialState.maxReorgDepth,
163
+ firstEventBlockNumber: rawInitialState.firstEventBlockNumber,
164
+ timestampCaughtUpToHeadOrEndblock: rawInitialState.timestampCaughtUpToHeadOrEndblock,
165
+ numEventsProcessed: rawInitialState.numEventsProcessed,
166
+ progressBlockNumber: rawInitialState.progressBlockNumber,
167
+ indexingAddresses: Stdlib_Option.getOr(indexingAddressesByChainId[ChainId.toString(id)], []),
168
+ sourceBlockNumber: rawInitialState.sourceBlockNumber
169
+ };
170
+ });
166
171
  }
167
172
 
168
173
  let progressFields = [
@@ -269,7 +274,7 @@ let EnvioInfo = {
269
274
 
270
275
  let dbSchema = S$RescriptSchema.object(s => ({
271
276
  id: s.f("id", Utils.$$BigInt.schema),
272
- chain_id: s.f("chain_id", S$RescriptSchema.int),
277
+ chain_id: s.f("chain_id", ChainId.schema),
273
278
  block_number: s.f("block_number", S$RescriptSchema.int),
274
279
  block_hash: s.f("block_hash", S$RescriptSchema.union([
275
280
  S$RescriptSchema.string,
@@ -280,7 +285,7 @@ let dbSchema = S$RescriptSchema.object(s => ({
280
285
 
281
286
  let table$2 = Table.mkTable("envio_checkpoints", undefined, [
282
287
  Table.mkField("id", "UInt64", S$RescriptSchema.bigint, undefined, undefined, undefined, true, undefined, undefined, undefined, undefined, undefined),
283
- Table.mkField("chain_id", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
288
+ Table.mkField("chain_id", "ChainId", ChainId.schema, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
284
289
  Table.mkField("block_number", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
285
290
  Table.mkField("block_hash", "String", S$RescriptSchema.$$null(S$RescriptSchema.string), undefined, undefined, true, undefined, undefined, undefined, undefined, undefined, undefined),
286
291
  Table.mkField("events_processed", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined)
@@ -311,13 +316,16 @@ function makeCommitedCheckpointIdQuery(pgSchema) {
311
316
  return `SELECT COALESCE(MAX(` + "id" + `), ` + (0n).toString() + `) AS id FROM "` + pgSchema + `"."` + table$2.tableName + `";`;
312
317
  }
313
318
 
314
- function makeInsertCheckpointQuery(pgSchema) {
319
+ function makeInsertCheckpointQuery(pgSchema, chainIdModeOpt) {
320
+ let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
321
+ let chainIdArrayType = Table.getPgFieldType("ChainId", pgSchema, true, false, false, chainIdMode);
315
322
  return `INSERT INTO "` + pgSchema + `"."` + table$2.tableName + `" ("` + "id" + `", "` + "chain_id" + `", "` + "block_number" + `", "` + "block_hash" + `", "` + "events_processed" + `")
316
- SELECT * FROM unnest($1::` + "BIGINT" + `[],$2::` + "INTEGER" + `[],$3::` + "INTEGER" + `[],$4::` + "TEXT" + `[],$5::` + "INTEGER" + `[]);`;
323
+ SELECT * FROM unnest($1::` + "BIGINT" + `[],$2::` + chainIdArrayType + `,$3::` + "INTEGER" + `[],$4::` + "TEXT" + `[],$5::` + "INTEGER" + `[]);`;
317
324
  }
318
325
 
319
- function insert(sql, pgSchema, checkpointIds, checkpointChainIds, checkpointBlockNumbers, checkpointBlockHashes, checkpointEventsProcessed) {
320
- let query = makeInsertCheckpointQuery(pgSchema);
326
+ function insert(sql, pgSchema, checkpointIds, checkpointChainIds, checkpointBlockNumbers, checkpointBlockHashes, checkpointEventsProcessed, chainIdModeOpt) {
327
+ let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
328
+ let query = makeInsertCheckpointQuery(pgSchema, chainIdMode);
321
329
  let checkpointIdStrings = Utils.$$BigInt.arrayToStringArray(checkpointIds);
322
330
  return sql.unsafe(query, [
323
331
  checkpointIdStrings,
@@ -359,7 +367,7 @@ function getRollbackTargetCheckpoint(sql, pgSchema, reorgChainId, lastKnownValid
359
367
 
360
368
  function makeGetRollbackProgressDiffQuery(pgSchema) {
361
369
  return `SELECT
362
- "` + "chain_id" + `",
370
+ "` + "chain_id" + `"::float8 as "` + "chain_id" + `",
363
371
  SUM("` + "events_processed" + `") as events_processed_diff,
364
372
  MIN("` + "block_number" + `") - 1 as new_progress_block_number
365
373
  FROM "` + pgSchema + `"."` + table$2.tableName + `"
@@ -389,7 +397,7 @@ let Checkpoints = {
389
397
  };
390
398
 
391
399
  let schema = S$RescriptSchema.schema(s => ({
392
- chain_id: s.m(S$RescriptSchema.int),
400
+ chain_id: s.m(ChainId.schema),
393
401
  event_id: s.m(S$RescriptSchema.bigint),
394
402
  event_name: s.m(S$RescriptSchema.string),
395
403
  contract_name: s.m(S$RescriptSchema.string),
@@ -404,7 +412,7 @@ let schema = S$RescriptSchema.schema(s => ({
404
412
  }));
405
413
 
406
414
  let table$3 = Table.mkTable("raw_events", undefined, [
407
- Table.mkField("chain_id", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
415
+ Table.mkField("chain_id", "ChainId", ChainId.schema, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
408
416
  Table.mkField("event_id", "UInt64", S$RescriptSchema.bigint, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
409
417
  Table.mkField("event_name", "String", S$RescriptSchema.string, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
410
418
  Table.mkField("contract_name", "String", S$RescriptSchema.string, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
package/src/db/Table.res CHANGED
@@ -23,6 +23,9 @@ type fieldType =
23
23
  | UInt52
24
24
  | UInt64
25
25
  | Int32
26
+ // Resolved to Int32 or UInt64 storage from the config's `ChainId.mode`, so
27
+ // the internal tables can stay module-level constants.
28
+ | ChainId
26
29
  | Number
27
30
  | BigInt({precision?: int})
28
31
  | BigDecimal({config?: (int, int)}) // (precision, scale)
@@ -138,11 +141,17 @@ let getPgFieldType = (
138
141
  ~isArray,
139
142
  ~isNumericArrayAsText,
140
143
  ~isNullable,
144
+ ~chainIdMode: ChainId.mode=Int32,
141
145
  ) => {
142
146
  let columnType = switch fieldType {
143
147
  | String => (Postgres.Text :> string)
144
148
  | Boolean => (Postgres.Boolean :> string)
145
149
  | Int32 => (Postgres.Integer :> string)
150
+ | ChainId =>
151
+ switch chainIdMode {
152
+ | Int32 => (Postgres.Integer :> string)
153
+ | Int64 => (Postgres.BigInt :> string)
154
+ }
146
155
  | Uint32 => (Postgres.BigInt :> string)
147
156
  | UInt52 => (Postgres.BigInt :> string)
148
157
  | UInt64 => (Postgres.BigInt :> string)
@@ -378,7 +387,7 @@ type sqlParams<'entity> = {
378
387
  hasArrayField: bool,
379
388
  }
380
389
 
381
- let toSqlParams = (table: table, ~schema, ~pgSchema) => {
390
+ let toSqlParams = (table: table, ~schema, ~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
382
391
  let quotedFieldNames = []
383
392
  let quotedNonPrimaryFieldNames = []
384
393
  let arrayFieldTypes = []
@@ -441,6 +450,7 @@ let toSqlParams = (table: table, ~schema, ~pgSchema) => {
441
450
  ~isArray=true,
442
451
  ~isNullable=f.isNullable,
443
452
  ~isNumericArrayAsText=false,
453
+ ~chainIdMode,
444
454
  )
445
455
  switch f.fieldType {
446
456
  | Enum(_) => `${(Text: Postgres.columnType :> string)}[]::${pgFieldType}`
@@ -96,7 +96,8 @@ function getPgFieldName(fieldOrDerived) {
96
96
 
97
97
  let idFieldName = "id";
98
98
 
99
- function getPgFieldType(fieldType, pgSchema, isArray, isNumericArrayAsText, isNullable) {
99
+ function getPgFieldType(fieldType, pgSchema, isArray, isNumericArrayAsText, isNullable, chainIdModeOpt) {
100
+ let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
100
101
  let columnType;
101
102
  if (typeof fieldType !== "object") {
102
103
  switch (fieldType) {
@@ -109,6 +110,9 @@ function getPgFieldType(fieldType, pgSchema, isArray, isNumericArrayAsText, isNu
109
110
  case "Int32" :
110
111
  columnType = "INTEGER";
111
112
  break;
113
+ case "ChainId" :
114
+ columnType = chainIdMode === "int32" ? "INTEGER" : "BIGINT";
115
+ break;
112
116
  case "Number" :
113
117
  columnType = "DOUBLE PRECISION";
114
118
  break;
@@ -247,7 +251,7 @@ function getIdFieldOrThrow(table) {
247
251
  }
248
252
 
249
253
  function getIdPgFieldType(table, pgSchema) {
250
- return getPgFieldType(getIdFieldOrThrow(table).fieldType, pgSchema, false, false, false);
254
+ return getPgFieldType(getIdFieldOrThrow(table).fieldType, pgSchema, false, false, false, undefined);
251
255
  }
252
256
 
253
257
  function getIdSchema(table) {
@@ -324,7 +328,8 @@ function getUnfilteredCompositeIndexesUnsafe(table) {
324
328
  }));
325
329
  }
326
330
 
327
- function toSqlParams(table, schema, pgSchema) {
331
+ function toSqlParams(table, schema, pgSchema, chainIdModeOpt) {
332
+ let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
328
333
  let quotedFieldNames = [];
329
334
  let quotedNonPrimaryFieldNames = [];
330
335
  let arrayFieldTypes = [];
@@ -395,7 +400,7 @@ function toSqlParams(table, schema, pgSchema) {
395
400
  let tmp;
396
401
  if (field$1.TAG === "Field") {
397
402
  let f = field$1._0;
398
- let pgFieldType = getPgFieldType(f.fieldType, pgSchema, true, false, f.isNullable);
403
+ let pgFieldType = getPgFieldType(f.fieldType, pgSchema, true, false, f.isNullable, chainIdMode);
399
404
  let match = f.fieldType;
400
405
  tmp = typeof match !== "object" ? (
401
406
  match === "Boolean" ? "INTEGER" + `[]::` + pgFieldType : pgFieldType
@@ -15,10 +15,11 @@ type startBlockGroup = {startBlock: int, count: int}
15
15
  // fetch state can size and split partitions without ever walking the addresses.
16
16
  @send external countFor: (t, string) => int = "countFor"
17
17
 
18
- // The chain-wide address gate, reachable through any set of the store. Not set
19
- // membership: the simulate source has no native query boundary, so it applies
20
- // the same gate every real source applies while routing.
21
- @send external has: (t, Address.t, string, int) => bool = "has"
18
+ // This set holds the address under the contract and it has already started at
19
+ // the block — the gate a real source's router applies to a server-side
20
+ // address-filtered query. The temporal half still comes from the store: a
21
+ // merged partition's addresses don't all start at the same block.
22
+ @send external containsAt: (t, Address.t, string, int) => bool = "containsAt"
22
23
 
23
24
  @send external contractNames: t => array<string> = "contractNames"
24
25
 
@@ -67,28 +67,39 @@ let make = (~ecosystem: Ecosystem.name, ~shouldChecksum: bool, ~contracts: array
67
67
  }
68
68
 
69
69
  // The contracts of a chain that have events, with the earliest block any of
70
- // their events may fire at.
70
+ // their events may fire at. `None` means unrestricted, so it wins over any
71
+ // `Some`: one registration without a start block makes the whole contract's
72
+ // address gate open from the chain's start, and the per-registration start
73
+ // block still holds back the registrations that declared one.
71
74
  let contractsOf = (~onEventRegistrations: array<Internal.onEventRegistration>): array<contract> => {
72
- let startBlocks = Dict.make()
75
+ // Only ever holds a declared start block, so a missing key is unambiguous —
76
+ // storing `None` in a dict would be indistinguishable from "not seen yet".
77
+ let startBlocks: dict<int> = Dict.make()
78
+ let unrestricted = Utils.Set.make()
73
79
  let names = []
74
80
  onEventRegistrations->Array.forEach(reg => {
75
81
  let name = reg.eventConfig.contractName
76
- switch startBlocks->Utils.Dict.dangerouslyGetNonOption(name) {
77
- | None =>
82
+ if !(names->Array.includes(name)) {
78
83
  names->Array.push(name)->ignore
79
- startBlocks->Dict.set(name, reg.startBlock)
80
- | Some(existing) =>
84
+ }
85
+ switch reg.startBlock {
86
+ | None => unrestricted->Utils.Set.add(name)->ignore
87
+ | Some(startBlock) =>
81
88
  startBlocks->Dict.set(
82
89
  name,
83
- switch (existing, reg.startBlock) {
84
- | (Some(a), Some(b)) => Some(Pervasives.min(a, b))
85
- | (Some(_) as s, None) | (None, Some(_) as s) => s
86
- | (None, None) => None
90
+ switch startBlocks->Utils.Dict.dangerouslyGetNonOption(name) {
91
+ | Some(existing) => Pervasives.min(existing, startBlock)
92
+ | None => startBlock
87
93
  },
88
94
  )
89
95
  }
90
96
  })
91
- names->Array.map(name => {name, startBlock: startBlocks->Dict.getUnsafe(name)})
97
+ names->Array.map(name => {
98
+ name,
99
+ startBlock: unrestricted->Utils.Set.has(name)
100
+ ? None
101
+ : startBlocks->Utils.Dict.dangerouslyGetNonOption(name),
102
+ })
92
103
  }
93
104
 
94
105
  @send external nextId: t => int = "nextId"
@@ -108,9 +119,9 @@ external startBlockGroups: (t, string) => array<AddressSet.startBlockGroup> = "s
108
119
  @send external contractCount: (t, string) => int = "contractCount"
109
120
  @send external size: t => int = "size"
110
121
 
111
- // The gate routing applies, exposed for the simulate source — it has no real
112
- // query boundary to gate at.
113
- @send external has: (t, Address.t, string, int) => bool = "has"
122
+ // The chain-wide gate routing applies, exposed for the simulate source — it has
123
+ // no real query boundary to gate at.
124
+ @send external isIndexedAt: (t, Address.t, string, int) => bool = "isIndexedAt"
114
125
 
115
126
  // Drops every address registered after the target block, returning how many
116
127
  // were dropped. Ids are tombstoned rather than reused, so sets built before the