envio 3.11.0 → 3.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/index.d.ts +3 -0
  2. package/package.json +6 -6
  3. package/src/Batch.res +111 -78
  4. package/src/Batch.res.mjs +53 -46
  5. package/src/BatchProcessing.res +1 -11
  6. package/src/BatchProcessing.res.mjs +1 -4
  7. package/src/ChainFetching.res +7 -3
  8. package/src/ChainFetching.res.mjs +1 -1
  9. package/src/ChainState.res +82 -20
  10. package/src/ChainState.res.mjs +51 -13
  11. package/src/ChainState.resi +6 -1
  12. package/src/Config.res +58 -37
  13. package/src/Config.res.mjs +63 -53
  14. package/src/CrossChainState.res +50 -53
  15. package/src/CrossChainState.res.mjs +25 -23
  16. package/src/CrossChainState.resi +6 -13
  17. package/src/EffectState.res +13 -4
  18. package/src/EffectState.resi +7 -1
  19. package/src/EventProcessing.res +6 -6
  20. package/src/EventProcessing.res.mjs +6 -6
  21. package/src/FetchState.res +65 -46
  22. package/src/FetchState.res.mjs +68 -36
  23. package/src/FinalizeBackfill.res +16 -12
  24. package/src/FinalizeBackfill.res.mjs +3 -3
  25. package/src/HistoryPolicy.res +46 -0
  26. package/src/HistoryPolicy.res.mjs +50 -0
  27. package/src/InMemoryStore.res +30 -32
  28. package/src/InMemoryStore.res.mjs +33 -28
  29. package/src/InMemoryTable.res +155 -67
  30. package/src/InMemoryTable.res.mjs +151 -60
  31. package/src/IndexerState.res +66 -45
  32. package/src/IndexerState.res.mjs +57 -29
  33. package/src/IndexerState.resi +8 -13
  34. package/src/Internal.res +4 -2
  35. package/src/LoadLayer.res +68 -38
  36. package/src/LoadLayer.res.mjs +50 -64
  37. package/src/LoadLayer.resi +1 -1
  38. package/src/Main.res +25 -24
  39. package/src/Main.res.mjs +24 -25
  40. package/src/Metrics.res +49 -1
  41. package/src/Metrics.res.mjs +26 -3
  42. package/src/Persistence.res +55 -16
  43. package/src/Persistence.res.mjs +18 -3
  44. package/src/PgStorage.res +286 -180
  45. package/src/PgStorage.res.mjs +209 -122
  46. package/src/PruneStaleHistory.res +9 -15
  47. package/src/PruneStaleHistory.res.mjs +11 -20
  48. package/src/Rollback.res +9 -14
  49. package/src/Rollback.res.mjs +5 -3
  50. package/src/Sink.res +7 -5
  51. package/src/Sink.res.mjs +4 -4
  52. package/src/Staging.res +298 -0
  53. package/src/Staging.res.mjs +257 -0
  54. package/src/TestIndexer.res +14 -30
  55. package/src/TestIndexer.res.mjs +11 -9
  56. package/src/UserContext.res +20 -36
  57. package/src/UserContext.res.mjs +6 -9
  58. package/src/Utils.res +10 -4
  59. package/src/Utils.res.mjs +16 -16
  60. package/src/Writing.res +34 -20
  61. package/src/Writing.res.mjs +15 -20
  62. package/src/bindings/ClickHouse.res +41 -34
  63. package/src/bindings/ClickHouse.res.mjs +36 -30
  64. package/src/bindings/ClickHouseSink.res +45 -186
  65. package/src/bindings/ClickHouseSink.res.mjs +21 -174
  66. package/src/db/CheckpointSequence.res +109 -0
  67. package/src/db/CheckpointSequence.res.mjs +99 -0
  68. package/src/db/EntityFilter.res +487 -275
  69. package/src/db/EntityFilter.res.mjs +557 -309
  70. package/src/db/EntityHistory.res +6 -5
  71. package/src/db/EntityHistory.res.mjs +5 -5
  72. package/src/db/Frontier.res +86 -0
  73. package/src/db/Frontier.res.mjs +126 -0
  74. package/src/db/InternalTable.res +145 -27
  75. package/src/db/InternalTable.res.mjs +115 -29
  76. package/src/db/RollbackFloors.res +34 -41
  77. package/src/db/RollbackFloors.res.mjs +27 -71
  78. package/src/db/Table.res +21 -6
  79. package/src/db/Table.res.mjs +13 -4
  80. package/src/sources/BlockStore.res +8 -0
  81. package/src/sources/EvmHyperSyncSource.res +5 -1
  82. package/src/sources/EvmHyperSyncSource.res.mjs +4 -3
  83. package/src/sources/FuelHyperSyncSource.res +1 -0
  84. package/src/sources/FuelHyperSyncSource.res.mjs +1 -1
  85. package/src/sources/HyperSync.res +6 -0
  86. package/src/sources/HyperSync.res.mjs +5 -2
  87. package/src/sources/HyperSync.resi +2 -0
  88. package/src/sources/HyperSyncClient.res +6 -0
  89. package/src/sources/HyperSyncSSE.res +1 -1
  90. package/src/sources/HyperSyncSSE.res.mjs +4 -10
  91. package/src/sources/RequestStat.res +8 -1
  92. package/src/sources/RpcSource.res +1 -0
  93. package/src/sources/RpcSource.res.mjs +1 -1
  94. package/src/sources/SimulateSource.res +1 -0
  95. package/src/sources/SimulateSource.res.mjs +1 -1
  96. package/src/sources/Source.res +11 -3
  97. package/src/sources/SourceManager.res +34 -13
  98. package/src/sources/SourceManager.res.mjs +26 -9
  99. package/src/sources/SourceManager.resi +2 -0
  100. package/src/sources/SvmHyperSyncClient.res +5 -0
  101. package/src/sources/SvmHyperSyncSource.res +3 -0
  102. package/src/sources/SvmHyperSyncSource.res.mjs +4 -2
  103. package/src/tui/Tui.res +56 -143
  104. package/src/tui/Tui.res.mjs +70 -166
  105. package/src/tui/components/TuiData.res +61 -16
  106. package/src/tui/components/TuiData.res.mjs +53 -15
  107. package/svm.schema.json +0 -7
  108. package/src/db/CheckpointBounds.res +0 -53
  109. package/src/db/CheckpointBounds.res.mjs +0 -44
@@ -69,17 +69,7 @@ and processNextBatch = async (state: IndexerState.t, ~scheduleFetch): unit => {
69
69
  let isInReorgThresholdBeforeUpdate = state->IndexerState.isInReorgThreshold
70
70
  let isRealtimeBeforeUpdate = state->IndexerState.isRealtime
71
71
 
72
- let isRollbackBatch = switch state->IndexerState.rollbackState {
73
- | RollbackReady(_) => true
74
- | _ => false
75
- }
76
-
77
- let batch =
78
- state->IndexerState.createBatch(
79
- ~processedCheckpointId=state->IndexerState.processedCheckpointId,
80
- ~batchSizeTarget=(state->IndexerState.config).batchSize,
81
- ~isRollback=isRollbackBatch,
82
- )
72
+ let batch = state->IndexerState.createBatch(~batchSizeTarget=(state->IndexerState.config).batchSize)
83
73
 
84
74
  let progressedChainsById = batch.progressedChainsById
85
75
 
@@ -23,10 +23,7 @@ function yieldTick() {
23
23
  async function processNextBatch(state, scheduleFetch) {
24
24
  let isInReorgThresholdBeforeUpdate = IndexerState.isInReorgThreshold(state);
25
25
  let isRealtimeBeforeUpdate = IndexerState.isRealtime(state);
26
- let match = IndexerState.rollbackState(state);
27
- let isRollbackBatch;
28
- isRollbackBatch = typeof match !== "object" ? false : match.TAG === "RollbackReady";
29
- let batch = IndexerState.createBatch(state, IndexerState.processedCheckpointId(state), IndexerState.config(state).batchSize, isRollbackBatch);
26
+ let batch = IndexerState.createBatch(state, IndexerState.config(state).batchSize);
30
27
  let progressedChainsById = batch.progressedChainsById;
31
28
  let isBelowReorgThreshold = !isInReorgThresholdBeforeUpdate && IndexerState.config(state).shouldRollbackOnReorg;
32
29
  let shouldEnterReorgThreshold = isBelowReorgThreshold && Object.values(IndexerState.chainStates(state)).every(cs => ChainState.isReadyToEnterReorgThresholdAfterBatch(cs, batch));
@@ -326,9 +326,13 @@ let fetchChain = async (
326
326
  let isRealtime = state->IndexerState.isRealtime
327
327
  let sourceManager = chainState->ChainState.sourceManager
328
328
 
329
- // Only affects the WaitingForNewBlock branch of dispatch, where
330
- // there's nothing to fetch. During backfill any such chain is idle.
331
- let reducedPolling = !isRealtime
329
+ // Only affects the WaitingForNewBlock branch of dispatch, where there's
330
+ // nothing to fetch. The line is whether any height is known at all, not
331
+ // whether the head is fetchable yet: a chain held back by its lag is still
332
+ // tracking a head it has, and asking faster would buy nothing. A chain with
333
+ // no height has nothing to show for itself until the first one lands, so it
334
+ // polls at the source's own cadence and earns the normal stall window.
335
+ let reducedPolling = !isRealtime && chainState->ChainState.knownHeight > 0
332
336
 
333
337
  // Owns its error boundary: launch doesn't catch, so any failure here (the
334
338
  // query, response handling, or dispatch itself) must stop the indexer.
@@ -180,7 +180,7 @@ async function fetchChain(state, chainId, action, stateId, scheduleFetch, schedu
180
180
  }
181
181
  let isRealtime = IndexerState.isRealtime(state);
182
182
  let sourceManager = ChainState.sourceManager(chainState);
183
- let reducedPolling = !isRealtime;
183
+ let reducedPolling = !isRealtime && ChainState.knownHeight(chainState) > 0;
184
184
  try {
185
185
  return await ChainState.dispatch(chainState, async query => {
186
186
  try {
@@ -41,6 +41,8 @@ type t = {
41
41
  // detected reorg either rolls back or is only logged.
42
42
  shouldRollbackOnReorg: bool,
43
43
  maxReorgDepth: int,
44
+ // One-way: past it, everything the chain writes can still be rolled back.
45
+ mutable isInReorgThreshold: bool,
44
46
  // Holds this chain's transactions (kept in Rust) keyed by (blockNumber,
45
47
  // transactionIndex). Fetch responses merge their page in; entries are pruned
46
48
  // as the chain progresses and dropped above the target on rollback.
@@ -60,6 +62,9 @@ type t = {
60
62
  mutable reorgDetectedBlock: option<int>,
61
63
  mutable rollbackTargetBlock: option<int>,
62
64
  mutable progressLatencyMs: option<int>,
65
+ // Timestamp of the committed progress block itself. See
66
+ // `Batch.chainAfterBatch.progressBlockTime`.
67
+ mutable committedProgressBlockTime: option<int>,
63
68
  }
64
69
 
65
70
  // The chain's config-declared addresses as storage rows: what a clean run
@@ -123,9 +128,11 @@ let make = (
123
128
  ~addressStore: AddressStore.t,
124
129
  ~sourceManager: SourceManager.t,
125
130
  ~committedProgressBlockNumber: int,
131
+ ~committedProgressBlockTime=None,
126
132
  ~safeCheckpointTracking=None,
127
133
  ~shouldRollbackOnReorg,
128
134
  ~maxReorgDepth,
135
+ ~isInReorgThreshold=false,
129
136
  ~numEventsProcessed=0.,
130
137
  ~timestampCaughtUpToHeadOrEndblock=None,
131
138
  ~isProgressAtHead=false,
@@ -155,6 +162,7 @@ let make = (
155
162
  safeCheckpointTracking,
156
163
  shouldRollbackOnReorg,
157
164
  maxReorgDepth,
165
+ isInReorgThreshold,
158
166
  transactionStore,
159
167
  blockStore,
160
168
  reorgThresholdReadyTolerance,
@@ -167,6 +175,7 @@ let make = (
167
175
  reorgDetectedBlock: None,
168
176
  rollbackTargetBlock: None,
169
177
  progressLatencyMs: None,
178
+ committedProgressBlockTime,
170
179
  }
171
180
  }
172
181
 
@@ -180,6 +189,7 @@ let makeInternal = (
180
189
  ~endBlock,
181
190
  ~firstEventBlock=None,
182
191
  ~progressBlockNumber,
192
+ ~committedProgressBlockTime=None,
183
193
  ~config: Config.t,
184
194
  ~registrationsByChainId: HandlerRegister.registrationsByChainId,
185
195
  ~logger,
@@ -308,12 +318,14 @@ let makeInternal = (
308
318
  ~sourceManager=SourceManager.make(~sources, ~isRealtime, ~reducedPollingInterval?),
309
319
  ~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
310
320
  ~maxReorgDepth,
321
+ ~isInReorgThreshold,
311
322
  ~safeCheckpointTracking=SafeCheckpointTracking.make(
312
323
  ~maxReorgDepth,
313
324
  ~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
314
325
  ~chainReorgCheckpoints,
315
326
  ),
316
327
  ~committedProgressBlockNumber=progressBlockNumber,
328
+ ~committedProgressBlockTime,
317
329
  ~perChainEntities=config.userEntities->EntityTables.perChain,
318
330
  ~timestampCaughtUpToHeadOrEndblock,
319
331
  ~numEventsProcessed,
@@ -363,6 +375,10 @@ let makeFromDbState = (
363
375
  ~maxReorgDepth=resumedChainState.maxReorgDepth,
364
376
  ~firstEventBlock=resumedChainState.firstEventBlockNumber,
365
377
  ~progressBlockNumber,
378
+ // A block's time never changes, so the stored one stays true for the block
379
+ // progress is at - and keeps the metric reporting from the first scrape
380
+ // after a restart, rather than only once a batch commits.
381
+ ~committedProgressBlockTime=resumedChainState.progressBlockTime,
366
382
  ~timestampCaughtUpToHeadOrEndblock=resumedChainState.timestampCaughtUpToHeadOrEndblock,
367
383
  ~numEventsProcessed=resumedChainState.numEventsProcessed,
368
384
  ~logger,
@@ -402,6 +418,7 @@ let getLatestValidScannedBlock = (cs: t, ~blockStore: BlockStore.t, ~blockNumber
402
418
  let safeCheckpointTracking = (cs: t) => cs.safeCheckpointTracking
403
419
  let isProgressAtHead = (cs: t) => cs.isProgressAtHead
404
420
  let committedProgressBlockNumber = (cs: t) => cs.committedProgressBlockNumber
421
+ let committedProgressBlockTime = (cs: t) => cs.committedProgressBlockTime
405
422
  let numEventsProcessed = (cs: t) => cs.numEventsProcessed
406
423
  let pendingBudget = (cs: t) => cs.pendingBudget
407
424
  let timestampCaughtUpToHeadOrEndblock = (cs: t) => cs.timestampCaughtUpToHeadOrEndblock
@@ -640,15 +657,21 @@ let hasProcessedToEndblock = (cs: t) => {
640
657
  // head has run away from it while the indexer was down.
641
658
  let isDurablyCaughtUp = (cs: t) => {
642
659
  let {committedProgressBlockNumber, fetchState} = cs
643
- switch fetchState.endBlock {
644
- | Some(endBlock) => committedProgressBlockNumber >= endBlock
645
- | None =>
646
- // The configured lag, not the fetch state's: pre-threshold that one also
647
- // carries maxReorgDepth, which would read a chain a whole reorg depth behind
648
- // the head as caught up.
660
+ let atEndBlock =
661
+ fetchState.endBlock->Option.mapOr(false, endBlock => committedProgressBlockNumber >= endBlock)
662
+ // Either one, like `isFetchingAtHead`: an `end_block` above the head is never
663
+ // reached, and testing only for it would leave such a chain reading as behind
664
+ // however long it sits at the head.
665
+ //
666
+ // The configured lag, not the fetch state's: pre-threshold that one also
667
+ // carries maxReorgDepth, which would read a chain a whole reorg depth behind
668
+ // the head as caught up. Clamped at zero: the -1 a run that has processed
669
+ // nothing carries would otherwise clear a chain younger than its own lag.
670
+ let atHead =
649
671
  fetchState.knownHeight > 0 &&
650
- committedProgressBlockNumber >= fetchState.knownHeight - cs.chainConfig.blockLag
651
- }
672
+ committedProgressBlockNumber >=
673
+ Pervasives.max(0, fetchState.knownHeight - cs.chainConfig.blockLag)
674
+ atEndBlock || atHead
652
675
  }
653
676
 
654
677
  let getHighestBlockBelowThreshold = (cs: t): int => {
@@ -929,8 +952,18 @@ let setEndBlockToFirstEvent = (cs: t, ~blockNumber) =>
929
952
  }
930
953
 
931
954
  // Shrink the fetch buffer by the configured blockLag on entering the reorg threshold.
932
- let enterReorgThreshold = (cs: t) =>
955
+ let enterReorgThreshold = (cs: t) => {
956
+ cs.isInReorgThreshold = true
933
957
  cs.fetchState = cs.fetchState->FetchState.updateInternal(~blockLag=cs.chainConfig.blockLag)
958
+ }
959
+
960
+ let isInReorgThreshold = (cs: t) => cs.isInReorgThreshold
961
+
962
+ // Whether the chain's writes need history: only what a rollback could still
963
+ // reach. A chain with no reorg depth is never rolled back, however far its
964
+ // progress has run.
965
+ let shouldSaveHistory = (cs: t) =>
966
+ cs.shouldRollbackOnReorg && cs.maxReorgDepth > 0 && cs.isInReorgThreshold
934
967
 
935
968
  // Snapshot the chain's metadata fields for staging into the chains table.
936
969
  let toChainMetadata = (cs: t): InternalTable.Chains.metaFields => {
@@ -964,6 +997,7 @@ let toMetrics = (cs: t): Metrics.chainMetrics => {
964
997
  sourceBlockNumber: cs.fetchState.knownHeight,
965
998
  progressBlockNumber: cs.committedProgressBlockNumber,
966
999
  progressLatencyMs: cs.progressLatencyMs,
1000
+ progressBlockTime: cs.committedProgressBlockTime,
967
1001
  concurrency: cs.sourceManager->SourceManager.inFlightCount,
968
1002
  partitionsCount: cs.fetchState->FetchState.partitionsCount,
969
1003
  bufferSize: cs.fetchState->FetchState.bufferSize,
@@ -979,12 +1013,15 @@ let toMetrics = (cs: t): Metrics.chainMetrics => {
979
1013
  reorgCount: cs.reorgCount,
980
1014
  reorgDetectedBlock: cs.reorgDetectedBlock,
981
1015
  rollbackTargetBlock: cs.rollbackTargetBlock,
1016
+ rateLimitTimeMs: cs.sourceManager->SourceManager.getRateLimitTimeMs,
1017
+ rateLimitResetInMs: cs.sourceManager->SourceManager.getRateLimitResetInMs,
982
1018
  }
983
1019
 
984
- // Snapshot the inputs a batch build needs from this chain, including an
985
- // immutable copy of the scanned in-threshold block hashes so the batch never
986
- // reads the live store mid-build.
987
- let toChainBeforeBatch = (cs: t): Batch.chainBeforeBatch => {
1020
+ // Snapshot the inputs a batch build needs from this chain. The scanned
1021
+ // in-threshold block hashes are copied up front because the build reuses the
1022
+ // whole set; the block time comes as a lookup instead, since the key it needs -
1023
+ // the batch's own progress block - isn't known until the build computes it.
1024
+ let toChainBeforeBatch = (cs: t, ~isRealtime): Batch.chainBeforeBatch => {
988
1025
  let {blockNumbers, hashes} =
989
1026
  cs.blockStore->BlockStore.getHashes(
990
1027
  ~fromBlock=Pervasives.max(cs.fetchState.knownHeight - cs.maxReorgDepth, 0),
@@ -1000,6 +1037,13 @@ let toChainBeforeBatch = (cs: t): Batch.chainBeforeBatch => {
1000
1037
  totalEventsProcessed: cs.numEventsProcessed,
1001
1038
  sourceBlockNumber: cs.fetchState.knownHeight,
1002
1039
  scannedHashes: {blockNumbers, hashByBlockNumber},
1040
+ // A slot that produced no block only counts as skipped where the query
1041
+ // covered every slot in its range, which is what a chain at the head asks
1042
+ // for.
1043
+ blockTimeAt: blockNumber =>
1044
+ cs.blockStore
1045
+ ->BlockStore.getTimestamp(blockNumber, ~allowSkippedSlot=isRealtime)
1046
+ ->Null.toOption,
1003
1047
  shouldRollbackOnReorg: cs.shouldRollbackOnReorg,
1004
1048
  chainConfig: cs.chainConfig,
1005
1049
  }
@@ -1099,6 +1143,7 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) =
1099
1143
  }
1100
1144
 
1101
1145
  cs.committedProgressBlockNumber = chainAfterBatch.progressBlockNumber
1146
+ cs.committedProgressBlockTime = chainAfterBatch.progressBlockTime
1102
1147
 
1103
1148
  // Normally already set by advanceAfterBatch at batch creation; catch up
1104
1149
  // here for paths that commit progress without it.
@@ -1135,16 +1180,33 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) =
1135
1180
  }
1136
1181
 
1137
1182
  // Mark the chain caught up to head/endblock. Called by CrossChainState only once
1138
- // every chain in the indexer is caught up and the deferred schema indexes are
1139
- // committed, so no chain flips to ready while another is still backfilling or
1140
- // while an index the schema promises is still missing. `readyAt` is the
1141
- // timestamp already committed to `envio_chains.ready_at` in that same
1142
- // transaction. Sticky: a chain stays ready once set.
1183
+ // every chain this process drives is caught up and the indexes the schema
1184
+ // promises them are committed, so no chain flips to ready while an index it
1185
+ // promised is still missing. `readyAt` is the timestamp already committed to
1186
+ // `envio_chains.ready_at` in that same transaction. Sticky: a chain stays ready
1187
+ // once set.
1143
1188
  let markReady = (cs: t, ~readyAt) =>
1144
1189
  if !(cs->isReady) {
1145
1190
  cs.timestampCaughtUpToHeadOrEndblock = Some(readyAt)
1146
1191
  }
1147
1192
 
1193
+ // Rewind progress to a reorg target. Moving it means the timestamp now belongs
1194
+ // to an orphaned block, so it's re-read for the block progress actually lands
1195
+ // on - normally `None`, since the store keeps only hashes below the last
1196
+ // committed progress, until the re-fetch commits a new progress block. A target
1197
+ // at or above the progress block leaves both alone: that block was never
1198
+ // orphaned, and re-reading its long-pruned header would drop a valid timestamp.
1199
+ let rollbackCommittedProgress = (cs: t, blockNumber) =>
1200
+ if blockNumber !== cs.committedProgressBlockNumber {
1201
+ cs.committedProgressBlockNumber = blockNumber
1202
+ // Exact block only: the rolled-back region is about to be refetched, and
1203
+ // the next batch re-establishes the time either way.
1204
+ cs.committedProgressBlockTime =
1205
+ cs.blockStore
1206
+ ->BlockStore.getTimestamp(blockNumber, ~allowSkippedSlot=false)
1207
+ ->Null.toOption
1208
+ }
1209
+
1148
1210
  type progressDiff = {blockNumber: int, eventsProcessed: float}
1149
1211
 
1150
1212
  type rolledBackTo =
@@ -1192,7 +1254,7 @@ let rollback = (cs: t, ~rolledBackTo: rolledBackTo): array<AddressRows.key> => {
1192
1254
  let rolledBackAddresses = rollbackTo(newProgressBlockNumber)
1193
1255
  cs.transactionStore->TransactionStore.rollback(newProgressBlockNumber)
1194
1256
  cs.blockStore->BlockStore.rollback(newProgressBlockNumber)
1195
- cs.committedProgressBlockNumber = newProgressBlockNumber
1257
+ cs->rollbackCommittedProgress(newProgressBlockNumber)
1196
1258
  cs.processingBlockNumber = newProgressBlockNumber
1197
1259
  cs.numEventsProcessed = newTotalEventsProcessed
1198
1260
  rolledBackAddresses
@@ -1202,7 +1264,7 @@ let rollback = (cs: t, ~rolledBackTo: rolledBackTo): array<AddressRows.key> => {
1202
1264
  let rolledBackAddresses = rollbackTo(forkBlock)
1203
1265
  cs.transactionStore->TransactionStore.rollback(forkBlock)
1204
1266
  cs.blockStore->BlockStore.rollback(forkBlock)
1205
- cs.committedProgressBlockNumber = Pervasives.min(cs.committedProgressBlockNumber, forkBlock)
1267
+ cs->rollbackCommittedProgress(Pervasives.min(cs.committedProgressBlockNumber, forkBlock))
1206
1268
  cs.processingBlockNumber = Pervasives.min(cs.processingBlockNumber, forkBlock)
1207
1269
  rolledBackAddresses
1208
1270
  | Untouched => []
@@ -55,9 +55,11 @@ function validateOnEventRegistrations(chainId, registrations) {
55
55
  });
56
56
  }
57
57
 
58
- function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, sourceManager, committedProgressBlockNumber, safeCheckpointTrackingOpt, shouldRollbackOnReorg, maxReorgDepth, 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, reorgThresholdReadyToleranceOpt, perChainEntitiesOpt, logger) {
59
59
  let onEventRegistrations = onEventRegistrationsOpt !== undefined ? onEventRegistrationsOpt : [];
60
+ let committedProgressBlockTime = committedProgressBlockTimeOpt !== undefined ? Primitive_option.valFromOption(committedProgressBlockTimeOpt) : undefined;
60
61
  let safeCheckpointTracking = safeCheckpointTrackingOpt !== undefined ? Primitive_option.valFromOption(safeCheckpointTrackingOpt) : undefined;
62
+ let isInReorgThreshold = isInReorgThresholdOpt !== undefined ? isInReorgThresholdOpt : false;
61
63
  let numEventsProcessed = numEventsProcessedOpt !== undefined ? numEventsProcessedOpt : 0;
62
64
  let timestampCaughtUpToHeadOrEndblock = timestampCaughtUpToHeadOrEndblockOpt !== undefined ? Primitive_option.valFromOption(timestampCaughtUpToHeadOrEndblockOpt) : undefined;
63
65
  let isProgressAtHead = isProgressAtHeadOpt !== undefined ? isProgressAtHeadOpt : false;
@@ -85,6 +87,7 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
85
87
  safeCheckpointTracking: safeCheckpointTracking,
86
88
  shouldRollbackOnReorg: shouldRollbackOnReorg,
87
89
  maxReorgDepth: maxReorgDepth,
90
+ isInReorgThreshold: isInReorgThreshold,
88
91
  transactionStore: transactionStore,
89
92
  blockStore: blockStore,
90
93
  reorgThresholdReadyTolerance: reorgThresholdReadyTolerance,
@@ -96,7 +99,8 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
96
99
  reorgCount: 0,
97
100
  reorgDetectedBlock: undefined,
98
101
  rollbackTargetBlock: undefined,
99
- progressLatencyMs: undefined
102
+ progressLatencyMs: undefined,
103
+ committedProgressBlockTime: committedProgressBlockTime
100
104
  };
101
105
  }
102
106
 
@@ -110,12 +114,14 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
110
114
  let startBlock = resumedChainState.startBlock;
111
115
  let endBlock = resumedChainState.endBlock;
112
116
  let firstEventBlockOpt = Primitive_option.some(resumedChainState.firstEventBlockNumber);
117
+ let committedProgressBlockTimeOpt = Primitive_option.some(resumedChainState.progressBlockTime);
113
118
  let timestampCaughtUpToHeadOrEndblock = resumedChainState.timestampCaughtUpToHeadOrEndblock;
114
119
  let numEventsProcessed = resumedChainState.numEventsProcessed;
115
120
  let maxReorgDepth = resumedChainState.maxReorgDepth;
116
121
  let knownHeightOpt = resumedChainState.sourceBlockNumber;
117
122
  let isResumedOpt = true;
118
123
  let firstEventBlock = firstEventBlockOpt !== undefined ? Primitive_option.valFromOption(firstEventBlockOpt) : undefined;
124
+ let committedProgressBlockTime = committedProgressBlockTimeOpt !== undefined ? Primitive_option.valFromOption(committedProgressBlockTimeOpt) : undefined;
119
125
  let knownHeight = knownHeightOpt !== undefined ? knownHeightOpt : 0;
120
126
  let isResumed = isResumedOpt !== undefined ? isResumedOpt : false;
121
127
  let match = Stdlib_Option.getOr(registrationsByChainId[ChainId.toString(chainConfig.id)], {
@@ -159,7 +165,7 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
159
165
  }
160
166
  let firstEventBlock$1 = fetchState.firstEventBlock;
161
167
  let chainDensity = firstEventBlock$1 !== undefined && progressBlockNumber > firstEventBlock$1 && numEventsProcessed > 0 ? numEventsProcessed / (progressBlockNumber - firstEventBlock$1 | 0) : undefined;
162
- return make(chainConfig, fetchState, onEventRegistrations, addressStore, SourceManager.make(sources, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), progressBlockNumber, Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), config.shouldRollbackOnReorg, maxReorgDepth, 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);
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);
163
169
  }
164
170
 
165
171
  function logger(cs) {
@@ -210,6 +216,10 @@ function committedProgressBlockNumber(cs) {
210
216
  return cs.committedProgressBlockNumber;
211
217
  }
212
218
 
219
+ function committedProgressBlockTime(cs) {
220
+ return cs.committedProgressBlockTime;
221
+ }
222
+
213
223
  function numEventsProcessed(cs) {
214
224
  return cs.numEventsProcessed;
215
225
  }
@@ -402,13 +412,12 @@ function hasProcessedToEndblock(cs) {
402
412
  function isDurablyCaughtUp(cs) {
403
413
  let fetchState = cs.fetchState;
404
414
  let committedProgressBlockNumber = cs.committedProgressBlockNumber;
405
- let endBlock = fetchState.endBlock;
406
- if (endBlock !== undefined) {
407
- return committedProgressBlockNumber >= endBlock;
408
- } else if (fetchState.knownHeight > 0) {
409
- return committedProgressBlockNumber >= (fetchState.knownHeight - cs.chainConfig.blockLag | 0);
415
+ let atEndBlock = Stdlib_Option.mapOr(fetchState.endBlock, false, endBlock => committedProgressBlockNumber >= endBlock);
416
+ let atHead = fetchState.knownHeight > 0 && committedProgressBlockNumber >= Primitive_int.max(0, fetchState.knownHeight - cs.chainConfig.blockLag | 0);
417
+ if (atEndBlock) {
418
+ return true;
410
419
  } else {
411
- return false;
420
+ return atHead;
412
421
  }
413
422
  }
414
423
 
@@ -641,9 +650,22 @@ function setEndBlockToFirstEvent(cs, blockNumber) {
641
650
  }
642
651
 
643
652
  function enterReorgThreshold(cs) {
653
+ cs.isInReorgThreshold = true;
644
654
  cs.fetchState = FetchState.updateInternal(cs.fetchState, undefined, undefined, undefined, cs.chainConfig.blockLag, undefined);
645
655
  }
646
656
 
657
+ function isInReorgThreshold(cs) {
658
+ return cs.isInReorgThreshold;
659
+ }
660
+
661
+ function shouldSaveHistory(cs) {
662
+ if (cs.shouldRollbackOnReorg && cs.maxReorgDepth > 0) {
663
+ return cs.isInReorgThreshold;
664
+ } else {
665
+ return false;
666
+ }
667
+ }
668
+
647
669
  function toChainMetadata(cs) {
648
670
  return {
649
671
  first_event_block: Stdlib_Null.fromOption(cs.fetchState.firstEventBlock),
@@ -675,6 +697,7 @@ function toMetrics(cs) {
675
697
  sourceBlockNumber: cs.fetchState.knownHeight,
676
698
  progressBlockNumber: cs.committedProgressBlockNumber,
677
699
  progressLatencyMs: cs.progressLatencyMs,
700
+ progressBlockTime: cs.committedProgressBlockTime,
678
701
  concurrency: SourceManager.inFlightCount(cs.sourceManager),
679
702
  partitionsCount: FetchState.partitionsCount(cs.fetchState),
680
703
  bufferSize: FetchState.bufferSize(cs.fetchState),
@@ -689,11 +712,13 @@ function toMetrics(cs) {
689
712
  blockRangeFetchedBlocks: cs.blockRangeFetchedBlocks,
690
713
  reorgCount: cs.reorgCount,
691
714
  reorgDetectedBlock: cs.reorgDetectedBlock,
692
- rollbackTargetBlock: cs.rollbackTargetBlock
715
+ rollbackTargetBlock: cs.rollbackTargetBlock,
716
+ rateLimitTimeMs: SourceManager.getRateLimitTimeMs(cs.sourceManager),
717
+ rateLimitResetInMs: SourceManager.getRateLimitResetInMs(cs.sourceManager)
693
718
  };
694
719
  }
695
720
 
696
- function toChainBeforeBatch(cs) {
721
+ function toChainBeforeBatch(cs, isRealtime) {
697
722
  let match = cs.blockStore.getHashes(Primitive_int.max(cs.fetchState.knownHeight - cs.maxReorgDepth | 0, 0), cs.fetchState.knownHeight + 1 | 0);
698
723
  let hashes = match.hashes;
699
724
  let blockNumbers = match.blockNumbers;
@@ -707,6 +732,7 @@ function toChainBeforeBatch(cs) {
707
732
  blockNumbers: blockNumbers,
708
733
  hashByBlockNumber: hashByBlockNumber
709
734
  },
735
+ blockTimeAt: blockNumber => Primitive_option.fromNull(cs.blockStore.getTimestamp(blockNumber, isRealtime)),
710
736
  shouldRollbackOnReorg: cs.shouldRollbackOnReorg,
711
737
  progressBlockNumber: cs.committedProgressBlockNumber,
712
738
  sourceBlockNumber: cs.fetchState.knownHeight,
@@ -786,6 +812,7 @@ function applyBatchProgress(cs, batch, blockTimestampName) {
786
812
  }
787
813
  }
788
814
  cs.committedProgressBlockNumber = chainAfterBatch.progressBlockNumber;
815
+ cs.committedProgressBlockTime = chainAfterBatch.progressBlockTime;
789
816
  cs.processingBlockNumber = Primitive_int.max(cs.processingBlockNumber, chainAfterBatch.progressBlockNumber);
790
817
  cs.numEventsProcessed = chainAfterBatch.totalEventsProcessed;
791
818
  cs.transactionStore.prune(chainAfterBatch.progressBlockNumber);
@@ -805,6 +832,14 @@ function markReady(cs, readyAt) {
805
832
  }
806
833
  }
807
834
 
835
+ function rollbackCommittedProgress(cs, blockNumber) {
836
+ if (blockNumber !== cs.committedProgressBlockNumber) {
837
+ cs.committedProgressBlockNumber = blockNumber;
838
+ cs.committedProgressBlockTime = Primitive_option.fromNull(cs.blockStore.getTimestamp(blockNumber, false));
839
+ return;
840
+ }
841
+ }
842
+
808
843
  function rollback(cs, rolledBackTo) {
809
844
  let rollbackTo = targetBlockNumber => {
810
845
  let match = FetchState.rollback(cs.fetchState, cs.addressStore, targetBlockNumber);
@@ -829,7 +864,7 @@ function rollback(cs, rolledBackTo) {
829
864
  let rolledBackAddresses = rollbackTo(newProgressBlockNumber);
830
865
  cs.transactionStore.rollback(newProgressBlockNumber);
831
866
  cs.blockStore.rollback(newProgressBlockNumber);
832
- cs.committedProgressBlockNumber = newProgressBlockNumber;
867
+ rollbackCommittedProgress(cs, newProgressBlockNumber);
833
868
  cs.processingBlockNumber = newProgressBlockNumber;
834
869
  cs.numEventsProcessed = newTotalEventsProcessed;
835
870
  return rolledBackAddresses;
@@ -838,7 +873,7 @@ function rollback(cs, rolledBackTo) {
838
873
  let rolledBackAddresses$1 = rollbackTo(forkBlock);
839
874
  cs.transactionStore.rollback(forkBlock);
840
875
  cs.blockStore.rollback(forkBlock);
841
- cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, forkBlock);
876
+ rollbackCommittedProgress(cs, Primitive_int.min(cs.committedProgressBlockNumber, forkBlock));
842
877
  cs.processingBlockNumber = Primitive_int.min(cs.processingBlockNumber, forkBlock);
843
878
  return rolledBackAddresses$1;
844
879
  }
@@ -860,6 +895,7 @@ export {
860
895
  safeCheckpointTracking,
861
896
  isProgressAtHead,
862
897
  committedProgressBlockNumber,
898
+ committedProgressBlockTime,
863
899
  numEventsProcessed,
864
900
  pendingBudget,
865
901
  startFetchingQueries,
@@ -902,6 +938,8 @@ export {
902
938
  updateKnownHeight,
903
939
  setEndBlockToFirstEvent,
904
940
  enterReorgThreshold,
941
+ isInReorgThreshold,
942
+ shouldSaveHistory,
905
943
  advanceAfterBatch,
906
944
  applyBatchProgress,
907
945
  markReady,
@@ -23,9 +23,11 @@ let make: (
23
23
  ~addressStore: AddressStore.t,
24
24
  ~sourceManager: SourceManager.t,
25
25
  ~committedProgressBlockNumber: int,
26
+ ~committedProgressBlockTime: option<int>=?,
26
27
  ~safeCheckpointTracking: option<SafeCheckpointTracking.t>=?,
27
28
  ~shouldRollbackOnReorg: bool,
28
29
  ~maxReorgDepth: int,
30
+ ~isInReorgThreshold: bool=?,
29
31
  ~numEventsProcessed: float=?,
30
32
  ~timestampCaughtUpToHeadOrEndblock: option<Date.t>=?,
31
33
  ~isProgressAtHead: bool=?,
@@ -67,6 +69,7 @@ let getLatestValidScannedBlock: (
67
69
  let safeCheckpointTracking: t => option<SafeCheckpointTracking.t>
68
70
  let isProgressAtHead: t => bool
69
71
  let committedProgressBlockNumber: t => int
72
+ let committedProgressBlockTime: t => option<int>
70
73
  let numEventsProcessed: t => float
71
74
  let pendingBudget: t => float
72
75
  let startFetchingQueries: (t, ~queries: array<FetchState.query>) => unit
@@ -116,7 +119,7 @@ let dispatch: (
116
119
  // Views.
117
120
  let toMetrics: t => Metrics.chainMetrics
118
121
  let toChainMetadata: t => InternalTable.Chains.metaFields
119
- let toChainBeforeBatch: t => Batch.chainBeforeBatch
122
+ let toChainBeforeBatch: (t, ~isRealtime: bool) => Batch.chainBeforeBatch
120
123
  let isReadyToEnterReorgThresholdAfterBatch: (t, ~batch: Batch.t) => bool
121
124
 
122
125
  // Derived (pure).
@@ -154,6 +157,8 @@ let resetPendingQueries: t => unit
154
157
  let updateKnownHeight: (t, ~knownHeight: int) => unit
155
158
  let setEndBlockToFirstEvent: (t, ~blockNumber: int) => unit
156
159
  let enterReorgThreshold: t => unit
160
+ let isInReorgThreshold: t => bool
161
+ let shouldSaveHistory: t => bool
157
162
  let advanceAfterBatch: (t, ~batch: Batch.t, ~enteringReorgThreshold: bool) => unit
158
163
  let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => unit
159
164
  let markReady: (t, ~readyAt: Date.t) => unit