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.
@@ -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` is matched
39
- * if the address filter is matched (new child contract) or the log
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({ blockNumber, bloom, filter, }: {
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;AAOrD,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,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,CAAC,SAAS,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;IACpC,OAAO,EACH;QAAE,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC;QAAC,aAAa,EAAE,SAAS,CAAA;KAAE,GAC9D,SAAS,CAAC;CACf,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,cAyBrD,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,KAAK,EACL,MAAM,GACP,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;CACxB,GAAG,OAAO,CAmBV"}
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;AAiB1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAYvD,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,YAqoCF,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.1",
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.1",
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",
@@ -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,
@@ -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
- // Return `false` for out of range blocks
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
- // Return `false` for out of range blocks
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
- // Return `false` for out of range blocks
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
- // Return `false` for out of range blocks
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
- // Return `false` for out of range blocks
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 = (filter: Filter, blockNumber: number) => {
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` is matched
119
- * if the address filter is matched (new child contract) or the log
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
- let shouldRequestLogs: boolean;
227
- if (isAsyncExecution) {
228
- shouldRequestLogs = logFilters.length > 0;
229
- } else if (maybeBlockHeader.logsBloom === zeroLogsBloom) {
230
- shouldRequestLogs = true;
231
- } else {
232
- const blockNumber = hexToNumber(maybeBlockHeader.number);
233
- const bloom = hexToBytes(maybeBlockHeader.logsBloom);
234
- shouldRequestLogs = logFilterBlooms.some((filter) =>
235
- isFilterInBloom({ blockNumber, bloom, filter }),
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: [{ blockHash: block.hash }],
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 (shouldRequestLogs === false && logFilters.length > 0) {
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",