envio 3.10.0 → 3.11.0-svm-alpha.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 (105) hide show
  1. package/evm.schema.json +21 -4
  2. package/fuel.schema.json +21 -4
  3. package/index.d.ts +10 -6
  4. package/package.json +6 -6
  5. package/src/Batch.res +100 -78
  6. package/src/Batch.res.mjs +52 -46
  7. package/src/BatchProcessing.res +1 -11
  8. package/src/BatchProcessing.res.mjs +1 -4
  9. package/src/ChainState.res +46 -87
  10. package/src/ChainState.res.mjs +32 -66
  11. package/src/ChainState.resi +3 -0
  12. package/src/Config.res +131 -55
  13. package/src/Config.res.mjs +132 -69
  14. package/src/CrossChainState.res +39 -49
  15. package/src/CrossChainState.res.mjs +24 -22
  16. package/src/CrossChainState.resi +6 -13
  17. package/src/EffectState.res +13 -4
  18. package/src/EffectState.resi +7 -1
  19. package/src/EventConfigBuilder.res +14 -10
  20. package/src/EventConfigBuilder.res.mjs +6 -4
  21. package/src/EventProcessing.res +6 -6
  22. package/src/EventProcessing.res.mjs +6 -6
  23. package/src/FetchState.res +14 -0
  24. package/src/FetchState.res.mjs +14 -0
  25. package/src/FinalizeBackfill.res +16 -12
  26. package/src/FinalizeBackfill.res.mjs +3 -3
  27. package/src/HandlerRegister.res +12 -11
  28. package/src/HandlerRegister.res.mjs +6 -5
  29. package/src/HistoryPolicy.res +46 -0
  30. package/src/HistoryPolicy.res.mjs +50 -0
  31. package/src/InMemoryStore.res +30 -32
  32. package/src/InMemoryStore.res.mjs +33 -28
  33. package/src/IndexerState.res +66 -45
  34. package/src/IndexerState.res.mjs +57 -29
  35. package/src/IndexerState.resi +8 -13
  36. package/src/Internal.res +24 -6
  37. package/src/Internal.res.mjs +9 -0
  38. package/src/LoadLayer.res +11 -4
  39. package/src/LoadLayer.res.mjs +8 -5
  40. package/src/Main.res +33 -25
  41. package/src/Main.res.mjs +25 -26
  42. package/src/Metrics.res +41 -1
  43. package/src/Metrics.res.mjs +25 -3
  44. package/src/Persistence.res +65 -17
  45. package/src/Persistence.res.mjs +23 -4
  46. package/src/PgStorage.res +192 -119
  47. package/src/PgStorage.res.mjs +130 -73
  48. package/src/PruneStaleHistory.res +9 -15
  49. package/src/PruneStaleHistory.res.mjs +11 -20
  50. package/src/Rollback.res +8 -14
  51. package/src/Rollback.res.mjs +4 -3
  52. package/src/SimulateItems.res +13 -6
  53. package/src/SimulateItems.res.mjs +7 -6
  54. package/src/Sink.res +7 -5
  55. package/src/Sink.res.mjs +4 -4
  56. package/src/TestIndexer.res +10 -10
  57. package/src/TestIndexer.res.mjs +11 -10
  58. package/src/UserContext.res +7 -4
  59. package/src/UserContext.res.mjs +5 -2
  60. package/src/Utils.res +9 -0
  61. package/src/Utils.res.mjs +9 -0
  62. package/src/Writing.res +34 -20
  63. package/src/Writing.res.mjs +15 -20
  64. package/src/bindings/ClickHouse.res +24 -8
  65. package/src/bindings/ClickHouse.res.mjs +20 -10
  66. package/src/bindings/ClickHouseSink.res +16 -5
  67. package/src/bindings/ClickHouseSink.res.mjs +3 -1
  68. package/src/db/CheckpointSequence.res +109 -0
  69. package/src/db/CheckpointSequence.res.mjs +99 -0
  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 +112 -21
  75. package/src/db/InternalTable.res.mjs +95 -28
  76. package/src/db/RollbackFloors.res +34 -41
  77. package/src/db/RollbackFloors.res.mjs +27 -71
  78. package/src/sources/ChainSources.res +72 -0
  79. package/src/sources/ChainSources.res.mjs +55 -0
  80. package/src/sources/EvmHyperSyncSource.res +13 -23
  81. package/src/sources/EvmHyperSyncSource.res.mjs +11 -42
  82. package/src/sources/FuelHyperSyncSource.res +10 -18
  83. package/src/sources/FuelHyperSyncSource.res.mjs +8 -38
  84. package/src/sources/HyperSync.res +33 -0
  85. package/src/sources/HyperSync.res.mjs +32 -0
  86. package/src/sources/HyperSync.resi +17 -0
  87. package/src/sources/HyperSyncClient.res +3 -0
  88. package/src/sources/RequestStat.res +8 -1
  89. package/src/sources/Source.res +4 -3
  90. package/src/sources/SourceManager.res +30 -10
  91. package/src/sources/SourceManager.res.mjs +24 -7
  92. package/src/sources/SourceManager.resi +2 -0
  93. package/src/sources/StartBlockResolver.res +173 -0
  94. package/src/sources/StartBlockResolver.res.mjs +134 -0
  95. package/src/sources/Svm.res +0 -51
  96. package/src/sources/Svm.res.mjs +0 -46
  97. package/src/sources/SvmHyperSyncSource.res +20 -9
  98. package/src/sources/SvmHyperSyncSource.res.mjs +34 -12
  99. package/src/tui/Tui.res +56 -143
  100. package/src/tui/Tui.res.mjs +70 -166
  101. package/src/tui/components/TuiData.res +61 -16
  102. package/src/tui/components/TuiData.res.mjs +53 -15
  103. package/svm.schema.json +80 -73
  104. package/src/db/CheckpointBounds.res +0 -53
  105. package/src/db/CheckpointBounds.res.mjs +0 -44
package/evm.schema.json CHANGED
@@ -494,10 +494,8 @@
494
494
  "minimum": 0
495
495
  },
496
496
  "start_block": {
497
- "description": "The block at which the indexer should start ingesting data",
498
- "type": "integer",
499
- "format": "uint64",
500
- "minimum": 0
497
+ "description": "The block at which the indexer should start ingesting data, or \"latest\" to start from the chain's current head block when the indexer is first deployed. Once resolved, the concrete block is persisted and reused every time the indexer resumes normally (for example recovering from a crash), so downtime is backfilled instead of skipped. Running `envio start`/`dev` with -r (--restart) resets this like any other config change: \"latest\" resolves again, against the head at that time.",
498
+ "$ref": "#/$defs/StartBlock"
501
499
  },
502
500
  "end_block": {
503
501
  "description": "The block at which the indexer should terminate.",
@@ -685,6 +683,25 @@
685
683
  "url"
686
684
  ]
687
685
  },
686
+ "StartBlock": {
687
+ "description": "A chain's configured start block: either a concrete block number or the\nliteral \"latest\". Config parsing never touches the network, so `Latest`\nstays unresolved here — it's resolved once at runtime, right before the\nindexer's first-ever persisted state is written, and never re-resolved\non a normal resume (see packages/envio/src/sources/StartBlockResolver.res).\nNote: this repo's `-r`/`--restart` CLI flag wipes the DB and re-deploys\nfrom scratch, so it re-resolves \"latest\" too — \"resume\" here means the\nopposite: recovering from a crash or process restart without `-r`.",
688
+ "anyOf": [
689
+ {
690
+ "type": "integer",
691
+ "format": "uint64",
692
+ "minimum": 0
693
+ },
694
+ {
695
+ "$ref": "#/$defs/StartBlockTag"
696
+ }
697
+ ]
698
+ },
699
+ "StartBlockTag": {
700
+ "type": "string",
701
+ "enum": [
702
+ "latest"
703
+ ]
704
+ },
688
705
  "ChainContract": {
689
706
  "type": "object",
690
707
  "properties": {
package/fuel.schema.json CHANGED
@@ -328,10 +328,8 @@
328
328
  ]
329
329
  },
330
330
  "start_block": {
331
- "description": "The block at which the indexer should start ingesting data",
332
- "type": "integer",
333
- "format": "uint64",
334
- "minimum": 0
331
+ "description": "The block at which the indexer should start ingesting data, or \"latest\" to start from the chain's current head block when the indexer is first deployed. Once resolved, the concrete block is persisted and reused every time the indexer resumes normally (for example recovering from a crash), so downtime is backfilled instead of skipped. Running `envio start`/`dev` with -r (--restart) resets this like any other config change: \"latest\" resolves again, against the head at that time.",
332
+ "$ref": "#/$defs/StartBlock"
335
333
  },
336
334
  "end_block": {
337
335
  "description": "The block at which the indexer should terminate.",
@@ -388,6 +386,25 @@
388
386
  "start_block"
389
387
  ]
390
388
  },
389
+ "StartBlock": {
390
+ "description": "A chain's configured start block: either a concrete block number or the\nliteral \"latest\". Config parsing never touches the network, so `Latest`\nstays unresolved here — it's resolved once at runtime, right before the\nindexer's first-ever persisted state is written, and never re-resolved\non a normal resume (see packages/envio/src/sources/StartBlockResolver.res).\nNote: this repo's `-r`/`--restart` CLI flag wipes the DB and re-deploys\nfrom scratch, so it re-resolves \"latest\" too — \"resume\" here means the\nopposite: recovering from a crash or process restart without `-r`.",
391
+ "anyOf": [
392
+ {
393
+ "type": "integer",
394
+ "format": "uint64",
395
+ "minimum": 0
396
+ },
397
+ {
398
+ "$ref": "#/$defs/StartBlockTag"
399
+ }
400
+ ]
401
+ },
402
+ "StartBlockTag": {
403
+ "type": "string",
404
+ "enum": [
405
+ "latest"
406
+ ]
407
+ },
391
408
  "HyperfuelConfig": {
392
409
  "type": "object",
393
410
  "properties": {
package/index.d.ts CHANGED
@@ -1216,6 +1216,9 @@ export type SvmAllTransactionFields = {
1216
1216
  readonly err: string | undefined;
1217
1217
  readonly fee: bigint;
1218
1218
  readonly computeUnitsConsumed: bigint | undefined;
1219
+ /** Every account the transaction resolves to: the static keys, then the
1220
+ * address lookup tables' writable then readonly addresses. A transaction's
1221
+ * account indexes address this list. */
1219
1222
  readonly accountKeys: readonly string[];
1220
1223
  readonly recentBlockhash: string;
1221
1224
  readonly version: string | undefined;
@@ -1377,7 +1380,7 @@ type SvmNamedAccounts<
1377
1380
  Acc extends Readonly<Record<string, unknown>>,
1378
1381
  Fields extends SvmFieldsSelection,
1379
1382
  > = {
1380
- readonly [K in keyof Acc & string]: SvmInstructionAccount<Fields, K>;
1383
+ readonly [K in keyof Acc]: SvmInstructionAccount<Fields, K & string>;
1381
1384
  };
1382
1385
 
1383
1386
  /** The parent transaction of a {@link SvmInstruction}, narrowed to the
@@ -1524,8 +1527,8 @@ export type SvmOnInstructionOptions<
1524
1527
  /** Program name as declared under `chains[].programs[].name` in
1525
1528
  * `config.yaml`. */
1526
1529
  readonly program: P;
1527
- /** Instruction name as declared under
1528
- * `chains[].programs[].instructions[].name` in `config.yaml`. */
1530
+ /** Instruction name from the program's IDL, or from
1531
+ * `chains[].programs[].instructions[].name` when the layout is inline. */
1529
1532
  readonly instruction: I;
1530
1533
  readonly fields?: Fields & SvmFieldsLiteralCheck<Fields>;
1531
1534
  readonly where?: SvmOnInstructionWhere<SvmAccountsOf<P, I>>;
@@ -1768,9 +1771,10 @@ type SvmEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
1768
1771
  }
1769
1772
  ? {
1770
1773
  /**
1771
- * Register an instruction handler. Dispatch matches on
1772
- * `(programId, discriminator)` from the YAML config.
1773
- * Handler `fields` is the only source of payload selection.
1774
+ * Register an instruction handler. `program` and `instruction`
1775
+ * name an entry from the IDL or YAML. Dispatch uses that
1776
+ * instruction's discriminator. Handler `fields` is the only
1777
+ * source of payload selection.
1774
1778
  */
1775
1779
  readonly onInstruction: <
1776
1780
  P extends keyof Programs & string,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.10.0",
3
+ "version": "3.11.0-svm-alpha.1",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -68,10 +68,10 @@
68
68
  "tsx": "4.21.0"
69
69
  },
70
70
  "optionalDependencies": {
71
- "envio-linux-x64": "3.10.0",
72
- "envio-linux-x64-musl": "3.10.0",
73
- "envio-linux-arm64": "3.10.0",
74
- "envio-darwin-x64": "3.10.0",
75
- "envio-darwin-arm64": "3.10.0"
71
+ "envio-linux-x64": "3.11.0-svm-alpha.1",
72
+ "envio-linux-x64-musl": "3.11.0-svm-alpha.1",
73
+ "envio-linux-arm64": "3.11.0-svm-alpha.1",
74
+ "envio-darwin-x64": "3.11.0-svm-alpha.1",
75
+ "envio-darwin-arm64": "3.11.0-svm-alpha.1"
76
76
  }
77
77
  }
package/src/Batch.res CHANGED
@@ -34,24 +34,32 @@ type t = {
34
34
  totalBatchSize: int,
35
35
  items: array<Internal.item>,
36
36
  progressedChainsById: dict<chainAfterBatch>,
37
- // Processed inside the reorg threshold. Drives whether history is saved, so
38
- // writes never merge across a change in this value.
39
- isInReorgThreshold: bool,
37
+ // Whether the batch's rows get history. Writes never merge across a change in
38
+ // it, so a single write can't mix the two.
39
+ history: HistoryPolicy.t,
40
40
  // Unnest-like checkpoint fields:
41
41
  checkpointIds: array<bigint>,
42
42
  checkpointChainIds: array<ChainId.t>,
43
43
  checkpointBlockNumbers: array<int>,
44
44
  checkpointBlockHashes: array<Null.t<string>>,
45
+ // Items the checkpoint carries, which is what indexes `items`.
46
+ checkpointItemsCount: array<int>,
47
+ // Logs the checkpoint carries: one log routed to several registrations is one
48
+ // event, however many items it made.
45
49
  checkpointEventsProcessed: array<int>,
46
50
  registeredAddresses: array<AddressRows.staged>,
47
51
  }
48
52
 
53
+ // What a chain contributed to the batch: items taken off its buffer, and the
54
+ // logs behind them.
55
+ type chainBatchCounts = {size: int, eventsProcessed: int}
56
+
49
57
  let getProgressedChainsById = {
50
58
  let getChainAfterBatchIfProgressed = (
51
59
  ~chainBeforeBatch: chainBeforeBatch,
52
60
  ~progressBlockNumberAfterBatch,
53
61
  ~fetchStateAfterBatch,
54
- ~batchSize,
62
+ ~counts: chainBatchCounts,
55
63
  ) => {
56
64
  // The check is sufficient, since we guarantee to include a full block in a batch
57
65
  // Also, this might be true even if batchSize is 0,
@@ -60,10 +68,11 @@ let getProgressedChainsById = {
60
68
  Some(
61
69
  (
62
70
  {
63
- batchSize,
71
+ batchSize: counts.size,
64
72
  progressBlockNumber: progressBlockNumberAfterBatch,
65
73
  sourceBlockNumber: chainBeforeBatch.sourceBlockNumber,
66
- totalEventsProcessed: chainBeforeBatch.totalEventsProcessed +. batchSize->Int.toFloat,
74
+ totalEventsProcessed: chainBeforeBatch.totalEventsProcessed +.
75
+ counts.eventsProcessed->Int.toFloat,
67
76
  fetchState: fetchStateAfterBatch,
68
77
  isProgressAtHeadWhenBatchCreated: progressBlockNumberAfterBatch >=
69
78
  chainBeforeBatch.sourceBlockNumber - chainBeforeBatch.chainConfig.blockLag,
@@ -77,7 +86,7 @@ let getProgressedChainsById = {
77
86
 
78
87
  (
79
88
  ~chainsBeforeBatch: dict<chainBeforeBatch>,
80
- ~batchSizePerChain: dict<int>,
89
+ ~countsPerChain: dict<chainBatchCounts>,
81
90
  ~progressBlockNumberPerChain: dict<int>,
82
91
  ) => {
83
92
  let progressedChainsById = Dict.make()
@@ -98,14 +107,14 @@ let getProgressedChainsById = {
98
107
  | None => chainBeforeBatch.progressBlockNumber
99
108
  }
100
109
 
101
- switch switch batchSizePerChain->Utils.Dict.dangerouslyGetNonOption(
110
+ switch switch countsPerChain->Utils.Dict.dangerouslyGetNonOption(
102
111
  fetchState.chainId->ChainId.toString,
103
112
  ) {
104
- | Some(batchSize) =>
105
- let leftItems = fetchState.buffer->Array.slice(~start=batchSize)
113
+ | Some(counts) =>
114
+ let leftItems = fetchState.buffer->Array.slice(~start=counts.size)
106
115
  getChainAfterBatchIfProgressed(
107
116
  ~chainBeforeBatch,
108
- ~batchSize,
117
+ ~counts,
109
118
  ~fetchStateAfterBatch=fetchState->FetchState.updateInternal(~mutItems=leftItems),
110
119
  ~progressBlockNumberAfterBatch,
111
120
  )
@@ -113,7 +122,7 @@ let getProgressedChainsById = {
113
122
  | None =>
114
123
  getChainAfterBatchIfProgressed(
115
124
  ~chainBeforeBatch,
116
- ~batchSize=0,
125
+ ~counts={size: 0, eventsProcessed: 0},
117
126
  ~fetchStateAfterBatch=chainBeforeBatch.fetchState,
118
127
  ~progressBlockNumberAfterBatch,
119
128
  )
@@ -146,7 +155,7 @@ let seekFirstAbove = (blockNumbers: array<int>, blockNumber) => {
146
155
 
147
156
  @inline
148
157
  let addReorgCheckpoints = (
149
- ~prevCheckpointId,
158
+ ~cursor,
150
159
  ~scannedHashes: reorgHashSnapshot,
151
160
  ~shouldRollbackOnReorg,
152
161
  ~fromBlockExclusive,
@@ -156,10 +165,10 @@ let addReorgCheckpoints = (
156
165
  ~mutCheckpointChainIds,
157
166
  ~mutCheckpointBlockNumbers,
158
167
  ~mutCheckpointBlockHashes,
168
+ ~mutCheckpointItemsCount,
159
169
  ~mutCheckpointEventsProcessed,
160
170
  ) => {
161
171
  if shouldRollbackOnReorg {
162
- let prevCheckpointId = ref(prevCheckpointId)
163
172
  // The snapshot already holds only in-threshold scanned hashes, ascending,
164
173
  // so seeking to the gap's lower bound gives the gap checkpoints without
165
174
  // rescanning the whole snapshot for every gap in the batch.
@@ -172,37 +181,30 @@ let addReorgCheckpoints = (
172
181
  scannedHashes.hashByBlockNumber
173
182
  ->Utils.Dict.dangerouslyGetByIntNonOption(blockNumber)
174
183
  ->Option.getUnsafe
175
- let checkpointId = prevCheckpointId.contents->BigInt.add(1n)
176
- prevCheckpointId := checkpointId
184
+ let checkpointId = cursor->CheckpointSequence.next(~chainId)
177
185
 
178
186
  mutCheckpointIds->Array.push(checkpointId)
179
187
  mutCheckpointChainIds->Array.push(chainId)
180
188
  mutCheckpointBlockNumbers->Array.push(blockNumber)
181
189
  mutCheckpointBlockHashes->Array.push(Null.Value(hash))
190
+ mutCheckpointItemsCount->Array.push(0)
182
191
  mutCheckpointEventsProcessed->Array.push(0)
183
192
 
184
193
  idx := idx.contents + 1
185
194
  }
186
- prevCheckpointId.contents
187
- } else {
188
- prevCheckpointId
189
195
  }
190
196
  }
191
197
 
192
- // Checkpoint ids are handed out in ascending order as each chain's items are
193
- // walked in block order, so within a chain a higher id always means a later
194
- // block. Rollback preserves that: it deletes the chain's ids above its target
195
- // before any higher one is allocated, so the ids the re-indexed blocks get are
196
- // above everything the chain still holds. An isolated rollback leans on the
197
- // invariant — it deletes by `chain_id = c AND id > target`, which is only the
198
- // chain's stale suffix if ids and blocks agree on order within the chain. Ids
199
- // interleave freely *across* chains, which is exactly why that predicate can't
200
- // be the id bound alone.
201
- let prepareBatch = (
202
- ~checkpointIdBeforeBatch,
198
+ // Within a chain, ids ascend with block order, and rollback preserves that by
199
+ // deleting the chain's ids above its target before any higher one is allocated.
200
+ // Every rollback deletes by `id > target`, which is only the stale suffix while
201
+ // ids and blocks agree on order within each chain.
202
+ let make = (
203
+ ~sequence: CheckpointSequence.t,
204
+ ~frontier: Frontier.t,
203
205
  ~chainsBeforeBatch: dict<chainBeforeBatch>,
204
206
  ~batchSizeTarget,
205
- ~isInReorgThreshold,
207
+ ~history,
206
208
  ) => {
207
209
  let preparedFetchStates =
208
210
  chainsBeforeBatch
@@ -214,8 +216,8 @@ let prepareBatch = (
214
216
  let preparedNumber = preparedFetchStates->Array.length
215
217
  let totalBatchSize = ref(0)
216
218
 
217
- let prevCheckpointId = ref(checkpointIdBeforeBatch)
218
- let mutBatchSizePerChain = Dict.make()
219
+ let cursor = sequence->CheckpointSequence.cursor(~frontier)
220
+ let mutCountsPerChain = Dict.make()
219
221
  let mutProgressBlockNumberPerChain = Dict.make()
220
222
 
221
223
  let items = []
@@ -223,6 +225,7 @@ let prepareBatch = (
223
225
  let checkpointChainIds = []
224
226
  let checkpointBlockNumbers = []
225
227
  let checkpointBlockHashes = []
228
+ let checkpointItemsCount = []
226
229
  let checkpointEventsProcessed = []
227
230
 
228
231
  // Accumulate items for all actively indexing chains
@@ -241,29 +244,38 @@ let prepareBatch = (
241
244
  ->Option.getUnsafe
242
245
 
243
246
  let prevBlockNumber = ref(chainBeforeBatch.progressBlockNumber)
247
+ let chainEventsProcessed = ref(0)
244
248
  if chainBatchSize > 0 {
245
249
  for idx in 0 to chainBatchSize - 1 {
246
250
  let item = fetchState.buffer->Array.getUnsafe(idx)
247
251
  let blockNumber = item->Internal.getItemBlockNumber
252
+ // The buffer is sorted, so a log's items are consecutive: the first of
253
+ // them is the only one that counts as an event.
254
+ let isNewEvent =
255
+ idx === 0 ||
256
+ !(fetchState.buffer->Array.getUnsafe(idx - 1)->FetchState.isSameLog(item))
257
+ if isNewEvent {
258
+ chainEventsProcessed := chainEventsProcessed.contents + 1
259
+ }
248
260
 
249
261
  // Every new block we should create a new checkpoint
250
262
  if blockNumber !== prevBlockNumber.contents {
251
- prevCheckpointId :=
252
- addReorgCheckpoints(
253
- ~chainId=fetchState.chainId,
254
- ~scannedHashes=chainBeforeBatch.scannedHashes,
255
- ~shouldRollbackOnReorg=chainBeforeBatch.shouldRollbackOnReorg,
256
- ~prevCheckpointId=prevCheckpointId.contents,
257
- ~fromBlockExclusive=prevBlockNumber.contents,
258
- ~toBlockExclusive=blockNumber,
259
- ~mutCheckpointIds=checkpointIds,
260
- ~mutCheckpointChainIds=checkpointChainIds,
261
- ~mutCheckpointBlockNumbers=checkpointBlockNumbers,
262
- ~mutCheckpointBlockHashes=checkpointBlockHashes,
263
- ~mutCheckpointEventsProcessed=checkpointEventsProcessed,
264
- )
265
-
266
- let checkpointId = prevCheckpointId.contents->BigInt.add(1n)
263
+ addReorgCheckpoints(
264
+ ~chainId=fetchState.chainId,
265
+ ~scannedHashes=chainBeforeBatch.scannedHashes,
266
+ ~shouldRollbackOnReorg=chainBeforeBatch.shouldRollbackOnReorg,
267
+ ~cursor,
268
+ ~fromBlockExclusive=prevBlockNumber.contents,
269
+ ~toBlockExclusive=blockNumber,
270
+ ~mutCheckpointIds=checkpointIds,
271
+ ~mutCheckpointChainIds=checkpointChainIds,
272
+ ~mutCheckpointBlockNumbers=checkpointBlockNumbers,
273
+ ~mutCheckpointBlockHashes=checkpointBlockHashes,
274
+ ~mutCheckpointItemsCount=checkpointItemsCount,
275
+ ~mutCheckpointEventsProcessed=checkpointEventsProcessed,
276
+ )
277
+
278
+ let checkpointId = cursor->CheckpointSequence.next(~chainId=fetchState.chainId)
267
279
 
268
280
  checkpointIds->Array.push(checkpointId)->ignore
269
281
  checkpointChainIds->Array.push(fetchState.chainId)->ignore
@@ -278,41 +290,49 @@ let prepareBatch = (
278
290
  },
279
291
  )
280
292
  ->ignore
293
+ checkpointItemsCount->Array.push(1)->ignore
281
294
  checkpointEventsProcessed->Array.push(1)->ignore
282
295
 
283
296
  prevBlockNumber := blockNumber
284
- prevCheckpointId := checkpointId
285
297
  } else {
286
- let lastIndex = checkpointEventsProcessed->Array.length - 1
287
- checkpointEventsProcessed
288
- ->Array.setUnsafe(lastIndex, checkpointEventsProcessed->Array.getUnsafe(lastIndex) + 1)
298
+ let lastIndex = checkpointItemsCount->Array.length - 1
299
+ checkpointItemsCount
300
+ ->Array.setUnsafe(lastIndex, checkpointItemsCount->Array.getUnsafe(lastIndex) + 1)
289
301
  ->ignore
302
+ if isNewEvent {
303
+ checkpointEventsProcessed
304
+ ->Array.setUnsafe(lastIndex, checkpointEventsProcessed->Array.getUnsafe(lastIndex) + 1)
305
+ ->ignore
306
+ }
290
307
  }
291
308
 
292
309
  items->Array.push(item)->ignore
293
310
  }
294
311
 
295
312
  totalBatchSize := totalBatchSize.contents + chainBatchSize
296
- mutBatchSizePerChain->ChainId.Dict.set(fetchState.chainId, chainBatchSize)
313
+ mutCountsPerChain->ChainId.Dict.set(
314
+ fetchState.chainId,
315
+ {size: chainBatchSize, eventsProcessed: chainEventsProcessed.contents},
316
+ )
297
317
  }
298
318
 
299
319
  let progressBlockNumberAfterBatch =
300
320
  fetchState->FetchState.getProgressBlockNumberAt(~index=chainBatchSize)
301
321
 
302
- prevCheckpointId :=
303
- addReorgCheckpoints(
304
- ~chainId=fetchState.chainId,
305
- ~scannedHashes=chainBeforeBatch.scannedHashes,
306
- ~shouldRollbackOnReorg=chainBeforeBatch.shouldRollbackOnReorg,
307
- ~prevCheckpointId=prevCheckpointId.contents,
308
- ~fromBlockExclusive=prevBlockNumber.contents,
309
- ~toBlockExclusive=progressBlockNumberAfterBatch + 1, // Make it inclusive
310
- ~mutCheckpointIds=checkpointIds,
311
- ~mutCheckpointChainIds=checkpointChainIds,
312
- ~mutCheckpointBlockNumbers=checkpointBlockNumbers,
313
- ~mutCheckpointBlockHashes=checkpointBlockHashes,
314
- ~mutCheckpointEventsProcessed=checkpointEventsProcessed,
315
- )
322
+ addReorgCheckpoints(
323
+ ~chainId=fetchState.chainId,
324
+ ~scannedHashes=chainBeforeBatch.scannedHashes,
325
+ ~shouldRollbackOnReorg=chainBeforeBatch.shouldRollbackOnReorg,
326
+ ~cursor,
327
+ ~fromBlockExclusive=prevBlockNumber.contents,
328
+ ~toBlockExclusive=progressBlockNumberAfterBatch + 1, // Make it inclusive
329
+ ~mutCheckpointIds=checkpointIds,
330
+ ~mutCheckpointChainIds=checkpointChainIds,
331
+ ~mutCheckpointBlockNumbers=checkpointBlockNumbers,
332
+ ~mutCheckpointBlockHashes=checkpointBlockHashes,
333
+ ~mutCheckpointItemsCount=checkpointItemsCount,
334
+ ~mutCheckpointEventsProcessed=checkpointEventsProcessed,
335
+ )
316
336
 
317
337
  mutProgressBlockNumberPerChain->ChainId.Dict.set(
318
338
  fetchState.chainId,
@@ -327,26 +347,28 @@ let prepareBatch = (
327
347
  items,
328
348
  progressedChainsById: getProgressedChainsById(
329
349
  ~chainsBeforeBatch,
330
- ~batchSizePerChain=mutBatchSizePerChain,
350
+ ~countsPerChain=mutCountsPerChain,
331
351
  ~progressBlockNumberPerChain=mutProgressBlockNumberPerChain,
332
352
  ),
333
- isInReorgThreshold,
353
+ history,
334
354
  checkpointIds,
335
355
  checkpointChainIds,
336
356
  checkpointBlockNumbers,
337
357
  checkpointBlockHashes,
358
+ checkpointItemsCount,
338
359
  checkpointEventsProcessed,
339
360
  registeredAddresses: [],
340
361
  }
341
362
  }
342
363
 
343
- let make = (
344
- ~checkpointIdBeforeBatch,
345
- ~chainsBeforeBatch: dict<chainBeforeBatch>,
346
- ~batchSizeTarget,
347
- ~isInReorgThreshold,
348
- ) => {
349
- prepareBatch(~checkpointIdBeforeBatch, ~chainsBeforeBatch, ~batchSizeTarget, ~isInReorgThreshold)
364
+ // Where the batch leaves each chain it handed ids to. Ids ascend within a
365
+ // chain, so the last one seen per chain is its highest.
366
+ let checkpointFrontier = (batch: t): Frontier.t => {
367
+ let frontier = Frontier.empty()
368
+ batch.checkpointChainIds->Array.forEachWithIndex((chainId, index) =>
369
+ frontier->Frontier.set(chainId, batch.checkpointIds->Array.getUnsafe(index))
370
+ )
371
+ frontier
350
372
  }
351
373
 
352
374
  let findFirstEventBlockNumber = (batch: t, ~chainId) => {
@@ -357,7 +379,7 @@ let findFirstEventBlockNumber = (batch: t, ~chainId) => {
357
379
  let checkpointChainId = batch.checkpointChainIds->Array.getUnsafe(idx.contents)
358
380
  if (
359
381
  checkpointChainId === chainId &&
360
- batch.checkpointEventsProcessed->Array.getUnsafe(idx.contents) > 0
382
+ batch.checkpointItemsCount->Array.getUnsafe(idx.contents) > 0
361
383
  ) {
362
384
  result := Some(batch.checkpointBlockNumbers->Array.getUnsafe(idx.contents))
363
385
  } else {