ponder 0.18.1 → 0.18.2
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/CHANGELOG.md +8 -0
- package/dist/esm/rpc/actions.js +2 -16
- package/dist/esm/rpc/actions.js.map +1 -1
- package/dist/esm/runtime/filter.js +70 -20
- package/dist/esm/runtime/filter.js.map +1 -1
- package/dist/esm/sync-historical/in-memory.js +1 -2
- package/dist/esm/sync-historical/in-memory.js.map +1 -1
- package/dist/esm/sync-historical/index.js +1 -2
- package/dist/esm/sync-historical/index.js.map +1 -1
- package/dist/esm/sync-realtime/bloom.js +21 -23
- package/dist/esm/sync-realtime/bloom.js.map +1 -1
- package/dist/esm/sync-realtime/index.js +28 -20
- package/dist/esm/sync-realtime/index.js.map +1 -1
- package/dist/types/rpc/actions.d.ts +2 -3
- package/dist/types/rpc/actions.d.ts.map +1 -1
- package/dist/types/runtime/filter.d.ts +24 -1
- package/dist/types/runtime/filter.d.ts.map +1 -1
- package/dist/types/sync-historical/in-memory.d.ts.map +1 -1
- package/dist/types/sync-historical/index.d.ts.map +1 -1
- package/dist/types/sync-realtime/bloom.d.ts +25 -12
- package/dist/types/sync-realtime/bloom.d.ts.map +1 -1
- package/dist/types/sync-realtime/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/rpc/actions.ts +1 -21
- package/src/runtime/filter.ts +105 -35
- package/src/sync-historical/in-memory.ts +0 -2
- package/src/sync-historical/index.ts +0 -2
- package/src/sync-realtime/bloom.ts +39 -38
- package/src/sync-realtime/index.ts +39 -26
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Hex } from "viem";
|
|
2
|
-
import type { LogFilter } from '../internal/types.js';
|
|
2
|
+
import type { Factory, LogFilter } from '../internal/types.js';
|
|
3
3
|
export declare const zeroLogsBloom = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
|
|
4
4
|
export type BloomBits = {
|
|
5
5
|
byte0: number;
|
|
@@ -15,14 +15,8 @@ export type BloomBits = {
|
|
|
15
15
|
* `undefined` matches every bloom. An array matches if any item is in the bloom.
|
|
16
16
|
*/
|
|
17
17
|
export type LogFilterBloom = {
|
|
18
|
-
fromBlock: number;
|
|
19
|
-
toBlock: number;
|
|
20
18
|
address: BloomBits[] | undefined;
|
|
21
19
|
topics: (BloomBits[] | undefined)[];
|
|
22
|
-
factory: {
|
|
23
|
-
address: BloomBits[] | undefined;
|
|
24
|
-
eventSelector: BloomBits;
|
|
25
|
-
} | undefined;
|
|
26
20
|
};
|
|
27
21
|
export declare const getBloomBits: (input: Hex) => BloomBits;
|
|
28
22
|
export declare const isBloomBitsInBloom: (bloom: Uint8Array, bits: BloomBits) => boolean;
|
|
@@ -35,15 +29,34 @@ export declare const getLogFilterBloom: (filter: LogFilter) => LogFilterBloom;
|
|
|
35
29
|
/**
|
|
36
30
|
* Return true if `filter` is in `bloom`.
|
|
37
31
|
*
|
|
38
|
-
* A filter with an address of type `LogFactory`
|
|
39
|
-
*
|
|
40
|
-
* filter is matched (log on child contract).
|
|
32
|
+
* A filter with an address of type `LogFactory` matches all addresses.
|
|
33
|
+
* Use `isFactoryInBloom` to check for new child contracts.
|
|
41
34
|
*
|
|
42
35
|
* Note: False positives are possible.
|
|
43
36
|
*/
|
|
44
|
-
export declare function isFilterInBloom({
|
|
45
|
-
blockNumber: number;
|
|
37
|
+
export declare function isFilterInBloom({ bloom, filter, }: {
|
|
46
38
|
bloom: Uint8Array;
|
|
47
39
|
filter: LogFilterBloom;
|
|
48
40
|
}): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* A `Factory` with its address and event selector converted to `BloomBits`.
|
|
43
|
+
*/
|
|
44
|
+
export type FactoryBloom = {
|
|
45
|
+
address: BloomBits[] | undefined;
|
|
46
|
+
eventSelector: BloomBits;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Convert `factory` to a `FactoryBloom`. Call once for each factory,
|
|
50
|
+
* so that the inputs are not hashed again for each block.
|
|
51
|
+
*/
|
|
52
|
+
export declare const getFactoryBloom: (factory: Factory) => FactoryBloom;
|
|
53
|
+
/**
|
|
54
|
+
* Return true if a log that matches `factory` can be in `bloom`.
|
|
55
|
+
*
|
|
56
|
+
* Note: False positives are possible.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isFactoryInBloom({ bloom, factory, }: {
|
|
59
|
+
bloom: Uint8Array;
|
|
60
|
+
factory: FactoryBloom;
|
|
61
|
+
}): boolean;
|
|
49
62
|
//# sourceMappingURL=bloom.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bloom.d.ts","sourceRoot":"","sources":["../../../src/sync-realtime/bloom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAa,MAAM,MAAM,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"bloom.d.ts","sourceRoot":"","sources":["../../../src/sync-realtime/bloom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAa,MAAM,MAAM,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAG9D,eAAO,MAAM,aAAa,ugBAC4e,CAAC;AAIvgB,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,CAAC,SAAS,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;CACrC,CAAC;AAEF,eAAO,MAAM,YAAY,UAAW,GAAG,KAAG,SAmBzC,CAAC;AAEF,eAAO,MAAM,kBAAkB,UACtB,UAAU,QACX,SAAS,KACd,OAGsC,CAAC;AAE1C,eAAO,MAAM,SAAS,UAAW,UAAU,SAAS,GAAG,KAAG,OACV,CAAC;AAgBjD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,WAAY,SAAS,KAAG,cAiBrD,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,MAAM,GACP,EAAE;IACD,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;CACxB,GAAG,OAAO,CAKV;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,SAAS,CAAC;CAC1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,YAAa,OAAO,KAAG,YAGjD,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,YAAY,CAAC;CACvB,GAAG,OAAO,CAKV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sync-realtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAOb,MAAM,MAAM,CAAC;AACd,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,KAAK,EAEV,KAAK,EACL,aAAa,EAEb,iBAAiB,EACjB,SAAS,EAET,UAAU,EAEV,SAAS,EACT,eAAe,EACf,OAAO,EACP,SAAS,EACT,eAAe,EACf,sBAAsB,EAIvB,MAAM,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sync-realtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAOb,MAAM,MAAM,CAAC;AACd,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,KAAK,EAEV,KAAK,EACL,aAAa,EAEb,iBAAiB,EACjB,SAAS,EAET,UAAU,EAEV,SAAS,EACT,eAAe,EACf,OAAO,EACP,SAAS,EACT,eAAe,EACf,sBAAsB,EAIvB,MAAM,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAmB1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAcvD,MAAM,MAAM,YAAY,GAAG;IACzB;;;;OAIG;IACH,IAAI,CACF,KAAK,EAAE,SAAS,GAAG,eAAe,EAClC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,GAC5C,cAAc,CAAC,iBAAiB,CAAC,CAAC;IACrC,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC5B,0DAA0D;IAC1D,iBAAiB,EAAE,UAAU,EAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,SAAS,GAAG,eAAe,CAAC;IACnC,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,mBAAmB,EAAE,sBAAsB,EAAE,CAAC;IAC9C,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,wEAAwE;IACxE,cAAc,EAAE,GAAG,CAAC,iBAAiB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,iBAAiB,GACzB,CAAC;IACC,IAAI,EAAE,OAAO,CAAC;IACd,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;CAC/C,GAAG,kBAAkB,CAAC,GACvB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,aAAa,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAEtE,KAAK,4BAA4B,GAAG;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,GAAG,CAAC;IACT,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IAC9C,cAAc,EAAE,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD,CAAC;AAKF,eAAO,MAAM,kBAAkB,SACvB,4BAA4B,KACjC,YA8oCF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ponder",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"description": "An open-source TypeScript framework for EVM data indexing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@pgsql/types": "16.0.0",
|
|
84
|
-
"@ponder/client": "0.18.
|
|
84
|
+
"@ponder/client": "0.18.2",
|
|
85
85
|
"@types/babel__code-frame": "^7.0.6",
|
|
86
86
|
"@types/glob": "^8.1.0",
|
|
87
87
|
"@types/node": "^22.0.0",
|
package/src/rpc/actions.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { zeroLogsBloom } from "@/sync-realtime/bloom.js";
|
|
|
30
30
|
import { chunk } from "@/utils/chunk.js";
|
|
31
31
|
import { PG_BIGINT_MAX, PG_INTEGER_MAX } from "@/utils/pg.js";
|
|
32
32
|
|
|
33
|
-
const ADDRESS_CHUNK_SIZE = 50;
|
|
33
|
+
export const ADDRESS_CHUNK_SIZE = 50;
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Helper function for "eth_getBlockByNumber" request.
|
|
@@ -503,8 +503,6 @@ export const validateTransactionsAndBlock = (
|
|
|
503
503
|
* Validate that the logs are consistent with the block.
|
|
504
504
|
*
|
|
505
505
|
* @dev Allows `log.transactionHash` to be `zeroHash`.
|
|
506
|
-
* @dev Allows `block.logsBloom` to be `zeroLogsBloom`.
|
|
507
|
-
* @dev Skips bloom validation on async-execution chains.
|
|
508
506
|
*/
|
|
509
507
|
export const validateLogsAndBlock = (
|
|
510
508
|
logs: SyncLog[],
|
|
@@ -514,25 +512,7 @@ export const validateLogsAndBlock = (
|
|
|
514
512
|
RequestParameters,
|
|
515
513
|
{ method: "eth_getBlockByNumber" | "eth_getBlockByHash" }
|
|
516
514
|
>,
|
|
517
|
-
isAsyncExecutionChain: boolean,
|
|
518
515
|
) => {
|
|
519
|
-
if (
|
|
520
|
-
isAsyncExecutionChain === false &&
|
|
521
|
-
block.logsBloom !== zeroLogsBloom &&
|
|
522
|
-
logs.length === 0
|
|
523
|
-
) {
|
|
524
|
-
const error = new RpcProviderError(
|
|
525
|
-
`Inconsistent RPC response data. The logs array has length 0, but the associated block has a non-empty 'block.logsBloom'.`,
|
|
526
|
-
);
|
|
527
|
-
error.meta = [
|
|
528
|
-
"Please report this error to the RPC operator.",
|
|
529
|
-
requestText(blockRequest),
|
|
530
|
-
requestText(logsRequest),
|
|
531
|
-
];
|
|
532
|
-
error.stack = undefined;
|
|
533
|
-
throw error;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
516
|
const transactionByIndex = new Map<Hex, SyncTransaction>(
|
|
537
517
|
block.transactions.map((transaction) => [
|
|
538
518
|
transaction.transactionIndex,
|
package/src/runtime/filter.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
TransactionFilter,
|
|
24
24
|
TransferFilter,
|
|
25
25
|
} from "@/internal/types.js";
|
|
26
|
+
import { ADDRESS_CHUNK_SIZE } from "@/rpc/actions.js";
|
|
26
27
|
import { type RequestParameters, sanitizeLogTopics } from "@/rpc/index.js";
|
|
27
28
|
import type { ChildAddresses, IntervalWithFilter } from "@/runtime/index.js";
|
|
28
29
|
import type {
|
|
@@ -222,6 +223,101 @@ export const mergeLogFiltersToRequests = (
|
|
|
222
223
|
return requests;
|
|
223
224
|
};
|
|
224
225
|
|
|
226
|
+
/** `address` and `topics` for an "eth_getLogs" request for one block. */
|
|
227
|
+
export type BlockLogsRequestFilter = {
|
|
228
|
+
address: Address[] | undefined;
|
|
229
|
+
topics: (Hex[] | null)[] | undefined;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const toLogFilterValues = (
|
|
233
|
+
value: Hex | Hex[] | null | undefined,
|
|
234
|
+
): Hex[] | undefined => {
|
|
235
|
+
if (value === null || value === undefined) return undefined;
|
|
236
|
+
return Array.isArray(value) ? value : [value];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Convert `values` to a request value. A list with more than
|
|
241
|
+
* `ADDRESS_CHUNK_SIZE` values matches all values, so that `eth_getLogs`
|
|
242
|
+
* sends exactly one request.
|
|
243
|
+
*/
|
|
244
|
+
const fromLogFilterValues = (values: Hex[] | undefined): Hex[] | null => {
|
|
245
|
+
if (values === undefined || values.length > ADDRESS_CHUNK_SIZE) return null;
|
|
246
|
+
return values;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Merge `LogFilter`s and `Factory`s into the `address` and `topics` of one
|
|
251
|
+
* "eth_getLogs" request for one block. The request matches a superset of the
|
|
252
|
+
* logs that `logFilters` and `factories` require. Returns `undefined` if no
|
|
253
|
+
* logs are required.
|
|
254
|
+
*
|
|
255
|
+
* @dev A log filter with a factory address matches all addresses, because child
|
|
256
|
+
* addresses created in the same block are not known before the request.
|
|
257
|
+
*
|
|
258
|
+
* @dev Each dimension is the union of all inputs, so the result can match logs
|
|
259
|
+
* that no single input matches. Logs must be filtered again after the request.
|
|
260
|
+
*
|
|
261
|
+
* @dev A dimension with more than `ADDRESS_CHUNK_SIZE` values matches all values.
|
|
262
|
+
*/
|
|
263
|
+
export const mergeLogFiltersToBlockRequest = ({
|
|
264
|
+
logFilters,
|
|
265
|
+
factories,
|
|
266
|
+
}: {
|
|
267
|
+
logFilters: LogFilter[];
|
|
268
|
+
factories: Factory[];
|
|
269
|
+
}): BlockLogsRequestFilter | undefined => {
|
|
270
|
+
let address: Hex[] | undefined = [];
|
|
271
|
+
let topics: (Hex[] | undefined)[] = [[], [], [], []];
|
|
272
|
+
let isEmpty = true;
|
|
273
|
+
|
|
274
|
+
const add = (
|
|
275
|
+
inputAddress: Hex[] | undefined,
|
|
276
|
+
inputTopics: (Hex[] | undefined)[],
|
|
277
|
+
) => {
|
|
278
|
+
isEmpty = false;
|
|
279
|
+
address = mergeLogFilterValue(address, inputAddress);
|
|
280
|
+
topics = topics.map((values, i) =>
|
|
281
|
+
mergeLogFilterValue(values, inputTopics[i]),
|
|
282
|
+
);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
for (const filter of logFilters) {
|
|
286
|
+
if (Array.isArray(filter.address) && filter.address.length === 0) continue;
|
|
287
|
+
|
|
288
|
+
add(
|
|
289
|
+
isAddressFactory(filter.address)
|
|
290
|
+
? undefined
|
|
291
|
+
: toLogFilterValues(filter.address),
|
|
292
|
+
[filter.topic0, filter.topic1, filter.topic2, filter.topic3].map(
|
|
293
|
+
toLogFilterValues,
|
|
294
|
+
),
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
for (const factory of factories) {
|
|
299
|
+
if (Array.isArray(factory.address) && factory.address.length === 0) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
add(toLogFilterValues(factory.address), [
|
|
304
|
+
[factory.eventSelector],
|
|
305
|
+
undefined,
|
|
306
|
+
undefined,
|
|
307
|
+
undefined,
|
|
308
|
+
]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (isEmpty) return undefined;
|
|
312
|
+
|
|
313
|
+
const requestTopics = sanitizeLogTopics(topics.map(fromLogFilterValues));
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
address: fromLogFilterValues(address) ?? undefined,
|
|
317
|
+
topics: requestTopics.length === 0 ? undefined : requestTopics,
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
|
|
225
321
|
export const getChildAddress = ({
|
|
226
322
|
log,
|
|
227
323
|
factory,
|
|
@@ -337,13 +433,7 @@ export const isLogFilterMatched = ({
|
|
|
337
433
|
filter: LogFilter;
|
|
338
434
|
log: InternalLog | SyncLog;
|
|
339
435
|
}): boolean => {
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
Number(log.blockNumber) < (filter.fromBlock ?? 0) ||
|
|
343
|
-
Number(log.blockNumber) > (filter.toBlock ?? Number.POSITIVE_INFINITY)
|
|
344
|
-
) {
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
436
|
+
if (isBlockInFilter(filter, Number(log.blockNumber)) === false) return false;
|
|
347
437
|
|
|
348
438
|
if (isValueMatched(filter.topic0, log.topics[0]) === false) return false;
|
|
349
439
|
if (isValueMatched(filter.topic1, log.topics[1]) === false) return false;
|
|
@@ -373,12 +463,7 @@ export const isTransactionFilterMatched = ({
|
|
|
373
463
|
filter: TransactionFilter;
|
|
374
464
|
transaction: InternalTransaction | SyncTransaction;
|
|
375
465
|
}): boolean => {
|
|
376
|
-
|
|
377
|
-
if (
|
|
378
|
-
Number(transaction.blockNumber) < (filter.fromBlock ?? 0) ||
|
|
379
|
-
Number(transaction.blockNumber) >
|
|
380
|
-
(filter.toBlock ?? Number.POSITIVE_INFINITY)
|
|
381
|
-
) {
|
|
466
|
+
if (isBlockInFilter(filter, Number(transaction.blockNumber)) === false) {
|
|
382
467
|
return false;
|
|
383
468
|
}
|
|
384
469
|
|
|
@@ -417,13 +502,7 @@ export const isTraceFilterMatched = ({
|
|
|
417
502
|
trace: InternalTrace | SyncTrace["trace"];
|
|
418
503
|
block: Pick<InternalBlock | SyncBlock, "number">;
|
|
419
504
|
}): boolean => {
|
|
420
|
-
|
|
421
|
-
if (
|
|
422
|
-
Number(block.number) < (filter.fromBlock ?? 0) ||
|
|
423
|
-
Number(block.number) > (filter.toBlock ?? Number.POSITIVE_INFINITY)
|
|
424
|
-
) {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
505
|
+
if (isBlockInFilter(filter, Number(block.number)) === false) return false;
|
|
427
506
|
|
|
428
507
|
if (
|
|
429
508
|
isAddressFactory(filter.fromAddress) === false &&
|
|
@@ -470,13 +549,7 @@ export const isTransferFilterMatched = ({
|
|
|
470
549
|
trace: InternalTrace | SyncTrace["trace"];
|
|
471
550
|
block: Pick<InternalBlock | SyncBlock, "number">;
|
|
472
551
|
}): boolean => {
|
|
473
|
-
|
|
474
|
-
if (
|
|
475
|
-
Number(block.number) < (filter.fromBlock ?? 0) ||
|
|
476
|
-
Number(block.number) > (filter.toBlock ?? Number.POSITIVE_INFINITY)
|
|
477
|
-
) {
|
|
478
|
-
return false;
|
|
479
|
-
}
|
|
552
|
+
if (isBlockInFilter(filter, Number(block.number)) === false) return false;
|
|
480
553
|
|
|
481
554
|
if (
|
|
482
555
|
trace.value === undefined ||
|
|
@@ -525,13 +598,7 @@ export const isBlockFilterMatched = ({
|
|
|
525
598
|
filter: BlockFilter;
|
|
526
599
|
block: Pick<InternalBlock | SyncBlock | SyncBlockHeader, "number">;
|
|
527
600
|
}): boolean => {
|
|
528
|
-
|
|
529
|
-
if (
|
|
530
|
-
Number(block.number) < (filter.fromBlock ?? 0) ||
|
|
531
|
-
Number(block.number) > (filter.toBlock ?? Number.POSITIVE_INFINITY)
|
|
532
|
-
) {
|
|
533
|
-
return false;
|
|
534
|
-
}
|
|
601
|
+
if (isBlockInFilter(filter, Number(block.number)) === false) return false;
|
|
535
602
|
|
|
536
603
|
return (Number(block.number) - filter.offset) % filter.interval === 0;
|
|
537
604
|
};
|
|
@@ -608,7 +675,10 @@ export const getFilterToBlock = (filter: Filter): number => {
|
|
|
608
675
|
return Math.max(...blocks);
|
|
609
676
|
};
|
|
610
677
|
|
|
611
|
-
export const isBlockInFilter = (
|
|
678
|
+
export const isBlockInFilter = (
|
|
679
|
+
filter: Filter | Factory,
|
|
680
|
+
blockNumber: number,
|
|
681
|
+
) => {
|
|
612
682
|
// Return `false` for out of range blocks
|
|
613
683
|
if (
|
|
614
684
|
blockNumber < (filter.fromBlock ?? 0) ||
|
|
@@ -53,7 +53,6 @@ import type {
|
|
|
53
53
|
IntervalWithFilter,
|
|
54
54
|
} from "@/runtime/index.js";
|
|
55
55
|
import type { SyncStore } from "@/sync-store/index.js";
|
|
56
|
-
import { isAsyncExecutionChain } from "@/utils/finality.js";
|
|
57
56
|
import { drainAsyncGenerator } from "@/utils/generators.js";
|
|
58
57
|
import { type Interval, intervalBounds } from "@/utils/interval.js";
|
|
59
58
|
import { startClock } from "@/utils/timer.js";
|
|
@@ -487,7 +486,6 @@ export function createInMemoryHistoricalSync(params: {
|
|
|
487
486
|
method: "eth_getBlockByNumber",
|
|
488
487
|
params: [toHex(blockNumber), true],
|
|
489
488
|
},
|
|
490
|
-
isAsyncExecutionChain(params.chain.id),
|
|
491
489
|
);
|
|
492
490
|
}
|
|
493
491
|
}
|
|
@@ -56,7 +56,6 @@ import type {
|
|
|
56
56
|
} from "@/runtime/index.js";
|
|
57
57
|
import type { SyncStore } from "@/sync-store/index.js";
|
|
58
58
|
import { dedupe } from "@/utils/dedupe.js";
|
|
59
|
-
import { isAsyncExecutionChain } from "@/utils/finality.js";
|
|
60
59
|
import {
|
|
61
60
|
getChunks,
|
|
62
61
|
type Interval,
|
|
@@ -557,7 +556,6 @@ export const createHistoricalSync = (
|
|
|
557
556
|
method: "eth_getBlockByNumber",
|
|
558
557
|
params: [toHex(blockNumber), true],
|
|
559
558
|
},
|
|
560
|
-
isAsyncExecutionChain(args.chain.id),
|
|
561
559
|
);
|
|
562
560
|
}
|
|
563
561
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { type Hex, keccak256 } from "viem";
|
|
2
|
-
import type { LogFilter } from "@/internal/types.js";
|
|
3
|
-
import {
|
|
4
|
-
getFilterFromBlock,
|
|
5
|
-
getFilterToBlock,
|
|
6
|
-
isAddressFactory,
|
|
7
|
-
} from "@/runtime/filter.js";
|
|
2
|
+
import type { Factory, LogFilter } from "@/internal/types.js";
|
|
3
|
+
import { isAddressFactory } from "@/runtime/filter.js";
|
|
8
4
|
|
|
9
5
|
export const zeroLogsBloom =
|
|
10
6
|
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
|
|
@@ -26,13 +22,8 @@ export type BloomBits = {
|
|
|
26
22
|
* `undefined` matches every bloom. An array matches if any item is in the bloom.
|
|
27
23
|
*/
|
|
28
24
|
export type LogFilterBloom = {
|
|
29
|
-
fromBlock: number;
|
|
30
|
-
toBlock: number;
|
|
31
25
|
address: BloomBits[] | undefined;
|
|
32
26
|
topics: (BloomBits[] | undefined)[];
|
|
33
|
-
factory:
|
|
34
|
-
| { address: BloomBits[] | undefined; eventSelector: BloomBits }
|
|
35
|
-
| undefined;
|
|
36
27
|
};
|
|
37
28
|
|
|
38
29
|
export const getBloomBits = (input: Hex): BloomBits => {
|
|
@@ -87,14 +78,9 @@ const toBloomBits = (
|
|
|
87
78
|
*/
|
|
88
79
|
export const getLogFilterBloom = (filter: LogFilter): LogFilterBloom => {
|
|
89
80
|
let address: BloomBits[] | undefined;
|
|
90
|
-
let factory: LogFilterBloom["factory"];
|
|
91
81
|
|
|
92
82
|
if (isAddressFactory(filter.address)) {
|
|
93
83
|
address = undefined;
|
|
94
|
-
factory = {
|
|
95
|
-
address: toBloomBits(filter.address.address),
|
|
96
|
-
eventSelector: getBloomBits(filter.address.eventSelector),
|
|
97
|
-
};
|
|
98
84
|
} else if (Array.isArray(filter.address) && filter.address.length === 0) {
|
|
99
85
|
address = undefined;
|
|
100
86
|
} else {
|
|
@@ -102,50 +88,65 @@ export const getLogFilterBloom = (filter: LogFilter): LogFilterBloom => {
|
|
|
102
88
|
}
|
|
103
89
|
|
|
104
90
|
return {
|
|
105
|
-
fromBlock: getFilterFromBlock(filter),
|
|
106
|
-
toBlock: getFilterToBlock(filter),
|
|
107
91
|
address,
|
|
108
92
|
topics: [filter.topic0, filter.topic1, filter.topic2, filter.topic3].map(
|
|
109
93
|
toBloomBits,
|
|
110
94
|
),
|
|
111
|
-
factory,
|
|
112
95
|
};
|
|
113
96
|
};
|
|
114
97
|
|
|
115
98
|
/**
|
|
116
99
|
* Return true if `filter` is in `bloom`.
|
|
117
100
|
*
|
|
118
|
-
* A filter with an address of type `LogFactory`
|
|
119
|
-
*
|
|
120
|
-
* filter is matched (log on child contract).
|
|
101
|
+
* A filter with an address of type `LogFactory` matches all addresses.
|
|
102
|
+
* Use `isFactoryInBloom` to check for new child contracts.
|
|
121
103
|
*
|
|
122
104
|
* Note: False positives are possible.
|
|
123
105
|
*/
|
|
124
106
|
export function isFilterInBloom({
|
|
125
|
-
blockNumber,
|
|
126
107
|
bloom,
|
|
127
108
|
filter,
|
|
128
109
|
}: {
|
|
129
|
-
blockNumber: number;
|
|
130
110
|
bloom: Uint8Array;
|
|
131
111
|
filter: LogFilterBloom;
|
|
132
112
|
}): boolean {
|
|
133
|
-
// Return `false` for out of range blocks
|
|
134
|
-
if (blockNumber < filter.fromBlock || blockNumber > filter.toBlock) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Return true if the `Factory` is matched.
|
|
139
|
-
if (
|
|
140
|
-
filter.factory !== undefined &&
|
|
141
|
-
isAnyInBloom(bloom, filter.factory.address) &&
|
|
142
|
-
isBloomBitsInBloom(bloom, filter.factory.eventSelector)
|
|
143
|
-
) {
|
|
144
|
-
return true;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
113
|
return (
|
|
148
114
|
isAnyInBloom(bloom, filter.address) &&
|
|
149
115
|
filter.topics.every((topic) => isAnyInBloom(bloom, topic))
|
|
150
116
|
);
|
|
151
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A `Factory` with its address and event selector converted to `BloomBits`.
|
|
121
|
+
*/
|
|
122
|
+
export type FactoryBloom = {
|
|
123
|
+
address: BloomBits[] | undefined;
|
|
124
|
+
eventSelector: BloomBits;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Convert `factory` to a `FactoryBloom`. Call once for each factory,
|
|
129
|
+
* so that the inputs are not hashed again for each block.
|
|
130
|
+
*/
|
|
131
|
+
export const getFactoryBloom = (factory: Factory): FactoryBloom => ({
|
|
132
|
+
address: toBloomBits(factory.address),
|
|
133
|
+
eventSelector: getBloomBits(factory.eventSelector),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Return true if a log that matches `factory` can be in `bloom`.
|
|
138
|
+
*
|
|
139
|
+
* Note: False positives are possible.
|
|
140
|
+
*/
|
|
141
|
+
export function isFactoryInBloom({
|
|
142
|
+
bloom,
|
|
143
|
+
factory,
|
|
144
|
+
}: {
|
|
145
|
+
bloom: Uint8Array;
|
|
146
|
+
factory: FactoryBloom;
|
|
147
|
+
}): boolean {
|
|
148
|
+
return (
|
|
149
|
+
isAnyInBloom(bloom, factory.address) &&
|
|
150
|
+
isBloomBitsInBloom(bloom, factory.eventSelector)
|
|
151
|
+
);
|
|
152
|
+
}
|
|
@@ -47,11 +47,13 @@ import {
|
|
|
47
47
|
isAddressFactory,
|
|
48
48
|
isAddressMatched,
|
|
49
49
|
isBlockFilterMatched,
|
|
50
|
+
isBlockInFilter,
|
|
50
51
|
isLogFactoryMatched,
|
|
51
52
|
isLogFilterMatched,
|
|
52
53
|
isTraceFilterMatched,
|
|
53
54
|
isTransactionFilterMatched,
|
|
54
55
|
isTransferFilterMatched,
|
|
56
|
+
mergeLogFiltersToBlockRequest,
|
|
55
57
|
} from "@/runtime/filter.js";
|
|
56
58
|
import {
|
|
57
59
|
getFactoryFragmentId,
|
|
@@ -63,7 +65,9 @@ import { createLock } from "@/utils/mutex.js";
|
|
|
63
65
|
import { range } from "@/utils/range.js";
|
|
64
66
|
import { startClock } from "@/utils/timer.js";
|
|
65
67
|
import {
|
|
68
|
+
getFactoryBloom,
|
|
66
69
|
getLogFilterBloom,
|
|
70
|
+
isFactoryInBloom,
|
|
67
71
|
isFilterInBloom,
|
|
68
72
|
isInBloom,
|
|
69
73
|
zeroLogsBloom,
|
|
@@ -184,6 +188,7 @@ export const createRealtimeSync = (
|
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
const logFilterBlooms = logFilters.map(getLogFilterBloom);
|
|
191
|
+
const factoryBlooms = factories.map(getFactoryBloom);
|
|
187
192
|
|
|
188
193
|
const getLatestUnfinalizedBlock = () => {
|
|
189
194
|
if (unfinalizedBlocks.length === 0) {
|
|
@@ -220,39 +225,45 @@ export const createRealtimeSync = (
|
|
|
220
225
|
// Logs
|
|
221
226
|
////////
|
|
222
227
|
|
|
223
|
-
// "eth_getLogs" calls can be skipped if no filters match `newHeadBlock.logsBloom`.
|
|
224
228
|
// Async-execution chains cannot reliably use blooms to filter or validate logs.
|
|
229
|
+
// A zero `logsBloom` is treated as missing.
|
|
225
230
|
const isAsyncExecution = isAsyncExecutionChain(args.chain.id);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
-
|
|
231
|
+
const blockNumber = hexToNumber(maybeBlockHeader.number);
|
|
232
|
+
const bloom = hexToBytes(maybeBlockHeader.logsBloom);
|
|
233
|
+
|
|
234
|
+
const logsRequestFilter = mergeLogFiltersToBlockRequest({
|
|
235
|
+
logFilters: logFilters.filter(
|
|
236
|
+
(logFilter, i) =>
|
|
237
|
+
isBlockInFilter(logFilter, blockNumber) &&
|
|
238
|
+
(isAsyncExecution ||
|
|
239
|
+
maybeBlockHeader.logsBloom === zeroLogsBloom ||
|
|
240
|
+
isFilterInBloom({ bloom, filter: logFilterBlooms[i]! })),
|
|
241
|
+
),
|
|
242
|
+
factories: factories.filter(
|
|
243
|
+
(factory, i) =>
|
|
244
|
+
isBlockInFilter(factory, blockNumber) &&
|
|
245
|
+
(isAsyncExecution ||
|
|
246
|
+
maybeBlockHeader.logsBloom === zeroLogsBloom ||
|
|
247
|
+
isFactoryInBloom({ bloom, factory: factoryBlooms[i]! })),
|
|
248
|
+
),
|
|
249
|
+
});
|
|
250
|
+
const shouldRequestLogs = logsRequestFilter !== undefined;
|
|
238
251
|
|
|
239
252
|
let logs: SyncLog[] = [];
|
|
240
253
|
if (shouldRequestLogs) {
|
|
254
|
+
const logsRequestParams = {
|
|
255
|
+
blockHash: maybeBlockHeader.hash,
|
|
256
|
+
address: logsRequestFilter.address,
|
|
257
|
+
topics: logsRequestFilter.topics,
|
|
258
|
+
};
|
|
259
|
+
|
|
241
260
|
if (block === undefined) {
|
|
242
261
|
[block, logs] = await Promise.all([
|
|
243
262
|
eth_getBlockByHash(args.rpc, [maybeBlockHeader.hash, true], context),
|
|
244
|
-
eth_getLogs(
|
|
245
|
-
args.rpc,
|
|
246
|
-
[{ blockHash: maybeBlockHeader.hash }],
|
|
247
|
-
context,
|
|
248
|
-
),
|
|
263
|
+
eth_getLogs(args.rpc, [logsRequestParams], context),
|
|
249
264
|
]);
|
|
250
265
|
} else {
|
|
251
|
-
logs = await eth_getLogs(
|
|
252
|
-
args.rpc,
|
|
253
|
-
[{ blockHash: block.hash }],
|
|
254
|
-
context,
|
|
255
|
-
);
|
|
266
|
+
logs = await eth_getLogs(args.rpc, [logsRequestParams], context);
|
|
256
267
|
}
|
|
257
268
|
|
|
258
269
|
validateLogsAndBlock(
|
|
@@ -260,7 +271,7 @@ export const createRealtimeSync = (
|
|
|
260
271
|
block,
|
|
261
272
|
{
|
|
262
273
|
method: "eth_getLogs",
|
|
263
|
-
params: [
|
|
274
|
+
params: [logsRequestParams],
|
|
264
275
|
},
|
|
265
276
|
ethGetBlockMethod === "eth_getBlockByNumber"
|
|
266
277
|
? {
|
|
@@ -271,7 +282,6 @@ export const createRealtimeSync = (
|
|
|
271
282
|
method: "eth_getBlockByHash",
|
|
272
283
|
params: [block.hash, true],
|
|
273
284
|
},
|
|
274
|
-
isAsyncExecutionChain(args.chain.id),
|
|
275
285
|
);
|
|
276
286
|
|
|
277
287
|
for (const log of logs) {
|
|
@@ -289,7 +299,10 @@ export const createRealtimeSync = (
|
|
|
289
299
|
}
|
|
290
300
|
}
|
|
291
301
|
|
|
292
|
-
if (
|
|
302
|
+
if (
|
|
303
|
+
shouldRequestLogs === false &&
|
|
304
|
+
(logFilters.length > 0 || factories.length > 0)
|
|
305
|
+
) {
|
|
293
306
|
args.common.logger.trace({
|
|
294
307
|
msg: "Skipped eth_getLogs request due to bloom filter result",
|
|
295
308
|
action: "fetch_block_data",
|