envio 3.6.1 → 3.7.0-subgraph
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/index.d.ts +197 -9
- package/package.json +6 -6
- package/src/Batch.res +64 -25
- package/src/Batch.res.mjs +69 -46
- package/src/ChainFetching.res +13 -17
- package/src/ChainFetching.res.mjs +9 -14
- package/src/ChainState.res +160 -119
- package/src/ChainState.res.mjs +75 -46
- package/src/ChainState.resi +15 -12
- package/src/Config.res +18 -2
- package/src/Config.res.mjs +9 -6
- package/src/Core.res +20 -0
- package/src/Core.res.mjs +11 -0
- package/src/Ecosystem.res +0 -1
- package/src/Envio.res +16 -3
- package/src/EventConfigBuilder.res +155 -40
- package/src/EventConfigBuilder.res.mjs +76 -24
- package/src/EventProcessing.res +10 -14
- package/src/EventProcessing.res.mjs +12 -10
- package/src/EventUtils.res +3 -47
- package/src/FetchState.res +124 -104
- package/src/FetchState.res.mjs +116 -100
- package/src/HandlerLoader.res +20 -8
- package/src/HandlerLoader.res.mjs +11 -2
- package/src/HandlerRegister.res +108 -2
- package/src/HandlerRegister.res.mjs +59 -10
- package/src/HandlerRegister.resi +4 -0
- package/src/InMemoryStore.res +1 -6
- package/src/InMemoryStore.res.mjs +1 -3
- package/src/Internal.res +84 -24
- package/src/Internal.res.mjs +33 -2
- package/src/LazyLoader.res +12 -11
- package/src/LazyLoader.res.mjs +13 -7
- package/src/Main.res +22 -9
- package/src/Main.res.mjs +18 -5
- package/src/MemoryStorage.res +756 -0
- package/src/MemoryStorage.res.mjs +623 -0
- package/src/Persistence.res +4 -2
- package/src/PgStorage.res +24 -8
- package/src/PgStorage.res.mjs +15 -12
- package/src/ReorgDetection.res +0 -222
- package/src/ReorgDetection.res.mjs +0 -163
- package/src/Rollback.res +14 -15
- package/src/Rollback.res.mjs +4 -5
- package/src/SimulateItems.res +2 -2
- package/src/UserContext.res +358 -51
- package/src/UserContext.res.mjs +271 -35
- package/src/Utils.res +3 -0
- package/src/bindings/ClickHouse.res +16 -11
- package/src/bindings/ClickHouse.res.mjs +10 -10
- package/src/bindings/Vitest.res +1 -1
- package/src/sources/BlockStore.res +115 -5
- package/src/sources/BlockStore.res.mjs +25 -0
- package/src/sources/Evm.res +0 -1
- package/src/sources/Evm.res.mjs +0 -1
- package/src/sources/EvmHyperSyncSource.res +19 -84
- package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
- package/src/sources/Fuel.res +24 -4
- package/src/sources/Fuel.res.mjs +23 -3
- package/src/sources/FuelHyperSync.res +8 -3
- package/src/sources/FuelHyperSync.res.mjs +5 -4
- package/src/sources/FuelHyperSync.resi +3 -1
- package/src/sources/FuelHyperSyncClient.res +7 -10
- package/src/sources/FuelHyperSyncSource.res +18 -45
- package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
- package/src/sources/HyperSync.res +49 -229
- package/src/sources/HyperSync.res.mjs +74 -191
- package/src/sources/HyperSync.resi +11 -35
- package/src/sources/HyperSyncClient.res +9 -79
- package/src/sources/HyperSyncClient.res.mjs +2 -6
- package/src/sources/RequestStat.res +5 -0
- package/src/sources/RequestStat.res.mjs +2 -0
- package/src/sources/RpcSource.res +149 -43
- package/src/sources/RpcSource.res.mjs +104 -41
- package/src/sources/SimulateSource.res +8 -5
- package/src/sources/SimulateSource.res.mjs +6 -6
- package/src/sources/Source.res +89 -16
- package/src/sources/Source.res.mjs +50 -1
- package/src/sources/SourceManager.res +255 -50
- package/src/sources/SourceManager.res.mjs +149 -55
- package/src/sources/SourceManager.resi +2 -6
- package/src/sources/Svm.res +0 -7
- package/src/sources/Svm.res.mjs +0 -8
- package/src/sources/SvmHyperSyncClient.res +21 -16
- package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
- package/src/sources/SvmHyperSyncSource.res +25 -117
- package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
- package/src/subgraph/blocks.ts +176 -0
- package/src/subgraph/calls.ts +217 -0
- package/src/subgraph/conformance.ts +99 -0
- package/src/subgraph/division.ts +110 -0
- package/src/subgraph/errors.ts +90 -0
- package/src/subgraph/graph-ts-types/VERSION +2 -0
- package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
- package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
- package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
- package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
- package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
- package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
- package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
- package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
- package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
- package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
- package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
- package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
- package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
- package/src/subgraph/graph-ts-types/index.d.ts +102 -0
- package/src/subgraph/graph-ts.ts +1871 -0
- package/src/subgraph/hosts.ts +148 -0
- package/src/subgraph/runtime.ts +845 -0
- package/src/subgraph/scope.ts +63 -0
- package/svm.schema.json +3 -2
|
@@ -36,9 +36,9 @@ async function query(client, fromBlock, toBlock, registrationIndexes, addressSet
|
|
|
36
36
|
registrationIndexes: registrationIndexes,
|
|
37
37
|
clientFilteredContracts: clientFilteredContracts
|
|
38
38
|
};
|
|
39
|
-
let
|
|
39
|
+
let match;
|
|
40
40
|
try {
|
|
41
|
-
|
|
41
|
+
match = await client.getEventItems(query$1, addressSet);
|
|
42
42
|
} catch (raw_exn) {
|
|
43
43
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
44
44
|
let missingParams = extractMissingParams(exn);
|
|
@@ -54,6 +54,7 @@ async function query(client, fromBlock, toBlock, registrationIndexes, addressSet
|
|
|
54
54
|
}
|
|
55
55
|
throw exn;
|
|
56
56
|
}
|
|
57
|
+
let res = match[0];
|
|
57
58
|
if (res.nextBlock <= fromBlock) {
|
|
58
59
|
throw {
|
|
59
60
|
RE_EXN_ID: $$Error,
|
|
@@ -63,9 +64,9 @@ async function query(client, fromBlock, toBlock, registrationIndexes, addressSet
|
|
|
63
64
|
}
|
|
64
65
|
return {
|
|
65
66
|
items: res.items,
|
|
66
|
-
blocks: res.blocks,
|
|
67
67
|
nextBlock: res.nextBlock,
|
|
68
|
-
archiveHeight: Stdlib_Option.getOr(res.archiveHeight, 0)
|
|
68
|
+
archiveHeight: Stdlib_Option.getOr(res.archiveHeight, 0),
|
|
69
|
+
blockStore: match[1]
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
type logsQueryPage = {
|
|
2
2
|
items: array<FuelHyperSyncClient.EventItems.item>,
|
|
3
|
-
blocks: array<FuelHyperSyncClient.EventItems.block>,
|
|
4
3
|
nextBlock: int,
|
|
5
4
|
archiveHeight: int,
|
|
5
|
+
// Page store owning this page's raw blocks (built on the Rust side); its ids
|
|
6
|
+
// drive reorg detection when merged into the chain store.
|
|
7
|
+
blockStore: BlockStore.t,
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
module GetLogs: {
|
|
@@ -87,17 +87,9 @@ module EventItems = {
|
|
|
87
87
|
to?: string,
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
type block = {
|
|
91
|
-
id: string,
|
|
92
|
-
height: int,
|
|
93
|
-
time: int,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
90
|
type response = {
|
|
97
91
|
archiveHeight: option<int>,
|
|
98
92
|
nextBlock: int,
|
|
99
|
-
// One block per height; items reference them by `blockHeight`.
|
|
100
|
-
blocks: array<block>,
|
|
101
93
|
items: array<item>,
|
|
102
94
|
}
|
|
103
95
|
}
|
|
@@ -121,9 +113,14 @@ let make = (cfg: cfg, ~eventRegistrations, ~addressStore) => {
|
|
|
121
113
|
)
|
|
122
114
|
}
|
|
123
115
|
|
|
116
|
+
// Returns the routed items/blocks plus the page's block store (Fuel blocks
|
|
117
|
+
// keyed by height); the store's ids drive reorg detection on merge.
|
|
124
118
|
@send
|
|
125
|
-
external getEventItems: (
|
|
126
|
-
|
|
119
|
+
external getEventItems: (
|
|
120
|
+
t,
|
|
121
|
+
EventItems.query,
|
|
122
|
+
AddressSet.t,
|
|
123
|
+
) => promise<(EventItems.response, BlockStore.t)> = "getEventItems"
|
|
127
124
|
|
|
128
125
|
@send
|
|
129
126
|
external getHeight: t => promise<int> = "getHeight"
|
|
@@ -25,7 +25,9 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
25
25
|
|
|
26
26
|
let client = switch FuelHyperSyncClient.make(
|
|
27
27
|
{url: endpointUrl, apiToken},
|
|
28
|
-
~eventRegistrations=FuelHyperSyncClient.Registration.fromOnEventRegistrations(
|
|
28
|
+
~eventRegistrations=FuelHyperSyncClient.Registration.fromOnEventRegistrations(
|
|
29
|
+
onEventRegistrations,
|
|
30
|
+
),
|
|
29
31
|
~addressStore,
|
|
30
32
|
) {
|
|
31
33
|
| client => client
|
|
@@ -57,29 +59,19 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
57
59
|
~addressSet,
|
|
58
60
|
~clientFilteredContracts=selection.clientFilteredContracts,
|
|
59
61
|
) catch {
|
|
60
|
-
| FuelHyperSync.GetLogs.Error(
|
|
62
|
+
| FuelHyperSync.GetLogs.Error(WrongInstance) =>
|
|
63
|
+
throw(Source.SourceBehindHead({blockNumber: fromBlock, requestStats: []}))
|
|
64
|
+
| FuelHyperSync.GetLogs.Error(UnexpectedMissingParams({missingParams})) =>
|
|
61
65
|
throw(
|
|
62
66
|
Source.GetItemsError(
|
|
63
67
|
Source.FailedGettingItems({
|
|
64
68
|
exn: %raw(`null`),
|
|
65
69
|
attemptedToBlock: toBlock->Option.getOr(knownHeight),
|
|
66
|
-
retry:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
WithBackoff({
|
|
73
|
-
message: `Block #${fromBlock->Int.toString} not found in FuelHyperSync. HyperFuel has multiple instances and it's possible that they drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${backoffMillis->Int.toString}ms.`,
|
|
74
|
-
backoffMillis,
|
|
75
|
-
})
|
|
76
|
-
| UnexpectedMissingParams({missingParams}) =>
|
|
77
|
-
ImpossibleForTheQuery({
|
|
78
|
-
message: `Source returned invalid data with missing required fields: ${missingParams->Array.joinUnsafe(
|
|
79
|
-
", ",
|
|
80
|
-
)}`,
|
|
81
|
-
})
|
|
82
|
-
},
|
|
70
|
+
retry: ImpossibleForTheQuery({
|
|
71
|
+
message: `Source returned invalid data with missing required fields: ${missingParams->Array.joinUnsafe(
|
|
72
|
+
", ",
|
|
73
|
+
)}`,
|
|
74
|
+
}),
|
|
83
75
|
}),
|
|
84
76
|
),
|
|
85
77
|
)
|
|
@@ -114,12 +106,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
114
106
|
|
|
115
107
|
let parsingTimeRef = Performance.now()
|
|
116
108
|
|
|
117
|
-
// Blocks are returned once per height; items reference them by blockHeight.
|
|
118
|
-
let blocksByHeight = Utils.Map.make()
|
|
119
|
-
pageUnsafe.blocks->Array.forEach(block => {
|
|
120
|
-
blocksByHeight->Utils.Map.set(block.height, block)->ignore
|
|
121
|
-
})
|
|
122
|
-
|
|
123
109
|
let parsedQueueItems = pageUnsafe.items->Array.map(item => {
|
|
124
110
|
// Routing happened in Rust; the item references its registration by
|
|
125
111
|
// chain-scoped index.
|
|
@@ -128,8 +114,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
128
114
|
onEventRegistration.eventConfig->(
|
|
129
115
|
Utils.magic: Internal.eventConfig => Internal.fuelEventConfig
|
|
130
116
|
)
|
|
131
|
-
// Presence of every routed item's block is validated in Rust.
|
|
132
|
-
let block = blocksByHeight->Utils.Map.unsafeGet(item.blockHeight)
|
|
133
117
|
|
|
134
118
|
let params = switch eventConfig.kind {
|
|
135
119
|
| LogData({decode}) =>
|
|
@@ -178,12 +162,13 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
178
162
|
payload: {
|
|
179
163
|
contractName: eventConfig.contractName,
|
|
180
164
|
eventName: eventConfig.name,
|
|
181
|
-
chainId
|
|
165
|
+
chainId,
|
|
182
166
|
params,
|
|
183
167
|
transaction: {
|
|
184
168
|
"id": item.txId,
|
|
185
169
|
}->Obj.magic, // TODO: Obj.magic needed until the field selection types are not configurable for Fuel and Evm separately
|
|
186
|
-
block
|
|
170
|
+
// `block` is omitted; it's materialised from the block store onto the
|
|
171
|
+
// payload at batch prep (like EVM/SVM).
|
|
187
172
|
srcAddress: item.srcAddress,
|
|
188
173
|
logIndex: item.receiptIndex,
|
|
189
174
|
}->Fuel.fromPayload,
|
|
@@ -192,18 +177,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
192
177
|
|
|
193
178
|
let parsingTimeElapsed = parsingTimeRef->Performance.secondsSince
|
|
194
179
|
|
|
195
|
-
// Fuel never rolls back on reorg, so block hashes here are purely informational
|
|
196
|
-
// for detect-only logging via ReorgDetection.
|
|
197
|
-
let blockHashes = pageUnsafe.blocks->Array.map(block => {
|
|
198
|
-
ReorgDetection.blockNumber: block.height,
|
|
199
|
-
blockHash: block.id,
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
let latestFetchedBlockTimestamp = switch blocksByHeight->Utils.Map.get(heighestBlockQueried) {
|
|
203
|
-
| Some(block) => block.time
|
|
204
|
-
| None => 0
|
|
205
|
-
}
|
|
206
|
-
|
|
207
180
|
let totalTimeElapsed = totalTimeRef->Performance.secondsSince
|
|
208
181
|
|
|
209
182
|
let stats = {
|
|
@@ -213,15 +186,15 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
213
186
|
}
|
|
214
187
|
|
|
215
188
|
{
|
|
216
|
-
latestFetchedBlockTimestamp,
|
|
217
189
|
parsedQueueItems,
|
|
218
|
-
// Fuel keeps transaction and block on the payload;
|
|
190
|
+
// Fuel keeps transaction and block inline on the payload; the block store
|
|
191
|
+
// carries only the (height, id) rows that drive reorg detection (Fuel
|
|
192
|
+
// never rolls back, so it's detect-only logging).
|
|
219
193
|
transactionStore: None,
|
|
220
|
-
blockStore:
|
|
194
|
+
blockStore: pageUnsafe.blockStore,
|
|
221
195
|
latestFetchedBlockNumber: heighestBlockQueried,
|
|
222
196
|
stats,
|
|
223
197
|
knownHeight,
|
|
224
|
-
blockHashes,
|
|
225
198
|
fromBlockQueried: fromBlock,
|
|
226
199
|
requestStats,
|
|
227
200
|
}
|
|
@@ -38,22 +38,16 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
38
38
|
let pageUnsafe;
|
|
39
39
|
try {
|
|
40
40
|
pageUnsafe = await FuelHyperSync.GetLogs.query(client, fromBlock, toBlock, selection.onEventRegistrations.map(reg => reg.index), addressSet, selection.clientFilteredContracts);
|
|
41
|
-
} catch (
|
|
42
|
-
let
|
|
43
|
-
if (
|
|
44
|
-
let
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
backoffMillis: backoffMillis
|
|
52
|
-
};
|
|
53
|
-
} else {
|
|
54
|
-
tmp = {
|
|
55
|
-
TAG: "ImpossibleForTheQuery",
|
|
56
|
-
message: `Source returned invalid data with missing required fields: ` + error$1.missingParams.join(", ")
|
|
41
|
+
} catch (raw_exn) {
|
|
42
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
43
|
+
if (exn.RE_EXN_ID === FuelHyperSync.GetLogs.$$Error) {
|
|
44
|
+
let match = exn._1;
|
|
45
|
+
if (typeof match !== "object") {
|
|
46
|
+
throw {
|
|
47
|
+
RE_EXN_ID: Source.SourceBehindHead,
|
|
48
|
+
blockNumber: fromBlock,
|
|
49
|
+
requestStats: [],
|
|
50
|
+
Error: new Error()
|
|
57
51
|
};
|
|
58
52
|
}
|
|
59
53
|
throw {
|
|
@@ -62,7 +56,10 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
62
56
|
TAG: "FailedGettingItems",
|
|
63
57
|
exn: null,
|
|
64
58
|
attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
|
|
65
|
-
retry:
|
|
59
|
+
retry: {
|
|
60
|
+
TAG: "ImpossibleForTheQuery",
|
|
61
|
+
message: `Source returned invalid data with missing required fields: ` + match.missingParams.join(", ")
|
|
62
|
+
}
|
|
66
63
|
},
|
|
67
64
|
Error: new Error()
|
|
68
65
|
};
|
|
@@ -71,7 +68,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
71
68
|
RE_EXN_ID: Source.GetItemsError,
|
|
72
69
|
_1: {
|
|
73
70
|
TAG: "FailedGettingItems",
|
|
74
|
-
exn:
|
|
71
|
+
exn: exn,
|
|
75
72
|
attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
|
|
76
73
|
retry: {
|
|
77
74
|
TAG: "WithBackoff",
|
|
@@ -90,14 +87,9 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
90
87
|
let knownHeight$1 = pageUnsafe.archiveHeight;
|
|
91
88
|
let heighestBlockQueried = pageUnsafe.nextBlock - 1 | 0;
|
|
92
89
|
let parsingTimeRef = Performance.now();
|
|
93
|
-
let blocksByHeight = new Map();
|
|
94
|
-
pageUnsafe.blocks.forEach(block => {
|
|
95
|
-
blocksByHeight.set(block.height, block);
|
|
96
|
-
});
|
|
97
90
|
let parsedQueueItems = pageUnsafe.items.map(item => {
|
|
98
91
|
let onEventRegistration = onEventRegistrations[item.onEventRegistrationIndex];
|
|
99
92
|
let eventConfig = onEventRegistration.eventConfig;
|
|
100
|
-
let block = blocksByHeight.get(item.blockHeight);
|
|
101
93
|
let match = eventConfig.kind;
|
|
102
94
|
let params;
|
|
103
95
|
let exit = 0;
|
|
@@ -158,18 +150,11 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
158
150
|
logIndex: item.receiptIndex,
|
|
159
151
|
transaction: {
|
|
160
152
|
id: item.txId
|
|
161
|
-
}
|
|
162
|
-
block: block
|
|
153
|
+
}
|
|
163
154
|
}
|
|
164
155
|
};
|
|
165
156
|
});
|
|
166
157
|
let parsingTimeElapsed = Performance.secondsSince(parsingTimeRef);
|
|
167
|
-
let blockHashes = pageUnsafe.blocks.map(block => ({
|
|
168
|
-
blockHash: block.id,
|
|
169
|
-
blockNumber: block.height
|
|
170
|
-
}));
|
|
171
|
-
let block = blocksByHeight.get(heighestBlockQueried);
|
|
172
|
-
let latestFetchedBlockTimestamp = block !== undefined ? block.time : 0;
|
|
173
158
|
let totalTimeElapsed = Performance.secondsSince(totalTimeRef);
|
|
174
159
|
let stats_parsing$unknowntime$unknown$lpars$rpar = parsingTimeElapsed;
|
|
175
160
|
let stats_page$unknownfetch$unknowntime$unknown$lpars$rpar = pageFetchTime;
|
|
@@ -180,13 +165,11 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
180
165
|
};
|
|
181
166
|
return {
|
|
182
167
|
knownHeight: knownHeight$1,
|
|
183
|
-
blockHashes: blockHashes,
|
|
184
168
|
parsedQueueItems: parsedQueueItems,
|
|
185
169
|
transactionStore: undefined,
|
|
186
|
-
blockStore:
|
|
170
|
+
blockStore: pageUnsafe.blockStore,
|
|
187
171
|
fromBlockQueried: fromBlock,
|
|
188
172
|
latestFetchedBlockNumber: heighestBlockQueried,
|
|
189
|
-
latestFetchedBlockTimestamp: latestFetchedBlockTimestamp,
|
|
190
173
|
stats: stats,
|
|
191
174
|
requestStats: requestStats
|
|
192
175
|
};
|
|
@@ -249,4 +232,4 @@ export {
|
|
|
249
232
|
isUnauthorizedError,
|
|
250
233
|
make,
|
|
251
234
|
}
|
|
252
|
-
/*
|
|
235
|
+
/* Source Not a pure module */
|
|
@@ -1,59 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
// A napi error carries nothing but a message, so the native clients signal the
|
|
2
|
+
// recoverable conditions SourceManager knows how to retry as a `PREFIX:<int>`
|
|
3
|
+
// marker. Keep in sync with `request_stats.rs`.
|
|
4
|
+
let rateLimitedPrefix = "RATE_LIMITED:"
|
|
5
|
+
let behindHeadPrefix = "SOURCE_BEHIND_HEAD:"
|
|
6
|
+
|
|
7
|
+
let markerValue = (msg, ~prefix) =>
|
|
8
|
+
msg->String.slice(~start=prefix->String.length, ~end=msg->String.length)->Int.fromString
|
|
9
|
+
|
|
10
|
+
let mapNativeFailure = (failure: Source.nativeRequestFailure) => {
|
|
11
|
+
switch failure.message {
|
|
12
|
+
| Some(msg) if msg->String.startsWith(rateLimitedPrefix) =>
|
|
13
|
+
Source.RateLimited({
|
|
14
|
+
resetMs: msg->markerValue(~prefix=rateLimitedPrefix)->Option.getOr(1000),
|
|
15
|
+
requestStats: failure.requestStats,
|
|
16
|
+
})
|
|
17
|
+
| Some(msg) if msg->String.startsWith(behindHeadPrefix) =>
|
|
18
|
+
Source.SourceBehindHead({
|
|
19
|
+
blockNumber: msg->markerValue(~prefix=behindHeadPrefix)->Option.getOr(0),
|
|
20
|
+
requestStats: failure.requestStats,
|
|
21
|
+
})
|
|
22
|
+
| _ => failure.cause
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let mapNativeFailureExn = exn => exn->Source.unpackNativeRequestFailure->mapNativeFailure
|
|
27
|
+
|
|
28
|
+
// Every HyperSync client paginates block hashes in Rust and returns the page
|
|
29
|
+
// store with the timings of the requests it took. A failure arrives as the
|
|
30
|
+
// native envelope, which carries those same timings, so the caller records them
|
|
31
|
+
// either way.
|
|
32
|
+
let makeGetBlockHashes = (
|
|
33
|
+
~query: (~blockNumbers: array<int>) => promise<(BlockStore.t, array<Source.requestStat>)>,
|
|
34
|
+
) =>
|
|
35
|
+
async (~blockNumbers, ~logger as _) => {
|
|
36
|
+
let (result, requestStats) = try {
|
|
37
|
+
let (blockStore, requestStats) = await query(~blockNumbers)
|
|
38
|
+
(Ok(blockStore), requestStats)
|
|
39
|
+
} catch {
|
|
40
|
+
| exn =>
|
|
41
|
+
let failure = exn->Source.unpackNativeRequestFailure
|
|
42
|
+
(Error(failure->mapNativeFailure), failure.requestStats)
|
|
13
43
|
}
|
|
44
|
+
{Source.result, requestStats}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let reraiseIfRecoverable = exn =>
|
|
48
|
+
switch exn->mapNativeFailureExn {
|
|
49
|
+
| (Source.RateLimited(_) | Source.SourceBehindHead(_)) as exn => throw(exn)
|
|
14
50
|
| _ => ()
|
|
15
51
|
}
|
|
16
52
|
|
|
17
53
|
type logsQueryPage = {
|
|
18
54
|
items: array<HyperSyncClient.EventItems.item>,
|
|
19
|
-
// Headers for every returned block, deduplicated by block number.
|
|
20
|
-
blocks: array<HyperSyncClient.EventItems.blockHeader>,
|
|
21
55
|
nextBlock: int,
|
|
22
56
|
archiveHeight: int,
|
|
23
|
-
rollbackGuard: option<HyperSyncClient.ResponseTypes.rollbackGuard>,
|
|
24
57
|
// Page store owning this page's raw transactions.
|
|
25
58
|
transactionStore: TransactionStore.t,
|
|
26
59
|
// Page store owning this page's raw blocks.
|
|
27
60
|
blockStore: BlockStore.t,
|
|
28
61
|
}
|
|
29
62
|
|
|
30
|
-
type missingParams = {
|
|
31
|
-
queryName: string,
|
|
32
|
-
missingParams: array<string>,
|
|
33
|
-
}
|
|
34
|
-
type queryError = UnexpectedMissingParams(missingParams)
|
|
35
|
-
|
|
36
|
-
exception HyperSyncQueryError(queryError)
|
|
37
|
-
|
|
38
|
-
let queryErrorToExn = queryError => {
|
|
39
|
-
HyperSyncQueryError(queryError)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let queryErrorToMsq = (e: queryError): string => {
|
|
43
|
-
switch e {
|
|
44
|
-
| UnexpectedMissingParams({queryName, missingParams}) =>
|
|
45
|
-
`${queryName} query failed due to unexpected missing params on response:
|
|
46
|
-
${missingParams->Array.joinUnsafe(", ")}`
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
type queryResponse<'a> = result<'a, queryError>
|
|
51
|
-
let mapExn = (queryResponse: queryResponse<'a>) =>
|
|
52
|
-
switch queryResponse {
|
|
53
|
-
| Ok(v) => Ok(v)
|
|
54
|
-
| Error(err) => err->queryErrorToExn->Error
|
|
55
|
-
}
|
|
56
|
-
|
|
57
63
|
module GetLogs = {
|
|
58
64
|
type error =
|
|
59
65
|
| UnexpectedMissingParams({missingParams: array<string>})
|
|
@@ -109,7 +115,7 @@ module GetLogs = {
|
|
|
109
115
|
) {
|
|
110
116
|
| res => res
|
|
111
117
|
| exception exn =>
|
|
112
|
-
|
|
118
|
+
reraiseIfRecoverable(exn)
|
|
113
119
|
switch extractMissingParams(exn) {
|
|
114
120
|
| Some(missingParams) => throw(Error(UnexpectedMissingParams({missingParams: missingParams})))
|
|
115
121
|
| None => throw(exn)
|
|
@@ -122,196 +128,10 @@ module GetLogs = {
|
|
|
122
128
|
|
|
123
129
|
{
|
|
124
130
|
items: res.items,
|
|
125
|
-
blocks: res.blocks,
|
|
126
131
|
nextBlock: res.nextBlock,
|
|
127
132
|
archiveHeight: res.archiveHeight->Option.getOr(0), //Archive Height is only None if height is 0
|
|
128
|
-
rollbackGuard: res.rollbackGuard,
|
|
129
133
|
transactionStore,
|
|
130
134
|
blockStore,
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
137
|
}
|
|
134
|
-
|
|
135
|
-
module BlockData = {
|
|
136
|
-
let makeRequestBody = (~fromBlock, ~toBlock): HyperSyncClient.QueryTypes.query => {
|
|
137
|
-
fromBlock,
|
|
138
|
-
toBlockExclusive: toBlock + 1,
|
|
139
|
-
fieldSelection: {
|
|
140
|
-
block: [Number, Hash, Timestamp],
|
|
141
|
-
},
|
|
142
|
-
includeAllBlocks: true,
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let convertResponse = (res: HyperSyncClient.queryResponse): queryResponse<
|
|
146
|
-
array<ReorgDetection.blockDataWithTimestamp>,
|
|
147
|
-
> => {
|
|
148
|
-
res.data.blocks
|
|
149
|
-
->Array.map(block => {
|
|
150
|
-
switch block {
|
|
151
|
-
| {number: blockNumber, hash: blockHash, timestamp: blockTimestamp} =>
|
|
152
|
-
Ok(
|
|
153
|
-
(
|
|
154
|
-
{
|
|
155
|
-
blockTimestamp,
|
|
156
|
-
blockNumber,
|
|
157
|
-
blockHash,
|
|
158
|
-
}: ReorgDetection.blockDataWithTimestamp
|
|
159
|
-
),
|
|
160
|
-
)
|
|
161
|
-
| _ =>
|
|
162
|
-
let missingParams =
|
|
163
|
-
[
|
|
164
|
-
block.number->Utils.Option.mapNone("block.number"),
|
|
165
|
-
block.timestamp->Utils.Option.mapNone("block.timestamp"),
|
|
166
|
-
block.hash->Utils.Option.mapNone("block.hash"),
|
|
167
|
-
]->Array.filterMap(p => p)
|
|
168
|
-
|
|
169
|
-
Error(
|
|
170
|
-
UnexpectedMissingParams({
|
|
171
|
-
queryName: "query block data HyperSync",
|
|
172
|
-
missingParams,
|
|
173
|
-
}),
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
->Utils.Array.transposeResults
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
let rec queryBlockData = async (
|
|
181
|
-
~client: HyperSyncClient.t,
|
|
182
|
-
~fromBlock,
|
|
183
|
-
~toBlock,
|
|
184
|
-
~sourceName,
|
|
185
|
-
~chainId,
|
|
186
|
-
~logger,
|
|
187
|
-
~requestStats: array<Source.requestStat>,
|
|
188
|
-
): queryResponse<array<ReorgDetection.blockDataWithTimestamp>> => {
|
|
189
|
-
let body = makeRequestBody(~fromBlock, ~toBlock)
|
|
190
|
-
|
|
191
|
-
let logger = Logging.createChildFrom(
|
|
192
|
-
~logger,
|
|
193
|
-
~params={
|
|
194
|
-
"logType": "HyperSync get block hash query",
|
|
195
|
-
"fromBlock": fromBlock,
|
|
196
|
-
"toBlock": toBlock,
|
|
197
|
-
},
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
let timerRef = Performance.now()
|
|
201
|
-
let maybeSuccessfulRes = switch await client.get(~query=body) {
|
|
202
|
-
| exception exn =>
|
|
203
|
-
reraisIfRateLimited(exn)
|
|
204
|
-
None
|
|
205
|
-
| res if res.nextBlock <= fromBlock => None
|
|
206
|
-
| res => Some(res)
|
|
207
|
-
}
|
|
208
|
-
requestStats
|
|
209
|
-
->Array.push({Source.method: "getBlockHashes", seconds: timerRef->Performance.secondsSince})
|
|
210
|
-
->ignore
|
|
211
|
-
|
|
212
|
-
// If the block is not found, retry the query. This can occur since replicas of hypersync might not have caught up yet
|
|
213
|
-
switch maybeSuccessfulRes {
|
|
214
|
-
| None => {
|
|
215
|
-
let delayMilliseconds = 100
|
|
216
|
-
logger->Logging.childInfo(
|
|
217
|
-
`Block #${fromBlock->Int.toString} not found in HyperSync. HyperSync has multiple instances and it's possible that they drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${delayMilliseconds->Int.toString}ms.`,
|
|
218
|
-
)
|
|
219
|
-
await Time.resolvePromiseAfterDelay(~delayMilliseconds)
|
|
220
|
-
await queryBlockData(
|
|
221
|
-
~client,
|
|
222
|
-
~fromBlock,
|
|
223
|
-
~toBlock,
|
|
224
|
-
~sourceName,
|
|
225
|
-
~chainId,
|
|
226
|
-
~logger,
|
|
227
|
-
~requestStats,
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
| Some(res) =>
|
|
231
|
-
switch res->convertResponse {
|
|
232
|
-
| Error(_) as err => err
|
|
233
|
-
| Ok(datas) if res.nextBlock <= toBlock => {
|
|
234
|
-
let restRes = await queryBlockData(
|
|
235
|
-
~client,
|
|
236
|
-
~fromBlock=res.nextBlock,
|
|
237
|
-
~toBlock,
|
|
238
|
-
~sourceName,
|
|
239
|
-
~chainId,
|
|
240
|
-
~logger,
|
|
241
|
-
~requestStats,
|
|
242
|
-
)
|
|
243
|
-
restRes->Result.map(rest => datas->Array.concat(rest))
|
|
244
|
-
}
|
|
245
|
-
| Ok(_) as ok => ok
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
let queryBlockDataMulti = async (
|
|
251
|
-
~client: HyperSyncClient.t,
|
|
252
|
-
~blockNumbers,
|
|
253
|
-
~sourceName,
|
|
254
|
-
~chainId,
|
|
255
|
-
~logger,
|
|
256
|
-
): (queryResponse<array<ReorgDetection.blockDataWithTimestamp>>, array<Source.requestStat>) => {
|
|
257
|
-
let requestStats = []
|
|
258
|
-
let result = switch blockNumbers->Array.get(0) {
|
|
259
|
-
| None => Ok([])
|
|
260
|
-
| Some(firstBlock) => {
|
|
261
|
-
let fromBlock = ref(firstBlock)
|
|
262
|
-
let toBlock = ref(firstBlock)
|
|
263
|
-
let set = Utils.Set.make()
|
|
264
|
-
for idx in 0 to blockNumbers->Array.length - 1 {
|
|
265
|
-
let blockNumber = blockNumbers->Array.getUnsafe(idx)
|
|
266
|
-
if blockNumber < fromBlock.contents {
|
|
267
|
-
fromBlock := blockNumber
|
|
268
|
-
}
|
|
269
|
-
if blockNumber > toBlock.contents {
|
|
270
|
-
toBlock := blockNumber
|
|
271
|
-
}
|
|
272
|
-
set->Utils.Set.add(blockNumber)->ignore
|
|
273
|
-
}
|
|
274
|
-
if toBlock.contents - fromBlock.contents > 1000 {
|
|
275
|
-
JsError.throwWithMessage(
|
|
276
|
-
`Invalid block data request. Range of block numbers is too large. Max range is 1000. Requested range: ${fromBlock.contents->Int.toString}-${toBlock.contents->Int.toString}`,
|
|
277
|
-
)
|
|
278
|
-
}
|
|
279
|
-
let res = await queryBlockData(
|
|
280
|
-
~fromBlock=fromBlock.contents,
|
|
281
|
-
~toBlock=toBlock.contents,
|
|
282
|
-
~client,
|
|
283
|
-
~sourceName,
|
|
284
|
-
~chainId,
|
|
285
|
-
~logger,
|
|
286
|
-
~requestStats,
|
|
287
|
-
)
|
|
288
|
-
let filtered = res->Result.map(datas => {
|
|
289
|
-
datas->Array.filter(data => set->Utils.Set.delete(data.blockNumber))
|
|
290
|
-
})
|
|
291
|
-
if set->Utils.Set.size > 0 {
|
|
292
|
-
JsError.throwWithMessage(
|
|
293
|
-
`Invalid response. Failed to get block data for block numbers: ${set
|
|
294
|
-
->Utils.Set.toArray
|
|
295
|
-
->Array.joinUnsafe(", ")}`,
|
|
296
|
-
)
|
|
297
|
-
}
|
|
298
|
-
filtered
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
(result, requestStats)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
let queryBlockData = (~client, ~blockNumber, ~sourceName, ~chainId, ~logger) => {
|
|
306
|
-
let requestStats = []
|
|
307
|
-
BlockData.queryBlockData(
|
|
308
|
-
~client,
|
|
309
|
-
~fromBlock=blockNumber,
|
|
310
|
-
~toBlock=blockNumber,
|
|
311
|
-
~sourceName,
|
|
312
|
-
~chainId,
|
|
313
|
-
~logger,
|
|
314
|
-
~requestStats,
|
|
315
|
-
)->Promise.thenResolve(res => (res->Result.map(res => res->Array.get(0)), requestStats))
|
|
316
|
-
}
|
|
317
|
-
let queryBlockDataMulti = BlockData.queryBlockDataMulti
|