envio 3.6.1 → 3.7.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 (80) hide show
  1. package/index.d.ts +197 -9
  2. package/package.json +6 -6
  3. package/src/Batch.res +64 -25
  4. package/src/Batch.res.mjs +69 -46
  5. package/src/ChainFetching.res +13 -17
  6. package/src/ChainFetching.res.mjs +9 -14
  7. package/src/ChainState.res +160 -119
  8. package/src/ChainState.res.mjs +75 -46
  9. package/src/ChainState.resi +15 -12
  10. package/src/Config.res +9 -2
  11. package/src/Config.res.mjs +4 -4
  12. package/src/Core.res +5 -0
  13. package/src/Ecosystem.res +0 -1
  14. package/src/Envio.res +16 -3
  15. package/src/EventConfigBuilder.res +155 -40
  16. package/src/EventConfigBuilder.res.mjs +76 -24
  17. package/src/EventProcessing.res +4 -8
  18. package/src/EventProcessing.res.mjs +4 -4
  19. package/src/EventUtils.res +3 -47
  20. package/src/FetchState.res +124 -104
  21. package/src/FetchState.res.mjs +116 -100
  22. package/src/HandlerRegister.res +108 -2
  23. package/src/HandlerRegister.res.mjs +59 -10
  24. package/src/HandlerRegister.resi +4 -0
  25. package/src/Internal.res +84 -24
  26. package/src/Internal.res.mjs +33 -2
  27. package/src/LazyLoader.res +12 -11
  28. package/src/LazyLoader.res.mjs +13 -7
  29. package/src/Main.res +22 -9
  30. package/src/Main.res.mjs +18 -5
  31. package/src/MemoryStorage.res +724 -0
  32. package/src/MemoryStorage.res.mjs +601 -0
  33. package/src/PgStorage.res +18 -7
  34. package/src/PgStorage.res.mjs +14 -11
  35. package/src/ReorgDetection.res +0 -222
  36. package/src/ReorgDetection.res.mjs +0 -163
  37. package/src/Rollback.res +14 -15
  38. package/src/Rollback.res.mjs +4 -5
  39. package/src/SimulateItems.res +2 -2
  40. package/src/Utils.res +3 -0
  41. package/src/bindings/ClickHouse.res +16 -11
  42. package/src/bindings/ClickHouse.res.mjs +10 -10
  43. package/src/bindings/Vitest.res +1 -1
  44. package/src/sources/BlockStore.res +115 -5
  45. package/src/sources/BlockStore.res.mjs +25 -0
  46. package/src/sources/Evm.res +0 -1
  47. package/src/sources/Evm.res.mjs +0 -1
  48. package/src/sources/EvmHyperSyncSource.res +19 -84
  49. package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
  50. package/src/sources/Fuel.res +24 -4
  51. package/src/sources/Fuel.res.mjs +23 -3
  52. package/src/sources/FuelHyperSync.res +8 -3
  53. package/src/sources/FuelHyperSync.res.mjs +5 -4
  54. package/src/sources/FuelHyperSync.resi +3 -1
  55. package/src/sources/FuelHyperSyncClient.res +7 -10
  56. package/src/sources/FuelHyperSyncSource.res +18 -45
  57. package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
  58. package/src/sources/HyperSync.res +49 -229
  59. package/src/sources/HyperSync.res.mjs +74 -191
  60. package/src/sources/HyperSync.resi +11 -35
  61. package/src/sources/HyperSyncClient.res +9 -79
  62. package/src/sources/HyperSyncClient.res.mjs +2 -6
  63. package/src/sources/RequestStat.res +5 -0
  64. package/src/sources/RequestStat.res.mjs +2 -0
  65. package/src/sources/RpcSource.res +149 -43
  66. package/src/sources/RpcSource.res.mjs +104 -41
  67. package/src/sources/SimulateSource.res +8 -5
  68. package/src/sources/SimulateSource.res.mjs +6 -6
  69. package/src/sources/Source.res +89 -16
  70. package/src/sources/Source.res.mjs +50 -1
  71. package/src/sources/SourceManager.res +255 -50
  72. package/src/sources/SourceManager.res.mjs +149 -55
  73. package/src/sources/SourceManager.resi +2 -6
  74. package/src/sources/Svm.res +0 -7
  75. package/src/sources/Svm.res.mjs +0 -8
  76. package/src/sources/SvmHyperSyncClient.res +21 -16
  77. package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
  78. package/src/sources/SvmHyperSyncSource.res +25 -117
  79. package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
  80. package/svm.schema.json +3 -2
@@ -471,9 +471,11 @@ function classifyWriteError(specificError, table, exn) {
471
471
  }
472
472
  }
473
473
 
474
- let setQueryCache = new WeakMap();
474
+ function makeSetQueryCache() {
475
+ return new WeakMap();
476
+ }
475
477
 
476
- async function setOrThrow(sql, items, table, itemSchema, pgSchema, chainIdModeOpt) {
478
+ async function setOrThrow(sql, items, table, itemSchema, pgSchema, setQueryCache, chainIdModeOpt) {
477
479
  let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
478
480
  if (items.length === 0) {
479
481
  return;
@@ -656,7 +658,7 @@ function executeSet(sql, items, dbFunction) {
656
658
  }
657
659
  }
658
660
 
659
- async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, updatedEffectsCache, updatedEntities, sinkPromise, chainMetaData, escapeTables) {
661
+ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, setQueryCache, updatedEffectsCache, updatedEntities, sinkPromise, chainMetaData, escapeTables) {
660
662
  try {
661
663
  let chainIdMode = config.chainIdMode;
662
664
  let shouldSaveHistory = Config.shouldSaveHistory(config, isInReorgThreshold);
@@ -670,7 +672,7 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
670
672
  if (item.kind !== 0) {
671
673
  return;
672
674
  }
673
- let coordinate = ChainId.toString(Internal.getItemChainId(item)) + `-` + item.blockNumber.toString() + `-` + item.logIndex.toString();
675
+ let coordinate = ChainId.toString(item.chainId) + `-` + item.blockNumber.toString() + `-` + item.logIndex.toString();
674
676
  if (seenLogCoordinates.has(coordinate)) {
675
677
  return;
676
678
  } else {
@@ -687,7 +689,7 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
687
689
  }
688
690
  let setRawEvents = async sql => {
689
691
  try {
690
- return await executeSet(sql, rawEvents, (sql, items) => setOrThrow(sql, items, InternalTable.RawEvents.table, InternalTable.RawEvents.schema, pgSchema, chainIdMode));
692
+ return await executeSet(sql, rawEvents, (sql, items) => setOrThrow(sql, items, InternalTable.RawEvents.table, InternalTable.RawEvents.schema, pgSchema, setQueryCache, chainIdMode));
691
693
  } catch (raw_exn) {
692
694
  let exn = Primitive_exceptions.internalToException(raw_exn);
693
695
  return classifyWriteError(specificError, InternalTable.RawEvents.table, exn);
@@ -791,14 +793,14 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
791
793
  }));
792
794
  }
793
795
  let entityHistory = getEntityHistory(entityConfig);
794
- promises.push(setOrThrow(sql, batchSetUpdates, entityHistory.table, entityHistory.setChangeSchema, pgSchema, chainIdMode));
796
+ promises.push(setOrThrow(sql, batchSetUpdates, entityHistory.table, entityHistory.setChangeSchema, pgSchema, setQueryCache, chainIdMode));
795
797
  }
796
798
  }
797
799
  if (Utils.$$Array.notEmpty(entitiesToSet)) {
798
800
  if (shouldRemoveInvalidUtf8) {
799
801
  removeInvalidUtf8InPlace(entitiesToSet);
800
802
  }
801
- promises.push(setOrThrow(sql, entitiesToSet, entityConfig.table, getRowSchema(entityConfig), pgSchema, chainIdMode));
803
+ promises.push(setOrThrow(sql, entitiesToSet, entityConfig.table, getRowSchema(entityConfig), pgSchema, setQueryCache, chainIdMode));
802
804
  }
803
805
  if (Utils.$$Array.notEmpty(idsToDelete)) {
804
806
  promises.push(deleteByIdsOrThrow(sql, pgSchema, idsToDelete, entityConfig.table, Primitive_option.some(scopeChainId)));
@@ -870,7 +872,7 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
870
872
  if (exn$1.RE_EXN_ID === PgEncodingError) {
871
873
  let escapeTables$1 = escapeTables !== undefined ? Primitive_option.valFromOption(escapeTables) : new Set();
872
874
  escapeTables$1.add(exn$1.table);
873
- return await writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, updatedEffectsCache, updatedEntities, sinkPromise, chainMetaData, Primitive_option.some(escapeTables$1));
875
+ return await writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, setQueryCache, updatedEffectsCache, updatedEntities, sinkPromise, chainMetaData, Primitive_option.some(escapeTables$1));
874
876
  }
875
877
  throw exn$1;
876
878
  }
@@ -954,6 +956,7 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
954
956
  let cacheDirPath = Path.resolve(".envio", "cache");
955
957
  let storageName = "postgres";
956
958
  let indexManager = IndexManager.make();
959
+ let setQueryCache = new WeakMap();
957
960
  let loadCatalogRows = (sql, indexName) => sql.unsafe(IndexCatalog.makeQuery(pgSchema, indexName)).then(rows => S$RescriptSchema.parseOrThrow(rows, IndexCatalog.rowsSchema));
958
961
  let refreshIndexCatalog = async () => {
959
962
  let rows = await loadCatalogRows(sql, undefined);
@@ -1325,7 +1328,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::` + addrCh
1325
1328
  msg: `Committed ` + missing.length.toString() + ` schema indexes and the ready timestamp in ` + formatSeconds(timeRef) + `s.`
1326
1329
  });
1327
1330
  };
1328
- let setOrThrow$1 = (items, table, itemSchema) => setOrThrow(sql, items, table, itemSchema, pgSchema, undefined);
1331
+ let setOrThrow$1 = (items, table, itemSchema) => setOrThrow(sql, items, table, itemSchema, pgSchema, setQueryCache, chainIdMode);
1329
1332
  let setEffectCacheOrThrow = async (table, itemSchema, items, initialize) => {
1330
1333
  if (initialize) {
1331
1334
  await sql.unsafe(makeCreateTableQuery(table, pgSchema, false, undefined));
@@ -1487,7 +1490,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::` + addrCh
1487
1490
  sinkPromise = undefined;
1488
1491
  }
1489
1492
  let primaryTimerRef = Performance.now();
1490
- await writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, updatedEffectsCache, pgUpdates, sinkPromise, chainMetaData, undefined);
1493
+ await writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, setQueryCache, updatedEffectsCache, pgUpdates, sinkPromise, chainMetaData, undefined);
1491
1494
  return onWrite(storageName, Performance.secondsSince(primaryTimerRef));
1492
1495
  };
1493
1496
  let close = () => sql.end();
@@ -1590,7 +1593,7 @@ export {
1590
1593
  pgErrorMessageSchema,
1591
1594
  PgEncodingError,
1592
1595
  classifyWriteError,
1593
- setQueryCache,
1596
+ makeSetQueryCache,
1594
1597
  setOrThrow,
1595
1598
  makeSchemaTableNamesQuery,
1596
1599
  makeEffectCacheTableNamesQuery,
@@ -1,17 +1,8 @@
1
- type blockDataWithTimestamp = {
2
- blockHash: string,
3
- blockNumber: int,
4
- blockTimestamp: int,
5
- }
6
-
7
1
  type blockData = {
8
- // Block hash is used for actual comparison to test for reorg
9
2
  blockHash: string,
10
3
  blockNumber: int,
11
4
  }
12
5
 
13
- external generalizeBlockDataWithTimestamp: blockDataWithTimestamp => blockData = "%identity"
14
-
15
6
  type reorgDetected = {
16
7
  scannedBlock: blockData,
17
8
  receivedBlock: blockData,
@@ -30,216 +21,3 @@ let reorgDetectedToLogParams = (reorgDetected: reorgDetected, ~shouldRollbackOnR
30
21
  }
31
22
 
32
23
  type reorgResult = NoReorg | ReorgDetected(reorgDetected)
33
-
34
- type t = {
35
- // Whether to rollback on reorg
36
- // Even if it's disabled, we still track reorgs checkpoints in memory
37
- // and log when we detect an unhandled reorg
38
- shouldRollbackOnReorg: bool,
39
- // Number of blocks behind head, we want to keep track
40
- // as a threshold for reorgs. If for eg. this is 200,
41
- // it means we are accounting for reorgs up to 200 blocks
42
- // behind the head
43
- maxReorgDepth: int,
44
- // A hash map of recent blockdata by block number to make comparison checks
45
- // for reorgs.
46
- dataByBlockNumber: dict<blockData>,
47
- }
48
-
49
- let make = (
50
- ~chainReorgCheckpoints: array<Internal.reorgCheckpoint>,
51
- ~maxReorgDepth,
52
- ~shouldRollbackOnReorg,
53
- ) => {
54
- let dataByBlockNumber = Dict.make()
55
-
56
- chainReorgCheckpoints->Array.forEach(block => {
57
- dataByBlockNumber->Utils.Dict.setByInt(
58
- block.blockNumber,
59
- {
60
- blockHash: block.blockHash,
61
- blockNumber: block.blockNumber,
62
- },
63
- )
64
- })
65
-
66
- {
67
- shouldRollbackOnReorg,
68
- maxReorgDepth,
69
- dataByBlockNumber,
70
- }
71
- }
72
-
73
- let getDataByBlockNumberCopyInThreshold = ({dataByBlockNumber, maxReorgDepth}: t, ~knownHeight) => {
74
- // Js engine automatically orders numeric object keys
75
- let ascBlockNumberKeys = dataByBlockNumber->Dict.keysToArray
76
- let thresholdBlockNumber = knownHeight - maxReorgDepth
77
-
78
- let copy = Dict.make()
79
-
80
- for idx in 0 to ascBlockNumberKeys->Array.length - 1 {
81
- let blockNumberKey = ascBlockNumberKeys->Array.getUnsafe(idx)
82
- let scannedBlock = dataByBlockNumber->Dict.getUnsafe(blockNumberKey)
83
- let isInReorgThreshold = scannedBlock.blockNumber >= thresholdBlockNumber
84
- if isInReorgThreshold {
85
- copy->Dict.set(blockNumberKey, scannedBlock)
86
- }
87
- }
88
-
89
- copy
90
- }
91
-
92
- /** Registers observed (blockNumber, blockHash) pairs from a range fetch, prunes
93
- * unneeded data, and returns the updated state.
94
- *
95
- * Iterates the provided block hashes, skips entries outside the reorg threshold,
96
- * and compares each one against the previously scanned data. Returns on the first
97
- * mismatch as `ReorgDetected`.
98
- *
99
- * Resets internal state if shouldRollbackOnReorg is false (detect-only mode).
100
- */
101
- let registerReorgGuard = (
102
- {maxReorgDepth, shouldRollbackOnReorg} as self: t,
103
- ~blockHashes: array<blockData>,
104
- ~knownHeight,
105
- ) => {
106
- let dataByBlockNumberCopyInThreshold = self->getDataByBlockNumberCopyInThreshold(~knownHeight)
107
- let thresholdBlockNumber = knownHeight - maxReorgDepth
108
-
109
- let maybeReorgDetected = ref(None)
110
- let idx = ref(0)
111
- while maybeReorgDetected.contents === None && idx.contents < blockHashes->Array.length {
112
- let receivedBlock = blockHashes->Array.getUnsafe(idx.contents)
113
- if receivedBlock.blockNumber >= thresholdBlockNumber {
114
- let key = receivedBlock.blockNumber->Int.toString
115
- // The working copy contains both previously scanned blocks AND blocks
116
- // already written by earlier iterations of this same call, so a duplicate
117
- // block number with a mismatching hash inside `blockHashes` itself is
118
- // flagged as a reorg.
119
- switch dataByBlockNumberCopyInThreshold->Utils.Dict.dangerouslyGetNonOption(key) {
120
- | Some(scannedBlock) if scannedBlock.blockHash !== receivedBlock.blockHash =>
121
- maybeReorgDetected := Some({receivedBlock, scannedBlock})
122
- | _ => dataByBlockNumberCopyInThreshold->Dict.set(key, receivedBlock)
123
- }
124
- }
125
- idx := idx.contents + 1
126
- }
127
-
128
- switch maybeReorgDetected.contents {
129
- | Some(reorgDetected) => (
130
- shouldRollbackOnReorg
131
- ? self
132
- : make(~chainReorgCheckpoints=[], ~maxReorgDepth, ~shouldRollbackOnReorg),
133
- ReorgDetected(reorgDetected),
134
- )
135
- | None => (
136
- {
137
- maxReorgDepth,
138
- dataByBlockNumber: dataByBlockNumberCopyInThreshold,
139
- shouldRollbackOnReorg,
140
- },
141
- NoReorg,
142
- )
143
- }
144
- }
145
-
146
- /**
147
- Returns the latest block number which matches block number and hashes in the provided array
148
- If it doesn't exist in the reorg threshold it returns NotFound
149
- */
150
- let getLatestValidScannedBlock = (
151
- reorgDetection: t,
152
- ~blockNumbersAndHashes: array<blockDataWithTimestamp>,
153
- ) => {
154
- let verifiedDataByBlockNumber = Dict.make()
155
- for idx in 0 to blockNumbersAndHashes->Array.length - 1 {
156
- let blockData = blockNumbersAndHashes->Array.getUnsafe(idx)
157
- verifiedDataByBlockNumber->Dict.set(blockData.blockNumber->Int.toString, blockData)
158
- }
159
- // Js engine automatically orders numeric object keys
160
- let ascBlockNumberKeys = verifiedDataByBlockNumber->Dict.keysToArray
161
-
162
- let getPrevScannedBlockNumber = idx =>
163
- ascBlockNumberKeys
164
- ->Array.get(idx - 1)
165
- ->Option.map(key => {
166
- (verifiedDataByBlockNumber->Dict.getUnsafe(key)).blockNumber
167
- })
168
-
169
- let rec loop = idx => {
170
- switch ascBlockNumberKeys->Array.get(idx) {
171
- | Some(blockNumberKey) =>
172
- switch reorgDetection.dataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(blockNumberKey) {
173
- | Some(scannedBlock)
174
- if (verifiedDataByBlockNumber->Dict.getUnsafe(blockNumberKey)).blockHash ===
175
- scannedBlock.blockHash =>
176
- loop(idx + 1)
177
- | _ => getPrevScannedBlockNumber(idx)
178
- }
179
- | None => getPrevScannedBlockNumber(idx)
180
- }
181
- }
182
- loop(0)
183
- }
184
-
185
- /**
186
- Return a BlockNumbersAndHashes.t rolled back to where blockData is less
187
- than the provided blockNumber
188
- */
189
- let rollbackToValidBlockNumber = (
190
- {dataByBlockNumber, maxReorgDepth, shouldRollbackOnReorg}: t,
191
- ~blockNumber: int,
192
- ) => {
193
- // Js engine automatically orders numeric object keys
194
- let ascBlockNumberKeys = dataByBlockNumber->Dict.keysToArray
195
-
196
- let newDataByBlockNumber = Dict.make()
197
-
198
- let rec loop = idx => {
199
- switch ascBlockNumberKeys->Array.get(idx) {
200
- | Some(blockNumberKey) => {
201
- let scannedBlock = dataByBlockNumber->Dict.getUnsafe(blockNumberKey)
202
- let shouldKeep = scannedBlock.blockNumber <= blockNumber
203
- if shouldKeep {
204
- newDataByBlockNumber->Dict.set(blockNumberKey, scannedBlock)
205
- loop(idx + 1)
206
- } else {
207
- ()
208
- }
209
- }
210
- | None => ()
211
- }
212
- }
213
- loop(0)
214
-
215
- {
216
- maxReorgDepth,
217
- dataByBlockNumber: newDataByBlockNumber,
218
- shouldRollbackOnReorg,
219
- }
220
- }
221
-
222
- let getThresholdBlockNumbersBelowBlock = (self: t, ~blockNumber: int, ~knownHeight) => {
223
- let arr = []
224
-
225
- // Js engine automatically orders numeric object keys
226
- let ascBlockNumberKeys = self.dataByBlockNumber->Dict.keysToArray
227
- let thresholdBlockNumber = knownHeight - self.maxReorgDepth
228
-
229
- for idx in 0 to ascBlockNumberKeys->Array.length - 1 {
230
- let blockNumberKey = ascBlockNumberKeys->Array.getUnsafe(idx)
231
- let scannedBlock = self.dataByBlockNumber->Dict.getUnsafe(blockNumberKey)
232
- let isInReorgThreshold = scannedBlock.blockNumber >= thresholdBlockNumber
233
- if isInReorgThreshold && scannedBlock.blockNumber < blockNumber {
234
- arr->Array.push(scannedBlock.blockNumber)
235
- }
236
- }
237
- arr
238
- }
239
-
240
- let getHashByBlockNumber = (reorgDetection: t, ~blockNumber) => {
241
- switch reorgDetection.dataByBlockNumber->Utils.Dict.dangerouslyGetByIntNonOption(blockNumber) {
242
- | Some(v) => Null.Value(v.blockHash)
243
- | None => Null.Null
244
- }
245
- }
@@ -1,6 +1,5 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
3
 
5
4
  function reorgDetectedToLogParams(reorgDetected, shouldRollbackOnReorg) {
6
5
  let scannedBlock = reorgDetected.scannedBlock;
@@ -14,169 +13,7 @@ function reorgDetectedToLogParams(reorgDetected, shouldRollbackOnReorg) {
14
13
  };
15
14
  }
16
15
 
17
- function make(chainReorgCheckpoints, maxReorgDepth, shouldRollbackOnReorg) {
18
- let dataByBlockNumber = {};
19
- chainReorgCheckpoints.forEach(block => {
20
- dataByBlockNumber[block.block_number] = {
21
- blockHash: block.block_hash,
22
- blockNumber: block.block_number
23
- };
24
- });
25
- return {
26
- shouldRollbackOnReorg: shouldRollbackOnReorg,
27
- maxReorgDepth: maxReorgDepth,
28
- dataByBlockNumber: dataByBlockNumber
29
- };
30
- }
31
-
32
- function getDataByBlockNumberCopyInThreshold(param, knownHeight) {
33
- let dataByBlockNumber = param.dataByBlockNumber;
34
- let ascBlockNumberKeys = Object.keys(dataByBlockNumber);
35
- let thresholdBlockNumber = knownHeight - param.maxReorgDepth | 0;
36
- let copy = {};
37
- for (let idx = 0, idx_finish = ascBlockNumberKeys.length; idx < idx_finish; ++idx) {
38
- let blockNumberKey = ascBlockNumberKeys[idx];
39
- let scannedBlock = dataByBlockNumber[blockNumberKey];
40
- let isInReorgThreshold = scannedBlock.blockNumber >= thresholdBlockNumber;
41
- if (isInReorgThreshold) {
42
- copy[blockNumberKey] = scannedBlock;
43
- }
44
- }
45
- return copy;
46
- }
47
-
48
- function registerReorgGuard(self, blockHashes, knownHeight) {
49
- let maxReorgDepth = self.maxReorgDepth;
50
- let shouldRollbackOnReorg = self.shouldRollbackOnReorg;
51
- let dataByBlockNumberCopyInThreshold = getDataByBlockNumberCopyInThreshold(self, knownHeight);
52
- let thresholdBlockNumber = knownHeight - maxReorgDepth | 0;
53
- let maybeReorgDetected;
54
- let idx = 0;
55
- while (maybeReorgDetected === undefined && idx < blockHashes.length) {
56
- let receivedBlock = blockHashes[idx];
57
- if (receivedBlock.blockNumber >= thresholdBlockNumber) {
58
- let key = receivedBlock.blockNumber.toString();
59
- let scannedBlock = dataByBlockNumberCopyInThreshold[key];
60
- if (scannedBlock !== undefined && scannedBlock.blockHash !== receivedBlock.blockHash) {
61
- maybeReorgDetected = {
62
- scannedBlock: scannedBlock,
63
- receivedBlock: receivedBlock
64
- };
65
- } else {
66
- dataByBlockNumberCopyInThreshold[key] = receivedBlock;
67
- }
68
- }
69
- idx = idx + 1 | 0;
70
- };
71
- let reorgDetected = maybeReorgDetected;
72
- if (reorgDetected !== undefined) {
73
- return [
74
- shouldRollbackOnReorg ? self : make([], maxReorgDepth, shouldRollbackOnReorg),
75
- {
76
- TAG: "ReorgDetected",
77
- _0: reorgDetected
78
- }
79
- ];
80
- } else {
81
- return [
82
- {
83
- shouldRollbackOnReorg: shouldRollbackOnReorg,
84
- maxReorgDepth: maxReorgDepth,
85
- dataByBlockNumber: dataByBlockNumberCopyInThreshold
86
- },
87
- "NoReorg"
88
- ];
89
- }
90
- }
91
-
92
- function getLatestValidScannedBlock(reorgDetection, blockNumbersAndHashes) {
93
- let verifiedDataByBlockNumber = {};
94
- for (let idx = 0, idx_finish = blockNumbersAndHashes.length; idx < idx_finish; ++idx) {
95
- let blockData = blockNumbersAndHashes[idx];
96
- verifiedDataByBlockNumber[blockData.blockNumber.toString()] = blockData;
97
- }
98
- let ascBlockNumberKeys = Object.keys(verifiedDataByBlockNumber);
99
- let getPrevScannedBlockNumber = idx => Stdlib_Option.map(ascBlockNumberKeys[idx - 1 | 0], key => verifiedDataByBlockNumber[key].blockNumber);
100
- let _idx = 0;
101
- while (true) {
102
- let idx$1 = _idx;
103
- let blockNumberKey = ascBlockNumberKeys[idx$1];
104
- if (blockNumberKey === undefined) {
105
- return getPrevScannedBlockNumber(idx$1);
106
- }
107
- let scannedBlock = reorgDetection.dataByBlockNumber[blockNumberKey];
108
- if (scannedBlock === undefined) {
109
- return getPrevScannedBlockNumber(idx$1);
110
- }
111
- if (verifiedDataByBlockNumber[blockNumberKey].blockHash !== scannedBlock.blockHash) {
112
- return getPrevScannedBlockNumber(idx$1);
113
- }
114
- _idx = idx$1 + 1 | 0;
115
- continue;
116
- };
117
- }
118
-
119
- function rollbackToValidBlockNumber(param, blockNumber) {
120
- let dataByBlockNumber = param.dataByBlockNumber;
121
- let ascBlockNumberKeys = Object.keys(dataByBlockNumber);
122
- let newDataByBlockNumber = {};
123
- let loop = _idx => {
124
- while (true) {
125
- let idx = _idx;
126
- let blockNumberKey = ascBlockNumberKeys[idx];
127
- if (blockNumberKey === undefined) {
128
- return;
129
- }
130
- let scannedBlock = dataByBlockNumber[blockNumberKey];
131
- let shouldKeep = scannedBlock.blockNumber <= blockNumber;
132
- if (!shouldKeep) {
133
- return;
134
- }
135
- newDataByBlockNumber[blockNumberKey] = scannedBlock;
136
- _idx = idx + 1 | 0;
137
- continue;
138
- };
139
- };
140
- loop(0);
141
- return {
142
- shouldRollbackOnReorg: param.shouldRollbackOnReorg,
143
- maxReorgDepth: param.maxReorgDepth,
144
- dataByBlockNumber: newDataByBlockNumber
145
- };
146
- }
147
-
148
- function getThresholdBlockNumbersBelowBlock(self, blockNumber, knownHeight) {
149
- let arr = [];
150
- let ascBlockNumberKeys = Object.keys(self.dataByBlockNumber);
151
- let thresholdBlockNumber = knownHeight - self.maxReorgDepth | 0;
152
- for (let idx = 0, idx_finish = ascBlockNumberKeys.length; idx < idx_finish; ++idx) {
153
- let blockNumberKey = ascBlockNumberKeys[idx];
154
- let scannedBlock = self.dataByBlockNumber[blockNumberKey];
155
- let isInReorgThreshold = scannedBlock.blockNumber >= thresholdBlockNumber;
156
- if (isInReorgThreshold && scannedBlock.blockNumber < blockNumber) {
157
- arr.push(scannedBlock.blockNumber);
158
- }
159
- }
160
- return arr;
161
- }
162
-
163
- function getHashByBlockNumber(reorgDetection, blockNumber) {
164
- let v = reorgDetection.dataByBlockNumber[blockNumber];
165
- if (v !== undefined) {
166
- return v.blockHash;
167
- } else {
168
- return null;
169
- }
170
- }
171
-
172
16
  export {
173
17
  reorgDetectedToLogParams,
174
- make,
175
- getDataByBlockNumberCopyInThreshold,
176
- registerReorgGuard,
177
- getLatestValidScannedBlock,
178
- rollbackToValidBlockNumber,
179
- getThresholdBlockNumbersBelowBlock,
180
- getHashByBlockNumber,
181
18
  }
182
19
  /* No side effect */
package/src/Rollback.res CHANGED
@@ -10,27 +10,29 @@ let getLastKnownValidBlock = async (
10
10
  ~reorgBlockNumber: int,
11
11
  ~isRealtime: bool,
12
12
  ) => {
13
- // Don't include the reorg block itself different source instances
13
+ // Before the search, not after: it re-fetches the scanned hashes through the
14
+ // sources, and a source answering from a cache it filled on the orphaned
15
+ // chain would confirm blocks that no longer exist - stopping the rollback
16
+ // short of the real fork.
17
+ chainState->ChainState.sourceManager->SourceManager.onReorg
18
+
19
+ // Don't include the reorg block itself - different source instances
14
20
  // may have mismatching hashes at the head, so we always rollback
15
21
  // the block where we detected the reorg.
16
22
  let scannedBlockNumbers =
17
- chainState
18
- ->ChainState.reorgDetection
19
- ->ReorgDetection.getThresholdBlockNumbersBelowBlock(
20
- ~blockNumber=reorgBlockNumber,
21
- ~knownHeight=chainState->ChainState.knownHeight,
22
- )
23
+ chainState->ChainState.getReorgThresholdBlockNumbersBelow(~blockNumber=reorgBlockNumber)
23
24
 
24
25
  switch scannedBlockNumbers {
25
26
  | [] => chainState->ChainState.getHighestBlockBelowThreshold
26
27
  | _ => {
27
- let blockNumbersAndHashes = await chainState
28
+ let blockStore = await chainState
28
29
  ->ChainState.sourceManager
29
30
  ->SourceManager.getBlockHashes(~blockNumbers=scannedBlockNumbers, ~isRealtime)
30
31
 
31
- switch chainState
32
- ->ChainState.reorgDetection
33
- ->ReorgDetection.getLatestValidScannedBlock(~blockNumbersAndHashes) {
32
+ switch chainState->ChainState.getLatestValidScannedBlock(
33
+ ~blockStore,
34
+ ~blockNumbers=scannedBlockNumbers,
35
+ ) {
34
36
  | Some(blockNumber) => blockNumber
35
37
  | None => chainState->ChainState.getHighestBlockBelowThreshold
36
38
  }
@@ -54,15 +56,12 @@ let rec rollback = async (
54
56
  let chainState = state->IndexerState.getChainState(~chainId)
55
57
 
56
58
  state->IndexerState.enterFindingReorgDepth
59
+
57
60
  let rollbackTargetBlockNumber = await chainState->getLastKnownValidBlock(
58
61
  ~reorgBlockNumber,
59
62
  ~isRealtime=state->IndexerState.isRealtime,
60
63
  )
61
64
 
62
- chainState
63
- ->ChainState.sourceManager
64
- ->SourceManager.onReorg(~rollbackTargetBlock=rollbackTargetBlockNumber)
65
-
66
65
  state->IndexerState.foundReorgDepth(~chainId, ~rollbackTargetBlockNumber)
67
66
  // Rendezvous with the processing loop: whichever of {depth found, loop
68
67
  // idle} happens last triggers the rollback; the earlier one finds the
@@ -12,17 +12,17 @@ import * as InMemoryStore from "./InMemoryStore.res.mjs";
12
12
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
13
13
  import * as SourceManager from "./sources/SourceManager.res.mjs";
14
14
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
15
- import * as ReorgDetection from "./ReorgDetection.res.mjs";
16
15
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
17
16
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
18
17
 
19
18
  async function getLastKnownValidBlock(chainState, reorgBlockNumber, isRealtime) {
20
- let scannedBlockNumbers = ReorgDetection.getThresholdBlockNumbersBelowBlock(ChainState.reorgDetection(chainState), reorgBlockNumber, ChainState.knownHeight(chainState));
19
+ SourceManager.onReorg(ChainState.sourceManager(chainState));
20
+ let scannedBlockNumbers = ChainState.getReorgThresholdBlockNumbersBelow(chainState, reorgBlockNumber);
21
21
  if (scannedBlockNumbers.length === 0) {
22
22
  return ChainState.getHighestBlockBelowThreshold(chainState);
23
23
  }
24
- let blockNumbersAndHashes = await SourceManager.getBlockHashes(ChainState.sourceManager(chainState), scannedBlockNumbers, isRealtime);
25
- let blockNumber = ReorgDetection.getLatestValidScannedBlock(ChainState.reorgDetection(chainState), blockNumbersAndHashes);
24
+ let blockStore = await SourceManager.getBlockHashes(ChainState.sourceManager(chainState), scannedBlockNumbers, isRealtime);
25
+ let blockNumber = ChainState.getLatestValidScannedBlock(chainState, blockStore, scannedBlockNumbers);
26
26
  if (blockNumber !== undefined) {
27
27
  return blockNumber;
28
28
  } else {
@@ -104,7 +104,6 @@ async function rollback(state, scheduleFetch, scheduleProcessing, scheduleRollba
104
104
  let chainState = IndexerState.getChainState(state, chainId);
105
105
  IndexerState.enterFindingReorgDepth(state);
106
106
  let rollbackTargetBlockNumber = await getLastKnownValidBlock(chainState, match.blockNumber, IndexerState.isRealtime(state));
107
- SourceManager.onReorg(ChainState.sourceManager(chainState), rollbackTargetBlockNumber);
108
107
  IndexerState.foundReorgDepth(state, chainId, rollbackTargetBlockNumber);
109
108
  return scheduleRollback();
110
109
  case "FoundReorgDepth" :
@@ -350,7 +350,7 @@ let parse = (
350
350
  let liveRegistrations =
351
351
  HandlerRegister.getSimulateOnEventRegistrations(
352
352
  ~config,
353
- ~chainId=chainId,
353
+ ~chainId,
354
354
  ~eventConfig,
355
355
  )->Array.filter(reg =>
356
356
  (reg.handler->Option.isSome || reg.contractRegister->Option.isSome) &&
@@ -388,7 +388,7 @@ let parse = (
388
388
  contractName: eventConfig.contractName,
389
389
  eventName: eventConfig.name,
390
390
  params,
391
- chainId: chainId,
391
+ chainId,
392
392
  srcAddress,
393
393
  logIndex,
394
394
  transaction,
package/src/Utils.res CHANGED
@@ -576,6 +576,9 @@ module Set = {
576
576
  @send
577
577
  external intersection: (t<'value>, t<'value>) => t<'value> = "intersection"
578
578
 
579
+ @send
580
+ external union: (t<'value>, t<'value>) => t<'value> = "union"
581
+
579
582
  let immutableAdd: (t<'a>, 'a) => t<'a> = %raw(`(set, value) => {
580
583
  return new Set([...set, value])
581
584
  }`)