envio 3.13.0-alpha.0 → 3.13.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 (94) hide show
  1. package/package.json +6 -8
  2. package/src/BatchProcessing.res +4 -5
  3. package/src/BatchProcessing.res.mjs +4 -11
  4. package/src/ChainFetching.res +18 -38
  5. package/src/ChainFetching.res.mjs +14 -20
  6. package/src/ChainState.res +113 -77
  7. package/src/ChainState.res.mjs +90 -73
  8. package/src/ChainState.resi +14 -5
  9. package/src/Config.res +14 -17
  10. package/src/Config.res.mjs +5 -20
  11. package/src/CrossChainState.res +68 -20
  12. package/src/CrossChainState.res.mjs +48 -5
  13. package/src/CrossChainState.resi +1 -0
  14. package/src/Env.res +2 -2
  15. package/src/ErrorHandling.res +15 -4
  16. package/src/ErrorHandling.res.mjs +16 -5
  17. package/src/FinalizeBackfill.res +21 -9
  18. package/src/FinalizeBackfill.res.mjs +17 -4
  19. package/src/HandlerRegister.res +0 -37
  20. package/src/HandlerRegister.res.mjs +0 -18
  21. package/src/IndexerState.res +2 -0
  22. package/src/IndexerState.res.mjs +5 -0
  23. package/src/IndexerState.resi +1 -0
  24. package/src/Logging.res +6 -38
  25. package/src/Logging.res.mjs +5 -32
  26. package/src/Main.res +6 -9
  27. package/src/Main.res.mjs +3 -5
  28. package/src/Metrics.res +9 -4
  29. package/src/Metrics.res.mjs +3 -3
  30. package/src/Persistence.res +6 -3
  31. package/src/Persistence.res.mjs +5 -3
  32. package/src/PgStorage.res +2 -2
  33. package/src/PgStorage.res.mjs +2 -2
  34. package/src/Rollback.res +3 -3
  35. package/src/Server.res +0 -3
  36. package/src/SimulateItems.res +1 -1
  37. package/src/SimulateItems.res.mjs +1 -1
  38. package/src/Supervisor.res +236 -109
  39. package/src/Supervisor.res.mjs +185 -55
  40. package/src/TestIndexer.res +6 -3
  41. package/src/TestIndexer.res.mjs +3 -3
  42. package/src/Worker.res +6 -0
  43. package/src/Worker.res.mjs +2 -1
  44. package/src/bindings/NodeJs.res +25 -16
  45. package/src/bindings/NodeJs.res.mjs +8 -1
  46. package/src/sources/AddressSet.res +5 -3
  47. package/src/sources/AddressStore.res +19 -11
  48. package/src/sources/AddressStore.res.mjs +2 -7
  49. package/src/sources/BlockStore.res +10 -9
  50. package/src/sources/BlockStore.res.mjs +4 -4
  51. package/src/sources/ChainSources.res +4 -0
  52. package/src/sources/ChainSources.res.mjs +2 -2
  53. package/src/sources/EvmChain.res +4 -0
  54. package/src/sources/EvmChain.res.mjs +3 -1
  55. package/src/sources/EvmEventItem.res +56 -0
  56. package/src/sources/EvmEventItem.res.mjs +32 -0
  57. package/src/sources/EvmHyperSyncSource.res +7 -37
  58. package/src/sources/EvmHyperSyncSource.res.mjs +8 -29
  59. package/src/sources/EvmRpcClient.res +41 -59
  60. package/src/sources/EvmRpcClient.res.mjs +4 -54
  61. package/src/sources/EvmRpcWs.res +4 -1
  62. package/src/sources/EvmRpcWs.res.mjs +6 -2
  63. package/src/sources/FuelHyperSyncSource.res +6 -4
  64. package/src/sources/FuelHyperSyncSource.res.mjs +7 -9
  65. package/src/sources/HeightFeed.res +7 -1
  66. package/src/sources/HeightFeed.res.mjs +9 -7
  67. package/src/sources/HyperSyncClient.res +1 -14
  68. package/src/sources/RequestStat.res +7 -0
  69. package/src/sources/RequestStat.res.mjs +14 -1
  70. package/src/sources/RpcSource.res +100 -1043
  71. package/src/sources/RpcSource.res.mjs +81 -1056
  72. package/src/sources/SimulateSource.res +2 -5
  73. package/src/sources/SimulateSource.res.mjs +2 -3
  74. package/src/sources/Source.res +34 -12
  75. package/src/sources/Source.res.mjs +11 -0
  76. package/src/sources/SourceManager.res +111 -31
  77. package/src/sources/SourceManager.res.mjs +65 -31
  78. package/src/sources/SourceManager.resi +4 -0
  79. package/src/sources/StartBlockResolver.res +8 -5
  80. package/src/sources/StartBlockResolver.res.mjs +4 -2
  81. package/src/sources/SvmHyperSyncSource.res +4 -2
  82. package/src/sources/SvmHyperSyncSource.res.mjs +4 -6
  83. package/src/sources/TransactionStore.res +9 -6
  84. package/src/sources/TransactionStore.res.mjs +2 -2
  85. package/src/tui/Tui.res +4 -18
  86. package/src/tui/Tui.res.mjs +3 -6
  87. package/src/LazyLoader.res +0 -135
  88. package/src/LazyLoader.res.mjs +0 -118
  89. package/src/bindings/SDSL.res +0 -12
  90. package/src/bindings/SDSL.res.mjs +0 -9
  91. package/src/bindings/Yargs.res +0 -8
  92. package/src/bindings/Yargs.res.mjs +0 -2
  93. package/src/sources/Rpc.res +0 -185
  94. package/src/sources/Rpc.res.mjs +0 -183
@@ -21,7 +21,6 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
21
21
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
22
22
  import * as ContractMapping from "./ContractMapping.res.mjs";
23
23
  import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
24
- import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
25
24
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
26
25
  import * as TransactionStore from "./sources/TransactionStore.res.mjs";
27
26
  import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
@@ -56,7 +55,7 @@ function validateOnEventRegistrations(chainId, registrations) {
56
55
  });
57
56
  }
58
57
 
59
- 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) {
60
59
  let onEventRegistrations = onEventRegistrationsOpt !== undefined ? onEventRegistrationsOpt : [];
61
60
  let committedProgressBlockTime = committedProgressBlockTimeOpt !== undefined ? Primitive_option.valFromOption(committedProgressBlockTimeOpt) : undefined;
62
61
  let safeCheckpointTracking = safeCheckpointTrackingOpt !== undefined ? Primitive_option.valFromOption(safeCheckpointTrackingOpt) : undefined;
@@ -64,9 +63,10 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
64
63
  let numEventsProcessed = numEventsProcessedOpt !== undefined ? numEventsProcessedOpt : 0;
65
64
  let timestampCaughtUpToHeadOrEndblock = timestampCaughtUpToHeadOrEndblockOpt !== undefined ? Primitive_option.valFromOption(timestampCaughtUpToHeadOrEndblockOpt) : undefined;
66
65
  let isProgressAtHead = isProgressAtHeadOpt !== undefined ? isProgressAtHeadOpt : false;
67
- let transactionStore = transactionStoreOpt !== undefined ? Primitive_option.valFromOption(transactionStoreOpt) : TransactionStore.make("evm", false);
66
+ let transactionStore = transactionStoreOpt !== undefined ? Primitive_option.valFromOption(transactionStoreOpt) : TransactionStore.make("evm");
68
67
  let chainDensity = chainDensityOpt !== undefined ? Primitive_option.valFromOption(chainDensityOpt) : undefined;
69
- 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;
70
70
  let reorgThresholdReadyTolerance = reorgThresholdReadyToleranceOpt !== undefined ? reorgThresholdReadyToleranceOpt : 100;
71
71
  let perChainEntities = perChainEntitiesOpt !== undefined ? perChainEntitiesOpt : [];
72
72
  validateOnEventRegistrations(chainConfig.id, onEventRegistrations);
@@ -89,6 +89,7 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
89
89
  shouldRollbackOnReorg: shouldRollbackOnReorg,
90
90
  maxReorgDepth: maxReorgDepth,
91
91
  isInReorgThreshold: isInReorgThreshold,
92
+ shouldChecksum: shouldChecksum,
92
93
  transactionStore: transactionStore,
93
94
  blockStore: blockStore,
94
95
  reorgThresholdReadyTolerance: reorgThresholdReadyTolerance,
@@ -97,7 +98,7 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
97
98
  blockRangeFetchCount: 0,
98
99
  blockRangeFetchedEvents: 0,
99
100
  blockRangeFetchedBlocks: 0,
100
- reportedFetchedTo: undefined,
101
+ reportedFinished: false,
101
102
  reorgCount: 0,
102
103
  reorgDetectedBlock: undefined,
103
104
  rollbackTargetBlock: undefined,
@@ -138,7 +139,7 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
138
139
  }
139
140
  });
140
141
  let lowercaseAddresses = config.lowercaseAddresses;
141
- let addressStore = AddressStore.make(config.ecosystem.name, !lowercaseAddresses, AddressStore.contractsOf(onEventRegistrations, contractMapping));
142
+ let addressStore = AddressStore.make(config.ecosystem.name, AddressStore.contractsOf(onEventRegistrations, contractMapping));
142
143
  let match$1 = config.ecosystem.name;
143
144
  let tmp;
144
145
  switch (match$1) {
@@ -156,18 +157,19 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
156
157
  return reorgCheckpoint;
157
158
  }
158
159
  });
159
- let sources = ChainSources.make(chainConfig, onEventRegistrations, addressStore, lowercaseAddresses);
160
- 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);
161
163
  if (Utils.$$Array.notEmpty(chainReorgCheckpoints)) {
162
164
  let seedPage = BlockStore.fromJs(chainReorgCheckpoints.map(cp => ({
163
165
  blockNumber: cp.block_number,
164
166
  blockHash: cp.block_hash
165
- })), config.ecosystem.name, !lowercaseAddresses);
167
+ })), config.ecosystem.name);
166
168
  blockStore.merge(seedPage, 0, false);
167
169
  }
168
170
  let firstEventBlock$1 = fetchState.firstEventBlock;
169
171
  let chainDensity = firstEventBlock$1 !== undefined && progressBlockNumber > firstEventBlock$1 && numEventsProcessed > 0 ? numEventsProcessed / (progressBlockNumber - firstEventBlock$1 | 0) : undefined;
170
- 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);
171
173
  }
172
174
 
173
175
  function logger(cs) {
@@ -178,6 +180,14 @@ function blockStore(cs) {
178
180
  return cs.blockStore;
179
181
  }
180
182
 
183
+ function transactionStore(cs) {
184
+ return cs.transactionStore;
185
+ }
186
+
187
+ function shouldChecksum(cs) {
188
+ return cs.shouldChecksum;
189
+ }
190
+
181
191
  function entities(cs) {
182
192
  return cs.entities;
183
193
  }
@@ -210,10 +220,6 @@ function safeCheckpointTracking(cs) {
210
220
  return cs.safeCheckpointTracking;
211
221
  }
212
222
 
213
- function isProgressAtHead(cs) {
214
- return cs.isProgressAtHead;
215
- }
216
-
217
223
  function committedProgressBlockNumber(cs) {
218
224
  return cs.committedProgressBlockNumber;
219
225
  }
@@ -256,7 +262,7 @@ function knownHeight(cs) {
256
262
  }
257
263
 
258
264
  function contractAddresses(cs, contractName) {
259
- return cs.addressStore.contractAddresses(contractName);
265
+ return cs.addressStore.contractAddresses(contractName, cs.shouldChecksum);
260
266
  }
261
267
 
262
268
  function drainAddressesForWrite(cs, toBlockInclusive, checkpointBlockNumbers) {
@@ -400,41 +406,6 @@ function dispatch(cs, executeQuery, waitForNewBlock, onNewBlock, action, stateId
400
406
  return SourceManager.dispatch(cs.sourceManager, cs.fetchState, executeQuery, waitForNewBlock, onNewBlock, action, stateId);
401
407
  }
402
408
 
403
- function fetchedTo(cs) {
404
- let endBlock = cs.fetchState.endBlock;
405
- if (endBlock !== undefined && endBlock <= (cs.fetchState.knownHeight - cs.fetchState.blockLag | 0)) {
406
- return [
407
- "the end block",
408
- endBlock
409
- ];
410
- }
411
- if (cs.isInReorgThreshold) {
412
- return [
413
- "the chain head",
414
- cs.fetchState.knownHeight
415
- ];
416
- } else {
417
- return [
418
- "the safe block",
419
- cs.fetchState.knownHeight - cs.fetchState.blockLag | 0
420
- ];
421
- }
422
- }
423
-
424
- function takeFetchedTo(cs) {
425
- let match = fetchedTo(cs);
426
- let target = match[0];
427
- if (Primitive_object.equal(cs.reportedFetchedTo, target)) {
428
- return;
429
- } else {
430
- cs.reportedFetchedTo = target;
431
- return [
432
- target,
433
- match[1]
434
- ];
435
- }
436
- }
437
-
438
409
  function hasProcessedToEndblock(cs) {
439
410
  let fetchState = cs.fetchState;
440
411
  let committedProgressBlockNumber = cs.committedProgressBlockNumber;
@@ -446,15 +417,46 @@ function hasProcessedToEndblock(cs) {
446
417
  }
447
418
  }
448
419
 
449
- function isDurablyCaughtUp(cs) {
450
- let fetchState = cs.fetchState;
451
- let committedProgressBlockNumber = cs.committedProgressBlockNumber;
452
- let atEndBlock = Stdlib_Option.mapOr(fetchState.endBlock, false, endBlock => committedProgressBlockNumber >= endBlock);
453
- let atHead = fetchState.knownHeight > 0 && committedProgressBlockNumber >= Primitive_int.max(0, fetchState.knownHeight - cs.chainConfig.blockLag | 0);
454
- if (atEndBlock) {
420
+ function hasCaughtUp(cs) {
421
+ if (cs.isProgressAtHead) {
455
422
  return true;
456
423
  } else {
457
- return atHead;
424
+ let fetchState = cs.fetchState;
425
+ let committedProgressBlockNumber = cs.committedProgressBlockNumber;
426
+ let atEndBlock = Stdlib_Option.mapOr(fetchState.endBlock, false, endBlock => committedProgressBlockNumber >= endBlock);
427
+ let atHead = fetchState.knownHeight > 0 && committedProgressBlockNumber >= Primitive_int.max(0, fetchState.knownHeight - cs.chainConfig.blockLag | 0);
428
+ if (atEndBlock) {
429
+ return true;
430
+ } else {
431
+ return atHead;
432
+ }
433
+ }
434
+ }
435
+
436
+ function takeFinished(cs) {
437
+ if (cs.reportedFinished) {
438
+ return;
439
+ }
440
+ let match = cs.fetchState.endBlock;
441
+ let match$1 = hasProcessedToEndblock(cs);
442
+ let match$2 = hasCaughtUp(cs);
443
+ if (match !== undefined && match$1) {
444
+ cs.reportedFinished = true;
445
+ return {
446
+ TAG: "EndBlock",
447
+ _0: match
448
+ };
449
+ }
450
+ if (match$2) {
451
+ cs.reportedFinished = true;
452
+ if (cs.timestampCaughtUpToHeadOrEndblock !== undefined) {
453
+ return;
454
+ } else {
455
+ return {
456
+ TAG: "Backfill",
457
+ _0: cs.committedProgressBlockNumber
458
+ };
459
+ }
458
460
  }
459
461
  }
460
462
 
@@ -548,12 +550,12 @@ function groupBatchItems(items) {
548
550
  ];
549
551
  }
550
552
 
551
- async function applyTransactionGroups(store, g) {
553
+ async function applyTransactionGroups(store, g, shouldChecksum) {
552
554
  if (!Utils.$$Array.notEmpty(g.payloadGroups)) {
553
555
  return;
554
556
  }
555
557
  if (g.anyTransactionFieldSelected) {
556
- let txs = await store.materialize(g.txBlockNumbers, g.transactionIndices, g.transactionMasks);
558
+ let txs = await store.materialize(g.txBlockNumbers, g.transactionIndices, g.transactionMasks, shouldChecksum);
557
559
  g.payloadGroups.forEach((payloads, i) => {
558
560
  let tx = txs[i];
559
561
  payloads.forEach(payload => {
@@ -574,11 +576,11 @@ async function applyTransactionGroups(store, g) {
574
576
  });
575
577
  }
576
578
 
577
- async function applyBlockGroups(store, g) {
579
+ async function applyBlockGroups(store, g, shouldChecksum) {
578
580
  if (!Utils.$$Array.notEmpty(g.blockItemGroups)) {
579
581
  return;
580
582
  }
581
- let blocks = await store.materialize(g.blockBlockNumbers, g.blockMasks);
583
+ let blocks = await store.materialize(g.blockBlockNumbers, g.blockMasks, shouldChecksum);
582
584
  g.blockItemGroups.forEach((group, i) => {
583
585
  let block = blocks[i];
584
586
  group.forEach(ei => {
@@ -590,23 +592,20 @@ async function applyBlockGroups(store, g) {
590
592
  async function materializeBatchItems(cs, items) {
591
593
  let match = groupBatchItems(items);
592
594
  await Promise.all([
593
- applyTransactionGroups(cs.transactionStore, match[0]),
594
- applyBlockGroups(cs.blockStore, match[1])
595
+ applyTransactionGroups(cs.transactionStore, match[0], cs.shouldChecksum),
596
+ applyBlockGroups(cs.blockStore, match[1], cs.shouldChecksum)
595
597
  ]);
596
598
  }
597
599
 
598
- async function materializePageItems(items, transactionStore, blockStore) {
600
+ async function materializePageItems(items, transactionStore, blockStore, shouldChecksum) {
599
601
  let match = groupBatchItems(items);
600
602
  await Promise.all([
601
- transactionStore !== undefined ? applyTransactionGroups(Primitive_option.valFromOption(transactionStore), match[0]) : Promise.resolve(),
602
- applyBlockGroups(blockStore, match[1])
603
+ applyTransactionGroups(transactionStore, match[0], shouldChecksum),
604
+ applyBlockGroups(blockStore, match[1], shouldChecksum)
603
605
  ]);
604
606
  }
605
607
 
606
- function handleQueryResult(cs, query, newItems, newRegistrations, latestFetchedBlock, knownHeight, txPage) {
607
- if (txPage !== undefined) {
608
- cs.transactionStore.merge(Primitive_option.valFromOption(txPage));
609
- }
608
+ function handleQueryResult(cs, query, newItems, newRegistrations, latestFetchedBlock, knownHeight) {
610
609
  let fs = newRegistrations.length !== 0 ? FetchState.registerDynamicContracts(cs.fetchState, cs.addressStore, Primitive_int.max(knownHeight, latestFetchedBlock), newRegistrations) : cs.fetchState;
611
610
  cs.fetchState = FetchState.updateKnownHeight(FetchState.handleQueryResult(fs, query, latestFetchedBlock, newItems), knownHeight);
612
611
  cs.pendingBudget = Primitive_float.max(0, cs.pendingBudget - query.itemsEst);
@@ -703,6 +702,19 @@ function shouldSaveHistory(cs) {
703
702
  }
704
703
  }
705
704
 
705
+ function reorgThresholdLiftsCeiling(cs) {
706
+ let ceiling = blockLag => {
707
+ let head = Primitive_int.max(0, cs.fetchState.knownHeight - blockLag | 0);
708
+ let endBlock = cs.fetchState.endBlock;
709
+ if (endBlock !== undefined) {
710
+ return Primitive_int.min(endBlock, head);
711
+ } else {
712
+ return head;
713
+ }
714
+ };
715
+ return ceiling(cs.chainConfig.blockLag) > ceiling(cs.fetchState.blockLag);
716
+ }
717
+
706
718
  function toChainMetadata(cs) {
707
719
  return {
708
720
  first_event_block: Stdlib_Null.fromOption(cs.fetchState.firstEventBlock),
@@ -919,6 +931,8 @@ function rollback(cs, rolledBackTo) {
919
931
  return rolledBackAddresses$1;
920
932
  }
921
933
 
934
+ 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.";
935
+
922
936
  export {
923
937
  configStorageRows,
924
938
  seedConfigAddresses,
@@ -926,6 +940,8 @@ export {
926
940
  makeFromDbState,
927
941
  logger,
928
942
  blockStore,
943
+ transactionStore,
944
+ shouldChecksum,
929
945
  entities,
930
946
  resetEntities,
931
947
  sourceManager,
@@ -934,7 +950,6 @@ export {
934
950
  getReorgThresholdBlockNumbersBelow,
935
951
  getLatestValidScannedBlock,
936
952
  safeCheckpointTracking,
937
- isProgressAtHead,
938
953
  committedProgressBlockNumber,
939
954
  committedProgressBlockTime,
940
955
  numEventsProcessed,
@@ -964,9 +979,11 @@ export {
964
979
  toChainBeforeBatch,
965
980
  isReadyToEnterReorgThreshold,
966
981
  isReadyToEnterReorgThresholdAfterBatch,
967
- takeFetchedTo,
982
+ takeFinished,
983
+ reorgThresholdLiftsCeiling,
984
+ reorgThresholdEntryMessage,
968
985
  hasProcessedToEndblock,
969
- isDurablyCaughtUp,
986
+ hasCaughtUp,
970
987
  getHighestBlockBelowThreshold,
971
988
  isActivelyIndexing,
972
989
  isReady,
@@ -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
@@ -67,7 +70,6 @@ let getLatestValidScannedBlock: (
67
70
  ~blockNumbers: array<int>,
68
71
  ) => option<int>
69
72
  let safeCheckpointTracking: t => option<SafeCheckpointTracking.t>
70
- let isProgressAtHead: t => bool
71
73
  let committedProgressBlockNumber: t => int
72
74
  let committedProgressBlockTime: t => option<int>
73
75
  let numEventsProcessed: t => float
@@ -124,9 +126,12 @@ let isReadyToEnterReorgThreshold: t => bool
124
126
  let isReadyToEnterReorgThresholdAfterBatch: (t, ~batch: Batch.t) => bool
125
127
 
126
128
  // Derived (pure).
127
- let takeFetchedTo: t => option<(string, int)>
129
+ type finished = EndBlock(int) | Backfill(int)
130
+ let takeFinished: t => option<finished>
131
+ let reorgThresholdLiftsCeiling: t => bool
132
+ let reorgThresholdEntryMessage: string
128
133
  let hasProcessedToEndblock: t => bool
129
- let isDurablyCaughtUp: t => bool
134
+ let hasCaughtUp: t => bool
130
135
  let getHighestBlockBelowThreshold: t => int
131
136
  let isActivelyIndexing: t => bool
132
137
  let isReady: t => bool
@@ -141,14 +146,18 @@ let handleQueryResult: (
141
146
  ~newRegistrations: array<AddressStore.registration>,
142
147
  ~latestFetchedBlock: int,
143
148
  ~knownHeight: int,
144
- ~transactionStore: option<TransactionStore.t>,
145
149
  ) => unit
146
150
  let materializeBatchItems: (t, ~items: array<Internal.item>) => promise<unit>
147
151
  let materializePageItems: (
148
152
  ~items: array<Internal.item>,
149
- ~transactionStore: option<TransactionStore.t>,
153
+ ~transactionStore: TransactionStore.t,
150
154
  ~blockStore: BlockStore.t,
155
+ ~shouldChecksum: bool,
151
156
  ) => promise<unit>
157
+ // Merges the page's blocks into the chain's store as it checks them: the check
158
+ // is the merge's own conflict report, so a page that passes is already applied.
159
+ // Its transactions are a separate page, merged by the caller once this returns
160
+ // no reorg.
152
161
  let registerReorgGuard: (
153
162
  t,
154
163
  ~blockStore: BlockStore.t,
package/src/Config.res CHANGED
@@ -625,20 +625,14 @@ 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. Only then is a unit of
629
- // this indexer's work attributable to a chain at all, which is what lets a run
630
- // be split across processes.
631
- let isPerChain = (config: t) => !(config.userEntities->Array.some(entity => entity.crossChain))
632
-
633
- // What every line this process logs is attributed to. A process driving one
634
- // of the schema's chains while siblings drive the rest names it, on the lines
635
- // that had no chain in hand. One driving several has no single owner to name,
636
- // and its chain-scoped lines already carry theirs.
637
- let logContext = (config: t): option<dict<JSON.t>> =>
638
- switch (config.isolated, config.chainMap->ChainMap.keys) {
639
- | (true, [chainId]) =>
640
- Some(Dict.fromArray([("chainId", chainId->S.reverseConvertToJsonOrThrow(ChainId.schema))]))
641
- | _ => None
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
642
636
  }
643
637
 
644
638
  // Narrows a config to the chains one `envio start --chain` process drives.
@@ -1237,9 +1231,11 @@ let prime = (json: JSON.t): unit => {
1237
1231
  cached := None
1238
1232
  }
1239
1233
 
1240
- // Narrows a public config to the chains one process drives. The supervisor
1241
- // plans the split; each worker applies the plan to the config it parsed itself.
1242
- let withIsolatedChains = (json: JSON.t, ~chainIds) =>
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) =>
1243
1239
  switch json->JSON.Decode.object {
1244
1240
  | Some(fields) => {
1245
1241
  let narrowed = fields->Dict.copy
@@ -1247,6 +1243,7 @@ let withIsolatedChains = (json: JSON.t, ~chainIds) =>
1247
1243
  "isolatedChains",
1248
1244
  chainIds->S.reverseConvertToJsonOrThrow(S.array(ChainId.schema)),
1249
1245
  )
1246
+ narrowed->Dict.set("isDev", JSON.Encode.bool(isDev))
1250
1247
  JSON.Object(narrowed)
1251
1248
  }
1252
1249
  | None => JsError.throwWithMessage("Invalid indexer config: not an object")
@@ -472,23 +472,8 @@ function getChain(config, chainId) {
472
472
  }
473
473
 
474
474
  function isPerChain(config) {
475
- return !config.userEntities.some(entity => entity.crossChain);
476
- }
477
-
478
- function logContext(config) {
479
- let match = config.isolated;
480
- let match$1 = ChainMap.keys(config.chainMap);
481
- if (!match) {
482
- return;
483
- }
484
- if (match$1.length !== 1) {
485
- return;
486
- }
487
- let chainId = match$1[0];
488
- return Object.fromEntries([[
489
- "chainId",
490
- S$RescriptSchema.reverseConvertToJsonOrThrow(chainId, ChainId.schema)
491
- ]]);
475
+ let match = config.checkpointSequence;
476
+ return match !== "SharedAcrossChains";
492
477
  }
493
478
 
494
479
  function isolate(config, chainIds) {
@@ -928,13 +913,14 @@ function prime(json) {
928
913
  cached.contents = undefined;
929
914
  }
930
915
 
931
- function withIsolatedChains(json, chainIds) {
916
+ function withCommandFields(json, chainIds, isDev) {
932
917
  let fields = Stdlib_JSON.Decode.object(json);
933
918
  if (fields === undefined) {
934
919
  return Stdlib_JsError.throwWithMessage("Invalid indexer config: not an object");
935
920
  }
936
921
  let narrowed = Object.assign({}, fields);
937
922
  narrowed["isolatedChains"] = S$RescriptSchema.reverseConvertToJsonOrThrow(chainIds, S$RescriptSchema.array(ChainId.schema));
923
+ narrowed["isDev"] = isDev;
938
924
  return narrowed;
939
925
  }
940
926
 
@@ -1208,14 +1194,13 @@ export {
1208
1194
  contractMappingOf,
1209
1195
  getChain,
1210
1196
  isPerChain,
1211
- logContext,
1212
1197
  isolate,
1213
1198
  fromPublic,
1214
1199
  normalizeUserAddress,
1215
1200
  normalizeSimulateAddress,
1216
1201
  getEventConfig,
1217
1202
  prime,
1218
- withIsolatedChains,
1203
+ withCommandFields,
1219
1204
  getPublicConfigJson,
1220
1205
  stripSensitiveData,
1221
1206
  envioInfo,
@@ -70,10 +70,10 @@ let isHoldingRealtime = (crossChainState: t) => crossChainState.holdRealtime
70
70
  let hasArrivedAtHead = (crossChainState: t) =>
71
71
  crossChainState.isCaughtUp ||
72
72
  crossChainState.isRealtime || {
73
- let chainStates = crossChainState.chainStates->Dict.valuesToArray
74
- chainStates->Utils.Array.notEmpty &&
75
- chainStates->Array.every(ChainState.isReadyToEnterReorgThreshold)
76
- }
73
+ let chainStates = crossChainState.chainStates->Dict.valuesToArray
74
+ chainStates->Utils.Array.notEmpty &&
75
+ chainStates->Array.every(ChainState.isReadyToEnterReorgThreshold)
76
+ }
77
77
 
78
78
  // Resolve a chain's state by id. The id always comes from `chainIds`, which is
79
79
  // derived from `chainStates`, so the entry is guaranteed present.
@@ -167,17 +167,23 @@ let createBatch = (
167
167
  // holds the others back whatever process it runs in.
168
168
  let isReadyToEnterReorgThreshold = (crossChainState: t, ~batch) =>
169
169
  !crossChainState.holdRealtime &&
170
- crossChainState.chainStates
171
- ->Dict.valuesToArray
172
- ->Array.every(cs => cs->ChainState.isReadyToEnterReorgThresholdAfterBatch(~batch))
170
+ crossChainState.chainStates
171
+ ->Dict.valuesToArray
172
+ ->Array.every(cs => cs->ChainState.isReadyToEnterReorgThresholdAfterBatch(~batch))
173
173
 
174
+ // Said by each chain rather than once for the indexer: what crossing changes
175
+ // is a chain's own, and the chains of a split run cross in processes that can
176
+ // only speak for the ones they drive.
174
177
  let enterReorgThreshold = (crossChainState: t) => {
175
- Logging.info("Reorg threshold reached")
176
-
177
178
  for i in 0 to crossChainState.chainIds->Array.length - 1 {
178
- crossChainState
179
- ->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
180
- ->ChainState.enterReorgThreshold
179
+ let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
180
+ // A chain whose end block sits below these blocks says nothing: crossing
181
+ // gives it nothing more to index, and it will never reach one of them.
182
+ let liftsCeiling = cs->ChainState.reorgThresholdLiftsCeiling
183
+ cs->ChainState.enterReorgThreshold
184
+ if liftsCeiling {
185
+ cs->ChainState.logger->Logging.childInfo(ChainState.reorgThresholdEntryMessage)
186
+ }
181
187
  }
182
188
  }
183
189
 
@@ -193,16 +199,16 @@ let applyBatchProgress = (crossChainState: t, ~batch: Batch.t, ~blockTimestampNa
193
199
  for i in 0 to chainIds->Array.length - 1 {
194
200
  let cs = crossChainState->getChainState(chainIds->Array.getUnsafe(i))
195
201
  cs->ChainState.applyBatchProgress(~batch, ~blockTimestampName)
196
- if !(cs->ChainState.hasProcessedToEndblock || cs->ChainState.isProgressAtHead) {
202
+ if !(cs->ChainState.hasCaughtUp) {
197
203
  everyChainCaughtUp := false
198
204
  }
199
205
  }
200
206
 
201
207
  crossChainState.isCaughtUp =
202
208
  crossChainState.isCaughtUp ||
203
- (!crossChainState.holdRealtime &&
204
- crossChainState->nextItemIsNone &&
205
- everyChainCaughtUp.contents)
209
+ (!crossChainState.holdRealtime &&
210
+ crossChainState->nextItemIsNone &&
211
+ everyChainCaughtUp.contents)
206
212
  }
207
213
 
208
214
  // Every chain has buffered up to its head (or endblock) with nothing
@@ -248,7 +254,7 @@ let markCaughtUpOnResume = (crossChainState: t) => {
248
254
  let everyChainCaughtUp = ref(crossChainState.chainIds->Array.length > 0)
249
255
  for i in 0 to crossChainState.chainIds->Array.length - 1 {
250
256
  let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
251
- if !(cs->ChainState.isDurablyCaughtUp) {
257
+ if !(cs->ChainState.hasCaughtUp) {
252
258
  everyChainCaughtUp := false
253
259
  }
254
260
  }
@@ -263,13 +269,55 @@ let markCaughtUpOnResume = (crossChainState: t) => {
263
269
  // and switches the indexer to realtime.
264
270
  let markReady = (crossChainState: t, ~readyAt) => {
265
271
  for i in 0 to crossChainState.chainIds->Array.length - 1 {
266
- crossChainState
267
- ->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
268
- ->ChainState.markReady(~readyAt)
272
+ let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
273
+ let wasReady = cs->ChainState.isReady
274
+ cs->ChainState.markReady(~readyAt)
275
+
276
+ // One line per chain, because `ready_at` is one column per chain: what the
277
+ // log says and what a reader finds in the row are the same fact.
278
+ if !wasReady {
279
+ cs
280
+ ->ChainState.logger
281
+ ->Logging.childInfo("Ready. Fully indexed for queries.")
282
+ }
269
283
  }
270
284
  crossChainState.isRealtime = true
271
285
  }
272
286
 
287
+ // What a caught-up chain is waiting on before its process can finalize: the run
288
+ // holding the process back, or another chain it drives still behind. Read when
289
+ // the chain speaks, so "waiting" is said only while it is true.
290
+ let isWaitingOnOthers = (crossChainState: t, cs: ChainState.t) =>
291
+ crossChainState.holdRealtime ||
292
+ crossChainState.chainStates
293
+ ->Dict.valuesToArray
294
+ ->Array.some(other => other !== cs && !(other->ChainState.hasCaughtUp))
295
+
296
+ // Each chain that has just finished indexing, said once, by the chain it is
297
+ // about — so a chain that finishes early says so then, rather than when the
298
+ // last chain in its process catches up.
299
+ let reportFinished = (crossChainState: t) =>
300
+ crossChainState.chainStates
301
+ ->Dict.valuesToArray
302
+ ->Array.forEach(cs =>
303
+ switch cs->ChainState.takeFinished {
304
+ | Some(EndBlock(block)) =>
305
+ cs
306
+ ->ChainState.logger
307
+ ->Logging.childInfo({"msg": "Indexed to the end block. This chain is done.", "block": block})
308
+ | Some(Backfill(block)) =>
309
+ cs
310
+ ->ChainState.logger
311
+ ->Logging.childInfo({
312
+ "msg": crossChainState->isWaitingOnOthers(cs)
313
+ ? "Finished backfill. Waiting for the other chains."
314
+ : "Finished backfill.",
315
+ "block": block,
316
+ })
317
+ | None => ()
318
+ }
319
+ )
320
+
273
321
  // --- Fetch control. ---
274
322
 
275
323
  // Chains ordered furthest-behind first by fetch-frontier progress, so the