envio 3.12.1 → 3.13.0-rc.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 (103) hide show
  1. package/package.json +6 -8
  2. package/src/BatchProcessing.res +15 -14
  3. package/src/BatchProcessing.res.mjs +8 -15
  4. package/src/Bin.res +24 -17
  5. package/src/Bin.res.mjs +5 -0
  6. package/src/ChainFetching.res +18 -24
  7. package/src/ChainFetching.res.mjs +14 -15
  8. package/src/ChainState.res +103 -38
  9. package/src/ChainState.res.mjs +79 -22
  10. package/src/ChainState.resi +14 -2
  11. package/src/Config.res +32 -0
  12. package/src/Config.res.mjs +24 -0
  13. package/src/Core.res +4 -0
  14. package/src/Core.res.mjs +4 -0
  15. package/src/CrossChainState.res +109 -11
  16. package/src/CrossChainState.res.mjs +71 -7
  17. package/src/CrossChainState.resi +11 -1
  18. package/src/Env.res +4 -0
  19. package/src/ErrorHandling.res +15 -4
  20. package/src/ErrorHandling.res.mjs +16 -5
  21. package/src/FinalizeBackfill.res +10 -4
  22. package/src/FinalizeBackfill.res.mjs +5 -3
  23. package/src/HandlerRegister.res +0 -37
  24. package/src/HandlerRegister.res.mjs +0 -18
  25. package/src/IndexerLoop.res +2 -0
  26. package/src/IndexerLoop.res.mjs +1 -0
  27. package/src/IndexerState.res +43 -1
  28. package/src/IndexerState.res.mjs +48 -4
  29. package/src/IndexerState.resi +11 -0
  30. package/src/Main.res +128 -268
  31. package/src/Main.res.mjs +27 -153
  32. package/src/Metrics.res +268 -102
  33. package/src/Metrics.res.mjs +227 -48
  34. package/src/Persistence.res +30 -2
  35. package/src/Persistence.res.mjs +12 -3
  36. package/src/PgStorage.res +19 -11
  37. package/src/PgStorage.res.mjs +13 -10
  38. package/src/Rollback.res +3 -3
  39. package/src/Server.res +178 -0
  40. package/src/Server.res.mjs +143 -0
  41. package/src/SimulateItems.res +1 -1
  42. package/src/SimulateItems.res.mjs +1 -1
  43. package/src/Supervisor.res +542 -0
  44. package/src/Supervisor.res.mjs +455 -0
  45. package/src/TestIndexer.res +1 -1
  46. package/src/TestIndexer.res.mjs +2 -2
  47. package/src/Worker.res +101 -0
  48. package/src/Worker.res.mjs +81 -0
  49. package/src/bindings/NodeJs.res +50 -0
  50. package/src/bindings/NodeJs.res.mjs +8 -1
  51. package/src/db/InternalTable.res +8 -1
  52. package/src/db/InternalTable.res.mjs +5 -1
  53. package/src/sources/AddressSet.res +5 -3
  54. package/src/sources/AddressStore.res +19 -11
  55. package/src/sources/AddressStore.res.mjs +2 -7
  56. package/src/sources/BlockStore.res +10 -9
  57. package/src/sources/BlockStore.res.mjs +4 -4
  58. package/src/sources/ChainSources.res +4 -0
  59. package/src/sources/ChainSources.res.mjs +2 -2
  60. package/src/sources/EvmChain.res +4 -0
  61. package/src/sources/EvmChain.res.mjs +3 -1
  62. package/src/sources/EvmEventItem.res +56 -0
  63. package/src/sources/EvmEventItem.res.mjs +32 -0
  64. package/src/sources/EvmHyperSyncSource.res +7 -37
  65. package/src/sources/EvmHyperSyncSource.res.mjs +8 -29
  66. package/src/sources/EvmRpcClient.res +41 -59
  67. package/src/sources/EvmRpcClient.res.mjs +4 -54
  68. package/src/sources/EvmRpcWs.res +4 -1
  69. package/src/sources/EvmRpcWs.res.mjs +6 -2
  70. package/src/sources/FuelHyperSyncSource.res +6 -4
  71. package/src/sources/FuelHyperSyncSource.res.mjs +7 -9
  72. package/src/sources/HeightFeed.res +7 -1
  73. package/src/sources/HeightFeed.res.mjs +9 -7
  74. package/src/sources/HyperSyncClient.res +1 -14
  75. package/src/sources/RequestStat.res +7 -0
  76. package/src/sources/RequestStat.res.mjs +14 -1
  77. package/src/sources/RpcSource.res +100 -1043
  78. package/src/sources/RpcSource.res.mjs +81 -1056
  79. package/src/sources/SimulateSource.res +2 -5
  80. package/src/sources/SimulateSource.res.mjs +2 -3
  81. package/src/sources/Source.res +34 -12
  82. package/src/sources/Source.res.mjs +11 -0
  83. package/src/sources/SourceManager.res +111 -31
  84. package/src/sources/SourceManager.res.mjs +65 -31
  85. package/src/sources/SourceManager.resi +4 -0
  86. package/src/sources/StartBlockResolver.res +8 -5
  87. package/src/sources/StartBlockResolver.res.mjs +4 -2
  88. package/src/sources/SvmHyperSyncSource.res +4 -2
  89. package/src/sources/SvmHyperSyncSource.res.mjs +4 -6
  90. package/src/sources/TransactionStore.res +9 -6
  91. package/src/sources/TransactionStore.res.mjs +2 -2
  92. package/src/tui/Tui.res +10 -0
  93. package/src/tui/Tui.res.mjs +15 -0
  94. package/src/tui/components/SyncETA.res +12 -6
  95. package/src/tui/components/SyncETA.res.mjs +12 -8
  96. package/src/LazyLoader.res +0 -135
  97. package/src/LazyLoader.res.mjs +0 -118
  98. package/src/bindings/SDSL.res +0 -12
  99. package/src/bindings/SDSL.res.mjs +0 -9
  100. package/src/bindings/Yargs.res +0 -8
  101. package/src/bindings/Yargs.res.mjs +0 -2
  102. package/src/sources/Rpc.res +0 -185
  103. package/src/sources/Rpc.res.mjs +0 -183
@@ -43,6 +43,10 @@ type t = {
43
43
  maxReorgDepth: int,
44
44
  // One-way: past it, everything the chain writes can still be rolled back.
45
45
  mutable isInReorgThreshold: bool,
46
+ // How this chain spells the addresses materialised out of its stores, from
47
+ // `lowercaseAddresses`. The stores hold raw bytes; the spelling is decided
48
+ // where they are read.
49
+ shouldChecksum: bool,
46
50
  // Holds this chain's transactions (kept in Rust) keyed by (blockNumber,
47
51
  // transactionIndex). Fetch responses merge their page in; entries are pruned
48
52
  // as the chain progresses and dropped above the target on rollback.
@@ -58,6 +62,10 @@ type t = {
58
62
  mutable blockRangeFetchCount: float,
59
63
  mutable blockRangeFetchedEvents: float,
60
64
  mutable blockRangeFetchedBlocks: float,
65
+ // Whether this chain has said where it finished indexing. Being finished
66
+ // stays true for the rest of the run, and every pass over the chains would
67
+ // say so again.
68
+ mutable reportedFinished: bool,
61
69
  mutable reorgCount: int,
62
70
  mutable reorgDetectedBlock: option<int>,
63
71
  mutable rollbackTargetBlock: option<int>,
@@ -136,9 +144,10 @@ let make = (
136
144
  ~numEventsProcessed=0.,
137
145
  ~timestampCaughtUpToHeadOrEndblock=None,
138
146
  ~isProgressAtHead=false,
139
- ~transactionStore=TransactionStore.make(~ecosystem=Ecosystem.Evm, ~shouldChecksum=false),
147
+ ~transactionStore=TransactionStore.make(~ecosystem=Ecosystem.Evm),
140
148
  ~chainDensity=None,
141
- ~blockStore=BlockStore.make(~ecosystem=Ecosystem.Evm, ~shouldChecksum=false),
149
+ ~blockStore=BlockStore.make(~ecosystem=Ecosystem.Evm),
150
+ ~shouldChecksum=false,
142
151
  ~reorgThresholdReadyTolerance=100,
143
152
  ~perChainEntities: array<Internal.entityConfig>=[],
144
153
  ~logger: Pino.t,
@@ -165,12 +174,14 @@ let make = (
165
174
  isInReorgThreshold,
166
175
  transactionStore,
167
176
  blockStore,
177
+ shouldChecksum,
168
178
  reorgThresholdReadyTolerance,
169
179
  blockRangeFetchSeconds: 0.,
170
180
  blockRangeParseSeconds: 0.,
171
181
  blockRangeFetchCount: 0.,
172
182
  blockRangeFetchedEvents: 0.,
173
183
  blockRangeFetchedBlocks: 0.,
184
+ reportedFinished: false,
174
185
  reorgCount: 0,
175
186
  reorgDetectedBlock: None,
176
187
  rollbackTargetBlock: None,
@@ -228,7 +239,6 @@ let makeInternal = (
228
239
  // chain's addresses into it, and every source client holds the same handle.
229
240
  let addressStore = AddressStore.make(
230
241
  ~ecosystem=config.ecosystem.name,
231
- ~shouldChecksum=!lowercaseAddresses,
232
242
  ~contracts=AddressStore.contractsOf(~onEventRegistrations, ~contractMapping),
233
243
  )
234
244
 
@@ -273,17 +283,18 @@ let makeInternal = (
273
283
  }
274
284
  })
275
285
 
286
+ // Before the sources, which are constructed holding them.
287
+ let blockStore = BlockStore.make(~ecosystem=config.ecosystem.name)
288
+ let transactionStore = TransactionStore.make(~ecosystem=config.ecosystem.name)
289
+
276
290
  // Create sources lazily here - this is where API token validation happens
277
291
  let sources = ChainSources.make(
278
292
  ~chainConfig,
279
293
  ~onEventRegistrations,
280
294
  ~addressStore,
281
295
  ~lowercaseAddresses,
282
- )
283
-
284
- let blockStore = BlockStore.make(
285
- ~ecosystem=config.ecosystem.name,
286
- ~shouldChecksum=!lowercaseAddresses,
296
+ ~blockStore,
297
+ ~transactionStore,
287
298
  )
288
299
 
289
300
  // Seed the stored reorg checkpoints (hash-only rows) so detection resumes
@@ -295,7 +306,6 @@ let makeInternal = (
295
306
  blockHash: cp.blockHash,
296
307
  }),
297
308
  ~ecosystem=config.ecosystem.name,
298
- ~shouldChecksum=!lowercaseAddresses,
299
309
  )
300
310
  blockStore
301
311
  ->BlockStore.merge(seedPage, ~fromBlock=0, ~reportOnly=false)
@@ -329,12 +339,10 @@ let makeInternal = (
329
339
  ~perChainEntities=config.userEntities->EntityTables.perChain,
330
340
  ~timestampCaughtUpToHeadOrEndblock,
331
341
  ~numEventsProcessed,
332
- ~transactionStore=TransactionStore.make(
333
- ~ecosystem=config.ecosystem.name,
334
- ~shouldChecksum=!lowercaseAddresses,
335
- ),
342
+ ~transactionStore,
336
343
  ~chainDensity,
337
344
  ~blockStore,
345
+ ~shouldChecksum=!lowercaseAddresses,
338
346
  ~reorgThresholdReadyTolerance=config.reorgThresholdReadyTolerance,
339
347
  ~logger,
340
348
  )
@@ -394,6 +402,8 @@ let makeFromDbState = (
394
402
 
395
403
  let logger = (cs: t) => cs.logger
396
404
  let blockStore = (cs: t) => cs.blockStore
405
+ let transactionStore = (cs: t) => cs.transactionStore
406
+ let shouldChecksum = (cs: t) => cs.shouldChecksum
397
407
  let entities = (cs: t) => cs.entities
398
408
 
399
409
  // Rollback discards every uncommitted change, so this chain's partition is
@@ -450,7 +460,7 @@ let setRollbackTargetBlock = (cs: t, ~blockNumber) => cs.rollbackTargetBlock = S
450
460
  // rather than reaching into it.
451
461
  let knownHeight = (cs: t) => cs.fetchState.knownHeight
452
462
  let contractAddresses = (cs: t, ~contractName) =>
453
- cs.addressStore->AddressStore.contractAddresses(contractName)
463
+ cs.addressStore->AddressStore.contractAddresses(contractName, ~shouldChecksum=cs.shouldChecksum)
454
464
 
455
465
  // Hands over the dynamically registered addresses the database hasn't seen yet,
456
466
  // up to the block the caller is about to commit, each paired with the checkpoint
@@ -650,6 +660,30 @@ let hasProcessedToEndblock = (cs: t) => {
650
660
  }
651
661
  }
652
662
 
663
+ // Where this chain has finished indexing, the first time it gets there.
664
+ // `EndBlock` is terminal: the chain indexed everything it was configured to.
665
+ // `Backfill` is the rest of the history, up to the point where blocks can
666
+ // still be reorged, which is as far as a chain indexes before the indexer
667
+ // crosses into them.
668
+ type finished = EndBlock(int) | Backfill(int)
669
+
670
+ let takeFinished = (cs: t) =>
671
+ if cs.reportedFinished {
672
+ None
673
+ } else {
674
+ switch (cs.fetchState.endBlock, cs->hasProcessedToEndblock, cs.isProgressAtHead) {
675
+ | (Some(endBlock), true, _) => {
676
+ cs.reportedFinished = true
677
+ Some(EndBlock(endBlock))
678
+ }
679
+ | (_, _, true) => {
680
+ cs.reportedFinished = true
681
+ Some(Backfill(cs.committedProgressBlockNumber))
682
+ }
683
+ | _ => None
684
+ }
685
+ }
686
+
653
687
  // Caught up as judged by persisted values alone: progress reached the endBlock,
654
688
  // or the head the previous run had already observed (less the lag that holds the
655
689
  // tip back). Unlike `isFetchingAtHead` this doesn't move when a fresh height
@@ -730,7 +764,7 @@ let groupBatchItems = (items: array<Internal.item>): (transactionGroups, blockGr
730
764
  let {blockNumber} = eventItem
731
765
 
732
766
  switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
733
- | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
767
+ | Some(_) => () // Fuel and Simulate carry the transaction inline.
734
768
  | None =>
735
769
  let {transactionIndex} = eventItem
736
770
  let mask = eventItem.onEventRegistration.fieldSelection.transactionMask
@@ -757,7 +791,7 @@ let groupBatchItems = (items: array<Internal.item>): (transactionGroups, blockGr
757
791
  }
758
792
 
759
793
  switch eventItem.payload->Internal.getPayloadBlock->Nullable.toOption {
760
- | Some(_) => () // RPC/simulate carry the block inline.
794
+ | Some(_) => () // Simulate carries the block inline.
761
795
  | None =>
762
796
  let mask = eventItem.onEventRegistration.fieldSelection.blockMask
763
797
  let last = blockItemGroups->Array.length - 1
@@ -796,13 +830,18 @@ let groupBatchItems = (items: array<Internal.item>): (transactionGroups, blockGr
796
830
  // `groupBatchItems`). Store-backed items always get a transaction object - the
797
831
  // selected fields, or `{}` when nothing was selected - so `event.transaction`
798
832
  // is never `undefined` (matching the inline sources).
799
- let applyTransactionGroups = async (store: TransactionStore.t, g: transactionGroups) => {
833
+ let applyTransactionGroups = async (
834
+ store: TransactionStore.t,
835
+ g: transactionGroups,
836
+ ~shouldChecksum,
837
+ ) => {
800
838
  if g.payloadGroups->Utils.Array.notEmpty {
801
839
  if g.anyTransactionFieldSelected {
802
840
  let txs = await store->TransactionStore.materialize(
803
841
  ~blockNumbers=g.txBlockNumbers,
804
842
  ~transactionIndices=g.transactionIndices,
805
843
  ~masks=g.transactionMasks,
844
+ ~shouldChecksum,
806
845
  )
807
846
  g.payloadGroups->Array.forEachWithIndex((payloads, i) => {
808
847
  let tx = txs->Array.getUnsafe(i)
@@ -823,11 +862,12 @@ let applyTransactionGroups = async (store: TransactionStore.t, g: transactionGro
823
862
  }
824
863
 
825
864
  // Materialise a `BlockStore` against precomputed groups (see `groupBatchItems`).
826
- let applyBlockGroups = async (store: BlockStore.t, g: blockGroups) => {
865
+ let applyBlockGroups = async (store: BlockStore.t, g: blockGroups, ~shouldChecksum) => {
827
866
  if g.blockItemGroups->Utils.Array.notEmpty {
828
867
  let blocks = await store->BlockStore.materialize(
829
868
  ~blockNumbers=g.blockBlockNumbers,
830
869
  ~masks=g.blockMasks,
870
+ ~shouldChecksum,
831
871
  )
832
872
  g.blockItemGroups->Array.forEachWithIndex((group, i) => {
833
873
  let block = blocks->Array.getUnsafe(i)
@@ -843,27 +883,26 @@ let applyBlockGroups = async (store: BlockStore.t, g: blockGroups) => {
843
883
  let materializeBatchItems = async (cs: t, ~items: array<Internal.item>) => {
844
884
  let (txGroups, blockGroups) = items->groupBatchItems
845
885
  let _ = await Promise.all2((
846
- cs.transactionStore->applyTransactionGroups(txGroups),
847
- cs.blockStore->applyBlockGroups(blockGroups),
886
+ cs.transactionStore->applyTransactionGroups(txGroups, ~shouldChecksum=cs.shouldChecksum),
887
+ cs.blockStore->applyBlockGroups(blockGroups, ~shouldChecksum=cs.shouldChecksum),
848
888
  ))
849
889
  }
850
890
 
851
891
  // Materialise a fetch-response's transactions and blocks onto its items before
852
- // contract-register handlers read them. Transactions come from the response's
853
- // page (`None` when kept inline); blocks come from the chain store, which the
854
- // response's page was already merged into by `registerReorgGuard`.
892
+ // contract-register handlers read them. Both come from the chain stores, which
893
+ // the response's pages were merged into once the reorg guard passed: a source
894
+ // that consults those stores to decide what to fetch leaves out what they
895
+ // already hold, so its page alone does not cover its own items.
855
896
  let materializePageItems = async (
856
897
  ~items: array<Internal.item>,
857
- ~transactionStore: option<TransactionStore.t>,
898
+ ~transactionStore: TransactionStore.t,
858
899
  ~blockStore: BlockStore.t,
900
+ ~shouldChecksum: bool,
859
901
  ) => {
860
902
  let (txGroups, blockGroups) = items->groupBatchItems
861
903
  let _ = await Promise.all2((
862
- switch transactionStore {
863
- | Some(store) => store->applyTransactionGroups(txGroups)
864
- | None => Promise.resolve()
865
- },
866
- blockStore->applyBlockGroups(blockGroups),
904
+ transactionStore->applyTransactionGroups(txGroups, ~shouldChecksum),
905
+ blockStore->applyBlockGroups(blockGroups, ~shouldChecksum),
867
906
  ))
868
907
  }
869
908
 
@@ -874,16 +913,7 @@ let handleQueryResult = (
874
913
  ~newRegistrations,
875
914
  ~latestFetchedBlock: int,
876
915
  ~knownHeight,
877
- ~transactionStore as txPage: option<TransactionStore.t>,
878
916
  ) => {
879
- // Merge this response's transaction page into the chain store in lockstep
880
- // with appending its items to the buffer. Inline sources contribute no page;
881
- // the block page was already merged by `registerReorgGuard`.
882
- switch txPage {
883
- | Some(page) => cs.transactionStore->TransactionStore.merge(page)
884
- | None => ()
885
- }
886
-
887
917
  let fs = switch newRegistrations {
888
918
  | [] => cs.fetchState
889
919
  | _ =>
@@ -964,6 +994,34 @@ let isInReorgThreshold = (cs: t) => cs.isInReorgThreshold
964
994
  // progress has run.
965
995
  let shouldSaveHistory = (cs: t) =>
966
996
  cs.shouldRollbackOnReorg && cs.maxReorgDepth > 0 && cs.isInReorgThreshold
997
+ // What crossing into the recent blocks changed for this chain. Until now it
998
+ // stopped short of the head by its reorg depth, because it kept nothing it
999
+ // could roll back with; crossing lifts both at once. The second half is the
1000
+ // answer to why the indexer starts writing more than it was.
1001
+ // Whether crossing gives this chain anything more to index: the last block it
1002
+ // may fetch afterwards against the last it may fetch now. Read before the
1003
+ // crossing, while the lag being lifted is still the one in place.
1004
+ //
1005
+ // False wherever the lag was never holding this chain back, which is every
1006
+ // configuration that also keeps no history: a chain that isn't rolled back on a
1007
+ // reorg, or has no reorg depth, already fetches as far as it ever will. It is
1008
+ // false too for a chain whose end block sits below the blocks being opened up,
1009
+ // which will never reach one of them.
1010
+ let reorgThresholdLiftsCeiling = (cs: t) => {
1011
+ let ceiling = (~blockLag) => {
1012
+ let head = Pervasives.max(0, cs.fetchState.knownHeight - blockLag)
1013
+ switch cs.fetchState.endBlock {
1014
+ | Some(endBlock) => Pervasives.min(endBlock, head)
1015
+ | None => head
1016
+ }
1017
+ }
1018
+ ceiling(~blockLag=cs.chainConfig.blockLag) > ceiling(~blockLag=cs.fetchState.blockLag)
1019
+ }
1020
+
1021
+ // Only ever said by a chain the crossing lifts, and lifting it takes a reorg
1022
+ // depth to have been held back by, which is the same thing that makes the
1023
+ // history below worth keeping. So there is no second form without it.
1024
+ let reorgThresholdEntryMessage = "Indexing the latest blocks now. These can be reorged, so the indexer starts storing a history of every change to roll back with."
967
1025
 
968
1026
  // Snapshot the chain's metadata fields for staging into the chains table.
969
1027
  let toChainMetadata = (cs: t): InternalTable.Chains.metaFields => {
@@ -1051,6 +1109,12 @@ let toChainBeforeBatch = (cs: t, ~isRealtime): Batch.chainBeforeBatch => {
1051
1109
 
1052
1110
  // Whether the chain's post-batch fetch frontier is ready to cross into the reorg
1053
1111
  // threshold, using the batch's progressed frontier when this chain advanced.
1112
+ // The same question asked of where the chain stands now rather than of where a
1113
+ // batch would leave it. Entering the threshold is what lifts the pre-threshold
1114
+ // lag, so a chain waiting to enter it has fetched as far as it can.
1115
+ let isReadyToEnterReorgThreshold = (cs: t) =>
1116
+ cs.fetchState->FetchState.isReadyToEnterReorgThreshold(~tolerance=cs.reorgThresholdReadyTolerance)
1117
+
1054
1118
  let isReadyToEnterReorgThresholdAfterBatch = (cs: t, ~batch: Batch.t) => {
1055
1119
  let fetchState = switch batch.progressedChainsById->ChainId.Dict.dangerouslyGetNonOption(
1056
1120
  cs.fetchState.chainId,
@@ -1199,6 +1263,7 @@ let markReady = (cs: t, ~readyAt) =>
1199
1263
  let rollbackCommittedProgress = (cs: t, blockNumber) =>
1200
1264
  if blockNumber !== cs.committedProgressBlockNumber {
1201
1265
  cs.committedProgressBlockNumber = blockNumber
1266
+
1202
1267
  // Exact block only: the rolled-back region is about to be refetched, and
1203
1268
  // the next batch re-establishes the time either way.
1204
1269
  cs.committedProgressBlockTime =
@@ -55,7 +55,7 @@ function validateOnEventRegistrations(chainId, registrations) {
55
55
  });
56
56
  }
57
57
 
58
- function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, sourceManager, committedProgressBlockNumber, committedProgressBlockTimeOpt, safeCheckpointTrackingOpt, shouldRollbackOnReorg, maxReorgDepth, isInReorgThresholdOpt, numEventsProcessedOpt, timestampCaughtUpToHeadOrEndblockOpt, isProgressAtHeadOpt, transactionStoreOpt, chainDensityOpt, blockStoreOpt, reorgThresholdReadyToleranceOpt, perChainEntitiesOpt, logger) {
58
+ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, sourceManager, committedProgressBlockNumber, committedProgressBlockTimeOpt, safeCheckpointTrackingOpt, shouldRollbackOnReorg, maxReorgDepth, isInReorgThresholdOpt, numEventsProcessedOpt, timestampCaughtUpToHeadOrEndblockOpt, isProgressAtHeadOpt, transactionStoreOpt, chainDensityOpt, blockStoreOpt, shouldChecksumOpt, reorgThresholdReadyToleranceOpt, perChainEntitiesOpt, logger) {
59
59
  let onEventRegistrations = onEventRegistrationsOpt !== undefined ? onEventRegistrationsOpt : [];
60
60
  let committedProgressBlockTime = committedProgressBlockTimeOpt !== undefined ? Primitive_option.valFromOption(committedProgressBlockTimeOpt) : undefined;
61
61
  let safeCheckpointTracking = safeCheckpointTrackingOpt !== undefined ? Primitive_option.valFromOption(safeCheckpointTrackingOpt) : undefined;
@@ -63,9 +63,10 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
63
63
  let numEventsProcessed = numEventsProcessedOpt !== undefined ? numEventsProcessedOpt : 0;
64
64
  let timestampCaughtUpToHeadOrEndblock = timestampCaughtUpToHeadOrEndblockOpt !== undefined ? Primitive_option.valFromOption(timestampCaughtUpToHeadOrEndblockOpt) : undefined;
65
65
  let isProgressAtHead = isProgressAtHeadOpt !== undefined ? isProgressAtHeadOpt : false;
66
- let transactionStore = transactionStoreOpt !== undefined ? Primitive_option.valFromOption(transactionStoreOpt) : TransactionStore.make("evm", false);
66
+ let transactionStore = transactionStoreOpt !== undefined ? Primitive_option.valFromOption(transactionStoreOpt) : TransactionStore.make("evm");
67
67
  let chainDensity = chainDensityOpt !== undefined ? Primitive_option.valFromOption(chainDensityOpt) : undefined;
68
- let blockStore = blockStoreOpt !== undefined ? Primitive_option.valFromOption(blockStoreOpt) : BlockStore.make("evm", false);
68
+ let blockStore = blockStoreOpt !== undefined ? Primitive_option.valFromOption(blockStoreOpt) : BlockStore.make("evm");
69
+ let shouldChecksum = shouldChecksumOpt !== undefined ? shouldChecksumOpt : false;
69
70
  let reorgThresholdReadyTolerance = reorgThresholdReadyToleranceOpt !== undefined ? reorgThresholdReadyToleranceOpt : 100;
70
71
  let perChainEntities = perChainEntitiesOpt !== undefined ? perChainEntitiesOpt : [];
71
72
  validateOnEventRegistrations(chainConfig.id, onEventRegistrations);
@@ -88,6 +89,7 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
88
89
  shouldRollbackOnReorg: shouldRollbackOnReorg,
89
90
  maxReorgDepth: maxReorgDepth,
90
91
  isInReorgThreshold: isInReorgThreshold,
92
+ shouldChecksum: shouldChecksum,
91
93
  transactionStore: transactionStore,
92
94
  blockStore: blockStore,
93
95
  reorgThresholdReadyTolerance: reorgThresholdReadyTolerance,
@@ -96,6 +98,7 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
96
98
  blockRangeFetchCount: 0,
97
99
  blockRangeFetchedEvents: 0,
98
100
  blockRangeFetchedBlocks: 0,
101
+ reportedFinished: false,
99
102
  reorgCount: 0,
100
103
  reorgDetectedBlock: undefined,
101
104
  rollbackTargetBlock: undefined,
@@ -136,7 +139,7 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
136
139
  }
137
140
  });
138
141
  let lowercaseAddresses = config.lowercaseAddresses;
139
- let addressStore = AddressStore.make(config.ecosystem.name, !lowercaseAddresses, AddressStore.contractsOf(onEventRegistrations, contractMapping));
142
+ let addressStore = AddressStore.make(config.ecosystem.name, AddressStore.contractsOf(onEventRegistrations, contractMapping));
140
143
  let match$1 = config.ecosystem.name;
141
144
  let tmp;
142
145
  switch (match$1) {
@@ -154,18 +157,19 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
154
157
  return reorgCheckpoint;
155
158
  }
156
159
  });
157
- let sources = ChainSources.make(chainConfig, onEventRegistrations, addressStore, lowercaseAddresses);
158
- let blockStore = BlockStore.make(config.ecosystem.name, !lowercaseAddresses);
160
+ let blockStore = BlockStore.make(config.ecosystem.name);
161
+ let transactionStore = TransactionStore.make(config.ecosystem.name);
162
+ let sources = ChainSources.make(chainConfig, onEventRegistrations, addressStore, lowercaseAddresses, blockStore, transactionStore);
159
163
  if (Utils.$$Array.notEmpty(chainReorgCheckpoints)) {
160
164
  let seedPage = BlockStore.fromJs(chainReorgCheckpoints.map(cp => ({
161
165
  blockNumber: cp.block_number,
162
166
  blockHash: cp.block_hash
163
- })), config.ecosystem.name, !lowercaseAddresses);
167
+ })), config.ecosystem.name);
164
168
  blockStore.merge(seedPage, 0, false);
165
169
  }
166
170
  let firstEventBlock$1 = fetchState.firstEventBlock;
167
171
  let chainDensity = firstEventBlock$1 !== undefined && progressBlockNumber > firstEventBlock$1 && numEventsProcessed > 0 ? numEventsProcessed / (progressBlockNumber - firstEventBlock$1 | 0) : undefined;
168
- return make(chainConfig, fetchState, onEventRegistrations, addressStore, SourceManager.make(sources, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), progressBlockNumber, Primitive_option.some(committedProgressBlockTime), Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), config.shouldRollbackOnReorg, maxReorgDepth, isInReorgThreshold, numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, Primitive_option.some(TransactionStore.make(config.ecosystem.name, !lowercaseAddresses)), Primitive_option.some(chainDensity), Primitive_option.some(blockStore), config.reorgThresholdReadyTolerance, EntityTables.perChain(config.userEntities), logger);
172
+ return make(chainConfig, fetchState, onEventRegistrations, addressStore, SourceManager.make(sources, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), progressBlockNumber, Primitive_option.some(committedProgressBlockTime), Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), config.shouldRollbackOnReorg, maxReorgDepth, isInReorgThreshold, numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, Primitive_option.some(transactionStore), Primitive_option.some(chainDensity), Primitive_option.some(blockStore), !lowercaseAddresses, config.reorgThresholdReadyTolerance, EntityTables.perChain(config.userEntities), logger);
169
173
  }
170
174
 
171
175
  function logger(cs) {
@@ -176,6 +180,14 @@ function blockStore(cs) {
176
180
  return cs.blockStore;
177
181
  }
178
182
 
183
+ function transactionStore(cs) {
184
+ return cs.transactionStore;
185
+ }
186
+
187
+ function shouldChecksum(cs) {
188
+ return cs.shouldChecksum;
189
+ }
190
+
179
191
  function entities(cs) {
180
192
  return cs.entities;
181
193
  }
@@ -254,7 +266,7 @@ function knownHeight(cs) {
254
266
  }
255
267
 
256
268
  function contractAddresses(cs, contractName) {
257
- return cs.addressStore.contractAddresses(contractName);
269
+ return cs.addressStore.contractAddresses(contractName, cs.shouldChecksum);
258
270
  }
259
271
 
260
272
  function drainAddressesForWrite(cs, toBlockInclusive, checkpointBlockNumbers) {
@@ -409,6 +421,29 @@ function hasProcessedToEndblock(cs) {
409
421
  }
410
422
  }
411
423
 
424
+ function takeFinished(cs) {
425
+ if (cs.reportedFinished) {
426
+ return;
427
+ }
428
+ let match = cs.fetchState.endBlock;
429
+ let match$1 = hasProcessedToEndblock(cs);
430
+ let match$2 = cs.isProgressAtHead;
431
+ if (match !== undefined && match$1) {
432
+ cs.reportedFinished = true;
433
+ return {
434
+ TAG: "EndBlock",
435
+ _0: match
436
+ };
437
+ }
438
+ if (match$2) {
439
+ cs.reportedFinished = true;
440
+ return {
441
+ TAG: "Backfill",
442
+ _0: cs.committedProgressBlockNumber
443
+ };
444
+ }
445
+ }
446
+
412
447
  function isDurablyCaughtUp(cs) {
413
448
  let fetchState = cs.fetchState;
414
449
  let committedProgressBlockNumber = cs.committedProgressBlockNumber;
@@ -511,12 +546,12 @@ function groupBatchItems(items) {
511
546
  ];
512
547
  }
513
548
 
514
- async function applyTransactionGroups(store, g) {
549
+ async function applyTransactionGroups(store, g, shouldChecksum) {
515
550
  if (!Utils.$$Array.notEmpty(g.payloadGroups)) {
516
551
  return;
517
552
  }
518
553
  if (g.anyTransactionFieldSelected) {
519
- let txs = await store.materialize(g.txBlockNumbers, g.transactionIndices, g.transactionMasks);
554
+ let txs = await store.materialize(g.txBlockNumbers, g.transactionIndices, g.transactionMasks, shouldChecksum);
520
555
  g.payloadGroups.forEach((payloads, i) => {
521
556
  let tx = txs[i];
522
557
  payloads.forEach(payload => {
@@ -537,11 +572,11 @@ async function applyTransactionGroups(store, g) {
537
572
  });
538
573
  }
539
574
 
540
- async function applyBlockGroups(store, g) {
575
+ async function applyBlockGroups(store, g, shouldChecksum) {
541
576
  if (!Utils.$$Array.notEmpty(g.blockItemGroups)) {
542
577
  return;
543
578
  }
544
- let blocks = await store.materialize(g.blockBlockNumbers, g.blockMasks);
579
+ let blocks = await store.materialize(g.blockBlockNumbers, g.blockMasks, shouldChecksum);
545
580
  g.blockItemGroups.forEach((group, i) => {
546
581
  let block = blocks[i];
547
582
  group.forEach(ei => {
@@ -553,23 +588,20 @@ async function applyBlockGroups(store, g) {
553
588
  async function materializeBatchItems(cs, items) {
554
589
  let match = groupBatchItems(items);
555
590
  await Promise.all([
556
- applyTransactionGroups(cs.transactionStore, match[0]),
557
- applyBlockGroups(cs.blockStore, match[1])
591
+ applyTransactionGroups(cs.transactionStore, match[0], cs.shouldChecksum),
592
+ applyBlockGroups(cs.blockStore, match[1], cs.shouldChecksum)
558
593
  ]);
559
594
  }
560
595
 
561
- async function materializePageItems(items, transactionStore, blockStore) {
596
+ async function materializePageItems(items, transactionStore, blockStore, shouldChecksum) {
562
597
  let match = groupBatchItems(items);
563
598
  await Promise.all([
564
- transactionStore !== undefined ? applyTransactionGroups(Primitive_option.valFromOption(transactionStore), match[0]) : Promise.resolve(),
565
- applyBlockGroups(blockStore, match[1])
599
+ applyTransactionGroups(transactionStore, match[0], shouldChecksum),
600
+ applyBlockGroups(blockStore, match[1], shouldChecksum)
566
601
  ]);
567
602
  }
568
603
 
569
- function handleQueryResult(cs, query, newItems, newRegistrations, latestFetchedBlock, knownHeight, txPage) {
570
- if (txPage !== undefined) {
571
- cs.transactionStore.merge(Primitive_option.valFromOption(txPage));
572
- }
604
+ function handleQueryResult(cs, query, newItems, newRegistrations, latestFetchedBlock, knownHeight) {
573
605
  let fs = newRegistrations.length !== 0 ? FetchState.registerDynamicContracts(cs.fetchState, cs.addressStore, Primitive_int.max(knownHeight, latestFetchedBlock), newRegistrations) : cs.fetchState;
574
606
  cs.fetchState = FetchState.updateKnownHeight(FetchState.handleQueryResult(fs, query, latestFetchedBlock, newItems), knownHeight);
575
607
  cs.pendingBudget = Primitive_float.max(0, cs.pendingBudget - query.itemsEst);
@@ -666,6 +698,19 @@ function shouldSaveHistory(cs) {
666
698
  }
667
699
  }
668
700
 
701
+ function reorgThresholdLiftsCeiling(cs) {
702
+ let ceiling = blockLag => {
703
+ let head = Primitive_int.max(0, cs.fetchState.knownHeight - blockLag | 0);
704
+ let endBlock = cs.fetchState.endBlock;
705
+ if (endBlock !== undefined) {
706
+ return Primitive_int.min(endBlock, head);
707
+ } else {
708
+ return head;
709
+ }
710
+ };
711
+ return ceiling(cs.chainConfig.blockLag) > ceiling(cs.fetchState.blockLag);
712
+ }
713
+
669
714
  function toChainMetadata(cs) {
670
715
  return {
671
716
  first_event_block: Stdlib_Null.fromOption(cs.fetchState.firstEventBlock),
@@ -741,6 +786,10 @@ function toChainBeforeBatch(cs, isRealtime) {
741
786
  };
742
787
  }
743
788
 
789
+ function isReadyToEnterReorgThreshold(cs) {
790
+ return FetchState.isReadyToEnterReorgThreshold(cs.reorgThresholdReadyTolerance, cs.fetchState);
791
+ }
792
+
744
793
  function isReadyToEnterReorgThresholdAfterBatch(cs, batch) {
745
794
  let chainAfterBatch = batch.progressedChainsById[cs.fetchState.chainId];
746
795
  let fetchState = chainAfterBatch !== undefined ? chainAfterBatch.fetchState : cs.fetchState;
@@ -878,6 +927,8 @@ function rollback(cs, rolledBackTo) {
878
927
  return rolledBackAddresses$1;
879
928
  }
880
929
 
930
+ let reorgThresholdEntryMessage = "Indexing the latest blocks now. These can be reorged, so the indexer starts storing a history of every change to roll back with.";
931
+
881
932
  export {
882
933
  configStorageRows,
883
934
  seedConfigAddresses,
@@ -885,6 +936,8 @@ export {
885
936
  makeFromDbState,
886
937
  logger,
887
938
  blockStore,
939
+ transactionStore,
940
+ shouldChecksum,
888
941
  entities,
889
942
  resetEntities,
890
943
  sourceManager,
@@ -921,7 +974,11 @@ export {
921
974
  toMetrics,
922
975
  toChainMetadata,
923
976
  toChainBeforeBatch,
977
+ isReadyToEnterReorgThreshold,
924
978
  isReadyToEnterReorgThresholdAfterBatch,
979
+ takeFinished,
980
+ reorgThresholdLiftsCeiling,
981
+ reorgThresholdEntryMessage,
925
982
  hasProcessedToEndblock,
926
983
  isDurablyCaughtUp,
927
984
  getHighestBlockBelowThreshold,
@@ -34,6 +34,7 @@ let make: (
34
34
  ~transactionStore: TransactionStore.t=?,
35
35
  ~chainDensity: option<float>=?,
36
36
  ~blockStore: BlockStore.t=?,
37
+ ~shouldChecksum: bool=?,
37
38
  ~reorgThresholdReadyTolerance: int=?,
38
39
  ~perChainEntities: array<Internal.entityConfig>=?,
39
40
  ~logger: Pino.t,
@@ -54,6 +55,8 @@ let makeFromDbState: (
54
55
  // Accessors.
55
56
  let logger: t => Pino.t
56
57
  let blockStore: t => BlockStore.t
58
+ let transactionStore: t => TransactionStore.t
59
+ let shouldChecksum: t => bool
57
60
  let entities: t => EntityTables.t
58
61
  let resetEntities: (t, ~perChainEntities: array<Internal.entityConfig>) => unit
59
62
  let sourceManager: t => SourceManager.t
@@ -120,9 +123,14 @@ let dispatch: (
120
123
  let toMetrics: t => Metrics.chainMetrics
121
124
  let toChainMetadata: t => InternalTable.Chains.metaFields
122
125
  let toChainBeforeBatch: (t, ~isRealtime: bool) => Batch.chainBeforeBatch
126
+ let isReadyToEnterReorgThreshold: t => bool
123
127
  let isReadyToEnterReorgThresholdAfterBatch: (t, ~batch: Batch.t) => bool
124
128
 
125
129
  // Derived (pure).
130
+ type finished = EndBlock(int) | Backfill(int)
131
+ let takeFinished: t => option<finished>
132
+ let reorgThresholdLiftsCeiling: t => bool
133
+ let reorgThresholdEntryMessage: string
126
134
  let hasProcessedToEndblock: t => bool
127
135
  let isDurablyCaughtUp: t => bool
128
136
  let getHighestBlockBelowThreshold: t => int
@@ -139,14 +147,18 @@ let handleQueryResult: (
139
147
  ~newRegistrations: array<AddressStore.registration>,
140
148
  ~latestFetchedBlock: int,
141
149
  ~knownHeight: int,
142
- ~transactionStore: option<TransactionStore.t>,
143
150
  ) => unit
144
151
  let materializeBatchItems: (t, ~items: array<Internal.item>) => promise<unit>
145
152
  let materializePageItems: (
146
153
  ~items: array<Internal.item>,
147
- ~transactionStore: option<TransactionStore.t>,
154
+ ~transactionStore: TransactionStore.t,
148
155
  ~blockStore: BlockStore.t,
156
+ ~shouldChecksum: bool,
149
157
  ) => promise<unit>
158
+ // Merges the page's blocks into the chain's store as it checks them: the check
159
+ // is the merge's own conflict report, so a page that passes is already applied.
160
+ // Its transactions are a separate page, merged by the caller once this returns
161
+ // no reorg.
150
162
  let registerReorgGuard: (
151
163
  t,
152
164
  ~blockStore: BlockStore.t,
package/src/Config.res CHANGED
@@ -625,6 +625,16 @@ let getChain = (config, ~chainId) =>
625
625
  "No chain with id " ++ chainId->ChainId.toString ++ " found in config.yaml",
626
626
  )
627
627
 
628
+ // Whether every entity belongs to exactly one chain. Read off the checkpoint
629
+ // sequence rather than the entities again: a chain gets a counter of its own
630
+ // only when no other chain can reach its rows, which is the same fact and the
631
+ // one that makes splitting a run across processes safe.
632
+ let isPerChain = (config: t) =>
633
+ switch config.checkpointSequence {
634
+ | PerChain => true
635
+ | SharedAcrossChains => false
636
+ }
637
+
628
638
  // Narrows a config to the chains one `envio start --chain` process drives.
629
639
  // `contractMapping` is deliberately left whole: its ids are what the migration
630
640
  // that created the schema stored, and one rebuilt from a subset would hand the
@@ -1221,6 +1231,24 @@ let prime = (json: JSON.t): unit => {
1221
1231
  cached := None
1222
1232
  }
1223
1233
 
1234
+ // What the command decided rather than the project's files: which chains this
1235
+ // process drives, and whether the run is a dev run. A worker parses the same
1236
+ // files its supervisor did, so these are the only two it cannot arrive at on
1237
+ // its own.
1238
+ let withCommandFields = (json: JSON.t, ~chainIds, ~isDev) =>
1239
+ switch json->JSON.Decode.object {
1240
+ | Some(fields) => {
1241
+ let narrowed = fields->Dict.copy
1242
+ narrowed->Dict.set(
1243
+ "isolatedChains",
1244
+ chainIds->S.reverseConvertToJsonOrThrow(S.array(ChainId.schema)),
1245
+ )
1246
+ narrowed->Dict.set("isDev", JSON.Encode.bool(isDev))
1247
+ JSON.Object(narrowed)
1248
+ }
1249
+ | None => JsError.throwWithMessage("Invalid indexer config: not an object")
1250
+ }
1251
+
1224
1252
  let getPublicConfigJson = () =>
1225
1253
  switch primedJson.contents {
1226
1254
  | Some(json) => json
@@ -1266,6 +1294,10 @@ let stripSensitiveData = (json: JSON.t): JSON.t => {
1266
1294
  cloned
1267
1295
  }
1268
1296
 
1297
+ // What the storage layer records as the config this schema was built from,
1298
+ // and checks a resuming run against.
1299
+ let envioInfo = () => getPublicConfigJson()->stripSensitiveData
1300
+
1269
1301
  // Postgres jsonb doesn't preserve key order, so canonicalize with sorted
1270
1302
  // keys before string-comparing.
1271
1303
  let rec canonicalJson = (json: JSON.t): JSON.t =>