envio 3.3.0-alpha.0 → 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 (140) 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 +48 -16
  15. package/src/ChainFetching.res.mjs +36 -17
  16. package/src/ChainMetadata.res +1 -11
  17. package/src/ChainMetadata.res.mjs +1 -10
  18. package/src/ChainState.res +628 -149
  19. package/src/ChainState.res.mjs +440 -102
  20. package/src/ChainState.resi +74 -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 +142 -93
  27. package/src/CrossChainState.res.mjs +88 -60
  28. package/src/CrossChainState.resi +2 -10
  29. package/src/Ecosystem.res +5 -6
  30. package/src/Ecosystem.res.mjs +3 -3
  31. package/src/Env.res +0 -7
  32. package/src/Env.res.mjs +0 -6
  33. package/src/Envio.res +17 -11
  34. package/src/EnvioGlobal.res +53 -0
  35. package/src/EnvioGlobal.res.mjs +31 -0
  36. package/src/EventConfigBuilder.res +177 -88
  37. package/src/EventConfigBuilder.res.mjs +99 -42
  38. package/src/EventProcessing.res +74 -28
  39. package/src/EventProcessing.res.mjs +55 -29
  40. package/src/ExitOnCaughtUp.res +10 -2
  41. package/src/ExitOnCaughtUp.res.mjs +10 -1
  42. package/src/FetchState.res +558 -300
  43. package/src/FetchState.res.mjs +437 -423
  44. package/src/HandlerLoader.res +8 -104
  45. package/src/HandlerLoader.res.mjs +2 -81
  46. package/src/HandlerRegister.res +518 -144
  47. package/src/HandlerRegister.res.mjs +285 -133
  48. package/src/HandlerRegister.resi +24 -8
  49. package/src/IndexerLoop.res +2 -3
  50. package/src/IndexerLoop.res.mjs +1 -1
  51. package/src/IndexerState.res +10 -11
  52. package/src/IndexerState.res.mjs +14 -11
  53. package/src/IndexerState.resi +2 -5
  54. package/src/IndexingAddresses.res +108 -0
  55. package/src/IndexingAddresses.res.mjs +101 -0
  56. package/src/IndexingAddresses.resi +34 -0
  57. package/src/Internal.res +155 -46
  58. package/src/Internal.res.mjs +30 -2
  59. package/src/LoadLayer.res +5 -5
  60. package/src/LoadLayer.res.mjs +6 -6
  61. package/src/LogSelection.res +102 -165
  62. package/src/LogSelection.res.mjs +101 -116
  63. package/src/Main.res +71 -236
  64. package/src/Main.res.mjs +60 -145
  65. package/src/Metrics.res +74 -0
  66. package/src/Metrics.res.mjs +75 -0
  67. package/src/PgStorage.res +4 -4
  68. package/src/PgStorage.res.mjs +5 -5
  69. package/src/Prometheus.res +10 -74
  70. package/src/Prometheus.res.mjs +124 -186
  71. package/src/PruneStaleHistory.res +2 -2
  72. package/src/PruneStaleHistory.res.mjs +3 -3
  73. package/src/RawEvent.res +7 -2
  74. package/src/RawEvent.res.mjs +4 -4
  75. package/src/Rollback.res +3 -3
  76. package/src/Rollback.res.mjs +4 -4
  77. package/src/RollbackCommit.res +4 -1
  78. package/src/RollbackCommit.res.mjs +3 -2
  79. package/src/SimulateDeadInputTracker.res +85 -0
  80. package/src/SimulateDeadInputTracker.res.mjs +73 -0
  81. package/src/SimulateDeadInputTracker.resi +12 -0
  82. package/src/SimulateItems.res +91 -23
  83. package/src/SimulateItems.res.mjs +55 -32
  84. package/src/TestIndexer.res +54 -40
  85. package/src/TestIndexer.res.mjs +37 -30
  86. package/src/bindings/Performance.res +7 -0
  87. package/src/bindings/Performance.res.mjs +21 -0
  88. package/src/bindings/Performance.resi +7 -0
  89. package/src/bindings/Viem.res +0 -41
  90. package/src/bindings/Viem.res.mjs +1 -43
  91. package/src/sources/BlockStore.res +46 -0
  92. package/src/sources/BlockStore.res.mjs +24 -0
  93. package/src/sources/EventRouter.res +36 -33
  94. package/src/sources/EventRouter.res.mjs +13 -14
  95. package/src/sources/Evm.res +83 -72
  96. package/src/sources/Evm.res.mjs +53 -69
  97. package/src/sources/EvmChain.res +18 -20
  98. package/src/sources/EvmChain.res.mjs +15 -15
  99. package/src/sources/EvmRpcClient.res +74 -5
  100. package/src/sources/EvmRpcClient.res.mjs +13 -4
  101. package/src/sources/FieldMask.res +39 -0
  102. package/src/sources/FieldMask.res.mjs +42 -0
  103. package/src/sources/Fuel.res +9 -6
  104. package/src/sources/Fuel.res.mjs +5 -10
  105. package/src/sources/HyperFuelSource.res +46 -50
  106. package/src/sources/HyperFuelSource.res.mjs +64 -54
  107. package/src/sources/HyperSync.res +39 -7
  108. package/src/sources/HyperSync.res.mjs +57 -37
  109. package/src/sources/HyperSync.resi +12 -2
  110. package/src/sources/HyperSyncClient.res +22 -111
  111. package/src/sources/HyperSyncClient.res.mjs +2 -30
  112. package/src/sources/HyperSyncHeightStream.res +1 -8
  113. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  114. package/src/sources/HyperSyncSource.res +94 -107
  115. package/src/sources/HyperSyncSource.res.mjs +82 -86
  116. package/src/sources/Rpc.res +15 -47
  117. package/src/sources/Rpc.res.mjs +25 -56
  118. package/src/sources/RpcSource.res +320 -467
  119. package/src/sources/RpcSource.res.mjs +268 -385
  120. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  121. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  122. package/src/sources/SimulateSource.res +10 -4
  123. package/src/sources/SimulateSource.res.mjs +16 -6
  124. package/src/sources/Source.res +34 -6
  125. package/src/sources/SourceManager.res +86 -27
  126. package/src/sources/SourceManager.res.mjs +84 -47
  127. package/src/sources/SourceManager.resi +12 -3
  128. package/src/sources/Svm.res +31 -16
  129. package/src/sources/Svm.res.mjs +36 -13
  130. package/src/sources/SvmHyperSyncClient.res +5 -32
  131. package/src/sources/SvmHyperSyncSource.res +159 -134
  132. package/src/sources/SvmHyperSyncSource.res.mjs +164 -129
  133. package/src/sources/TransactionStore.res +49 -0
  134. package/src/sources/TransactionStore.res.mjs +30 -0
  135. package/src/tui/Tui.res +13 -16
  136. package/src/tui/Tui.res.mjs +12 -14
  137. package/svm.schema.json +49 -37
  138. package/src/bindings/Hrtime.res +0 -58
  139. package/src/bindings/Hrtime.res.mjs +0 -90
  140. 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.0",
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.0",
73
- "envio-linux-x64-musl": "3.3.0-alpha.0",
74
- "envio-linux-arm64": "3.3.0-alpha.0",
75
- "envio-darwin-x64": "3.3.0-alpha.0",
76
- "envio-darwin-arm64": "3.3.0-alpha.0"
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.
@@ -308,8 +344,7 @@ let finishWaitingForNewBlock = (
308
344
  let fetchChain = async (
309
345
  state: IndexerState.t,
310
346
  chain,
311
- ~concurrencyLimit,
312
- ~bufferLimit,
347
+ ~action,
313
348
  ~stateId,
314
349
  ~scheduleFetch,
315
350
  ~scheduleProcessing,
@@ -317,19 +352,17 @@ let fetchChain = async (
317
352
  ) => {
318
353
  let chainState = state->IndexerState.getChainState(~chain)
319
354
  if !(state->IndexerState.isResolvingReorg) && !(state->IndexerState.isStopped) {
320
- let fetchState = chainState->ChainState.fetchState
321
355
  let isRealtime = state->IndexerState.isRealtime
322
356
  let sourceManager = chainState->ChainState.sourceManager
323
357
 
324
- // Only affects the WaitingForNewBlock branch of fetchNext, where
358
+ // Only affects the WaitingForNewBlock branch of dispatch, where
325
359
  // there's nothing to fetch. During backfill any such chain is idle.
326
360
  let reducedPolling = !isRealtime
327
361
 
328
362
  // Owns its error boundary: launch doesn't catch, so any failure here (the
329
- // query, response handling, or fetchNext itself) must stop the indexer.
363
+ // query, response handling, or dispatch itself) must stop the indexer.
330
364
  try {
331
- await sourceManager->SourceManager.fetchNext(
332
- ~fetchState,
365
+ await chainState->ChainState.dispatch(
333
366
  ~waitForNewBlock=(~knownHeight) =>
334
367
  sourceManager->SourceManager.waitForNewBlock(~knownHeight, ~isRealtime, ~reducedPolling),
335
368
  ~onNewBlock=(~knownHeight) =>
@@ -343,12 +376,12 @@ let fetchChain = async (
343
376
  ),
344
377
  ~executeQuery=async query => {
345
378
  // Caught here (not just by the outer try) so the query promise never
346
- // rejects: fetchNext spins a side-chain off it that would otherwise
379
+ // rejects: dispatch spins a side-chain off it that would otherwise
347
380
  // become an unhandled rejection.
348
381
  try {
349
382
  let response = await sourceManager->SourceManager.executeQuery(
350
383
  ~query,
351
- ~knownHeight=fetchState.knownHeight,
384
+ ~knownHeight=chainState->ChainState.knownHeight,
352
385
  ~isRealtime,
353
386
  )
354
387
  await onQueryResponse(
@@ -363,8 +396,7 @@ let fetchChain = async (
363
396
  | exn => IndexerState.errorExit(state, exn->ErrorHandling.make)
364
397
  }
365
398
  },
366
- ~concurrencyLimit,
367
- ~bufferLimit,
399
+ ~action,
368
400
  ~stateId,
369
401
  )
370
402
  } catch {