envio 3.2.0 → 3.3.0-alpha.0
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/package.json +6 -7
- package/src/Batch.res +12 -15
- package/src/Batch.res.mjs +4 -5
- package/src/BatchProcessing.res +199 -0
- package/src/BatchProcessing.res.mjs +125 -0
- package/src/ChainFetching.res +375 -0
- package/src/ChainFetching.res.mjs +206 -0
- package/src/ChainMetadata.res +27 -0
- package/src/ChainMetadata.res.mjs +27 -0
- package/src/ChainState.res +618 -0
- package/src/ChainState.res.mjs +461 -0
- package/src/ChainState.resi +85 -0
- package/src/Config.res +13 -4
- package/src/Config.res.mjs +8 -3
- package/src/ContractRegisterContext.res +106 -0
- package/src/ContractRegisterContext.res.mjs +76 -0
- package/src/Core.res +3 -0
- package/src/CrossChainState.res +269 -0
- package/src/CrossChainState.res.mjs +197 -0
- package/src/CrossChainState.resi +47 -0
- package/src/Ecosystem.res +58 -0
- package/src/Ecosystem.res.mjs +43 -1
- package/src/Env.res +7 -2
- package/src/Env.res.mjs +5 -2
- package/src/EventConfigBuilder.res +35 -0
- package/src/EventConfigBuilder.res.mjs +21 -0
- package/src/EventProcessing.res +60 -46
- package/src/EventProcessing.res.mjs +33 -32
- package/src/EventUtils.res +0 -4
- package/src/EventUtils.res.mjs +0 -4
- package/src/ExitOnCaughtUp.res +10 -0
- package/src/ExitOnCaughtUp.res.mjs +22 -0
- package/src/FetchState.res +162 -113
- package/src/FetchState.res.mjs +181 -119
- package/src/HandlerLoader.res +6 -1
- package/src/HandlerLoader.res.mjs +13 -36
- package/src/InMemoryStore.res +36 -451
- package/src/InMemoryStore.res.mjs +20 -369
- package/src/IndexerLoop.res +44 -0
- package/src/IndexerLoop.res.mjs +46 -0
- package/src/IndexerState.res +544 -0
- package/src/IndexerState.res.mjs +547 -0
- package/src/IndexerState.resi +132 -0
- package/src/Internal.res +44 -7
- package/src/LoadLayer.res +20 -18
- package/src/LoadLayer.res.mjs +14 -12
- package/src/LoadLayer.resi +6 -3
- package/src/LogSelection.res +86 -17
- package/src/LogSelection.res.mjs +67 -14
- package/src/Logging.res +11 -44
- package/src/Logging.res.mjs +10 -42
- package/src/Main.res +54 -79
- package/src/Main.res.mjs +44 -56
- package/src/PgStorage.res +8 -71
- package/src/PgStorage.res.mjs +3 -47
- package/src/Prometheus.res +7 -8
- package/src/Prometheus.res.mjs +7 -4
- package/src/PruneStaleHistory.res +45 -0
- package/src/PruneStaleHistory.res.mjs +46 -0
- package/src/RawEvent.res +73 -0
- package/src/RawEvent.res.mjs +51 -0
- package/src/Rollback.res +204 -0
- package/src/Rollback.res.mjs +118 -0
- package/src/SimulateItems.res +12 -10
- package/src/SimulateItems.res.mjs +1 -1
- package/src/TestIndexer.res +34 -27
- package/src/TestIndexer.res.mjs +4 -1
- package/src/UserContext.res +26 -114
- package/src/UserContext.res.mjs +15 -78
- package/src/Writing.res +242 -0
- package/src/Writing.res.mjs +215 -0
- package/src/db/InternalTable.res +14 -27
- package/src/sources/Evm.res +40 -1
- package/src/sources/Evm.res.mjs +94 -84
- package/src/sources/Fuel.res +40 -1
- package/src/sources/Fuel.res.mjs +36 -26
- package/src/sources/HyperFuel.res +41 -120
- package/src/sources/HyperFuel.res.mjs +42 -80
- package/src/sources/HyperFuel.resi +1 -24
- package/src/sources/HyperFuelClient.res +16 -297
- package/src/sources/HyperFuelClient.res.mjs +5 -4
- package/src/sources/HyperFuelSource.res +33 -8
- package/src/sources/HyperFuelSource.res.mjs +56 -10
- package/src/sources/HyperSyncSource.res +7 -5
- package/src/sources/HyperSyncSource.res.mjs +3 -3
- package/src/sources/RpcSource.res +2 -2
- package/src/sources/RpcSource.res.mjs +1 -1
- package/src/sources/SourceManager.res +8 -18
- package/src/sources/SourceManager.res.mjs +8 -5
- package/src/sources/SourceManager.resi +4 -1
- package/src/sources/Svm.res +18 -1
- package/src/sources/Svm.res.mjs +30 -22
- package/src/sources/SvmHyperSyncSource.res +1 -1
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/src/tui/Tui.res +38 -36
- package/src/tui/Tui.res.mjs +51 -51
- package/src/ChainFetcher.res +0 -519
- package/src/ChainFetcher.res.mjs +0 -314
- package/src/ChainManager.res +0 -358
- package/src/ChainManager.res.mjs +0 -291
- package/src/Ctx.res +0 -6
- package/src/Ctx.res.mjs +0 -2
- package/src/GlobalState.res +0 -1056
- package/src/GlobalState.res.mjs +0 -1086
- package/src/GlobalStateManager.res +0 -57
- package/src/GlobalStateManager.res.mjs +0 -68
- package/src/GlobalStateManager.resi +0 -7
- package/src/Ports.res +0 -5
- package/src/Ports.res.mjs +0 -9
- package/src/adapters/MarkBatchProcessedAdapter.res +0 -5
- package/src/adapters/MarkBatchProcessedAdapter.res.mjs +0 -14
- package/src/sources/EnvioApiClient.res +0 -15
- package/src/sources/EnvioApiClient.res.mjs +0 -24
package/src/Internal.res
CHANGED
|
@@ -269,12 +269,15 @@ type genericEvent<'params, 'block, 'transaction> = {
|
|
|
269
269
|
// (blockNumber, timestamp, blockHash) live on the item instead.
|
|
270
270
|
type event
|
|
271
271
|
|
|
272
|
-
|
|
272
|
+
// Opaque payload an item carries. A source builds an ecosystem-specific
|
|
273
|
+
// concrete payload (see `Evm.payload` / `Fuel.payload`) and erases it to this
|
|
274
|
+
// type; consumers never read it directly — the ecosystem converts it back to
|
|
275
|
+
// its own payload to produce the user-facing `event`, a logger, or a raw
|
|
276
|
+
// event. The concrete payload types deliberately live in the ecosystem
|
|
277
|
+
// modules, not here, and are distinct per ecosystem.
|
|
278
|
+
type eventPayload
|
|
273
279
|
|
|
274
|
-
|
|
275
|
-
// which genuinely need the runtime shape.
|
|
276
|
-
external toGenericEvent: event => genericEvent<eventParams, eventBlock, eventTransaction> =
|
|
277
|
-
"%identity"
|
|
280
|
+
external payloadToEvent: eventPayload => event = "%identity"
|
|
278
281
|
|
|
279
282
|
type genericLoaderArgs<'event, 'context> = {
|
|
280
283
|
event: 'event,
|
|
@@ -344,6 +347,16 @@ type rec paramMeta = {
|
|
|
344
347
|
components?: array<paramMeta>,
|
|
345
348
|
}
|
|
346
349
|
|
|
350
|
+
// Fetch-state registry value for an indexed contract address.
|
|
351
|
+
// `effectiveStartBlock` is derived from the registration block and the
|
|
352
|
+
// contract's configured start block (see `FetchState.deriveEffectiveStartBlock`).
|
|
353
|
+
type indexingContract = {
|
|
354
|
+
address: Address.t,
|
|
355
|
+
contractName: string,
|
|
356
|
+
registrationBlock: int,
|
|
357
|
+
effectiveStartBlock: int,
|
|
358
|
+
}
|
|
359
|
+
|
|
347
360
|
// This is private so it's not manually constructed internally
|
|
348
361
|
// The idea is that it can only be coerced from fuel/evmEventConfig
|
|
349
362
|
// and it can include their fields. We prevent manual creation,
|
|
@@ -358,6 +371,12 @@ type eventConfig = private {
|
|
|
358
371
|
// Usually always false for wildcard events
|
|
359
372
|
// But might be true for wildcard event with dynamic event filter by addresses
|
|
360
373
|
dependsOnAddresses: bool,
|
|
374
|
+
// Precompiled predicate (EVM only) for events that filter an indexed address
|
|
375
|
+
// param by registered addresses. Given the decoded event and the log's block
|
|
376
|
+
// number, drops an event whose param-address isn't registered at/before that
|
|
377
|
+
// block — the param-level analogue of EventRouter's srcAddress
|
|
378
|
+
// `effectiveStartBlock` check. Absent otherwise.
|
|
379
|
+
clientAddressFilter?: (eventPayload, int, dict<indexingContract>) => bool,
|
|
361
380
|
handler: option<handler>,
|
|
362
381
|
contractRegister: option<contractRegister>,
|
|
363
382
|
paramsRawEventSchema: S.schema<eventParams>,
|
|
@@ -483,7 +502,25 @@ type eventItem = private {
|
|
|
483
502
|
blockNumber: int,
|
|
484
503
|
blockHash: string,
|
|
485
504
|
logIndex: int,
|
|
486
|
-
|
|
505
|
+
payload: eventPayload,
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Row shape for the `raw_events` table. Defined here (rather than in
|
|
509
|
+
// `InternalTable`) so the ecosystem's `toRawEvent` can reference it without
|
|
510
|
+
// pulling in `InternalTable`'s dependency on `Config`.
|
|
511
|
+
type rawEvent = {
|
|
512
|
+
chain_id: int,
|
|
513
|
+
event_id: bigint,
|
|
514
|
+
event_name: string,
|
|
515
|
+
contract_name: string,
|
|
516
|
+
block_number: int,
|
|
517
|
+
log_index: int,
|
|
518
|
+
src_address: Address.t,
|
|
519
|
+
block_hash: string,
|
|
520
|
+
block_timestamp: int,
|
|
521
|
+
block_fields: JSON.t,
|
|
522
|
+
transaction_fields: JSON.t,
|
|
523
|
+
params: JSON.t,
|
|
487
524
|
}
|
|
488
525
|
|
|
489
526
|
// Opaque type to support both EVM and other ecosystems
|
|
@@ -517,7 +554,7 @@ type item =
|
|
|
517
554
|
blockNumber: int,
|
|
518
555
|
blockHash: string,
|
|
519
556
|
logIndex: int,
|
|
520
|
-
|
|
557
|
+
payload: eventPayload,
|
|
521
558
|
})
|
|
522
559
|
| @as(1) Block({onBlockConfig: onBlockConfig, blockNumber: int, logIndex: int})
|
|
523
560
|
|
package/src/LoadLayer.res
CHANGED
|
@@ -2,19 +2,20 @@ let loadById = (
|
|
|
2
2
|
~loadManager,
|
|
3
3
|
~persistence: Persistence.t,
|
|
4
4
|
~entityConfig: Internal.entityConfig,
|
|
5
|
-
~
|
|
5
|
+
~indexerState,
|
|
6
6
|
~shouldGroup,
|
|
7
7
|
~item,
|
|
8
|
+
~ecosystem,
|
|
8
9
|
~entityId,
|
|
9
10
|
) => {
|
|
10
11
|
let key = `${entityConfig.name}.get`
|
|
11
|
-
let inMemTable =
|
|
12
|
+
let inMemTable = indexerState->InMemoryStore.getInMemTable(~entityConfig)
|
|
12
13
|
|
|
13
14
|
let load = async (idsToLoad, ~onError as _) => {
|
|
14
15
|
let storage = persistence->Persistence.getInitializedStorageOrThrow
|
|
15
16
|
let timerRef = Prometheus.StorageLoad.startOperation(~storage=storage.name, ~operation=key)
|
|
16
17
|
|
|
17
|
-
// Since LoadManager.call prevents
|
|
18
|
+
// Since LoadManager.call prevents registering entities already in the in-memory store,
|
|
18
19
|
// we can be sure that we load only the new ones.
|
|
19
20
|
let dbEntities = try {
|
|
20
21
|
(
|
|
@@ -28,7 +29,10 @@ let loadById = (
|
|
|
28
29
|
)->(Utils.magic: array<unknown> => array<Internal.entity>)
|
|
29
30
|
} catch {
|
|
30
31
|
| Persistence.StorageError({message, reason}) =>
|
|
31
|
-
reason->ErrorHandling.mkLogAndRaise(
|
|
32
|
+
reason->ErrorHandling.mkLogAndRaise(
|
|
33
|
+
~logger=Ecosystem.getItemLogger(item, ~ecosystem),
|
|
34
|
+
~msg=message,
|
|
35
|
+
)
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
let entitiesMap = Dict.make()
|
|
@@ -40,7 +44,7 @@ let loadById = (
|
|
|
40
44
|
}
|
|
41
45
|
idsToLoad->Array.forEach(entityId => {
|
|
42
46
|
inMemTable->InMemoryTable.Entity.initValue(
|
|
43
|
-
~committedCheckpointId=
|
|
47
|
+
~committedCheckpointId=indexerState->IndexerState.committedCheckpointId,
|
|
44
48
|
~key=entityId,
|
|
45
49
|
~entity=entitiesMap->Utils.Dict.dangerouslyGetNonOption(entityId),
|
|
46
50
|
)
|
|
@@ -68,7 +72,7 @@ let loadById = (
|
|
|
68
72
|
let callEffect = (
|
|
69
73
|
~effect: Internal.effect,
|
|
70
74
|
~arg: Internal.effectArgs,
|
|
71
|
-
~inMemTable:
|
|
75
|
+
~inMemTable: IndexerState.effectCacheInMemTable,
|
|
72
76
|
~timerRef,
|
|
73
77
|
~onError,
|
|
74
78
|
) => {
|
|
@@ -245,13 +249,14 @@ let loadEffect = (
|
|
|
245
249
|
~persistence: Persistence.t,
|
|
246
250
|
~effect: Internal.effect,
|
|
247
251
|
~effectArgs,
|
|
248
|
-
~
|
|
252
|
+
~indexerState,
|
|
249
253
|
~shouldGroup,
|
|
250
254
|
~item,
|
|
255
|
+
~ecosystem,
|
|
251
256
|
) => {
|
|
252
257
|
let effectName = effect.name
|
|
253
258
|
let key = `${effectName}.effect`
|
|
254
|
-
let inMemTable =
|
|
259
|
+
let inMemTable = indexerState->InMemoryStore.getEffectInMemTable(~effect)
|
|
255
260
|
|
|
256
261
|
let load = async (args, ~onError) => {
|
|
257
262
|
let idsToLoad = args->Array.map((arg: Internal.effectArgs) => arg.cacheKey)
|
|
@@ -279,9 +284,7 @@ let loadEffect = (
|
|
|
279
284
|
)->(Utils.magic: array<unknown> => array<Internal.effectCacheItem>)
|
|
280
285
|
} catch {
|
|
281
286
|
| exn =>
|
|
282
|
-
item
|
|
283
|
-
->Logging.getItemLogger
|
|
284
|
-
->Logging.childWarn({
|
|
287
|
+
Ecosystem.getItemLogger(item, ~ecosystem)->Logging.childWarn({
|
|
285
288
|
"msg": `Failed to load cache effect cache. The indexer will continue working, but the effect will not be able to use the cache.`,
|
|
286
289
|
"err": exn->Utils.prettifyExn,
|
|
287
290
|
"effect": effectName,
|
|
@@ -298,9 +301,7 @@ let loadEffect = (
|
|
|
298
301
|
| S.Raised(error) =>
|
|
299
302
|
inMemTable.invalidationsCount = inMemTable.invalidationsCount + 1
|
|
300
303
|
Prometheus.EffectCacheInvalidationsCount.increment(~effectName)
|
|
301
|
-
item
|
|
302
|
-
->Logging.getItemLogger
|
|
303
|
-
->Logging.childTrace({
|
|
304
|
+
Ecosystem.getItemLogger(item, ~ecosystem)->Logging.childTrace({
|
|
304
305
|
"msg": "Invalidated effect cache",
|
|
305
306
|
"input": dbEntity.id,
|
|
306
307
|
"effect": effectName,
|
|
@@ -354,13 +355,14 @@ let loadByFilter = (
|
|
|
354
355
|
~loadManager,
|
|
355
356
|
~persistence: Persistence.t,
|
|
356
357
|
~entityConfig: Internal.entityConfig,
|
|
357
|
-
~
|
|
358
|
+
~indexerState,
|
|
358
359
|
~shouldGroup,
|
|
359
360
|
~item,
|
|
361
|
+
~ecosystem,
|
|
360
362
|
~filter: EntityFilter.t,
|
|
361
363
|
) => {
|
|
362
364
|
let key = filter->EntityFilter.toOperationKey(~entityName=entityConfig.name)
|
|
363
|
-
let inMemTable =
|
|
365
|
+
let inMemTable = indexerState->InMemoryStore.getInMemTable(~entityConfig)
|
|
364
366
|
|
|
365
367
|
let load = async (filters: array<EntityFilter.t>, ~onError as _) => {
|
|
366
368
|
let storage = persistence->Persistence.getInitializedStorageOrThrow
|
|
@@ -385,7 +387,7 @@ let loadByFilter = (
|
|
|
385
387
|
|
|
386
388
|
entities->Array.forEach(entity => {
|
|
387
389
|
inMemTable->InMemoryTable.Entity.initValue(
|
|
388
|
-
~committedCheckpointId=
|
|
390
|
+
~committedCheckpointId=indexerState->IndexerState.committedCheckpointId,
|
|
389
391
|
~key=entity.id,
|
|
390
392
|
~entity=Some(entity),
|
|
391
393
|
)
|
|
@@ -396,7 +398,7 @@ let loadByFilter = (
|
|
|
396
398
|
| Persistence.StorageError({message, reason}) =>
|
|
397
399
|
reason->ErrorHandling.mkLogAndRaise(
|
|
398
400
|
~logger=Logging.createChildFrom(
|
|
399
|
-
~logger=
|
|
401
|
+
~logger=Ecosystem.getItemLogger(item, ~ecosystem),
|
|
400
402
|
// The executed query might be merged from multiple getWhere
|
|
401
403
|
// calls, so report it as the operation users write with the
|
|
402
404
|
// values bound to its placeholders, instead of an internal
|
package/src/LoadLayer.res.mjs
CHANGED
|
@@ -4,10 +4,12 @@ import * as Table from "./db/Table.res.mjs";
|
|
|
4
4
|
import * as Utils from "./Utils.res.mjs";
|
|
5
5
|
import * as Hrtime from "./bindings/Hrtime.res.mjs";
|
|
6
6
|
import * as Logging from "./Logging.res.mjs";
|
|
7
|
+
import * as Ecosystem from "./Ecosystem.res.mjs";
|
|
7
8
|
import * as Prometheus from "./Prometheus.res.mjs";
|
|
8
9
|
import * as LoadManager from "./LoadManager.res.mjs";
|
|
9
10
|
import * as Persistence from "./Persistence.res.mjs";
|
|
10
11
|
import * as EntityFilter from "./db/EntityFilter.res.mjs";
|
|
12
|
+
import * as IndexerState from "./IndexerState.res.mjs";
|
|
11
13
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
12
14
|
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
13
15
|
import * as InMemoryStore from "./InMemoryStore.res.mjs";
|
|
@@ -15,9 +17,9 @@ import * as InMemoryTable from "./InMemoryTable.res.mjs";
|
|
|
15
17
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
16
18
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
17
19
|
|
|
18
|
-
function loadById(loadManager, persistence, entityConfig,
|
|
20
|
+
function loadById(loadManager, persistence, entityConfig, indexerState, shouldGroup, item, ecosystem, entityId) {
|
|
19
21
|
let key = entityConfig.name + `.get`;
|
|
20
|
-
let inMemTable = InMemoryStore.getInMemTable(
|
|
22
|
+
let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig);
|
|
21
23
|
let load = async (idsToLoad, param) => {
|
|
22
24
|
let storage = Persistence.getInitializedStorageOrThrow(persistence);
|
|
23
25
|
let timerRef = Prometheus.StorageLoad.startOperation(storage.name, key);
|
|
@@ -31,7 +33,7 @@ function loadById(loadManager, persistence, entityConfig, inMemoryStore, shouldG
|
|
|
31
33
|
} catch (raw_exn) {
|
|
32
34
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
33
35
|
if (exn.RE_EXN_ID === Persistence.StorageError) {
|
|
34
|
-
dbEntities = ErrorHandling.mkLogAndRaise(
|
|
36
|
+
dbEntities = ErrorHandling.mkLogAndRaise(Ecosystem.getItemLogger(item, ecosystem), exn.message, exn.reason);
|
|
35
37
|
} else {
|
|
36
38
|
throw exn;
|
|
37
39
|
}
|
|
@@ -41,7 +43,7 @@ function loadById(loadManager, persistence, entityConfig, inMemoryStore, shouldG
|
|
|
41
43
|
let entity = dbEntities[idx];
|
|
42
44
|
entitiesMap[entity.id] = entity;
|
|
43
45
|
}
|
|
44
|
-
idsToLoad.forEach(entityId => InMemoryTable.Entity.initValue(inMemTable,
|
|
46
|
+
idsToLoad.forEach(entityId => InMemoryTable.Entity.initValue(inMemTable, IndexerState.committedCheckpointId(indexerState), entityId, entitiesMap[entityId]));
|
|
45
47
|
return Prometheus.StorageLoad.endOperation(timerRef, storage.name, key, idsToLoad.length, dbEntities.length);
|
|
46
48
|
};
|
|
47
49
|
return LoadManager.call(loadManager, entityId, key, load, LoadManager.noopHasher, shouldGroup, hash => hash in inMemTable.latestEntityChangeById, InMemoryTable.Entity.getUnsafe(inMemTable));
|
|
@@ -126,10 +128,10 @@ function executeWithRateLimit(effect, effectArgs, inMemTable, onError, isFromQue
|
|
|
126
128
|
return Promise.all(promises);
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
function loadEffect(loadManager, persistence, effect, effectArgs,
|
|
131
|
+
function loadEffect(loadManager, persistence, effect, effectArgs, indexerState, shouldGroup, item, ecosystem) {
|
|
130
132
|
let effectName = effect.name;
|
|
131
133
|
let key = effectName + `.effect`;
|
|
132
|
-
let inMemTable = InMemoryStore.getEffectInMemTable(
|
|
134
|
+
let inMemTable = InMemoryStore.getEffectInMemTable(indexerState, effect);
|
|
133
135
|
let load = async (args, onError) => {
|
|
134
136
|
let idsToLoad = args.map(arg => arg.cacheKey);
|
|
135
137
|
let idsFromCache = new Set();
|
|
@@ -150,7 +152,7 @@ function loadEffect(loadManager, persistence, effect, effectArgs, inMemoryStore,
|
|
|
150
152
|
}, match$1.table);
|
|
151
153
|
} catch (raw_exn) {
|
|
152
154
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
153
|
-
Logging.childWarn(
|
|
155
|
+
Logging.childWarn(Ecosystem.getItemLogger(item, ecosystem), {
|
|
154
156
|
msg: `Failed to load cache effect cache. The indexer will continue working, but the effect will not be able to use the cache.`,
|
|
155
157
|
err: Utils.prettifyExn(exn),
|
|
156
158
|
effect: effectName
|
|
@@ -167,7 +169,7 @@ function loadEffect(loadManager, persistence, effect, effectArgs, inMemoryStore,
|
|
|
167
169
|
if (error.RE_EXN_ID === S$RescriptSchema.Raised) {
|
|
168
170
|
inMemTable.invalidationsCount = inMemTable.invalidationsCount + 1 | 0;
|
|
169
171
|
Prometheus.EffectCacheInvalidationsCount.increment(effectName);
|
|
170
|
-
return Logging.childTrace(
|
|
172
|
+
return Logging.childTrace(Ecosystem.getItemLogger(item, ecosystem), {
|
|
171
173
|
msg: "Invalidated effect cache",
|
|
172
174
|
input: dbEntity.id,
|
|
173
175
|
effect: effectName,
|
|
@@ -197,9 +199,9 @@ function loadEffect(loadManager, persistence, effect, effectArgs, inMemoryStore,
|
|
|
197
199
|
return LoadManager.call(loadManager, effectArgs, key, load, args => args.cacheKey, shouldGroup, hash => InMemoryStore.hasEffectOutput(inMemTable, hash), hash => InMemoryStore.getEffectOutputUnsafe(inMemTable, hash));
|
|
198
200
|
}
|
|
199
201
|
|
|
200
|
-
function loadByFilter(loadManager, persistence, entityConfig,
|
|
202
|
+
function loadByFilter(loadManager, persistence, entityConfig, indexerState, shouldGroup, item, ecosystem, filter) {
|
|
201
203
|
let key = EntityFilter.toOperationKey(filter, entityConfig.name);
|
|
202
|
-
let inMemTable = InMemoryStore.getInMemTable(
|
|
204
|
+
let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig);
|
|
203
205
|
let load = async (filters, param) => {
|
|
204
206
|
let storage = Persistence.getInitializedStorageOrThrow(persistence);
|
|
205
207
|
let timerRef = Prometheus.StorageLoad.startOperation(storage.name, key);
|
|
@@ -211,13 +213,13 @@ function loadByFilter(loadManager, persistence, entityConfig, inMemoryStore, sho
|
|
|
211
213
|
await Promise.all(queries.map(async filter => {
|
|
212
214
|
try {
|
|
213
215
|
let entities = await storage.loadOrThrow(filter, entityConfig.table);
|
|
214
|
-
entities.forEach(entity => InMemoryTable.Entity.initValue(inMemTable,
|
|
216
|
+
entities.forEach(entity => InMemoryTable.Entity.initValue(inMemTable, IndexerState.committedCheckpointId(indexerState), entity.id, entity));
|
|
215
217
|
size.contents = size.contents + entities.length | 0;
|
|
216
218
|
return;
|
|
217
219
|
} catch (raw_exn) {
|
|
218
220
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
219
221
|
if (exn.RE_EXN_ID === Persistence.StorageError) {
|
|
220
|
-
return ErrorHandling.mkLogAndRaise(Logging.createChildFrom(
|
|
222
|
+
return ErrorHandling.mkLogAndRaise(Logging.createChildFrom(Ecosystem.getItemLogger(item, ecosystem), {
|
|
221
223
|
operation: key,
|
|
222
224
|
params: EntityFilter.getParams(filter)
|
|
223
225
|
}), exn.message, exn.reason);
|
package/src/LoadLayer.resi
CHANGED
|
@@ -2,9 +2,10 @@ let loadById: (
|
|
|
2
2
|
~loadManager: LoadManager.t,
|
|
3
3
|
~persistence: Persistence.t,
|
|
4
4
|
~entityConfig: Internal.entityConfig,
|
|
5
|
-
~
|
|
5
|
+
~indexerState: IndexerState.t,
|
|
6
6
|
~shouldGroup: bool,
|
|
7
7
|
~item: Internal.item,
|
|
8
|
+
~ecosystem: Ecosystem.t,
|
|
8
9
|
~entityId: string,
|
|
9
10
|
) => promise<option<Internal.entity>>
|
|
10
11
|
|
|
@@ -12,9 +13,10 @@ let loadByFilter: (
|
|
|
12
13
|
~loadManager: LoadManager.t,
|
|
13
14
|
~persistence: Persistence.t,
|
|
14
15
|
~entityConfig: Internal.entityConfig,
|
|
15
|
-
~
|
|
16
|
+
~indexerState: IndexerState.t,
|
|
16
17
|
~shouldGroup: bool,
|
|
17
18
|
~item: Internal.item,
|
|
19
|
+
~ecosystem: Ecosystem.t,
|
|
18
20
|
~filter: EntityFilter.t,
|
|
19
21
|
) => promise<array<Internal.entity>>
|
|
20
22
|
|
|
@@ -23,7 +25,8 @@ let loadEffect: (
|
|
|
23
25
|
~persistence: Persistence.t,
|
|
24
26
|
~effect: Internal.effect,
|
|
25
27
|
~effectArgs: Internal.effectArgs,
|
|
26
|
-
~
|
|
28
|
+
~indexerState: IndexerState.t,
|
|
27
29
|
~shouldGroup: bool,
|
|
28
30
|
~item: Internal.item,
|
|
31
|
+
~ecosystem: Ecosystem.t,
|
|
29
32
|
) => promise<Internal.effectOutput>
|
package/src/LogSelection.res
CHANGED
|
@@ -60,6 +60,10 @@ let make = (~addresses, ~topicSelections) => {
|
|
|
60
60
|
type parsedEventFilters = {
|
|
61
61
|
getEventFiltersOrThrow: ChainMap.Chain.t => Internal.eventFilters,
|
|
62
62
|
filterByAddresses: bool,
|
|
63
|
+
// Indexed params filtered by `chain.<Contract>.addresses`, in disjunctive
|
|
64
|
+
// normal form (outer array OR of AND-groups). Empty unless `filterByAddresses`.
|
|
65
|
+
// Consumed by the codegen of the event's `clientAddressFilter`.
|
|
66
|
+
addressFilterParamGroups: array<array<string>>,
|
|
63
67
|
// `_gte` from the top-level `block` filter of the user's `where`,
|
|
64
68
|
// resolved at build time (per-chain via the `probeChainId`). The
|
|
65
69
|
// caller uses this to override the per-event `startBlock` — a
|
|
@@ -151,6 +155,70 @@ let makeDetectionChainArg = (
|
|
|
151
155
|
chainObj
|
|
152
156
|
}
|
|
153
157
|
|
|
158
|
+
// Sentinel returned by `chain.<Contract>.addresses` during detection. A Proxy
|
|
159
|
+
// whose traps throw on any access, so the only non-throwing use is passing it
|
|
160
|
+
// straight through as a param filter value — which `extractAddressFilterGroups`
|
|
161
|
+
// then finds by identity. Misuse (spread/map/index/...) fails loud at the site.
|
|
162
|
+
let makeAddressesProbe: (
|
|
163
|
+
~contractName: string,
|
|
164
|
+
) => array<Address.t> = %raw(`function (contractName) {
|
|
165
|
+
var trap = function () {
|
|
166
|
+
throw new Error(
|
|
167
|
+
'Invalid where configuration for "' + contractName +
|
|
168
|
+
'": chain.' + contractName + '.addresses must be passed directly as an indexed-param ' +
|
|
169
|
+
'filter value (e.g. { params: { to: chain.' + contractName + '.addresses } }). ' +
|
|
170
|
+
'It cannot be spread, mapped, indexed, or otherwise transformed.'
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
return new Proxy([], {get: trap});
|
|
174
|
+
}`)
|
|
175
|
+
|
|
176
|
+
// Find which indexed params the probed `where` result assigned the Proxy to
|
|
177
|
+
// (DNF: object => one AND-group, array => OR of groups), neutralizing each match
|
|
178
|
+
// to `[]` so later passes can't touch the throwing Proxy. Throws when the
|
|
179
|
+
// callback read the addresses but didn't use them as a param filter value, since
|
|
180
|
+
// the caller only invokes this once it knows the addresses were read.
|
|
181
|
+
let extractAddressFilterGroupsOrThrow = (
|
|
182
|
+
result: JSON.t,
|
|
183
|
+
~probe: array<Address.t>,
|
|
184
|
+
~contractName: string,
|
|
185
|
+
): array<array<string>> => {
|
|
186
|
+
let groups = []
|
|
187
|
+
let scanGroup = (paramsObj: dict<JSON.t>) => {
|
|
188
|
+
let names = []
|
|
189
|
+
paramsObj->Utils.Dict.forEachWithKey((value, key) => {
|
|
190
|
+
if value === probe->(Utils.magic: array<Address.t> => JSON.t) {
|
|
191
|
+
names->Array.push(key)->ignore
|
|
192
|
+
paramsObj->Dict.set(key, []->(Utils.magic: array<unknown> => JSON.t))
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
if names->Utils.Array.isEmpty->not {
|
|
196
|
+
groups->Array.push(names)->ignore
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
switch result {
|
|
200
|
+
| Object(obj) =>
|
|
201
|
+
switch obj->Dict.get("params") {
|
|
202
|
+
| Some(Object(p)) => scanGroup(p)
|
|
203
|
+
| Some(Array(arr)) =>
|
|
204
|
+
arr->Array.forEach(item =>
|
|
205
|
+
switch item {
|
|
206
|
+
| Object(p) => scanGroup(p)
|
|
207
|
+
| _ => ()
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
| _ => ()
|
|
211
|
+
}
|
|
212
|
+
| _ => ()
|
|
213
|
+
}
|
|
214
|
+
if groups->Utils.Array.isEmpty {
|
|
215
|
+
JsError.throwWithMessage(
|
|
216
|
+
`Invalid where configuration for ${contractName}. The callback reads \`chain.${contractName}.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.${contractName}.addresses } }.`,
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
groups
|
|
220
|
+
}
|
|
221
|
+
|
|
154
222
|
let parseEventFiltersOrThrow = {
|
|
155
223
|
let emptyTopics = []
|
|
156
224
|
let noopGetter = _ => emptyTopics
|
|
@@ -167,6 +235,7 @@ let parseEventFiltersOrThrow = {
|
|
|
167
235
|
~topic3=noopGetter,
|
|
168
236
|
): parsedEventFilters => {
|
|
169
237
|
let filterByAddresses = ref(false)
|
|
238
|
+
let addressFilterParamGroups = ref([])
|
|
170
239
|
let startBlock = ref(None)
|
|
171
240
|
let topic0 = [sighash->EvmTypes.Hex.fromStringUnsafe]
|
|
172
241
|
let default = {
|
|
@@ -286,24 +355,23 @@ let parseEventFiltersOrThrow = {
|
|
|
286
355
|
// `startBlock` (from `where.block`) for this chain — a second
|
|
287
356
|
// invocation would risk observing different state for callbacks
|
|
288
357
|
// that close over mutable references.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
startBlock := extractStartBlock(~onEventBlockFilterSchema, ~contractName, result)
|
|
305
|
-
| None => ()
|
|
358
|
+
// A misused Proxy (or any throw from the callback) propagates as-is —
|
|
359
|
+
// the Proxy's guidance message surfaces without wrapping.
|
|
360
|
+
let addressesProbe = makeAddressesProbe(~contractName)
|
|
361
|
+
let chain = makeDetectionChainArg(
|
|
362
|
+
~contractName,
|
|
363
|
+
~chainId=probeChainId,
|
|
364
|
+
~getAddresses=() => {
|
|
365
|
+
filterByAddresses := true
|
|
366
|
+
addressesProbe
|
|
367
|
+
},
|
|
368
|
+
)
|
|
369
|
+
let probedResult = fn({chain: chain->Obj.magic})
|
|
370
|
+
if filterByAddresses.contents {
|
|
371
|
+
addressFilterParamGroups :=
|
|
372
|
+
extractAddressFilterGroupsOrThrow(probedResult, ~probe=addressesProbe, ~contractName)
|
|
306
373
|
}
|
|
374
|
+
startBlock := extractStartBlock(~onEventBlockFilterSchema, ~contractName, probedResult)
|
|
307
375
|
if filterByAddresses.contents {
|
|
308
376
|
chain => Internal.Dynamic(
|
|
309
377
|
addresses => {
|
|
@@ -343,6 +411,7 @@ let parseEventFiltersOrThrow = {
|
|
|
343
411
|
{
|
|
344
412
|
getEventFiltersOrThrow,
|
|
345
413
|
filterByAddresses: filterByAddresses.contents,
|
|
414
|
+
addressFilterParamGroups: addressFilterParamGroups.contents,
|
|
346
415
|
startBlock: startBlock.contents,
|
|
347
416
|
}
|
|
348
417
|
}
|
package/src/LogSelection.res.mjs
CHANGED
|
@@ -121,6 +121,58 @@ function makeDetectionChainArg(contractName, chainId, getAddresses) {
|
|
|
121
121
|
return chainObj;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
let makeAddressesProbe = (function (contractName) {
|
|
125
|
+
var trap = function () {
|
|
126
|
+
throw new Error(
|
|
127
|
+
'Invalid where configuration for "' + contractName +
|
|
128
|
+
'": chain.' + contractName + '.addresses must be passed directly as an indexed-param ' +
|
|
129
|
+
'filter value (e.g. { params: { to: chain.' + contractName + '.addresses } }). ' +
|
|
130
|
+
'It cannot be spread, mapped, indexed, or otherwise transformed.'
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
return new Proxy([], {get: trap});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
function extractAddressFilterGroupsOrThrow(result, probe, contractName) {
|
|
137
|
+
let groups = [];
|
|
138
|
+
let scanGroup = paramsObj => {
|
|
139
|
+
let names = [];
|
|
140
|
+
Utils.Dict.forEachWithKey(paramsObj, (value, key) => {
|
|
141
|
+
if (value === probe) {
|
|
142
|
+
names.push(key);
|
|
143
|
+
paramsObj[key] = [];
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
if (!Utils.$$Array.isEmpty(names)) {
|
|
148
|
+
groups.push(names);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
if (typeof result === "object" && result !== null && !Array.isArray(result)) {
|
|
153
|
+
let match = result["params"];
|
|
154
|
+
if (match !== undefined) {
|
|
155
|
+
if (Array.isArray(match)) {
|
|
156
|
+
match.forEach(item => {
|
|
157
|
+
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
158
|
+
return scanGroup(item);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
switch (typeof match) {
|
|
163
|
+
case "object" :
|
|
164
|
+
scanGroup(match);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (Utils.$$Array.isEmpty(groups)) {
|
|
171
|
+
Stdlib_JsError.throwWithMessage(`Invalid where configuration for ` + contractName + `. The callback reads \`chain.` + contractName + `.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.` + contractName + `.addresses } }.`);
|
|
172
|
+
}
|
|
173
|
+
return groups;
|
|
174
|
+
}
|
|
175
|
+
|
|
124
176
|
let emptyTopics = [];
|
|
125
177
|
|
|
126
178
|
function noopGetter(param) {
|
|
@@ -134,6 +186,7 @@ function parseEventFiltersOrThrow(eventFilters, sighash, params, contractName, p
|
|
|
134
186
|
let filterByAddresses = {
|
|
135
187
|
contents: false
|
|
136
188
|
};
|
|
189
|
+
let addressFilterParamGroups = [];
|
|
137
190
|
let startBlock;
|
|
138
191
|
let topic0 = [sighash];
|
|
139
192
|
let $$default = {
|
|
@@ -208,21 +261,18 @@ function parseEventFiltersOrThrow(eventFilters, sighash, params, contractName, p
|
|
|
208
261
|
let getEventFiltersOrThrow;
|
|
209
262
|
if (eventFilters !== undefined) {
|
|
210
263
|
if (typeof eventFilters === "function") {
|
|
211
|
-
let
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
probedResult = undefined;
|
|
222
|
-
}
|
|
223
|
-
if (probedResult !== undefined) {
|
|
224
|
-
startBlock = extractStartBlock(probedResult, onEventBlockFilterSchema, contractName);
|
|
264
|
+
let addressesProbe = makeAddressesProbe(contractName);
|
|
265
|
+
let chain = makeDetectionChainArg(contractName, probeChainId, () => {
|
|
266
|
+
filterByAddresses.contents = true;
|
|
267
|
+
return addressesProbe;
|
|
268
|
+
});
|
|
269
|
+
let probedResult = eventFilters({
|
|
270
|
+
chain: chain
|
|
271
|
+
});
|
|
272
|
+
if (filterByAddresses.contents) {
|
|
273
|
+
addressFilterParamGroups = extractAddressFilterGroupsOrThrow(probedResult, addressesProbe, contractName);
|
|
225
274
|
}
|
|
275
|
+
startBlock = extractStartBlock(probedResult, onEventBlockFilterSchema, contractName);
|
|
226
276
|
getEventFiltersOrThrow = filterByAddresses.contents ? chain => ({
|
|
227
277
|
TAG: "Dynamic",
|
|
228
278
|
_0: addresses => {
|
|
@@ -258,6 +308,7 @@ function parseEventFiltersOrThrow(eventFilters, sighash, params, contractName, p
|
|
|
258
308
|
return {
|
|
259
309
|
getEventFiltersOrThrow: getEventFiltersOrThrow,
|
|
260
310
|
filterByAddresses: filterByAddresses.contents,
|
|
311
|
+
addressFilterParamGroups: addressFilterParamGroups,
|
|
261
312
|
startBlock: startBlock
|
|
262
313
|
};
|
|
263
314
|
}
|
|
@@ -272,6 +323,8 @@ export {
|
|
|
272
323
|
extractStartBlock,
|
|
273
324
|
makeChainArg,
|
|
274
325
|
makeDetectionChainArg,
|
|
326
|
+
makeAddressesProbe,
|
|
327
|
+
extractAddressFilterGroupsOrThrow,
|
|
275
328
|
parseEventFiltersOrThrow,
|
|
276
329
|
}
|
|
277
330
|
/* eventBlockRangeSchema Not a pure module */
|
package/src/Logging.res
CHANGED
|
@@ -155,46 +155,11 @@ let createChildFrom = (~logger: t, ~params: 'a) => {
|
|
|
155
155
|
logger->child(params->createChildParams)
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
let getItemLogger = {
|
|
159
|
-
let cacheKey = "_logger"
|
|
160
|
-
(item: Internal.item) => {
|
|
161
|
-
switch item
|
|
162
|
-
->(Utils.magic: Internal.item => dict<Pino.t>)
|
|
163
|
-
->Utils.Dict.dangerouslyGetNonOption(cacheKey) {
|
|
164
|
-
| Some(l) => l
|
|
165
|
-
| None => {
|
|
166
|
-
let l = getLogger()->child(
|
|
167
|
-
switch item {
|
|
168
|
-
| Event({eventConfig, chain, blockNumber, logIndex, event}) =>
|
|
169
|
-
{
|
|
170
|
-
"contractName": eventConfig.contractName,
|
|
171
|
-
"eventName": eventConfig.name,
|
|
172
|
-
"chainId": chain->ChainMap.Chain.toChainId,
|
|
173
|
-
"block": blockNumber,
|
|
174
|
-
"logIndex": logIndex,
|
|
175
|
-
"address": (event->Internal.toGenericEvent).srcAddress,
|
|
176
|
-
}->createChildParams
|
|
177
|
-
| Block({blockNumber, onBlockConfig}) =>
|
|
178
|
-
{
|
|
179
|
-
"onBlock": onBlockConfig.name,
|
|
180
|
-
"chainId": onBlockConfig.chainId,
|
|
181
|
-
"block": blockNumber,
|
|
182
|
-
}->createChildParams
|
|
183
|
-
},
|
|
184
|
-
)
|
|
185
|
-
item->(Utils.magic: Internal.item => dict<Pino.t>)->Dict.set(cacheKey, l)
|
|
186
|
-
l
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
158
|
@inline
|
|
193
|
-
let
|
|
159
|
+
let logAtLevel = (logger: t, level: Pino.logLevel, message: string, ~params=?) => {
|
|
194
160
|
(
|
|
195
|
-
|
|
196
|
-
->
|
|
197
|
-
->(Utils.magic: Pino.t => dict<(option<'a>, string) => unit>)
|
|
161
|
+
logger
|
|
162
|
+
->(Utils.magic: t => dict<(option<'a>, string) => unit>)
|
|
198
163
|
->Dict.getUnsafe((level :> string))
|
|
199
164
|
)(params, message)
|
|
200
165
|
}
|
|
@@ -207,11 +172,13 @@ let noopLogger: Envio.logger = {
|
|
|
207
172
|
errorWithExn: (_message: string, _exn) => (),
|
|
208
173
|
}
|
|
209
174
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
175
|
+
// Wrap a (child) logger as the user-facing `context.log`, routing through the
|
|
176
|
+
// custom `u*` levels. The caller builds the per-item logger via the ecosystem.
|
|
177
|
+
let userLogger = (logger: t): Envio.logger => {
|
|
178
|
+
info: (message: string, ~params=?) => logger->logAtLevel(#uinfo, message, ~params?),
|
|
179
|
+
debug: (message: string, ~params=?) => logger->logAtLevel(#udebug, message, ~params?),
|
|
180
|
+
warn: (message: string, ~params=?) => logger->logAtLevel(#uwarn, message, ~params?),
|
|
181
|
+
error: (message: string, ~params=?) => logger->logAtLevel(#uerror, message, ~params?),
|
|
215
182
|
errorWithExn: (message: string, exn) =>
|
|
216
|
-
|
|
183
|
+
logger->logAtLevel(#uerror, message, ~params={"err": exn->Utils.prettifyExn}),
|
|
217
184
|
}
|