envio 3.4.0 → 3.5.0-alpha.1
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/package.json +6 -6
- package/src/ChainState.res +8 -17
- package/src/ChainState.res.mjs +20 -8
- package/src/ChainState.resi +1 -7
- package/src/Config.res +9 -1
- package/src/Config.res.mjs +2 -1
- package/src/CrossChainState.res +2 -20
- package/src/CrossChainState.res.mjs +2 -6
- package/src/Env.res +16 -0
- package/src/Env.res.mjs +6 -0
- package/src/FetchState.res +369 -76
- package/src/FetchState.res.mjs +280 -68
- package/src/IndexingAddresses.res +67 -24
- package/src/IndexingAddresses.res.mjs +51 -22
- package/src/IndexingAddresses.resi +9 -4
- package/src/TestIndexer.res +8 -2
- package/src/TestIndexer.res.mjs +2 -2
- package/src/sources/EvmHyperSyncSource.res +3 -6
- package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
- package/src/sources/EvmRpcClient.res +4 -0
- package/src/sources/HyperSync.res +3 -1
- package/src/sources/HyperSync.res.mjs +3 -2
- package/src/sources/HyperSync.resi +2 -1
- package/src/sources/HyperSyncClient.res +6 -1
- package/src/sources/RpcSource.res +57 -58
- package/src/sources/RpcSource.res.mjs +2 -1
- package/src/sources/Source.res +4 -2
- package/src/sources/SvmHyperSyncClient.res +2 -1
- package/src/sources/SvmHyperSyncSource.res +1 -1
|
@@ -2,7 +2,12 @@ type indexingAddress = Internal.indexingContract
|
|
|
2
2
|
|
|
3
3
|
type contractConfig = {startBlock: option<int>}
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Grouped by contract name, then by address string. Contracts are few, so
|
|
6
|
+
// per-contract operations (address count, the client-side filter's lookup
|
|
7
|
+
// dict) are direct, and whole-index scans (get by address, rollback) walk the
|
|
8
|
+
// small contract set. Address strings are globally unique across contracts
|
|
9
|
+
// (conflicting registrations are rejected before they reach here).
|
|
10
|
+
type t = dict<dict<indexingAddress>>
|
|
6
11
|
|
|
7
12
|
let deriveEffectiveStartBlock = (~registrationBlock: int, ~contractStartBlock: option<int>) => {
|
|
8
13
|
Pervasives.max(Pervasives.max(registrationBlock, 0), contractStartBlock->Option.getOr(0))
|
|
@@ -59,50 +64,88 @@ let makeIndexingAddress = (
|
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
let insert = (indexingAddresses: t, indexingAddress: indexingAddress) => {
|
|
68
|
+
indexingAddresses
|
|
69
|
+
->Utils.Dict.getOrInsertEmptyDict(indexingAddress.contractName)
|
|
70
|
+
->Dict.set(indexingAddress.address->Address.toString, indexingAddress)
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
let make = (
|
|
63
74
|
~contractConfigs: dict<contractConfig>,
|
|
64
75
|
~addresses: array<Internal.indexingAddress>,
|
|
65
76
|
): t => {
|
|
66
77
|
let indexingAddresses = Dict.make()
|
|
67
78
|
addresses->Array.forEach(contract => {
|
|
68
|
-
indexingAddresses->
|
|
69
|
-
contract.address->Address.toString,
|
|
70
|
-
makeIndexingAddress(~contract, ~contractConfigs),
|
|
71
|
-
)
|
|
79
|
+
indexingAddresses->insert(makeIndexingAddress(~contract, ~contractConfigs))
|
|
72
80
|
})
|
|
73
81
|
indexingAddresses
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
// Address strings are globally unique, so the first inner dict holding the
|
|
85
|
+
// address owns it. for..in returns on the first hit without allocating a values
|
|
86
|
+
// array per lookup (get runs once per registration).
|
|
87
|
+
let get: (t, string) => option<indexingAddress> = %raw(`(index, address) => {
|
|
88
|
+
for (var contractName in index) {
|
|
89
|
+
var entry = index[contractName][address];
|
|
90
|
+
if (entry !== undefined) {
|
|
91
|
+
return entry;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}`)
|
|
96
|
+
|
|
97
|
+
let size = (indexingAddresses: t) => {
|
|
98
|
+
let total = ref(0)
|
|
99
|
+
indexingAddresses->Utils.Dict.forEach(inner => {
|
|
100
|
+
total := total.contents + inner->Utils.Dict.size
|
|
101
|
+
})
|
|
102
|
+
total.contents
|
|
103
|
+
}
|
|
78
104
|
|
|
79
|
-
|
|
105
|
+
// Number of registered addresses for a single contract — a for..in over that
|
|
106
|
+
// one contract's addresses. The trigger deciding when to switch a contract to
|
|
107
|
+
// client-side filtering reads this.
|
|
108
|
+
let contractCount = (indexingAddresses: t, ~contractName) =>
|
|
109
|
+
switch indexingAddresses->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
110
|
+
| Some(inner) => inner->Utils.Dict.size
|
|
111
|
+
| None => 0
|
|
112
|
+
}
|
|
80
113
|
|
|
81
114
|
let getContractAddresses = (indexingAddresses: t, ~contractName): array<Address.t> => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
addresses
|
|
115
|
+
switch indexingAddresses->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
116
|
+
| Some(inner) => inner->Dict.valuesToArray->Array.map(ia => ia.address)
|
|
117
|
+
| None => []
|
|
118
|
+
}
|
|
89
119
|
}
|
|
90
120
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
|
|
121
|
+
let emptyContractDict: dict<indexingAddress> = Dict.make()
|
|
122
|
+
|
|
123
|
+
// The address→entry dict for a single contract, passed to that contract's
|
|
124
|
+
// precompiled `clientAddressFilter` (which does raw `byAddr[srcAddress]` access
|
|
125
|
+
// in generated JS). Every leaf of a filter references the event's own contract
|
|
126
|
+
// — `chain.<Contract>.addresses` only exposes the event's contract — so one
|
|
127
|
+
// inner dict covers the srcAddress and param-address checks alike. Returns a
|
|
128
|
+
// shared empty dict when the contract has no registered addresses.
|
|
129
|
+
let forContract = (indexingAddresses: t, ~contractName): dict<indexingAddress> =>
|
|
130
|
+
switch indexingAddresses->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
131
|
+
| Some(inner) => inner
|
|
132
|
+
| None => emptyContractDict
|
|
133
|
+
}
|
|
95
134
|
|
|
96
135
|
let register = (indexingAddresses: t, additions: dict<indexingAddress>) => {
|
|
97
|
-
|
|
136
|
+
additions->Utils.Dict.forEach(indexingAddress => {
|
|
137
|
+
indexingAddresses->insert(indexingAddress)
|
|
138
|
+
})
|
|
98
139
|
}
|
|
99
140
|
|
|
100
141
|
let rollbackInPlace = (indexingAddresses: t, ~targetBlockNumber: int): unit => {
|
|
101
142
|
// forEachWithKey is a `for..in`, so deleting the key currently being visited is
|
|
102
143
|
// safe — it doesn't affect enumeration of the remaining keys.
|
|
103
|
-
indexingAddresses->Utils.Dict.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
144
|
+
indexingAddresses->Utils.Dict.forEach(inner => {
|
|
145
|
+
inner->Utils.Dict.forEachWithKey((indexingContract, address) => {
|
|
146
|
+
if indexingContract.registrationBlock > targetBlockNumber {
|
|
147
|
+
inner->Utils.Dict.deleteInPlace(address)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
107
150
|
})
|
|
108
151
|
}
|
|
@@ -43,47 +43,75 @@ function makeIndexingAddress(contract, contractConfigs) {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function insert(indexingAddresses, indexingAddress) {
|
|
47
|
+
Utils.Dict.getOrInsertEmptyDict(indexingAddresses, indexingAddress.contractName)[indexingAddress.address] = indexingAddress;
|
|
48
|
+
}
|
|
49
|
+
|
|
46
50
|
function make(contractConfigs, addresses) {
|
|
47
51
|
let indexingAddresses = {};
|
|
48
|
-
addresses.forEach(contract =>
|
|
49
|
-
indexingAddresses[contract.address] = makeIndexingAddress(contract, contractConfigs);
|
|
50
|
-
});
|
|
52
|
+
addresses.forEach(contract => insert(indexingAddresses, makeIndexingAddress(contract, contractConfigs)));
|
|
51
53
|
return indexingAddresses;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
let get = ((index, address) => {
|
|
57
|
+
for (var contractName in index) {
|
|
58
|
+
var entry = index[contractName][address];
|
|
59
|
+
if (entry !== undefined) {
|
|
60
|
+
return entry;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
});
|
|
57
65
|
|
|
58
66
|
function size(indexingAddresses) {
|
|
59
|
-
|
|
67
|
+
let total = {
|
|
68
|
+
contents: 0
|
|
69
|
+
};
|
|
70
|
+
Utils.Dict.forEach(indexingAddresses, inner => {
|
|
71
|
+
total.contents = total.contents + Utils.Dict.size(inner) | 0;
|
|
72
|
+
});
|
|
73
|
+
return total.contents;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function contractCount(indexingAddresses, contractName) {
|
|
77
|
+
let inner = indexingAddresses[contractName];
|
|
78
|
+
if (inner !== undefined) {
|
|
79
|
+
return Utils.Dict.size(inner);
|
|
80
|
+
} else {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
function getContractAddresses(indexingAddresses, contractName) {
|
|
63
|
-
let
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
70
|
-
return addresses;
|
|
86
|
+
let inner = indexingAddresses[contractName];
|
|
87
|
+
if (inner !== undefined) {
|
|
88
|
+
return Object.values(inner).map(ia => ia.address);
|
|
89
|
+
} else {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
|
|
94
|
+
let emptyContractDict = {};
|
|
95
|
+
|
|
96
|
+
function forContract(indexingAddresses, contractName) {
|
|
97
|
+
let inner = indexingAddresses[contractName];
|
|
98
|
+
if (inner !== undefined) {
|
|
99
|
+
return inner;
|
|
100
|
+
} else {
|
|
101
|
+
return emptyContractDict;
|
|
102
|
+
}
|
|
75
103
|
}
|
|
76
104
|
|
|
77
105
|
function register(indexingAddresses, additions) {
|
|
78
|
-
|
|
106
|
+
Utils.Dict.forEach(additions, indexingAddress => insert(indexingAddresses, indexingAddress));
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
function rollbackInPlace(indexingAddresses, targetBlockNumber) {
|
|
82
|
-
Utils.Dict.
|
|
110
|
+
Utils.Dict.forEach(indexingAddresses, inner => Utils.Dict.forEachWithKey(inner, (indexingContract, address) => {
|
|
83
111
|
if (indexingContract.registrationBlock > targetBlockNumber) {
|
|
84
|
-
return Utils.Dict.deleteInPlace(
|
|
112
|
+
return Utils.Dict.deleteInPlace(inner, address);
|
|
85
113
|
}
|
|
86
|
-
});
|
|
114
|
+
}));
|
|
87
115
|
}
|
|
88
116
|
|
|
89
117
|
export {
|
|
@@ -93,8 +121,9 @@ export {
|
|
|
93
121
|
make,
|
|
94
122
|
get,
|
|
95
123
|
size,
|
|
124
|
+
contractCount,
|
|
96
125
|
getContractAddresses,
|
|
97
|
-
|
|
126
|
+
forContract,
|
|
98
127
|
register,
|
|
99
128
|
rollbackInPlace,
|
|
100
129
|
}
|
|
@@ -21,12 +21,17 @@ let get: (t, string) => option<indexingAddress>
|
|
|
21
21
|
|
|
22
22
|
let size: t => int
|
|
23
23
|
|
|
24
|
+
// Number of registered addresses for a single contract (scans just that
|
|
25
|
+
// contract's addresses).
|
|
26
|
+
let contractCount: (t, ~contractName: string) => int
|
|
27
|
+
|
|
24
28
|
let getContractAddresses: (t, ~contractName: string) => array<Address.t>
|
|
25
29
|
|
|
26
|
-
//
|
|
27
|
-
// `
|
|
28
|
-
// prefer the domain accessors so the
|
|
29
|
-
|
|
30
|
+
// The address→entry dict for a single contract, passed to that contract's
|
|
31
|
+
// precompiled `clientAddressFilter` (raw `byAddr[srcAddress]` access in
|
|
32
|
+
// generated JS). Don't use elsewhere — prefer the domain accessors so the
|
|
33
|
+
// opaque type stays enforced.
|
|
34
|
+
let forContract: (t, ~contractName: string) => dict<indexingAddress>
|
|
30
35
|
|
|
31
36
|
let register: (t, dict<indexingAddress>) => unit
|
|
32
37
|
|
package/src/TestIndexer.res
CHANGED
|
@@ -199,7 +199,11 @@ let handleWriteBatch = (
|
|
|
199
199
|
if deleted->Array.length > 0 {
|
|
200
200
|
entityObj->Dict.set("deleted", deleted->(Utils.magic: array<string> => unknown))
|
|
201
201
|
}
|
|
202
|
-
|
|
202
|
+
// Match the capitalized entity accessor the generated change types expose.
|
|
203
|
+
change->Dict.set(
|
|
204
|
+
entityName->Utils.String.capitalize,
|
|
205
|
+
entityObj->(Utils.magic: dict<unknown> => unknown),
|
|
206
|
+
)
|
|
203
207
|
}
|
|
204
208
|
})
|
|
205
209
|
| None => ()
|
|
@@ -699,7 +703,9 @@ let createTestIndexer = (): t<'processConfig> => {
|
|
|
699
703
|
entityOpsDict
|
|
700
704
|
->Dict.toArray
|
|
701
705
|
->Array.forEach(((name, ops)) => {
|
|
702
|
-
|
|
706
|
+
// Expose the capitalized accessor (indexer.Pool_snapshots) the generated
|
|
707
|
+
// types declare, matching the handler-context keys.
|
|
708
|
+
result->Dict.set(name->Utils.String.capitalize, ops->(Utils.magic: entityOperations => unknown))
|
|
703
709
|
})
|
|
704
710
|
|
|
705
711
|
result->Dict.set(
|
package/src/TestIndexer.res.mjs
CHANGED
|
@@ -146,7 +146,7 @@ function handleWriteBatch(state, updatedEntities, checkpointIds, checkpointChain
|
|
|
146
146
|
if (deleted.length !== 0) {
|
|
147
147
|
entityObj$1["deleted"] = deleted;
|
|
148
148
|
}
|
|
149
|
-
change[entityName] = entityObj$1;
|
|
149
|
+
change[Utils.$$String.capitalize(entityName)] = entityObj$1;
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
state.processChanges.push(change);
|
|
@@ -470,7 +470,7 @@ function createTestIndexer() {
|
|
|
470
470
|
result["chainIds"] = chainIds;
|
|
471
471
|
result["chains"] = chains;
|
|
472
472
|
Object.entries(entityOpsDict).forEach(param => {
|
|
473
|
-
result[param[0]] = param[1];
|
|
473
|
+
result[Utils.$$String.capitalize(param[0])] = param[1];
|
|
474
474
|
});
|
|
475
475
|
result["process"] = processConfig => {
|
|
476
476
|
if (state.processInProgress) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
open Source
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
// Surfaced by HyperSyncClient.getHeight (Rust) when HyperSync rejects the API
|
|
5
4
|
// token. The corrupted-token test feeds the real server error through this
|
|
6
5
|
// check so it can't silently drift away from what getHeightOrThrow guards on.
|
|
@@ -46,9 +45,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
46
45
|
~url=endpointUrl,
|
|
47
46
|
~apiToken,
|
|
48
47
|
~httpReqTimeoutMillis=clientTimeoutMillis,
|
|
49
|
-
~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(
|
|
50
|
-
onEventRegistrations,
|
|
51
|
-
),
|
|
48
|
+
~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations),
|
|
52
49
|
~enableChecksumAddresses=!lowercaseAddresses,
|
|
53
50
|
~serializationFormat,
|
|
54
51
|
~enableQueryCaching,
|
|
@@ -110,6 +107,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
110
107
|
~maxNumLogs=itemsTarget,
|
|
111
108
|
~registrationIndexes=selection.onEventRegistrations->Array.map(reg => reg.index),
|
|
112
109
|
~addressesByContractName,
|
|
110
|
+
~clientFilteredContracts=selection.clientFilteredContracts,
|
|
113
111
|
) catch {
|
|
114
112
|
| HyperSync.GetLogs.Error(error) =>
|
|
115
113
|
throw(
|
|
@@ -180,8 +178,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
|
|
|
180
178
|
let getBlock = blockNumber => blocksByNumber->Utils.Map.unsafeGet(blockNumber)
|
|
181
179
|
|
|
182
180
|
pageUnsafe.items->Array.forEach(item => {
|
|
183
|
-
let onEventRegistration =
|
|
184
|
-
onEventRegistrations->Array.getUnsafe(item.onEventRegistrationIndex)
|
|
181
|
+
let onEventRegistration = onEventRegistrations->Array.getUnsafe(item.onEventRegistrationIndex)
|
|
185
182
|
parsedQueueItems
|
|
186
183
|
->Array.push(makeEventBatchQueueItem(item, ~onEventRegistration))
|
|
187
184
|
->ignore
|
|
@@ -57,7 +57,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
|
|
|
57
57
|
let startFetchingBatchTimeRef = Performance.now();
|
|
58
58
|
let pageUnsafe;
|
|
59
59
|
try {
|
|
60
|
-
pageUnsafe = await HyperSync.GetLogs.query(client, fromBlock, toBlock, itemsTarget, selection.onEventRegistrations.map(reg => reg.index), addressesByContractName);
|
|
60
|
+
pageUnsafe = await HyperSync.GetLogs.query(client, fromBlock, toBlock, itemsTarget, selection.onEventRegistrations.map(reg => reg.index), addressesByContractName, selection.clientFilteredContracts);
|
|
61
61
|
} catch (raw_error) {
|
|
62
62
|
let error = Primitive_exceptions.internalToException(raw_error);
|
|
63
63
|
if (error.RE_EXN_ID === HyperSync.GetLogs.$$Error) {
|
|
@@ -27,6 +27,10 @@ type nextPageParams = {
|
|
|
27
27
|
// registrations passed at construction.
|
|
28
28
|
registrationIndexes: array<int>,
|
|
29
29
|
addressesByContractName: dict<array<Address.t>>,
|
|
30
|
+
// Contract names to fetch address-free even though their registrations
|
|
31
|
+
// depend on addresses (client-side filtering). None/empty means
|
|
32
|
+
// every address-dependent contract is filtered server-side.
|
|
33
|
+
clientFilteredContracts: option<array<string>>,
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
type nextPageResponse = {
|
|
@@ -93,13 +93,15 @@ module GetLogs = {
|
|
|
93
93
|
~maxNumLogs,
|
|
94
94
|
~registrationIndexes,
|
|
95
95
|
~addressesByContractName,
|
|
96
|
+
~clientFilteredContracts,
|
|
96
97
|
): logsQueryPage => {
|
|
97
98
|
let query: HyperSyncClient.EventItems.query = {
|
|
98
99
|
fromBlock,
|
|
99
100
|
toBlock,
|
|
100
|
-
maxNumLogs,
|
|
101
|
+
?maxNumLogs,
|
|
101
102
|
registrationIndexes,
|
|
102
103
|
addressesByContractName,
|
|
104
|
+
clientFilteredContracts,
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
let (res, transactionStore, blockStore) = switch await client.getEventItems(~query) {
|
|
@@ -82,13 +82,14 @@ function extractMissingParams(exn) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
async function query(client, fromBlock, toBlock, maxNumLogs, registrationIndexes, addressesByContractName) {
|
|
85
|
+
async function query(client, fromBlock, toBlock, maxNumLogs, registrationIndexes, addressesByContractName, clientFilteredContracts) {
|
|
86
86
|
let query$1 = {
|
|
87
87
|
fromBlock: fromBlock,
|
|
88
88
|
toBlock: toBlock,
|
|
89
89
|
maxNumLogs: maxNumLogs,
|
|
90
90
|
registrationIndexes: registrationIndexes,
|
|
91
|
-
addressesByContractName: addressesByContractName
|
|
91
|
+
addressesByContractName: addressesByContractName,
|
|
92
|
+
clientFilteredContracts: clientFilteredContracts
|
|
92
93
|
};
|
|
93
94
|
let match;
|
|
94
95
|
try {
|
|
@@ -32,9 +32,10 @@ module GetLogs: {
|
|
|
32
32
|
~client: HyperSyncClient.t,
|
|
33
33
|
~fromBlock: int,
|
|
34
34
|
~toBlock: option<int>,
|
|
35
|
-
~maxNumLogs: int
|
|
35
|
+
~maxNumLogs: option<int>,
|
|
36
36
|
~registrationIndexes: array<int>,
|
|
37
37
|
~addressesByContractName: dict<array<Address.t>>,
|
|
38
|
+
~clientFilteredContracts: option<array<string>>,
|
|
38
39
|
) => promise<logsQueryPage>
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -309,9 +309,14 @@ module EventItems = {
|
|
|
309
309
|
fromBlock: int,
|
|
310
310
|
// Inclusive; None queries to the end of available data.
|
|
311
311
|
toBlock: option<int>,
|
|
312
|
-
|
|
312
|
+
// Absent means no server-side cap on the number of logs returned.
|
|
313
|
+
maxNumLogs?: int,
|
|
313
314
|
registrationIndexes: array<int>,
|
|
314
315
|
addressesByContractName: dict<array<Address.t>>,
|
|
316
|
+
// Contract names to fetch address-free even though their registrations
|
|
317
|
+
// depend on addresses (client-side filtering). None/empty means
|
|
318
|
+
// every address-dependent contract is filtered server-side.
|
|
319
|
+
clientFilteredContracts: option<array<string>>,
|
|
315
320
|
}
|
|
316
321
|
|
|
317
322
|
type item = {
|
|
@@ -97,7 +97,6 @@ let getErrorMessage = (exn: exn): option<string> =>
|
|
|
97
97
|
| _ => None
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
101
100
|
// `EvmRpcClient.getNextPage` throws a napi error whose message is a JSON
|
|
102
101
|
// payload describing the retry decision:
|
|
103
102
|
// `{"kind":"Retry","attemptedToBlock":int,"errorMessage":string|null,
|
|
@@ -647,9 +646,7 @@ let make = (
|
|
|
647
646
|
let client = Rpc.makeClient(url, ~headers?)
|
|
648
647
|
let rpcClient = EvmRpcClient.make(
|
|
649
648
|
~url,
|
|
650
|
-
~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(
|
|
651
|
-
onEventRegistrations,
|
|
652
|
-
),
|
|
649
|
+
~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations),
|
|
653
650
|
~checksumAddresses=!lowercaseAddresses,
|
|
654
651
|
~syncConfig,
|
|
655
652
|
~headers?,
|
|
@@ -839,6 +836,7 @@ let make = (
|
|
|
839
836
|
partitionId,
|
|
840
837
|
registrationIndexes: selection.onEventRegistrations->Array.map(reg => reg.index),
|
|
841
838
|
addressesByContractName,
|
|
839
|
+
clientFilteredContracts: selection.clientFilteredContracts,
|
|
842
840
|
}) catch {
|
|
843
841
|
| exn =>
|
|
844
842
|
switch exn->parseGetNextPageRetryError {
|
|
@@ -879,63 +877,64 @@ let make = (
|
|
|
879
877
|
onEventRegistration.eventConfig->(
|
|
880
878
|
Utils.magic: Internal.eventConfig => Internal.evmEventConfig
|
|
881
879
|
)
|
|
880
|
+
|
|
882
881
|
(
|
|
883
882
|
async () => {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
logIndex: log.logIndex,
|
|
917
|
-
}),
|
|
918
|
-
),
|
|
919
|
-
)
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
Internal.Event({
|
|
923
|
-
onEventRegistration: (onEventRegistration :> Internal.onEventRegistration),
|
|
924
|
-
blockNumber: block->getBlockNumber,
|
|
925
|
-
chain,
|
|
883
|
+
let (block, transaction) = try await Promise.all2((
|
|
884
|
+
log->getEventBlockOrThrow(~selectedBlockFields=eventConfig.selectedBlockFields),
|
|
885
|
+
log->getEventTransactionOrThrow(
|
|
886
|
+
~selectedTransactionFields=eventConfig.selectedTransactionFields->(
|
|
887
|
+
Utils.magic: Utils.Set.t<string> => Utils.Set.t<Internal.evmTransactionField>
|
|
888
|
+
),
|
|
889
|
+
),
|
|
890
|
+
)) catch {
|
|
891
|
+
| TransactionDataNotFound({message}) =>
|
|
892
|
+
let backoffMillis = switch retry {
|
|
893
|
+
| 0 => 100
|
|
894
|
+
| _ => 500 * retry
|
|
895
|
+
}
|
|
896
|
+
throw(
|
|
897
|
+
Source.GetItemsError(
|
|
898
|
+
FailedGettingItems({
|
|
899
|
+
exn: %raw(`null`),
|
|
900
|
+
attemptedToBlock: toBlock,
|
|
901
|
+
retry: WithBackoff({
|
|
902
|
+
message: `${message}. The RPC provider might be load-balanced between nodes that drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${backoffMillis->Int.toString}ms.`,
|
|
903
|
+
backoffMillis,
|
|
904
|
+
}),
|
|
905
|
+
}),
|
|
906
|
+
),
|
|
907
|
+
)
|
|
908
|
+
| exn =>
|
|
909
|
+
throw(
|
|
910
|
+
Source.GetItemsError(
|
|
911
|
+
FailedGettingFieldSelection({
|
|
912
|
+
message: "Failed getting selected fields. Please double-check your RPC provider returns correct data.",
|
|
913
|
+
exn,
|
|
914
|
+
blockNumber: log.blockNumber,
|
|
926
915
|
logIndex: log.logIndex,
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
916
|
+
}),
|
|
917
|
+
),
|
|
918
|
+
)
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
Internal.Event({
|
|
922
|
+
onEventRegistration: (onEventRegistration :> Internal.onEventRegistration),
|
|
923
|
+
blockNumber: block->getBlockNumber,
|
|
924
|
+
chain,
|
|
925
|
+
logIndex: log.logIndex,
|
|
926
|
+
transactionIndex: log.transactionIndex,
|
|
927
|
+
payload: {
|
|
928
|
+
contractName: eventConfig.contractName,
|
|
929
|
+
eventName: eventConfig.name,
|
|
930
|
+
chainId: chain->ChainMap.Chain.toChainId,
|
|
931
|
+
params: decoded,
|
|
932
|
+
block,
|
|
933
|
+
transaction,
|
|
934
|
+
srcAddress: log.address,
|
|
935
|
+
logIndex: log.logIndex,
|
|
936
|
+
}->Evm.fromPayload,
|
|
937
|
+
})
|
|
939
938
|
}
|
|
940
939
|
)()
|
|
941
940
|
})
|
|
@@ -841,7 +841,8 @@ function make(param) {
|
|
|
841
841
|
toBlockCeiling: toBlock$1,
|
|
842
842
|
partitionId: partitionId,
|
|
843
843
|
registrationIndexes: selection.onEventRegistrations.map(reg => reg.index),
|
|
844
|
-
addressesByContractName: addressesByContractName
|
|
844
|
+
addressesByContractName: addressesByContractName,
|
|
845
|
+
clientFilteredContracts: selection.clientFilteredContracts
|
|
845
846
|
});
|
|
846
847
|
} catch (raw_exn) {
|
|
847
848
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
package/src/sources/Source.res
CHANGED
|
@@ -82,8 +82,10 @@ type t = {
|
|
|
82
82
|
// source should ask its backend for, from the query's own estResponseSize.
|
|
83
83
|
// A HyperSync-backed source enforces it server-side, so a wrong estimate
|
|
84
84
|
// truncates the response instead of overshooting the shared buffer. Sources
|
|
85
|
-
// without an equivalent lever (RPC, Fuel, Simulate) ignore it.
|
|
86
|
-
|
|
85
|
+
// without an equivalent lever (RPC, Fuel, Simulate) ignore it. None means no
|
|
86
|
+
// cap: bounded chunk queries fetch their whole range even if denser than
|
|
87
|
+
// expected, so client-side-filtered items can't truncate the range short.
|
|
88
|
+
~itemsTarget: option<int>,
|
|
87
89
|
~retry: int,
|
|
88
90
|
~logger: Pino.t,
|
|
89
91
|
) => promise<blockRangeFetchResponse>,
|
|
@@ -188,7 +188,8 @@ module EventItems = {
|
|
|
188
188
|
fromSlot: int,
|
|
189
189
|
// Inclusive; None queries to the end of available data.
|
|
190
190
|
toSlot: option<int>,
|
|
191
|
-
|
|
191
|
+
// Absent means no server-side cap on the number of instructions returned.
|
|
192
|
+
maxNumInstructions?: int,
|
|
192
193
|
registrationIndexes: array<int>,
|
|
193
194
|
addressesByContractName: dict<array<Address.t>>,
|
|
194
195
|
}
|
|
@@ -100,7 +100,7 @@ let make = (
|
|
|
100
100
|
let query: SvmHyperSyncClient.EventItems.query = {
|
|
101
101
|
fromSlot: fromBlock,
|
|
102
102
|
toSlot: toBlock,
|
|
103
|
-
maxNumInstructions: itemsTarget,
|
|
103
|
+
maxNumInstructions: ?itemsTarget,
|
|
104
104
|
registrationIndexes: selection.onEventRegistrations->Array.map(reg => reg.index),
|
|
105
105
|
addressesByContractName,
|
|
106
106
|
}
|