envio 3.3.0-alpha.1 → 3.3.0-alpha.11
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/evm.schema.json +10 -0
- package/index.d.ts +45 -25
- 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 +10 -9
- package/src/Address.res +5 -2
- package/src/Address.res.mjs +3 -1
- package/src/Api.res +1 -1
- package/src/Api.res.mjs +1 -1
- package/src/Batch.res +0 -6
- package/src/Batch.res.mjs +1 -12
- package/src/BatchProcessing.res +7 -12
- package/src/BatchProcessing.res.mjs +8 -7
- package/src/Bin.res +3 -0
- package/src/Bin.res.mjs +4 -0
- package/src/ChainFetching.res +53 -16
- package/src/ChainFetching.res.mjs +37 -19
- package/src/ChainMetadata.res +1 -11
- package/src/ChainMetadata.res.mjs +1 -10
- package/src/ChainState.res +630 -161
- package/src/ChainState.res.mjs +438 -101
- package/src/ChainState.resi +77 -4
- package/src/Config.res +70 -38
- package/src/Config.res.mjs +52 -12
- package/src/ContractRegisterContext.res +2 -12
- package/src/ContractRegisterContext.res.mjs +3 -5
- package/src/Core.res +34 -3
- package/src/Core.res.mjs +12 -0
- package/src/CrossChainState.res +130 -89
- package/src/CrossChainState.res.mjs +66 -61
- package/src/CrossChainState.resi +1 -1
- package/src/Ecosystem.res +5 -6
- package/src/Ecosystem.res.mjs +3 -3
- package/src/Envio.res +17 -11
- package/src/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +191 -127
- package/src/EventConfigBuilder.res.mjs +116 -66
- package/src/EventProcessing.res +77 -28
- package/src/EventProcessing.res.mjs +56 -29
- package/src/ExitOnCaughtUp.res +10 -2
- package/src/ExitOnCaughtUp.res.mjs +10 -1
- package/src/FetchState.res +684 -415
- package/src/FetchState.res.mjs +505 -486
- package/src/HandlerLoader.res +8 -104
- package/src/HandlerLoader.res.mjs +2 -81
- package/src/HandlerRegister.res +520 -144
- package/src/HandlerRegister.res.mjs +286 -133
- package/src/HandlerRegister.resi +24 -8
- package/src/IndexerState.res +10 -3
- package/src/IndexerState.res.mjs +11 -4
- package/src/IndexerState.resi +2 -1
- package/src/IndexingAddresses.res +108 -0
- package/src/IndexingAddresses.res.mjs +101 -0
- package/src/IndexingAddresses.resi +34 -0
- package/src/Internal.res +166 -48
- package/src/Internal.res.mjs +30 -2
- package/src/LoadLayer.res +5 -5
- package/src/LoadLayer.res.mjs +6 -6
- package/src/LogSelection.res +92 -217
- package/src/LogSelection.res.mjs +95 -184
- package/src/Main.res +71 -236
- package/src/Main.res.mjs +60 -145
- package/src/Metrics.res +74 -0
- package/src/Metrics.res.mjs +75 -0
- package/src/PgStorage.res +4 -4
- package/src/PgStorage.res.mjs +5 -5
- package/src/Prometheus.res +10 -63
- package/src/Prometheus.res.mjs +124 -171
- package/src/PruneStaleHistory.res +2 -2
- package/src/PruneStaleHistory.res.mjs +3 -3
- package/src/RawEvent.res +7 -2
- package/src/RawEvent.res.mjs +4 -4
- package/src/Rollback.res +3 -3
- package/src/Rollback.res.mjs +4 -4
- package/src/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- package/src/SimulateDeadInputTracker.res +85 -0
- package/src/SimulateDeadInputTracker.res.mjs +73 -0
- package/src/SimulateDeadInputTracker.resi +12 -0
- package/src/SimulateItems.res +128 -28
- package/src/SimulateItems.res.mjs +82 -40
- package/src/TestIndexer.res +55 -41
- package/src/TestIndexer.res.mjs +39 -32
- package/src/TopicFilter.res +1 -25
- package/src/TopicFilter.res.mjs +0 -74
- package/src/bindings/Performance.res +7 -0
- package/src/bindings/Performance.res.mjs +21 -0
- package/src/bindings/Performance.resi +7 -0
- package/src/bindings/Viem.res +0 -46
- package/src/bindings/Viem.res.mjs +1 -43
- package/src/sources/BlockStore.res +46 -0
- package/src/sources/BlockStore.res.mjs +24 -0
- package/src/sources/EventRouter.res +31 -49
- package/src/sources/EventRouter.res.mjs +12 -25
- package/src/sources/Evm.res +83 -72
- package/src/sources/Evm.res.mjs +53 -69
- package/src/sources/EvmChain.res +7 -34
- package/src/sources/EvmChain.res.mjs +6 -27
- package/src/sources/EvmRpcClient.res +71 -5
- package/src/sources/EvmRpcClient.res.mjs +13 -4
- package/src/sources/FieldMask.res +39 -0
- package/src/sources/FieldMask.res.mjs +42 -0
- package/src/sources/Fuel.res +9 -6
- package/src/sources/Fuel.res.mjs +5 -10
- package/src/sources/HyperFuelSource.res +46 -50
- package/src/sources/HyperFuelSource.res.mjs +64 -54
- package/src/sources/HyperSync.res +45 -42
- package/src/sources/HyperSync.res.mjs +68 -60
- package/src/sources/HyperSync.resi +14 -4
- package/src/sources/HyperSyncClient.res +107 -119
- package/src/sources/HyperSyncClient.res.mjs +36 -31
- package/src/sources/HyperSyncHeightStream.res +1 -8
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
- package/src/sources/HyperSyncSource.res +60 -254
- package/src/sources/HyperSyncSource.res.mjs +57 -180
- package/src/sources/Rpc.res +15 -79
- package/src/sources/Rpc.res.mjs +25 -101
- package/src/sources/RpcSource.res +221 -514
- package/src/sources/RpcSource.res.mjs +216 -411
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +44 -20
- package/src/sources/SimulateSource.res.mjs +41 -14
- package/src/sources/Source.res +34 -6
- package/src/sources/SourceManager.res +77 -19
- package/src/sources/SourceManager.res.mjs +63 -21
- package/src/sources/SourceManager.resi +10 -0
- package/src/sources/Svm.res +31 -16
- package/src/sources/Svm.res.mjs +36 -13
- package/src/sources/SvmHyperSyncClient.res +5 -32
- package/src/sources/SvmHyperSyncSource.res +159 -134
- package/src/sources/SvmHyperSyncSource.res.mjs +164 -129
- package/src/sources/TransactionStore.res +49 -0
- package/src/sources/TransactionStore.res.mjs +30 -0
- package/src/tui/Tui.res +13 -16
- package/src/tui/Tui.res.mjs +12 -14
- package/svm.schema.json +49 -37
- package/src/bindings/Hrtime.res +0 -58
- package/src/bindings/Hrtime.res.mjs +0 -90
- package/src/bindings/Hrtime.resi +0 -30
package/src/RawEvent.res.mjs
CHANGED
|
@@ -18,10 +18,10 @@ function convertFieldsToJson(fields) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function make(eventItem, block, transaction, params, srcAddress, cleanUpRawEventFieldsInPlace) {
|
|
21
|
+
function make(eventItem, block, transaction, params, srcAddress, blockHash, blockTimestamp, cleanUpRawEventFieldsInPlace) {
|
|
22
22
|
let logIndex = eventItem.logIndex;
|
|
23
23
|
let blockNumber = eventItem.blockNumber;
|
|
24
|
-
let eventConfig = eventItem.eventConfig;
|
|
24
|
+
let eventConfig = eventItem.onEventRegistration.eventConfig;
|
|
25
25
|
let eventId = EventUtils.packEventIndex(blockNumber, logIndex);
|
|
26
26
|
let blockFields = convertFieldsToJson(block);
|
|
27
27
|
let transactionFields = convertFieldsToJson(transaction);
|
|
@@ -36,8 +36,8 @@ function make(eventItem, block, transaction, params, srcAddress, cleanUpRawEvent
|
|
|
36
36
|
block_number: blockNumber,
|
|
37
37
|
log_index: logIndex,
|
|
38
38
|
src_address: srcAddress,
|
|
39
|
-
block_hash:
|
|
40
|
-
block_timestamp:
|
|
39
|
+
block_hash: blockHash,
|
|
40
|
+
block_timestamp: blockTimestamp,
|
|
41
41
|
block_fields: blockFields,
|
|
42
42
|
transaction_fields: transactionFields,
|
|
43
43
|
params: params$2
|
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=
|
|
21
|
+
~knownHeight=chainState->ChainState.knownHeight,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
switch scannedBlockNumbers {
|
|
@@ -94,7 +94,7 @@ and executeRollback = async (
|
|
|
94
94
|
~scheduleFetch,
|
|
95
95
|
~scheduleProcessing,
|
|
96
96
|
) => {
|
|
97
|
-
let startTime =
|
|
97
|
+
let startTime = Performance.now()
|
|
98
98
|
|
|
99
99
|
let chainState = state->IndexerState.getChainState(~chain=reorgChain)
|
|
100
100
|
|
|
@@ -194,7 +194,7 @@ and executeRollback = async (
|
|
|
194
194
|
"targetCheckpointId": rollbackTargetCheckpointId,
|
|
195
195
|
})
|
|
196
196
|
Prometheus.RollbackSuccess.increment(
|
|
197
|
-
~timeSeconds=
|
|
197
|
+
~timeSeconds=Performance.secondsSince(startTime),
|
|
198
198
|
~rollbackedProcessedEvents=rollbackedProcessedEvents.contents,
|
|
199
199
|
)
|
|
200
200
|
|
package/src/Rollback.res.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Utils from "./Utils.res.mjs";
|
|
4
|
-
import * as Hrtime from "./bindings/Hrtime.res.mjs";
|
|
5
4
|
import * as Logging from "./Logging.res.mjs";
|
|
6
5
|
import * as Writing from "./Writing.res.mjs";
|
|
7
6
|
import * as ChainState from "./ChainState.res.mjs";
|
|
8
7
|
import * as Prometheus from "./Prometheus.res.mjs";
|
|
8
|
+
import * as Performance from "./bindings/Performance.res.mjs";
|
|
9
9
|
import * as IndexerState from "./IndexerState.res.mjs";
|
|
10
10
|
import * as Stdlib_Float from "@rescript/runtime/lib/es6/Stdlib_Float.js";
|
|
11
11
|
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
@@ -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.
|
|
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
|
}
|
|
@@ -32,7 +32,7 @@ async function getLastKnownValidBlock(chainState, reorgBlockNumber, isRealtime)
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, scheduleFetch, scheduleProcessing) {
|
|
35
|
-
let startTime =
|
|
35
|
+
let startTime = Performance.now();
|
|
36
36
|
let chainState = IndexerState.getChainState(state, reorgChain);
|
|
37
37
|
let logger = Logging.createChildFrom(ChainState.logger(chainState), {
|
|
38
38
|
action: "Rollback",
|
|
@@ -70,7 +70,7 @@ async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, sch
|
|
|
70
70
|
beforeCheckpointId: IndexerState.committedCheckpointId(state),
|
|
71
71
|
targetCheckpointId: rollbackTargetCheckpointId
|
|
72
72
|
});
|
|
73
|
-
Prometheus.RollbackSuccess.increment(
|
|
73
|
+
Prometheus.RollbackSuccess.increment(Performance.secondsSince(startTime), rollbackedProcessedEvents);
|
|
74
74
|
IndexerState.completeRollback(state, eventsProcessedDiffByChain);
|
|
75
75
|
scheduleFetch();
|
|
76
76
|
return scheduleProcessing();
|
package/src/RollbackCommit.res
CHANGED
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
type args = {chainId: int, rollbackToBlock: int}
|
|
7
7
|
type callback = args => promise<unit>
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// Lives in the process-wide `EnvioGlobal` record so callbacks registered
|
|
10
|
+
// through a duplicate envio module instance still fire.
|
|
11
|
+
let callbacks =
|
|
12
|
+
EnvioGlobal.value.rollbackCommitCallbacks->(Utils.magic: array<unknown> => array<callback>)
|
|
10
13
|
|
|
11
14
|
let register = (callback: callback) => {
|
|
12
15
|
callbacks->Array.push(callback)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
4
|
+
import * as EnvioGlobal from "./EnvioGlobal.res.mjs";
|
|
4
5
|
|
|
5
|
-
let callbacks =
|
|
6
|
+
let callbacks = EnvioGlobal.value.rollbackCommitCallbacks;
|
|
6
7
|
|
|
7
8
|
function register(callback) {
|
|
8
9
|
callbacks.push(callback);
|
|
@@ -32,4 +33,4 @@ export {
|
|
|
32
33
|
register,
|
|
33
34
|
fire,
|
|
34
35
|
}
|
|
35
|
-
/*
|
|
36
|
+
/* EnvioGlobal Not a pure module */
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Lives on IndexerState and is fed each processed batch, so ChainState and the
|
|
2
|
+
// fetch loop carry no simulate-specific state.
|
|
3
|
+
|
|
4
|
+
// Match a provided item to a processed one by its (chain, block, logIndex)
|
|
5
|
+
// coordinate rather than object identity, so matching survives any copy or
|
|
6
|
+
// transform of the item between the source and the batch.
|
|
7
|
+
let itemKey = (item: Internal.item): string =>
|
|
8
|
+
switch item {
|
|
9
|
+
| Internal.Event({chain, blockNumber, logIndex}) =>
|
|
10
|
+
`${chain
|
|
11
|
+
->ChainMap.Chain.toChainId
|
|
12
|
+
->Int.toString}:${blockNumber->Int.toString}:${logIndex->Int.toString}`
|
|
13
|
+
| Internal.Block(_) => ""
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// `index` is the item's position in its chain's `simulate` array, reported back
|
|
17
|
+
// so a user finds it without echoing its fields.
|
|
18
|
+
type entry = {chainId: int, index: int, key: string}
|
|
19
|
+
|
|
20
|
+
type t = {mutable unprocessed: array<entry>}
|
|
21
|
+
|
|
22
|
+
let makeFromConfig = (config: Config.t): option<t> => {
|
|
23
|
+
let entries =
|
|
24
|
+
config.chainMap
|
|
25
|
+
->ChainMap.values
|
|
26
|
+
->Array.flatMap(chainConfig =>
|
|
27
|
+
switch chainConfig.sourceConfig {
|
|
28
|
+
| Config.CustomSources(sources) =>
|
|
29
|
+
sources->Array.flatMap(source =>
|
|
30
|
+
source.simulateItems
|
|
31
|
+
->Option.getOr([])
|
|
32
|
+
->Array.mapWithIndex(
|
|
33
|
+
(item, index) => {
|
|
34
|
+
chainId: chainConfig.id,
|
|
35
|
+
index,
|
|
36
|
+
key: itemKey(item),
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
| _ => []
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
switch entries {
|
|
44
|
+
| [] => None
|
|
45
|
+
| _ => Some({unprocessed: entries})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let recordProcessed = (t: t, ~batch: Batch.t) => {
|
|
50
|
+
let processedKeys = batch.items->Array.map(itemKey)->Utils.Set.fromArray
|
|
51
|
+
t.unprocessed = t.unprocessed->Array.filter(entry => !(processedKeys->Utils.Set.has(entry.key)))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Unrouted item indices grouped by chain, in the order chains were first seen.
|
|
55
|
+
let unroutedByChain = (t: t): array<(int, array<int>)> => {
|
|
56
|
+
let indicesByChain = Dict.make()
|
|
57
|
+
let chainOrder = []
|
|
58
|
+
t.unprocessed->Array.forEach(entry => {
|
|
59
|
+
let key = entry.chainId->Int.toString
|
|
60
|
+
if indicesByChain->Dict.get(key)->Option.isNone {
|
|
61
|
+
chainOrder->Array.push(entry.chainId)->ignore
|
|
62
|
+
}
|
|
63
|
+
indicesByChain->Utils.Dict.push(key, entry.index)
|
|
64
|
+
})
|
|
65
|
+
chainOrder->Array.map(chainId => (chainId, indicesByChain->Dict.getUnsafe(chainId->Int.toString)))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let failureMessage = (t: t): option<string> =>
|
|
69
|
+
switch t->unroutedByChain {
|
|
70
|
+
| [] => None
|
|
71
|
+
| byChain =>
|
|
72
|
+
let count = byChain->Array.reduce(0, (acc, (_chainId, indices)) => acc + indices->Array.length)
|
|
73
|
+
let itemWord = count === 1 ? "item" : "items"
|
|
74
|
+
let lines =
|
|
75
|
+
byChain
|
|
76
|
+
->Array.map(((chainId, indices)) =>
|
|
77
|
+
` - chain ${chainId->Int.toString}: ${indices
|
|
78
|
+
->Array.map(index => index->Int.toString)
|
|
79
|
+
->Array.join(", ")}`
|
|
80
|
+
)
|
|
81
|
+
->Array.join("\n")
|
|
82
|
+
Some(
|
|
83
|
+
`simulate: ${count->Int.toString} ${itemWord} you passed to simulate never reached a handler, so nothing ran for them. Each was filtered out before the handler — usually a non-wildcard srcAddress that isn't indexed for the contract, or a where/block filter that excluded the event. Unrouted items, by index in each chain's simulate array:\n${lines}`,
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as ChainMap from "./ChainMap.res.mjs";
|
|
5
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
|
|
8
|
+
function itemKey(item) {
|
|
9
|
+
if (item.kind === 0) {
|
|
10
|
+
return item.chain.toString() + `:` + item.blockNumber.toString() + `:` + item.logIndex.toString();
|
|
11
|
+
} else {
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeFromConfig(config) {
|
|
17
|
+
let entries = ChainMap.values(config.chainMap).flatMap(chainConfig => {
|
|
18
|
+
let sources = chainConfig.sourceConfig;
|
|
19
|
+
if (sources.TAG === "CustomSources") {
|
|
20
|
+
return sources._0.flatMap(source => Stdlib_Option.getOr(source.simulateItems, []).map((item, index) => ({
|
|
21
|
+
chainId: chainConfig.id,
|
|
22
|
+
index: index,
|
|
23
|
+
key: itemKey(item)
|
|
24
|
+
})));
|
|
25
|
+
} else {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (entries.length !== 0) {
|
|
30
|
+
return {
|
|
31
|
+
unprocessed: entries
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function recordProcessed(t, batch) {
|
|
37
|
+
let processedKeys = new Set(batch.items.map(itemKey));
|
|
38
|
+
t.unprocessed = t.unprocessed.filter(entry => !processedKeys.has(entry.key));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function unroutedByChain(t) {
|
|
42
|
+
let indicesByChain = {};
|
|
43
|
+
let chainOrder = [];
|
|
44
|
+
t.unprocessed.forEach(entry => {
|
|
45
|
+
let key = entry.chainId.toString();
|
|
46
|
+
if (Stdlib_Option.isNone(indicesByChain[key])) {
|
|
47
|
+
chainOrder.push(entry.chainId);
|
|
48
|
+
}
|
|
49
|
+
Utils.Dict.push(indicesByChain, key, entry.index);
|
|
50
|
+
});
|
|
51
|
+
return chainOrder.map(chainId => [
|
|
52
|
+
chainId,
|
|
53
|
+
indicesByChain[chainId.toString()]
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function failureMessage(t) {
|
|
58
|
+
let byChain = unroutedByChain(t);
|
|
59
|
+
if (byChain.length === 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let count = Stdlib_Array.reduce(byChain, 0, (acc, param) => acc + param[1].length | 0);
|
|
63
|
+
let itemWord = count === 1 ? "item" : "items";
|
|
64
|
+
let lines = byChain.map(param => ` - chain ` + param[0].toString() + `: ` + param[1].map(index => index.toString()).join(", ")).join("\n");
|
|
65
|
+
return `simulate: ` + count.toString() + ` ` + itemWord + ` you passed to simulate never reached a handler, so nothing ran for them. Each was filtered out before the handler — usually a non-wildcard srcAddress that isn't indexed for the contract, or a where/block filter that excluded the event. Unrouted items, by index in each chain's simulate array:\n` + lines;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
makeFromConfig,
|
|
70
|
+
recordProcessed,
|
|
71
|
+
failureMessage,
|
|
72
|
+
}
|
|
73
|
+
/* Utils Not a pure module */
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type t
|
|
2
|
+
|
|
3
|
+
// Builds a tracker from the run's config, seeded with every item each simulate
|
|
4
|
+
// source provided. None when no chain has a simulate source.
|
|
5
|
+
let makeFromConfig: Config.t => option<t>
|
|
6
|
+
|
|
7
|
+
// Drop the batch's items from the not-yet-routed set, matched by event coordinate.
|
|
8
|
+
let recordProcessed: (t, ~batch: Batch.t) => unit
|
|
9
|
+
|
|
10
|
+
// The error to fail the run with when items never reached a handler, listing them
|
|
11
|
+
// by chain and index within that chain's `simulate` array. None when all routed.
|
|
12
|
+
let failureMessage: t => option<string>
|
package/src/SimulateItems.res
CHANGED
|
@@ -195,9 +195,57 @@ let findEventConfig = (~config: Config.t, ~contractName: string, ~eventName: str
|
|
|
195
195
|
found.contents
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
let
|
|
199
|
-
|
|
198
|
+
let dummySrcAddress = Address.unsafeFromString("0x0000000000000000000000000000000000000000")
|
|
199
|
+
|
|
200
|
+
// First address configured for `contractName` on the simulated chain. Only the
|
|
201
|
+
// simulated chain is consulted — a contract's address on another chain has no
|
|
202
|
+
// meaning here.
|
|
203
|
+
let firstContractAddress = (~chainConfig: Config.chain, ~contractName: string): option<
|
|
204
|
+
Address.t,
|
|
200
205
|
> => {
|
|
206
|
+
let found = ref(None)
|
|
207
|
+
chainConfig.contracts->Array.forEach(contract => {
|
|
208
|
+
if found.contents->Option.isNone && contract.name === contractName {
|
|
209
|
+
found := contract.addresses->Array.get(0)
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
found.contents
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let deriveSrcAddress = (
|
|
216
|
+
~providedSrcAddress: option<Address.t>,
|
|
217
|
+
~eventConfig: Internal.eventConfig,
|
|
218
|
+
~chainConfig: Config.chain,
|
|
219
|
+
~config: Config.t,
|
|
220
|
+
): Address.t => {
|
|
221
|
+
switch providedSrcAddress {
|
|
222
|
+
// Canonicalize to the configured casing; the fallback addresses below already
|
|
223
|
+
// come from the parsed config and need no normalization. Relaxed (not
|
|
224
|
+
// Config.normalizeUserAddress) so test placeholders like "0xfoo" are allowed.
|
|
225
|
+
| Some(addr) => config->Config.normalizeSimulateAddress(addr)
|
|
226
|
+
| None =>
|
|
227
|
+
if (
|
|
228
|
+
HandlerRegister.isWildcard(
|
|
229
|
+
~contractName=eventConfig.contractName,
|
|
230
|
+
~eventName=eventConfig.name,
|
|
231
|
+
)
|
|
232
|
+
) {
|
|
233
|
+
dummySrcAddress
|
|
234
|
+
} else {
|
|
235
|
+
switch firstContractAddress(~chainConfig, ~contractName=eventConfig.contractName) {
|
|
236
|
+
| Some(addr) => addr
|
|
237
|
+
| None => dummySrcAddress
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let parse = (
|
|
244
|
+
~simulateItems: array<JSON.t>,
|
|
245
|
+
~config: Config.t,
|
|
246
|
+
~chainConfig: Config.chain,
|
|
247
|
+
~onEventRegistrations: array<Internal.onEventRegistration>,
|
|
248
|
+
): array<Internal.item> => {
|
|
201
249
|
let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)
|
|
202
250
|
let chainId = chainConfig.id
|
|
203
251
|
let startBlock = chainConfig.startBlock
|
|
@@ -205,8 +253,11 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
|
|
|
205
253
|
let currentLogIndex = ref(0)
|
|
206
254
|
|
|
207
255
|
let items = []
|
|
256
|
+
// Coordinate "block:logIndex" -> the index of the first item that claimed it,
|
|
257
|
+
// used to reject two items resolving to the same (block, logIndex).
|
|
258
|
+
let seenCoordinates = Dict.make()
|
|
208
259
|
|
|
209
|
-
simulateItems->Array.
|
|
260
|
+
simulateItems->Array.forEachWithIndex((rawJson, itemIndex) => {
|
|
210
261
|
let raw = rawJson->(Utils.magic: JSON.t => rawSimulateItem)
|
|
211
262
|
|
|
212
263
|
switch (raw->getContract, raw->getEvent) {
|
|
@@ -230,44 +281,41 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
|
|
|
230
281
|
}
|
|
231
282
|
let params = paramsJson->S.convertOrThrow(eventConfig.simulateParamsSchema)
|
|
232
283
|
|
|
284
|
+
// An explicit logIndex advances the auto-increment counter past itself, so a
|
|
285
|
+
// later item that omits logIndex picks up after it instead of colliding.
|
|
233
286
|
let logIndex = switch item.logIndex {
|
|
234
|
-
| Some(li) =>
|
|
287
|
+
| Some(li) =>
|
|
288
|
+
if li >= currentLogIndex.contents {
|
|
289
|
+
currentLogIndex := li + 1
|
|
290
|
+
}
|
|
291
|
+
li
|
|
235
292
|
| None =>
|
|
236
293
|
let li = currentLogIndex.contents
|
|
237
294
|
currentLogIndex := li + 1
|
|
238
295
|
li
|
|
239
296
|
}
|
|
240
297
|
|
|
241
|
-
let srcAddress =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if contract.name === contractName {
|
|
248
|
-
switch contract.addresses->Array.get(0) {
|
|
249
|
-
| Some(a) => addr := a
|
|
250
|
-
| None => ()
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
})
|
|
254
|
-
addr.contents
|
|
255
|
-
}
|
|
298
|
+
let srcAddress = deriveSrcAddress(
|
|
299
|
+
~providedSrcAddress=item.srcAddress,
|
|
300
|
+
~eventConfig,
|
|
301
|
+
~chainConfig,
|
|
302
|
+
~config,
|
|
303
|
+
)
|
|
256
304
|
|
|
257
305
|
let rawItem = rawJson->(Utils.magic: JSON.t => {..})
|
|
258
306
|
let blockJson: option<JSON.t> =
|
|
259
307
|
rawItem["block"]->(Utils.magic: 'a => Nullable.t<JSON.t>)->Nullable.toOption
|
|
260
308
|
let transactionJson: option<JSON.t> =
|
|
261
309
|
rawItem["transaction"]->(Utils.magic: 'a => Nullable.t<JSON.t>)->Nullable.toOption
|
|
262
|
-
let (block, blockNumber
|
|
310
|
+
let (block, blockNumber) = switch config.ecosystem.name {
|
|
263
311
|
| Fuel =>
|
|
264
312
|
let block = parseFuelSimulateBlock(~defaultBlockNumber=currentBlock.contents, ~blockJson)
|
|
265
313
|
let blockFields = block->(Utils.magic: Internal.eventBlock => fuelSimulateBlock)
|
|
266
|
-
(block, blockFields.height
|
|
314
|
+
(block, blockFields.height)
|
|
267
315
|
| Evm =>
|
|
268
316
|
let block = parseEvmSimulateBlock(~defaultBlockNumber=currentBlock.contents, ~blockJson)
|
|
269
317
|
let blockFields = block->(Utils.magic: Internal.eventBlock => evmSimulateBlock)
|
|
270
|
-
(block, blockFields.number
|
|
318
|
+
(block, blockFields.number)
|
|
271
319
|
| Svm => JsError.throwWithMessage("simulate is not supported for SVM ecosystem")
|
|
272
320
|
}
|
|
273
321
|
let transaction = switch config.ecosystem.name {
|
|
@@ -279,15 +327,43 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
|
|
|
279
327
|
// Update currentBlock for subsequent items
|
|
280
328
|
currentBlock := blockNumber
|
|
281
329
|
|
|
330
|
+
// A simulate item must land on a distinct (block, logIndex): event ordering
|
|
331
|
+
// and the dead-input tracker both key on it. Catch explicit duplicates and
|
|
332
|
+
// explicit-vs-auto-increment collisions here, naming both offending indices.
|
|
333
|
+
let coordinate = `${blockNumber->Int.toString}:${logIndex->Int.toString}`
|
|
334
|
+
switch seenCoordinates->Dict.get(coordinate) {
|
|
335
|
+
| Some(firstIndex) =>
|
|
336
|
+
JsError.throwWithMessage(
|
|
337
|
+
`simulate: items at index ${firstIndex->Int.toString} and ${itemIndex->Int.toString} on chain ${chainId->Int.toString} both resolve to block ${blockNumber->Int.toString}, logIndex ${logIndex->Int.toString}. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`,
|
|
338
|
+
)
|
|
339
|
+
| None => seenCoordinates->Dict.set(coordinate, itemIndex)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Build a real registration the same way `HandlerRegister.finishRegistration`
|
|
343
|
+
// does at startup (not a stub), so the address filter and `where`
|
|
344
|
+
// behave identically to real indexing — the dead-input tracker relies
|
|
345
|
+
// on `clientAddressFilter` actually gating unrouted items.
|
|
346
|
+
let onEventRegistration = HandlerRegister.buildOnEventRegistration(
|
|
347
|
+
~config,
|
|
348
|
+
~chainId,
|
|
349
|
+
~eventConfig,
|
|
350
|
+
)
|
|
351
|
+
// Append into the registration array that the chain state will own and
|
|
352
|
+
// put that same registration object directly on the simulated item.
|
|
353
|
+
let onEventRegistrationIndex = onEventRegistrations->Array.length
|
|
354
|
+
let onEventRegistration = {...onEventRegistration, index: onEventRegistrationIndex}
|
|
355
|
+
onEventRegistrations->Array.push(onEventRegistration)->ignore
|
|
356
|
+
|
|
282
357
|
items
|
|
283
358
|
->Array.push(
|
|
284
359
|
Internal.Event({
|
|
285
|
-
|
|
286
|
-
timestamp,
|
|
360
|
+
onEventRegistration,
|
|
287
361
|
chain,
|
|
288
362
|
blockNumber,
|
|
289
|
-
blockHash,
|
|
290
363
|
logIndex,
|
|
364
|
+
// Simulate keeps the transaction inline on the payload, so the store
|
|
365
|
+
// key is unused.
|
|
366
|
+
transactionIndex: 0,
|
|
291
367
|
payload: (
|
|
292
368
|
{
|
|
293
369
|
contractName: eventConfig.contractName,
|
|
@@ -314,7 +390,11 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
|
|
|
314
390
|
|
|
315
391
|
// Apply simulate source config from processConfig JSON to a Config.t
|
|
316
392
|
// This patches chainMap entries that have simulate items with CustomSources
|
|
317
|
-
let patchConfig = (
|
|
393
|
+
let patchConfig = (
|
|
394
|
+
~config: Config.t,
|
|
395
|
+
~processConfig: JSON.t,
|
|
396
|
+
~registrationsByChainId: HandlerRegister.registrationsByChainId,
|
|
397
|
+
): Config.t => {
|
|
318
398
|
let processChains: option<dict<JSON.t>> =
|
|
319
399
|
(processConfig->(Utils.magic: JSON.t => {..}))["chains"]->Nullable.toOption
|
|
320
400
|
switch processChains {
|
|
@@ -327,11 +407,31 @@ let patchConfig = (~config: Config.t, ~processConfig: JSON.t): Config.t => {
|
|
|
327
407
|
let simulateRaw: option<array<JSON.t>> = raw["simulate"]->Nullable.toOption
|
|
328
408
|
switch simulateRaw {
|
|
329
409
|
| Some(simulateItems) =>
|
|
330
|
-
let
|
|
410
|
+
let chainRegistrations = switch registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(
|
|
411
|
+
chainIdStr,
|
|
412
|
+
) {
|
|
413
|
+
| Some(registrations) => registrations
|
|
414
|
+
| None =>
|
|
415
|
+
let registrations: HandlerRegister.chainRegistrations = {
|
|
416
|
+
onEventRegistrations: [],
|
|
417
|
+
onBlockRegistrations: [],
|
|
418
|
+
}
|
|
419
|
+
registrationsByChainId->Dict.set(chainIdStr, registrations)
|
|
420
|
+
registrations
|
|
421
|
+
}
|
|
331
422
|
let startBlock: int = raw["startBlock"]->(Utils.magic: 'a => int)
|
|
332
423
|
let endBlock: int = raw["endBlock"]->(Utils.magic: 'a => int)
|
|
424
|
+
// Parse with the process's startBlock so items default into the range
|
|
425
|
+
// the source will be queried over; the source now filters by range.
|
|
426
|
+
let chainConfig = {...chainConfig, startBlock, endBlock}
|
|
427
|
+
let items = parse(
|
|
428
|
+
~simulateItems,
|
|
429
|
+
~config,
|
|
430
|
+
~chainConfig,
|
|
431
|
+
~onEventRegistrations=chainRegistrations.onEventRegistrations,
|
|
432
|
+
)
|
|
333
433
|
let source = SimulateSource.make(~items, ~endBlock, ~chain)
|
|
334
|
-
{...chainConfig,
|
|
434
|
+
{...chainConfig, sourceConfig: Config.CustomSources([source])}
|
|
335
435
|
| None => chainConfig
|
|
336
436
|
}
|
|
337
437
|
| None => chainConfig
|