envio 3.3.0-alpha.0 → 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 +48 -16
- package/src/ChainFetching.res.mjs +36 -17
- package/src/ChainMetadata.res +1 -11
- package/src/ChainMetadata.res.mjs +1 -10
- package/src/ChainState.res +628 -149
- package/src/ChainState.res.mjs +440 -102
- package/src/ChainState.resi +74 -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 +142 -93
- package/src/CrossChainState.res.mjs +88 -60
- package/src/CrossChainState.resi +2 -10
- package/src/Ecosystem.res +5 -6
- package/src/Ecosystem.res.mjs +3 -3
- package/src/Env.res +0 -7
- package/src/Env.res.mjs +0 -6
- 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 +558 -300
- package/src/FetchState.res.mjs +437 -423
- 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/IndexerLoop.res +2 -3
- package/src/IndexerLoop.res.mjs +1 -1
- package/src/IndexerState.res +10 -11
- package/src/IndexerState.res.mjs +14 -11
- package/src/IndexerState.resi +2 -5
- 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 -74
- package/src/Prometheus.res.mjs +124 -186
- 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 +86 -27
- package/src/sources/SourceManager.res.mjs +84 -47
- package/src/sources/SourceManager.resi +12 -3
- 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
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
open Source
|
|
2
2
|
|
|
3
|
-
exception QueryTimout(string)
|
|
4
|
-
|
|
5
3
|
// eth_getTransactionByHash/eth_getTransactionReceipt returning null is usually
|
|
6
4
|
// transient: a load-balanced provider can route the lookup to a node that
|
|
7
5
|
// hasn't caught up with the one that served eth_getLogs. Must stay retryable,
|
|
@@ -48,18 +46,16 @@ let getKnownRawBlockWithBackoff = async (
|
|
|
48
46
|
~chain,
|
|
49
47
|
~blockNumber,
|
|
50
48
|
~backoffMsOnFailure,
|
|
49
|
+
~recordRequest: (~method: string, ~seconds: float) => unit,
|
|
51
50
|
) => {
|
|
52
51
|
let currentBackoff = ref(backoffMsOnFailure)
|
|
53
52
|
let result = ref(None)
|
|
54
53
|
|
|
55
54
|
while result.contents->Option.isNone {
|
|
56
|
-
|
|
57
|
-
~sourceName,
|
|
58
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
59
|
-
~method="eth_getBlockByNumber",
|
|
60
|
-
)
|
|
55
|
+
let timerRef = Performance.now()
|
|
61
56
|
switch await getKnownRawBlock(~client, ~blockNumber) {
|
|
62
57
|
| exception err =>
|
|
58
|
+
recordRequest(~method="eth_getBlockByNumber", ~seconds=timerRef->Performance.secondsSince)
|
|
63
59
|
Logging.warn({
|
|
64
60
|
"err": err->Utils.prettifyExn,
|
|
65
61
|
"msg": `Issue while running fetching batch of events from the RPC. Will wait ${currentBackoff.contents->Int.toString}ms and try again.`,
|
|
@@ -69,327 +65,217 @@ let getKnownRawBlockWithBackoff = async (
|
|
|
69
65
|
})
|
|
70
66
|
await Time.resolvePromiseAfterDelay(~delayMilliseconds=currentBackoff.contents)
|
|
71
67
|
currentBackoff := currentBackoff.contents * 2
|
|
72
|
-
| json =>
|
|
68
|
+
| json =>
|
|
69
|
+
recordRequest(~method="eth_getBlockByNumber", ~seconds=timerRef->Performance.secondsSince)
|
|
70
|
+
result := Some(json)
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
73
|
result.contents->Option.getOrThrow
|
|
76
74
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
let blockpiRangeRegExp = /limited to (\d+) block/
|
|
95
|
-
|
|
96
|
-
// Base: "block range too large" - fixed 2000 block limit
|
|
97
|
-
let baseRangeRegExp = /block range too large/
|
|
98
|
-
|
|
99
|
-
// evm-rpc.sei-apis.com: "block range too large (2000), maximum allowed is 1000 blocks"
|
|
100
|
-
let maxAllowedBlocksRegExp = /maximum allowed is (\d+) blocks/
|
|
101
|
-
|
|
102
|
-
// Blast (paid): "exceeds the range allowed for your plan (5000 > 3000)"
|
|
103
|
-
let blastPaidRegExp = /exceeds the range allowed for your plan \(\d+ > (\d+)\)/
|
|
104
|
-
|
|
105
|
-
// Chainstack: "Block range limit exceeded" - 10000 block limit
|
|
106
|
-
let chainstackRegExp = /Block range limit exceeded./
|
|
107
|
-
|
|
108
|
-
// Coinbase: "please limit the query to at most 1000 blocks"
|
|
109
|
-
let coinbaseRegExp = /please limit the query to at most (\d+) blocks/
|
|
110
|
-
|
|
111
|
-
// PublicNode: "maximum block range: 2000"
|
|
112
|
-
let publicNodeRegExp = /maximum block range: (\d+)/
|
|
113
|
-
|
|
114
|
-
// Hyperliquid: "query exceeds max block range 1000"
|
|
115
|
-
let hyperliquidRegExp = /query exceeds max block range (\d+)/
|
|
116
|
-
|
|
117
|
-
// TODO: Reproduce how the error message looks like
|
|
118
|
-
// when we send request with numeric block range instead of hex
|
|
119
|
-
// Infura, ZkSync: "Try with this block range [0x123,0x456]"
|
|
120
|
-
|
|
121
|
-
// Future handling needed for these providers that don't suggest ranges:
|
|
122
|
-
// - Ankr: "block range is too wide"
|
|
123
|
-
// - 1RPC: "response size should not greater than 10000000 bytes"
|
|
124
|
-
// - ZkEVM: "query returned more than 10000 results"
|
|
125
|
-
// - LlamaRPC: "query exceeds max results"
|
|
126
|
-
// - Optimism: "backend response too large" or "Block range is too large"
|
|
127
|
-
// - Arbitrum: "logs matched by query exceeds limit of 10000"
|
|
128
|
-
|
|
129
|
-
let parseMessageForBlockRange = (message: string) => {
|
|
130
|
-
// Helper to extract block range from regex match
|
|
131
|
-
let extractBlockRange = (execResult, ~isMaxRange) =>
|
|
132
|
-
switch execResult->RegExp.Result.matches {
|
|
133
|
-
| [Some(blockRangeLimit)] =>
|
|
134
|
-
switch blockRangeLimit->Int.fromString {
|
|
135
|
-
| Some(blockRangeLimit) if blockRangeLimit > 0 => Some(blockRangeLimit, isMaxRange)
|
|
75
|
+
// Pulls the underlying provider error message back out of a caught exn, for
|
|
76
|
+
// logging/debugging. Provider JSON-RPC errors surface as `Rpc.JsonRpcError`;
|
|
77
|
+
// the paging retry decision (see `parseGetNextPageRetryError` below) surfaces
|
|
78
|
+
// as a napi `JsExn` whose message is the JSON payload `EvmRpcClient.getNextPage`
|
|
79
|
+
// throws, carrying the classified message (if any) under `errorMessage`.
|
|
80
|
+
let getErrorMessage = (exn: exn): option<string> =>
|
|
81
|
+
switch exn {
|
|
82
|
+
| Rpc.JsonRpcError({message}) => Some(message)
|
|
83
|
+
| JsExn(e) =>
|
|
84
|
+
switch e->JsExn.message {
|
|
85
|
+
| Some(msg) =>
|
|
86
|
+
switch msg->JSON.parseOrThrow->JSON.Decode.object {
|
|
87
|
+
| exception _ => None
|
|
88
|
+
| None => None
|
|
89
|
+
| Some(obj) =>
|
|
90
|
+
switch obj->Dict.get("errorMessage") {
|
|
91
|
+
| Some(String(message)) => Some(message)
|
|
136
92
|
| _ => None
|
|
137
93
|
}
|
|
138
|
-
| _ => None
|
|
139
94
|
}
|
|
95
|
+
| None => None
|
|
96
|
+
}
|
|
97
|
+
| _ => None
|
|
98
|
+
}
|
|
140
99
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
| None =>
|
|
176
|
-
switch blastPaidRegExp->RegExp.exec(message) {
|
|
177
|
-
| Some(execResult) => extractBlockRange(execResult, ~isMaxRange=true)
|
|
178
|
-
| None =>
|
|
179
|
-
switch chainstackRegExp->RegExp.exec(message) {
|
|
180
|
-
| Some(_) => Some(10000, true)
|
|
181
|
-
| None =>
|
|
182
|
-
switch coinbaseRegExp->RegExp.exec(message) {
|
|
183
|
-
| Some(execResult) => extractBlockRange(execResult, ~isMaxRange=true)
|
|
184
|
-
| None =>
|
|
185
|
-
switch publicNodeRegExp->RegExp.exec(message) {
|
|
186
|
-
| Some(execResult) => extractBlockRange(execResult, ~isMaxRange=true)
|
|
187
|
-
| None =>
|
|
188
|
-
switch hyperliquidRegExp->RegExp.exec(message) {
|
|
189
|
-
| Some(execResult) => extractBlockRange(execResult, ~isMaxRange=true)
|
|
190
|
-
| None => None
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
100
|
+
type logSelection = {
|
|
101
|
+
addresses: option<array<Address.t>>,
|
|
102
|
+
topicQuery: Rpc.GetLogs.topicQuery,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// `EvmRpcClient.getNextPage` throws a napi error whose message is a JSON
|
|
106
|
+
// payload describing the retry decision:
|
|
107
|
+
// `{"kind":"Retry","attemptedToBlock":int,"errorMessage":string|null,
|
|
108
|
+
// "requestStats":[{"method":string,"seconds":float}],"retry":
|
|
109
|
+
// {"tag":"WithSuggestedToBlock","toBlock":int} |
|
|
110
|
+
// {"tag":"WithBackoff","message":string,"backoffMillis":int}}`.
|
|
111
|
+
let parseGetNextPageRetryError = (exn: exn): option<(
|
|
112
|
+
int,
|
|
113
|
+
Source.getItemsRetry,
|
|
114
|
+
array<Source.requestStat>,
|
|
115
|
+
)> =>
|
|
116
|
+
switch exn {
|
|
117
|
+
| JsExn(e) =>
|
|
118
|
+
switch e->JsExn.message {
|
|
119
|
+
| Some(msg) =>
|
|
120
|
+
switch msg->JSON.parseOrThrow->JSON.Decode.object {
|
|
121
|
+
| exception _ => None
|
|
122
|
+
| None => None
|
|
123
|
+
| Some(obj) =>
|
|
124
|
+
switch (obj->Dict.get("kind"), obj->Dict.get("attemptedToBlock"), obj->Dict.get("retry")) {
|
|
125
|
+
| (Some(String("Retry")), Some(Number(attemptedToBlock)), Some(Object(retryObj))) =>
|
|
126
|
+
let requestStats = switch obj->Dict.get("requestStats") {
|
|
127
|
+
| Some(Array(stats)) =>
|
|
128
|
+
stats->Array.filterMap(s =>
|
|
129
|
+
switch s->JSON.Decode.object {
|
|
130
|
+
| Some(o) =>
|
|
131
|
+
switch (o->Dict.get("method"), o->Dict.get("seconds")) {
|
|
132
|
+
| (Some(String(method)), Some(Number(seconds))) => Some({Source.method, seconds})
|
|
133
|
+
| _ => None
|
|
197
134
|
}
|
|
135
|
+
| None => None
|
|
198
136
|
}
|
|
137
|
+
)
|
|
138
|
+
| _ => []
|
|
139
|
+
}
|
|
140
|
+
let retry = switch retryObj->Dict.get("tag") {
|
|
141
|
+
| Some(String("WithSuggestedToBlock")) =>
|
|
142
|
+
switch retryObj->Dict.get("toBlock") {
|
|
143
|
+
| Some(Number(toBlock)) =>
|
|
144
|
+
Some(Source.WithSuggestedToBlock({toBlock: toBlock->Float.toInt}))
|
|
145
|
+
| _ => None
|
|
146
|
+
}
|
|
147
|
+
| Some(String("WithBackoff")) =>
|
|
148
|
+
switch (retryObj->Dict.get("message"), retryObj->Dict.get("backoffMillis")) {
|
|
149
|
+
| (Some(String(message)), Some(Number(backoffMillis))) =>
|
|
150
|
+
Some(Source.WithBackoff({message, backoffMillis: backoffMillis->Float.toInt}))
|
|
151
|
+
| _ => None
|
|
199
152
|
}
|
|
153
|
+
| _ => None
|
|
200
154
|
}
|
|
155
|
+
retry->Option.map(retry => (attemptedToBlock->Float.toInt, retry, requestStats))
|
|
156
|
+
| _ => None
|
|
201
157
|
}
|
|
202
158
|
}
|
|
159
|
+
| None => None
|
|
203
160
|
}
|
|
161
|
+
| _ => None
|
|
204
162
|
}
|
|
205
163
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
bool,
|
|
211
|
-
)> =>
|
|
212
|
-
switch exn {
|
|
213
|
-
| Rpc.JsonRpcError({message}) => parseMessageForBlockRange(message)
|
|
214
|
-
| JsExn(error) =>
|
|
215
|
-
try {
|
|
216
|
-
let message: string = (error->Obj.magic)["error"]["message"]
|
|
217
|
-
message->S.assertOrThrow(S.string)
|
|
218
|
-
parseMessageForBlockRange(message)
|
|
219
|
-
} catch {
|
|
220
|
-
| _ => None
|
|
221
|
-
}
|
|
222
|
-
| _ => None
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
type eventBatchQuery = {
|
|
227
|
-
logs: array<Rpc.GetLogs.log>,
|
|
228
|
-
latestFetchedBlockInfo: blockInfo,
|
|
164
|
+
type selectionConfig = {
|
|
165
|
+
getLogSelectionsOrThrow: (
|
|
166
|
+
~addressesByContractName: dict<array<Address.t>>,
|
|
167
|
+
) => array<logSelection>,
|
|
229
168
|
}
|
|
230
169
|
|
|
231
|
-
let
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
~toBlock,
|
|
236
|
-
~addresses,
|
|
237
|
-
~topicQuery,
|
|
238
|
-
~loadBlock,
|
|
239
|
-
~syncConfig as sc: Config.sourceSync,
|
|
240
|
-
~client,
|
|
241
|
-
~mutSuggestedBlockIntervals,
|
|
242
|
-
~partitionId,
|
|
243
|
-
~sourceName,
|
|
244
|
-
~chainId,
|
|
245
|
-
): promise<eventBatchQuery> => {
|
|
246
|
-
//If the query hangs for longer than this, reject this promise to reduce the block interval
|
|
247
|
-
let queryTimoutPromise =
|
|
248
|
-
Time.resolvePromiseAfterDelay(~delayMilliseconds=sc.queryTimeoutMillis)->Promise.then(() =>
|
|
249
|
-
Promise.reject(
|
|
250
|
-
QueryTimout(`Query took longer than ${Int.toString(sc.queryTimeoutMillis / 1000)} seconds`),
|
|
251
|
-
)
|
|
170
|
+
let getSelectionConfig = (selection: FetchState.selection) => {
|
|
171
|
+
let evmOnEventRegistrations =
|
|
172
|
+
selection.onEventRegistrations->(
|
|
173
|
+
Utils.magic: array<Internal.onEventRegistration> => array<Internal.evmOnEventRegistration>
|
|
252
174
|
)
|
|
253
175
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
},
|
|
264
|
-
)->Promise.then(async logs => {
|
|
265
|
-
{
|
|
266
|
-
logs,
|
|
267
|
-
latestFetchedBlockInfo: await latestFetchedBlockPromise,
|
|
268
|
-
}
|
|
269
|
-
})
|
|
176
|
+
if evmOnEventRegistrations->Utils.Array.isEmpty {
|
|
177
|
+
throw(
|
|
178
|
+
Source.GetItemsError(
|
|
179
|
+
UnsupportedSelection({
|
|
180
|
+
message: "Invalid events configuration for the partition. Nothing to fetch. Please, report to the Envio team.",
|
|
181
|
+
}),
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
}
|
|
270
185
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
186
|
+
// eth_getLogs takes one address list and one topic selection per request, so
|
|
187
|
+
// fan out to one request per selection. Each address-bound event is grouped by
|
|
188
|
+
// its contract and later scoped to that contract's own addresses — pooling all
|
|
189
|
+
// contracts' addresses would let one contract's query fetch a sibling's logs,
|
|
190
|
+
// which route back by address and bypass the sibling's filter (routing never
|
|
191
|
+
// re-applies it). Pure-wildcard events carry no address constraint, so they're
|
|
192
|
+
// pooled and resolved once.
|
|
193
|
+
let noAddressTopicSelections = []
|
|
194
|
+
let byContract = Dict.make()
|
|
195
|
+
let wildcardByContract = Dict.make()
|
|
196
|
+
let contractNames = Utils.Set.make()
|
|
197
|
+
|
|
198
|
+
evmOnEventRegistrations->Array.forEach(reg => {
|
|
199
|
+
let contractName = reg.eventConfig.contractName
|
|
200
|
+
let {isWildcard, dependsOnAddresses, resolvedWhere} = reg
|
|
201
|
+
if dependsOnAddresses {
|
|
202
|
+
contractNames->Utils.Set.add(contractName)->ignore
|
|
203
|
+
(isWildcard ? wildcardByContract : byContract)->Utils.Dict.pushMany(
|
|
204
|
+
contractName,
|
|
205
|
+
resolvedWhere.topicSelections,
|
|
279
206
|
)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
attemptedToBlock: toBlock,
|
|
285
|
-
retry: WithSuggestedToBlock({
|
|
286
|
-
toBlock: fromBlock + nextBlockIntervalTry - 1,
|
|
287
|
-
}),
|
|
288
|
-
}),
|
|
289
|
-
),
|
|
290
|
-
)
|
|
291
|
-
| None =>
|
|
292
|
-
let executedBlockInterval = toBlock - fromBlock + 1
|
|
293
|
-
let nextBlockIntervalTry =
|
|
294
|
-
(executedBlockInterval->Int.toFloat *. sc.backoffMultiplicative)->Float.toInt
|
|
295
|
-
mutSuggestedBlockIntervals->Dict.set(partitionId, nextBlockIntervalTry)
|
|
296
|
-
throw(
|
|
297
|
-
Source.GetItemsError(
|
|
298
|
-
Source.FailedGettingItems({
|
|
299
|
-
exn: err,
|
|
300
|
-
attemptedToBlock: toBlock,
|
|
301
|
-
retry: WithBackoff({
|
|
302
|
-
message: `Failed getting data for the block range. Will try smaller block range for the next attempt.`,
|
|
303
|
-
backoffMillis: sc.backoffMillis,
|
|
304
|
-
}),
|
|
305
|
-
}),
|
|
306
|
-
),
|
|
207
|
+
} else {
|
|
208
|
+
noAddressTopicSelections
|
|
209
|
+
->Array.pushMany(
|
|
210
|
+
resolvedWhere.topicSelections->LogSelection.materializeTopicSelections(~addresses=[]),
|
|
307
211
|
)
|
|
212
|
+
->ignore
|
|
308
213
|
}
|
|
309
214
|
})
|
|
310
|
-
}
|
|
311
215
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
216
|
+
// `compressTopicSelections` folds the filter-less events into a single topic0
|
|
217
|
+
// OR-set, keeping the common case at one request.
|
|
218
|
+
let toLogSelections = (~addresses, topicSelections): array<logSelection> =>
|
|
219
|
+
topicSelections
|
|
220
|
+
->LogSelection.compressTopicSelections
|
|
221
|
+
->Array.map(topicSelection => {
|
|
222
|
+
addresses,
|
|
223
|
+
topicQuery: topicSelection->Rpc.GetLogs.mapTopicQuery,
|
|
224
|
+
})
|
|
320
225
|
|
|
321
|
-
|
|
322
|
-
let
|
|
323
|
-
let dynamicEventFilters = []
|
|
226
|
+
// Address-independent, so resolve once (the wildcard partition reuses this).
|
|
227
|
+
let noAddressLogSelections = toLogSelections(~addresses=None, noAddressTopicSelections)
|
|
324
228
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
229
|
+
let getLogSelectionsOrThrow = if contractNames->Utils.Set.size === 0 {
|
|
230
|
+
(~addressesByContractName as _) => noAddressLogSelections
|
|
231
|
+
} else {
|
|
232
|
+
(~addressesByContractName): array<logSelection> => {
|
|
233
|
+
let logSelections = noAddressLogSelections->Array.copy
|
|
234
|
+
contractNames->Utils.Set.forEach(contractName => {
|
|
235
|
+
switch addressesByContractName->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
236
|
+
| None
|
|
237
|
+
| Some([]) => ()
|
|
238
|
+
| Some(addresses) =>
|
|
239
|
+
// Non-wildcard filters, scoped to this contract's addresses.
|
|
240
|
+
switch byContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
241
|
+
| Some(topicSelections) =>
|
|
242
|
+
logSelections
|
|
243
|
+
->Array.pushMany(
|
|
244
|
+
toLogSelections(
|
|
245
|
+
~addresses=Some(addresses),
|
|
246
|
+
topicSelections->LogSelection.materializeTopicSelections(~addresses),
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
->ignore
|
|
250
|
+
| None => ()
|
|
251
|
+
}
|
|
333
252
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
| addresses => Some(addresses)
|
|
352
|
-
},
|
|
353
|
-
topicQuery,
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
| ([], [dynamicEventFilter]) if selection.eventConfigs->Array.length === 1 =>
|
|
357
|
-
let eventConfig = selection.eventConfigs->Utils.Array.firstUnsafe
|
|
358
|
-
|
|
359
|
-
(~addressesByContractName) => {
|
|
360
|
-
let addresses = addressesByContractName->FetchState.addressesByContractNameGetAll
|
|
361
|
-
{
|
|
362
|
-
addresses: eventConfig.isWildcard ? None : Some(addresses),
|
|
363
|
-
topicQuery: switch dynamicEventFilter(addresses) {
|
|
364
|
-
| [topicSelection] => topicSelection->Rpc.GetLogs.mapTopicQuery
|
|
365
|
-
| _ =>
|
|
366
|
-
throw(
|
|
367
|
-
Source.GetItemsError(
|
|
368
|
-
UnsupportedSelection({
|
|
369
|
-
message: "RPC data-source currently doesn't support an array of event filters. Please, create a GitHub issue if it's a blocker for you.",
|
|
370
|
-
}),
|
|
371
|
-
),
|
|
372
|
-
)
|
|
373
|
-
},
|
|
374
|
-
}
|
|
253
|
+
// Wildcard-by-address filters fold the address into the topics,
|
|
254
|
+
// so they still match any address.
|
|
255
|
+
switch wildcardByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
256
|
+
| Some(topicSelections) =>
|
|
257
|
+
logSelections
|
|
258
|
+
->Array.pushMany(
|
|
259
|
+
toLogSelections(
|
|
260
|
+
~addresses=None,
|
|
261
|
+
topicSelections->LogSelection.materializeTopicSelections(~addresses),
|
|
262
|
+
),
|
|
263
|
+
)
|
|
264
|
+
->ignore
|
|
265
|
+
| None => ()
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
logSelections
|
|
375
270
|
}
|
|
376
|
-
| _ =>
|
|
377
|
-
throw(
|
|
378
|
-
Source.GetItemsError(
|
|
379
|
-
UnsupportedSelection({
|
|
380
|
-
message: "RPC data-source currently supports event filters only when there's a single wildcard event. Please, create a GitHub issue if it's a blocker for you.",
|
|
381
|
-
}),
|
|
382
|
-
),
|
|
383
|
-
)
|
|
384
271
|
}
|
|
385
272
|
|
|
386
273
|
{
|
|
387
|
-
|
|
274
|
+
getLogSelectionsOrThrow: getLogSelectionsOrThrow,
|
|
388
275
|
}
|
|
389
276
|
}
|
|
390
277
|
|
|
391
|
-
let memoGetSelectionConfig = (
|
|
392
|
-
Utils.WeakMap.memoize(selection => selection->getSelectionConfig(~chain))
|
|
278
|
+
let memoGetSelectionConfig = () => Utils.WeakMap.memoize(getSelectionConfig)
|
|
393
279
|
|
|
394
280
|
// Type-erase a schema for storage in the field registry
|
|
395
281
|
external toFieldSchema: S.t<'a> => S.t<JSON.t> = "%identity"
|
|
@@ -531,11 +417,9 @@ let makeThrowingGetEventBlock = (
|
|
|
531
417
|
}
|
|
532
418
|
}
|
|
533
419
|
|
|
534
|
-
// `number
|
|
535
|
-
//
|
|
420
|
+
// `number` is always part of the selected block fields, so it can be read
|
|
421
|
+
// from the assembled block for the item's own `blockNumber`.
|
|
536
422
|
@get external getBlockNumber: Internal.eventBlock => int = "number"
|
|
537
|
-
@get external getBlockTimestamp: Internal.eventBlock => int = "timestamp"
|
|
538
|
-
@get external getBlockHash: Internal.eventBlock => string = "hash"
|
|
539
423
|
|
|
540
424
|
// Field source classification for RPC calls
|
|
541
425
|
type fieldSource = TransactionOnly | ReceiptOnly | Both
|
|
@@ -851,10 +735,11 @@ type options = {
|
|
|
851
735
|
syncConfig: Config.sourceSync,
|
|
852
736
|
url: string,
|
|
853
737
|
chain: ChainMap.Chain.t,
|
|
854
|
-
eventRouter: EventRouter.t<Internal.
|
|
738
|
+
eventRouter: EventRouter.t<Internal.evmOnEventRegistration>,
|
|
855
739
|
allEventParams: array<HyperSyncClient.Decoder.eventParamsInput>,
|
|
856
740
|
lowercaseAddresses: bool,
|
|
857
741
|
ws?: string,
|
|
742
|
+
headers?: dict<string>,
|
|
858
743
|
}
|
|
859
744
|
|
|
860
745
|
let make = (
|
|
@@ -867,6 +752,7 @@ let make = (
|
|
|
867
752
|
allEventParams,
|
|
868
753
|
lowercaseAddresses,
|
|
869
754
|
?ws,
|
|
755
|
+
?headers,
|
|
870
756
|
}: options,
|
|
871
757
|
): t => {
|
|
872
758
|
let chainId = chain->ChainMap.Chain.toChainId
|
|
@@ -879,22 +765,48 @@ let make = (
|
|
|
879
765
|
}
|
|
880
766
|
let name = `RPC (${urlHost})`
|
|
881
767
|
|
|
882
|
-
let getSelectionConfig = memoGetSelectionConfig(
|
|
768
|
+
let getSelectionConfig = memoGetSelectionConfig()
|
|
883
769
|
|
|
884
|
-
let
|
|
770
|
+
let client = Rpc.makeClient(url, ~headers?)
|
|
771
|
+
let rpcClient = EvmRpcClient.make(
|
|
772
|
+
~url,
|
|
773
|
+
~allEventParams,
|
|
774
|
+
~checksumAddresses=!lowercaseAddresses,
|
|
775
|
+
~syncConfig,
|
|
776
|
+
~headers?,
|
|
777
|
+
)
|
|
885
778
|
|
|
886
|
-
|
|
887
|
-
|
|
779
|
+
// Requests are made from shared, memoized loaders, so they can't be
|
|
780
|
+
// attributed to a single getItemsOrThrow/getHeightOrThrow/getBlockHashes
|
|
781
|
+
// call at its call site. Every actual request (cache/dedup hits never reach
|
|
782
|
+
// recordRequest) pushes here; each method drains whatever is pending when it
|
|
783
|
+
// returns. Since a push always lands in exactly one drain, per-source totals
|
|
784
|
+
// stay exact even with concurrent in-flight calls — which call happens to
|
|
785
|
+
// drain a given entry doesn't matter, since SourceManager aggregates by
|
|
786
|
+
// (source, method) regardless of which call returned it.
|
|
787
|
+
let pendingRequestStats: array<Source.requestStat> = []
|
|
788
|
+
let recordRequest = (~method, ~seconds) => {
|
|
789
|
+
pendingRequestStats->Array.push({Source.method, seconds})->ignore
|
|
790
|
+
}
|
|
791
|
+
let drainRequestStats = () => {
|
|
792
|
+
let stats = pendingRequestStats->Utils.Array.copy
|
|
793
|
+
pendingRequestStats->Utils.Array.clearInPlace
|
|
794
|
+
stats
|
|
795
|
+
}
|
|
888
796
|
|
|
889
797
|
let makeTransactionLoader = () =>
|
|
890
798
|
LazyLoader.make(
|
|
891
799
|
~loaderFn=transactionHash => {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
800
|
+
let timerRef = Performance.now()
|
|
801
|
+
Rpc.GetTransactionByHash.rawRoute
|
|
802
|
+
->Rest.fetch(transactionHash, ~client)
|
|
803
|
+
->Promise.thenResolve(res => {
|
|
804
|
+
recordRequest(
|
|
805
|
+
~method="eth_getTransactionByHash",
|
|
806
|
+
~seconds=timerRef->Performance.secondsSince,
|
|
807
|
+
)
|
|
808
|
+
res
|
|
809
|
+
})
|
|
898
810
|
},
|
|
899
811
|
~onError=(am, ~exn) => {
|
|
900
812
|
Logging.error({
|
|
@@ -922,6 +834,7 @@ let make = (
|
|
|
922
834
|
~chain,
|
|
923
835
|
~backoffMsOnFailure=1000,
|
|
924
836
|
~blockNumber,
|
|
837
|
+
~recordRequest,
|
|
925
838
|
)
|
|
926
839
|
},
|
|
927
840
|
~onError=(am, ~exn) => {
|
|
@@ -944,12 +857,16 @@ let make = (
|
|
|
944
857
|
let makeReceiptLoader = () =>
|
|
945
858
|
LazyLoader.make(
|
|
946
859
|
~loaderFn=transactionHash => {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
860
|
+
let timerRef = Performance.now()
|
|
861
|
+
Rpc.GetTransactionReceipt.rawRoute
|
|
862
|
+
->Rest.fetch(transactionHash, ~client)
|
|
863
|
+
->Promise.thenResolve(res => {
|
|
864
|
+
recordRequest(
|
|
865
|
+
~method="eth_getTransactionReceipt",
|
|
866
|
+
~seconds=timerRef->Performance.secondsSince,
|
|
867
|
+
)
|
|
868
|
+
res
|
|
869
|
+
})
|
|
953
870
|
},
|
|
954
871
|
~onError=(am, ~exn) => {
|
|
955
872
|
Logging.error({
|
|
@@ -1000,58 +917,19 @@ let make = (
|
|
|
1000
917
|
~lowercaseAddresses,
|
|
1001
918
|
)
|
|
1002
919
|
|
|
1003
|
-
let convertLogToHyperSyncEvent = (log: Rpc.GetLogs.log): HyperSyncClient.ResponseTypes.event => {
|
|
1004
|
-
let hyperSyncLog: HyperSyncClient.ResponseTypes.log = {
|
|
1005
|
-
removed: log.removed,
|
|
1006
|
-
index: log.logIndex,
|
|
1007
|
-
transactionIndex: log.transactionIndex,
|
|
1008
|
-
transactionHash: log.transactionHash,
|
|
1009
|
-
blockHash: log.blockHash,
|
|
1010
|
-
blockNumber: log.blockNumber,
|
|
1011
|
-
address: log.address,
|
|
1012
|
-
data: log.data,
|
|
1013
|
-
topics: log.topics->(Utils.magic: array<string> => array<Nullable.t<EvmTypes.Hex.t>>),
|
|
1014
|
-
}
|
|
1015
|
-
{log: hyperSyncLog}
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
let hscDecoder: ref<option<HyperSyncClient.Decoder.tWithParams>> = ref(None)
|
|
1019
|
-
let getHscDecoder = () => {
|
|
1020
|
-
switch hscDecoder.contents {
|
|
1021
|
-
| Some(decoder) => decoder
|
|
1022
|
-
| None => {
|
|
1023
|
-
let decoder = HyperSyncClient.Decoder.fromParams(
|
|
1024
|
-
allEventParams,
|
|
1025
|
-
~checksumAddresses=!lowercaseAddresses,
|
|
1026
|
-
)
|
|
1027
|
-
hscDecoder := Some(decoder)
|
|
1028
|
-
decoder
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
920
|
let getItemsOrThrow = async (
|
|
1034
921
|
~fromBlock,
|
|
1035
922
|
~toBlock,
|
|
1036
923
|
~addressesByContractName,
|
|
1037
|
-
~
|
|
924
|
+
~contractNameByAddress,
|
|
1038
925
|
~knownHeight,
|
|
1039
926
|
~partitionId,
|
|
1040
927
|
~selection: FetchState.selection,
|
|
928
|
+
~itemsTarget as _,
|
|
1041
929
|
~retry,
|
|
1042
930
|
~logger as _,
|
|
1043
931
|
) => {
|
|
1044
|
-
let startFetchingBatchTimeRef =
|
|
1045
|
-
|
|
1046
|
-
let suggestedBlockInterval = switch mutSuggestedBlockIntervals->Utils.Dict.dangerouslyGetNonOption(
|
|
1047
|
-
maxSuggestedBlockIntervalKey,
|
|
1048
|
-
) {
|
|
1049
|
-
| Some(maxSuggestedBlockInterval) => maxSuggestedBlockInterval
|
|
1050
|
-
| None =>
|
|
1051
|
-
mutSuggestedBlockIntervals
|
|
1052
|
-
->Utils.Dict.dangerouslyGetNonOption(partitionId)
|
|
1053
|
-
->Option.getOr(syncConfig.initialBlockInterval)
|
|
1054
|
-
}
|
|
932
|
+
let startFetchingBatchTimeRef = Performance.now()
|
|
1055
933
|
|
|
1056
934
|
// Always have a toBlock for an RPC worker
|
|
1057
935
|
let toBlock = switch toBlock {
|
|
@@ -1059,10 +937,6 @@ let make = (
|
|
|
1059
937
|
| None => knownHeight
|
|
1060
938
|
}
|
|
1061
939
|
|
|
1062
|
-
let suggestedToBlock = Pervasives.min(fromBlock + suggestedBlockInterval - 1, toBlock)
|
|
1063
|
-
//Defensively ensure we never query a target block below fromBlock
|
|
1064
|
-
->Pervasives.max(fromBlock)
|
|
1065
|
-
|
|
1066
940
|
let firstBlockParentPromise =
|
|
1067
941
|
fromBlock > 0
|
|
1068
942
|
? blockLoader.contents
|
|
@@ -1070,71 +944,60 @@ let make = (
|
|
|
1070
944
|
->Promise.thenResolve(json => Some(parseBlockInfo(json)))
|
|
1071
945
|
: Promise.resolve(None)
|
|
1072
946
|
|
|
1073
|
-
let {
|
|
1074
|
-
let
|
|
1075
|
-
|
|
1076
|
-
let {logs, latestFetchedBlockInfo} = await getNextPage(
|
|
1077
|
-
~fromBlock,
|
|
1078
|
-
~toBlock=suggestedToBlock,
|
|
1079
|
-
~addresses,
|
|
1080
|
-
~topicQuery,
|
|
1081
|
-
~loadBlock=blockNumber =>
|
|
1082
|
-
blockLoader.contents
|
|
1083
|
-
->LazyLoader.get(blockNumber)
|
|
1084
|
-
->Promise.thenResolve(parseBlockInfo),
|
|
1085
|
-
~syncConfig,
|
|
1086
|
-
~client,
|
|
1087
|
-
~mutSuggestedBlockIntervals,
|
|
1088
|
-
~partitionId,
|
|
1089
|
-
~sourceName=name,
|
|
1090
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
1091
|
-
)
|
|
947
|
+
let {getLogSelectionsOrThrow} = getSelectionConfig(selection)
|
|
948
|
+
let logSelections = getLogSelectionsOrThrow(~addressesByContractName)
|
|
1092
949
|
|
|
1093
|
-
let
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
executedBlockInterval + syncConfig.accelerationAdditive,
|
|
1108
|
-
syncConfig.intervalCeiling,
|
|
950
|
+
let {items, toBlock: queriedToBlock, requestStats} = try await rpcClient.getNextPage({
|
|
951
|
+
fromBlock,
|
|
952
|
+
toBlockCeiling: toBlock,
|
|
953
|
+
logSelections: logSelections->Array.map(({
|
|
954
|
+
addresses,
|
|
955
|
+
topicQuery,
|
|
956
|
+
}): EvmRpcClient.logSelectionInput => {
|
|
957
|
+
?addresses,
|
|
958
|
+
topics: topicQuery->Array.map(filter =>
|
|
959
|
+
switch filter {
|
|
960
|
+
| Rpc.GetLogs.Null => Nullable.null
|
|
961
|
+
| Single(topic) => Nullable.make([topic])
|
|
962
|
+
| Multiple(topics) => Nullable.make(topics)
|
|
963
|
+
}
|
|
1109
964
|
),
|
|
1110
|
-
)
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
// Convert RPC logs to HyperSync events
|
|
1114
|
-
let hyperSyncEvents = logs->Array.map(convertLogToHyperSyncEvent)
|
|
1115
|
-
|
|
1116
|
-
// Decode using HyperSyncClient decoder
|
|
1117
|
-
let parsedEvents = try await getHscDecoder().decodeLogs(hyperSyncEvents) catch {
|
|
965
|
+
}),
|
|
966
|
+
partitionId,
|
|
967
|
+
}) catch {
|
|
1118
968
|
| exn =>
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
969
|
+
switch exn->parseGetNextPageRetryError {
|
|
970
|
+
| Some((attemptedToBlock, retry, requestStats)) =>
|
|
971
|
+
requestStats->Array.forEach(stat =>
|
|
972
|
+
recordRequest(~method=stat.method, ~seconds=stat.seconds)
|
|
973
|
+
)
|
|
974
|
+
throw(Source.GetItemsError(FailedGettingItems({exn, attemptedToBlock, retry})))
|
|
975
|
+
| None =>
|
|
976
|
+
throw(
|
|
977
|
+
Source.GetItemsError(
|
|
978
|
+
FailedGettingItems({
|
|
979
|
+
exn,
|
|
980
|
+
attemptedToBlock: toBlock,
|
|
981
|
+
retry: WithBackoff({
|
|
982
|
+
message: "Unexpected issue while fetching events from the RPC client. Attempt a retry.",
|
|
983
|
+
backoffMillis: switch retry {
|
|
984
|
+
| 0 => 500
|
|
985
|
+
| _ => 1000 * retry
|
|
986
|
+
},
|
|
987
|
+
}),
|
|
1126
988
|
}),
|
|
1127
|
-
|
|
1128
|
-
)
|
|
1129
|
-
|
|
989
|
+
),
|
|
990
|
+
)
|
|
991
|
+
}
|
|
1130
992
|
}
|
|
993
|
+
requestStats->Array.forEach(stat => recordRequest(~method=stat.method, ~seconds=stat.seconds))
|
|
994
|
+
|
|
995
|
+
let latestFetchedBlockInfo = await blockLoader.contents
|
|
996
|
+
->LazyLoader.get(queriedToBlock)
|
|
997
|
+
->Promise.thenResolve(parseBlockInfo)
|
|
1131
998
|
|
|
1132
|
-
let parsedQueueItems = await
|
|
1133
|
-
->Array.
|
|
1134
|
-
->Array.filterMap(((
|
|
1135
|
-
log: Rpc.GetLogs.log,
|
|
1136
|
-
maybeDecodedEvent: Nullable.t<dict<Internal.eventParams>>,
|
|
1137
|
-
)) => {
|
|
999
|
+
let parsedQueueItems = await items
|
|
1000
|
+
->Array.filterMap(({log, params: maybeDecodedEvent}: EvmRpcClient.rpcEventItem) => {
|
|
1138
1001
|
let topic0 = log.topics[0]->Option.getOr("0x0")
|
|
1139
1002
|
let routedAddress = if lowercaseAddresses {
|
|
1140
1003
|
log.address->Address.Evm.fromAddressLowercaseOrThrow
|
|
@@ -1144,12 +1007,15 @@ let make = (
|
|
|
1144
1007
|
|
|
1145
1008
|
switch eventRouter->EventRouter.get(
|
|
1146
1009
|
~tag=EventRouter.getEvmEventId(~sighash=topic0, ~topicCount=log.topics->Array.length),
|
|
1147
|
-
~
|
|
1010
|
+
~contractNameByAddress,
|
|
1148
1011
|
~contractAddress=routedAddress,
|
|
1149
|
-
~blockNumber=log.blockNumber,
|
|
1150
1012
|
) {
|
|
1151
1013
|
| None => None
|
|
1152
|
-
| Some(
|
|
1014
|
+
| Some(onEventRegistration) =>
|
|
1015
|
+
let eventConfig =
|
|
1016
|
+
onEventRegistration.eventConfig->(
|
|
1017
|
+
Utils.magic: Internal.eventConfig => Internal.evmEventConfig
|
|
1018
|
+
)
|
|
1153
1019
|
switch maybeDecodedEvent
|
|
1154
1020
|
->Nullable.toOption
|
|
1155
1021
|
->Option.flatMap(Dict.get(_, eventConfig.contractName)) {
|
|
@@ -1160,7 +1026,9 @@ let make = (
|
|
|
1160
1026
|
let (block, transaction) = try await Promise.all2((
|
|
1161
1027
|
log->getEventBlockOrThrow(~selectedBlockFields=eventConfig.selectedBlockFields),
|
|
1162
1028
|
log->getEventTransactionOrThrow(
|
|
1163
|
-
~selectedTransactionFields=eventConfig.selectedTransactionFields
|
|
1029
|
+
~selectedTransactionFields=eventConfig.selectedTransactionFields->(
|
|
1030
|
+
Utils.magic: Utils.Set.t<string> => Utils.Set.t<Internal.evmTransactionField>
|
|
1031
|
+
),
|
|
1164
1032
|
),
|
|
1165
1033
|
)) catch {
|
|
1166
1034
|
| TransactionDataNotFound({message}) =>
|
|
@@ -1194,19 +1062,18 @@ let make = (
|
|
|
1194
1062
|
}
|
|
1195
1063
|
|
|
1196
1064
|
Internal.Event({
|
|
1197
|
-
|
|
1198
|
-
timestamp: block->getBlockTimestamp,
|
|
1065
|
+
onEventRegistration: (onEventRegistration :> Internal.onEventRegistration),
|
|
1199
1066
|
blockNumber: block->getBlockNumber,
|
|
1200
|
-
blockHash: block->getBlockHash,
|
|
1201
1067
|
chain,
|
|
1202
1068
|
logIndex: log.logIndex,
|
|
1069
|
+
transactionIndex: log.transactionIndex,
|
|
1203
1070
|
payload: {
|
|
1204
1071
|
contractName: eventConfig.contractName,
|
|
1205
1072
|
eventName: eventConfig.name,
|
|
1206
1073
|
chainId: chain->ChainMap.Chain.toChainId,
|
|
1207
1074
|
params: decoded,
|
|
1208
|
-
transaction,
|
|
1209
1075
|
block,
|
|
1076
|
+
transaction,
|
|
1210
1077
|
srcAddress: routedAddress,
|
|
1211
1078
|
logIndex: log.logIndex,
|
|
1212
1079
|
}->Evm.fromPayload,
|
|
@@ -1222,7 +1089,7 @@ let make = (
|
|
|
1222
1089
|
|
|
1223
1090
|
let optFirstBlockParent = await firstBlockParentPromise
|
|
1224
1091
|
|
|
1225
|
-
let totalTimeElapsed = startFetchingBatchTimeRef->
|
|
1092
|
+
let totalTimeElapsed = startFetchingBatchTimeRef->Performance.secondsSince
|
|
1226
1093
|
|
|
1227
1094
|
// Every fetched block carries `hash` and `parentHash`, so each one yields
|
|
1228
1095
|
// two confirmed (number, hash) pairs for reorg detection at no extra cost.
|
|
@@ -1240,7 +1107,7 @@ let make = (
|
|
|
1240
1107
|
| Some(b) => pushBlockInfo(b)
|
|
1241
1108
|
| None => ()
|
|
1242
1109
|
}
|
|
1243
|
-
|
|
1110
|
+
items->Array.forEach(({log}) =>
|
|
1244
1111
|
blockHashes
|
|
1245
1112
|
->Array.push({ReorgDetection.blockNumber: log.blockNumber, blockHash: log.blockHash})
|
|
1246
1113
|
->ignore
|
|
@@ -1250,12 +1117,16 @@ let make = (
|
|
|
1250
1117
|
latestFetchedBlockTimestamp: latestFetchedBlockInfo.timestamp,
|
|
1251
1118
|
latestFetchedBlockNumber: latestFetchedBlockInfo.number,
|
|
1252
1119
|
parsedQueueItems,
|
|
1120
|
+
// RPC keeps the transaction and block inline on the payload; no store pages.
|
|
1121
|
+
transactionStore: None,
|
|
1122
|
+
blockStore: None,
|
|
1253
1123
|
stats: {
|
|
1254
1124
|
totalTimeElapsed: totalTimeElapsed,
|
|
1255
1125
|
},
|
|
1256
1126
|
knownHeight,
|
|
1257
1127
|
blockHashes,
|
|
1258
1128
|
fromBlockQueried: fromBlock,
|
|
1129
|
+
requestStats: drainRequestStats(),
|
|
1259
1130
|
}
|
|
1260
1131
|
}
|
|
1261
1132
|
|
|
@@ -1272,21 +1143,25 @@ let make = (
|
|
|
1272
1143
|
->Array.map(blockNum => blockLoader.contents->LazyLoader.get(blockNum))
|
|
1273
1144
|
->Promise.all
|
|
1274
1145
|
->Promise.thenResolve(rawBlocks => {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1146
|
+
let result =
|
|
1147
|
+
rawBlocks
|
|
1148
|
+
->Array.map(json => {
|
|
1149
|
+
let b = parseBlockInfo(json)
|
|
1150
|
+
|
|
1151
|
+
(
|
|
1152
|
+
{
|
|
1153
|
+
blockNumber: b.number,
|
|
1154
|
+
blockHash: b.hash,
|
|
1155
|
+
blockTimestamp: b.timestamp,
|
|
1156
|
+
}: ReorgDetection.blockDataWithTimestamp
|
|
1157
|
+
)
|
|
1158
|
+
})
|
|
1159
|
+
->Ok
|
|
1160
|
+
{Source.result, requestStats: drainRequestStats()}
|
|
1288
1161
|
})
|
|
1289
|
-
->Promise.catch(exn =>
|
|
1162
|
+
->Promise.catch(exn =>
|
|
1163
|
+
{Source.result: Error(exn), requestStats: drainRequestStats()}->Promise.resolve
|
|
1164
|
+
)
|
|
1290
1165
|
}
|
|
1291
1166
|
|
|
1292
1167
|
let createHeightSubscription =
|
|
@@ -1303,38 +1178,16 @@ let make = (
|
|
|
1303
1178
|
getBlockHashes,
|
|
1304
1179
|
onReorg,
|
|
1305
1180
|
getHeightOrThrow: async () => {
|
|
1306
|
-
let timerRef =
|
|
1181
|
+
let timerRef = Performance.now()
|
|
1307
1182
|
let height = try {
|
|
1308
1183
|
await rpcClient.getHeight()
|
|
1309
1184
|
} catch {
|
|
1310
1185
|
| exn =>
|
|
1311
|
-
|
|
1312
|
-
Prometheus.SourceRequestCount.increment(
|
|
1313
|
-
~sourceName=name,
|
|
1314
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
1315
|
-
~method="eth_blockNumber",
|
|
1316
|
-
)
|
|
1317
|
-
Prometheus.SourceRequestCount.addSeconds(
|
|
1318
|
-
~sourceName=name,
|
|
1319
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
1320
|
-
~method="eth_blockNumber",
|
|
1321
|
-
~seconds,
|
|
1322
|
-
)
|
|
1186
|
+
recordRequest(~method="eth_blockNumber", ~seconds=timerRef->Performance.secondsSince)
|
|
1323
1187
|
exn->throw
|
|
1324
1188
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
~sourceName=name,
|
|
1328
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
1329
|
-
~method="eth_blockNumber",
|
|
1330
|
-
)
|
|
1331
|
-
Prometheus.SourceRequestCount.addSeconds(
|
|
1332
|
-
~sourceName=name,
|
|
1333
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
1334
|
-
~method="eth_blockNumber",
|
|
1335
|
-
~seconds,
|
|
1336
|
-
)
|
|
1337
|
-
height
|
|
1189
|
+
recordRequest(~method="eth_blockNumber", ~seconds=timerRef->Performance.secondsSince)
|
|
1190
|
+
{height, requestStats: drainRequestStats()}
|
|
1338
1191
|
},
|
|
1339
1192
|
getItemsOrThrow,
|
|
1340
1193
|
?createHeightSubscription,
|