ponder 0.14.13 → 0.15.0

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.
Files changed (237) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/esm/bin/commands/createViews.js +28 -11
  3. package/dist/esm/bin/commands/createViews.js.map +1 -1
  4. package/dist/esm/bin/commands/dev.js +42 -22
  5. package/dist/esm/bin/commands/dev.js.map +1 -1
  6. package/dist/esm/bin/commands/prune.js +3 -0
  7. package/dist/esm/bin/commands/prune.js.map +1 -1
  8. package/dist/esm/bin/commands/serve.js +4 -1
  9. package/dist/esm/bin/commands/serve.js.map +1 -1
  10. package/dist/esm/bin/commands/start.js +18 -6
  11. package/dist/esm/bin/commands/start.js.map +1 -1
  12. package/dist/esm/bin/isolatedController.js +200 -0
  13. package/dist/esm/bin/isolatedController.js.map +1 -0
  14. package/dist/esm/bin/isolatedWorker.js +146 -0
  15. package/dist/esm/bin/isolatedWorker.js.map +1 -0
  16. package/dist/esm/build/config.js +322 -402
  17. package/dist/esm/build/config.js.map +1 -1
  18. package/dist/esm/build/index.js +8 -11
  19. package/dist/esm/build/index.js.map +1 -1
  20. package/dist/esm/build/pre.js +1 -4
  21. package/dist/esm/build/pre.js.map +1 -1
  22. package/dist/esm/build/schema.js +25 -3
  23. package/dist/esm/build/schema.js.map +1 -1
  24. package/dist/esm/client/index.js +306 -42
  25. package/dist/esm/client/index.js.map +1 -1
  26. package/dist/esm/database/actions.js +264 -104
  27. package/dist/esm/database/actions.js.map +1 -1
  28. package/dist/esm/database/index.js +39 -33
  29. package/dist/esm/database/index.js.map +1 -1
  30. package/dist/esm/database/queryBuilder.js +1 -0
  31. package/dist/esm/database/queryBuilder.js.map +1 -1
  32. package/dist/esm/drizzle/index.js +11 -7
  33. package/dist/esm/drizzle/index.js.map +1 -1
  34. package/dist/esm/drizzle/onchain.js +18 -0
  35. package/dist/esm/drizzle/onchain.js.map +1 -1
  36. package/dist/esm/indexing/client.js +32 -25
  37. package/dist/esm/indexing/client.js.map +1 -1
  38. package/dist/esm/indexing/index.js +110 -178
  39. package/dist/esm/indexing/index.js.map +1 -1
  40. package/dist/esm/indexing/profile.js +1 -1
  41. package/dist/esm/indexing/profile.js.map +1 -1
  42. package/dist/esm/indexing-store/cache.js +196 -274
  43. package/dist/esm/indexing-store/cache.js.map +1 -1
  44. package/dist/esm/indexing-store/historical.js +17 -13
  45. package/dist/esm/indexing-store/historical.js.map +1 -1
  46. package/dist/esm/indexing-store/index.js +10 -1
  47. package/dist/esm/indexing-store/index.js.map +1 -1
  48. package/dist/esm/indexing-store/profile.js +3 -3
  49. package/dist/esm/indexing-store/profile.js.map +1 -1
  50. package/dist/esm/indexing-store/realtime.js +27 -2
  51. package/dist/esm/indexing-store/realtime.js.map +1 -1
  52. package/dist/esm/internal/errors.js +28 -0
  53. package/dist/esm/internal/errors.js.map +1 -1
  54. package/dist/esm/internal/metrics.js +279 -82
  55. package/dist/esm/internal/metrics.js.map +1 -1
  56. package/dist/esm/internal/options.js +1 -0
  57. package/dist/esm/internal/options.js.map +1 -1
  58. package/dist/esm/internal/telemetry.js +1 -1
  59. package/dist/esm/internal/telemetry.js.map +1 -1
  60. package/dist/esm/rpc/http.js +130 -0
  61. package/dist/esm/rpc/http.js.map +1 -0
  62. package/dist/esm/rpc/index.js +38 -7
  63. package/dist/esm/rpc/index.js.map +1 -1
  64. package/dist/esm/runtime/events.js +179 -212
  65. package/dist/esm/runtime/events.js.map +1 -1
  66. package/dist/esm/runtime/filter.js +71 -0
  67. package/dist/esm/runtime/filter.js.map +1 -1
  68. package/dist/esm/runtime/fragments.js +78 -73
  69. package/dist/esm/runtime/fragments.js.map +1 -1
  70. package/dist/esm/runtime/historical.js +306 -130
  71. package/dist/esm/runtime/historical.js.map +1 -1
  72. package/dist/esm/runtime/index.js +183 -58
  73. package/dist/esm/runtime/index.js.map +1 -1
  74. package/dist/esm/runtime/isolated.js +462 -0
  75. package/dist/esm/runtime/isolated.js.map +1 -0
  76. package/dist/esm/runtime/multichain.js +80 -73
  77. package/dist/esm/runtime/multichain.js.map +1 -1
  78. package/dist/esm/runtime/omnichain.js +82 -75
  79. package/dist/esm/runtime/omnichain.js.map +1 -1
  80. package/dist/esm/runtime/realtime.js +198 -66
  81. package/dist/esm/runtime/realtime.js.map +1 -1
  82. package/dist/esm/sync-historical/index.js +416 -457
  83. package/dist/esm/sync-historical/index.js.map +1 -1
  84. package/dist/esm/sync-realtime/bloom.js +3 -3
  85. package/dist/esm/sync-realtime/bloom.js.map +1 -1
  86. package/dist/esm/sync-realtime/index.js +27 -46
  87. package/dist/esm/sync-realtime/index.js.map +1 -1
  88. package/dist/esm/sync-store/index.js +112 -63
  89. package/dist/esm/sync-store/index.js.map +1 -1
  90. package/dist/esm/utils/abi.js +20 -32
  91. package/dist/esm/utils/abi.js.map +1 -1
  92. package/dist/esm/utils/chunk.js +8 -0
  93. package/dist/esm/utils/chunk.js.map +1 -0
  94. package/dist/esm/utils/promiseAllSettledWithThrow.js +19 -0
  95. package/dist/esm/utils/promiseAllSettledWithThrow.js.map +1 -0
  96. package/dist/esm/{client/parse.js → utils/sql-parse.js} +94 -80
  97. package/dist/esm/utils/sql-parse.js.map +1 -0
  98. package/dist/types/bin/commands/createViews.d.ts.map +1 -1
  99. package/dist/types/bin/commands/dev.d.ts.map +1 -1
  100. package/dist/types/bin/commands/prune.d.ts.map +1 -1
  101. package/dist/types/bin/commands/serve.d.ts.map +1 -1
  102. package/dist/types/bin/commands/start.d.ts.map +1 -1
  103. package/dist/types/bin/isolatedController.d.ts +13 -0
  104. package/dist/types/bin/isolatedController.d.ts.map +1 -0
  105. package/dist/types/bin/isolatedWorker.d.ts +9 -0
  106. package/dist/types/bin/isolatedWorker.d.ts.map +1 -0
  107. package/dist/types/build/config.d.ts +29 -11
  108. package/dist/types/build/config.d.ts.map +1 -1
  109. package/dist/types/build/index.d.ts +3 -2
  110. package/dist/types/build/index.d.ts.map +1 -1
  111. package/dist/types/build/pre.d.ts +1 -1
  112. package/dist/types/build/pre.d.ts.map +1 -1
  113. package/dist/types/build/schema.d.ts +5 -3
  114. package/dist/types/build/schema.d.ts.map +1 -1
  115. package/dist/types/client/index.d.ts +1 -1
  116. package/dist/types/client/index.d.ts.map +1 -1
  117. package/dist/types/config/index.d.ts +3 -3
  118. package/dist/types/config/index.d.ts.map +1 -1
  119. package/dist/types/database/actions.d.ts +53 -7
  120. package/dist/types/database/actions.d.ts.map +1 -1
  121. package/dist/types/database/index.d.ts +21 -21
  122. package/dist/types/database/index.d.ts.map +1 -1
  123. package/dist/types/database/queryBuilder.d.ts.map +1 -1
  124. package/dist/types/drizzle/index.d.ts +4 -5
  125. package/dist/types/drizzle/index.d.ts.map +1 -1
  126. package/dist/types/drizzle/onchain.d.ts +6 -0
  127. package/dist/types/drizzle/onchain.d.ts.map +1 -1
  128. package/dist/types/indexing/client.d.ts.map +1 -1
  129. package/dist/types/indexing/index.d.ts +2 -5
  130. package/dist/types/indexing/index.d.ts.map +1 -1
  131. package/dist/types/indexing-store/cache.d.ts +3 -2
  132. package/dist/types/indexing-store/cache.d.ts.map +1 -1
  133. package/dist/types/indexing-store/historical.d.ts +2 -1
  134. package/dist/types/indexing-store/historical.d.ts.map +1 -1
  135. package/dist/types/indexing-store/index.d.ts +1 -0
  136. package/dist/types/indexing-store/index.d.ts.map +1 -1
  137. package/dist/types/indexing-store/realtime.d.ts +2 -1
  138. package/dist/types/indexing-store/realtime.d.ts.map +1 -1
  139. package/dist/types/internal/errors.d.ts +5 -0
  140. package/dist/types/internal/errors.d.ts.map +1 -1
  141. package/dist/types/internal/metrics.d.ts +21 -0
  142. package/dist/types/internal/metrics.d.ts.map +1 -1
  143. package/dist/types/internal/options.d.ts +2 -0
  144. package/dist/types/internal/options.d.ts.map +1 -1
  145. package/dist/types/internal/types.d.ts +66 -58
  146. package/dist/types/internal/types.d.ts.map +1 -1
  147. package/dist/types/rpc/http.d.ts +17 -0
  148. package/dist/types/rpc/http.d.ts.map +1 -0
  149. package/dist/types/rpc/index.d.ts.map +1 -1
  150. package/dist/types/runtime/events.d.ts +4 -4
  151. package/dist/types/runtime/events.d.ts.map +1 -1
  152. package/dist/types/runtime/filter.d.ts +5 -1
  153. package/dist/types/runtime/filter.d.ts.map +1 -1
  154. package/dist/types/runtime/fragments.d.ts +4 -3
  155. package/dist/types/runtime/fragments.d.ts.map +1 -1
  156. package/dist/types/runtime/historical.d.ts +29 -13
  157. package/dist/types/runtime/historical.d.ts.map +1 -1
  158. package/dist/types/runtime/index.d.ts +49 -6
  159. package/dist/types/runtime/index.d.ts.map +1 -1
  160. package/dist/types/runtime/init.d.ts +5 -5
  161. package/dist/types/runtime/init.d.ts.map +1 -1
  162. package/dist/types/runtime/isolated.d.ts +14 -0
  163. package/dist/types/runtime/isolated.d.ts.map +1 -0
  164. package/dist/types/runtime/multichain.d.ts.map +1 -1
  165. package/dist/types/runtime/omnichain.d.ts.map +1 -1
  166. package/dist/types/runtime/realtime.d.ts +21 -10
  167. package/dist/types/runtime/realtime.d.ts.map +1 -1
  168. package/dist/types/sync-historical/index.d.ts +18 -8
  169. package/dist/types/sync-historical/index.d.ts.map +1 -1
  170. package/dist/types/sync-realtime/bloom.d.ts.map +1 -1
  171. package/dist/types/sync-realtime/index.d.ts +2 -2
  172. package/dist/types/sync-realtime/index.d.ts.map +1 -1
  173. package/dist/types/sync-store/index.d.ts +9 -9
  174. package/dist/types/sync-store/index.d.ts.map +1 -1
  175. package/dist/types/utils/abi.d.ts +3 -34
  176. package/dist/types/utils/abi.d.ts.map +1 -1
  177. package/dist/types/utils/chunk.d.ts +2 -0
  178. package/dist/types/utils/chunk.d.ts.map +1 -0
  179. package/dist/types/utils/promiseAllSettledWithThrow.d.ts +8 -0
  180. package/dist/types/utils/promiseAllSettledWithThrow.d.ts.map +1 -0
  181. package/dist/types/utils/sql-parse.d.ts +21 -0
  182. package/dist/types/utils/sql-parse.d.ts.map +1 -0
  183. package/package.json +2 -2
  184. package/src/bin/commands/createViews.ts +35 -15
  185. package/src/bin/commands/dev.ts +43 -21
  186. package/src/bin/commands/prune.ts +6 -0
  187. package/src/bin/commands/serve.ts +4 -1
  188. package/src/bin/commands/start.ts +20 -5
  189. package/src/bin/isolatedController.ts +300 -0
  190. package/src/bin/isolatedWorker.ts +192 -0
  191. package/src/build/config.ts +570 -632
  192. package/src/build/index.ts +14 -14
  193. package/src/build/pre.ts +1 -4
  194. package/src/build/schema.ts +49 -4
  195. package/src/client/index.ts +386 -48
  196. package/src/config/index.ts +3 -3
  197. package/src/database/actions.ts +469 -120
  198. package/src/database/index.ts +85 -58
  199. package/src/database/queryBuilder.ts +1 -0
  200. package/src/drizzle/index.ts +15 -7
  201. package/src/drizzle/onchain.ts +19 -0
  202. package/src/indexing/client.ts +38 -25
  203. package/src/indexing/index.ts +137 -230
  204. package/src/indexing/profile.ts +1 -1
  205. package/src/indexing-store/cache.ts +285 -414
  206. package/src/indexing-store/historical.ts +20 -10
  207. package/src/indexing-store/index.ts +16 -0
  208. package/src/indexing-store/profile.ts +3 -3
  209. package/src/indexing-store/realtime.ts +28 -0
  210. package/src/internal/errors.ts +26 -0
  211. package/src/internal/metrics.ts +341 -111
  212. package/src/internal/options.ts +4 -0
  213. package/src/internal/telemetry.ts +1 -1
  214. package/src/internal/types.ts +70 -87
  215. package/src/rpc/http.ts +164 -0
  216. package/src/rpc/index.ts +39 -7
  217. package/src/runtime/events.ts +195 -240
  218. package/src/runtime/filter.ts +85 -1
  219. package/src/runtime/fragments.ts +109 -113
  220. package/src/runtime/historical.ts +467 -189
  221. package/src/runtime/index.ts +337 -69
  222. package/src/runtime/init.ts +5 -5
  223. package/src/runtime/isolated.ts +768 -0
  224. package/src/runtime/multichain.ts +137 -102
  225. package/src/runtime/omnichain.ts +138 -106
  226. package/src/runtime/realtime.ts +322 -123
  227. package/src/sync-historical/index.ts +556 -692
  228. package/src/sync-realtime/bloom.ts +7 -3
  229. package/src/sync-realtime/index.ts +31 -46
  230. package/src/sync-store/index.ts +189 -95
  231. package/src/utils/abi.ts +33 -90
  232. package/src/utils/chunk.ts +7 -0
  233. package/src/utils/promiseAllSettledWithThrow.ts +27 -0
  234. package/src/{client/parse.ts → utils/sql-parse.ts} +100 -90
  235. package/dist/esm/client/parse.js.map +0 -1
  236. package/dist/types/client/parse.d.ts +0 -14
  237. package/dist/types/client/parse.d.ts.map +0 -1
@@ -4,11 +4,7 @@ import type {
4
4
  Chain,
5
5
  Factory,
6
6
  FactoryId,
7
- FilterWithoutBlocks,
8
- Fragment,
9
- LogFactory,
10
7
  LogFilter,
11
- Source,
12
8
  SyncBlock,
13
9
  SyncLog,
14
10
  SyncTrace,
@@ -34,29 +30,36 @@ import {
34
30
  getChildAddress,
35
31
  isAddressFactory,
36
32
  isAddressMatched,
33
+ isBlockFilterMatched,
34
+ isBlockInFilter,
37
35
  isLogFactoryMatched,
36
+ isLogFilterMatched,
38
37
  isTraceFilterMatched,
39
38
  isTransactionFilterMatched,
40
39
  isTransferFilterMatched,
41
40
  } from "@/runtime/filter.js";
42
- import { recoverFilter } from "@/runtime/fragments.js";
43
- import type { CachedIntervals } from "@/runtime/index.js";
41
+ import type {
42
+ ChildAddresses,
43
+ IntervalWithFactory,
44
+ IntervalWithFilter,
45
+ } from "@/runtime/index.js";
44
46
  import type { SyncStore } from "@/sync-store/index.js";
45
47
  import {
46
48
  type Interval,
47
49
  getChunks,
48
50
  intervalBounds,
49
- intervalDifference,
50
51
  intervalRange,
51
- intervalUnion,
52
52
  } from "@/utils/interval.js";
53
+ import { promiseAllSettledWithThrow } from "@/utils/promiseAllSettledWithThrow.js";
54
+ import { createQueue } from "@/utils/queue.js";
53
55
  import { startClock } from "@/utils/timer.js";
54
56
  import { getLogsRetryHelper } from "@ponder/utils";
55
57
  import {
56
58
  type Address,
57
59
  type Hash,
60
+ type Hex,
61
+ type LogTopic,
58
62
  type RpcError,
59
- hexToBigInt,
60
63
  hexToNumber,
61
64
  toHex,
62
65
  zeroHash,
@@ -64,20 +67,30 @@ import {
64
67
 
65
68
  export type HistoricalSync = {
66
69
  /**
67
- * Extract raw data for `interval` and return the closest-to-tip block
68
- * that is synced.
70
+ * Sync block data that can be queried for a range of blocks (logs).
71
+ */
72
+ syncBlockRangeData(params: {
73
+ interval: Interval;
74
+ requiredIntervals: IntervalWithFilter[];
75
+ requiredFactoryIntervals: IntervalWithFactory[];
76
+ syncStore: SyncStore;
77
+ }): Promise<SyncLog[]>;
78
+ /**
79
+ * Sync block data that must be queried for a single block (block, transactions, receipts, traces).
69
80
  */
70
- sync(interval: Interval): Promise<SyncBlock | undefined>;
81
+ syncBlockData(params: {
82
+ interval: Interval;
83
+ requiredIntervals: IntervalWithFilter[];
84
+ logs: SyncLog[];
85
+ syncStore: SyncStore;
86
+ }): Promise<SyncBlock | undefined>;
71
87
  };
72
88
 
73
89
  type CreateHistoricalSyncParameters = {
74
90
  common: Common;
75
91
  chain: Chain;
76
92
  rpc: Rpc;
77
- sources: Source[];
78
93
  childAddresses: Map<FactoryId, Map<Address, number>>;
79
- cachedIntervals: CachedIntervals;
80
- syncStore: SyncStore;
81
94
  };
82
95
 
83
96
  export const createHistoricalSync = (
@@ -87,36 +100,6 @@ export const createHistoricalSync = (
87
100
  * Flag to fetch transaction receipts through _eth_getBlockReceipts (true) or _eth_getTransactionReceipt (false)
88
101
  */
89
102
  let isBlockReceipts = true;
90
- /**
91
- * Blocks that have already been extracted.
92
- * Note: All entries are deleted at the end of each call to `sync()`.
93
- */
94
- const blockCache = new Map<number, Promise<SyncBlock>>();
95
- /**
96
- * Traces that have already been fetched.
97
- * Note: All entries are deleted at the end of each call to `sync()`.
98
- */
99
- const traceCache = new Map<number, Promise<SyncTrace[]>>();
100
- /**
101
- * Transactions that should be saved to the sync-store.
102
- * Note: All entries are deleted at the end of each call to `sync()`.
103
- */
104
- const transactionsCache = new Set<Hash>();
105
- /**
106
- * Block transaction receipts that have already been fetched.
107
- * Note: All entries are deleted at the end of each call to `sync()`.
108
- */
109
- const blockReceiptsCache = new Map<Hash, Promise<SyncTransactionReceipt[]>>();
110
- /**
111
- * Transaction receipts that have already been fetched.
112
- * Note: All entries are deleted at the end of each call to `sync()`.
113
- */
114
- const transactionReceiptsCache = new Map<
115
- Hash,
116
- Promise<SyncTransactionReceipt>
117
- >();
118
-
119
- const childAddressesCache = new Map<LogFactory, Map<Address, number>>();
120
103
 
121
104
  /**
122
105
  * Data about the range passed to "eth_getLogs" share among all log
@@ -131,32 +114,28 @@ export const createHistoricalSync = (
131
114
  estimatedRange: 500,
132
115
  };
133
116
 
134
- // Closest-to-tip block that has been synced.
135
- let latestBlock: SyncBlock | undefined;
136
-
137
117
  ////////
138
118
  // Helper functions for sync tasks
139
119
  ////////
140
120
 
121
+ type EthGetLogsParams = {
122
+ address: Address | Address[] | undefined;
123
+ topic0?: LogTopic;
124
+ topic1?: LogTopic;
125
+ topic2?: LogTopic;
126
+ topic3?: LogTopic;
127
+ interval: Interval;
128
+ };
129
+
141
130
  /**
142
131
  * Split "eth_getLogs" requests into ranges inferred from errors
143
132
  * and batch requests.
144
133
  */
145
134
  const syncLogsDynamic = async (
146
- {
147
- filter,
148
- address,
149
- interval,
150
- }: {
151
- filter: LogFilter | LogFactory;
152
- interval: Interval;
153
- /** Explicitly set because of the complexity of factory contracts. */
154
- address: Address | Address[] | undefined;
155
- },
135
+ { address, topic0, topic1, topic2, topic3, interval }: EthGetLogsParams,
156
136
  context?: Parameters<Rpc["request"]>[1],
157
137
  ): Promise<SyncLog[]> => {
158
- // Use the recommended range if available, else don't chunk the interval at all.
159
-
138
+ // Use the recommended range if available, else don't chunk the interval at all.
160
139
  const intervals = getChunks({
161
140
  interval,
162
141
  maxChunkSize:
@@ -164,15 +143,12 @@ export const createHistoricalSync = (
164
143
  logsRequestMetadata.estimatedRange,
165
144
  });
166
145
 
167
- const topics =
168
- "eventSelector" in filter
169
- ? [filter.eventSelector]
170
- : [
171
- filter.topic0 ?? null,
172
- filter.topic1 ?? null,
173
- filter.topic2 ?? null,
174
- filter.topic3 ?? null,
175
- ];
146
+ const topics = [
147
+ topic0 ?? null,
148
+ topic1 ?? null,
149
+ topic2 ?? null,
150
+ topic3 ?? null,
151
+ ];
176
152
 
177
153
  // Note: the `topics` field is very fragile for many rpc providers, and
178
154
  // cannot handle extra "null" topics
@@ -257,7 +233,10 @@ export const createHistoricalSync = (
257
233
  : undefined,
258
234
  };
259
235
 
260
- return syncLogsDynamic({ address, interval, filter }, context);
236
+ return syncLogsDynamic(
237
+ { address, topic0, topic1, topic2, topic3, interval },
238
+ context,
239
+ );
261
240
  }),
262
241
  ),
263
242
  ),
@@ -277,66 +256,6 @@ export const createHistoricalSync = (
277
256
  return logs;
278
257
  };
279
258
 
280
- /**
281
- * Extract block, using `blockCache` to avoid fetching
282
- * the same block twice. Also, update `latestBlock`.
283
- *
284
- * @param number Block to be extracted
285
- *
286
- * Note: This function could more accurately skip chain requests by taking
287
- * advantage of `syncStore.hasBlock` and `syncStore.hasTransaction`.
288
- */
289
- const syncBlock = async (
290
- number: number,
291
- context?: Parameters<Rpc["request"]>[1],
292
- ): Promise<SyncBlock> => {
293
- let block: SyncBlock;
294
-
295
- /**
296
- * `blockCache` contains all blocks that have been extracted during the
297
- * current call to `sync()`. If `number` is present in `blockCache` use it,
298
- * otherwise, request the block and add it to `blockCache` and the sync-store.
299
- */
300
-
301
- if (blockCache.has(number)) {
302
- block = await blockCache.get(number)!;
303
- } else {
304
- const _block = _eth_getBlockByNumber(
305
- args.rpc,
306
- { blockNumber: toHex(number) },
307
- { ...context, retryNullBlockRequest: true },
308
- );
309
- blockCache.set(number, _block);
310
- block = await _block;
311
-
312
- // Update `latestBlock` if `block` is closer to tip.
313
- if (
314
- hexToBigInt(block.number) >= hexToBigInt(latestBlock?.number ?? "0x0")
315
- ) {
316
- latestBlock = block;
317
- }
318
- }
319
-
320
- return block;
321
- };
322
-
323
- const syncTrace = async (
324
- block: number,
325
- context?: Parameters<Rpc["request"]>[1],
326
- ) => {
327
- if (traceCache.has(block)) {
328
- return traceCache.get(block)!;
329
- } else {
330
- const traces = _debug_traceBlockByNumber(
331
- args.rpc,
332
- { blockNumber: block },
333
- context,
334
- );
335
- traceCache.set(block, traces);
336
- return traces;
337
- }
338
- };
339
-
340
259
  const syncTransactionReceipts = async (
341
260
  block: SyncBlock,
342
261
  transactionHashes: Set<Hash>,
@@ -349,7 +268,7 @@ export const createHistoricalSync = (
349
268
  if (isBlockReceipts === false) {
350
269
  const transactionReceipts = await Promise.all(
351
270
  Array.from(transactionHashes).map((hash) =>
352
- syncTransactionReceipt(hash, context),
271
+ _eth_getTransactionReceipt(args.rpc, { hash }, context),
353
272
  ),
354
273
  );
355
274
 
@@ -365,7 +284,11 @@ export const createHistoricalSync = (
365
284
 
366
285
  let blockReceipts: SyncTransactionReceipt[];
367
286
  try {
368
- blockReceipts = await syncBlockReceipts(block, context);
287
+ blockReceipts = await _eth_getBlockReceipts(
288
+ args.rpc,
289
+ { blockHash: block.hash },
290
+ context,
291
+ );
369
292
  } catch (_error) {
370
293
  const error = _error as Error;
371
294
  args.common.logger.warn({
@@ -394,58 +317,26 @@ export const createHistoricalSync = (
394
317
  return transactionReceipts;
395
318
  };
396
319
 
397
- const syncTransactionReceipt = async (
398
- transaction: Hash,
399
- context?: Parameters<Rpc["request"]>[1],
400
- ) => {
401
- if (transactionReceiptsCache.has(transaction)) {
402
- return transactionReceiptsCache.get(transaction)!;
403
- } else {
404
- const receipt = _eth_getTransactionReceipt(
405
- args.rpc,
406
- { hash: transaction },
407
- context,
408
- );
409
- transactionReceiptsCache.set(transaction, receipt);
410
- return receipt;
411
- }
412
- };
413
-
414
- const syncBlockReceipts = async (
415
- block: SyncBlock,
416
- context?: Parameters<Rpc["request"]>[1],
417
- ) => {
418
- if (blockReceiptsCache.has(block.hash)) {
419
- return blockReceiptsCache.get(block.hash)!;
420
- } else {
421
- const blockReceipts = _eth_getBlockReceipts(
422
- args.rpc,
423
- { blockHash: block.hash },
424
- context,
425
- );
426
- blockReceiptsCache.set(block.hash, blockReceipts);
427
- return blockReceipts;
428
- }
429
- };
430
-
431
- /** Extract and insert the log-based addresses that match `filter` + `interval`. */
432
- const syncLogFactory = async (
433
- factory: LogFactory,
320
+ /**
321
+ * Fetch child addresses for `factory` within `interval`
322
+ *
323
+ * @dev Newly fetched child addresses are added into `args.childAddresses`
324
+ */
325
+ const syncAddressFactory = async (
326
+ factory: Factory,
434
327
  interval: Interval,
435
328
  context?: Parameters<Rpc["request"]>[1],
436
- ) => {
329
+ ): Promise<Map<Address, number>> => {
437
330
  const logs = await syncLogsDynamic(
438
331
  {
439
- filter: factory,
440
- interval,
441
332
  address: factory.address,
333
+ topic0: factory.eventSelector,
334
+ interval,
442
335
  },
443
336
  context,
444
337
  );
445
338
 
446
- const childAddresses =
447
- childAddressesCache.get(factory) ?? new Map<Address, number>();
448
-
339
+ const childAddresses = new Map<Address, number>();
449
340
  const childAddressesRecord = args.childAddresses.get(factory.id)!;
450
341
 
451
342
  for (const log of logs) {
@@ -464,108 +355,160 @@ export const createHistoricalSync = (
464
355
  }
465
356
  }
466
357
 
467
- // Note: `factory` must refer to the same original `factory` in `filter`
468
- // and not be a recovered factory from `recoverFilter`.
469
- childAddressesCache.set(factory, childAddresses);
358
+ return childAddresses;
470
359
  };
471
360
 
472
- /**
473
- * Return all addresses that match `filter` after extracting addresses
474
- * that match `filter` and `interval`.
475
- */
476
- const syncAddressFactory = async (
477
- factory: Factory,
478
- interval: Interval,
479
- context?: Parameters<Rpc["request"]>[1],
480
- ): Promise<Map<Address, number>> => {
481
- const factoryInterval: Interval = [
482
- Math.max(factory.fromBlock ?? 0, interval[0]),
483
- Math.min(factory.toBlock ?? Number.POSITIVE_INFINITY, interval[1]),
484
- ];
485
-
486
- if (factoryInterval[0] <= factoryInterval[1]) {
487
- await syncLogFactory(factory, factoryInterval, context);
488
- }
489
-
490
- // Note: `factory` must refer to the same original `factory` in `filter`
491
- // and not be a recovered factory from `recoverFilter`.
492
- return args.childAddresses.get(factory.id)!;
493
- };
361
+ return {
362
+ async syncBlockRangeData({
363
+ interval,
364
+ requiredIntervals,
365
+ requiredFactoryIntervals,
366
+ syncStore,
367
+ }) {
368
+ const context = {
369
+ logger: args.common.logger.child({ action: "fetch_block_data" }),
370
+ };
371
+ const endClock = startClock();
372
+ const childAddresses: ChildAddresses = new Map();
373
+ const logs: SyncLog[] = [];
374
+
375
+ // Dedupe factory intervals by factory id
376
+
377
+ const factoryIntervalsById: Map<
378
+ Factory["id"],
379
+ { factory: Factory; interval: Interval }
380
+ > = new Map();
381
+
382
+ for (const { factory, interval } of requiredFactoryIntervals) {
383
+ if (factoryIntervalsById.has(factory.id)) {
384
+ const existingInterval = factoryIntervalsById.get(
385
+ factory.id,
386
+ )!.interval;
387
+
388
+ factoryIntervalsById.get(factory.id)!.interval = intervalBounds([
389
+ existingInterval,
390
+ interval,
391
+ ]);
392
+ } else {
393
+ factoryIntervalsById.set(factory.id, { factory, interval });
394
+ }
395
+ }
494
396
 
495
- ////////
496
- // Helper function for filter types
497
- ////////
397
+ requiredFactoryIntervals = Array.from(factoryIntervalsById.values());
498
398
 
499
- const syncLogFilter = async (
500
- filter: LogFilter,
501
- interval: Interval,
502
- context?: Parameters<Rpc["request"]>[1],
503
- ) => {
504
- let logs: SyncLog[];
505
- if (isAddressFactory(filter.address)) {
506
- const childAddresses = await syncAddressFactory(
507
- filter.address,
508
- interval,
509
- context,
399
+ await Promise.all(
400
+ requiredFactoryIntervals.map(async ({ factory, interval }) => {
401
+ childAddresses.set(
402
+ factory.id,
403
+ await syncAddressFactory(factory, interval, context)!,
404
+ );
405
+ }),
510
406
  );
511
407
 
512
- // Note: Exit early when only the factory needs to be synced
513
- if ((filter.fromBlock ?? 0) > interval[1]) return;
514
-
515
- logs = await syncLogsDynamic(
516
- {
517
- filter,
518
- interval,
519
- address:
520
- childAddresses.size >=
521
- args.common.options.factoryAddressCountThreshold
522
- ? undefined
523
- : Array.from(childAddresses.keys()),
524
- },
525
- context,
526
- );
408
+ const mergedEthGetLogsParams: Map<string, EthGetLogsParams> = new Map();
409
+ const singleEthGetLogsParams: EthGetLogsParams[] = [];
410
+
411
+ for (const { filter, interval } of requiredIntervals) {
412
+ if (filter.type !== "log") continue;
413
+
414
+ const hasAddress = filter.address !== undefined;
415
+ const hasTopic1 = filter.topic1 !== undefined;
416
+ const hasTopic2 = filter.topic2 !== undefined;
417
+ const hasTopic3 = filter.topic3 !== undefined;
418
+
419
+ if (hasAddress === false || hasTopic1 || hasTopic2 || hasTopic3) {
420
+ if (isAddressFactory(filter.address)) {
421
+ const childAddresses = args.childAddresses.get(filter.address.id)!;
422
+ singleEthGetLogsParams.push({
423
+ address:
424
+ childAddresses.size >=
425
+ args.common.options.factoryAddressCountThreshold
426
+ ? undefined
427
+ : Array.from(childAddresses.keys()),
428
+ topic0: filter.topic0,
429
+ topic1: filter.topic1,
430
+ topic2: filter.topic2,
431
+ topic3: filter.topic3,
432
+ interval,
433
+ });
434
+ } else {
435
+ singleEthGetLogsParams.push({
436
+ address: filter.address,
437
+ topic0: filter.topic0,
438
+ topic1: filter.topic1,
439
+ topic2: filter.topic2,
440
+ topic3: filter.topic3,
441
+ interval,
442
+ });
443
+ }
527
444
 
528
- logs = logs.filter((log) =>
529
- isAddressMatched({
530
- address: log.address,
531
- blockNumber: hexToNumber(log.blockNumber),
532
- childAddresses,
533
- }),
534
- );
535
- } else {
536
- logs = await syncLogsDynamic(
537
- {
538
- filter,
539
- interval,
540
- address: filter.address,
541
- },
542
- context,
543
- );
544
- }
445
+ continue;
446
+ }
545
447
 
546
- await args.syncStore.insertLogs({ logs, chainId: args.chain.id }, context);
448
+ let addressKey: string;
449
+ if (isAddressFactory(filter.address)) {
450
+ addressKey = filter.address.id;
451
+ } else if (Array.isArray(filter.address)) {
452
+ addressKey = filter.address.join("_");
453
+ } else {
454
+ addressKey = filter.address as Address;
455
+ }
547
456
 
548
- const logsPerBlock = new Map<number, SyncLog[]>();
549
- for (const log of logs) {
550
- const blockNumber = hexToNumber(log.blockNumber);
551
- if (logsPerBlock.has(blockNumber) === false) {
552
- logsPerBlock.set(blockNumber, []);
457
+ if (mergedEthGetLogsParams.has(addressKey) === false) {
458
+ if (isAddressFactory(filter.address)) {
459
+ const childAddresses = args.childAddresses.get(filter.address.id)!;
460
+ mergedEthGetLogsParams.set(addressKey, {
461
+ address:
462
+ childAddresses.size >=
463
+ args.common.options.factoryAddressCountThreshold
464
+ ? undefined
465
+ : Array.from(childAddresses.keys()),
466
+ topic0: filter.topic0,
467
+ topic1: filter.topic1,
468
+ topic2: filter.topic2,
469
+ topic3: filter.topic3,
470
+ interval,
471
+ });
472
+ } else {
473
+ mergedEthGetLogsParams.set(addressKey, {
474
+ address: filter.address,
475
+ topic0: filter.topic0,
476
+ topic1: filter.topic1,
477
+ topic2: filter.topic2,
478
+ topic3: filter.topic3,
479
+ interval,
480
+ });
481
+ }
482
+ } else {
483
+ const existingInterval =
484
+ mergedEthGetLogsParams.get(addressKey)!.interval;
485
+ const existingTopic0 = mergedEthGetLogsParams.get(addressKey)!
486
+ .topic0 as Hex | Hex[];
487
+
488
+ mergedEthGetLogsParams.get(addressKey)!.topic0 = [
489
+ ...(Array.isArray(existingTopic0)
490
+ ? existingTopic0
491
+ : [existingTopic0]),
492
+ filter.topic0,
493
+ ];
494
+ mergedEthGetLogsParams.get(addressKey)!.interval = intervalBounds([
495
+ existingInterval,
496
+ interval,
497
+ ]);
498
+ }
553
499
  }
554
- logsPerBlock.get(blockNumber)!.push(log);
555
- }
556
-
557
- const blocks = await Promise.all(
558
- Array.from(logsPerBlock.keys()).map((number) =>
559
- syncBlock(number, context),
560
- ),
561
- );
562
500
 
563
- // Validate that logs point to the valid transaction hash in the block
564
- for (const block of blocks) {
565
- const logs = logsPerBlock.get(hexToNumber(block.number))!;
501
+ const ethGetLogsParams = [
502
+ ...singleEthGetLogsParams,
503
+ ...Array.from(mergedEthGetLogsParams.values()),
504
+ ];
566
505
 
567
- validateTransactionsAndBlock(block, "number");
568
- validateLogsAndBlock(logs, block, "number");
506
+ await Promise.all(
507
+ ethGetLogsParams.map(async (params) => {
508
+ const _logs = await syncLogsDynamic(params, context);
509
+ logs.push(..._logs);
510
+ }),
511
+ );
569
512
 
570
513
  for (const log of logs) {
571
514
  if (log.transactionHash === zeroHash) {
@@ -574,503 +517,424 @@ export const createHistoricalSync = (
574
517
  action: "fetch_block_data",
575
518
  chain: args.chain.name,
576
519
  chain_id: args.chain.id,
577
- number: hexToNumber(block.number),
578
- hash: block.hash,
520
+ number: hexToNumber(log.blockNumber),
521
+ hash: log.blockHash,
579
522
  logIndex: hexToNumber(log.logIndex),
580
523
  });
581
524
  }
582
525
  }
583
- }
584
-
585
- const transactionHashes = new Set(logs.map((l) => l.transactionHash));
586
- for (const hash of transactionHashes) {
587
- transactionsCache.add(hash);
588
- }
589
-
590
- if (filter.hasTransactionReceipt) {
591
- const transactionReceipts = await Promise.all(
592
- blocks.map((block) => {
593
- const blockTransactionHashes = new Set<Hash>();
594
-
595
- for (const log of logsPerBlock.get(hexToNumber(block.number))!) {
596
- if (log.transactionHash !== zeroHash) {
597
- blockTransactionHashes.add(log.transactionHash);
598
- }
599
- }
600
526
 
601
- return syncTransactionReceipts(
602
- block,
603
- blockTransactionHashes,
604
- context,
605
- );
606
- }),
607
- ).then((receipts) => receipts.flat());
527
+ let childAddressCount = 0;
528
+ for (const { size } of childAddresses.values()) {
529
+ childAddressCount += size;
530
+ }
608
531
 
609
- await args.syncStore.insertTransactionReceipts(
532
+ args.common.logger.debug(
610
533
  {
611
- transactionReceipts,
612
- chainId: args.chain.id,
534
+ msg: "Fetched block range data",
535
+ chain: args.chain.name,
536
+ chain_id: args.chain.id,
537
+ block_range: JSON.stringify(interval),
538
+ log_count: logs.length,
539
+ child_address_count: childAddressCount,
540
+ duration: endClock(),
613
541
  },
614
- context,
542
+ ["chain", "block_range"],
615
543
  );
616
- }
617
- };
618
-
619
- const syncBlockFilter = async (
620
- filter: BlockFilter,
621
- interval: Interval,
622
- context?: Parameters<Rpc["request"]>[1],
623
- ) => {
624
- const baseOffset = (interval[0] - filter.offset) % filter.interval;
625
- const offset = baseOffset === 0 ? 0 : filter.interval - baseOffset;
626
-
627
- // Determine which blocks are matched by the block filter.
628
- const requiredBlocks: number[] = [];
629
- for (let b = interval[0] + offset; b <= interval[1]; b += filter.interval) {
630
- requiredBlocks.push(b);
631
- }
632
-
633
- await Promise.all(
634
- requiredBlocks.map(async (number) => {
635
- const block = await syncBlock(number, context);
636
- validateTransactionsAndBlock(block, "number");
637
- return block;
638
- }),
639
- );
640
- };
641
-
642
- const syncTransactionFilter = async (
643
- filter: TransactionFilter,
644
- interval: Interval,
645
- context?: Parameters<Rpc["request"]>[1],
646
- ) => {
647
- const fromChildAddresses = isAddressFactory(filter.fromAddress)
648
- ? await syncAddressFactory(filter.fromAddress, interval, context)
649
- : undefined;
650
-
651
- const toChildAddresses = isAddressFactory(filter.toAddress)
652
- ? await syncAddressFactory(filter.toAddress, interval, context)
653
- : undefined;
654
-
655
- // Note: Exit early when only the factory needs to be synced
656
- if ((filter.fromBlock ?? 0) > interval[1]) return;
657
544
 
658
- const blocks = await Promise.all(
659
- intervalRange(interval).map((number) => syncBlock(number, context)),
660
- );
545
+ await promiseAllSettledWithThrow(
546
+ Array.from(childAddresses.entries()).map(
547
+ ([factoryId, childAddresses]) =>
548
+ syncStore.insertChildAddresses(
549
+ {
550
+ factory: factoryIntervalsById.get(factoryId)!.factory,
551
+ childAddresses,
552
+ chainId: args.chain.id,
553
+ },
554
+ context,
555
+ ),
556
+ ),
557
+ );
661
558
 
662
- const transactionHashes: Set<Hash> = new Set();
663
- const requiredBlocks: Set<SyncBlock> = new Set();
559
+ return logs;
560
+ },
561
+ async syncBlockData({ syncStore, interval, requiredIntervals, logs }) {
562
+ const context = {
563
+ logger: args.common.logger.child({ action: "fetch_block_data" }),
564
+ };
565
+ const endClock = startClock();
664
566
 
665
- for (const block of blocks) {
666
- validateTransactionsAndBlock(block, "number");
567
+ const blockFilters: BlockFilter[] = [];
568
+ const transactionFilters: TransactionFilter[] = [];
569
+ const traceFilters: TraceFilter[] = [];
570
+ const logFilters: LogFilter[] = [];
571
+ const transferFilters: TransferFilter[] = [];
667
572
 
668
- for (const transaction of block.transactions) {
669
- if (isTransactionFilterMatched({ filter, transaction }) === false) {
670
- continue;
573
+ for (const { filter } of requiredIntervals) {
574
+ switch (filter.type) {
575
+ case "block": {
576
+ blockFilters.push(filter as BlockFilter);
577
+ break;
578
+ }
579
+ case "transaction": {
580
+ transactionFilters.push(filter as TransactionFilter);
581
+ break;
582
+ }
583
+ case "trace": {
584
+ traceFilters.push(filter as TraceFilter);
585
+ break;
586
+ }
587
+ case "log": {
588
+ logFilters.push(filter as LogFilter);
589
+ break;
590
+ }
591
+ case "transfer": {
592
+ transferFilters.push(filter as TransferFilter);
593
+ break;
594
+ }
671
595
  }
596
+ }
672
597
 
673
- if (
674
- isAddressFactory(filter.fromAddress) &&
675
- isAddressMatched({
676
- address: transaction.from,
677
- blockNumber: Number(block.number),
678
- childAddresses: fromChildAddresses!,
679
- }) === false
680
- ) {
681
- continue;
598
+ const perBlockLogs = new Map<number, SyncLog[]>();
599
+ for (const log of logs) {
600
+ const blockNumber = hexToNumber(log.blockNumber);
601
+ if (perBlockLogs.has(blockNumber) === false) {
602
+ perBlockLogs.set(blockNumber, []);
682
603
  }
604
+ perBlockLogs.get(blockNumber)!.push(log);
605
+ }
683
606
 
684
- if (
685
- isAddressFactory(filter.toAddress) &&
686
- isAddressMatched({
687
- address: transaction.to ?? undefined,
688
- blockNumber: Number(block.number),
689
- childAddresses: toChildAddresses!,
690
- }) === false
691
- ) {
692
- continue;
693
- }
607
+ let closestToTipBlock: SyncBlock | undefined;
694
608
 
695
- transactionHashes.add(transaction.hash);
696
- requiredBlocks.add(block);
697
- }
698
- }
609
+ const syncBlockData = async (blockNumber: number) => {
610
+ let block: SyncBlock | undefined;
699
611
 
700
- for (const hash of transactionHashes) {
701
- transactionsCache.add(hash);
702
- }
612
+ const requiredTransactions = new Set<Hash>();
613
+ const requiredTransactionReceipts = new Set<Hash>();
703
614
 
704
- const transactionReceipts = await Promise.all(
705
- Array.from(requiredBlocks).map((block) => {
706
- const blockTransactionHashes = new Set(
707
- block.transactions
708
- .filter((t) => transactionHashes.has(t.hash))
709
- .map((t) => t.hash),
710
- );
711
- return syncTransactionReceipts(block, blockTransactionHashes, context);
712
- }),
713
- ).then((receipts) => receipts.flat());
615
+ ////////
616
+ // Logs
617
+ ////////
714
618
 
715
- await args.syncStore.insertTransactionReceipts(
716
- {
717
- transactionReceipts,
718
- chainId: args.chain.id,
719
- },
720
- context,
721
- );
722
- };
619
+ let logs: SyncLog[] = [];
620
+ if (perBlockLogs.has(blockNumber)) {
621
+ block = await _eth_getBlockByNumber(
622
+ args.rpc,
623
+ { blockNumber },
624
+ context,
625
+ );
723
626
 
724
- const syncTraceOrTransferFilter = async (
725
- filter: TraceFilter | TransferFilter,
726
- interval: Interval,
727
- context?: Parameters<Rpc["request"]>[1],
728
- ) => {
729
- const fromChildAddresses = isAddressFactory(filter.fromAddress)
730
- ? await syncAddressFactory(filter.fromAddress, interval, context)
731
- : undefined;
732
-
733
- const toChildAddresses = isAddressFactory(filter.toAddress)
734
- ? await syncAddressFactory(filter.toAddress, interval, context)
735
- : undefined;
736
-
737
- // Note: Exit early when only the factory needs to be synced
738
- if ((filter.fromBlock ?? 0) > interval[1]) return;
739
-
740
- const requiredBlocks: Set<SyncBlock> = new Set();
741
- const traces = await Promise.all(
742
- intervalRange(interval).map(async (number) => {
743
- let traces = await syncTrace(number, context);
744
-
745
- // remove unmatched traces
746
- traces = traces.filter((trace) => {
747
- if (
748
- filter.type === "trace" &&
749
- isTraceFilterMatched({
750
- filter,
751
- trace: trace.trace,
752
- block: { number: BigInt(number) },
753
- }) === false
754
- ) {
755
- return false;
756
- }
627
+ logs = perBlockLogs.get(blockNumber)!.filter((log) => {
628
+ let isMatched = false;
629
+
630
+ for (const filter of logFilters) {
631
+ if (
632
+ isLogFilterMatched({ filter, log }) &&
633
+ (isAddressFactory(filter.address)
634
+ ? isAddressMatched({
635
+ address: log.address,
636
+ blockNumber,
637
+ childAddresses: args.childAddresses.get(
638
+ filter.address.id,
639
+ )!,
640
+ })
641
+ : true)
642
+ ) {
643
+ isMatched = true;
644
+
645
+ requiredTransactions.add(log.transactionHash);
646
+ if (filter.hasTransactionReceipt) {
647
+ requiredTransactionReceipts.add(log.transactionHash);
648
+
649
+ // skip to next log
650
+ break;
651
+ }
652
+ }
653
+ }
757
654
 
758
- if (
759
- filter.type === "transfer" &&
760
- isTransferFilterMatched({
761
- filter,
762
- trace: trace.trace,
763
- block: { number: BigInt(number) },
764
- }) === false
765
- ) {
766
- return false;
767
- }
655
+ return isMatched;
656
+ });
768
657
 
769
- if (
770
- isAddressFactory(filter.fromAddress) &&
771
- isAddressMatched({
772
- address: trace.trace.from,
773
- blockNumber: number,
774
- childAddresses: fromChildAddresses!,
775
- }) === false
776
- ) {
777
- return false;
658
+ if (logs.length > 0) {
659
+ validateLogsAndBlock(logs, block!, "number");
778
660
  }
661
+ }
779
662
 
780
- if (
781
- isAddressFactory(filter.toAddress) &&
782
- isAddressMatched({
783
- address: trace.trace.to,
784
- blockNumber: number,
785
- childAddresses: toChildAddresses!,
786
- }) === false
787
- ) {
788
- return false;
789
- }
663
+ ////////
664
+ // Traces
665
+ ////////
790
666
 
791
- return true;
792
- });
667
+ const shouldRequestTraces =
668
+ traceFilters.some((filter) => isBlockInFilter(filter, blockNumber)) ||
669
+ transferFilters.some((filter) =>
670
+ isBlockInFilter(filter, blockNumber),
671
+ );
793
672
 
794
- if (traces.length === 0) return [];
673
+ let traces: SyncTrace[] = [];
674
+ if (shouldRequestTraces) {
675
+ if (block === undefined) {
676
+ [block, traces] = await Promise.all([
677
+ _eth_getBlockByNumber(args.rpc, { blockNumber }, context),
678
+ _debug_traceBlockByNumber(args.rpc, { blockNumber }, context),
679
+ ]);
680
+ } else {
681
+ traces = await _debug_traceBlockByNumber(
682
+ args.rpc,
683
+ { blockNumber },
684
+ context,
685
+ );
686
+ }
795
687
 
796
- const block = await syncBlock(number, context);
688
+ traces = traces.filter((trace) => {
689
+ let isMatched = false;
690
+ for (const filter of transferFilters) {
691
+ if (
692
+ isTransferFilterMatched({
693
+ filter,
694
+ trace: trace.trace,
695
+ block: { number: BigInt(blockNumber) },
696
+ }) &&
697
+ (isAddressFactory(filter.fromAddress)
698
+ ? isAddressMatched({
699
+ address: trace.trace.from,
700
+ blockNumber,
701
+ childAddresses: args.childAddresses.get(
702
+ filter.fromAddress.id,
703
+ )!,
704
+ })
705
+ : true) &&
706
+ (isAddressFactory(filter.toAddress)
707
+ ? isAddressMatched({
708
+ address: trace.trace.to,
709
+ blockNumber,
710
+ childAddresses: args.childAddresses.get(
711
+ filter.toAddress.id,
712
+ )!,
713
+ })
714
+ : true)
715
+ ) {
716
+ isMatched = true;
717
+ requiredTransactions.add(trace.transactionHash);
718
+ if (filter.hasTransactionReceipt) {
719
+ requiredTransactionReceipts.add(trace.transactionHash);
720
+ // skip to next trace
721
+ break;
722
+ }
723
+ }
724
+ }
797
725
 
798
- validateTransactionsAndBlock(block, "number");
799
- validateTracesAndBlock(traces, block, "number");
726
+ for (const filter of traceFilters) {
727
+ if (
728
+ isTraceFilterMatched({
729
+ filter,
730
+ trace: trace.trace,
731
+ block: { number: BigInt(blockNumber) },
732
+ }) &&
733
+ (isAddressFactory(filter.fromAddress)
734
+ ? isAddressMatched({
735
+ address: trace.trace.from,
736
+ blockNumber,
737
+ childAddresses: args.childAddresses.get(
738
+ filter.fromAddress.id,
739
+ )!,
740
+ })
741
+ : true) &&
742
+ (isAddressFactory(filter.toAddress)
743
+ ? isAddressMatched({
744
+ address: trace.trace.to,
745
+ blockNumber,
746
+ childAddresses: args.childAddresses.get(
747
+ filter.toAddress.id,
748
+ )!,
749
+ })
750
+ : true)
751
+ ) {
752
+ isMatched = true;
753
+ requiredTransactions.add(trace.transactionHash);
754
+ if (filter.hasTransactionReceipt) {
755
+ requiredTransactionReceipts.add(trace.transactionHash);
756
+ // skip to next trace
757
+ break;
758
+ }
759
+ }
760
+ }
800
761
 
801
- requiredBlocks.add(block);
762
+ return isMatched;
763
+ });
802
764
 
803
- const transactionsByHash = new Map<Hash, SyncTransaction>();
804
- for (const transaction of block.transactions) {
805
- transactionsByHash.set(transaction.hash, transaction);
765
+ if (traces.length > 0) {
766
+ validateTracesAndBlock(traces, block, "number");
767
+ }
806
768
  }
807
769
 
808
- return traces.map((trace) => {
809
- const transaction = transactionsByHash.get(trace.transactionHash)!;
810
- transactionsCache.add(trace.transactionHash);
811
-
812
- return { trace, transaction, block };
813
- });
814
- }),
815
- ).then((traces) => traces.flat());
816
-
817
- await args.syncStore.insertTraces(
818
- {
819
- traces,
820
- chainId: args.chain.id,
821
- },
822
- context,
823
- );
770
+ ////////
771
+ // Block
772
+ ////////
824
773
 
825
- if (filter.hasTransactionReceipt) {
826
- const transactionReceipts = await Promise.all(
827
- Array.from(requiredBlocks).map((block) => {
828
- const blockTransactionHashes = new Set(
829
- traces
830
- .filter((t) => t.block.hash === block.hash)
831
- .map((t) => t.transaction.hash),
832
- );
833
- return syncTransactionReceipts(
834
- block,
835
- blockTransactionHashes,
774
+ if (
775
+ block === undefined &&
776
+ blockFilters.some((filter) =>
777
+ isBlockFilterMatched({
778
+ filter,
779
+ block: { number: BigInt(blockNumber) },
780
+ }),
781
+ )
782
+ ) {
783
+ block = await _eth_getBlockByNumber(
784
+ args.rpc,
785
+ { blockNumber },
836
786
  context,
837
787
  );
838
- }),
839
- ).then((receipts) => receipts.flat());
840
-
841
- await args.syncStore.insertTransactionReceipts(
842
- {
843
- transactionReceipts,
844
- chainId: args.chain.id,
845
- },
846
- context,
847
- );
848
- }
849
- };
850
-
851
- return {
852
- async sync(_interval) {
853
- const context = {
854
- logger: args.common.logger.child({ action: "fetch_block_data" }),
855
- };
856
- const endClock = startClock();
857
-
858
- const intervalsToSync: {
859
- interval: Interval;
860
- filter: FilterWithoutBlocks;
861
- }[] = [];
862
-
863
- // Determine the requests that need to be made, and which intervals need to be inserted.
864
- // Fragments are used to create a minimal filter, to avoid refetching data even if a filter
865
- // is only partially synced.
866
-
867
- for (const { filter } of args.sources) {
868
- let filterIntervals: Interval[] = [
869
- [
870
- Math.max(filter.fromBlock ?? 0, _interval[0]),
871
- Math.min(filter.toBlock ?? Number.POSITIVE_INFINITY, _interval[1]),
872
- ],
873
- ];
874
-
875
- switch (filter.type) {
876
- case "log":
877
- if (isAddressFactory(filter.address)) {
878
- filterIntervals.push([
879
- Math.max(filter.address.fromBlock ?? 0, _interval[0]),
880
- Math.min(
881
- filter.address.toBlock ?? Number.POSITIVE_INFINITY,
882
- _interval[1],
883
- ),
884
- ]);
885
- }
886
- break;
887
- case "trace":
888
- case "transaction":
889
- case "transfer":
890
- if (isAddressFactory(filter.fromAddress)) {
891
- filterIntervals.push([
892
- Math.max(filter.fromAddress.fromBlock ?? 0, _interval[0]),
893
- Math.min(
894
- filter.fromAddress.toBlock ?? Number.POSITIVE_INFINITY,
895
- _interval[1],
896
- ),
897
- ]);
898
- }
899
-
900
- if (isAddressFactory(filter.toAddress)) {
901
- filterIntervals.push([
902
- Math.max(filter.toAddress.fromBlock ?? 0, _interval[0]),
903
- Math.min(
904
- filter.toAddress.toBlock ?? Number.POSITIVE_INFINITY,
905
- _interval[1],
906
- ),
907
- ]);
908
- }
909
788
  }
910
789
 
911
- filterIntervals = filterIntervals.filter(
912
- ([start, end]) => start <= end,
913
- );
790
+ ////////
791
+ // Transactions
792
+ ////////
914
793
 
915
- if (filterIntervals.length === 0) {
916
- continue;
794
+ // Return early if no data is fetched
795
+ if (
796
+ block === undefined &&
797
+ transactionFilters.some((filter) =>
798
+ isBlockInFilter(filter, blockNumber),
799
+ ) === false
800
+ ) {
801
+ return;
917
802
  }
918
803
 
919
- filterIntervals = intervalUnion(filterIntervals);
920
-
921
- const completedIntervals = args.cachedIntervals.get(filter)!;
922
- const requiredIntervals: {
923
- fragment: Fragment;
924
- intervals: Interval[];
925
- }[] = [];
926
-
927
- for (const {
928
- fragment,
929
- intervals: fragmentIntervals,
930
- } of completedIntervals) {
931
- const requiredFragmentIntervals = intervalDifference(
932
- filterIntervals,
933
- fragmentIntervals,
804
+ if (block === undefined) {
805
+ block = await _eth_getBlockByNumber(
806
+ args.rpc,
807
+ { blockNumber },
808
+ context,
934
809
  );
810
+ }
935
811
 
936
- if (requiredFragmentIntervals.length > 0) {
937
- requiredIntervals.push({
938
- fragment,
939
- intervals: requiredFragmentIntervals,
940
- });
941
- }
812
+ if (
813
+ closestToTipBlock === undefined ||
814
+ hexToNumber(block.number) > hexToNumber(closestToTipBlock.number)
815
+ ) {
816
+ closestToTipBlock = block;
942
817
  }
943
818
 
944
- if (requiredIntervals.length > 0) {
945
- const requiredInterval = intervalBounds(
946
- requiredIntervals.flatMap(({ intervals }) => intervals),
947
- );
819
+ const transactions = block.transactions.filter((transaction) => {
820
+ let isMatched = requiredTransactions.has(transaction.hash);
821
+ for (const filter of transactionFilters) {
822
+ if (
823
+ isTransactionFilterMatched({ filter, transaction }) &&
824
+ (isAddressFactory(filter.fromAddress)
825
+ ? isAddressMatched({
826
+ address: transaction.from,
827
+ blockNumber,
828
+ childAddresses: args.childAddresses.get(
829
+ filter.fromAddress.id,
830
+ )!,
831
+ })
832
+ : true) &&
833
+ (isAddressFactory(filter.toAddress)
834
+ ? isAddressMatched({
835
+ address: transaction.to ?? undefined,
836
+ blockNumber,
837
+ childAddresses: args.childAddresses.get(
838
+ filter.toAddress.id,
839
+ )!,
840
+ })
841
+ : true)
842
+ ) {
843
+ requiredTransactionReceipts.add(transaction.hash);
844
+ isMatched = true;
845
+ }
846
+ }
847
+ return isMatched;
848
+ });
948
849
 
949
- const requiredFilter = recoverFilter(
950
- filter,
951
- requiredIntervals.map(({ fragment }) => fragment),
952
- );
850
+ if (transactions.length > 0) {
851
+ validateTransactionsAndBlock(block, "number");
852
+ }
953
853
 
954
- intervalsToSync.push({
955
- filter: requiredFilter,
956
- interval: requiredInterval,
957
- });
854
+ const transactionsByHash = new Map<Hash, SyncTransaction>();
855
+ for (const transaction of transactions) {
856
+ transactionsByHash.set(transaction.hash, transaction);
958
857
  }
959
- }
960
858
 
961
- await Promise.all(
962
- intervalsToSync.map(async ({ filter, interval }) => {
963
- // Request last block of interval
964
- const blockPromise = syncBlock(interval[1], context);
965
-
966
- switch (filter.type) {
967
- case "log": {
968
- await syncLogFilter(filter as LogFilter, interval, context);
969
- break;
970
- }
859
+ ////////
860
+ // Transaction Receipts
861
+ ////////
971
862
 
972
- case "block": {
973
- await syncBlockFilter(filter as BlockFilter, interval, context);
974
- break;
975
- }
863
+ const transactionReceipts = await syncTransactionReceipts(
864
+ block,
865
+ requiredTransactionReceipts,
866
+ );
976
867
 
977
- case "transaction": {
978
- await syncTransactionFilter(
979
- filter as TransactionFilter,
980
- interval,
981
- context,
982
- );
983
- break;
984
- }
868
+ blockCount += 1;
869
+ transactionCount += transactions.length;
870
+ receiptCount += transactionReceipts.length;
871
+ traceCount += traces.length;
985
872
 
986
- case "trace":
987
- case "transfer": {
988
- await syncTraceOrTransferFilter(
989
- filter as TraceFilter | TransferFilter,
990
- interval,
991
- context,
992
- );
993
- break;
994
- }
995
- }
873
+ // Free memory of all unused transactions
874
+ block.transactions = transactions;
996
875
 
997
- await blockPromise;
998
- }),
876
+ await promiseAllSettledWithThrow([
877
+ syncStore.insertBlocks({ blocks: [block], chainId: args.chain.id }),
878
+ syncStore.insertTransactions({
879
+ transactions,
880
+ chainId: args.chain.id,
881
+ }),
882
+ syncStore.insertTransactionReceipts({
883
+ transactionReceipts,
884
+ chainId: args.chain.id,
885
+ }),
886
+ syncStore.insertTraces({
887
+ traces: traces.map((trace) => ({
888
+ trace,
889
+ block: block!,
890
+ transaction: transactionsByHash.get(trace.transactionHash)!,
891
+ })),
892
+ chainId: args.chain.id,
893
+ }),
894
+ syncStore.insertLogs({ logs, chainId: args.chain.id }),
895
+ ]);
896
+ };
897
+
898
+ let blockCount = 0;
899
+ let transactionCount = 0;
900
+ let receiptCount = 0;
901
+ let traceCount = 0;
902
+
903
+ // Same memory usage as `sync-realtime`.
904
+ const MAX_BLOCKS_IN_MEM = Math.max(
905
+ args.chain.finalityBlockCount * 2,
906
+ 100,
999
907
  );
1000
908
 
1001
- const blocks = await Promise.all(blockCache.values());
909
+ if (requiredIntervals.length > 0) {
910
+ const queue = createQueue({
911
+ browser: false,
912
+ initialStart: true,
913
+ concurrency: MAX_BLOCKS_IN_MEM,
914
+ worker: syncBlockData,
915
+ });
1002
916
 
1003
- await Promise.all([
1004
- args.syncStore.insertBlocks(
1005
- { blocks, chainId: args.chain.id },
1006
- context,
1007
- ),
1008
- args.syncStore.insertTransactions(
1009
- {
1010
- transactions: blocks.flatMap((block) =>
1011
- block.transactions.filter(({ hash }) =>
1012
- transactionsCache.has(hash),
1013
- ),
1014
- ),
1015
- chainId: args.chain.id,
1016
- },
1017
- context,
1018
- ),
1019
- ...Array.from(childAddressesCache.entries()).map(
1020
- ([factory, childAddresses]) =>
1021
- args.syncStore.insertChildAddresses(
1022
- {
1023
- factory,
1024
- childAddresses,
1025
- chainId: args.chain.id,
1026
- },
1027
- context,
1028
- ),
1029
- ),
1030
- ]);
1031
-
1032
- // Add corresponding intervals to the sync-store
1033
- // Note: this should happen after so the database doesn't become corrupted
1034
- if (args.chain.disableCache === false) {
1035
- await args.syncStore.insertIntervals(
1036
- {
1037
- intervals: intervalsToSync,
1038
- chainId: args.chain.id,
1039
- },
1040
- context,
917
+ await Promise.all(
918
+ intervalRange(interval).map((blockNumber) => queue.add(blockNumber)),
1041
919
  );
1042
920
  }
1043
921
 
1044
- let childAddressCount = 0;
1045
- for (const childAddresses of childAddressesCache.values()) {
1046
- childAddressCount += childAddresses.size;
1047
- }
1048
-
1049
922
  args.common.logger.debug(
1050
923
  {
1051
924
  msg: "Fetched block data",
1052
925
  chain: args.chain.name,
1053
926
  chain_id: args.chain.id,
1054
- block_range: JSON.stringify(_interval),
1055
- block_count: blockCache.size,
1056
- transaction_count: transactionsCache.size,
1057
- // receipt_count:
1058
- // log_count:
1059
- trace_count: traceCache.size,
1060
- child_address_count: childAddressCount,
927
+ block_range: JSON.stringify(interval),
928
+ block_count: blockCount,
929
+ transaction_count: transactionCount,
930
+ receipt_count: receiptCount,
931
+ trace_count: traceCount,
1061
932
  duration: endClock(),
1062
933
  },
1063
934
  ["chain", "block_range"],
1064
935
  );
1065
936
 
1066
- blockCache.clear();
1067
- traceCache.clear();
1068
- transactionsCache.clear();
1069
- blockReceiptsCache.clear();
1070
- transactionReceiptsCache.clear();
1071
- childAddressesCache.clear();
1072
-
1073
- return latestBlock;
937
+ return closestToTipBlock;
1074
938
  },
1075
939
  };
1076
940
  };