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
|
@@ -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,328 +65,98 @@ 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
|
-
| Some(execResult) => extractBlockRange(execResult, ~isMaxRange=true)
|
|
172
|
-
| None =>
|
|
173
|
-
switch baseRangeRegExp->RegExp.exec(message) {
|
|
174
|
-
| Some(_) => Some(2000, true)
|
|
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
|
+
|
|
101
|
+
// `EvmRpcClient.getNextPage` throws a napi error whose message is a JSON
|
|
102
|
+
// payload describing the retry decision:
|
|
103
|
+
// `{"kind":"Retry","attemptedToBlock":int,"errorMessage":string|null,
|
|
104
|
+
// "requestStats":[{"method":string,"seconds":float}],"retry":
|
|
105
|
+
// {"tag":"WithSuggestedToBlock","toBlock":int} |
|
|
106
|
+
// {"tag":"WithBackoff","message":string,"backoffMillis":int}}`.
|
|
107
|
+
let parseGetNextPageRetryError = (exn: exn): option<(
|
|
108
|
+
int,
|
|
109
|
+
Source.getItemsRetry,
|
|
110
|
+
array<Source.requestStat>,
|
|
111
|
+
)> =>
|
|
112
|
+
switch exn {
|
|
113
|
+
| JsExn(e) =>
|
|
114
|
+
switch e->JsExn.message {
|
|
115
|
+
| Some(msg) =>
|
|
116
|
+
switch msg->JSON.parseOrThrow->JSON.Decode.object {
|
|
117
|
+
| exception _ => None
|
|
118
|
+
| None => None
|
|
119
|
+
| Some(obj) =>
|
|
120
|
+
switch (obj->Dict.get("kind"), obj->Dict.get("attemptedToBlock"), obj->Dict.get("retry")) {
|
|
121
|
+
| (Some(String("Retry")), Some(Number(attemptedToBlock)), Some(Object(retryObj))) =>
|
|
122
|
+
let requestStats = switch obj->Dict.get("requestStats") {
|
|
123
|
+
| Some(Array(stats)) =>
|
|
124
|
+
stats->Array.filterMap(s =>
|
|
125
|
+
switch s->JSON.Decode.object {
|
|
126
|
+
| Some(o) =>
|
|
127
|
+
switch (o->Dict.get("method"), o->Dict.get("seconds")) {
|
|
128
|
+
| (Some(String(method)), Some(Number(seconds))) => Some({Source.method, seconds})
|
|
129
|
+
| _ => None
|
|
197
130
|
}
|
|
131
|
+
| None => None
|
|
198
132
|
}
|
|
133
|
+
)
|
|
134
|
+
| _ => []
|
|
135
|
+
}
|
|
136
|
+
let retry = switch retryObj->Dict.get("tag") {
|
|
137
|
+
| Some(String("WithSuggestedToBlock")) =>
|
|
138
|
+
switch retryObj->Dict.get("toBlock") {
|
|
139
|
+
| Some(Number(toBlock)) =>
|
|
140
|
+
Some(Source.WithSuggestedToBlock({toBlock: toBlock->Float.toInt}))
|
|
141
|
+
| _ => None
|
|
199
142
|
}
|
|
143
|
+
| Some(String("WithBackoff")) =>
|
|
144
|
+
switch (retryObj->Dict.get("message"), retryObj->Dict.get("backoffMillis")) {
|
|
145
|
+
| (Some(String(message)), Some(Number(backoffMillis))) =>
|
|
146
|
+
Some(Source.WithBackoff({message, backoffMillis: backoffMillis->Float.toInt}))
|
|
147
|
+
| _ => None
|
|
148
|
+
}
|
|
149
|
+
| _ => None
|
|
200
150
|
}
|
|
151
|
+
retry->Option.map(retry => (attemptedToBlock->Float.toInt, retry, requestStats))
|
|
152
|
+
| _ => None
|
|
201
153
|
}
|
|
202
154
|
}
|
|
155
|
+
| None => None
|
|
203
156
|
}
|
|
157
|
+
| _ => None
|
|
204
158
|
}
|
|
205
159
|
|
|
206
|
-
(exn): option<(
|
|
207
|
-
// The suggested block range
|
|
208
|
-
int,
|
|
209
|
-
// Whether it's the max range that the provider allows
|
|
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,
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
let maxSuggestedBlockIntervalKey = "max"
|
|
232
|
-
|
|
233
|
-
let getNextPage = (
|
|
234
|
-
~fromBlock,
|
|
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
|
-
)
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
let latestFetchedBlockPromise = loadBlock(toBlock)
|
|
255
|
-
Prometheus.SourceRequestCount.increment(~sourceName, ~chainId, ~method="eth_getLogs")
|
|
256
|
-
let logsPromise = Rpc.getLogs(
|
|
257
|
-
~client,
|
|
258
|
-
~param={
|
|
259
|
-
address: ?addresses,
|
|
260
|
-
topics: topicQuery,
|
|
261
|
-
fromBlock,
|
|
262
|
-
toBlock,
|
|
263
|
-
},
|
|
264
|
-
)->Promise.then(async logs => {
|
|
265
|
-
{
|
|
266
|
-
logs,
|
|
267
|
-
latestFetchedBlockInfo: await latestFetchedBlockPromise,
|
|
268
|
-
}
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
[queryTimoutPromise, logsPromise]
|
|
272
|
-
->Promise.race
|
|
273
|
-
->Promise.catch(err => {
|
|
274
|
-
switch getSuggestedBlockIntervalFromExn(err) {
|
|
275
|
-
| Some((nextBlockIntervalTry, isMaxRange)) =>
|
|
276
|
-
mutSuggestedBlockIntervals->Dict.set(
|
|
277
|
-
isMaxRange ? maxSuggestedBlockIntervalKey : partitionId,
|
|
278
|
-
nextBlockIntervalTry,
|
|
279
|
-
)
|
|
280
|
-
throw(
|
|
281
|
-
Source.GetItemsError(
|
|
282
|
-
FailedGettingItems({
|
|
283
|
-
exn: err,
|
|
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
|
-
),
|
|
307
|
-
)
|
|
308
|
-
}
|
|
309
|
-
})
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
type logSelection = {
|
|
313
|
-
addresses: option<array<Address.t>>,
|
|
314
|
-
topicQuery: Rpc.GetLogs.topicQuery,
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
type selectionConfig = {
|
|
318
|
-
getLogSelectionOrThrow: (~addressesByContractName: dict<array<Address.t>>) => logSelection,
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
let getSelectionConfig = (selection: FetchState.selection, ~chain) => {
|
|
322
|
-
let staticTopicSelections = []
|
|
323
|
-
let dynamicEventFilters = []
|
|
324
|
-
|
|
325
|
-
selection.eventConfigs
|
|
326
|
-
->(Utils.magic: array<Internal.eventConfig> => array<Internal.evmEventConfig>)
|
|
327
|
-
->Array.forEach(({getEventFiltersOrThrow}) => {
|
|
328
|
-
switch getEventFiltersOrThrow(chain) {
|
|
329
|
-
| Static(s) => staticTopicSelections->Array.pushMany(s)->ignore
|
|
330
|
-
| Dynamic(fn) => dynamicEventFilters->Array.push(fn)->ignore
|
|
331
|
-
}
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
let getLogSelectionOrThrow = switch (
|
|
335
|
-
staticTopicSelections->LogSelection.compressTopicSelections,
|
|
336
|
-
dynamicEventFilters,
|
|
337
|
-
) {
|
|
338
|
-
| ([], []) =>
|
|
339
|
-
throw(
|
|
340
|
-
Source.GetItemsError(
|
|
341
|
-
UnsupportedSelection({
|
|
342
|
-
message: "Invalid events configuration for the partition. Nothing to fetch. Please, report to the Envio team.",
|
|
343
|
-
}),
|
|
344
|
-
),
|
|
345
|
-
)
|
|
346
|
-
| ([topicSelection], []) => {
|
|
347
|
-
let topicQuery = topicSelection->Rpc.GetLogs.mapTopicQuery
|
|
348
|
-
(~addressesByContractName) => {
|
|
349
|
-
addresses: switch addressesByContractName->FetchState.addressesByContractNameGetAll {
|
|
350
|
-
| [] => None
|
|
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
|
-
}
|
|
375
|
-
}
|
|
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
|
-
}
|
|
385
|
-
|
|
386
|
-
{
|
|
387
|
-
getLogSelectionOrThrow: getLogSelectionOrThrow,
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
let memoGetSelectionConfig = (~chain) =>
|
|
392
|
-
Utils.WeakMap.memoize(selection => selection->getSelectionConfig(~chain))
|
|
393
|
-
|
|
394
160
|
// Type-erase a schema for storage in the field registry
|
|
395
161
|
external toFieldSchema: S.t<'a> => S.t<JSON.t> = "%identity"
|
|
396
162
|
|
|
@@ -531,11 +297,9 @@ let makeThrowingGetEventBlock = (
|
|
|
531
297
|
}
|
|
532
298
|
}
|
|
533
299
|
|
|
534
|
-
// `number
|
|
535
|
-
//
|
|
300
|
+
// `number` is always part of the selected block fields, so it can be read
|
|
301
|
+
// from the assembled block for the item's own `blockNumber`.
|
|
536
302
|
@get external getBlockNumber: Internal.eventBlock => int = "number"
|
|
537
|
-
@get external getBlockTimestamp: Internal.eventBlock => int = "timestamp"
|
|
538
|
-
@get external getBlockHash: Internal.eventBlock => string = "hash"
|
|
539
303
|
|
|
540
304
|
// Field source classification for RPC calls
|
|
541
305
|
type fieldSource = TransactionOnly | ReceiptOnly | Both
|
|
@@ -851,10 +615,11 @@ type options = {
|
|
|
851
615
|
syncConfig: Config.sourceSync,
|
|
852
616
|
url: string,
|
|
853
617
|
chain: ChainMap.Chain.t,
|
|
854
|
-
|
|
855
|
-
|
|
618
|
+
// The chain's registrations, indexed by their sequential `index`.
|
|
619
|
+
onEventRegistrations: array<Internal.evmOnEventRegistration>,
|
|
856
620
|
lowercaseAddresses: bool,
|
|
857
621
|
ws?: string,
|
|
622
|
+
headers?: dict<string>,
|
|
858
623
|
}
|
|
859
624
|
|
|
860
625
|
let make = (
|
|
@@ -863,10 +628,10 @@ let make = (
|
|
|
863
628
|
syncConfig,
|
|
864
629
|
url,
|
|
865
630
|
chain,
|
|
866
|
-
|
|
867
|
-
allEventParams,
|
|
631
|
+
onEventRegistrations,
|
|
868
632
|
lowercaseAddresses,
|
|
869
633
|
?ws,
|
|
634
|
+
?headers,
|
|
870
635
|
}: options,
|
|
871
636
|
): t => {
|
|
872
637
|
let chainId = chain->ChainMap.Chain.toChainId
|
|
@@ -879,22 +644,48 @@ let make = (
|
|
|
879
644
|
}
|
|
880
645
|
let name = `RPC (${urlHost})`
|
|
881
646
|
|
|
882
|
-
let
|
|
883
|
-
|
|
884
|
-
|
|
647
|
+
let client = Rpc.makeClient(url, ~headers?)
|
|
648
|
+
let rpcClient = EvmRpcClient.make(
|
|
649
|
+
~url,
|
|
650
|
+
~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(
|
|
651
|
+
onEventRegistrations,
|
|
652
|
+
),
|
|
653
|
+
~checksumAddresses=!lowercaseAddresses,
|
|
654
|
+
~syncConfig,
|
|
655
|
+
~headers?,
|
|
656
|
+
)
|
|
885
657
|
|
|
886
|
-
|
|
887
|
-
|
|
658
|
+
// Requests are made from shared, memoized loaders, so they can't be
|
|
659
|
+
// attributed to a single getItemsOrThrow/getHeightOrThrow/getBlockHashes
|
|
660
|
+
// call at its call site. Every actual request (cache/dedup hits never reach
|
|
661
|
+
// recordRequest) pushes here; each method drains whatever is pending when it
|
|
662
|
+
// returns. Since a push always lands in exactly one drain, per-source totals
|
|
663
|
+
// stay exact even with concurrent in-flight calls — which call happens to
|
|
664
|
+
// drain a given entry doesn't matter, since SourceManager aggregates by
|
|
665
|
+
// (source, method) regardless of which call returned it.
|
|
666
|
+
let pendingRequestStats: array<Source.requestStat> = []
|
|
667
|
+
let recordRequest = (~method, ~seconds) => {
|
|
668
|
+
pendingRequestStats->Array.push({Source.method, seconds})->ignore
|
|
669
|
+
}
|
|
670
|
+
let drainRequestStats = () => {
|
|
671
|
+
let stats = pendingRequestStats->Utils.Array.copy
|
|
672
|
+
pendingRequestStats->Utils.Array.clearInPlace
|
|
673
|
+
stats
|
|
674
|
+
}
|
|
888
675
|
|
|
889
676
|
let makeTransactionLoader = () =>
|
|
890
677
|
LazyLoader.make(
|
|
891
678
|
~loaderFn=transactionHash => {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
679
|
+
let timerRef = Performance.now()
|
|
680
|
+
Rpc.GetTransactionByHash.rawRoute
|
|
681
|
+
->Rest.fetch(transactionHash, ~client)
|
|
682
|
+
->Promise.thenResolve(res => {
|
|
683
|
+
recordRequest(
|
|
684
|
+
~method="eth_getTransactionByHash",
|
|
685
|
+
~seconds=timerRef->Performance.secondsSince,
|
|
686
|
+
)
|
|
687
|
+
res
|
|
688
|
+
})
|
|
898
689
|
},
|
|
899
690
|
~onError=(am, ~exn) => {
|
|
900
691
|
Logging.error({
|
|
@@ -922,6 +713,7 @@ let make = (
|
|
|
922
713
|
~chain,
|
|
923
714
|
~backoffMsOnFailure=1000,
|
|
924
715
|
~blockNumber,
|
|
716
|
+
~recordRequest,
|
|
925
717
|
)
|
|
926
718
|
},
|
|
927
719
|
~onError=(am, ~exn) => {
|
|
@@ -944,12 +736,16 @@ let make = (
|
|
|
944
736
|
let makeReceiptLoader = () =>
|
|
945
737
|
LazyLoader.make(
|
|
946
738
|
~loaderFn=transactionHash => {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
739
|
+
let timerRef = Performance.now()
|
|
740
|
+
Rpc.GetTransactionReceipt.rawRoute
|
|
741
|
+
->Rest.fetch(transactionHash, ~client)
|
|
742
|
+
->Promise.thenResolve(res => {
|
|
743
|
+
recordRequest(
|
|
744
|
+
~method="eth_getTransactionReceipt",
|
|
745
|
+
~seconds=timerRef->Performance.secondsSince,
|
|
746
|
+
)
|
|
747
|
+
res
|
|
748
|
+
})
|
|
953
749
|
},
|
|
954
750
|
~onError=(am, ~exn) => {
|
|
955
751
|
Logging.error({
|
|
@@ -1000,58 +796,19 @@ let make = (
|
|
|
1000
796
|
~lowercaseAddresses,
|
|
1001
797
|
)
|
|
1002
798
|
|
|
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
799
|
let getItemsOrThrow = async (
|
|
1034
800
|
~fromBlock,
|
|
1035
801
|
~toBlock,
|
|
1036
802
|
~addressesByContractName,
|
|
1037
|
-
~
|
|
803
|
+
~contractNameByAddress as _,
|
|
1038
804
|
~knownHeight,
|
|
1039
805
|
~partitionId,
|
|
1040
806
|
~selection: FetchState.selection,
|
|
807
|
+
~itemsTarget as _,
|
|
1041
808
|
~retry,
|
|
1042
809
|
~logger as _,
|
|
1043
810
|
) => {
|
|
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
|
-
}
|
|
811
|
+
let startFetchingBatchTimeRef = Performance.now()
|
|
1055
812
|
|
|
1056
813
|
// Always have a toBlock for an RPC worker
|
|
1057
814
|
let toBlock = switch toBlock {
|
|
@@ -1059,10 +816,6 @@ let make = (
|
|
|
1059
816
|
| None => knownHeight
|
|
1060
817
|
}
|
|
1061
818
|
|
|
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
819
|
let firstBlockParentPromise =
|
|
1067
820
|
fromBlock > 0
|
|
1068
821
|
? blockLoader.contents
|
|
@@ -1070,97 +823,70 @@ let make = (
|
|
|
1070
823
|
->Promise.thenResolve(json => Some(parseBlockInfo(json)))
|
|
1071
824
|
: Promise.resolve(None)
|
|
1072
825
|
|
|
1073
|
-
|
|
1074
|
-
let {addresses, topicQuery} = getLogSelectionOrThrow(~addressesByContractName)
|
|
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
|
-
)
|
|
1092
|
-
|
|
1093
|
-
let executedBlockInterval = suggestedToBlock - fromBlock + 1
|
|
1094
|
-
|
|
1095
|
-
// Increase the suggested block interval only when it was actually applied
|
|
1096
|
-
// and we didn't query to a hard toBlock
|
|
1097
|
-
// We also don't care about it when we have a hard max block interval
|
|
1098
|
-
if (
|
|
1099
|
-
executedBlockInterval >= suggestedBlockInterval &&
|
|
1100
|
-
!(mutSuggestedBlockIntervals->Dict.has(maxSuggestedBlockIntervalKey))
|
|
1101
|
-
) {
|
|
1102
|
-
// Increase batch size going forward, but do not increase past a configured maximum
|
|
1103
|
-
// See: https://en.wikipedia.org/wiki/Additive_increase/multiplicative_decrease
|
|
1104
|
-
mutSuggestedBlockIntervals->Dict.set(
|
|
1105
|
-
partitionId,
|
|
1106
|
-
Pervasives.min(
|
|
1107
|
-
executedBlockInterval + syncConfig.accelerationAdditive,
|
|
1108
|
-
syncConfig.intervalCeiling,
|
|
1109
|
-
),
|
|
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 {
|
|
1118
|
-
| exn =>
|
|
826
|
+
if selection.onEventRegistrations->Utils.Array.isEmpty {
|
|
1119
827
|
throw(
|
|
1120
828
|
Source.GetItemsError(
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
attemptedToBlock: toBlock,
|
|
1124
|
-
retry: ImpossibleForTheQuery({
|
|
1125
|
-
message: "Failed to parse events using hypersync client decoder. Please double-check your ABI.",
|
|
1126
|
-
}),
|
|
829
|
+
UnsupportedSelection({
|
|
830
|
+
message: "Invalid events configuration for the partition. Nothing to fetch. Please, report to the Envio team.",
|
|
1127
831
|
}),
|
|
1128
832
|
),
|
|
1129
833
|
)
|
|
1130
834
|
}
|
|
1131
835
|
|
|
1132
|
-
let
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
836
|
+
let {items, toBlock: queriedToBlock, requestStats} = try await rpcClient.getNextPage({
|
|
837
|
+
fromBlock,
|
|
838
|
+
toBlockCeiling: toBlock,
|
|
839
|
+
partitionId,
|
|
840
|
+
registrationIndexes: selection.onEventRegistrations->Array.map(reg => reg.index),
|
|
841
|
+
addressesByContractName,
|
|
842
|
+
}) catch {
|
|
843
|
+
| exn =>
|
|
844
|
+
switch exn->parseGetNextPageRetryError {
|
|
845
|
+
| Some((attemptedToBlock, retry, requestStats)) =>
|
|
846
|
+
requestStats->Array.forEach(stat =>
|
|
847
|
+
recordRequest(~method=stat.method, ~seconds=stat.seconds)
|
|
848
|
+
)
|
|
849
|
+
throw(Source.GetItemsError(FailedGettingItems({exn, attemptedToBlock, retry})))
|
|
850
|
+
| None =>
|
|
851
|
+
throw(
|
|
852
|
+
Source.GetItemsError(
|
|
853
|
+
FailedGettingItems({
|
|
854
|
+
exn,
|
|
855
|
+
attemptedToBlock: toBlock,
|
|
856
|
+
retry: WithBackoff({
|
|
857
|
+
message: "Unexpected issue while fetching events from the RPC client. Attempt a retry.",
|
|
858
|
+
backoffMillis: switch retry {
|
|
859
|
+
| 0 => 500
|
|
860
|
+
| _ => 1000 * retry
|
|
861
|
+
},
|
|
862
|
+
}),
|
|
863
|
+
}),
|
|
864
|
+
),
|
|
865
|
+
)
|
|
1143
866
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
867
|
+
}
|
|
868
|
+
requestStats->Array.forEach(stat => recordRequest(~method=stat.method, ~seconds=stat.seconds))
|
|
869
|
+
|
|
870
|
+
let latestFetchedBlockInfo = await blockLoader.contents
|
|
871
|
+
->LazyLoader.get(queriedToBlock)
|
|
872
|
+
->Promise.thenResolve(parseBlockInfo)
|
|
873
|
+
|
|
874
|
+
let parsedQueueItems = await items
|
|
875
|
+
->Array.map(({log, onEventRegistrationIndex, params: decoded}: EvmRpcClient.rpcEventItem) => {
|
|
876
|
+
// `log.address` comes back already normalized to the client's casing.
|
|
877
|
+
let onEventRegistration = onEventRegistrations->Array.getUnsafe(onEventRegistrationIndex)
|
|
878
|
+
let eventConfig =
|
|
879
|
+
onEventRegistration.eventConfig->(
|
|
880
|
+
Utils.magic: Internal.eventConfig => Internal.evmEventConfig
|
|
881
|
+
)
|
|
882
|
+
(
|
|
883
|
+
async () => {
|
|
1160
884
|
let (block, transaction) = try await Promise.all2((
|
|
1161
885
|
log->getEventBlockOrThrow(~selectedBlockFields=eventConfig.selectedBlockFields),
|
|
1162
886
|
log->getEventTransactionOrThrow(
|
|
1163
|
-
~selectedTransactionFields=eventConfig.selectedTransactionFields
|
|
887
|
+
~selectedTransactionFields=eventConfig.selectedTransactionFields->(
|
|
888
|
+
Utils.magic: Utils.Set.t<string> => Utils.Set.t<Internal.evmTransactionField>
|
|
889
|
+
),
|
|
1164
890
|
),
|
|
1165
891
|
)) catch {
|
|
1166
892
|
| TransactionDataNotFound({message}) =>
|
|
@@ -1194,35 +920,30 @@ let make = (
|
|
|
1194
920
|
}
|
|
1195
921
|
|
|
1196
922
|
Internal.Event({
|
|
1197
|
-
|
|
1198
|
-
timestamp: block->getBlockTimestamp,
|
|
923
|
+
onEventRegistration: (onEventRegistration :> Internal.onEventRegistration),
|
|
1199
924
|
blockNumber: block->getBlockNumber,
|
|
1200
|
-
blockHash: block->getBlockHash,
|
|
1201
925
|
chain,
|
|
1202
926
|
logIndex: log.logIndex,
|
|
927
|
+
transactionIndex: log.transactionIndex,
|
|
1203
928
|
payload: {
|
|
1204
929
|
contractName: eventConfig.contractName,
|
|
1205
930
|
eventName: eventConfig.name,
|
|
1206
931
|
chainId: chain->ChainMap.Chain.toChainId,
|
|
1207
932
|
params: decoded,
|
|
1208
|
-
transaction,
|
|
1209
933
|
block,
|
|
1210
|
-
|
|
934
|
+
transaction,
|
|
935
|
+
srcAddress: log.address,
|
|
1211
936
|
logIndex: log.logIndex,
|
|
1212
937
|
}->Evm.fromPayload,
|
|
1213
938
|
})
|
|
1214
|
-
}
|
|
1215
|
-
)(),
|
|
1216
|
-
)
|
|
1217
|
-
| None => None
|
|
1218
939
|
}
|
|
1219
|
-
|
|
940
|
+
)()
|
|
1220
941
|
})
|
|
1221
942
|
->Promise.all
|
|
1222
943
|
|
|
1223
944
|
let optFirstBlockParent = await firstBlockParentPromise
|
|
1224
945
|
|
|
1225
|
-
let totalTimeElapsed = startFetchingBatchTimeRef->
|
|
946
|
+
let totalTimeElapsed = startFetchingBatchTimeRef->Performance.secondsSince
|
|
1226
947
|
|
|
1227
948
|
// Every fetched block carries `hash` and `parentHash`, so each one yields
|
|
1228
949
|
// two confirmed (number, hash) pairs for reorg detection at no extra cost.
|
|
@@ -1240,7 +961,7 @@ let make = (
|
|
|
1240
961
|
| Some(b) => pushBlockInfo(b)
|
|
1241
962
|
| None => ()
|
|
1242
963
|
}
|
|
1243
|
-
|
|
964
|
+
items->Array.forEach(({log}) =>
|
|
1244
965
|
blockHashes
|
|
1245
966
|
->Array.push({ReorgDetection.blockNumber: log.blockNumber, blockHash: log.blockHash})
|
|
1246
967
|
->ignore
|
|
@@ -1250,12 +971,16 @@ let make = (
|
|
|
1250
971
|
latestFetchedBlockTimestamp: latestFetchedBlockInfo.timestamp,
|
|
1251
972
|
latestFetchedBlockNumber: latestFetchedBlockInfo.number,
|
|
1252
973
|
parsedQueueItems,
|
|
974
|
+
// RPC keeps the transaction and block inline on the payload; no store pages.
|
|
975
|
+
transactionStore: None,
|
|
976
|
+
blockStore: None,
|
|
1253
977
|
stats: {
|
|
1254
978
|
totalTimeElapsed: totalTimeElapsed,
|
|
1255
979
|
},
|
|
1256
980
|
knownHeight,
|
|
1257
981
|
blockHashes,
|
|
1258
982
|
fromBlockQueried: fromBlock,
|
|
983
|
+
requestStats: drainRequestStats(),
|
|
1259
984
|
}
|
|
1260
985
|
}
|
|
1261
986
|
|
|
@@ -1272,21 +997,25 @@ let make = (
|
|
|
1272
997
|
->Array.map(blockNum => blockLoader.contents->LazyLoader.get(blockNum))
|
|
1273
998
|
->Promise.all
|
|
1274
999
|
->Promise.thenResolve(rawBlocks => {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1000
|
+
let result =
|
|
1001
|
+
rawBlocks
|
|
1002
|
+
->Array.map(json => {
|
|
1003
|
+
let b = parseBlockInfo(json)
|
|
1278
1004
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1005
|
+
(
|
|
1006
|
+
{
|
|
1007
|
+
blockNumber: b.number,
|
|
1008
|
+
blockHash: b.hash,
|
|
1009
|
+
blockTimestamp: b.timestamp,
|
|
1010
|
+
}: ReorgDetection.blockDataWithTimestamp
|
|
1011
|
+
)
|
|
1012
|
+
})
|
|
1013
|
+
->Ok
|
|
1014
|
+
{Source.result, requestStats: drainRequestStats()}
|
|
1288
1015
|
})
|
|
1289
|
-
->Promise.catch(exn =>
|
|
1016
|
+
->Promise.catch(exn =>
|
|
1017
|
+
{Source.result: Error(exn), requestStats: drainRequestStats()}->Promise.resolve
|
|
1018
|
+
)
|
|
1290
1019
|
}
|
|
1291
1020
|
|
|
1292
1021
|
let createHeightSubscription =
|
|
@@ -1303,38 +1032,16 @@ let make = (
|
|
|
1303
1032
|
getBlockHashes,
|
|
1304
1033
|
onReorg,
|
|
1305
1034
|
getHeightOrThrow: async () => {
|
|
1306
|
-
let timerRef =
|
|
1035
|
+
let timerRef = Performance.now()
|
|
1307
1036
|
let height = try {
|
|
1308
1037
|
await rpcClient.getHeight()
|
|
1309
1038
|
} catch {
|
|
1310
1039
|
| 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
|
-
)
|
|
1040
|
+
recordRequest(~method="eth_blockNumber", ~seconds=timerRef->Performance.secondsSince)
|
|
1323
1041
|
exn->throw
|
|
1324
1042
|
}
|
|
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
|
|
1043
|
+
recordRequest(~method="eth_blockNumber", ~seconds=timerRef->Performance.secondsSince)
|
|
1044
|
+
{height, requestStats: drainRequestStats()}
|
|
1338
1045
|
},
|
|
1339
1046
|
getItemsOrThrow,
|
|
1340
1047
|
?createHeightSubscription,
|