envio 3.3.0-alpha.10 → 3.3.0-alpha.12
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.
- package/licenses/CLA.md +35 -0
- package/licenses/EULA.md +67 -0
- package/licenses/LICENSE.md +45 -0
- package/licenses/README.md +35 -0
- package/package.json +9 -8
- package/src/Batch.res.mjs +1 -1
- package/src/ChainFetching.res +30 -18
- package/src/ChainFetching.res.mjs +23 -13
- package/src/ChainState.res +45 -30
- package/src/ChainState.res.mjs +22 -8
- package/src/ChainState.resi +5 -0
- package/src/Config.res +9 -5
- package/src/Config.res.mjs +2 -2
- package/src/Core.res +20 -0
- package/src/Core.res.mjs +12 -0
- package/src/CrossChainState.res +64 -29
- package/src/CrossChainState.res.mjs +17 -8
- package/src/EventConfigBuilder.res +21 -46
- package/src/EventConfigBuilder.res.mjs +18 -25
- package/src/EventProcessing.res +8 -5
- package/src/EventProcessing.res.mjs +2 -1
- package/src/FetchState.res +428 -322
- package/src/FetchState.res.mjs +316 -233
- package/src/HandlerRegister.res +3 -1
- package/src/HandlerRegister.res.mjs +3 -2
- package/src/Internal.res +11 -2
- package/src/LogSelection.res +0 -62
- package/src/LogSelection.res.mjs +0 -74
- package/src/RawEvent.res +2 -2
- package/src/Rollback.res +32 -13
- package/src/Rollback.res.mjs +24 -11
- package/src/SimulateDeadInputTracker.res +1 -1
- package/src/SimulateItems.res +38 -6
- package/src/SimulateItems.res.mjs +28 -9
- package/src/TestIndexer.res +2 -2
- package/src/TestIndexer.res.mjs +2 -2
- package/src/TopicFilter.res +1 -25
- package/src/TopicFilter.res.mjs +0 -74
- package/src/bindings/Viem.res +0 -5
- package/src/sources/EventRouter.res +0 -21
- package/src/sources/EventRouter.res.mjs +0 -12
- package/src/sources/EvmChain.res +2 -27
- package/src/sources/EvmChain.res.mjs +2 -23
- package/src/sources/EvmRpcClient.res +12 -15
- package/src/sources/EvmRpcClient.res.mjs +3 -3
- package/src/sources/HyperSync.res +9 -38
- package/src/sources/HyperSync.res.mjs +16 -28
- package/src/sources/HyperSync.resi +2 -2
- package/src/sources/HyperSyncClient.res +88 -11
- package/src/sources/HyperSyncClient.res.mjs +39 -6
- package/src/sources/HyperSyncSource.res +18 -199
- package/src/sources/HyperSyncSource.res.mjs +9 -128
- package/src/sources/Rpc.res +0 -32
- package/src/sources/Rpc.res.mjs +1 -46
- package/src/sources/RpcSource.res +29 -175
- package/src/sources/RpcSource.res.mjs +32 -110
- package/src/sources/SimulateSource.res +37 -19
- package/src/sources/SimulateSource.res.mjs +27 -10
- package/src/sources/SvmHyperSyncSource.res +13 -3
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
package/src/Config.res.mjs
CHANGED
|
@@ -115,10 +115,10 @@ let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
|
|
|
115
115
|
transactionFields: s.m(S$RescriptSchema.array(Internal.svmTransactionFieldSchema)),
|
|
116
116
|
blockFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.svmBlockFieldSchema))),
|
|
117
117
|
includeLogs: s.m(S$RescriptSchema.bool),
|
|
118
|
-
accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
|
|
118
|
+
accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
|
|
119
119
|
position: s.m(S$RescriptSchema.int),
|
|
120
120
|
values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
|
|
121
|
-
}))))),
|
|
121
|
+
})))))),
|
|
122
122
|
isInner: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
|
|
123
123
|
accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.string))),
|
|
124
124
|
args: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))
|
package/src/Core.res
CHANGED
|
@@ -10,9 +10,17 @@ type svmHypersyncClientCtor
|
|
|
10
10
|
type hyperfuelClientCtor
|
|
11
11
|
type transactionStoreCtor
|
|
12
12
|
type blockStoreCtor
|
|
13
|
+
type parseConfigYamlOptions = {
|
|
14
|
+
schema?: string,
|
|
15
|
+
env?: dict<string>,
|
|
16
|
+
files?: dict<string>,
|
|
17
|
+
isRescript?: bool,
|
|
18
|
+
}
|
|
13
19
|
|
|
14
20
|
type addon = {
|
|
15
21
|
getConfigJson: (~configPath: Null.t<string>, ~directory: Null.t<string>) => string,
|
|
22
|
+
encodeIndexedTopic: (~abiType: string, ~value: unknown) => EvmTypes.Hex.t,
|
|
23
|
+
parseConfigYaml: (string, parseConfigYamlOptions) => string,
|
|
16
24
|
runCli: (~args: array<string>, ~envioPackageDir: Null.t<string>) => promise<Null.t<string>>,
|
|
17
25
|
@as("EvmHypersyncClient")
|
|
18
26
|
evmHypersyncClient: evmHypersyncClientCtor,
|
|
@@ -197,6 +205,18 @@ let getConfigJson = (~configPath=?, ~directory=?) => {
|
|
|
197
205
|
)
|
|
198
206
|
}
|
|
199
207
|
|
|
208
|
+
// Pure config entry point: no cwd, config file, schema file, .env, or process env lookup.
|
|
209
|
+
// A supplied schema is authoritative; omitted or blank schema text means an empty schema.
|
|
210
|
+
let parseConfigYaml = (~schema=?, ~env=?, ~files=?, ~isRescript=false, yaml) => {
|
|
211
|
+
let addon = getAddon()
|
|
212
|
+
addon.parseConfigYaml(yaml, {
|
|
213
|
+
?schema,
|
|
214
|
+
?env,
|
|
215
|
+
?files,
|
|
216
|
+
isRescript,
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
200
220
|
let runCli = args => {
|
|
201
221
|
let addon = getAddon()
|
|
202
222
|
addon.runCli(~args, ~envioPackageDir=Null.make(envioPackageDir))
|
package/src/Core.res.mjs
CHANGED
|
@@ -180,6 +180,17 @@ function getConfigJson(configPath, directory) {
|
|
|
180
180
|
return addon.getConfigJson(Stdlib_Null.fromOption(configPath), Stdlib_Null.fromOption(directory));
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
function parseConfigYaml(schema, env, files, isRescriptOpt, yaml) {
|
|
184
|
+
let isRescript = isRescriptOpt !== undefined ? isRescriptOpt : false;
|
|
185
|
+
let addon = getAddon();
|
|
186
|
+
return addon.parseConfigYaml(yaml, {
|
|
187
|
+
schema: schema,
|
|
188
|
+
env: env,
|
|
189
|
+
files: files,
|
|
190
|
+
isRescript: isRescript
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
183
194
|
function runCli(args) {
|
|
184
195
|
let addon = getAddon();
|
|
185
196
|
return addon.runCli(args, envioPackageDir);
|
|
@@ -196,6 +207,7 @@ export {
|
|
|
196
207
|
addonRef,
|
|
197
208
|
getAddon,
|
|
198
209
|
getConfigJson,
|
|
210
|
+
parseConfigYaml,
|
|
199
211
|
runCli,
|
|
200
212
|
}
|
|
201
213
|
/* envioPackageDir Not a pure module */
|
package/src/CrossChainState.res
CHANGED
|
@@ -170,7 +170,7 @@ let applyBatchProgress = (crossChainState: t, ~batch: Batch.t, ~blockTimestampNa
|
|
|
170
170
|
// --- Fetch control. ---
|
|
171
171
|
|
|
172
172
|
// Chains ordered furthest-behind first, so the shared buffer pool goes to the
|
|
173
|
-
// chains with the most backfill work before the rest.
|
|
173
|
+
// chains with the most fetchable backfill work before the rest.
|
|
174
174
|
let priorityOrder = (crossChainState: t) =>
|
|
175
175
|
crossChainState.chainStates
|
|
176
176
|
->Dict.valuesToArray
|
|
@@ -198,29 +198,37 @@ let totalReservedSize = (crossChainState: t) => {
|
|
|
198
198
|
// chain-density-derived target block, then subtract what it actually used
|
|
199
199
|
// before moving to the next chain. So a chain that can only use a little
|
|
200
200
|
// (density too low, or already caught up) leaves the rest for the others
|
|
201
|
-
// automatically.
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
201
|
+
// automatically. Starting a new query requires at least 10% of the target pool
|
|
202
|
+
// to be free. A chain visited after the budget falls below that admission unit
|
|
203
|
+
// doesn't query this round — reservations release as responses land, so the
|
|
204
|
+
// next tick redistributes. Chains that do get budget are additionally capped
|
|
205
|
+
// at the first querying chain's target progress mapped onto their own range, so
|
|
206
|
+
// no chain runs further ahead than the chain currently using the pool. Waiting
|
|
207
|
+
// and idle chains do not establish this alignment line.
|
|
207
208
|
let checkAndFetch = async (
|
|
208
209
|
crossChainState: t,
|
|
209
210
|
~dispatchChain: (~chain: ChainMap.Chain.t, ~action: FetchState.nextQuery) => promise<unit>,
|
|
210
211
|
) => {
|
|
212
|
+
let targetBudget = crossChainState.targetBufferSize->Int.toFloat
|
|
211
213
|
let remaining = ref(
|
|
212
214
|
Pervasives.max(
|
|
213
215
|
0.,
|
|
214
|
-
|
|
216
|
+
targetBudget -.
|
|
215
217
|
crossChainState->totalReadyCount->Int.toFloat -.
|
|
216
218
|
crossChainState->totalReservedSize,
|
|
217
219
|
),
|
|
218
220
|
)
|
|
219
221
|
|
|
222
|
+
// New fetch work is admitted in units of 10% of the target pool. Waiting
|
|
223
|
+
// below this floor avoids spending the last few free items on undersized
|
|
224
|
+
// queries; response and batch completion schedule another tick after they
|
|
225
|
+
// release enough budget.
|
|
226
|
+
let minimumAdmissionBudget = targetBudget *. 0.1
|
|
227
|
+
|
|
220
228
|
// A chain with no density signal probes blind, so it only gets a bounded
|
|
221
229
|
// slice of the pool — one unknown chain shouldn't hold the whole budget
|
|
222
|
-
// while it takes its first measurements.
|
|
223
|
-
let coldChainBudget =
|
|
230
|
+
// while it takes its first measurements. Its probe is one admission unit.
|
|
231
|
+
let coldChainBudget = minimumAdmissionBudget
|
|
224
232
|
|
|
225
233
|
// Chunk reservations get headroom over the density estimate so a
|
|
226
234
|
// denser-than-expected range doesn't truncate at the server cap; realtime
|
|
@@ -233,30 +241,33 @@ let checkAndFetch = async (
|
|
|
233
241
|
->priorityOrder
|
|
234
242
|
->Array.forEach(cs => {
|
|
235
243
|
let chainId = (cs->ChainState.chainConfig).id
|
|
236
|
-
if
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
244
|
+
if remaining.contents < minimumAdmissionBudget {
|
|
245
|
+
// More than 90% of the target pool is ready or reserved. Do not attempt
|
|
246
|
+
// any chain action until a full admission unit becomes free.
|
|
247
|
+
actionByChain->Utils.Dict.setByInt(chainId, FetchState.NothingToQuery)
|
|
248
|
+
} else if cs->ChainState.knownHeight == 0 {
|
|
249
|
+
// Let getNextQuery own the waiting decision without trying to size work
|
|
250
|
+
// against an unknown height.
|
|
251
|
+
actionByChain->Utils.Dict.setByInt(
|
|
252
|
+
chainId,
|
|
253
|
+
cs->ChainState.getNextQuery(~chainTargetItems=0., ~chunkItemsMultiplier),
|
|
254
|
+
)
|
|
241
255
|
} else {
|
|
242
256
|
let isCold = cs->ChainState.effectiveDensity === None
|
|
243
257
|
let chainTargetItems =
|
|
244
258
|
(isCold ? Pervasives.min(remaining.contents, coldChainBudget) : remaining.contents) +.
|
|
245
259
|
cs->ChainState.pendingBudget
|
|
260
|
+
let candidateProgress = switch maxProgress.contents {
|
|
261
|
+
| None if !isCold =>
|
|
262
|
+
Some(
|
|
263
|
+
cs->ChainState.progressAtBlock(
|
|
264
|
+
~blockNumber=cs->ChainState.targetBlock(~chainTargetItems),
|
|
265
|
+
),
|
|
266
|
+
)
|
|
267
|
+
| _ => None
|
|
268
|
+
}
|
|
246
269
|
let maxTargetBlock = switch maxProgress.contents {
|
|
247
|
-
| None
|
|
248
|
-
// A cold chain's target is a guess — it doesn't set the alignment
|
|
249
|
-
// line; the first density-bearing chain after it does.
|
|
250
|
-
None
|
|
251
|
-
| None =>
|
|
252
|
-
// The most-behind chain sets the alignment line for everyone after it.
|
|
253
|
-
maxProgress :=
|
|
254
|
-
Some(
|
|
255
|
-
cs->ChainState.progressAtBlock(
|
|
256
|
-
~blockNumber=cs->ChainState.targetBlock(~chainTargetItems),
|
|
257
|
-
),
|
|
258
|
-
)
|
|
259
|
-
None
|
|
270
|
+
| None => None
|
|
260
271
|
// 5% margin past the leader's line: chains whose progress tracks the
|
|
261
272
|
// leader closely would otherwise flap in and out of the clamp as
|
|
262
273
|
// leadership alternates, stalling their pipeline every other tick.
|
|
@@ -267,9 +278,33 @@ let checkAndFetch = async (
|
|
|
267
278
|
~chunkItemsMultiplier,
|
|
268
279
|
~maxTargetBlock?,
|
|
269
280
|
) {
|
|
270
|
-
|
|
|
281
|
+
| WaitingForNewBlock as action => actionByChain->Utils.Dict.setByInt(chainId, action)
|
|
282
|
+
| NothingToQuery =>
|
|
283
|
+
// A chain below its head that emitted no query this tick — its budget
|
|
284
|
+
// went to more-behind chains, or the cross-chain alignment clamped its
|
|
285
|
+
// range to nothing — and has nothing else to wake it must keep polling
|
|
286
|
+
// for new blocks instead of going silent. NothingToQuery isn't
|
|
287
|
+
// dispatched, so such a chain would never be re-scheduled and its head
|
|
288
|
+
// tracking would freeze. A chain is genuinely idle — and correctly left
|
|
289
|
+
// undispatched — when it is caught up to its head/endblock, still
|
|
290
|
+
// draining in-flight queries, or holding ready items that batch
|
|
291
|
+
// processing will drain and re-schedule from.
|
|
292
|
+
let action =
|
|
293
|
+
cs->ChainState.isFetchingAtHead ||
|
|
294
|
+
cs->ChainState.pendingBudget > 0. ||
|
|
295
|
+
cs->ChainState.bufferReadyCount > 0
|
|
296
|
+
? FetchState.NothingToQuery
|
|
297
|
+
: FetchState.WaitingForNewBlock
|
|
271
298
|
actionByChain->Utils.Dict.setByInt(chainId, action)
|
|
272
299
|
| Ready(queries) => {
|
|
300
|
+
// A density-bearing chain establishes the alignment line only after
|
|
301
|
+
// proving that it can use the pool. Waiting and idle chains leave it
|
|
302
|
+
// open for the next chain; cold-chain targets remain guesses and do
|
|
303
|
+
// not lead, as before.
|
|
304
|
+
switch candidateProgress {
|
|
305
|
+
| Some(progress) => maxProgress := Some(progress)
|
|
306
|
+
| None => ()
|
|
307
|
+
}
|
|
273
308
|
let consumed =
|
|
274
309
|
queries->Array.reduce(0., (acc, query: FetchState.query) =>
|
|
275
310
|
acc +. query.itemsEst->Int.toFloat
|
|
@@ -134,10 +134,11 @@ function totalReservedSize(crossChainState) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
async function checkAndFetch(crossChainState, dispatchChain) {
|
|
137
|
+
let targetBudget = crossChainState.targetBufferSize;
|
|
137
138
|
let remaining = {
|
|
138
|
-
contents: Primitive_float.max(0,
|
|
139
|
+
contents: Primitive_float.max(0, targetBudget - totalReadyCount(crossChainState) - totalReservedSize(crossChainState))
|
|
139
140
|
};
|
|
140
|
-
let
|
|
141
|
+
let minimumAdmissionBudget = targetBudget * 0.1;
|
|
141
142
|
let chunkItemsMultiplier = crossChainState.isRealtime ? 3 : 1.5;
|
|
142
143
|
let actionByChain = {};
|
|
143
144
|
let maxProgress = {
|
|
@@ -145,28 +146,36 @@ async function checkAndFetch(crossChainState, dispatchChain) {
|
|
|
145
146
|
};
|
|
146
147
|
priorityOrder(crossChainState).forEach(cs => {
|
|
147
148
|
let chainId = ChainState.chainConfig(cs).id;
|
|
149
|
+
if (remaining.contents < minimumAdmissionBudget) {
|
|
150
|
+
actionByChain[chainId] = "NothingToQuery";
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
148
153
|
if (ChainState.knownHeight(cs) === 0) {
|
|
149
|
-
actionByChain[chainId] =
|
|
154
|
+
actionByChain[chainId] = ChainState.getNextQuery(cs, 0, chunkItemsMultiplier, undefined);
|
|
150
155
|
return;
|
|
151
156
|
}
|
|
152
157
|
let isCold = ChainState.effectiveDensity(cs) === undefined;
|
|
153
158
|
let chainTargetItems = (
|
|
154
|
-
isCold ? Primitive_float.min(remaining.contents,
|
|
159
|
+
isCold ? Primitive_float.min(remaining.contents, minimumAdmissionBudget) : remaining.contents
|
|
155
160
|
) + ChainState.pendingBudget(cs);
|
|
161
|
+
let match = maxProgress.contents;
|
|
162
|
+
let candidateProgress = match !== undefined || isCold ? undefined : ChainState.progressAtBlock(cs, ChainState.targetBlock(cs, chainTargetItems));
|
|
156
163
|
let progress = maxProgress.contents;
|
|
157
|
-
let maxTargetBlock = progress !== undefined ? ChainState.blockAtProgress(cs, progress + 0.05) :
|
|
158
|
-
isCold ? undefined : (maxProgress.contents = ChainState.progressAtBlock(cs, ChainState.targetBlock(cs, chainTargetItems)), undefined)
|
|
159
|
-
);
|
|
164
|
+
let maxTargetBlock = progress !== undefined ? ChainState.blockAtProgress(cs, progress + 0.05) : undefined;
|
|
160
165
|
let action = ChainState.getNextQuery(cs, chainTargetItems, chunkItemsMultiplier, maxTargetBlock);
|
|
161
166
|
if (typeof action !== "object") {
|
|
162
167
|
if (action === "WaitingForNewBlock") {
|
|
163
168
|
actionByChain[chainId] = action;
|
|
164
169
|
return;
|
|
165
170
|
}
|
|
166
|
-
|
|
171
|
+
let action$1 = ChainState.isFetchingAtHead(cs) || ChainState.pendingBudget(cs) > 0 || ChainState.bufferReadyCount(cs) > 0 ? "NothingToQuery" : "WaitingForNewBlock";
|
|
172
|
+
actionByChain[chainId] = action$1;
|
|
167
173
|
return;
|
|
168
174
|
} else {
|
|
169
175
|
let queries = action._0;
|
|
176
|
+
if (candidateProgress !== undefined) {
|
|
177
|
+
maxProgress.contents = candidateProgress;
|
|
178
|
+
}
|
|
170
179
|
let consumed = Stdlib_Array.reduce(queries, 0, (acc, query) => acc + query.itemsEst);
|
|
171
180
|
let partitions = {};
|
|
172
181
|
queries.forEach(query => {
|
|
@@ -236,58 +236,30 @@ let buildSimulateParamsSchema = (params: array<paramMeta>): S.t<Internal.eventPa
|
|
|
236
236
|
|
|
237
237
|
// ============== Build topic filter getters ==============
|
|
238
238
|
|
|
239
|
-
let getTopicEncoder = (abiType: string): (unknown => EvmTypes.Hex.t) =>
|
|
240
|
-
|
|
241
|
-
if abiType->String.endsWith("]") || abiType->String.startsWith("(") {
|
|
242
|
-
TopicFilter.castToHexUnsafe->(Utils.magic: ('a => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t)
|
|
243
|
-
} else {
|
|
244
|
-
switch abiType {
|
|
245
|
-
| "address" =>
|
|
246
|
-
// Lowercase before encoding so mixed-case (checksummed) user input
|
|
247
|
-
// still matches the lowercase hex topics returned by sources.
|
|
248
|
-
|
|
249
|
-
(
|
|
250
|
-
(value: string) =>
|
|
251
|
-
value->String.toLowerCase->Address.unsafeFromString->TopicFilter.fromAddress
|
|
252
|
-
)->(Utils.magic: (string => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t)
|
|
253
|
-
|
|
254
|
-
| "bool" =>
|
|
255
|
-
TopicFilter.fromBool->(Utils.magic: (bool => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t)
|
|
256
|
-
| "string" =>
|
|
257
|
-
TopicFilter.fromDynamicString->(
|
|
258
|
-
Utils.magic: (string => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
| "bytes" =>
|
|
262
|
-
TopicFilter.fromDynamicBytes->(
|
|
263
|
-
Utils.magic: (string => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
| t if t->String.startsWith("uint") =>
|
|
267
|
-
TopicFilter.fromBigInt->(Utils.magic: (bigint => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t)
|
|
268
|
-
| t if t->String.startsWith("int") =>
|
|
269
|
-
TopicFilter.fromSignedBigInt->(
|
|
270
|
-
Utils.magic: (bigint => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
| t if t->String.startsWith("bytes") =>
|
|
274
|
-
TopicFilter.castToHexUnsafe->(
|
|
275
|
-
Utils.magic: ('a => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
| other => JsError.throwWithMessage(`Unsupported topic filter ABI type: ${other}`)
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
239
|
+
let getTopicEncoder = (abiType: string): (unknown => EvmTypes.Hex.t) => value =>
|
|
240
|
+
Core.getAddon().encodeIndexedTopic(~abiType, ~value)
|
|
282
241
|
|
|
283
242
|
let buildTopicGetter = (p: paramMeta) => {
|
|
284
243
|
let encoder = getTopicEncoder(p.abiType)
|
|
244
|
+
let isTuple = p.abiType->String.startsWith("(")
|
|
285
245
|
(eventFilter: dict<JSON.t>) =>
|
|
286
246
|
eventFilter
|
|
287
247
|
->Utils.Dict.dangerouslyGetNonOption(p.name)
|
|
288
|
-
->Option.mapOr([], topicFilters =>
|
|
289
|
-
topicFilters->(Utils.magic: JSON.t => unknown)
|
|
290
|
-
|
|
248
|
+
->Option.mapOr([], topicFilters => {
|
|
249
|
+
let raw = topicFilters->(Utils.magic: JSON.t => unknown)
|
|
250
|
+
// A tuple filter value is itself an array, so a directly-passed tuple is
|
|
251
|
+
// indistinguishable from an OR-list by shape alone. A single tuple is
|
|
252
|
+
// the common case, so try it first; when the value doesn't ABI-encode as
|
|
253
|
+
// one tuple it must be an OR-list of tuples.
|
|
254
|
+
if isTuple {
|
|
255
|
+
switch encoder(raw) {
|
|
256
|
+
| encoded => [encoded]
|
|
257
|
+
| exception _ => raw->normalizeOrThrow->Array.map(encoder)
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
raw->normalizeOrThrow->Array.map(encoder)
|
|
261
|
+
}
|
|
262
|
+
})
|
|
291
263
|
}
|
|
292
264
|
|
|
293
265
|
// ============== Field selection ==============
|
|
@@ -452,6 +424,7 @@ let buildEvmOnEventRegistration = (
|
|
|
452
424
|
}
|
|
453
425
|
|
|
454
426
|
{
|
|
427
|
+
index: -1,
|
|
455
428
|
eventConfig: (eventConfig :> Internal.eventConfig),
|
|
456
429
|
isWildcard,
|
|
457
430
|
handler,
|
|
@@ -536,6 +509,7 @@ let buildSvmOnEventRegistration = (
|
|
|
536
509
|
~contractRegister: option<Internal.contractRegister>,
|
|
537
510
|
~startBlock: option<int>=?,
|
|
538
511
|
): Internal.svmOnEventRegistration => {
|
|
512
|
+
index: -1,
|
|
539
513
|
eventConfig: (eventConfig :> Internal.eventConfig),
|
|
540
514
|
handler,
|
|
541
515
|
contractRegister,
|
|
@@ -613,6 +587,7 @@ let buildFuelOnEventRegistration = (
|
|
|
613
587
|
~contractRegister: option<Internal.contractRegister>,
|
|
614
588
|
~startBlock: option<int>=?,
|
|
615
589
|
): Internal.fuelOnEventRegistration => {
|
|
590
|
+
index: -1,
|
|
616
591
|
eventConfig: (eventConfig :> Internal.eventConfig),
|
|
617
592
|
handler,
|
|
618
593
|
contractRegister,
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Evm from "./sources/Evm.res.mjs";
|
|
4
4
|
import * as Svm from "./sources/Svm.res.mjs";
|
|
5
|
+
import * as Core from "./Core.res.mjs";
|
|
5
6
|
import * as Utils from "./Utils.res.mjs";
|
|
6
7
|
import * as Address from "./Address.res.mjs";
|
|
7
8
|
import * as FuelSDK from "./sources/FuelSDK.res.mjs";
|
|
8
9
|
import * as Internal from "./Internal.res.mjs";
|
|
9
|
-
import * as TopicFilter from "./TopicFilter.res.mjs";
|
|
10
10
|
import * as LogSelection from "./LogSelection.res.mjs";
|
|
11
11
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
12
12
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
@@ -208,34 +208,24 @@ function buildSimulateParamsSchema(params) {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
function getTopicEncoder(abiType) {
|
|
211
|
-
|
|
212
|
-
return TopicFilter.castToHexUnsafe;
|
|
213
|
-
}
|
|
214
|
-
switch (abiType) {
|
|
215
|
-
case "address" :
|
|
216
|
-
return value => TopicFilter.fromAddress(value.toLowerCase());
|
|
217
|
-
case "bool" :
|
|
218
|
-
return TopicFilter.fromBool;
|
|
219
|
-
case "bytes" :
|
|
220
|
-
return TopicFilter.fromDynamicBytes;
|
|
221
|
-
case "string" :
|
|
222
|
-
return TopicFilter.fromDynamicString;
|
|
223
|
-
default:
|
|
224
|
-
if (abiType.startsWith("uint")) {
|
|
225
|
-
return TopicFilter.fromBigInt;
|
|
226
|
-
} else if (abiType.startsWith("int")) {
|
|
227
|
-
return TopicFilter.fromSignedBigInt;
|
|
228
|
-
} else if (abiType.startsWith("bytes")) {
|
|
229
|
-
return TopicFilter.castToHexUnsafe;
|
|
230
|
-
} else {
|
|
231
|
-
return Stdlib_JsError.throwWithMessage(`Unsupported topic filter ABI type: ` + abiType);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
211
|
+
return value => Core.getAddon().encodeIndexedTopic(abiType, value);
|
|
234
212
|
}
|
|
235
213
|
|
|
236
214
|
function buildTopicGetter(p) {
|
|
237
215
|
let encoder = getTopicEncoder(p.abiType);
|
|
238
|
-
|
|
216
|
+
let isTuple = p.abiType.startsWith("(");
|
|
217
|
+
return eventFilter => Stdlib_Option.mapOr(eventFilter[p.name], [], topicFilters => {
|
|
218
|
+
if (!isTuple) {
|
|
219
|
+
return normalizeOrThrow(topicFilters).map(encoder);
|
|
220
|
+
}
|
|
221
|
+
let encoded;
|
|
222
|
+
try {
|
|
223
|
+
encoded = encoder(topicFilters);
|
|
224
|
+
} catch (exn) {
|
|
225
|
+
return normalizeOrThrow(topicFilters).map(encoder);
|
|
226
|
+
}
|
|
227
|
+
return [encoded];
|
|
228
|
+
});
|
|
239
229
|
}
|
|
240
230
|
|
|
241
231
|
let alwaysIncludedBlockFields = [
|
|
@@ -317,6 +307,7 @@ function buildEvmOnEventRegistration(eventConfig, isWildcard, handler, contractR
|
|
|
317
307
|
let sb = resolvedWhere.startBlock;
|
|
318
308
|
let resolvedStartBlock = sb !== undefined ? sb : startBlock;
|
|
319
309
|
return {
|
|
310
|
+
index: -1,
|
|
320
311
|
eventConfig: eventConfig,
|
|
321
312
|
handler: handler,
|
|
322
313
|
contractRegister: contractRegister,
|
|
@@ -369,6 +360,7 @@ function buildSvmInstructionEventConfig(contractName, instructionName, programId
|
|
|
369
360
|
|
|
370
361
|
function buildSvmOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, startBlock) {
|
|
371
362
|
return {
|
|
363
|
+
index: -1,
|
|
372
364
|
eventConfig: eventConfig,
|
|
373
365
|
handler: handler,
|
|
374
366
|
contractRegister: contractRegister,
|
|
@@ -438,6 +430,7 @@ function buildFuelEventConfig(contractName, eventName, kind, sighash, rawAbi) {
|
|
|
438
430
|
|
|
439
431
|
function buildFuelOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, startBlock) {
|
|
440
432
|
return {
|
|
433
|
+
index: -1,
|
|
441
434
|
eventConfig: eventConfig,
|
|
442
435
|
handler: handler,
|
|
443
436
|
contractRegister: contractRegister,
|
package/src/EventProcessing.res
CHANGED
|
@@ -73,9 +73,10 @@ let runEventHandlerOrThrow = async (
|
|
|
73
73
|
)
|
|
74
74
|
}
|
|
75
75
|
let handlerDuration = timeBeforeHandler->Performance.secondsSince
|
|
76
|
+
let eventConfig = eventItem.onEventRegistration.eventConfig
|
|
76
77
|
Prometheus.ProcessingHandler.increment(
|
|
77
|
-
~contract=
|
|
78
|
-
~event=
|
|
78
|
+
~contract=eventConfig.contractName,
|
|
79
|
+
~event=eventConfig.name,
|
|
79
80
|
~duration=handlerDuration,
|
|
80
81
|
)
|
|
81
82
|
}
|
|
@@ -121,8 +122,8 @@ let runHandlerOrThrow = async (
|
|
|
121
122
|
}),
|
|
122
123
|
)
|
|
123
124
|
}
|
|
124
|
-
| Event(
|
|
125
|
-
switch onEventRegistration.handler {
|
|
125
|
+
| Event(_) =>
|
|
126
|
+
switch (item->Internal.castUnsafeEventItem).onEventRegistration.handler {
|
|
126
127
|
| Some(handler) =>
|
|
127
128
|
await item->runEventHandlerOrThrow(
|
|
128
129
|
~handler,
|
|
@@ -161,7 +162,9 @@ let preloadBatchOrThrow = async (
|
|
|
161
162
|
for idx in 0 to checkpointEventsProcessed - 1 {
|
|
162
163
|
let item = batch.items->Array.getUnsafe(itemIdx.contents + idx)
|
|
163
164
|
switch item {
|
|
164
|
-
| Event(
|
|
165
|
+
| Event(_) =>
|
|
166
|
+
let {handler, eventConfig: {contractName, name: eventName}} =
|
|
167
|
+
(item->Internal.castUnsafeEventItem).onEventRegistration
|
|
165
168
|
switch handler {
|
|
166
169
|
| None => ()
|
|
167
170
|
| Some(handler) =>
|
|
@@ -65,7 +65,8 @@ async function runEventHandlerOrThrow(item, checkpointId, handler, indexerState,
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
let handlerDuration = Performance.secondsSince(timeBeforeHandler);
|
|
68
|
-
|
|
68
|
+
let eventConfig = item.onEventRegistration.eventConfig;
|
|
69
|
+
return Prometheus.ProcessingHandler.increment(eventConfig.contractName, eventConfig.name, handlerDuration);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
async function runHandlerOrThrow(item, checkpointId, indexerState, loadManager, persistence, config, chains) {
|