envio 3.3.0-alpha.3 → 3.3.0-alpha.5

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 (53) hide show
  1. package/evm.schema.json +10 -0
  2. package/package.json +7 -7
  3. package/src/Batch.res.mjs +1 -1
  4. package/src/Bin.res +3 -0
  5. package/src/Bin.res.mjs +4 -0
  6. package/src/ChainFetching.res +1 -3
  7. package/src/ChainFetching.res.mjs +3 -3
  8. package/src/ChainState.res +44 -19
  9. package/src/ChainState.res.mjs +25 -23
  10. package/src/ChainState.resi +3 -3
  11. package/src/Config.res +3 -0
  12. package/src/Config.res.mjs +3 -1
  13. package/src/Core.res +0 -3
  14. package/src/Ecosystem.res +0 -4
  15. package/src/EventConfigBuilder.res +3 -0
  16. package/src/EventConfigBuilder.res.mjs +7 -1
  17. package/src/FetchState.res +73 -160
  18. package/src/FetchState.res.mjs +120 -222
  19. package/src/IndexingAddresses.res +105 -0
  20. package/src/IndexingAddresses.res.mjs +100 -0
  21. package/src/IndexingAddresses.resi +32 -0
  22. package/src/Internal.res +5 -0
  23. package/src/Main.res +21 -24
  24. package/src/Main.res.mjs +21 -20
  25. package/src/Metrics.res +33 -0
  26. package/src/Metrics.res.mjs +39 -0
  27. package/src/Prometheus.res +2 -20
  28. package/src/Prometheus.res.mjs +83 -95
  29. package/src/SimulateItems.res +4 -0
  30. package/src/TestIndexer.res +43 -17
  31. package/src/TestIndexer.res.mjs +16 -9
  32. package/src/bindings/Viem.res +0 -41
  33. package/src/bindings/Viem.res.mjs +1 -43
  34. package/src/sources/Evm.res +17 -39
  35. package/src/sources/Evm.res.mjs +8 -40
  36. package/src/sources/EvmChain.res +3 -1
  37. package/src/sources/EvmChain.res.mjs +2 -1
  38. package/src/sources/EvmRpcClient.res +36 -5
  39. package/src/sources/EvmRpcClient.res.mjs +7 -4
  40. package/src/sources/Fuel.res +0 -2
  41. package/src/sources/Fuel.res.mjs +0 -1
  42. package/src/sources/HyperSyncClient.res +0 -35
  43. package/src/sources/HyperSyncClient.res.mjs +1 -8
  44. package/src/sources/Rpc.res +15 -47
  45. package/src/sources/Rpc.res.mjs +25 -56
  46. package/src/sources/RpcSource.res +30 -73
  47. package/src/sources/RpcSource.res.mjs +24 -73
  48. package/src/sources/SourceManager.res +1 -1
  49. package/src/sources/SourceManager.res.mjs +1 -1
  50. package/src/sources/Svm.res +10 -17
  51. package/src/sources/Svm.res.mjs +6 -18
  52. package/src/sources/TransactionStore.res +98 -76
  53. package/src/sources/TransactionStore.res.mjs +49 -35
@@ -6,8 +6,20 @@
6
6
  // in columnar form and zipped into plain JS objects on the main thread.
7
7
  type t
8
8
 
9
- @send external classNew: Core.transactionStoreCtor => t = "new"
10
- let make = (): t => Core.getAddon().transactionStore->classNew
9
+ @send external newEvm: (Core.transactionStoreCtor, ~shouldChecksum: bool) => t = "newEvm"
10
+ @send external newSvm: Core.transactionStoreCtor => t = "newSvm"
11
+ @send external newFuel: Core.transactionStoreCtor => t = "newFuel"
12
+
13
+ // The store's ecosystem is fixed here, from the chain's config. EVM carries the
14
+ // chain's address-checksumming setting; SVM/Fuel need no extra data.
15
+ let make = (~ecosystem: Ecosystem.name, ~shouldChecksum: bool): t => {
16
+ let ctor = Core.getAddon().transactionStore
17
+ switch ecosystem {
18
+ | Evm => ctor->newEvm(~shouldChecksum)
19
+ | Svm => ctor->newSvm
20
+ | Fuel => ctor->newFuel
21
+ }
22
+ }
11
23
 
12
24
  // Field-name → bit-index map from an ordered field-name array. The index is the
13
25
  // field code shared with the Rust store (`EvmTxField`/`SvmTxField`).
@@ -19,39 +31,45 @@ let fieldCodes = (fields: array<string>): dict<int> => {
19
31
 
20
32
  let pow2: int => float = %raw(`c => Math.pow(2, c)`)
21
33
 
22
- // Union of an ecosystem's selected transaction fields as a bitmask float (bit
23
- // `code` set ⇔ selected). Built arithmetically to dodge 32-bit JS bitwise ops.
24
- let mask = (eventConfigs: array<Internal.eventConfig>, ~codes: dict<int>): float => {
25
- let selected = Utils.Set.make()
26
- eventConfigs->Array.forEach(eventConfig =>
27
- eventConfig.selectedTransactionFields->Utils.Set.forEach(name =>
28
- switch codes->Utils.Dict.dangerouslyGetNonOption(name) {
29
- | Some(code) => selected->Utils.Set.add(code)->ignore
30
- | None => ()
31
- }
32
- )
34
+ // One event's selected transaction fields as a bitmask float (bit `code` set ⇔
35
+ // selected). Each field appears once, so summing `pow2(code)` sets distinct bits
36
+ // with no overlap; the result stays exact in f64 (codes span 0..31, so a mask is
37
+ // at most 2^32-1).
38
+ let maskFromFields = (selectedTransactionFields: Utils.Set.t<string>, ~codes: dict<int>): float => {
39
+ let mask = ref(0.)
40
+ selectedTransactionFields->Utils.Set.forEach(name =>
41
+ switch codes->Utils.Dict.dangerouslyGetNonOption(name) {
42
+ | Some(code) => mask := mask.contents +. pow2(code)
43
+ | None => ()
44
+ }
33
45
  )
34
- selected->Utils.Set.toArray->Array.reduce(0., (mask, code) => mask +. pow2(code))
46
+ mask.contents
35
47
  }
36
48
 
37
- // Build an ecosystem's mask function from its ordered field-name array. The
38
- // field codes are derived once and closed over.
39
- let makeMaskFn = (fields: array<string>): (array<Internal.eventConfig> => float) => {
49
+ // Build an ecosystem's per-event mask function from its ordered field-name
50
+ // array. The field codes are derived once and closed over.
51
+ let makeMaskFn = (fields: array<string>): (Utils.Set.t<string> => float) => {
40
52
  let codes = fieldCodes(fields)
41
- eventConfigs => eventConfigs->mask(~codes)
53
+ selectedTransactionFields => selectedTransactionFields->maskFromFields(~codes)
42
54
  }
43
55
 
56
+ // Bitwise OR of two per-event masks. Masks fit in 32 bits (≤32 transaction
57
+ // fields), so `>>> 0` recovers the unsigned value that a plain `|` renders
58
+ // negative once bit 31 is set.
59
+ let orMask: (float, float) => float = %raw(`(a, b) => (a | b) >>> 0`)
60
+
44
61
  // Drain another store (a fetch-response page) into this one.
45
62
  @send external merge: (t, t) => unit = "merge"
46
63
 
47
- // Bulk-materialise the fields selected by `mask` (one bit per field code) for
48
- // the given transactions, off the JS thread. Result is aligned with the input.
64
+ // Bulk-materialise transactions off the JS thread, one row per
65
+ // (blockNumbers[i], transactionIndices[i]) key, decoding only the fields set in
66
+ // that row's own masks[i]. Result is aligned with the input.
49
67
  @send
50
68
  external materialize: (
51
69
  t,
52
70
  ~blockNumbers: array<int>,
53
71
  ~transactionIndices: array<int>,
54
- ~mask: float,
72
+ ~masks: array<float>,
55
73
  ) => promise<array<Internal.eventTransaction>> = "materialize"
56
74
 
57
75
  // Drop transactions for blocks at or below the given block (already processed).
@@ -60,69 +78,73 @@ external materialize: (
60
78
  // Drop transactions for blocks above the given block (rolled back).
61
79
  @send external rollback: (t, int) => unit = "rollback"
62
80
 
63
- // Materialise the mask-selected fields for the store-backed items and write the
64
- // resulting transaction onto each item's payload. Items that already carry an
65
- // inline transaction (RPC/simulate/Fuel) are skipped. Store-backed items always
66
- // get a transaction objectthe selected fields, or `{}` when the chain
67
- // selected none so `event.transaction` is never `undefined` (matching the
68
- // inline sources). Deduped per (blockNumber, transactionIndex).
69
- let materializeItems = async (store: t, ~items: array<Internal.item>, ~mask: float) => {
70
- if mask == 0. {
71
- // No fields selected: there's nothing to decode and no reason to group by
72
- // key, but each store-backed item still gets an empty transaction object so
73
- // `event.transaction` is never undefined (matching the inline sources).
74
- items->Array.forEach(item =>
75
- switch item {
76
- | Internal.Event(_) =>
77
- let payload = (item->Internal.castUnsafeEventItem).payload
78
- switch payload->Internal.getPayloadTransaction->Nullable.toOption {
79
- | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
80
- | None => payload->Internal.setPayloadTransaction(%raw(`{}`))
81
- }
82
- | Internal.Block(_) => ()
83
- }
84
- )
85
- } else {
86
- // Store-backed items arrive in (block, logIndex) order, and a transaction's
87
- // logs are contiguous within a block, so events sharing a (blockNumber,
88
- // transactionIndex) are adjacent. Group them by extending the current run
89
- // rather than hashing a string key per item. A key recurring non-adjacently
90
- // just splits into two groups (one redundant decode) — never incorrect.
91
- let blockNumbers = []
92
- let transactionIndices = []
93
- let payloadGroups = []
81
+ // Materialise each store-backed item's selected transaction fields and write the
82
+ // resulting transaction onto its payload. Every event's mask comes from its own
83
+ // `eventConfig.transactionFieldMask`, so a transaction decodes only the fields
84
+ // the events on it selected a large field (e.g. `input`) never materialises
85
+ // for events that didn't ask for it. Items that already carry an inline
86
+ // transaction (RPC/simulate/Fuel) are skipped. Store-backed items always get a
87
+ // transaction object the selected fields, or `{}` when nothing was selected —
88
+ // so `event.transaction` is never `undefined` (matching the inline sources).
89
+ // Deduped per (blockNumber, transactionIndex); each row's mask is the OR of the
90
+ // masks of the events sharing that transaction.
91
+ let materializeItems = async (store: t, ~items: array<Internal.item>) => {
92
+ // Store-backed items arrive in (block, logIndex) order, and a transaction's
93
+ // logs are contiguous within a block, so events sharing a (blockNumber,
94
+ // transactionIndex) are adjacent regardless of event. Group them by extending
95
+ // the current run rather than hashing a string key per item. A key recurring
96
+ // non-adjacently just splits into two groups (one redundant decode) — never
97
+ // incorrect.
98
+ let blockNumbers = []
99
+ let transactionIndices = []
100
+ let masks = []
101
+ let payloadGroups = []
102
+ let anySelected = ref(false)
94
103
 
95
- items->Array.forEach(item =>
96
- switch item {
97
- | Internal.Event(_) =>
98
- let eventItem = item->Internal.castUnsafeEventItem
99
- switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
100
- | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
101
- | None =>
102
- let {blockNumber, transactionIndex} = eventItem
103
- let last = payloadGroups->Array.length - 1
104
- if (
105
- last >= 0 &&
106
- blockNumbers->Array.getUnsafe(last) == blockNumber &&
107
- transactionIndices->Array.getUnsafe(last) == transactionIndex
108
- ) {
109
- payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
110
- } else {
111
- blockNumbers->Array.push(blockNumber)
112
- transactionIndices->Array.push(transactionIndex)
113
- payloadGroups->Array.push([eventItem.payload])
114
- }
104
+ items->Array.forEach(item =>
105
+ switch item {
106
+ | Internal.Event(_) =>
107
+ let eventItem = item->Internal.castUnsafeEventItem
108
+ switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
109
+ | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
110
+ | None =>
111
+ let {blockNumber, transactionIndex} = eventItem
112
+ let mask = eventItem.eventConfig.transactionFieldMask
113
+ if mask != 0. {
114
+ anySelected := true
115
+ }
116
+ let last = payloadGroups->Array.length - 1
117
+ if (
118
+ last >= 0 &&
119
+ blockNumbers->Array.getUnsafe(last) == blockNumber &&
120
+ transactionIndices->Array.getUnsafe(last) == transactionIndex
121
+ ) {
122
+ payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
123
+ masks->Array.setUnsafe(last, orMask(masks->Array.getUnsafe(last), mask))
124
+ } else {
125
+ blockNumbers->Array.push(blockNumber)
126
+ transactionIndices->Array.push(transactionIndex)
127
+ masks->Array.push(mask)
128
+ payloadGroups->Array.push([eventItem.payload])
115
129
  }
116
- | Internal.Block(_) => ()
117
130
  }
118
- )
131
+ | Internal.Block(_) => ()
132
+ }
133
+ )
119
134
 
120
- if payloadGroups->Utils.Array.notEmpty {
121
- let txs = await store->materialize(~blockNumbers, ~transactionIndices, ~mask)
135
+ if payloadGroups->Utils.Array.notEmpty {
136
+ if anySelected.contents {
137
+ let txs = await store->materialize(~blockNumbers, ~transactionIndices, ~masks)
122
138
  payloadGroups->Array.forEachWithIndex((payloads, i) => {
123
139
  let tx = txs->Array.getUnsafe(i)
124
140
  payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
125
141
  })
142
+ } else {
143
+ // No event selected any field: stamp an empty transaction object so
144
+ // `event.transaction` is never undefined, without a materialize call.
145
+ payloadGroups->Array.forEach(payloads =>
146
+ payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(%raw(`{}`)))
147
+ )
126
148
  }
127
149
  }
128
150
  }
@@ -2,10 +2,17 @@
2
2
 
3
3
  import * as Core from "../Core.res.mjs";
4
4
  import * as Utils from "../Utils.res.mjs";
5
- import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
5
 
7
- function make() {
8
- return Core.getAddon().TransactionStore.new();
6
+ function make(ecosystem, shouldChecksum) {
7
+ let ctor = Core.getAddon().TransactionStore;
8
+ switch (ecosystem) {
9
+ case "evm" :
10
+ return ctor.newEvm(shouldChecksum);
11
+ case "fuel" :
12
+ return ctor.newFuel();
13
+ case "svm" :
14
+ return ctor.newSvm();
15
+ }
9
16
  }
10
17
 
11
18
  function fieldCodes(fields) {
@@ -18,43 +25,35 @@ function fieldCodes(fields) {
18
25
 
19
26
  let pow2 = (c => Math.pow(2, c));
20
27
 
21
- function mask(eventConfigs, codes) {
22
- let selected = new Set();
23
- eventConfigs.forEach(eventConfig => {
24
- eventConfig.selectedTransactionFields.forEach(name => {
25
- let code = codes[name];
26
- if (code !== undefined) {
27
- selected.add(code);
28
- return;
29
- }
30
- });
28
+ function maskFromFields(selectedTransactionFields, codes) {
29
+ let mask = {
30
+ contents: 0
31
+ };
32
+ selectedTransactionFields.forEach(name => {
33
+ let code = codes[name];
34
+ if (code !== undefined) {
35
+ mask.contents = mask.contents + pow2(code);
36
+ return;
37
+ }
31
38
  });
32
- return Stdlib_Array.reduce(Array.from(selected), 0, (mask, code) => mask + pow2(code));
39
+ return mask.contents;
33
40
  }
34
41
 
35
42
  function makeMaskFn(fields) {
36
43
  let codes = fieldCodes(fields);
37
- return eventConfigs => mask(eventConfigs, codes);
44
+ return selectedTransactionFields => maskFromFields(selectedTransactionFields, codes);
38
45
  }
39
46
 
40
- async function materializeItems(store, items, mask) {
41
- if (mask === 0) {
42
- items.forEach(item => {
43
- if (item.kind !== 0) {
44
- return;
45
- }
46
- let payload = item.payload;
47
- let match = payload.transaction;
48
- if (match == null) {
49
- payload.transaction = {};
50
- return;
51
- }
52
- });
53
- return;
54
- }
47
+ let orMask = ((a, b) => (a | b) >>> 0);
48
+
49
+ async function materializeItems(store, items) {
55
50
  let blockNumbers = [];
56
51
  let transactionIndices = [];
52
+ let masks = [];
57
53
  let payloadGroups = [];
54
+ let anySelected = {
55
+ contents: false
56
+ };
58
57
  items.forEach(item => {
59
58
  if (item.kind !== 0) {
60
59
  return;
@@ -65,23 +64,37 @@ async function materializeItems(store, items, mask) {
65
64
  }
66
65
  let transactionIndex = item.transactionIndex;
67
66
  let blockNumber = item.blockNumber;
67
+ let mask = item.eventConfig.transactionFieldMask;
68
+ if (mask !== 0) {
69
+ anySelected.contents = true;
70
+ }
68
71
  let last = payloadGroups.length - 1 | 0;
69
72
  if (last >= 0 && blockNumbers[last] === blockNumber && transactionIndices[last] === transactionIndex) {
70
73
  payloadGroups[last].push(item.payload);
74
+ masks[last] = orMask(masks[last], mask);
71
75
  } else {
72
76
  blockNumbers.push(blockNumber);
73
77
  transactionIndices.push(transactionIndex);
78
+ masks.push(mask);
74
79
  payloadGroups.push([item.payload]);
75
80
  }
76
81
  });
77
82
  if (!Utils.$$Array.notEmpty(payloadGroups)) {
78
83
  return;
79
84
  }
80
- let txs = await store.materialize(blockNumbers, transactionIndices, mask);
81
- payloadGroups.forEach((payloads, i) => {
82
- let tx = txs[i];
85
+ if (anySelected.contents) {
86
+ let txs = await store.materialize(blockNumbers, transactionIndices, masks);
87
+ payloadGroups.forEach((payloads, i) => {
88
+ let tx = txs[i];
89
+ payloads.forEach(payload => {
90
+ payload.transaction = tx;
91
+ });
92
+ });
93
+ return;
94
+ }
95
+ payloadGroups.forEach(payloads => {
83
96
  payloads.forEach(payload => {
84
- payload.transaction = tx;
97
+ payload.transaction = {};
85
98
  });
86
99
  });
87
100
  }
@@ -90,8 +103,9 @@ export {
90
103
  make,
91
104
  fieldCodes,
92
105
  pow2,
93
- mask,
106
+ maskFromFields,
94
107
  makeMaskFn,
108
+ orMask,
95
109
  materializeItems,
96
110
  }
97
111
  /* Core Not a pure module */