envio 3.3.0-alpha.1 → 3.3.0-alpha.2

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 (58) hide show
  1. package/package.json +6 -6
  2. package/src/Batch.res +0 -6
  3. package/src/Batch.res.mjs +0 -11
  4. package/src/BatchProcessing.res +2 -12
  5. package/src/BatchProcessing.res.mjs +2 -7
  6. package/src/ChainFetching.res +17 -6
  7. package/src/ChainFetching.res.mjs +11 -11
  8. package/src/ChainMetadata.res +1 -11
  9. package/src/ChainMetadata.res.mjs +1 -10
  10. package/src/ChainState.res +150 -1
  11. package/src/ChainState.res.mjs +138 -9
  12. package/src/ChainState.resi +51 -1
  13. package/src/Config.res +3 -6
  14. package/src/Config.res.mjs +2 -3
  15. package/src/Core.res +7 -0
  16. package/src/CrossChainState.res +9 -26
  17. package/src/CrossChainState.res.mjs +7 -16
  18. package/src/Ecosystem.res +6 -1
  19. package/src/Envio.res +3 -2
  20. package/src/EventConfigBuilder.res +19 -5
  21. package/src/EventConfigBuilder.res.mjs +6 -4
  22. package/src/EventProcessing.res +26 -0
  23. package/src/EventProcessing.res.mjs +20 -0
  24. package/src/Internal.res +43 -4
  25. package/src/Internal.res.mjs +18 -0
  26. package/src/Main.res +3 -50
  27. package/src/Main.res.mjs +2 -23
  28. package/src/Rollback.res +1 -1
  29. package/src/Rollback.res.mjs +1 -1
  30. package/src/SimulateItems.res +3 -0
  31. package/src/SimulateItems.res.mjs +2 -1
  32. package/src/sources/Evm.res +52 -38
  33. package/src/sources/Evm.res.mjs +48 -36
  34. package/src/sources/Fuel.res +3 -1
  35. package/src/sources/Fuel.res.mjs +1 -0
  36. package/src/sources/HyperFuelSource.res +5 -0
  37. package/src/sources/HyperFuelSource.res.mjs +2 -0
  38. package/src/sources/HyperSync.res +4 -1
  39. package/src/sources/HyperSync.res.mjs +5 -3
  40. package/src/sources/HyperSync.resi +1 -0
  41. package/src/sources/HyperSyncClient.res +9 -78
  42. package/src/sources/HyperSyncClient.res.mjs +1 -22
  43. package/src/sources/HyperSyncSource.res +3 -4
  44. package/src/sources/HyperSyncSource.res.mjs +2 -1
  45. package/src/sources/RpcSource.res +7 -2
  46. package/src/sources/RpcSource.res.mjs +3 -1
  47. package/src/sources/SimulateSource.res +3 -1
  48. package/src/sources/SimulateSource.res.mjs +1 -0
  49. package/src/sources/Source.res +4 -0
  50. package/src/sources/Svm.res +25 -2
  51. package/src/sources/Svm.res.mjs +27 -2
  52. package/src/sources/SvmHyperSyncClient.res +3 -29
  53. package/src/sources/SvmHyperSyncSource.res +60 -84
  54. package/src/sources/SvmHyperSyncSource.res.mjs +59 -78
  55. package/src/sources/TransactionStore.res +111 -0
  56. package/src/sources/TransactionStore.res.mjs +77 -0
  57. package/src/tui/Tui.res +13 -16
  58. package/src/tui/Tui.res.mjs +12 -14
@@ -297,20 +297,22 @@ function buildEvmEventConfig(contractName, eventName, sighash, params, isWildcar
297
297
  paramsRawEventSchema: buildParamsSchema(params),
298
298
  simulateParamsSchema: buildSimulateParamsSchema(params),
299
299
  startBlock: resolvedStartBlock,
300
+ selectedTransactionFields: match$1[1],
300
301
  getEventFiltersOrThrow: match.getEventFiltersOrThrow,
301
302
  selectedBlockFields: match$1[0],
302
- selectedTransactionFields: match$1[1],
303
303
  sighash: sighash,
304
304
  topicCount: topicCount,
305
305
  paramsMetadata: params
306
306
  };
307
307
  }
308
308
 
309
- function buildSvmInstructionEventConfig(contractName, instructionName, programId, discriminator, discriminatorByteLen, includeTransaction, includeLogs, includeTokenBalances, accountFilters, isInner, isWildcard, handler, contractRegister, accountsOpt, argsOpt, definedTypesOpt, startBlock) {
309
+ function buildSvmInstructionEventConfig(contractName, instructionName, programId, discriminator, discriminatorByteLen, includeLogs, transactionFieldsOpt, accountFilters, isInner, isWildcard, handler, contractRegister, accountsOpt, argsOpt, definedTypesOpt, startBlock) {
310
+ let transactionFields = transactionFieldsOpt !== undefined ? transactionFieldsOpt : [];
310
311
  let accounts = accountsOpt !== undefined ? accountsOpt : [];
311
312
  let args = argsOpt !== undefined ? argsOpt : null;
312
313
  let definedTypes = definedTypesOpt !== undefined ? definedTypesOpt : null;
313
314
  let paramsSchema = Utils.Schema.coerceToJsonPgType(S$RescriptSchema.json(false));
315
+ let selectedTransactionFields = new Set(transactionFields);
314
316
  return {
315
317
  id: discriminator !== undefined ? discriminator : "none",
316
318
  name: instructionName,
@@ -323,12 +325,11 @@ function buildSvmInstructionEventConfig(contractName, instructionName, programId
323
325
  paramsRawEventSchema: paramsSchema,
324
326
  simulateParamsSchema: paramsSchema,
325
327
  startBlock: startBlock,
328
+ selectedTransactionFields: selectedTransactionFields,
326
329
  programId: programId,
327
330
  discriminator: discriminator,
328
331
  discriminatorByteLen: discriminatorByteLen,
329
- includeTransaction: includeTransaction,
330
332
  includeLogs: includeLogs,
331
- includeTokenBalances: includeTokenBalances,
332
333
  accountFilters: accountFilters,
333
334
  isInner: isInner,
334
335
  accounts: accounts,
@@ -392,6 +393,7 @@ function buildFuelEventConfig(contractName, eventName, kind, sighash, rawAbi, is
392
393
  paramsRawEventSchema: paramsSchema,
393
394
  simulateParamsSchema: paramsSchema,
394
395
  startBlock: startBlock,
396
+ selectedTransactionFields: new Set(),
395
397
  kind: fuelKind
396
398
  };
397
399
  }
@@ -281,6 +281,29 @@ type logPartitionInfo = {
281
281
  lastItemBlockNumber?: int,
282
282
  }
283
283
 
284
+ // Off the hot path: bulk-materialise the selected transaction fields for the
285
+ // batch's store-backed (HyperSync) items and write them onto the payloads, so
286
+ // handlers read plain objects. A batch can span chains, each with its own store
287
+ // and field mask, so group items by chain before materialising.
288
+ let materializeBatchTransactions = async (batch: Batch.t, ~chainStates: dict<ChainState.t>) => {
289
+ let itemsByChain: dict<array<Internal.item>> = Dict.make()
290
+ batch.items->Array.forEach(item => {
291
+ let chainId = item->Internal.getItemChainId->Int.toString
292
+ switch itemsByChain->Utils.Dict.dangerouslyGetNonOption(chainId) {
293
+ | Some(items) => items->Array.push(item)
294
+ | None => itemsByChain->Dict.set(chainId, [item])
295
+ }
296
+ })
297
+
298
+ let _ = await itemsByChain
299
+ ->Dict.toArray
300
+ ->Array.map(async ((chainId, items)) => {
301
+ let cs = chainStates->Dict.getUnsafe(chainId)
302
+ await cs->ChainState.materializeBatchItems(~items)
303
+ })
304
+ ->Promise.all
305
+ }
306
+
284
307
  let processEventBatch = async (
285
308
  ~batch: Batch.t,
286
309
  ~indexerState: IndexerState.t,
@@ -312,6 +335,9 @@ let processEventBatch = async (
312
335
  let timeRef = Hrtime.makeTimer()
313
336
 
314
337
  if batch.items->Utils.Array.notEmpty {
338
+ // Materialise store-backed transactions onto payloads before any handler
339
+ // (preload or execute) reads them.
340
+ await materializeBatchTransactions(batch, ~chainStates)
315
341
  await batch->preloadBatchOrThrow(~loadManager, ~persistence, ~indexerState, ~chains, ~config)
316
342
  }
317
343
 
@@ -4,6 +4,7 @@ import * as Utils from "./Utils.res.mjs";
4
4
  import * as Hrtime from "./bindings/Hrtime.res.mjs";
5
5
  import * as Logging from "./Logging.res.mjs";
6
6
  import * as Writing from "./Writing.res.mjs";
7
+ import * as Internal from "./Internal.res.mjs";
7
8
  import * as Ecosystem from "./Ecosystem.res.mjs";
8
9
  import * as ChainState from "./ChainState.res.mjs";
9
10
  import * as Prometheus from "./Prometheus.res.mjs";
@@ -180,6 +181,23 @@ function registerProcessEventBatchMetrics(logger, loadDuration, handlerDuration)
180
181
  Prometheus.ProcessingBatch.registerMetrics(loadDuration, handlerDuration);
181
182
  }
182
183
 
184
+ async function materializeBatchTransactions(batch, chainStates) {
185
+ let itemsByChain = {};
186
+ batch.items.forEach(item => {
187
+ let chainId = Internal.getItemChainId(item).toString();
188
+ let items = itemsByChain[chainId];
189
+ if (items !== undefined) {
190
+ items.push(item);
191
+ } else {
192
+ itemsByChain[chainId] = [item];
193
+ }
194
+ });
195
+ await Promise.all(Object.entries(itemsByChain).map(async param => {
196
+ let cs = chainStates[param[0]];
197
+ return await ChainState.materializeBatchItems(cs, param[1]);
198
+ }));
199
+ }
200
+
183
201
  async function processEventBatch(batch, indexerState, loadManager, persistence, config, chainStates) {
184
202
  let totalBatchSize = batch.totalBatchSize;
185
203
  let chains = computeChainsState(chainStates);
@@ -196,6 +214,7 @@ async function processEventBatch(batch, indexerState, loadManager, persistence,
196
214
  await Writing.awaitCapacity(indexerState);
197
215
  let timeRef = Hrtime.makeTimer();
198
216
  if (Utils.$$Array.notEmpty(batch.items)) {
217
+ await materializeBatchTransactions(batch, chainStates);
199
218
  await preloadBatchOrThrow(batch, loadManager, persistence, config, indexerState, chains);
200
219
  }
201
220
  let elapsedTimeAfterLoaders = Hrtime.toSecondsFloat(Hrtime.timeSince(timeRef));
@@ -240,6 +259,7 @@ export {
240
259
  preloadBatchOrThrow,
241
260
  runBatchHandlersOrThrow,
242
261
  registerProcessEventBatchMetrics,
262
+ materializeBatchTransactions,
243
263
  processEventBatch,
244
264
  }
245
265
  /* Utils Not a pure module */
package/src/Internal.res CHANGED
@@ -136,6 +136,36 @@ let allEvmTransactionFields: array<evmTransactionField> = [
136
136
  ]
137
137
  let evmTransactionFieldSchema = S.enum(allEvmTransactionFields)
138
138
 
139
+ // SVM transaction fields. Order mirrors the Rust `SvmTxField` ordinals (the bit
140
+ // position in the selection mask) and `Svm.res` `transactionFields`.
141
+ type svmTransactionField =
142
+ | @as("transactionIndex") TransactionIndex
143
+ | @as("signatures") Signatures
144
+ | @as("feePayer") FeePayer
145
+ | @as("success") Success
146
+ | @as("err") Err
147
+ | @as("fee") Fee
148
+ | @as("computeUnitsConsumed") ComputeUnitsConsumed
149
+ | @as("accountKeys") AccountKeys
150
+ | @as("recentBlockhash") RecentBlockhash
151
+ | @as("version") Version
152
+ | @as("tokenBalances") TokenBalances
153
+
154
+ let allSvmTransactionFields: array<svmTransactionField> = [
155
+ TransactionIndex,
156
+ Signatures,
157
+ FeePayer,
158
+ Success,
159
+ Err,
160
+ Fee,
161
+ ComputeUnitsConsumed,
162
+ AccountKeys,
163
+ RecentBlockhash,
164
+ Version,
165
+ TokenBalances,
166
+ ]
167
+ let svmTransactionFieldSchema = S.enum(allSvmTransactionFields)
168
+
139
169
  // Static sets of nullable field names — used by RpcSource and HyperSyncSource to wrap schemas with S.nullable
140
170
  let evmNullableBlockFields = Utils.Set.fromArray(
141
171
  (
@@ -277,7 +307,10 @@ type event
277
307
  // modules, not here, and are distinct per ecosystem.
278
308
  type eventPayload
279
309
 
280
- external payloadToEvent: eventPayload => event = "%identity"
310
+ // Generic access to the payload's `transaction`, written at batch prep for
311
+ // store-backed ecosystems (HyperSync) and present inline otherwise.
312
+ @get external getPayloadTransaction: eventPayload => Nullable.t<eventTransaction> = "transaction"
313
+ @set external setPayloadTransaction: (eventPayload, eventTransaction) => unit = "transaction"
281
314
 
282
315
  type genericLoaderArgs<'event, 'context> = {
283
316
  event: 'event,
@@ -382,6 +415,11 @@ type eventConfig = private {
382
415
  paramsRawEventSchema: S.schema<eventParams>,
383
416
  simulateParamsSchema: S.schema<eventParams>,
384
417
  startBlock: option<int>,
418
+ // Field names selected for the chain's transaction-store materialisation
419
+ // (camelCase, matching the ecosystem's `transactionFields`). Stored as a
420
+ // string set so the shared mask logic is ecosystem-agnostic; sources recover
421
+ // the typed view where they need it.
422
+ selectedTransactionFields: Utils.Set.t<string>,
385
423
  }
386
424
 
387
425
  type fuelEventKind =
@@ -419,7 +457,6 @@ type evmEventConfig = {
419
457
  ...eventConfig,
420
458
  getEventFiltersOrThrow: ChainMap.Chain.t => eventFilters,
421
459
  selectedBlockFields: Utils.Set.t<evmBlockField>,
422
- selectedTransactionFields: Utils.Set.t<evmTransactionField>,
423
460
  sighash: string,
424
461
  topicCount: int,
425
462
  paramsMetadata: array<paramMeta>,
@@ -456,9 +493,7 @@ type svmInstructionEventConfig = {
456
493
  `dN` selector at query time and the dispatch-key precomputation in the
457
494
  router. */
458
495
  discriminatorByteLen: int,
459
- includeTransaction: bool,
460
496
  includeLogs: bool,
461
- includeTokenBalances: bool,
462
497
  /** Disjunctive normal form: outer array is OR of AND-groups, inner array is
463
498
  AND across positions. Empty outer array means "no account filter". */
464
499
  accountFilters: array<svmAccountFilterGroup>,
@@ -502,6 +537,9 @@ type eventItem = private {
502
537
  blockNumber: int,
503
538
  blockHash: string,
504
539
  logIndex: int,
540
+ // Within-block transaction index — the key into the per-chain transaction
541
+ // store. Unused (0) for ecosystems that carry the transaction inline (Fuel).
542
+ transactionIndex: int,
505
543
  payload: eventPayload,
506
544
  }
507
545
 
@@ -554,6 +592,7 @@ type item =
554
592
  blockNumber: int,
555
593
  blockHash: string,
556
594
  logIndex: int,
595
+ transactionIndex: int,
557
596
  payload: eventPayload,
558
597
  })
559
598
  | @as(1) Block({onBlockConfig: onBlockConfig, blockNumber: int, logIndex: int})
@@ -74,6 +74,22 @@ let allEvmTransactionFields = [
74
74
 
75
75
  let evmTransactionFieldSchema = S$RescriptSchema.$$enum(allEvmTransactionFields);
76
76
 
77
+ let allSvmTransactionFields = [
78
+ "transactionIndex",
79
+ "signatures",
80
+ "feePayer",
81
+ "success",
82
+ "err",
83
+ "fee",
84
+ "computeUnitsConsumed",
85
+ "accountKeys",
86
+ "recentBlockhash",
87
+ "version",
88
+ "tokenBalances"
89
+ ];
90
+
91
+ let svmTransactionFieldSchema = S$RescriptSchema.$$enum(allSvmTransactionFields);
92
+
77
93
  let evmNullableBlockFields = new Set([
78
94
  "nonce",
79
95
  "difficulty",
@@ -160,6 +176,8 @@ export {
160
176
  evmBlockFieldSchema,
161
177
  allEvmTransactionFields,
162
178
  evmTransactionFieldSchema,
179
+ allSvmTransactionFields,
180
+ svmTransactionFieldSchema,
163
181
  evmNullableBlockFields,
164
182
  evmNullableTransactionFields,
165
183
  dependsOnAddresses,
package/src/Main.res CHANGED
@@ -1,19 +1,4 @@
1
- type chainData = {
2
- chainId: float,
3
- poweredByHyperSync: bool,
4
- firstEventBlockNumber: option<int>,
5
- latestProcessedBlock: option<int>,
6
- timestampCaughtUpToHeadOrEndblock: option<Date.t>,
7
- numEventsProcessed: float,
8
- latestFetchedBlockNumber: int,
9
- // Need this for API backwards compatibility
10
- @as("currentBlockHeight")
11
- knownHeight: int,
12
- numBatchesFetched: int,
13
- startBlock: int,
14
- endBlock: option<int>,
15
- numAddresses: int,
16
- }
1
+ type chainData = ChainState.chainData
17
2
  @tag("status")
18
3
  type state =
19
4
  | @as("disabled") Disabled({})
@@ -183,7 +168,7 @@ let buildChainsObject = (~config: Config.t) => {
183
168
  | Some(state) => {
184
169
  let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)
185
170
  let chainState = state->IndexerState.getChainState(~chain)
186
- let indexingAddresses = (chainState->ChainState.fetchState).indexingAddresses
171
+ let indexingAddresses = chainState->ChainState.indexingAddresses
187
172
 
188
173
  // Collect all addresses for this contract name from indexingAddresses
189
174
  let addresses = []
@@ -780,39 +765,7 @@ let start = async (
780
765
  | None => Initializing({})
781
766
  | Some(state) => {
782
767
  let chains =
783
- state
784
- ->IndexerState.chainStates
785
- ->Dict.valuesToArray
786
- ->Array.map(cs => {
787
- let fetchState = cs->ChainState.fetchState
788
- let latestFetchedBlockNumber = Pervasives.max(
789
- FetchState.bufferBlockNumber(fetchState),
790
- 0,
791
- )
792
- let knownHeight =
793
- cs->ChainState.hasProcessedToEndblock
794
- ? fetchState.endBlock->Option.getOr(fetchState.knownHeight)
795
- : fetchState.knownHeight
796
-
797
- {
798
- chainId: (cs->ChainState.chainConfig).id->Int.toFloat,
799
- poweredByHyperSync: (
800
- cs->ChainState.sourceManager->SourceManager.getActiveSource
801
- ).poweredByHyperSync,
802
- latestFetchedBlockNumber,
803
- knownHeight,
804
- numBatchesFetched: 0,
805
- startBlock: fetchState.startBlock,
806
- endBlock: fetchState.endBlock,
807
- firstEventBlockNumber: fetchState.firstEventBlock,
808
- latestProcessedBlock: cs->ChainState.committedProgressBlockNumber === -1
809
- ? None
810
- : Some(cs->ChainState.committedProgressBlockNumber),
811
- timestampCaughtUpToHeadOrEndblock: cs->ChainState.timestampCaughtUpToHeadOrEndblock,
812
- numEventsProcessed: cs->ChainState.numEventsProcessed,
813
- numAddresses: fetchState->FetchState.numAddresses,
814
- }
815
- })
768
+ state->IndexerState.chainStates->Dict.valuesToArray->Array.map(ChainState.toChainData)
816
769
  Active({
817
770
  envioVersion,
818
771
  chains,
package/src/Main.res.mjs CHANGED
@@ -11,7 +11,6 @@ import * as Process from "process";
11
11
  import * as ChainMap from "./ChainMap.res.mjs";
12
12
  import * as PgStorage from "./PgStorage.res.mjs";
13
13
  import * as ChainState from "./ChainState.res.mjs";
14
- import * as FetchState from "./FetchState.res.mjs";
15
14
  import * as Prometheus from "./Prometheus.res.mjs";
16
15
  import * as IndexerLoop from "./IndexerLoop.res.mjs";
17
16
  import * as Persistence from "./Persistence.res.mjs";
@@ -20,8 +19,6 @@ import Yargs from "yargs/yargs";
20
19
  import * as IndexerState from "./IndexerState.res.mjs";
21
20
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
22
21
  import * as HandlerLoader from "./HandlerLoader.res.mjs";
23
- import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
24
- import * as SourceManager from "./sources/SourceManager.res.mjs";
25
22
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
26
23
  import * as Helpers from "yargs/helpers";
27
24
  import * as RollbackCommit from "./RollbackCommit.res.mjs";
@@ -168,7 +165,7 @@ function buildChainsObject(config) {
168
165
  if (state !== undefined) {
169
166
  let chain = ChainMap.Chain.makeUnsafe(chainConfig.id);
170
167
  let chainState = IndexerState.getChainState(Primitive_option.valFromOption(state), chain);
171
- let indexingAddresses = ChainState.fetchState(chainState).indexingAddresses;
168
+ let indexingAddresses = ChainState.indexingAddresses(chainState);
172
169
  let addresses = [];
173
170
  let values = Object.values(indexingAddresses);
174
171
  for (let idx = 0, idx_finish = values.length; idx < idx_finish; ++idx) {
@@ -586,25 +583,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
586
583
  };
587
584
  }
588
585
  let state$1 = Primitive_option.valFromOption(state);
589
- let chains = Object.values(IndexerState.chainStates(state$1)).map(cs => {
590
- let fetchState = ChainState.fetchState(cs);
591
- let latestFetchedBlockNumber = Primitive_int.max(FetchState.bufferBlockNumber(fetchState), 0);
592
- let knownHeight = ChainState.hasProcessedToEndblock(cs) ? Stdlib_Option.getOr(fetchState.endBlock, fetchState.knownHeight) : fetchState.knownHeight;
593
- return {
594
- chainId: ChainState.chainConfig(cs).id,
595
- poweredByHyperSync: SourceManager.getActiveSource(ChainState.sourceManager(cs)).poweredByHyperSync,
596
- firstEventBlockNumber: fetchState.firstEventBlock,
597
- latestProcessedBlock: ChainState.committedProgressBlockNumber(cs) === -1 ? undefined : ChainState.committedProgressBlockNumber(cs),
598
- timestampCaughtUpToHeadOrEndblock: ChainState.timestampCaughtUpToHeadOrEndblock(cs),
599
- numEventsProcessed: ChainState.numEventsProcessed(cs),
600
- latestFetchedBlockNumber: latestFetchedBlockNumber,
601
- currentBlockHeight: knownHeight,
602
- numBatchesFetched: 0,
603
- startBlock: fetchState.startBlock,
604
- endBlock: fetchState.endBlock,
605
- numAddresses: FetchState.numAddresses(fetchState)
606
- };
607
- });
586
+ let chains = Object.values(IndexerState.chainStates(state$1)).map(ChainState.toChainData);
608
587
  return {
609
588
  status: "active",
610
589
  envioVersion: envioVersion,
package/src/Rollback.res CHANGED
@@ -18,7 +18,7 @@ let getLastKnownValidBlock = async (
18
18
  ->ChainState.reorgDetection
19
19
  ->ReorgDetection.getThresholdBlockNumbersBelowBlock(
20
20
  ~blockNumber=reorgBlockNumber,
21
- ~knownHeight=(chainState->ChainState.fetchState).knownHeight,
21
+ ~knownHeight=chainState->ChainState.knownHeight,
22
22
  )
23
23
 
24
24
  switch scannedBlockNumbers {
@@ -18,7 +18,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
18
18
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
19
19
 
20
20
  async function getLastKnownValidBlock(chainState, reorgBlockNumber, isRealtime) {
21
- let scannedBlockNumbers = ReorgDetection.getThresholdBlockNumbersBelowBlock(ChainState.reorgDetection(chainState), reorgBlockNumber, ChainState.fetchState(chainState).knownHeight);
21
+ let scannedBlockNumbers = ReorgDetection.getThresholdBlockNumbersBelowBlock(ChainState.reorgDetection(chainState), reorgBlockNumber, ChainState.knownHeight(chainState));
22
22
  if (scannedBlockNumbers.length === 0) {
23
23
  return ChainState.getHighestBlockBelowThreshold(chainState);
24
24
  }
@@ -288,6 +288,9 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
288
288
  blockNumber,
289
289
  blockHash,
290
290
  logIndex,
291
+ // Simulate keeps the transaction inline on the payload, so the store
292
+ // key is unused.
293
+ transactionIndex: 0,
291
294
  payload: (
292
295
  {
293
296
  contractName: eventConfig.contractName,
@@ -241,6 +241,7 @@ function parse(simulateItems, config, chainConfig) {
241
241
  blockNumber: blockNumber,
242
242
  blockHash: match$3[3],
243
243
  logIndex: logIndex,
244
+ transactionIndex: 0,
244
245
  payload: {
245
246
  contractName: eventConfig.contractName,
246
247
  eventName: eventConfig.name,
@@ -248,7 +249,7 @@ function parse(simulateItems, config, chainConfig) {
248
249
  chainId: chainId,
249
250
  srcAddress: srcAddress,
250
251
  logIndex: logIndex,
251
- transaction: transaction,
252
+ transaction: Primitive_option.some(transaction),
252
253
  block: match$3[0]
253
254
  }
254
255
  });
@@ -1,5 +1,7 @@
1
1
  // EVM's concrete item payload. Erased to `Internal.eventPayload` on the item
2
- // and recovered here via `toPayload`.
2
+ // and recovered here via `toPayload`. HyperSync omits `transaction` (it lives
3
+ // raw in the per-chain store and is written onto the payload at batch prep);
4
+ // RPC/simulate build it inline.
3
5
  type payload = {
4
6
  contractName: string,
5
7
  eventName: string,
@@ -7,12 +9,55 @@ type payload = {
7
9
  chainId: int,
8
10
  srcAddress: Address.t,
9
11
  logIndex: int,
10
- transaction: Internal.eventTransaction,
12
+ transaction?: Internal.eventTransaction,
11
13
  block: Internal.eventBlock,
12
14
  }
13
15
  external fromPayload: payload => Internal.eventPayload = "%identity"
14
16
  external toPayload: Internal.eventPayload => payload = "%identity"
15
17
 
18
+ // Ordered transaction field names. The index of each is the field code shared
19
+ // with the Rust store (`EvmTxField`) — keep this order in sync.
20
+ let transactionFields = [
21
+ "transactionIndex",
22
+ "hash",
23
+ "from",
24
+ "to",
25
+ "gas",
26
+ "gasPrice",
27
+ "maxPriorityFeePerGas",
28
+ "maxFeePerGas",
29
+ "cumulativeGasUsed",
30
+ "effectiveGasPrice",
31
+ "gasUsed",
32
+ "input",
33
+ "nonce",
34
+ "value",
35
+ "v",
36
+ "r",
37
+ "s",
38
+ "contractAddress",
39
+ "logsBloom",
40
+ "root",
41
+ "status",
42
+ "yParity",
43
+ "maxFeePerBlobGas",
44
+ "blobVersionedHashes",
45
+ "type",
46
+ "l1Fee",
47
+ "l1GasPrice",
48
+ "l1GasUsed",
49
+ "l1FeeScalar",
50
+ "gasUsedForL1",
51
+ "accessList",
52
+ "authorizationList",
53
+ ]
54
+
55
+ // Field name → bit index (the code shared with the Rust store), built once.
56
+ let transactionFieldCodes = TransactionStore.fieldCodes(transactionFields)
57
+
58
+ let transactionFieldMask = (eventConfigs: array<Internal.eventConfig>): float =>
59
+ eventConfigs->TransactionStore.mask(~codes=transactionFieldCodes)
60
+
16
61
  let cleanUpRawEventFieldsInPlace: JSON.t => unit = %raw(`fields => {
17
62
  delete fields.hash
18
63
  delete fields.number
@@ -50,41 +95,7 @@ let make = (~logger: Pino.t): Ecosystem.t => {
50
95
  "sendRoot",
51
96
  "mixHash",
52
97
  ],
53
- transactionFields: [
54
- "transactionIndex",
55
- "hash",
56
- "from",
57
- "to",
58
- "gas",
59
- "gasPrice",
60
- "maxPriorityFeePerGas",
61
- "maxFeePerGas",
62
- "cumulativeGasUsed",
63
- "effectiveGasPrice",
64
- "gasUsed",
65
- "input",
66
- "nonce",
67
- "value",
68
- "v",
69
- "r",
70
- "s",
71
- "contractAddress",
72
- "logsBloom",
73
- "root",
74
- "status",
75
- "yParity",
76
- "chainId",
77
- "maxFeePerBlobGas",
78
- "blobVersionedHashes",
79
- "type",
80
- "l1Fee",
81
- "l1GasPrice",
82
- "l1GasUsed",
83
- "l1FeeScalar",
84
- "gasUsedForL1",
85
- "accessList",
86
- "authorizationList",
87
- ],
98
+ transactionFields,
88
99
  blockNumberName: "number",
89
100
  blockTimestampName: "timestamp",
90
101
  blockHashName: "hash",
@@ -106,7 +117,10 @@ let make = (~logger: Pino.t): Ecosystem.t => {
106
117
  s.field("block", S.option(S.object(s2 => s2.field("number", S.unknown))))
107
118
  ),
108
119
  logger,
109
- toEvent: eventItem => eventItem.payload->Internal.payloadToEvent,
120
+ transactionFieldMask,
121
+ // The payload carries `transaction` by batch prep (HyperSync) or inline
122
+ // (RPC/simulate), so the event is the payload as-is.
123
+ toEvent: eventItem => eventItem.payload->(Utils.magic: Internal.eventPayload => Internal.event),
110
124
  toEventLogger: eventItem =>
111
125
  Logging.createChildFrom(
112
126
  ~logger,
@@ -3,6 +3,48 @@
3
3
  import * as Logging from "../Logging.res.mjs";
4
4
  import * as RawEvent from "../RawEvent.res.mjs";
5
5
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
6
+ import * as TransactionStore from "./TransactionStore.res.mjs";
7
+
8
+ let transactionFields = [
9
+ "transactionIndex",
10
+ "hash",
11
+ "from",
12
+ "to",
13
+ "gas",
14
+ "gasPrice",
15
+ "maxPriorityFeePerGas",
16
+ "maxFeePerGas",
17
+ "cumulativeGasUsed",
18
+ "effectiveGasPrice",
19
+ "gasUsed",
20
+ "input",
21
+ "nonce",
22
+ "value",
23
+ "v",
24
+ "r",
25
+ "s",
26
+ "contractAddress",
27
+ "logsBloom",
28
+ "root",
29
+ "status",
30
+ "yParity",
31
+ "maxFeePerBlobGas",
32
+ "blobVersionedHashes",
33
+ "type",
34
+ "l1Fee",
35
+ "l1GasPrice",
36
+ "l1GasUsed",
37
+ "l1FeeScalar",
38
+ "gasUsedForL1",
39
+ "accessList",
40
+ "authorizationList"
41
+ ];
42
+
43
+ let transactionFieldCodes = TransactionStore.fieldCodes(transactionFields);
44
+
45
+ function transactionFieldMask(eventConfigs) {
46
+ return TransactionStore.mask(eventConfigs, transactionFieldCodes);
47
+ }
6
48
 
7
49
  let cleanUpRawEventFieldsInPlace = (fields => {
8
50
  delete fields.hash
@@ -42,41 +84,7 @@ function make(logger) {
42
84
  "sendRoot",
43
85
  "mixHash"
44
86
  ],
45
- transactionFields: [
46
- "transactionIndex",
47
- "hash",
48
- "from",
49
- "to",
50
- "gas",
51
- "gasPrice",
52
- "maxPriorityFeePerGas",
53
- "maxFeePerGas",
54
- "cumulativeGasUsed",
55
- "effectiveGasPrice",
56
- "gasUsed",
57
- "input",
58
- "nonce",
59
- "value",
60
- "v",
61
- "r",
62
- "s",
63
- "contractAddress",
64
- "logsBloom",
65
- "root",
66
- "status",
67
- "yParity",
68
- "chainId",
69
- "maxFeePerBlobGas",
70
- "blobVersionedHashes",
71
- "type",
72
- "l1Fee",
73
- "l1GasPrice",
74
- "l1GasUsed",
75
- "l1FeeScalar",
76
- "gasUsedForL1",
77
- "accessList",
78
- "authorizationList"
79
- ],
87
+ transactionFields: transactionFields,
80
88
  blockNumberName: "number",
81
89
  blockTimestampName: "timestamp",
82
90
  blockHashName: "hash",
@@ -86,6 +94,7 @@ function make(logger) {
86
94
  onEventBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("number", S$RescriptSchema.unknown))))),
87
95
  logger: logger,
88
96
  toEvent: eventItem => eventItem.payload,
97
+ transactionFieldMask: transactionFieldMask,
89
98
  toEventLogger: eventItem => Logging.createChildFrom(logger, {
90
99
  contract: eventItem.eventConfig.contractName,
91
100
  event: eventItem.eventConfig.name,
@@ -102,7 +111,10 @@ function make(logger) {
102
111
  }
103
112
 
104
113
  export {
114
+ transactionFields,
115
+ transactionFieldCodes,
116
+ transactionFieldMask,
105
117
  cleanUpRawEventFieldsInPlace,
106
118
  make,
107
119
  }
108
- /* Logging Not a pure module */
120
+ /* transactionFieldCodes Not a pure module */
@@ -41,7 +41,9 @@ let make = (~logger: Pino.t): Ecosystem.t => {
41
41
  s.field("block", S.option(S.object(s2 => s2.field("height", S.unknown))))
42
42
  ),
43
43
  logger,
44
- toEvent: eventItem => eventItem.payload->Internal.payloadToEvent,
44
+ // Fuel carries the transaction inline on the payload.
45
+ transactionFieldMask: _ => 0.,
46
+ toEvent: eventItem => eventItem.payload->(Utils.magic: Internal.eventPayload => Internal.event),
45
47
  toEventLogger: eventItem =>
46
48
  Logging.createChildFrom(
47
49
  ~logger,