envio 3.3.0-alpha.1 → 3.3.0-alpha.10
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/package.json +7 -7
- 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 +43 -9
- package/src/ChainFetching.res.mjs +34 -15
- package/src/ChainMetadata.res +1 -11
- package/src/ChainMetadata.res.mjs +1 -10
- package/src/ChainState.res +605 -151
- package/src/ChainState.res.mjs +427 -104
- package/src/ChainState.resi +72 -4
- package/src/Config.res +61 -33
- package/src/Config.res.mjs +50 -10
- package/src/ContractRegisterContext.res +2 -12
- package/src/ContractRegisterContext.res.mjs +3 -5
- package/src/Core.res +14 -3
- package/src/CrossChainState.res +113 -89
- package/src/CrossChainState.res.mjs +64 -60
- 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 +177 -88
- package/src/EventConfigBuilder.res.mjs +99 -42
- package/src/EventProcessing.res +74 -28
- package/src/EventProcessing.res.mjs +55 -29
- package/src/ExitOnCaughtUp.res +10 -2
- package/src/ExitOnCaughtUp.res.mjs +10 -1
- package/src/FetchState.res +544 -356
- package/src/FetchState.res.mjs +412 -471
- package/src/HandlerLoader.res +8 -104
- package/src/HandlerLoader.res.mjs +2 -81
- package/src/HandlerRegister.res +518 -144
- package/src/HandlerRegister.res.mjs +285 -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 +155 -46
- package/src/Internal.res.mjs +30 -2
- package/src/LoadLayer.res +5 -5
- package/src/LoadLayer.res.mjs +6 -6
- package/src/LogSelection.res +102 -165
- package/src/LogSelection.res.mjs +101 -116
- 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 +91 -23
- package/src/SimulateItems.res.mjs +55 -32
- package/src/TestIndexer.res +54 -40
- package/src/TestIndexer.res.mjs +37 -30
- 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 -41
- 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 +36 -33
- package/src/sources/EventRouter.res.mjs +13 -14
- package/src/sources/Evm.res +83 -72
- package/src/sources/Evm.res.mjs +53 -69
- package/src/sources/EvmChain.res +18 -20
- package/src/sources/EvmChain.res.mjs +15 -15
- package/src/sources/EvmRpcClient.res +74 -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 +39 -7
- package/src/sources/HyperSync.res.mjs +57 -37
- package/src/sources/HyperSync.resi +12 -2
- package/src/sources/HyperSyncClient.res +22 -111
- package/src/sources/HyperSyncClient.res.mjs +2 -30
- package/src/sources/HyperSyncHeightStream.res +1 -8
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
- package/src/sources/HyperSyncSource.res +94 -107
- package/src/sources/HyperSyncSource.res.mjs +82 -86
- package/src/sources/Rpc.res +15 -47
- package/src/sources/Rpc.res.mjs +25 -56
- package/src/sources/RpcSource.res +320 -467
- package/src/sources/RpcSource.res.mjs +268 -385
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +10 -4
- package/src/sources/SimulateSource.res.mjs +16 -6
- 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
|
+
| _ => "" // non-event items are never a provided simulate input
|
|
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,6 +195,51 @@ let findEventConfig = (~config: Config.t, ~contractName: string, ~eventName: str
|
|
|
195
195
|
found.contents
|
|
196
196
|
}
|
|
197
197
|
|
|
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,
|
|
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
|
+
|
|
198
243
|
let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Config.chain): array<
|
|
199
244
|
Internal.item,
|
|
200
245
|
> => {
|
|
@@ -205,8 +250,11 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
|
|
|
205
250
|
let currentLogIndex = ref(0)
|
|
206
251
|
|
|
207
252
|
let items = []
|
|
253
|
+
// Coordinate "block:logIndex" -> the index of the first item that claimed it,
|
|
254
|
+
// used to reject two items resolving to the same (block, logIndex).
|
|
255
|
+
let seenCoordinates = Dict.make()
|
|
208
256
|
|
|
209
|
-
simulateItems->Array.
|
|
257
|
+
simulateItems->Array.forEachWithIndex((rawJson, itemIndex) => {
|
|
210
258
|
let raw = rawJson->(Utils.magic: JSON.t => rawSimulateItem)
|
|
211
259
|
|
|
212
260
|
switch (raw->getContract, raw->getEvent) {
|
|
@@ -230,44 +278,41 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
|
|
|
230
278
|
}
|
|
231
279
|
let params = paramsJson->S.convertOrThrow(eventConfig.simulateParamsSchema)
|
|
232
280
|
|
|
281
|
+
// An explicit logIndex advances the auto-increment counter past itself, so a
|
|
282
|
+
// later item that omits logIndex picks up after it instead of colliding.
|
|
233
283
|
let logIndex = switch item.logIndex {
|
|
234
|
-
| Some(li) =>
|
|
284
|
+
| Some(li) =>
|
|
285
|
+
if li >= currentLogIndex.contents {
|
|
286
|
+
currentLogIndex := li + 1
|
|
287
|
+
}
|
|
288
|
+
li
|
|
235
289
|
| None =>
|
|
236
290
|
let li = currentLogIndex.contents
|
|
237
291
|
currentLogIndex := li + 1
|
|
238
292
|
li
|
|
239
293
|
}
|
|
240
294
|
|
|
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
|
-
}
|
|
295
|
+
let srcAddress = deriveSrcAddress(
|
|
296
|
+
~providedSrcAddress=item.srcAddress,
|
|
297
|
+
~eventConfig,
|
|
298
|
+
~chainConfig,
|
|
299
|
+
~config,
|
|
300
|
+
)
|
|
256
301
|
|
|
257
302
|
let rawItem = rawJson->(Utils.magic: JSON.t => {..})
|
|
258
303
|
let blockJson: option<JSON.t> =
|
|
259
304
|
rawItem["block"]->(Utils.magic: 'a => Nullable.t<JSON.t>)->Nullable.toOption
|
|
260
305
|
let transactionJson: option<JSON.t> =
|
|
261
306
|
rawItem["transaction"]->(Utils.magic: 'a => Nullable.t<JSON.t>)->Nullable.toOption
|
|
262
|
-
let (block, blockNumber
|
|
307
|
+
let (block, blockNumber) = switch config.ecosystem.name {
|
|
263
308
|
| Fuel =>
|
|
264
309
|
let block = parseFuelSimulateBlock(~defaultBlockNumber=currentBlock.contents, ~blockJson)
|
|
265
310
|
let blockFields = block->(Utils.magic: Internal.eventBlock => fuelSimulateBlock)
|
|
266
|
-
(block, blockFields.height
|
|
311
|
+
(block, blockFields.height)
|
|
267
312
|
| Evm =>
|
|
268
313
|
let block = parseEvmSimulateBlock(~defaultBlockNumber=currentBlock.contents, ~blockJson)
|
|
269
314
|
let blockFields = block->(Utils.magic: Internal.eventBlock => evmSimulateBlock)
|
|
270
|
-
(block, blockFields.number
|
|
315
|
+
(block, blockFields.number)
|
|
271
316
|
| Svm => JsError.throwWithMessage("simulate is not supported for SVM ecosystem")
|
|
272
317
|
}
|
|
273
318
|
let transaction = switch config.ecosystem.name {
|
|
@@ -279,15 +324,38 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
|
|
|
279
324
|
// Update currentBlock for subsequent items
|
|
280
325
|
currentBlock := blockNumber
|
|
281
326
|
|
|
327
|
+
// A simulate item must land on a distinct (block, logIndex): event ordering
|
|
328
|
+
// and the dead-input tracker both key on it. Catch explicit duplicates and
|
|
329
|
+
// explicit-vs-auto-increment collisions here, naming both offending indices.
|
|
330
|
+
let coordinate = `${blockNumber->Int.toString}:${logIndex->Int.toString}`
|
|
331
|
+
switch seenCoordinates->Dict.get(coordinate) {
|
|
332
|
+
| Some(firstIndex) =>
|
|
333
|
+
JsError.throwWithMessage(
|
|
334
|
+
`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).`,
|
|
335
|
+
)
|
|
336
|
+
| None => seenCoordinates->Dict.set(coordinate, itemIndex)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Build a real registration the same way `HandlerRegister.finishRegistration`
|
|
340
|
+
// does at startup (not a stub), so the address filter and `where`
|
|
341
|
+
// behave identically to real indexing — the dead-input tracker relies
|
|
342
|
+
// on `clientAddressFilter` actually gating unrouted items.
|
|
343
|
+
let onEventRegistration = HandlerRegister.buildOnEventRegistration(
|
|
344
|
+
~config,
|
|
345
|
+
~chainId,
|
|
346
|
+
~eventConfig,
|
|
347
|
+
)
|
|
348
|
+
|
|
282
349
|
items
|
|
283
350
|
->Array.push(
|
|
284
351
|
Internal.Event({
|
|
285
|
-
|
|
286
|
-
timestamp,
|
|
352
|
+
onEventRegistration,
|
|
287
353
|
chain,
|
|
288
354
|
blockNumber,
|
|
289
|
-
blockHash,
|
|
290
355
|
logIndex,
|
|
356
|
+
// Simulate keeps the transaction inline on the payload, so the store
|
|
357
|
+
// key is unused.
|
|
358
|
+
transactionIndex: 0,
|
|
291
359
|
payload: (
|
|
292
360
|
{
|
|
293
361
|
contractName: eventConfig.contractName,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Config from "./Config.res.mjs";
|
|
3
4
|
import * as Address from "./Address.res.mjs";
|
|
4
5
|
import * as ChainMap from "./ChainMap.res.mjs";
|
|
5
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
7
|
import * as SimulateSource from "./sources/SimulateSource.res.mjs";
|
|
7
8
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
9
|
+
import * as HandlerRegister from "./HandlerRegister.res.mjs";
|
|
8
10
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
9
11
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
10
12
|
|
|
@@ -135,6 +137,36 @@ function findEventConfig(config, contractName, eventName) {
|
|
|
135
137
|
return found.contents;
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
let dummySrcAddress = "0x0000000000000000000000000000000000000000";
|
|
141
|
+
|
|
142
|
+
function firstContractAddress(chainConfig, contractName) {
|
|
143
|
+
let found = {
|
|
144
|
+
contents: undefined
|
|
145
|
+
};
|
|
146
|
+
chainConfig.contracts.forEach(contract => {
|
|
147
|
+
if (Stdlib_Option.isNone(found.contents) && contract.name === contractName) {
|
|
148
|
+
found.contents = contract.addresses[0];
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return found.contents;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function deriveSrcAddress(providedSrcAddress, eventConfig, chainConfig, config) {
|
|
156
|
+
if (providedSrcAddress !== undefined) {
|
|
157
|
+
return Config.normalizeSimulateAddress(config, Primitive_option.valFromOption(providedSrcAddress));
|
|
158
|
+
}
|
|
159
|
+
if (HandlerRegister.isWildcard(eventConfig.contractName, eventConfig.name)) {
|
|
160
|
+
return dummySrcAddress;
|
|
161
|
+
}
|
|
162
|
+
let addr = firstContractAddress(chainConfig, eventConfig.contractName);
|
|
163
|
+
if (addr !== undefined) {
|
|
164
|
+
return Primitive_option.valFromOption(addr);
|
|
165
|
+
} else {
|
|
166
|
+
return dummySrcAddress;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
138
170
|
function parse(simulateItems, config, chainConfig) {
|
|
139
171
|
let chain = ChainMap.Chain.makeUnsafe(chainConfig.id);
|
|
140
172
|
let chainId = chainConfig.id;
|
|
@@ -146,7 +178,8 @@ function parse(simulateItems, config, chainConfig) {
|
|
|
146
178
|
contents: 0
|
|
147
179
|
};
|
|
148
180
|
let items = [];
|
|
149
|
-
|
|
181
|
+
let seenCoordinates = {};
|
|
182
|
+
simulateItems.forEach((rawJson, itemIndex) => {
|
|
150
183
|
let match = rawJson.contract;
|
|
151
184
|
let match$1 = rawJson.event;
|
|
152
185
|
if (match === undefined) {
|
|
@@ -163,32 +196,16 @@ function parse(simulateItems, config, chainConfig) {
|
|
|
163
196
|
let li = rawJson.logIndex;
|
|
164
197
|
let logIndex;
|
|
165
198
|
if (li !== undefined) {
|
|
199
|
+
if (li >= currentLogIndex.contents) {
|
|
200
|
+
currentLogIndex.contents = li + 1 | 0;
|
|
201
|
+
}
|
|
166
202
|
logIndex = li;
|
|
167
203
|
} else {
|
|
168
204
|
let li$1 = currentLogIndex.contents;
|
|
169
205
|
currentLogIndex.contents = li$1 + 1 | 0;
|
|
170
206
|
logIndex = li$1;
|
|
171
207
|
}
|
|
172
|
-
let
|
|
173
|
-
let srcAddress;
|
|
174
|
-
if (addr !== undefined) {
|
|
175
|
-
srcAddress = Primitive_option.valFromOption(addr);
|
|
176
|
-
} else {
|
|
177
|
-
let addr$1 = {
|
|
178
|
-
contents: "0x0000000000000000000000000000000000000000"
|
|
179
|
-
};
|
|
180
|
-
chainConfig.contracts.forEach(contract => {
|
|
181
|
-
if (contract.name !== match) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
let a = contract.addresses[0];
|
|
185
|
-
if (a !== undefined) {
|
|
186
|
-
addr$1.contents = Primitive_option.valFromOption(a);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
srcAddress = addr$1.contents;
|
|
191
|
-
}
|
|
208
|
+
let srcAddress = deriveSrcAddress(rawJson.srcAddress, eventConfig, chainConfig, config);
|
|
192
209
|
let blockJson = rawJson.block;
|
|
193
210
|
let blockJson$1 = (blockJson == null) ? undefined : Primitive_option.some(blockJson);
|
|
194
211
|
let transactionJson = rawJson.transaction;
|
|
@@ -200,18 +217,14 @@ function parse(simulateItems, config, chainConfig) {
|
|
|
200
217
|
let block = parseEvmSimulateBlock(currentBlock.contents, blockJson$1);
|
|
201
218
|
match$3 = [
|
|
202
219
|
block,
|
|
203
|
-
block.number
|
|
204
|
-
block.timestamp,
|
|
205
|
-
block.hash
|
|
220
|
+
block.number
|
|
206
221
|
];
|
|
207
222
|
break;
|
|
208
223
|
case "fuel" :
|
|
209
224
|
let block$1 = parseFuelSimulateBlock(currentBlock.contents, blockJson$1);
|
|
210
225
|
match$3 = [
|
|
211
226
|
block$1,
|
|
212
|
-
block$1.height
|
|
213
|
-
block$1.time,
|
|
214
|
-
block$1.id
|
|
227
|
+
block$1.height
|
|
215
228
|
];
|
|
216
229
|
break;
|
|
217
230
|
case "svm" :
|
|
@@ -233,14 +246,21 @@ function parse(simulateItems, config, chainConfig) {
|
|
|
233
246
|
break;
|
|
234
247
|
}
|
|
235
248
|
currentBlock.contents = blockNumber;
|
|
249
|
+
let coordinate = blockNumber.toString() + `:` + logIndex.toString();
|
|
250
|
+
let firstIndex = seenCoordinates[coordinate];
|
|
251
|
+
if (firstIndex !== undefined) {
|
|
252
|
+
Stdlib_JsError.throwWithMessage(`simulate: items at index ` + firstIndex.toString() + ` and ` + itemIndex.toString() + ` on chain ` + chainId.toString() + ` both resolve to block ` + blockNumber.toString() + `, logIndex ` + logIndex.toString() + `. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`);
|
|
253
|
+
} else {
|
|
254
|
+
seenCoordinates[coordinate] = itemIndex;
|
|
255
|
+
}
|
|
256
|
+
let onEventRegistration = HandlerRegister.buildOnEventRegistration(config, chainId, eventConfig, undefined);
|
|
236
257
|
items.push({
|
|
237
258
|
kind: 0,
|
|
238
|
-
|
|
239
|
-
timestamp: match$3[2],
|
|
259
|
+
onEventRegistration: onEventRegistration,
|
|
240
260
|
chain: chain,
|
|
241
261
|
blockNumber: blockNumber,
|
|
242
|
-
blockHash: match$3[3],
|
|
243
262
|
logIndex: logIndex,
|
|
263
|
+
transactionIndex: 0,
|
|
244
264
|
payload: {
|
|
245
265
|
contractName: eventConfig.contractName,
|
|
246
266
|
eventName: eventConfig.name,
|
|
@@ -248,8 +268,8 @@ function parse(simulateItems, config, chainConfig) {
|
|
|
248
268
|
chainId: chainId,
|
|
249
269
|
srcAddress: srcAddress,
|
|
250
270
|
logIndex: logIndex,
|
|
251
|
-
transaction: transaction,
|
|
252
|
-
block: match$3[0]
|
|
271
|
+
transaction: Primitive_option.some(transaction),
|
|
272
|
+
block: Primitive_option.some(match$3[0])
|
|
253
273
|
}
|
|
254
274
|
});
|
|
255
275
|
});
|
|
@@ -317,6 +337,9 @@ export {
|
|
|
317
337
|
fuelSimulateTransactionSchema,
|
|
318
338
|
parseFuelSimulateTransaction,
|
|
319
339
|
findEventConfig,
|
|
340
|
+
dummySrcAddress,
|
|
341
|
+
firstContractAddress,
|
|
342
|
+
deriveSrcAddress,
|
|
320
343
|
parse,
|
|
321
344
|
patchConfig,
|
|
322
345
|
}
|