envio 3.3.0-alpha.1 → 3.3.0-alpha.10

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 (136) hide show
  1. package/evm.schema.json +10 -0
  2. package/index.d.ts +45 -25
  3. package/package.json +7 -7
  4. package/src/Address.res +5 -2
  5. package/src/Address.res.mjs +3 -1
  6. package/src/Api.res +1 -1
  7. package/src/Api.res.mjs +1 -1
  8. package/src/Batch.res +0 -6
  9. package/src/Batch.res.mjs +1 -12
  10. package/src/BatchProcessing.res +7 -12
  11. package/src/BatchProcessing.res.mjs +8 -7
  12. package/src/Bin.res +3 -0
  13. package/src/Bin.res.mjs +4 -0
  14. package/src/ChainFetching.res +43 -9
  15. package/src/ChainFetching.res.mjs +34 -15
  16. package/src/ChainMetadata.res +1 -11
  17. package/src/ChainMetadata.res.mjs +1 -10
  18. package/src/ChainState.res +605 -151
  19. package/src/ChainState.res.mjs +427 -104
  20. package/src/ChainState.resi +72 -4
  21. package/src/Config.res +61 -33
  22. package/src/Config.res.mjs +50 -10
  23. package/src/ContractRegisterContext.res +2 -12
  24. package/src/ContractRegisterContext.res.mjs +3 -5
  25. package/src/Core.res +14 -3
  26. package/src/CrossChainState.res +113 -89
  27. package/src/CrossChainState.res.mjs +64 -60
  28. package/src/CrossChainState.resi +1 -1
  29. package/src/Ecosystem.res +5 -6
  30. package/src/Ecosystem.res.mjs +3 -3
  31. package/src/Envio.res +17 -11
  32. package/src/EnvioGlobal.res +53 -0
  33. package/src/EnvioGlobal.res.mjs +31 -0
  34. package/src/EventConfigBuilder.res +177 -88
  35. package/src/EventConfigBuilder.res.mjs +99 -42
  36. package/src/EventProcessing.res +74 -28
  37. package/src/EventProcessing.res.mjs +55 -29
  38. package/src/ExitOnCaughtUp.res +10 -2
  39. package/src/ExitOnCaughtUp.res.mjs +10 -1
  40. package/src/FetchState.res +544 -356
  41. package/src/FetchState.res.mjs +412 -471
  42. package/src/HandlerLoader.res +8 -104
  43. package/src/HandlerLoader.res.mjs +2 -81
  44. package/src/HandlerRegister.res +518 -144
  45. package/src/HandlerRegister.res.mjs +285 -133
  46. package/src/HandlerRegister.resi +24 -8
  47. package/src/IndexerState.res +10 -3
  48. package/src/IndexerState.res.mjs +11 -4
  49. package/src/IndexerState.resi +2 -1
  50. package/src/IndexingAddresses.res +108 -0
  51. package/src/IndexingAddresses.res.mjs +101 -0
  52. package/src/IndexingAddresses.resi +34 -0
  53. package/src/Internal.res +155 -46
  54. package/src/Internal.res.mjs +30 -2
  55. package/src/LoadLayer.res +5 -5
  56. package/src/LoadLayer.res.mjs +6 -6
  57. package/src/LogSelection.res +102 -165
  58. package/src/LogSelection.res.mjs +101 -116
  59. package/src/Main.res +71 -236
  60. package/src/Main.res.mjs +60 -145
  61. package/src/Metrics.res +74 -0
  62. package/src/Metrics.res.mjs +75 -0
  63. package/src/PgStorage.res +4 -4
  64. package/src/PgStorage.res.mjs +5 -5
  65. package/src/Prometheus.res +10 -63
  66. package/src/Prometheus.res.mjs +124 -171
  67. package/src/PruneStaleHistory.res +2 -2
  68. package/src/PruneStaleHistory.res.mjs +3 -3
  69. package/src/RawEvent.res +7 -2
  70. package/src/RawEvent.res.mjs +4 -4
  71. package/src/Rollback.res +3 -3
  72. package/src/Rollback.res.mjs +4 -4
  73. package/src/RollbackCommit.res +4 -1
  74. package/src/RollbackCommit.res.mjs +3 -2
  75. package/src/SimulateDeadInputTracker.res +85 -0
  76. package/src/SimulateDeadInputTracker.res.mjs +73 -0
  77. package/src/SimulateDeadInputTracker.resi +12 -0
  78. package/src/SimulateItems.res +91 -23
  79. package/src/SimulateItems.res.mjs +55 -32
  80. package/src/TestIndexer.res +54 -40
  81. package/src/TestIndexer.res.mjs +37 -30
  82. package/src/bindings/Performance.res +7 -0
  83. package/src/bindings/Performance.res.mjs +21 -0
  84. package/src/bindings/Performance.resi +7 -0
  85. package/src/bindings/Viem.res +0 -41
  86. package/src/bindings/Viem.res.mjs +1 -43
  87. package/src/sources/BlockStore.res +46 -0
  88. package/src/sources/BlockStore.res.mjs +24 -0
  89. package/src/sources/EventRouter.res +36 -33
  90. package/src/sources/EventRouter.res.mjs +13 -14
  91. package/src/sources/Evm.res +83 -72
  92. package/src/sources/Evm.res.mjs +53 -69
  93. package/src/sources/EvmChain.res +18 -20
  94. package/src/sources/EvmChain.res.mjs +15 -15
  95. package/src/sources/EvmRpcClient.res +74 -5
  96. package/src/sources/EvmRpcClient.res.mjs +13 -4
  97. package/src/sources/FieldMask.res +39 -0
  98. package/src/sources/FieldMask.res.mjs +42 -0
  99. package/src/sources/Fuel.res +9 -6
  100. package/src/sources/Fuel.res.mjs +5 -10
  101. package/src/sources/HyperFuelSource.res +46 -50
  102. package/src/sources/HyperFuelSource.res.mjs +64 -54
  103. package/src/sources/HyperSync.res +39 -7
  104. package/src/sources/HyperSync.res.mjs +57 -37
  105. package/src/sources/HyperSync.resi +12 -2
  106. package/src/sources/HyperSyncClient.res +22 -111
  107. package/src/sources/HyperSyncClient.res.mjs +2 -30
  108. package/src/sources/HyperSyncHeightStream.res +1 -8
  109. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  110. package/src/sources/HyperSyncSource.res +94 -107
  111. package/src/sources/HyperSyncSource.res.mjs +82 -86
  112. package/src/sources/Rpc.res +15 -47
  113. package/src/sources/Rpc.res.mjs +25 -56
  114. package/src/sources/RpcSource.res +320 -467
  115. package/src/sources/RpcSource.res.mjs +268 -385
  116. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  117. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  118. package/src/sources/SimulateSource.res +10 -4
  119. package/src/sources/SimulateSource.res.mjs +16 -6
  120. package/src/sources/Source.res +34 -6
  121. package/src/sources/SourceManager.res +77 -19
  122. package/src/sources/SourceManager.res.mjs +63 -21
  123. package/src/sources/SourceManager.resi +10 -0
  124. package/src/sources/Svm.res +31 -16
  125. package/src/sources/Svm.res.mjs +36 -13
  126. package/src/sources/SvmHyperSyncClient.res +5 -32
  127. package/src/sources/SvmHyperSyncSource.res +159 -134
  128. package/src/sources/SvmHyperSyncSource.res.mjs +164 -129
  129. package/src/sources/TransactionStore.res +49 -0
  130. package/src/sources/TransactionStore.res.mjs +30 -0
  131. package/src/tui/Tui.res +13 -16
  132. package/src/tui/Tui.res.mjs +12 -14
  133. package/svm.schema.json +49 -37
  134. package/src/bindings/Hrtime.res +0 -58
  135. package/src/bindings/Hrtime.res.mjs +0 -90
  136. package/src/bindings/Hrtime.resi +0 -30
package/evm.schema.json CHANGED
@@ -548,6 +548,16 @@
548
548
  "null"
549
549
  ]
550
550
  },
551
+ "headers": {
552
+ "description": "Optional HTTP headers sent with every request to this RPC endpoint, e.g. an Authorization bearer token for gated endpoints. Values support ${ENV_VAR} interpolation.",
553
+ "type": [
554
+ "object",
555
+ "null"
556
+ ],
557
+ "additionalProperties": {
558
+ "type": "string"
559
+ }
560
+ },
551
561
  "initial_block_interval": {
552
562
  "description": "The starting interval in range of blocks per query",
553
563
  "type": [
package/index.d.ts CHANGED
@@ -979,13 +979,24 @@ export type SvmInstructionParams = {
979
979
  readonly extraAccounts: readonly string[];
980
980
  };
981
981
 
982
- /** Block context for a matched instruction. */
982
+ /** Permissive fallback shape for an instruction's `block`. The generated
983
+ * per-instruction type narrows this to `slot`/`hash` (always present) and
984
+ * `time` (always present but possibly `undefined`), plus the selected
985
+ * `field_selection.block_fields`. */
983
986
  export type SvmInstructionBlock = {
984
987
  /** Slot this instruction's block was matched in. */
985
988
  readonly slot: number;
986
- readonly time: number;
987
- /** Always empty for now — reserved for the future reorg-guard route. */
989
+ /** Unix block time (seconds). Absent when HyperSync/Solana doesn't report a
990
+ * block time for this slot. */
991
+ readonly time?: number;
992
+ /** Block hash. */
988
993
  readonly hash: string;
994
+ /** Block height. Select via `field_selection.block_fields`. */
995
+ readonly height?: number;
996
+ /** Parent slot. Select via `field_selection.block_fields`. */
997
+ readonly parentSlot?: number;
998
+ /** Parent block hash. Select via `field_selection.block_fields`. */
999
+ readonly parentHash?: string;
989
1000
  };
990
1001
 
991
1002
  export type SvmTokenBalance = {
@@ -997,24 +1008,6 @@ export type SvmTokenBalance = {
997
1008
  readonly postAmount?: string;
998
1009
  };
999
1010
 
1000
- /** Parent transaction surfaced when an instruction's
1001
- * `include_transaction` flag is `true`. */
1002
- export type SvmTransaction = {
1003
- readonly signatures: readonly string[];
1004
- readonly feePayer?: string;
1005
- readonly success?: boolean;
1006
- readonly err?: string;
1007
- /** Lamports. */
1008
- readonly fee?: bigint;
1009
- readonly computeUnitsConsumed?: bigint;
1010
- readonly accountKeys: readonly string[];
1011
- readonly recentBlockhash?: string;
1012
- readonly version?: string;
1013
- /** SPL Token / Token-2022 balance snapshots for this transaction.
1014
- * Present when `include_token_balances` is `true`. */
1015
- readonly tokenBalances?: readonly SvmTokenBalance[];
1016
- };
1017
-
1018
1011
  export type SvmLog = {
1019
1012
  readonly kind: string;
1020
1013
  readonly message: string;
@@ -1033,6 +1026,8 @@ export type SvmLog = {
1033
1026
  * are base58 strings. */
1034
1027
  export type SvmInstruction<
1035
1028
  Params extends SvmInstructionParams = SvmInstructionParams,
1029
+ Tx = SvmTransaction,
1030
+ Block = SvmInstructionBlock,
1036
1031
  > = {
1037
1032
  /** Program name as declared under `programs[].name` in `config.yaml`. */
1038
1033
  readonly programName: string;
@@ -1050,12 +1045,20 @@ export type SvmInstruction<
1050
1045
  readonly d8?: string;
1051
1046
  /** Borsh-decoded params. Present when a schema is configured and matched. */
1052
1047
  readonly params?: Params;
1053
- /** Present when the instruction's `include_transaction` is `true`. */
1054
- readonly transaction?: SvmTransaction;
1048
+ /** Parent transaction. Carries only the fields selected via this
1049
+ * instruction's `field_selection`; unselected fields are typed as
1050
+ * `FieldNotSelected<...>` so reading them is a compile error. Always present
1051
+ * (`{}` when no fields are selected). */
1052
+ readonly transaction: Tx;
1055
1053
  /** Present when the instruction's `include_logs` is `true`; only logs
1056
1054
  * scoped to this exact instruction (matching `instruction_address`). */
1057
1055
  readonly logs?: readonly SvmLog[];
1058
- readonly block: SvmInstructionBlock;
1056
+ /** The block this instruction's slot belongs to. Carries `slot`/`hash`
1057
+ * (always present) and `time` (always present but possibly `undefined`),
1058
+ * plus the fields selected via this instruction's
1059
+ * `field_selection.block_fields`; unselected fields are typed as
1060
+ * `FieldNotSelected<...>`. */
1061
+ readonly block: Block;
1059
1062
  };
1060
1063
 
1061
1064
  /** Arguments passed to handlers registered via `indexer.onInstruction`. */
@@ -1327,7 +1330,11 @@ type SvmEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
1327
1330
  handler: (
1328
1331
  args: SvmOnInstructionHandlerArgs<
1329
1332
  Config,
1330
- SvmInstruction<SvmParamsFromProgramTable<Programs[P][I]>>
1333
+ SvmInstruction<
1334
+ SvmParamsFromProgramTable<Programs[P][I]>,
1335
+ Programs[P][I]["transaction"],
1336
+ Programs[P][I]["block"]
1337
+ >
1331
1338
  >,
1332
1339
  ) => Promise<void>,
1333
1340
  ) => void;
@@ -1695,6 +1702,7 @@ type EvmContractsT = GlobalConfig extends { evm: { contracts: infer X extends
1695
1702
  type FuelChainsT = GlobalConfig extends { fuel: { chains: infer X extends Record<string, { id: number }> } } ? X : {};
1696
1703
  type FuelContractsT = GlobalConfig extends { fuel: { contracts: infer X extends Record<string, Record<string, any>> } } ? X : {};
1697
1704
  type SvmChainsT = GlobalConfig extends { svm: { chains: infer X extends Record<string, { id: number }> } } ? X : {};
1705
+ type SvmProgramsT = GlobalConfig extends { svm: { programs: infer X extends Record<string, Record<string, any>> } } ? X : {};
1698
1706
  type EntitiesT = GlobalConfig extends { entities: infer X extends Record<string, object> } ? X : {};
1699
1707
  type EnumsT = GlobalConfig extends { enums: infer X extends Record<string, any> } ? X : {};
1700
1708
 
@@ -1716,6 +1724,18 @@ export type FuelChainId = IsEmptyObject<FuelChainsT> extends true ? NotConfigure
1716
1724
  /** Union of all configured SVM chain IDs. */
1717
1725
  export type SvmChainId = IsEmptyObject<SvmChainsT> extends true ? NotConfigured<"SvmChainId", "Configure SVM chains"> : SvmChainsT [keyof SvmChainsT ]["id"];
1718
1726
 
1727
+ /** The SVM parent-transaction type generated from this project's
1728
+ * `field_selection`: the union of every instruction's `transaction` shape,
1729
+ * with unselected fields typed as `FieldNotSelected<...>`. Resolves to a
1730
+ * `NotConfigured` hint until `envio codegen` augments {@link Global}. */
1731
+ export type SvmTransaction = IsEmptyObject<SvmProgramsT> extends true
1732
+ ? NotConfigured<"SvmTransaction", "Configure SVM programs">
1733
+ : {
1734
+ [P in keyof SvmProgramsT]: {
1735
+ [I in keyof SvmProgramsT[P]]: SvmProgramsT[P][I]["transaction"];
1736
+ }[keyof SvmProgramsT[P]];
1737
+ }[keyof SvmProgramsT];
1738
+
1719
1739
  /** Lookup an EVM event type by contract and event name. Without generics,
1720
1740
  * resolves to the discriminated union of every EVM event in the project. */
1721
1741
  export type EvmEvent<
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.3.0-alpha.1",
3
+ "version": "3.3.0-alpha.10",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -57,7 +57,7 @@
57
57
  "yargs": "17.7.2",
58
58
  "@rescript/runtime": "12.2.0",
59
59
  "rescript-schema": "9.5.1",
60
- "viem": "2.46.2",
60
+ "viem": "2.54.0",
61
61
  "dotenv": "16.4.5",
62
62
  "date-fns": "3.3.1",
63
63
  "@rescript/react": "0.14.1",
@@ -69,10 +69,10 @@
69
69
  "tsx": "4.21.0"
70
70
  },
71
71
  "optionalDependencies": {
72
- "envio-linux-x64": "3.3.0-alpha.1",
73
- "envio-linux-x64-musl": "3.3.0-alpha.1",
74
- "envio-linux-arm64": "3.3.0-alpha.1",
75
- "envio-darwin-x64": "3.3.0-alpha.1",
76
- "envio-darwin-arm64": "3.3.0-alpha.1"
72
+ "envio-linux-x64": "3.3.0-alpha.10",
73
+ "envio-linux-x64-musl": "3.3.0-alpha.10",
74
+ "envio-linux-arm64": "3.3.0-alpha.10",
75
+ "envio-darwin-x64": "3.3.0-alpha.10",
76
+ "envio-darwin-arm64": "3.3.0-alpha.10"
77
77
  }
78
78
  }
package/src/Address.res CHANGED
@@ -11,8 +11,11 @@ module Evm = {
11
11
  external fromStringOrThrow: string => t = "getAddress"
12
12
 
13
13
  // NOTE: the function is named to be overshadowed by the one below, so that we don't have to import viem in the handler code
14
+ // strict:false checks only the 20-byte hex shape, not EIP-55 checksum casing —
15
+ // since we're about to lowercase it anyway, an all-uppercase or wrongly-cased
16
+ // input address is just as valid as one that's already lowercase.
14
17
  @module("viem")
15
- external fromStringLowercaseOrThrow: string => bool = "isAddress"
18
+ external fromStringLowercaseOrThrow: (string, {"strict": bool}) => bool = "isAddress"
16
19
 
17
20
  // Reassign since the function might be used in the handler code
18
21
  // and we don't want to have a "viem" import there. It's needed to keep "viem" a dependency
@@ -20,7 +23,7 @@ module Evm = {
20
23
  // Also, we want a custom error message, which is searchable in our codebase.
21
24
  // Validate that the string is a proper address but return a lowercased value
22
25
  let fromStringLowercaseOrThrow = string => {
23
- if fromStringLowercaseOrThrow(string) {
26
+ if fromStringLowercaseOrThrow(string, {"strict": false}) {
24
27
  unsafeFromString(string->String.toLowerCase)
25
28
  } else {
26
29
  JsError.throwWithMessage(
@@ -7,7 +7,9 @@ import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
7
7
  let schema = S$RescriptSchema.setName(S$RescriptSchema.string, "Address");
8
8
 
9
9
  function fromStringLowercaseOrThrow(string) {
10
- if (Viem.isAddress(string)) {
10
+ if (Viem.isAddress(string, {
11
+ strict: false
12
+ })) {
11
13
  return string.toLowerCase();
12
14
  } else {
13
15
  return Stdlib_JsError.throwWithMessage(`Address "` + string + `" is invalid. Expected a 20-byte hex string starting with 0x.`);
package/src/Api.res CHANGED
@@ -9,7 +9,7 @@ let createTestIndexer: unit => unknown = () => {
9
9
  NodeJs.Path.getDirname(NodeJs.ImportMeta.importMeta),
10
10
  "TestIndexerWorker.res.mjs",
11
11
  )->NodeJs.Path.toString
12
- TestIndexer.makeCreateTestIndexer(~config=Config.loadWithoutRegistrations(), ~workerPath)()->(
12
+ TestIndexer.makeCreateTestIndexer(~config=Config.load(), ~workerPath)()->(
13
13
  Utils.magic: TestIndexer.t<'a> => unknown
14
14
  )
15
15
  }
package/src/Api.res.mjs CHANGED
@@ -10,7 +10,7 @@ let indexer = Main.getGlobalIndexer();
10
10
 
11
11
  function createTestIndexer() {
12
12
  let workerPath = Path.join(NodeJs.Path.getDirname(import.meta), "TestIndexerWorker.res.mjs");
13
- return TestIndexer.makeCreateTestIndexer(Config.loadWithoutRegistrations(), workerPath)();
13
+ return TestIndexer.makeCreateTestIndexer(Config.load(), workerPath)();
14
14
  }
15
15
 
16
16
  export {
package/src/Batch.res CHANGED
@@ -34,12 +34,6 @@ type t = {
34
34
  checkpointEventsProcessed: array<int>,
35
35
  }
36
36
 
37
- let hasReadyItem = (fetchStates: array<FetchState.t>) => {
38
- fetchStates->Array.some(fetchState => {
39
- fetchState->FetchState.isActivelyIndexing && fetchState->FetchState.hasReadyItem
40
- })
41
- }
42
-
43
37
  let getProgressedChainsById = {
44
38
  let getChainAfterBatchIfProgressed = (
45
39
  ~chainBeforeBatch: chainBeforeBatch,
package/src/Batch.res.mjs CHANGED
@@ -4,16 +4,6 @@ import * as Utils from "./Utils.res.mjs";
4
4
  import * as FetchState from "./FetchState.res.mjs";
5
5
  import * as ReorgDetection from "./ReorgDetection.res.mjs";
6
6
 
7
- function hasReadyItem(fetchStates) {
8
- return fetchStates.some(fetchState => {
9
- if (FetchState.isActivelyIndexing(fetchState)) {
10
- return FetchState.hasReadyItem(fetchState);
11
- } else {
12
- return false;
13
- }
14
- });
15
- }
16
-
17
7
  function getChainAfterBatchIfProgressed(chainBeforeBatch, progressBlockNumberAfterBatch, fetchStateAfterBatch, batchSize) {
18
8
  if (chainBeforeBatch.progressBlockNumber < progressBlockNumberAfterBatch) {
19
9
  return {
@@ -37,7 +27,7 @@ function getProgressedChainsById(chainsBeforeBatch, batchSizePerChain, progressB
37
27
  let progressedChain;
38
28
  if (batchSize !== undefined) {
39
29
  let leftItems = fetchState.buffer.slice(batchSize);
40
- progressedChain = getChainAfterBatchIfProgressed(chainBeforeBatch, progressBlockNumberAfterBatch, FetchState.updateInternal(fetchState, undefined, undefined, leftItems, undefined, undefined), batchSize);
30
+ progressedChain = getChainAfterBatchIfProgressed(chainBeforeBatch, progressBlockNumberAfterBatch, FetchState.updateInternal(fetchState, undefined, leftItems, undefined, undefined), batchSize);
41
31
  } else {
42
32
  progressedChain = getChainAfterBatchIfProgressed(chainBeforeBatch, progressBlockNumberAfterBatch, chainBeforeBatch.fetchState, 0);
43
33
  }
@@ -208,7 +198,6 @@ function findLastEventItem(batch, chainId) {
208
198
  }
209
199
 
210
200
  export {
211
- hasReadyItem,
212
201
  getProgressedChainsById,
213
202
  addReorgCheckpoints,
214
203
  prepareBatch,
@@ -77,15 +77,7 @@ and processNextBatch = async (state: IndexerState.t, ~scheduleFetch): unit => {
77
77
  state
78
78
  ->IndexerState.chainStates
79
79
  ->Dict.valuesToArray
80
- ->Array.every(cs => {
81
- let fetchState = switch progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
82
- (cs->ChainState.fetchState).chainId,
83
- ) {
84
- | Some(chainAfterBatch) => chainAfterBatch.fetchState
85
- | None => cs->ChainState.fetchState
86
- }
87
- fetchState->FetchState.isReadyToEnterReorgThreshold
88
- })
80
+ ->Array.every(cs => cs->ChainState.isReadyToEnterReorgThresholdAfterBatch(~batch))
89
81
 
90
82
  if shouldEnterReorgThreshold {
91
83
  IndexerState.enterReorgThreshold(state)
@@ -135,6 +127,11 @@ and processNextBatch = async (state: IndexerState.t, ~scheduleFetch): unit => {
135
127
  | Ok() =>
136
128
  state->IndexerState.recordProcessedBatch
137
129
 
130
+ switch state->IndexerState.simulateDeadInputTracker {
131
+ | Some(tracker) => tracker->SimulateDeadInputTracker.recordProcessed(~batch)
132
+ | None => ()
133
+ }
134
+
138
135
  if state->IndexerState.isResolvingReorg {
139
136
  // A reorg landed while this batch was processing. Apply its progress so
140
137
  // the rollback diff is computed against up-to-date chain progress, but
@@ -171,9 +168,7 @@ and processNextBatch = async (state: IndexerState.t, ~scheduleFetch): unit => {
171
168
  state
172
169
  ->IndexerState.chainStates
173
170
  ->Dict.valuesToArray
174
- ->Array.every(cs =>
175
- cs->ChainState.isProgressAtHead && (cs->ChainState.fetchState).endBlock->Option.isNone
176
- )
171
+ ->Array.every(ChainState.isAtHeadWithoutEndBlock)
177
172
  ) {
178
173
  IndexerState.errorExit(
179
174
  state,
@@ -4,16 +4,16 @@ import * as Utils from "./Utils.res.mjs";
4
4
  import * as Config from "./Config.res.mjs";
5
5
  import * as Logging from "./Logging.res.mjs";
6
6
  import * as ChainState from "./ChainState.res.mjs";
7
- import * as FetchState from "./FetchState.res.mjs";
8
7
  import * as IndexerState from "./IndexerState.res.mjs";
9
8
  import * as ChainMetadata from "./ChainMetadata.res.mjs";
10
9
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
11
10
  import * as InMemoryStore from "./InMemoryStore.res.mjs";
12
- import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
13
11
  import * as ExitOnCaughtUp from "./ExitOnCaughtUp.res.mjs";
14
12
  import * as EventProcessing from "./EventProcessing.res.mjs";
13
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
15
14
  import * as PruneStaleHistory from "./PruneStaleHistory.res.mjs";
16
15
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
16
+ import * as SimulateDeadInputTracker from "./SimulateDeadInputTracker.res.mjs";
17
17
 
18
18
  function yieldTick() {
19
19
  return new Promise((resolve, param) => {
@@ -30,10 +30,7 @@ async function processNextBatch(state, scheduleFetch) {
30
30
  let batch = IndexerState.createBatch(state, IndexerState.processedCheckpointId(state), IndexerState.config(state).batchSize, isRollbackBatch);
31
31
  let progressedChainsById = batch.progressedChainsById;
32
32
  let isBelowReorgThreshold = !isInReorgThresholdBeforeUpdate && IndexerState.config(state).shouldRollbackOnReorg;
33
- let shouldEnterReorgThreshold = isBelowReorgThreshold && Object.values(IndexerState.chainStates(state)).every(cs => {
34
- let chainAfterBatch = progressedChainsById[ChainState.fetchState(cs).chainId];
35
- return FetchState.isReadyToEnterReorgThreshold(chainAfterBatch !== undefined ? chainAfterBatch.fetchState : ChainState.fetchState(cs));
36
- });
33
+ let shouldEnterReorgThreshold = isBelowReorgThreshold && Object.values(IndexerState.chainStates(state)).every(cs => ChainState.isReadyToEnterReorgThresholdAfterBatch(cs, batch));
37
34
  if (shouldEnterReorgThreshold) {
38
35
  IndexerState.enterReorgThreshold(state);
39
36
  }
@@ -60,6 +57,10 @@ async function processNextBatch(state, scheduleFetch) {
60
57
  return IndexerState.errorExit(state, errHandler._0);
61
58
  }
62
59
  IndexerState.recordProcessedBatch(state);
60
+ let tracker = IndexerState.simulateDeadInputTracker(state);
61
+ if (tracker !== undefined) {
62
+ SimulateDeadInputTracker.recordProcessed(Primitive_option.valFromOption(tracker), batch);
63
+ }
63
64
  if (IndexerState.isResolvingReorg(state)) {
64
65
  return IndexerState.applyBatchProgress(state, batch);
65
66
  }
@@ -75,7 +76,7 @@ async function processNextBatch(state, scheduleFetch) {
75
76
  }
76
77
  if (allCaughtUp && !IndexerState.keepProcessAlive(state)) {
77
78
  return await ExitOnCaughtUp.run(state);
78
- } else if (!allCaughtUp && IndexerState.exitAfterFirstEventBlock(state) && Object.values(IndexerState.chainStates(state)).every(cs => ChainState.isProgressAtHead(cs) ? Stdlib_Option.isNone(ChainState.fetchState(cs).endBlock) : false)) {
79
+ } else if (!allCaughtUp && IndexerState.exitAfterFirstEventBlock(state) && Object.values(IndexerState.chainStates(state)).every(ChainState.isAtHeadWithoutEndBlock)) {
79
80
  return IndexerState.errorExit(state, ErrorHandling.make(new Error("No events found between startBlock and chain head. Cannot auto-detect endBlock."), undefined, undefined));
80
81
  } else {
81
82
  ChainMetadata.stage(state);
package/src/Bin.res CHANGED
@@ -71,6 +71,9 @@ let run = async args => {
71
71
  }
72
72
  }
73
73
  } catch {
74
+ | Main.FatalError(_) =>
75
+ // Already logged with full context by Main.start's onError; just exit.
76
+ NodeJs.process->NodeJs.exitWithCode(Failure)
74
77
  | exn =>
75
78
  // Log just the exception's own message — wrapping it in "Failed at
76
79
  // initialization" and pino's err serializer buries the real cause under
package/src/Bin.res.mjs CHANGED
@@ -79,6 +79,10 @@ async function run(args) {
79
79
  }
80
80
  } catch (raw_exn) {
81
81
  let exn = Primitive_exceptions.internalToException(raw_exn);
82
+ if (exn.RE_EXN_ID === Main.FatalError) {
83
+ Process.exit(1);
84
+ return;
85
+ }
82
86
  let e = Primitive_exceptions.internalToException(exn);
83
87
  let message = e.RE_EXN_ID === "JsExn" ? Stdlib_Option.getOr(Stdlib_JsExn.message(e._1), "Failed at initialization") : "Failed at initialization";
84
88
  Logging.error(message);
@@ -11,7 +11,19 @@ type partitionQueryResponse = {
11
11
  let runContractRegistersOrThrow = async (
12
12
  ~itemsWithContractRegister: array<Internal.item>,
13
13
  ~config: Config.t,
14
+ ~transactionStore: option<TransactionStore.t>,
15
+ ~blockStore: option<BlockStore.t>,
14
16
  ) => {
17
+ // contractRegister handlers can read event.transaction and event.block, so
18
+ // materialise the selected fields onto the payloads before running them. All
19
+ // items belong to the chain being fetched, hence its single page stores.
20
+ await ChainState.materializePageItems(
21
+ ~items=itemsWithContractRegister,
22
+ ~transactionStore,
23
+ ~blockStore,
24
+ ~ecosystem=config.ecosystem.name,
25
+ )
26
+
15
27
  let itemsWithDcs = []
16
28
 
17
29
  let onRegister = (~item: Internal.item, ~contractAddress, ~contractName) => {
@@ -38,8 +50,8 @@ let runContractRegistersOrThrow = async (
38
50
  let item = itemsWithContractRegister->Array.getUnsafe(idx)
39
51
  let eventItem = item->Internal.castUnsafeEventItem
40
52
  let contractRegister = switch eventItem {
41
- | {eventConfig: {contractRegister: Some(contractRegister)}} => contractRegister
42
- | {eventConfig: {contractRegister: None, name: eventName}} =>
53
+ | {onEventRegistration: {contractRegister: Some(contractRegister)}} => contractRegister
54
+ | {onEventRegistration: {contractRegister: None, eventConfig: {name: eventName}}} =>
43
55
  // Unexpected case, since we should pass only events with contract register to this function
44
56
  JsError.throwWithMessage("Contract register is not set for event " ++ eventName)
45
57
  }
@@ -106,6 +118,8 @@ let rec onQueryResponse = async (
106
118
  let chainState = state->IndexerState.getChainState(~chain)
107
119
  let {
108
120
  parsedQueueItems,
121
+ transactionStore,
122
+ blockStore,
109
123
  latestFetchedBlockNumber,
110
124
  latestFetchedBlockTimestamp,
111
125
  stats,
@@ -114,7 +128,7 @@ let rec onQueryResponse = async (
114
128
  fromBlockQueried,
115
129
  } = response
116
130
 
117
- if knownHeight > (chainState->ChainState.fetchState).knownHeight {
131
+ if knownHeight > chainState->ChainState.knownHeight {
118
132
  Prometheus.SourceHeight.set(
119
133
  ~blockNumber=knownHeight,
120
134
  ~chainId=(chainState->ChainState.chainConfig).id,
@@ -133,6 +147,20 @@ let rec onQueryResponse = async (
133
147
  ~blockRangeSize=latestFetchedBlockNumber - fromBlockQueried + 1,
134
148
  )
135
149
 
150
+ let numContractRegisterEvents = parsedQueueItems->Array.reduce(0, (count, item) => {
151
+ let eventItem = item->Internal.castUnsafeEventItem
152
+ eventItem.onEventRegistration.contractRegister !== None ? count + 1 : count
153
+ })
154
+ Logging.trace({
155
+ "msg": "Finished querying",
156
+ "chainId": chain->ChainMap.Chain.toChainId,
157
+ "partitionId": query.partitionId,
158
+ "fromBlock": fromBlockQueried,
159
+ "toBlock": latestFetchedBlockNumber,
160
+ "numEvents": parsedQueueItems->Array.length,
161
+ "numContractRegisterEvents": numContractRegisterEvents,
162
+ })
163
+
136
164
  let reorgResult = chainState->ChainState.registerReorgGuard(~blockHashes, ~knownHeight)
137
165
 
138
166
  let rollbackWithReorgDetectedBlockNumber = switch reorgResult {
@@ -189,7 +217,7 @@ let rec onQueryResponse = async (
189
217
  for idx in 0 to parsedQueueItems->Array.length - 1 {
190
218
  let item = parsedQueueItems->Array.getUnsafe(idx)
191
219
  let eventItem = item->Internal.castUnsafeEventItem
192
- if eventItem.eventConfig.contractRegister !== None {
220
+ if eventItem.onEventRegistration.contractRegister !== None {
193
221
  itemsWithContractRegister->Array.push(item)
194
222
  }
195
223
  // TODO: Don't really need to keep it in the queue
@@ -212,6 +240,8 @@ let rec onQueryResponse = async (
212
240
  blockTimestamp: latestFetchedBlockTimestamp,
213
241
  },
214
242
  ~query,
243
+ ~transactionStore,
244
+ ~blockStore,
215
245
  )
216
246
  ChainMetadata.stage(state)
217
247
  scheduleFetch()
@@ -224,6 +254,8 @@ let rec onQueryResponse = async (
224
254
  switch await runContractRegistersOrThrow(
225
255
  ~itemsWithContractRegister,
226
256
  ~config=state->IndexerState.config,
257
+ ~transactionStore,
258
+ ~blockStore,
227
259
  ) {
228
260
  | exception exn => IndexerState.errorExit(state, exn->ErrorHandling.make)
229
261
  | newItemsWithDcs => proceed(~newItemsWithDcs)
@@ -240,6 +272,8 @@ and applyQueryResponse = (
240
272
  ~knownHeight,
241
273
  ~latestFetchedBlock,
242
274
  ~query,
275
+ ~transactionStore,
276
+ ~blockStore,
243
277
  ) => {
244
278
  let chainState = state->IndexerState.getChainState(~chain)
245
279
  let wasFetchingAtHead = chainState->ChainState.isFetchingAtHead
@@ -250,6 +284,8 @@ and applyQueryResponse = (
250
284
  ~newItems,
251
285
  ~newItemsWithDcs,
252
286
  ~knownHeight,
287
+ ~transactionStore,
288
+ ~blockStore,
253
289
  )
254
290
 
255
291
  // In auto-exit mode, set endBlock to the first event's block when events arrive.
@@ -294,7 +330,7 @@ let finishWaitingForNewBlock = (
294
330
  ->IndexerState.chainStates
295
331
  ->Dict.valuesToArray
296
332
  ->Array.every(cs => {
297
- cs->ChainState.fetchState->FetchState.isReadyToEnterReorgThreshold
333
+ cs->ChainState.isReadyToEnterReorgThreshold
298
334
  })
299
335
 
300
336
  // Kick processing in case there are block handlers to run.
@@ -316,7 +352,6 @@ let fetchChain = async (
316
352
  ) => {
317
353
  let chainState = state->IndexerState.getChainState(~chain)
318
354
  if !(state->IndexerState.isResolvingReorg) && !(state->IndexerState.isStopped) {
319
- let fetchState = chainState->ChainState.fetchState
320
355
  let isRealtime = state->IndexerState.isRealtime
321
356
  let sourceManager = chainState->ChainState.sourceManager
322
357
 
@@ -327,8 +362,7 @@ let fetchChain = async (
327
362
  // Owns its error boundary: launch doesn't catch, so any failure here (the
328
363
  // query, response handling, or dispatch itself) must stop the indexer.
329
364
  try {
330
- await sourceManager->SourceManager.dispatch(
331
- ~fetchState,
365
+ await chainState->ChainState.dispatch(
332
366
  ~waitForNewBlock=(~knownHeight) =>
333
367
  sourceManager->SourceManager.waitForNewBlock(~knownHeight, ~isRealtime, ~reducedPolling),
334
368
  ~onNewBlock=(~knownHeight) =>
@@ -347,7 +381,7 @@ let fetchChain = async (
347
381
  try {
348
382
  let response = await sourceManager->SourceManager.executeQuery(
349
383
  ~query,
350
- ~knownHeight=fetchState.knownHeight,
384
+ ~knownHeight=chainState->ChainState.knownHeight,
351
385
  ~isRealtime,
352
386
  )
353
387
  await onQueryResponse(