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
@@ -47,6 +47,15 @@ function makeSelection(onEventRegistrations, dependsOnAddresses, clientFilteredC
47
47
  };
48
48
  }
49
49
 
50
+ function floorAtSelectionStart(latestFetchedBlock, selection) {
51
+ let startBlock = selection.startBlock;
52
+ if (startBlock !== undefined) {
53
+ return Primitive_int.max(latestFetchedBlock, startBlock - 1 | 0);
54
+ } else {
55
+ return latestFetchedBlock;
56
+ }
57
+ }
58
+
50
59
  function withAddresses(p, addresses) {
51
60
  return {
52
61
  id: p.id,
@@ -305,6 +314,26 @@ function getAnchorSafeBlock(p) {
305
314
  }
306
315
 
307
316
  function make(partitions, maxAddrInPartition, nextPartitionIndex, dynamicContracts, clientFilteredContracts) {
317
+ let partitions$1 = partitions.map(p => {
318
+ let floored = floorAtSelectionStart(p.latestFetchedBlock, p.selection);
319
+ if (floored === p.latestFetchedBlock) {
320
+ return p;
321
+ } else {
322
+ return {
323
+ id: p.id,
324
+ latestFetchedBlock: floored,
325
+ selection: p.selection,
326
+ addresses: p.addresses,
327
+ mergeBlock: p.mergeBlock,
328
+ dynamicContract: p.dynamicContract,
329
+ mutPendingQueries: p.mutPendingQueries,
330
+ sourceRangeCapacity: p.sourceRangeCapacity,
331
+ prevSourceRangeCapacity: p.prevSourceRangeCapacity,
332
+ eventDensity: p.eventDensity,
333
+ latestSourceRangeCapacityUpdateBlock: p.latestSourceRangeCapacityUpdateBlock
334
+ };
335
+ }
336
+ });
308
337
  let newPartitions = [];
309
338
  let mergingPartitions = {};
310
339
  let nextPartitionIndexRef = {
@@ -313,7 +342,7 @@ function make(partitions, maxAddrInPartition, nextPartitionIndex, dynamicContrac
313
342
  let anchorSafeBlocks = {};
314
343
  let anchoredContractsSet = new Set();
315
344
  if (clientFilteredContracts.size > 0) {
316
- partitions.forEach(p => {
345
+ partitions$1.forEach(p => {
317
346
  let match = p.mergeBlock;
318
347
  let match$1 = p.selection.clientFilteredContracts;
319
348
  if (match !== undefined) {
@@ -332,8 +361,8 @@ function make(partitions, maxAddrInPartition, nextPartitionIndex, dynamicContrac
332
361
  });
333
362
  });
334
363
  }
335
- for (let idx = 0, idx_finish = partitions.length; idx < idx_finish; ++idx) {
336
- let p = partitions[idx];
364
+ for (let idx = 0, idx_finish = partitions$1.length; idx < idx_finish; ++idx) {
365
+ let p = partitions$1[idx];
337
366
  let mergeBlock = p.mergeBlock;
338
367
  if (mergeBlock !== undefined) {
339
368
  if (p.latestFetchedBlock < mergeBlock || isFetching(p)) {
@@ -676,20 +705,8 @@ function bufferBlockNumber(param) {
676
705
  let optimizedPartitions = param.optimizedPartitions;
677
706
  let id = optimizedPartitions.idsInAscOrder[0];
678
707
  let latestFullyFetchedBlock = id !== undefined ? optimizedPartitions.entities[id].latestFetchedBlock : undefined;
679
- if (latestFullyFetchedBlock !== undefined && latestOnBlockBlockNumber >= latestFullyFetchedBlock) {
680
- return latestFullyFetchedBlock;
681
- } else {
682
- return latestOnBlockBlockNumber;
683
- }
684
- }
685
-
686
- function bufferBlock(param) {
687
- let latestOnBlockBlockNumber = param.latestOnBlockBlockNumber;
688
- let optimizedPartitions = param.optimizedPartitions;
689
- let id = optimizedPartitions.idsInAscOrder[0];
690
- let latestFullyFetchedBlock = id !== undefined ? optimizedPartitions.entities[id].latestFetchedBlock : undefined;
691
- if (latestFullyFetchedBlock !== undefined && latestOnBlockBlockNumber >= latestFullyFetchedBlock) {
692
- return latestFullyFetchedBlock;
708
+ if (latestFullyFetchedBlock !== undefined) {
709
+ return Primitive_int.min(latestOnBlockBlockNumber, latestFullyFetchedBlock);
693
710
  } else {
694
711
  return latestOnBlockBlockNumber;
695
712
  }
@@ -795,6 +812,19 @@ function compareBufferItem(a, b) {
795
812
  }
796
813
  }
797
814
 
815
+ function isSameLog(a, b) {
816
+ if (a.kind !== 0 || b.kind !== 0 || a.blockNumber !== b.blockNumber || a.logIndex !== b.logIndex) {
817
+ return false;
818
+ }
819
+ let match = a.orderPath;
820
+ let match$1 = b.orderPath;
821
+ if ((match == null) || (match$1 == null)) {
822
+ return true;
823
+ } else {
824
+ return comparePath(match, match$1) === 0;
825
+ }
826
+ }
827
+
798
828
  function mergeIntoBuffer(buffer, newItems) {
799
829
  let n = newItems.length;
800
830
  for (let i = 1; i < n; ++i) {
@@ -884,14 +914,15 @@ function updateInternal(fetchState, optimizedPartitionsOpt, mutItems, mutItemsSo
884
914
  let latestOnBlockBlockNumber;
885
915
  if (onBlockRegistrations.length !== 0) {
886
916
  let item = base[fetchState.maxOnBlockBufferSize - 1 | 0];
887
- let maxBlockNumber;
917
+ let tmp;
888
918
  if (item !== undefined) {
889
- maxBlockNumber = item.blockNumber;
919
+ tmp = item.blockNumber;
890
920
  } else {
891
921
  let id = optimizedPartitions.idsInAscOrder[0];
892
922
  let latestFullyFetchedBlock = id !== undefined ? optimizedPartitions.entities[id].latestFetchedBlock : undefined;
893
- maxBlockNumber = latestFullyFetchedBlock !== undefined ? latestFullyFetchedBlock : knownHeight;
923
+ tmp = latestFullyFetchedBlock !== undefined ? latestFullyFetchedBlock : knownHeight;
894
924
  }
925
+ let maxBlockNumber = Primitive_int.min(tmp, knownHeight);
895
926
  latestOnBlockBlockNumber = appendOnBlockItems(blockItems, onBlockRegistrations, fetchState.startBlock, fetchState.latestOnBlockBlockNumber, maxBlockNumber, fetchState.maxOnBlockBufferSize);
896
927
  } else {
897
928
  latestOnBlockBlockNumber = knownHeight;
@@ -1473,19 +1504,11 @@ function walkPartitionPending(p, partitionId, inFlightCount, candidates, headBlo
1473
1504
  }
1474
1505
  pqIdx = pqIdx + 1 | 0;
1475
1506
  };
1476
- let startBlock = p.selection.startBlock;
1477
- let cursor$1;
1478
- if (startBlock !== undefined && startBlock > cursor) {
1479
- let reachable = Utils.$$Math.minOptInt(headBlockNumber, queryEndBlock);
1480
- cursor$1 = reachable !== undefined && startBlock <= reachable ? startBlock : cursor;
1481
- } else {
1482
- cursor$1 = cursor;
1483
- }
1484
1507
  if (canContinue) {
1485
1508
  return {
1486
1509
  partitionId: partitionId,
1487
1510
  p: p,
1488
- cursor: cursor$1,
1511
+ cursor: cursor,
1489
1512
  chunksUsedThisCall: chunksUsedThisCall,
1490
1513
  inFlightCount: inFlightCount,
1491
1514
  queryEndBlock: queryEndBlock,
@@ -1787,13 +1810,21 @@ function make$1(startBlock, endBlock, onEventRegistrations, addressStore, addres
1787
1810
  }
1788
1811
  let buffer = [];
1789
1812
  let latestOnBlockBlockNumber;
1790
- if (knownHeight > 0 && Utils.$$Array.notEmpty(onBlockRegistrations)) {
1791
- let id = optimizedPartitions.idsInAscOrder[0];
1792
- let latestFullyFetchedBlock = id !== undefined ? optimizedPartitions.entities[id].latestFetchedBlock : undefined;
1793
- let maxBlockNumber = latestFullyFetchedBlock !== undefined ? latestFullyFetchedBlock : knownHeight;
1794
- latestOnBlockBlockNumber = appendOnBlockItems(buffer, onBlockRegistrations, startBlock, progressBlockNumber, maxBlockNumber, maxOnBlockBufferSize);
1813
+ let exit = 0;
1814
+ if (onBlockRegistrations.length !== 0 || knownHeight <= 0) {
1815
+ exit = 1;
1795
1816
  } else {
1796
- latestOnBlockBlockNumber = progressBlockNumber;
1817
+ latestOnBlockBlockNumber = knownHeight;
1818
+ }
1819
+ if (exit === 1) {
1820
+ if (knownHeight > 0) {
1821
+ let id = optimizedPartitions.idsInAscOrder[0];
1822
+ let latestFullyFetchedBlock = id !== undefined ? optimizedPartitions.entities[id].latestFetchedBlock : undefined;
1823
+ let maxBlockNumber = latestFullyFetchedBlock !== undefined ? Primitive_int.min(latestFullyFetchedBlock, knownHeight) : knownHeight;
1824
+ latestOnBlockBlockNumber = appendOnBlockItems(buffer, onBlockRegistrations, startBlock, progressBlockNumber, maxBlockNumber, maxOnBlockBufferSize);
1825
+ } else {
1826
+ latestOnBlockBlockNumber = progressBlockNumber;
1827
+ }
1797
1828
  }
1798
1829
  return {
1799
1830
  optimizedPartitions: optimizedPartitions,
@@ -2098,6 +2129,7 @@ let chunkRangeGrowthFactor = 1.8;
2098
2129
  export {
2099
2130
  deriveSelectionStartBlock,
2100
2131
  makeSelection,
2132
+ floorAtSelectionStart,
2101
2133
  withAddresses,
2102
2134
  narrowSelectionToRange,
2103
2135
  densityItemsTarget,
@@ -2107,12 +2139,12 @@ export {
2107
2139
  getMinQueryRange,
2108
2140
  OptimizedPartitions,
2109
2141
  bufferBlockNumber,
2110
- bufferBlock,
2111
2142
  bufferReadyCount,
2112
2143
  getRegistrationIndex,
2113
2144
  comparePath,
2114
2145
  compareTiebreak,
2115
2146
  compareBufferItem,
2147
+ isSameLog,
2116
2148
  mergeIntoBuffer,
2117
2149
  appendOnBlockItems,
2118
2150
  updateInternal,
@@ -1,10 +1,14 @@
1
- // The FinalizingIndexes phase. Reached from the processing loop when every
2
- // chain has caught up: processing is already paused (the loop awaits this),
3
- // pending writes are flushed, then storage builds every missing schema-defined
4
- // index and, once they all verify, commits `ready_at`. A failure part-way
5
- // leaves the indexes built so far in place and reaches the processing loop's
6
- // error boundary; the retry only owes what's left. Either way the indexer never
7
- // reports ready with an index the schema promised still missing.
1
+ // The FinalizingIndexes phase. Reached from the processing loop when the chains
2
+ // this process drives have caught up: processing is already paused (the loop
3
+ // awaits this), pending writes are flushed, then storage builds every index the
4
+ // schema promises for those chains and, once they all verify, commits their
5
+ // `ready_at`. A failure part-way leaves the indexes built so far in place and
6
+ // reaches the processing loop's error boundary; the retry only owes what's left.
7
+ // Either way a chain never reports ready with an index the schema promised for
8
+ // it still missing.
9
+ //
10
+ // A chain's rows live in a partition of its own, so this touches nothing another
11
+ // `envio start --chain` process is indexing and never waits on one.
8
12
 
9
13
  let runOnce = async (state: IndexerState.t) => {
10
14
  Logging.info(
@@ -22,10 +26,7 @@ let runOnce = async (state: IndexerState.t) => {
22
26
 
23
27
  await storage.finalizeBackfill(
24
28
  ~entities=persistence.allEntities,
25
- ~chainIds=state
26
- ->IndexerState.chainStates
27
- ->Dict.valuesToArray
28
- ->Array.map(cs => (cs->ChainState.chainConfig).id),
29
+ ~chainIds=state->IndexerState.crossChainState->CrossChainState.chainIds,
29
30
  ~readyAt,
30
31
  )
31
32
 
@@ -57,5 +58,8 @@ let run = (state: IndexerState.t) =>
57
58
  let repairSchemaIndexes = (state: IndexerState.t) => {
58
59
  let persistence = state->IndexerState.persistence
59
60
  let storage = persistence->Persistence.getInitializedStorageOrThrow
60
- storage.ensureSchemaIndexes(~entities=persistence.allEntities)
61
+ storage.ensureSchemaIndexes(
62
+ ~entities=persistence.allEntities,
63
+ ~chainIds=state->IndexerState.crossChainState->CrossChainState.chainIds,
64
+ )
61
65
  }
@@ -2,9 +2,9 @@
2
2
 
3
3
  import * as Logging from "./Logging.res.mjs";
4
4
  import * as Writing from "./Writing.res.mjs";
5
- import * as ChainState from "./ChainState.res.mjs";
6
5
  import * as Persistence from "./Persistence.res.mjs";
7
6
  import * as IndexerState from "./IndexerState.res.mjs";
7
+ import * as CrossChainState from "./CrossChainState.res.mjs";
8
8
 
9
9
  async function runOnce(state) {
10
10
  Logging.info("All chains are caught up. Finalizing the indexer before switching to realtime: flushing pending writes, then creating the indexes the schema promises.");
@@ -15,7 +15,7 @@ async function runOnce(state) {
15
15
  let persistence = IndexerState.persistence(state);
16
16
  let storage = Persistence.getInitializedStorageOrThrow(persistence);
17
17
  let readyAt = new Date();
18
- await storage.finalizeBackfill(persistence.allEntities, Object.values(IndexerState.chainStates(state)).map(cs => ChainState.chainConfig(cs).id), readyAt);
18
+ await storage.finalizeBackfill(persistence.allEntities, CrossChainState.chainIds(IndexerState.crossChainState(state)), readyAt);
19
19
  IndexerState.markReady(state, readyAt);
20
20
  return Logging.info("The indexer is ready. Switching to realtime indexing.");
21
21
  }
@@ -33,7 +33,7 @@ function run(state) {
33
33
  function repairSchemaIndexes(state) {
34
34
  let persistence = IndexerState.persistence(state);
35
35
  let storage = Persistence.getInitializedStorageOrThrow(persistence);
36
- return storage.ensureSchemaIndexes(persistence.allEntities);
36
+ return storage.ensureSchemaIndexes(persistence.allEntities, CrossChainState.chainIds(IndexerState.crossChainState(state)));
37
37
  }
38
38
 
39
39
  export {
@@ -0,0 +1,46 @@
1
+ // Whether each chain's rows get history on a write, keyed by chain id. History
2
+ // is only ever kept for what a rollback could reach: under one shared sequence
3
+ // any chain's rollback reaches every chain's rows, so the run decides together;
4
+ // with a counter per chain only the group's own chain can reach its rows.
5
+ type t = dict<bool>
6
+
7
+ %%private(
8
+ let anyChainSaves = (shouldSaveHistory: dict<bool>) =>
9
+ shouldSaveHistory->Dict.valuesToArray->Array.some(saves => saves)
10
+ )
11
+
12
+ let decide = (config: Config.t, ~shouldSaveHistory: dict<bool>): t =>
13
+ if config.shouldSaveFullHistory {
14
+ shouldSaveHistory->Utils.Dict.mapValues(_ => true)
15
+ } else {
16
+ switch config.checkpointSequence {
17
+ | SharedAcrossChains =>
18
+ let saves = shouldSaveHistory->anyChainSaves
19
+ shouldSaveHistory->Utils.Dict.mapValues(_ => saves)
20
+ | PerChain => shouldSaveHistory
21
+ }
22
+ }
23
+
24
+ // Every chain the run indexes was decided for, so an absent one is a bug —
25
+ // answering `false` for it would silently leave the chain with nothing to roll
26
+ // back to.
27
+ let forChain = (t: t, chainId: ChainId.t): bool =>
28
+ switch t->ChainId.Dict.dangerouslyGetNonOption(chainId) {
29
+ | Some(saves) => saves
30
+ | None =>
31
+ JsError.throwWithMessage(
32
+ `Internal error: no history decision for chain ${chainId->ChainId.toString}. The policy is decided for every chain the run indexes.`,
33
+ )
34
+ }
35
+
36
+ // A cross-chain group's rows are reachable by any chain's rollback.
37
+ let forScope = (t: t, ~scope: Internal.chainScope): bool =>
38
+ switch scope {
39
+ | Chain(chainId) => t->forChain(chainId)
40
+ | CrossChain => t->anyChainSaves
41
+ }
42
+
43
+ // Whether a run has stale history to prune at all: history it keeps but doesn't
44
+ // keep forever.
45
+ let mayPrune = (config: Config.t, ~shouldSaveHistory: dict<bool>) =>
46
+ !config.shouldSaveFullHistory && shouldSaveHistory->anyChainSaves
@@ -0,0 +1,50 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Utils from "./Utils.res.mjs";
4
+ import * as ChainId from "./ChainId.res.mjs";
5
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
6
+
7
+ function decide(config, shouldSaveHistory) {
8
+ if (config.shouldSaveFullHistory) {
9
+ return Utils.Dict.mapValues(shouldSaveHistory, param => true);
10
+ }
11
+ let match = config.checkpointSequence;
12
+ if (match !== "SharedAcrossChains") {
13
+ return shouldSaveHistory;
14
+ }
15
+ let saves = Object.values(shouldSaveHistory).some(saves => saves);
16
+ return Utils.Dict.mapValues(shouldSaveHistory, param => saves);
17
+ }
18
+
19
+ function forChain(t, chainId) {
20
+ let saves = t[chainId];
21
+ if (saves !== undefined) {
22
+ return saves;
23
+ } else {
24
+ return Stdlib_JsError.throwWithMessage(`Internal error: no history decision for chain ` + ChainId.toString(chainId) + `. The policy is decided for every chain the run indexes.`);
25
+ }
26
+ }
27
+
28
+ function forScope(t, scope) {
29
+ if (scope === "crossChain") {
30
+ return Object.values(t).some(saves => saves);
31
+ } else {
32
+ return forChain(t, scope);
33
+ }
34
+ }
35
+
36
+ function mayPrune(config, shouldSaveHistory) {
37
+ if (config.shouldSaveFullHistory) {
38
+ return false;
39
+ } else {
40
+ return Object.values(shouldSaveHistory).some(saves => saves);
41
+ }
42
+ }
43
+
44
+ export {
45
+ decide,
46
+ forChain,
47
+ forScope,
48
+ mayPrune,
49
+ }
50
+ /* Utils Not a pure module */
@@ -49,10 +49,7 @@ let getEffectInMemTable = (
49
49
  ) => state->IndexerState.effectState->EffectState.getTable(~effect, ~scope)
50
50
 
51
51
  let hasEffectOutput = (inMemTable: EffectState.effectCacheInMemTable, key) =>
52
- switch inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(key) {
53
- | Some(Set(_)) => true
54
- | Some(Delete(_)) | None => false
55
- }
52
+ inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(key)->Option.isSome
56
53
 
57
54
  // Returns the raw output. The output is itself an option for effects with an
58
55
  // optional output, so it must never be wrapped in another option here: Some(None)
@@ -62,14 +59,15 @@ let getEffectOutputUnsafe = (
62
59
  key,
63
60
  ): Internal.effectOutput =>
64
61
  switch inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(key) {
65
- | Some(Set({entity: output})) => output
66
- | Some(Delete(_)) | None => %raw(`undefined`)
62
+ | Some({output}) => output
63
+ | None => %raw(`undefined`)
67
64
  }
68
65
 
69
66
  // Records a handler output. Persisted on the next write only when shouldCache;
70
67
  // otherwise kept in memory (re-run on a later miss) but never written to the db.
71
68
  let setEffectOutput = (
72
69
  inMemTable: EffectState.effectCacheInMemTable,
70
+ ~chainId,
73
71
  ~checkpointId,
74
72
  ~cacheKey,
75
73
  ~output,
@@ -79,10 +77,7 @@ let setEffectOutput = (
79
77
  | Some(_) => ()
80
78
  | None => inMemTable.changesCount = inMemTable.changesCount +. 1.
81
79
  }
82
- inMemTable.dict->Dict.set(
83
- cacheKey,
84
- Set({entityId: cacheKey->EntityId.unsafeOfString, entity: output, checkpointId}),
85
- )
80
+ inMemTable.dict->Dict.set(cacheKey, {output, chainId, checkpointId})
86
81
  if shouldCache {
87
82
  inMemTable.idsToStore->Array.push(cacheKey)->ignore
88
83
  }
@@ -90,16 +85,17 @@ let setEffectOutput = (
90
85
 
91
86
  // Seeds an entry from a db read. Stamped with loadedFromDbCheckpointId so it's
92
87
  // always droppable (re-readable from the db) and never re-persisted.
93
- let initEffectOutputFromDb = (inMemTable: EffectState.effectCacheInMemTable, ~cacheKey, ~output) =>
88
+ let initEffectOutputFromDb = (
89
+ inMemTable: EffectState.effectCacheInMemTable,
90
+ ~chainId,
91
+ ~cacheKey,
92
+ ~output,
93
+ ) =>
94
94
  if inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(cacheKey)->Option.isNone {
95
95
  inMemTable.changesCount = inMemTable.changesCount +. 1.
96
96
  inMemTable.dict->Dict.set(
97
97
  cacheKey,
98
- Set({
99
- entityId: cacheKey->EntityId.unsafeOfString,
100
- entity: output,
101
- checkpointId: Internal.loadedFromDbCheckpointId,
102
- }),
98
+ {output, chainId, checkpointId: Internal.loadedFromDbCheckpointId},
103
99
  )
104
100
  }
105
101
 
@@ -108,14 +104,13 @@ let initEffectOutputFromDb = (inMemTable: EffectState.effectCacheInMemTable, ~ca
108
104
  // seeded from a db read are spared. Mirrors entity dropCommittedChanges.
109
105
  let dropCommittedEffects = (
110
106
  inMemTable: EffectState.effectCacheInMemTable,
111
- ~committedCheckpointId,
107
+ ~committedFrontier: Frontier.t,
112
108
  ~keepLoadedFromDb,
113
109
  ) => {
114
110
  let keysToDelete = []
115
- inMemTable.dict->Utils.Dict.forEachWithKey((change, key) => {
116
- let checkpointId = change->Change.getCheckpointId
111
+ inMemTable.dict->Utils.Dict.forEachWithKey(({chainId, checkpointId}, key) => {
117
112
  if (
118
- !(checkpointId > committedCheckpointId) &&
113
+ !(checkpointId > committedFrontier->Frontier.get(chainId)) &&
119
114
  !(keepLoadedFromDb && checkpointId == Internal.loadedFromDbCheckpointId)
120
115
  ) {
121
116
  keysToDelete->Array.push(key)
@@ -128,18 +123,21 @@ let dropCommittedEffects = (
128
123
  let prepareRollbackDiff = async (
129
124
  state: IndexerState.t,
130
125
  ~floors: RollbackFloors.t,
131
- ~rollbackDiffCheckpointId,
132
126
  ~progressedChains,
133
127
  ~rolledBackAddresses,
134
128
  ) => {
135
- state->IndexerState.beginRollbackDiff(
136
- ~diffCheckpointId=rollbackDiffCheckpointId,
137
- ~floors,
138
- ~progressedChains,
139
- ~rolledBackAddresses,
140
- )
129
+ let diffFrontier =
130
+ state->IndexerState.beginRollbackDiff(~floors, ~progressedChains, ~rolledBackAddresses)
141
131
  let persistence = state->IndexerState.persistence
142
- let committedCheckpointId = state->IndexerState.committedCheckpointId
132
+ let sequence = (state->IndexerState.config).checkpointSequence
133
+ let diffCheckpointId = scope =>
134
+ switch sequence->CheckpointSequence.findForScope(diffFrontier, ~scope) {
135
+ | Some(checkpointId) => checkpointId
136
+ | None =>
137
+ JsError.throwWithMessage(
138
+ "Internal error: the rollback returned rows for a chain its diff never moved.",
139
+ )
140
+ }
143
141
 
144
142
  let deletedEntities = Dict.make()
145
143
  let setEntities = Dict.make()
@@ -160,10 +158,10 @@ let prepareRollbackDiff = async (
160
158
  state
161
159
  ->getInMemTable(~entityConfig, ~scope)
162
160
  ->InMemoryTable.Entity.set(
163
- ~committedCheckpointId,
161
+ ~committedCheckpointId=state->IndexerState.committedCheckpointIdFor(~scope),
164
162
  Delete({
165
163
  entityId,
166
- checkpointId: rollbackDiffCheckpointId,
164
+ checkpointId: diffCheckpointId(scope),
167
165
  }),
168
166
  )
169
167
  })
@@ -174,10 +172,10 @@ let prepareRollbackDiff = async (
174
172
  state
175
173
  ->getInMemTable(~entityConfig, ~scope)
176
174
  ->InMemoryTable.Entity.set(
177
- ~committedCheckpointId,
175
+ ~committedCheckpointId=state->IndexerState.committedCheckpointIdFor(~scope),
178
176
  Set({
179
177
  entityId: entity.id->EntityId.unsafeOfString,
180
- checkpointId: rollbackDiffCheckpointId,
178
+ checkpointId: diffCheckpointId(scope),
181
179
  entity,
182
180
  }),
183
181
  )
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as Table from "./db/Table.res.mjs";
4
4
  import * as Utils from "./Utils.res.mjs";
5
+ import * as Frontier from "./db/Frontier.res.mjs";
5
6
  import * as Internal from "./Internal.res.mjs";
6
7
  import * as ChainState from "./ChainState.res.mjs";
7
8
  import * as EffectState from "./EffectState.res.mjs";
@@ -11,6 +12,7 @@ import * as InMemoryTable from "./InMemoryTable.res.mjs";
11
12
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
12
13
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
13
14
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
15
+ import * as CheckpointSequence from "./db/CheckpointSequence.res.mjs";
14
16
 
15
17
  function getInMemTable(state, entityConfig, scope) {
16
18
  let tmp;
@@ -45,23 +47,19 @@ function getEffectInMemTable(state, effect, scope) {
45
47
  }
46
48
 
47
49
  function hasEffectOutput(inMemTable, key) {
48
- let match = inMemTable.dict[key];
49
- if (match !== undefined) {
50
- return match.type === "SET";
51
- } else {
52
- return false;
53
- }
50
+ return Stdlib_Option.isSome(inMemTable.dict[key]);
54
51
  }
55
52
 
56
53
  function getEffectOutputUnsafe(inMemTable, key) {
57
54
  let match = inMemTable.dict[key];
58
- if (match !== undefined && match.type === "SET") {
59
- return match.entity;
55
+ if (match !== undefined) {
56
+ return match.output;
57
+ } else {
58
+ return undefined;
60
59
  }
61
- return undefined;
62
60
  }
63
61
 
64
- function setEffectOutput(inMemTable, checkpointId, cacheKey, output, shouldCache) {
62
+ function setEffectOutput(inMemTable, chainId, checkpointId, cacheKey, output, shouldCache) {
65
63
  let match = inMemTable.dict[cacheKey];
66
64
  if (match !== undefined) {
67
65
 
@@ -69,9 +67,8 @@ function setEffectOutput(inMemTable, checkpointId, cacheKey, output, shouldCache
69
67
  inMemTable.changesCount = inMemTable.changesCount + 1;
70
68
  }
71
69
  inMemTable.dict[cacheKey] = {
72
- type: "SET",
73
- entityId: cacheKey,
74
- entity: output,
70
+ output: output,
71
+ chainId: chainId,
75
72
  checkpointId: checkpointId
76
73
  };
77
74
  if (shouldCache) {
@@ -80,24 +77,23 @@ function setEffectOutput(inMemTable, checkpointId, cacheKey, output, shouldCache
80
77
  }
81
78
  }
82
79
 
83
- function initEffectOutputFromDb(inMemTable, cacheKey, output) {
80
+ function initEffectOutputFromDb(inMemTable, chainId, cacheKey, output) {
84
81
  if (Stdlib_Option.isNone(inMemTable.dict[cacheKey])) {
85
82
  inMemTable.changesCount = inMemTable.changesCount + 1;
86
83
  inMemTable.dict[cacheKey] = {
87
- type: "SET",
88
- entityId: cacheKey,
89
- entity: output,
84
+ output: output,
85
+ chainId: chainId,
90
86
  checkpointId: Internal.loadedFromDbCheckpointId
91
87
  };
92
88
  return;
93
89
  }
94
90
  }
95
91
 
96
- function dropCommittedEffects(inMemTable, committedCheckpointId, keepLoadedFromDb) {
92
+ function dropCommittedEffects(inMemTable, committedFrontier, keepLoadedFromDb) {
97
93
  let keysToDelete = [];
98
- Utils.Dict.forEachWithKey(inMemTable.dict, (change, key) => {
99
- let checkpointId = change.checkpointId;
100
- if (checkpointId <= committedCheckpointId && !(keepLoadedFromDb && checkpointId === Internal.loadedFromDbCheckpointId)) {
94
+ Utils.Dict.forEachWithKey(inMemTable.dict, (param, key) => {
95
+ let checkpointId = param.checkpointId;
96
+ if (checkpointId <= Frontier.get(committedFrontier, param.chainId) && !(keepLoadedFromDb && checkpointId === Internal.loadedFromDbCheckpointId)) {
101
97
  keysToDelete.push(key);
102
98
  return;
103
99
  }
@@ -106,31 +102,40 @@ function dropCommittedEffects(inMemTable, committedCheckpointId, keepLoadedFromD
106
102
  inMemTable.changesCount = inMemTable.changesCount - keysToDelete.length;
107
103
  }
108
104
 
109
- async function prepareRollbackDiff(state, floors, rollbackDiffCheckpointId, progressedChains, rolledBackAddresses) {
110
- IndexerState.beginRollbackDiff(state, rollbackDiffCheckpointId, floors, progressedChains, rolledBackAddresses);
105
+ async function prepareRollbackDiff(state, floors, progressedChains, rolledBackAddresses) {
106
+ let diffFrontier = IndexerState.beginRollbackDiff(state, floors, progressedChains, rolledBackAddresses);
111
107
  let persistence = IndexerState.persistence(state);
112
- let committedCheckpointId = IndexerState.committedCheckpointId(state);
108
+ let sequence = IndexerState.config(state).checkpointSequence;
109
+ let diffCheckpointId = scope => {
110
+ let checkpointId = CheckpointSequence.findForScope(sequence, diffFrontier, scope);
111
+ if (checkpointId !== undefined) {
112
+ return checkpointId;
113
+ } else {
114
+ return Stdlib_JsError.throwWithMessage("Internal error: the rollback returned rows for a chain its diff never moved.");
115
+ }
116
+ };
113
117
  let deletedEntities = {};
114
118
  let setEntities = {};
115
119
  await Promise.all(persistence.allEntities.filter(entityConfig => entityConfig.storage.postgres).map(async entityConfig => {
116
120
  let match = await persistence.storage.getRollbackData(entityConfig, floors);
117
121
  match[0].forEach(param => {
122
+ let scope = param.scope;
118
123
  let entityId = param.entityId;
119
124
  Utils.Dict.push(deletedEntities, entityConfig.name, entityId);
120
- InMemoryTable.Entity.set(getInMemTable(state, entityConfig, param.scope), committedCheckpointId, {
125
+ InMemoryTable.Entity.set(getInMemTable(state, entityConfig, scope), IndexerState.committedCheckpointIdFor(state, scope), {
121
126
  type: "DELETE",
122
127
  entityId: entityId,
123
- checkpointId: rollbackDiffCheckpointId
128
+ checkpointId: diffCheckpointId(scope)
124
129
  });
125
130
  });
126
131
  match[1].forEach(entity => {
127
132
  let scope = takeRowScope(entity, entityConfig);
128
133
  Utils.Dict.push(setEntities, entityConfig.name, entity.id);
129
- InMemoryTable.Entity.set(getInMemTable(state, entityConfig, scope), committedCheckpointId, {
134
+ InMemoryTable.Entity.set(getInMemTable(state, entityConfig, scope), IndexerState.committedCheckpointIdFor(state, scope), {
130
135
  type: "SET",
131
136
  entityId: entity.id,
132
137
  entity: entity,
133
- checkpointId: rollbackDiffCheckpointId
138
+ checkpointId: diffCheckpointId(scope)
134
139
  });
135
140
  });
136
141
  }));