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
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
// page that is merged in. At batch preparation the selected fields are
|
|
5
5
|
// materialised in bulk, off the JS thread, in columnar form and zipped into
|
|
6
6
|
// plain JS objects on the main thread.
|
|
7
|
+
//
|
|
8
|
+
// The store also owns response validation and reorg detection. A response page
|
|
9
|
+
// records conflicts found while it is built; SourceManager rejects those pages
|
|
10
|
+
// before the persistent store is touched. Merging a validated page compares
|
|
11
|
+
// only persistent-vs-response hashes, and pruning keeps the hash of processed
|
|
12
|
+
// blocks still inside the reorg threshold.
|
|
7
13
|
type t
|
|
8
14
|
|
|
9
15
|
@send external newEvm: (Core.blockStoreCtor, ~shouldChecksum: bool) => t = "newEvm"
|
|
@@ -26,8 +32,93 @@ let make = (~ecosystem: Ecosystem.name, ~shouldChecksum: bool): t => {
|
|
|
26
32
|
// with the Rust store, `EvmBlockField`).
|
|
27
33
|
let makeMaskFn = FieldMask.makeMaskFn
|
|
28
34
|
|
|
29
|
-
//
|
|
30
|
-
|
|
35
|
+
// Sparse JS blocks accepted by the `fromJs*` page constructors. Every field is
|
|
36
|
+
// optional except the key, so a page can carry anything from a full block to a
|
|
37
|
+
// hash-only reorg observation. The Rust side re-encodes them through the same
|
|
38
|
+
// column fill as fetched blocks.
|
|
39
|
+
type evmBlockInput = {number: int, hash?: string, timestamp?: int}
|
|
40
|
+
type svmBlockInput = {slot: int, hash?: string, time?: int}
|
|
41
|
+
type fuelBlockInput = {height: int, id?: string, time?: int}
|
|
42
|
+
|
|
43
|
+
@send
|
|
44
|
+
external fromJsEvm: (Core.blockStoreCtor, array<evmBlockInput>, bool) => t = "fromJsEvm"
|
|
45
|
+
@send
|
|
46
|
+
external fromJsSvm: (Core.blockStoreCtor, array<svmBlockInput>) => t = "fromJsSvm"
|
|
47
|
+
@send
|
|
48
|
+
external fromJsFuel: (Core.blockStoreCtor, array<fuelBlockInput>) => t = "fromJsFuel"
|
|
49
|
+
|
|
50
|
+
// An ecosystem-agnostic (number, hash, timestamp) observation, mapped onto the
|
|
51
|
+
// ecosystem's own field names when the page is built.
|
|
52
|
+
type inputBlock = {blockNumber: int, blockHash?: string, blockTimestamp?: int}
|
|
53
|
+
|
|
54
|
+
// Build a page from JS-observed blocks (RPC responses, stored reorg
|
|
55
|
+
// checkpoints) for merging into the per-chain store.
|
|
56
|
+
let fromJs = (blocks: array<inputBlock>, ~ecosystem: Ecosystem.name, ~shouldChecksum): t => {
|
|
57
|
+
let ctor = Core.getAddon().blockStore
|
|
58
|
+
switch ecosystem {
|
|
59
|
+
| Evm =>
|
|
60
|
+
ctor->fromJsEvm(
|
|
61
|
+
blocks->Array.map(b => {
|
|
62
|
+
number: b.blockNumber,
|
|
63
|
+
hash: ?b.blockHash,
|
|
64
|
+
timestamp: ?b.blockTimestamp,
|
|
65
|
+
}),
|
|
66
|
+
shouldChecksum,
|
|
67
|
+
)
|
|
68
|
+
| Svm =>
|
|
69
|
+
ctor->fromJsSvm(
|
|
70
|
+
blocks->Array.map(b => {
|
|
71
|
+
slot: b.blockNumber,
|
|
72
|
+
hash: ?b.blockHash,
|
|
73
|
+
time: ?b.blockTimestamp,
|
|
74
|
+
}),
|
|
75
|
+
)
|
|
76
|
+
| Fuel =>
|
|
77
|
+
ctor->fromJsFuel(
|
|
78
|
+
blocks->Array.map(b => {
|
|
79
|
+
height: b.blockNumber,
|
|
80
|
+
id: ?b.blockHash,
|
|
81
|
+
time: ?b.blockTimestamp,
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The lowest merged block at or above the reorg threshold whose received hash
|
|
88
|
+
// differed from the stored one.
|
|
89
|
+
type hashMismatch = {
|
|
90
|
+
blockNumber: int,
|
|
91
|
+
storedHash: string,
|
|
92
|
+
receivedHash: string,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Drain another store (a fetch-response page) into this one, comparing hashes
|
|
96
|
+
// on the way. Blocks below `fromBlock` (outside the reorg threshold) or without
|
|
97
|
+
// a hash on either side are merged without comparison. On a mismatch the page
|
|
98
|
+
// is discarded - the stored hashes stay for the rollback comparison - unless
|
|
99
|
+
// `reportOnly` is set (detect-only mode), which merges anyway so the same
|
|
100
|
+
// mismatch doesn't re-report on every response.
|
|
101
|
+
@send
|
|
102
|
+
external merge: (t, t, ~fromBlock: int, ~reportOnly: bool) => Null.t<hashMismatch> = "merge"
|
|
103
|
+
|
|
104
|
+
// Append a backend page to a logical response store. This always appends rows;
|
|
105
|
+
// any conflict is retained as response metadata for SourceManager to validate.
|
|
106
|
+
@send
|
|
107
|
+
external appendPage: (t, t) => unit = "appendPage"
|
|
108
|
+
|
|
109
|
+
// A conflict observed within the response itself. Such a response is
|
|
110
|
+
// discarded and retried, rather than treated as a chain reorg.
|
|
111
|
+
@send external responseConflict: t => Null.t<hashMismatch> = "responseConflict"
|
|
112
|
+
|
|
113
|
+
// Requested block numbers not covered by this response. SVM gaps count as
|
|
114
|
+
// covered when HyperSync's cursor has fully processed their half-open range;
|
|
115
|
+
// parent slot/hash links are validated separately as response consistency.
|
|
116
|
+
@send external missingHashes: (t, array<int>) => array<int> = "missingHashes"
|
|
117
|
+
|
|
118
|
+
// Compare a validated response store against the persistent store in ascending
|
|
119
|
+
// block order and stop at the first mismatch.
|
|
120
|
+
@send
|
|
121
|
+
external latestValidBlockFromStore: (t, t, array<int>) => Null.t<int> = "latestValidBlockFromStore"
|
|
31
122
|
|
|
32
123
|
// Bulk-materialise blocks off the JS thread, one row per `blockNumbers[i]` key,
|
|
33
124
|
// decoding only the fields set in that row's own `masks[i]`. Result is aligned
|
|
@@ -39,8 +130,27 @@ external materialize: (
|
|
|
39
130
|
~masks: array<float>,
|
|
40
131
|
) => promise<array<Internal.eventBlock>> = "materialize"
|
|
41
132
|
|
|
42
|
-
// Drop blocks at or below the given block (already processed)
|
|
43
|
-
|
|
133
|
+
// Drop blocks at or below the given block (already processed), keeping the
|
|
134
|
+
// hashes of blocks at or above `keepHashesFrom` for reorg detection.
|
|
135
|
+
@send external prune: (t, int, ~keepHashesFrom: int) => unit = "prune"
|
|
44
136
|
|
|
45
|
-
// Drop blocks above the given block (rolled back).
|
|
137
|
+
// Drop blocks above the given block (rolled back), hashes included. The
|
|
138
|
+
// rolled-back range is refetched, so keeping any of its hashes would compare
|
|
139
|
+
// the refetch against a fork the rollback already disproved.
|
|
46
140
|
@send external rollback: (t, int) => unit = "rollback"
|
|
141
|
+
|
|
142
|
+
// Hash of a stored block, if the store still holds it.
|
|
143
|
+
@send external getHash: (t, int) => Null.t<string> = "getHash"
|
|
144
|
+
|
|
145
|
+
// Block numbers in `[fromBlock, belowBlock)` with a stored hash, ascending.
|
|
146
|
+
@send
|
|
147
|
+
external getHashedBlockNumbers: (t, ~fromBlock: int, ~belowBlock: int) => array<int> =
|
|
148
|
+
"getHashedBlockNumbers"
|
|
149
|
+
|
|
150
|
+
// Every stored hash in `[fromBlock, belowBlock)` as two aligned columns,
|
|
151
|
+
// ascending by block number - one crossing for what `getHash` would charge per
|
|
152
|
+
// block.
|
|
153
|
+
type hashedBlocks = {blockNumbers: array<int>, hashes: array<string>}
|
|
154
|
+
|
|
155
|
+
@send
|
|
156
|
+
external getHashes: (t, ~fromBlock: int, ~belowBlock: int) => hashedBlocks = "getHashes"
|
|
@@ -15,10 +15,35 @@ function make(ecosystem, shouldChecksum) {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function fromJs(blocks, ecosystem, shouldChecksum) {
|
|
19
|
+
let ctor = Core.getAddon().BlockStore;
|
|
20
|
+
switch (ecosystem) {
|
|
21
|
+
case "evm" :
|
|
22
|
+
return ctor.fromJsEvm(blocks.map(b => ({
|
|
23
|
+
number: b.blockNumber,
|
|
24
|
+
hash: b.blockHash,
|
|
25
|
+
timestamp: b.blockTimestamp
|
|
26
|
+
})), shouldChecksum);
|
|
27
|
+
case "fuel" :
|
|
28
|
+
return ctor.fromJsFuel(blocks.map(b => ({
|
|
29
|
+
height: b.blockNumber,
|
|
30
|
+
id: b.blockHash,
|
|
31
|
+
time: b.blockTimestamp
|
|
32
|
+
})));
|
|
33
|
+
case "svm" :
|
|
34
|
+
return ctor.fromJsSvm(blocks.map(b => ({
|
|
35
|
+
slot: b.blockNumber,
|
|
36
|
+
hash: b.blockHash,
|
|
37
|
+
time: b.blockTimestamp
|
|
38
|
+
})));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
let makeMaskFn = FieldMask.makeMaskFn;
|
|
19
43
|
|
|
20
44
|
export {
|
|
21
45
|
make,
|
|
22
46
|
makeMaskFn,
|
|
47
|
+
fromJs,
|
|
23
48
|
}
|
|
24
49
|
/* Core Not a pure module */
|
package/src/sources/Evm.res
CHANGED
|
@@ -76,7 +76,6 @@ let make = (~logger: Pino.t): Ecosystem.t => {
|
|
|
76
76
|
blockNumberName: "number",
|
|
77
77
|
blockTimestampName: "timestamp",
|
|
78
78
|
blockHashName: "hash",
|
|
79
|
-
cleanUpRawEventFieldsInPlace,
|
|
80
79
|
onBlockMethodName: "onBlock",
|
|
81
80
|
// EVM filter shape: `{block: {number: {_gte?, _lte?, _every?}}}`.
|
|
82
81
|
// The inner range chunk is returned as raw `S.unknown` and parsed a
|
package/src/sources/Evm.res.mjs
CHANGED
|
@@ -55,7 +55,6 @@ function make(logger) {
|
|
|
55
55
|
blockNumberName: "number",
|
|
56
56
|
blockTimestampName: "timestamp",
|
|
57
57
|
blockHashName: "hash",
|
|
58
|
-
cleanUpRawEventFieldsInPlace: cleanUpRawEventFieldsInPlace,
|
|
59
58
|
onBlockMethodName: "onBlock",
|
|
60
59
|
onBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("number", S$RescriptSchema.unknown))))),
|
|
61
60
|
onEventBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.strict(S$RescriptSchema.object(s2 => s2.f("number", S$RescriptSchema.unknown)))))),
|
|
@@ -79,7 +79,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
79
79
|
payload: {
|
|
80
80
|
contractName: onEventRegistration.eventConfig.contractName,
|
|
81
81
|
eventName: onEventRegistration.eventConfig.name,
|
|
82
|
-
chainId
|
|
82
|
+
chainId,
|
|
83
83
|
params: item.params,
|
|
84
84
|
srcAddress,
|
|
85
85
|
logIndex,
|
|
@@ -112,33 +112,23 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
112
112
|
~addressSet,
|
|
113
113
|
~clientFilteredContracts=selection.clientFilteredContracts,
|
|
114
114
|
) catch {
|
|
115
|
-
| HyperSync.GetLogs.Error(
|
|
115
|
+
| HyperSync.GetLogs.Error(WrongInstance) =>
|
|
116
|
+
throw(Source.SourceBehindHead({blockNumber: fromBlock, requestStats: []}))
|
|
117
|
+
| HyperSync.GetLogs.Error(UnexpectedMissingParams({missingParams})) =>
|
|
116
118
|
throw(
|
|
117
119
|
Source.GetItemsError(
|
|
118
120
|
Source.FailedGettingItems({
|
|
119
121
|
exn: %raw(`null`),
|
|
120
122
|
attemptedToBlock: toBlock->Option.getOr(knownHeight),
|
|
121
|
-
retry:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
WithBackoff({
|
|
128
|
-
message: `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 ${backoffMillis->Int.toString}ms.`,
|
|
129
|
-
backoffMillis,
|
|
130
|
-
})
|
|
131
|
-
| UnexpectedMissingParams({missingParams}) =>
|
|
132
|
-
ImpossibleForTheQuery({
|
|
133
|
-
message: `Source returned invalid data with missing required fields: ${missingParams->Array.joinUnsafe(
|
|
134
|
-
", ",
|
|
135
|
-
)}`,
|
|
136
|
-
})
|
|
137
|
-
},
|
|
123
|
+
retry: ImpossibleForTheQuery({
|
|
124
|
+
message: `Source returned invalid data with missing required fields: ${missingParams->Array.joinUnsafe(
|
|
125
|
+
", ",
|
|
126
|
+
)}`,
|
|
127
|
+
}),
|
|
138
128
|
}),
|
|
139
129
|
),
|
|
140
130
|
)
|
|
141
|
-
| Source.RateLimited(_) as exn => throw(exn)
|
|
131
|
+
| (Source.RateLimited(_) | Source.SourceBehindHead(_)) as exn => throw(exn)
|
|
142
132
|
| exn =>
|
|
143
133
|
throw(
|
|
144
134
|
Source.GetItemsError(
|
|
@@ -173,13 +163,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
173
163
|
//Parse page items into queue items
|
|
174
164
|
let parsedQueueItems = []
|
|
175
165
|
|
|
176
|
-
// Block headers are returned once per number; items reference them by blockNumber.
|
|
177
|
-
let blocksByNumber = Utils.Map.make()
|
|
178
|
-
pageUnsafe.blocks->Array.forEach(block => {
|
|
179
|
-
blocksByNumber->Utils.Map.set(block.number, block)->ignore
|
|
180
|
-
})
|
|
181
|
-
let getBlock = blockNumber => blocksByNumber->Utils.Map.unsafeGet(blockNumber)
|
|
182
|
-
|
|
183
166
|
pageUnsafe.items->Array.forEach(item => {
|
|
184
167
|
let onEventRegistration = onEventRegistrations->Array.getUnsafe(item.onEventRegistrationIndex)
|
|
185
168
|
parsedQueueItems
|
|
@@ -189,43 +172,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
189
172
|
|
|
190
173
|
let parsingTimeElapsed = parsingTimeRef->Performance.secondsSince
|
|
191
174
|
|
|
192
|
-
// Collect (blockNumber, blockHash) pairs we already have from the response —
|
|
193
|
-
// one per returned block plus, when present, the rollbackGuard's head block
|
|
194
|
-
// and the parent of the range's first block. Duplicates are allowed; reorg
|
|
195
|
-
// detection notices same-block-number-different-hash collisions itself.
|
|
196
|
-
let blockHashes = []
|
|
197
|
-
pageUnsafe.blocks->Array.forEach(block => {
|
|
198
|
-
blockHashes
|
|
199
|
-
->Array.push({ReorgDetection.blockNumber: block.number, blockHash: block.hash})
|
|
200
|
-
->ignore
|
|
201
|
-
})
|
|
202
|
-
switch pageUnsafe.rollbackGuard {
|
|
203
|
-
| None => ()
|
|
204
|
-
| Some({blockNumber, hash, firstBlockNumber, firstParentHash}) => {
|
|
205
|
-
blockHashes->Array.push({ReorgDetection.blockNumber, blockHash: hash})->ignore
|
|
206
|
-
blockHashes
|
|
207
|
-
->Array.push({
|
|
208
|
-
ReorgDetection.blockNumber: firstBlockNumber - 1,
|
|
209
|
-
blockHash: firstParentHash,
|
|
210
|
-
})
|
|
211
|
-
->ignore
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Best-effort timestamp for the queried-range head: prefer the rollbackGuard
|
|
216
|
-
// (set at the head for unconfirmed blocks), otherwise the last item if it
|
|
217
|
-
// happens to be in the range's last block. 0 is a tolerated placeholder
|
|
218
|
-
// when neither is available (FetchState already uses 0 in several spots).
|
|
219
|
-
let latestFetchedBlockTimestamp = switch pageUnsafe.rollbackGuard {
|
|
220
|
-
| Some({timestamp}) => timestamp
|
|
221
|
-
| None =>
|
|
222
|
-
switch pageUnsafe.items->Array.get(pageUnsafe.items->Array.length - 1) {
|
|
223
|
-
| Some(item) if item.blockNumber == heighestBlockQueried =>
|
|
224
|
-
getBlock(item.blockNumber).timestamp
|
|
225
|
-
| _ => 0
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
175
|
let totalTimeElapsed = totalTimeRef->Performance.secondsSince
|
|
230
176
|
|
|
231
177
|
let stats = {
|
|
@@ -235,30 +181,24 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
235
181
|
}
|
|
236
182
|
|
|
237
183
|
{
|
|
238
|
-
latestFetchedBlockTimestamp,
|
|
239
184
|
parsedQueueItems,
|
|
240
185
|
transactionStore: Some(pageUnsafe.transactionStore),
|
|
241
|
-
|
|
186
|
+
// The page store also carries the rollbackGuard's blocks (head block and
|
|
187
|
+
// parent of the range's first block), inserted on the Rust side.
|
|
188
|
+
blockStore: pageUnsafe.blockStore,
|
|
242
189
|
latestFetchedBlockNumber: heighestBlockQueried,
|
|
243
190
|
stats,
|
|
244
191
|
knownHeight,
|
|
245
|
-
blockHashes,
|
|
246
192
|
fromBlockQueried: fromBlock,
|
|
247
193
|
requestStats,
|
|
248
194
|
}
|
|
249
195
|
}
|
|
250
196
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
~chainId=chainId,
|
|
257
|
-
~logger,
|
|
258
|
-
)->Promise.thenResolve(((queryRes, requestStats)) => {
|
|
259
|
-
Source.result: queryRes->HyperSync.mapExn,
|
|
260
|
-
requestStats,
|
|
261
|
-
})
|
|
197
|
+
// Called through the client rather than passed as a value: the client is a
|
|
198
|
+
// napi class, so a detached method reference loses the instance it belongs to.
|
|
199
|
+
let getBlockHashes = HyperSync.makeGetBlockHashes(
|
|
200
|
+
~query=(~blockNumbers) => client.getBlockHashes(~blockNumbers),
|
|
201
|
+
)
|
|
262
202
|
|
|
263
203
|
{
|
|
264
204
|
name,
|
|
@@ -287,11 +227,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
287
227
|
},
|
|
288
228
|
getItemsOrThrow,
|
|
289
229
|
createHeightSubscription: (~onHeight) =>
|
|
290
|
-
HyperSyncHeightStream.subscribe(
|
|
291
|
-
~hyperSyncUrl=endpointUrl,
|
|
292
|
-
~apiToken,
|
|
293
|
-
~chainId=chainId,
|
|
294
|
-
~onHeight,
|
|
295
|
-
),
|
|
230
|
+
HyperSyncHeightStream.subscribe(~hyperSyncUrl=endpointUrl, ~apiToken, ~chainId, ~onHeight),
|
|
296
231
|
}
|
|
297
232
|
}
|
|
@@ -22,7 +22,6 @@ function make(param) {
|
|
|
22
22
|
let onEventRegistrations = param.onEventRegistrations;
|
|
23
23
|
let endpointUrl = param.endpointUrl;
|
|
24
24
|
let chainId = param.chainId;
|
|
25
|
-
let name = "HyperSync";
|
|
26
25
|
let apiToken$1 = apiToken !== undefined ? apiToken : Stdlib_JsError.throwWithMessage(`An Envio API token is required for using HyperSync as a data-source.
|
|
27
26
|
Set the ENVIO_API_TOKEN environment variable in your .env file.
|
|
28
27
|
Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
@@ -58,22 +57,16 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
58
57
|
let pageUnsafe;
|
|
59
58
|
try {
|
|
60
59
|
pageUnsafe = await HyperSync.GetLogs.query(client, fromBlock, toBlock, itemsTarget, selection.onEventRegistrations.map(reg => reg.index), addressSet, selection.clientFilteredContracts);
|
|
61
|
-
} catch (
|
|
62
|
-
let
|
|
63
|
-
if (
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
backoffMillis: backoffMillis
|
|
72
|
-
};
|
|
73
|
-
} else {
|
|
74
|
-
tmp = {
|
|
75
|
-
TAG: "ImpossibleForTheQuery",
|
|
76
|
-
message: `Source returned invalid data with missing required fields: ` + error$1.missingParams.join(", ")
|
|
60
|
+
} catch (raw_exn) {
|
|
61
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
62
|
+
if (exn.RE_EXN_ID === HyperSync.GetLogs.$$Error) {
|
|
63
|
+
let match = exn._1;
|
|
64
|
+
if (typeof match !== "object") {
|
|
65
|
+
throw {
|
|
66
|
+
RE_EXN_ID: Source.SourceBehindHead,
|
|
67
|
+
blockNumber: fromBlock,
|
|
68
|
+
requestStats: [],
|
|
69
|
+
Error: new Error()
|
|
77
70
|
};
|
|
78
71
|
}
|
|
79
72
|
throw {
|
|
@@ -82,19 +75,25 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
82
75
|
TAG: "FailedGettingItems",
|
|
83
76
|
exn: null,
|
|
84
77
|
attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
|
|
85
|
-
retry:
|
|
78
|
+
retry: {
|
|
79
|
+
TAG: "ImpossibleForTheQuery",
|
|
80
|
+
message: `Source returned invalid data with missing required fields: ` + match.missingParams.join(", ")
|
|
81
|
+
}
|
|
86
82
|
},
|
|
87
83
|
Error: new Error()
|
|
88
84
|
};
|
|
89
85
|
}
|
|
90
|
-
if (
|
|
91
|
-
throw
|
|
86
|
+
if (exn.RE_EXN_ID === Source.RateLimited) {
|
|
87
|
+
throw exn;
|
|
88
|
+
}
|
|
89
|
+
if (exn.RE_EXN_ID === Source.SourceBehindHead) {
|
|
90
|
+
throw exn;
|
|
92
91
|
}
|
|
93
92
|
throw {
|
|
94
93
|
RE_EXN_ID: Source.GetItemsError,
|
|
95
94
|
_1: {
|
|
96
95
|
TAG: "FailedGettingItems",
|
|
97
|
-
exn:
|
|
96
|
+
exn: exn,
|
|
98
97
|
attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
|
|
99
98
|
retry: {
|
|
100
99
|
TAG: "WithBackoff",
|
|
@@ -114,41 +113,11 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
114
113
|
let heighestBlockQueried = pageUnsafe.nextBlock - 1 | 0;
|
|
115
114
|
let parsingTimeRef = Performance.now();
|
|
116
115
|
let parsedQueueItems = [];
|
|
117
|
-
let blocksByNumber = new Map();
|
|
118
|
-
pageUnsafe.blocks.forEach(block => {
|
|
119
|
-
blocksByNumber.set(block.number, block);
|
|
120
|
-
});
|
|
121
116
|
pageUnsafe.items.forEach(item => {
|
|
122
117
|
let onEventRegistration = onEventRegistrations[item.onEventRegistrationIndex];
|
|
123
118
|
parsedQueueItems.push(makeEventBatchQueueItem(item, onEventRegistration));
|
|
124
119
|
});
|
|
125
120
|
let parsingTimeElapsed = Performance.secondsSince(parsingTimeRef);
|
|
126
|
-
let blockHashes = [];
|
|
127
|
-
pageUnsafe.blocks.forEach(block => {
|
|
128
|
-
blockHashes.push({
|
|
129
|
-
blockHash: block.hash,
|
|
130
|
-
blockNumber: block.number
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
let match = pageUnsafe.rollbackGuard;
|
|
134
|
-
if (match !== undefined) {
|
|
135
|
-
blockHashes.push({
|
|
136
|
-
blockHash: match.hash,
|
|
137
|
-
blockNumber: match.blockNumber
|
|
138
|
-
});
|
|
139
|
-
blockHashes.push({
|
|
140
|
-
blockHash: match.firstParentHash,
|
|
141
|
-
blockNumber: match.firstBlockNumber - 1 | 0
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
let match$1 = pageUnsafe.rollbackGuard;
|
|
145
|
-
let latestFetchedBlockTimestamp;
|
|
146
|
-
if (match$1 !== undefined) {
|
|
147
|
-
latestFetchedBlockTimestamp = match$1.timestamp;
|
|
148
|
-
} else {
|
|
149
|
-
let item = pageUnsafe.items[pageUnsafe.items.length - 1 | 0];
|
|
150
|
-
latestFetchedBlockTimestamp = item !== undefined && item.blockNumber === heighestBlockQueried ? blocksByNumber.get(item.blockNumber).timestamp : 0;
|
|
151
|
-
}
|
|
152
121
|
let totalTimeElapsed = Performance.secondsSince(totalTimeRef);
|
|
153
122
|
let stats_parsing$unknowntime$unknown$lpars$rpar = parsingTimeElapsed;
|
|
154
123
|
let stats_page$unknownfetch$unknowntime$unknown$lpars$rpar = pageFetchTime;
|
|
@@ -159,23 +128,18 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
159
128
|
};
|
|
160
129
|
return {
|
|
161
130
|
knownHeight: knownHeight$1,
|
|
162
|
-
blockHashes: blockHashes,
|
|
163
131
|
parsedQueueItems: parsedQueueItems,
|
|
164
132
|
transactionStore: Primitive_option.some(pageUnsafe.transactionStore),
|
|
165
|
-
blockStore:
|
|
133
|
+
blockStore: pageUnsafe.blockStore,
|
|
166
134
|
fromBlockQueried: fromBlock,
|
|
167
135
|
latestFetchedBlockNumber: heighestBlockQueried,
|
|
168
|
-
latestFetchedBlockTimestamp: latestFetchedBlockTimestamp,
|
|
169
136
|
stats: stats,
|
|
170
137
|
requestStats: requestStats
|
|
171
138
|
};
|
|
172
139
|
};
|
|
173
|
-
let getBlockHashes = (blockNumbers
|
|
174
|
-
result: HyperSync.mapExn(param[0]),
|
|
175
|
-
requestStats: param[1]
|
|
176
|
-
}));
|
|
140
|
+
let getBlockHashes = HyperSync.makeGetBlockHashes(blockNumbers => client.getBlockHashes(blockNumbers));
|
|
177
141
|
return {
|
|
178
|
-
name:
|
|
142
|
+
name: "HyperSync",
|
|
179
143
|
sourceFor: "Sync",
|
|
180
144
|
chainId: chainId,
|
|
181
145
|
poweredByHyperSync: true,
|
|
@@ -232,4 +196,4 @@ export {
|
|
|
232
196
|
isUnauthorizedError,
|
|
233
197
|
make,
|
|
234
198
|
}
|
|
235
|
-
/*
|
|
199
|
+
/* Source Not a pure module */
|
package/src/sources/Fuel.res
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Fuel's concrete item payload. Erased to `Internal.eventPayload` on the item
|
|
2
|
-
// and recovered here via `toPayload`.
|
|
2
|
+
// and recovered here via `toPayload`. The block lives raw in the per-chain
|
|
3
|
+
// store and is written onto the payload at batch prep (like HyperSync EVM);
|
|
4
|
+
// simulate builds it inline.
|
|
3
5
|
type payload = {
|
|
4
6
|
contractName: string,
|
|
5
7
|
eventName: string,
|
|
@@ -8,11 +10,24 @@ type payload = {
|
|
|
8
10
|
srcAddress: Address.t,
|
|
9
11
|
logIndex: int,
|
|
10
12
|
transaction: Internal.eventTransaction,
|
|
11
|
-
block
|
|
13
|
+
block?: Internal.eventBlock,
|
|
12
14
|
}
|
|
13
15
|
external fromPayload: payload => Internal.eventPayload = "%identity"
|
|
14
16
|
external toPayload: Internal.eventPayload => payload = "%identity"
|
|
15
17
|
|
|
18
|
+
// Ordered block field names. The index of each is the field code shared with
|
|
19
|
+
// the Rust store (`FuelBlockField`) — keep this order in sync.
|
|
20
|
+
let blockFields = ["height", "id", "time"]
|
|
21
|
+
|
|
22
|
+
// Fuel has no per-event block field selection: every event materialises the
|
|
23
|
+
// full (height, time, id) trio, matching what the source always queries.
|
|
24
|
+
let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields)
|
|
25
|
+
|
|
26
|
+
// Fuel keeps the transaction inline on the event payload rather than in the
|
|
27
|
+
// per-chain store, so there is nothing to select and no field code to hold.
|
|
28
|
+
let transactionFields = []
|
|
29
|
+
let eventTransactionFieldMask = TransactionStore.makeMaskFn(transactionFields)
|
|
30
|
+
|
|
16
31
|
let cleanUpRawEventFieldsInPlace: JSON.t => unit = %raw(`fields => {
|
|
17
32
|
delete fields.id
|
|
18
33
|
delete fields.height
|
|
@@ -24,7 +39,6 @@ let make = (~logger: Pino.t): Ecosystem.t => {
|
|
|
24
39
|
blockNumberName: "height",
|
|
25
40
|
blockTimestampName: "time",
|
|
26
41
|
blockHashName: "id",
|
|
27
|
-
cleanUpRawEventFieldsInPlace,
|
|
28
42
|
onBlockMethodName: "onBlock",
|
|
29
43
|
// Fuel filter shape: `{block: {height: {_gte?, _lte?, _every?}}}`.
|
|
30
44
|
// Inner range chunk parsed by `blockRangeSchema` in `Main.res`.
|
|
@@ -56,7 +70,13 @@ let make = (~logger: Pino.t): Ecosystem.t => {
|
|
|
56
70
|
),
|
|
57
71
|
toRawEvent: eventItem => {
|
|
58
72
|
let payload = eventItem.payload->toPayload
|
|
59
|
-
|
|
73
|
+
// Store-backed payloads get `block` written at batch prep and simulate
|
|
74
|
+
// carries it from the start, so it's present by the time a raw event is built.
|
|
75
|
+
let header = switch payload.block {
|
|
76
|
+
| Some(block) => block->(Utils.magic: Internal.eventBlock => {"id": string, "time": int})
|
|
77
|
+
| None =>
|
|
78
|
+
JsError.throwWithMessage("Unexpected case: The event block is missing for a raw event")
|
|
79
|
+
}
|
|
60
80
|
eventItem->RawEvent.make(
|
|
61
81
|
~block=payload.block,
|
|
62
82
|
~transaction=payload.transaction,
|
package/src/sources/Fuel.res.mjs
CHANGED
|
@@ -2,7 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Logging from "../Logging.res.mjs";
|
|
4
4
|
import * as RawEvent from "../RawEvent.res.mjs";
|
|
5
|
+
import * as BlockStore from "./BlockStore.res.mjs";
|
|
6
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
5
8
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
9
|
+
import * as TransactionStore from "./TransactionStore.res.mjs";
|
|
10
|
+
|
|
11
|
+
let blockFields = [
|
|
12
|
+
"height",
|
|
13
|
+
"id",
|
|
14
|
+
"time"
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields);
|
|
18
|
+
|
|
19
|
+
let transactionFields = [];
|
|
20
|
+
|
|
21
|
+
let eventTransactionFieldMask = TransactionStore.makeMaskFn(transactionFields);
|
|
6
22
|
|
|
7
23
|
let cleanUpRawEventFieldsInPlace = (fields => {
|
|
8
24
|
delete fields.id
|
|
@@ -16,7 +32,6 @@ function make(logger) {
|
|
|
16
32
|
blockNumberName: "height",
|
|
17
33
|
blockTimestampName: "time",
|
|
18
34
|
blockHashName: "id",
|
|
19
|
-
cleanUpRawEventFieldsInPlace: cleanUpRawEventFieldsInPlace,
|
|
20
35
|
onBlockMethodName: "onBlock",
|
|
21
36
|
onBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("height", S$RescriptSchema.unknown))))),
|
|
22
37
|
onEventBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.strict(S$RescriptSchema.object(s2 => s2.f("height", S$RescriptSchema.unknown)))))),
|
|
@@ -32,14 +47,19 @@ function make(logger) {
|
|
|
32
47
|
}),
|
|
33
48
|
toRawEvent: eventItem => {
|
|
34
49
|
let payload = eventItem.payload;
|
|
35
|
-
let
|
|
50
|
+
let block = payload.block;
|
|
51
|
+
let header = block !== undefined ? Primitive_option.valFromOption(block) : Stdlib_JsError.throwWithMessage("Unexpected case: The event block is missing for a raw event");
|
|
36
52
|
return RawEvent.make(eventItem, payload.block, payload.transaction, payload.params, payload.srcAddress, header.id, header.time, cleanUpRawEventFieldsInPlace);
|
|
37
53
|
}
|
|
38
54
|
};
|
|
39
55
|
}
|
|
40
56
|
|
|
41
57
|
export {
|
|
58
|
+
blockFields,
|
|
59
|
+
eventBlockFieldMask,
|
|
60
|
+
transactionFields,
|
|
61
|
+
eventTransactionFieldMask,
|
|
42
62
|
cleanUpRawEventFieldsInPlace,
|
|
43
63
|
make,
|
|
44
64
|
}
|
|
45
|
-
/*
|
|
65
|
+
/* eventBlockFieldMask Not a pure module */
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
type logsQueryPage = {
|
|
2
2
|
items: array<FuelHyperSyncClient.EventItems.item>,
|
|
3
3
|
// Blocks referenced by `items`, one per height.
|
|
4
|
-
blocks: array<FuelHyperSyncClient.EventItems.block>,
|
|
5
4
|
nextBlock: int,
|
|
6
5
|
archiveHeight: int,
|
|
6
|
+
// Page store owning this page's raw blocks (built on the Rust side); its ids
|
|
7
|
+
// drive reorg detection when merged into the chain store.
|
|
8
|
+
blockStore: BlockStore.t,
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
module GetLogs = {
|
|
@@ -53,7 +55,10 @@ module GetLogs = {
|
|
|
53
55
|
clientFilteredContracts,
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
let res = switch await client->FuelHyperSyncClient.getEventItems(
|
|
58
|
+
let (res, blockStore) = switch await client->FuelHyperSyncClient.getEventItems(
|
|
59
|
+
query,
|
|
60
|
+
addressSet,
|
|
61
|
+
) {
|
|
57
62
|
| res => res
|
|
58
63
|
| exception exn =>
|
|
59
64
|
switch exn->extractMissingParams {
|
|
@@ -67,9 +72,9 @@ module GetLogs = {
|
|
|
67
72
|
}
|
|
68
73
|
{
|
|
69
74
|
items: res.items,
|
|
70
|
-
blocks: res.blocks,
|
|
71
75
|
nextBlock: res.nextBlock,
|
|
72
76
|
archiveHeight: res.archiveHeight->Option.getOr(0), // TODO: FIXME: Shouldn't have a default here
|
|
77
|
+
blockStore,
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
}
|