envio 3.6.1 → 3.7.0-subgraph

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 (114) 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 +18 -2
  11. package/src/Config.res.mjs +9 -6
  12. package/src/Core.res +20 -0
  13. package/src/Core.res.mjs +11 -0
  14. package/src/Ecosystem.res +0 -1
  15. package/src/Envio.res +16 -3
  16. package/src/EventConfigBuilder.res +155 -40
  17. package/src/EventConfigBuilder.res.mjs +76 -24
  18. package/src/EventProcessing.res +10 -14
  19. package/src/EventProcessing.res.mjs +12 -10
  20. package/src/EventUtils.res +3 -47
  21. package/src/FetchState.res +124 -104
  22. package/src/FetchState.res.mjs +116 -100
  23. package/src/HandlerLoader.res +20 -8
  24. package/src/HandlerLoader.res.mjs +11 -2
  25. package/src/HandlerRegister.res +108 -2
  26. package/src/HandlerRegister.res.mjs +59 -10
  27. package/src/HandlerRegister.resi +4 -0
  28. package/src/InMemoryStore.res +1 -6
  29. package/src/InMemoryStore.res.mjs +1 -3
  30. package/src/Internal.res +84 -24
  31. package/src/Internal.res.mjs +33 -2
  32. package/src/LazyLoader.res +12 -11
  33. package/src/LazyLoader.res.mjs +13 -7
  34. package/src/Main.res +22 -9
  35. package/src/Main.res.mjs +18 -5
  36. package/src/MemoryStorage.res +756 -0
  37. package/src/MemoryStorage.res.mjs +623 -0
  38. package/src/Persistence.res +4 -2
  39. package/src/PgStorage.res +24 -8
  40. package/src/PgStorage.res.mjs +15 -12
  41. package/src/ReorgDetection.res +0 -222
  42. package/src/ReorgDetection.res.mjs +0 -163
  43. package/src/Rollback.res +14 -15
  44. package/src/Rollback.res.mjs +4 -5
  45. package/src/SimulateItems.res +2 -2
  46. package/src/UserContext.res +358 -51
  47. package/src/UserContext.res.mjs +271 -35
  48. package/src/Utils.res +3 -0
  49. package/src/bindings/ClickHouse.res +16 -11
  50. package/src/bindings/ClickHouse.res.mjs +10 -10
  51. package/src/bindings/Vitest.res +1 -1
  52. package/src/sources/BlockStore.res +115 -5
  53. package/src/sources/BlockStore.res.mjs +25 -0
  54. package/src/sources/Evm.res +0 -1
  55. package/src/sources/Evm.res.mjs +0 -1
  56. package/src/sources/EvmHyperSyncSource.res +19 -84
  57. package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
  58. package/src/sources/Fuel.res +24 -4
  59. package/src/sources/Fuel.res.mjs +23 -3
  60. package/src/sources/FuelHyperSync.res +8 -3
  61. package/src/sources/FuelHyperSync.res.mjs +5 -4
  62. package/src/sources/FuelHyperSync.resi +3 -1
  63. package/src/sources/FuelHyperSyncClient.res +7 -10
  64. package/src/sources/FuelHyperSyncSource.res +18 -45
  65. package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
  66. package/src/sources/HyperSync.res +49 -229
  67. package/src/sources/HyperSync.res.mjs +74 -191
  68. package/src/sources/HyperSync.resi +11 -35
  69. package/src/sources/HyperSyncClient.res +9 -79
  70. package/src/sources/HyperSyncClient.res.mjs +2 -6
  71. package/src/sources/RequestStat.res +5 -0
  72. package/src/sources/RequestStat.res.mjs +2 -0
  73. package/src/sources/RpcSource.res +149 -43
  74. package/src/sources/RpcSource.res.mjs +104 -41
  75. package/src/sources/SimulateSource.res +8 -5
  76. package/src/sources/SimulateSource.res.mjs +6 -6
  77. package/src/sources/Source.res +89 -16
  78. package/src/sources/Source.res.mjs +50 -1
  79. package/src/sources/SourceManager.res +255 -50
  80. package/src/sources/SourceManager.res.mjs +149 -55
  81. package/src/sources/SourceManager.resi +2 -6
  82. package/src/sources/Svm.res +0 -7
  83. package/src/sources/Svm.res.mjs +0 -8
  84. package/src/sources/SvmHyperSyncClient.res +21 -16
  85. package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
  86. package/src/sources/SvmHyperSyncSource.res +25 -117
  87. package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
  88. package/src/subgraph/blocks.ts +176 -0
  89. package/src/subgraph/calls.ts +217 -0
  90. package/src/subgraph/conformance.ts +99 -0
  91. package/src/subgraph/division.ts +110 -0
  92. package/src/subgraph/errors.ts +90 -0
  93. package/src/subgraph/graph-ts-types/VERSION +2 -0
  94. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  95. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  96. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  97. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  98. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  99. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  100. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  101. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  102. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  103. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  104. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  105. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  106. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  107. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  108. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  109. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  110. package/src/subgraph/graph-ts.ts +1871 -0
  111. package/src/subgraph/hosts.ts +148 -0
  112. package/src/subgraph/runtime.ts +845 -0
  113. package/src/subgraph/scope.ts +63 -0
  114. package/svm.schema.json +3 -2
package/src/PgStorage.res CHANGED
@@ -630,14 +630,19 @@ let classifyWriteError = (~specificError: ref<option<exn>>, ~table: Table.table,
630
630
  }
631
631
  }
632
632
 
633
- // WeakMap for caching table batch set queries
634
- let setQueryCache = Utils.WeakMap.make()
633
+ // Batch set queries, cached per table. The query text bakes in the schema and
634
+ // the chain-id mode, so the cache belongs to the storage instance those came
635
+ // from — `make` creates one and threads it down. A process-wide cache would
636
+ // hand a second storage the first one's schema.
637
+ let makeSetQueryCache = () => Utils.WeakMap.make()
638
+
635
639
  let setOrThrow = async (
636
640
  sql,
637
641
  ~items,
638
642
  ~table: Table.table,
639
643
  ~itemSchema,
640
644
  ~pgSchema,
645
+ ~setQueryCache,
641
646
  ~chainIdMode: ChainId.mode=Int32,
642
647
  ) => {
643
648
  if items->Array.length === 0 {
@@ -953,6 +958,7 @@ let rec writeBatch = async (
953
958
  ~config: Config.t,
954
959
  ~allEntities: array<Internal.entityConfig>,
955
960
  ~setEffectCacheOrThrow,
961
+ ~setQueryCache,
956
962
  ~updatedEffectsCache,
957
963
  ~updatedEntities: array<Persistence.updatedEntity>,
958
964
  ~sinkPromise: option<promise<option<exn>>>,
@@ -973,11 +979,8 @@ let rec writeBatch = async (
973
979
  let rows = batch.items->Array.filterMap(item =>
974
980
  switch item {
975
981
  | Internal.Event(_) =>
976
- let coordinate = `${item
977
- ->Internal.getItemChainId
978
- ->ChainId.toString}-${item
979
- ->Internal.getItemBlockNumber
980
- ->Int.toString}-${item->Internal.getItemLogIndex->Int.toString}`
982
+ let eventItem = item->Internal.castUnsafeEventItem
983
+ let coordinate = `${eventItem.chainId->ChainId.toString}-${eventItem.blockNumber->Int.toString}-${eventItem.logIndex->Int.toString}`
981
984
  if seenLogCoordinates->Utils.Set.has(coordinate) {
982
985
  None
983
986
  } else {
@@ -1006,6 +1009,7 @@ let rec writeBatch = async (
1006
1009
  ~itemSchema=InternalTable.RawEvents.schema,
1007
1010
  ~pgSchema,
1008
1011
  ~chainIdMode,
1012
+ ~setQueryCache,
1009
1013
  )
1010
1014
  }, ~items=rawEvents)
1011
1015
  } catch {
@@ -1161,6 +1165,7 @@ let rec writeBatch = async (
1161
1165
  ~table=entityHistory.table,
1162
1166
  ~pgSchema,
1163
1167
  ~chainIdMode,
1168
+ ~setQueryCache,
1164
1169
  ),
1165
1170
  )
1166
1171
  ->ignore
@@ -1178,6 +1183,7 @@ let rec writeBatch = async (
1178
1183
  ~itemSchema=entityConfig->getRowSchema,
1179
1184
  ~pgSchema,
1180
1185
  ~chainIdMode,
1186
+ ~setQueryCache,
1181
1187
  ),
1182
1188
  )
1183
1189
  }
@@ -1340,6 +1346,7 @@ let rec writeBatch = async (
1340
1346
  ~escapeTables,
1341
1347
  ~batch,
1342
1348
  ~pgSchema,
1349
+ ~setQueryCache,
1343
1350
  ~rollback,
1344
1351
  ~isInReorgThreshold,
1345
1352
  ~config,
@@ -1483,6 +1490,7 @@ let make = (
1483
1490
  let storageName = "postgres"
1484
1491
 
1485
1492
  let indexManager = IndexManager.make()
1493
+ let setQueryCache = makeSetQueryCache()
1486
1494
 
1487
1495
  let loadCatalogRows = (sql, ~indexName=?) =>
1488
1496
  sql
@@ -2064,6 +2072,8 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
2064
2072
  ~table,
2065
2073
  ~itemSchema=itemSchema->S.toUnknown,
2066
2074
  ~pgSchema,
2075
+ ~setQueryCache,
2076
+ ~chainIdMode,
2067
2077
  )
2068
2078
  }
2069
2079
 
@@ -2298,7 +2308,12 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
2298
2308
  }
2299
2309
  })
2300
2310
 
2301
- (removals, restoredEntitiesResult)
2311
+ (
2312
+ removals,
2313
+ restoredEntitiesResult
2314
+ ->S.parseOrThrow(entityConfig.table->Table.pgRowsSchema)
2315
+ ->(Utils.magic: array<unknown> => array<Internal.entity>),
2316
+ )
2302
2317
  }
2303
2318
 
2304
2319
  let writeBatchMethod = async (
@@ -2347,6 +2362,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
2347
2362
  sql,
2348
2363
  ~batch,
2349
2364
  ~pgSchema,
2365
+ ~setQueryCache,
2350
2366
  ~rollback,
2351
2367
  ~isInReorgThreshold,
2352
2368
  ~config,
@@ -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));
@@ -1461,7 +1464,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::` + addrCh
1461
1464
  });
1462
1465
  return [
1463
1466
  removals,
1464
- restoredEntitiesResult
1467
+ S$RescriptSchema.parseOrThrow(restoredEntitiesResult, Table.pgRowsSchema(entityConfig.table))
1465
1468
  ];
1466
1469
  };
1467
1470
  let writeBatchMethod = async (batch, rollback, isInReorgThreshold, config, allEntities, updatedEffectsCache, updatedEntities, chainMetaData, onWrite) => {
@@ -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