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
@@ -1,41 +1,16 @@
1
1
  import { _debug_traceBlockByNumber, _eth_getBlockByNumber, _eth_getBlockReceipts, _eth_getLogs, _eth_getTransactionReceipt, validateLogsAndBlock, validateReceiptsAndBlock, validateTracesAndBlock, validateTransactionsAndBlock, } from '../rpc/actions.js';
2
- import { getChildAddress, isAddressFactory, isAddressMatched, isLogFactoryMatched, isTraceFilterMatched, isTransactionFilterMatched, isTransferFilterMatched, } from '../runtime/filter.js';
3
- import { recoverFilter } from '../runtime/fragments.js';
4
- import { getChunks, intervalBounds, intervalDifference, intervalRange, intervalUnion, } from '../utils/interval.js';
2
+ import { getChildAddress, isAddressFactory, isAddressMatched, isBlockFilterMatched, isBlockInFilter, isLogFactoryMatched, isLogFilterMatched, isTraceFilterMatched, isTransactionFilterMatched, isTransferFilterMatched, } from '../runtime/filter.js';
3
+ import { getChunks, intervalBounds, intervalRange, } from '../utils/interval.js';
4
+ import { promiseAllSettledWithThrow } from '../utils/promiseAllSettledWithThrow.js';
5
+ import { createQueue } from '../utils/queue.js';
5
6
  import { startClock } from '../utils/timer.js';
6
7
  import { getLogsRetryHelper } from "@ponder/utils";
7
- import { hexToBigInt, hexToNumber, toHex, zeroHash, } from "viem";
8
+ import { hexToNumber, toHex, zeroHash, } from "viem";
8
9
  export const createHistoricalSync = (args) => {
9
10
  /**
10
11
  * Flag to fetch transaction receipts through _eth_getBlockReceipts (true) or _eth_getTransactionReceipt (false)
11
12
  */
12
13
  let isBlockReceipts = true;
13
- /**
14
- * Blocks that have already been extracted.
15
- * Note: All entries are deleted at the end of each call to `sync()`.
16
- */
17
- const blockCache = new Map();
18
- /**
19
- * Traces that have already been fetched.
20
- * Note: All entries are deleted at the end of each call to `sync()`.
21
- */
22
- const traceCache = new Map();
23
- /**
24
- * Transactions that should be saved to the sync-store.
25
- * Note: All entries are deleted at the end of each call to `sync()`.
26
- */
27
- const transactionsCache = new Set();
28
- /**
29
- * Block transaction receipts that have already been fetched.
30
- * Note: All entries are deleted at the end of each call to `sync()`.
31
- */
32
- const blockReceiptsCache = new Map();
33
- /**
34
- * Transaction receipts that have already been fetched.
35
- * Note: All entries are deleted at the end of each call to `sync()`.
36
- */
37
- const transactionReceiptsCache = new Map();
38
- const childAddressesCache = new Map();
39
14
  /**
40
15
  * Data about the range passed to "eth_getLogs" share among all log
41
16
  * filters and log factories.
@@ -43,30 +18,23 @@ export const createHistoricalSync = (args) => {
43
18
  let logsRequestMetadata = {
44
19
  estimatedRange: 500,
45
20
  };
46
- // Closest-to-tip block that has been synced.
47
- let latestBlock;
48
- ////////
49
- // Helper functions for sync tasks
50
- ////////
51
21
  /**
52
22
  * Split "eth_getLogs" requests into ranges inferred from errors
53
23
  * and batch requests.
54
24
  */
55
- const syncLogsDynamic = async ({ filter, address, interval, }, context) => {
56
- // Use the recommended range if available, else don't chunk the interval at all.
25
+ const syncLogsDynamic = async ({ address, topic0, topic1, topic2, topic3, interval }, context) => {
26
+ // Use the recommended range if available, else don't chunk the interval at all.
57
27
  const intervals = getChunks({
58
28
  interval,
59
29
  maxChunkSize: logsRequestMetadata.confirmedRange ??
60
30
  logsRequestMetadata.estimatedRange,
61
31
  });
62
- const topics = "eventSelector" in filter
63
- ? [filter.eventSelector]
64
- : [
65
- filter.topic0 ?? null,
66
- filter.topic1 ?? null,
67
- filter.topic2 ?? null,
68
- filter.topic3 ?? null,
69
- ];
32
+ const topics = [
33
+ topic0 ?? null,
34
+ topic1 ?? null,
35
+ topic2 ?? null,
36
+ topic3 ?? null,
37
+ ];
70
38
  // Note: the `topics` field is very fragile for many rpc providers, and
71
39
  // cannot handle extra "null" topics
72
40
  if (topics[3] === null) {
@@ -136,7 +104,7 @@ export const createHistoricalSync = (args) => {
136
104
  ? range
137
105
  : undefined,
138
106
  };
139
- return syncLogsDynamic({ address, interval, filter }, context);
107
+ return syncLogsDynamic({ address, topic0, topic1, topic2, topic3, interval }, context);
140
108
  })))).then((logs) => logs.flat());
141
109
  /**
142
110
  * Dynamically increase the range used in "eth_getLogs" if an
@@ -147,58 +115,18 @@ export const createHistoricalSync = (args) => {
147
115
  }
148
116
  return logs;
149
117
  };
150
- /**
151
- * Extract block, using `blockCache` to avoid fetching
152
- * the same block twice. Also, update `latestBlock`.
153
- *
154
- * @param number Block to be extracted
155
- *
156
- * Note: This function could more accurately skip chain requests by taking
157
- * advantage of `syncStore.hasBlock` and `syncStore.hasTransaction`.
158
- */
159
- const syncBlock = async (number, context) => {
160
- let block;
161
- /**
162
- * `blockCache` contains all blocks that have been extracted during the
163
- * current call to `sync()`. If `number` is present in `blockCache` use it,
164
- * otherwise, request the block and add it to `blockCache` and the sync-store.
165
- */
166
- if (blockCache.has(number)) {
167
- block = await blockCache.get(number);
168
- }
169
- else {
170
- const _block = _eth_getBlockByNumber(args.rpc, { blockNumber: toHex(number) }, { ...context, retryNullBlockRequest: true });
171
- blockCache.set(number, _block);
172
- block = await _block;
173
- // Update `latestBlock` if `block` is closer to tip.
174
- if (hexToBigInt(block.number) >= hexToBigInt(latestBlock?.number ?? "0x0")) {
175
- latestBlock = block;
176
- }
177
- }
178
- return block;
179
- };
180
- const syncTrace = async (block, context) => {
181
- if (traceCache.has(block)) {
182
- return traceCache.get(block);
183
- }
184
- else {
185
- const traces = _debug_traceBlockByNumber(args.rpc, { blockNumber: block }, context);
186
- traceCache.set(block, traces);
187
- return traces;
188
- }
189
- };
190
118
  const syncTransactionReceipts = async (block, transactionHashes, context) => {
191
119
  if (transactionHashes.size === 0) {
192
120
  return [];
193
121
  }
194
122
  if (isBlockReceipts === false) {
195
- const transactionReceipts = await Promise.all(Array.from(transactionHashes).map((hash) => syncTransactionReceipt(hash, context)));
123
+ const transactionReceipts = await Promise.all(Array.from(transactionHashes).map((hash) => _eth_getTransactionReceipt(args.rpc, { hash }, context)));
196
124
  validateReceiptsAndBlock(transactionReceipts, block, "eth_getTransactionReceipt", "number");
197
125
  return transactionReceipts;
198
126
  }
199
127
  let blockReceipts;
200
128
  try {
201
- blockReceipts = await syncBlockReceipts(block, context);
129
+ blockReceipts = await _eth_getBlockReceipts(args.rpc, { blockHash: block.hash }, context);
202
130
  }
203
131
  catch (_error) {
204
132
  const error = _error;
@@ -216,34 +144,18 @@ export const createHistoricalSync = (args) => {
216
144
  const transactionReceipts = blockReceipts.filter((receipt) => transactionHashes.has(receipt.transactionHash));
217
145
  return transactionReceipts;
218
146
  };
219
- const syncTransactionReceipt = async (transaction, context) => {
220
- if (transactionReceiptsCache.has(transaction)) {
221
- return transactionReceiptsCache.get(transaction);
222
- }
223
- else {
224
- const receipt = _eth_getTransactionReceipt(args.rpc, { hash: transaction }, context);
225
- transactionReceiptsCache.set(transaction, receipt);
226
- return receipt;
227
- }
228
- };
229
- const syncBlockReceipts = async (block, context) => {
230
- if (blockReceiptsCache.has(block.hash)) {
231
- return blockReceiptsCache.get(block.hash);
232
- }
233
- else {
234
- const blockReceipts = _eth_getBlockReceipts(args.rpc, { blockHash: block.hash }, context);
235
- blockReceiptsCache.set(block.hash, blockReceipts);
236
- return blockReceipts;
237
- }
238
- };
239
- /** Extract and insert the log-based addresses that match `filter` + `interval`. */
240
- const syncLogFactory = async (factory, interval, context) => {
147
+ /**
148
+ * Fetch child addresses for `factory` within `interval`
149
+ *
150
+ * @dev Newly fetched child addresses are added into `args.childAddresses`
151
+ */
152
+ const syncAddressFactory = async (factory, interval, context) => {
241
153
  const logs = await syncLogsDynamic({
242
- filter: factory,
243
- interval,
244
154
  address: factory.address,
155
+ topic0: factory.eventSelector,
156
+ interval,
245
157
  }, context);
246
- const childAddresses = childAddressesCache.get(factory) ?? new Map();
158
+ const childAddresses = new Map();
247
159
  const childAddressesRecord = args.childAddresses.get(factory.id);
248
160
  for (const log of logs) {
249
161
  if (isLogFactoryMatched({ factory, log })) {
@@ -257,72 +169,130 @@ export const createHistoricalSync = (args) => {
257
169
  }
258
170
  }
259
171
  }
260
- // Note: `factory` must refer to the same original `factory` in `filter`
261
- // and not be a recovered factory from `recoverFilter`.
262
- childAddressesCache.set(factory, childAddresses);
172
+ return childAddresses;
263
173
  };
264
- /**
265
- * Return all addresses that match `filter` after extracting addresses
266
- * that match `filter` and `interval`.
267
- */
268
- const syncAddressFactory = async (factory, interval, context) => {
269
- const factoryInterval = [
270
- Math.max(factory.fromBlock ?? 0, interval[0]),
271
- Math.min(factory.toBlock ?? Number.POSITIVE_INFINITY, interval[1]),
272
- ];
273
- if (factoryInterval[0] <= factoryInterval[1]) {
274
- await syncLogFactory(factory, factoryInterval, context);
275
- }
276
- // Note: `factory` must refer to the same original `factory` in `filter`
277
- // and not be a recovered factory from `recoverFilter`.
278
- return args.childAddresses.get(factory.id);
279
- };
280
- ////////
281
- // Helper function for filter types
282
- ////////
283
- const syncLogFilter = async (filter, interval, context) => {
284
- let logs;
285
- if (isAddressFactory(filter.address)) {
286
- const childAddresses = await syncAddressFactory(filter.address, interval, context);
287
- // Note: Exit early when only the factory needs to be synced
288
- if ((filter.fromBlock ?? 0) > interval[1])
289
- return;
290
- logs = await syncLogsDynamic({
291
- filter,
292
- interval,
293
- address: childAddresses.size >=
294
- args.common.options.factoryAddressCountThreshold
295
- ? undefined
296
- : Array.from(childAddresses.keys()),
297
- }, context);
298
- logs = logs.filter((log) => isAddressMatched({
299
- address: log.address,
300
- blockNumber: hexToNumber(log.blockNumber),
301
- childAddresses,
174
+ return {
175
+ async syncBlockRangeData({ interval, requiredIntervals, requiredFactoryIntervals, syncStore, }) {
176
+ const context = {
177
+ logger: args.common.logger.child({ action: "fetch_block_data" }),
178
+ };
179
+ const endClock = startClock();
180
+ const childAddresses = new Map();
181
+ const logs = [];
182
+ // Dedupe factory intervals by factory id
183
+ const factoryIntervalsById = new Map();
184
+ for (const { factory, interval } of requiredFactoryIntervals) {
185
+ if (factoryIntervalsById.has(factory.id)) {
186
+ const existingInterval = factoryIntervalsById.get(factory.id).interval;
187
+ factoryIntervalsById.get(factory.id).interval = intervalBounds([
188
+ existingInterval,
189
+ interval,
190
+ ]);
191
+ }
192
+ else {
193
+ factoryIntervalsById.set(factory.id, { factory, interval });
194
+ }
195
+ }
196
+ requiredFactoryIntervals = Array.from(factoryIntervalsById.values());
197
+ await Promise.all(requiredFactoryIntervals.map(async ({ factory, interval }) => {
198
+ childAddresses.set(factory.id, await syncAddressFactory(factory, interval, context));
302
199
  }));
303
- }
304
- else {
305
- logs = await syncLogsDynamic({
306
- filter,
307
- interval,
308
- address: filter.address,
309
- }, context);
310
- }
311
- await args.syncStore.insertLogs({ logs, chainId: args.chain.id }, context);
312
- const logsPerBlock = new Map();
313
- for (const log of logs) {
314
- const blockNumber = hexToNumber(log.blockNumber);
315
- if (logsPerBlock.has(blockNumber) === false) {
316
- logsPerBlock.set(blockNumber, []);
200
+ const mergedEthGetLogsParams = new Map();
201
+ const singleEthGetLogsParams = [];
202
+ for (const { filter, interval } of requiredIntervals) {
203
+ if (filter.type !== "log")
204
+ continue;
205
+ const hasAddress = filter.address !== undefined;
206
+ const hasTopic1 = filter.topic1 !== undefined;
207
+ const hasTopic2 = filter.topic2 !== undefined;
208
+ const hasTopic3 = filter.topic3 !== undefined;
209
+ if (hasAddress === false || hasTopic1 || hasTopic2 || hasTopic3) {
210
+ if (isAddressFactory(filter.address)) {
211
+ const childAddresses = args.childAddresses.get(filter.address.id);
212
+ singleEthGetLogsParams.push({
213
+ address: childAddresses.size >=
214
+ args.common.options.factoryAddressCountThreshold
215
+ ? undefined
216
+ : Array.from(childAddresses.keys()),
217
+ topic0: filter.topic0,
218
+ topic1: filter.topic1,
219
+ topic2: filter.topic2,
220
+ topic3: filter.topic3,
221
+ interval,
222
+ });
223
+ }
224
+ else {
225
+ singleEthGetLogsParams.push({
226
+ address: filter.address,
227
+ topic0: filter.topic0,
228
+ topic1: filter.topic1,
229
+ topic2: filter.topic2,
230
+ topic3: filter.topic3,
231
+ interval,
232
+ });
233
+ }
234
+ continue;
235
+ }
236
+ let addressKey;
237
+ if (isAddressFactory(filter.address)) {
238
+ addressKey = filter.address.id;
239
+ }
240
+ else if (Array.isArray(filter.address)) {
241
+ addressKey = filter.address.join("_");
242
+ }
243
+ else {
244
+ addressKey = filter.address;
245
+ }
246
+ if (mergedEthGetLogsParams.has(addressKey) === false) {
247
+ if (isAddressFactory(filter.address)) {
248
+ const childAddresses = args.childAddresses.get(filter.address.id);
249
+ mergedEthGetLogsParams.set(addressKey, {
250
+ address: childAddresses.size >=
251
+ args.common.options.factoryAddressCountThreshold
252
+ ? undefined
253
+ : Array.from(childAddresses.keys()),
254
+ topic0: filter.topic0,
255
+ topic1: filter.topic1,
256
+ topic2: filter.topic2,
257
+ topic3: filter.topic3,
258
+ interval,
259
+ });
260
+ }
261
+ else {
262
+ mergedEthGetLogsParams.set(addressKey, {
263
+ address: filter.address,
264
+ topic0: filter.topic0,
265
+ topic1: filter.topic1,
266
+ topic2: filter.topic2,
267
+ topic3: filter.topic3,
268
+ interval,
269
+ });
270
+ }
271
+ }
272
+ else {
273
+ const existingInterval = mergedEthGetLogsParams.get(addressKey).interval;
274
+ const existingTopic0 = mergedEthGetLogsParams.get(addressKey)
275
+ .topic0;
276
+ mergedEthGetLogsParams.get(addressKey).topic0 = [
277
+ ...(Array.isArray(existingTopic0)
278
+ ? existingTopic0
279
+ : [existingTopic0]),
280
+ filter.topic0,
281
+ ];
282
+ mergedEthGetLogsParams.get(addressKey).interval = intervalBounds([
283
+ existingInterval,
284
+ interval,
285
+ ]);
286
+ }
317
287
  }
318
- logsPerBlock.get(blockNumber).push(log);
319
- }
320
- const blocks = await Promise.all(Array.from(logsPerBlock.keys()).map((number) => syncBlock(number, context)));
321
- // Validate that logs point to the valid transaction hash in the block
322
- for (const block of blocks) {
323
- const logs = logsPerBlock.get(hexToNumber(block.number));
324
- validateTransactionsAndBlock(block, "number");
325
- validateLogsAndBlock(logs, block, "number");
288
+ const ethGetLogsParams = [
289
+ ...singleEthGetLogsParams,
290
+ ...Array.from(mergedEthGetLogsParams.values()),
291
+ ];
292
+ await Promise.all(ethGetLogsParams.map(async (params) => {
293
+ const _logs = await syncLogsDynamic(params, context);
294
+ logs.push(..._logs);
295
+ }));
326
296
  for (const log of logs) {
327
297
  if (log.transactionHash === zeroHash) {
328
298
  args.common.logger.warn({
@@ -330,318 +300,307 @@ export const createHistoricalSync = (args) => {
330
300
  action: "fetch_block_data",
331
301
  chain: args.chain.name,
332
302
  chain_id: args.chain.id,
333
- number: hexToNumber(block.number),
334
- hash: block.hash,
303
+ number: hexToNumber(log.blockNumber),
304
+ hash: log.blockHash,
335
305
  logIndex: hexToNumber(log.logIndex),
336
306
  });
337
307
  }
338
308
  }
339
- }
340
- const transactionHashes = new Set(logs.map((l) => l.transactionHash));
341
- for (const hash of transactionHashes) {
342
- transactionsCache.add(hash);
343
- }
344
- if (filter.hasTransactionReceipt) {
345
- const transactionReceipts = await Promise.all(blocks.map((block) => {
346
- const blockTransactionHashes = new Set();
347
- for (const log of logsPerBlock.get(hexToNumber(block.number))) {
348
- if (log.transactionHash !== zeroHash) {
349
- blockTransactionHashes.add(log.transactionHash);
350
- }
351
- }
352
- return syncTransactionReceipts(block, blockTransactionHashes, context);
353
- })).then((receipts) => receipts.flat());
354
- await args.syncStore.insertTransactionReceipts({
355
- transactionReceipts,
356
- chainId: args.chain.id,
357
- }, context);
358
- }
359
- };
360
- const syncBlockFilter = async (filter, interval, context) => {
361
- const baseOffset = (interval[0] - filter.offset) % filter.interval;
362
- const offset = baseOffset === 0 ? 0 : filter.interval - baseOffset;
363
- // Determine which blocks are matched by the block filter.
364
- const requiredBlocks = [];
365
- for (let b = interval[0] + offset; b <= interval[1]; b += filter.interval) {
366
- requiredBlocks.push(b);
367
- }
368
- await Promise.all(requiredBlocks.map(async (number) => {
369
- const block = await syncBlock(number, context);
370
- validateTransactionsAndBlock(block, "number");
371
- return block;
372
- }));
373
- };
374
- const syncTransactionFilter = async (filter, interval, context) => {
375
- const fromChildAddresses = isAddressFactory(filter.fromAddress)
376
- ? await syncAddressFactory(filter.fromAddress, interval, context)
377
- : undefined;
378
- const toChildAddresses = isAddressFactory(filter.toAddress)
379
- ? await syncAddressFactory(filter.toAddress, interval, context)
380
- : undefined;
381
- // Note: Exit early when only the factory needs to be synced
382
- if ((filter.fromBlock ?? 0) > interval[1])
383
- return;
384
- const blocks = await Promise.all(intervalRange(interval).map((number) => syncBlock(number, context)));
385
- const transactionHashes = new Set();
386
- const requiredBlocks = new Set();
387
- for (const block of blocks) {
388
- validateTransactionsAndBlock(block, "number");
389
- for (const transaction of block.transactions) {
390
- if (isTransactionFilterMatched({ filter, transaction }) === false) {
391
- continue;
392
- }
393
- if (isAddressFactory(filter.fromAddress) &&
394
- isAddressMatched({
395
- address: transaction.from,
396
- blockNumber: Number(block.number),
397
- childAddresses: fromChildAddresses,
398
- }) === false) {
399
- continue;
400
- }
401
- if (isAddressFactory(filter.toAddress) &&
402
- isAddressMatched({
403
- address: transaction.to ?? undefined,
404
- blockNumber: Number(block.number),
405
- childAddresses: toChildAddresses,
406
- }) === false) {
407
- continue;
408
- }
409
- transactionHashes.add(transaction.hash);
410
- requiredBlocks.add(block);
411
- }
412
- }
413
- for (const hash of transactionHashes) {
414
- transactionsCache.add(hash);
415
- }
416
- const transactionReceipts = await Promise.all(Array.from(requiredBlocks).map((block) => {
417
- const blockTransactionHashes = new Set(block.transactions
418
- .filter((t) => transactionHashes.has(t.hash))
419
- .map((t) => t.hash));
420
- return syncTransactionReceipts(block, blockTransactionHashes, context);
421
- })).then((receipts) => receipts.flat());
422
- await args.syncStore.insertTransactionReceipts({
423
- transactionReceipts,
424
- chainId: args.chain.id,
425
- }, context);
426
- };
427
- const syncTraceOrTransferFilter = async (filter, interval, context) => {
428
- const fromChildAddresses = isAddressFactory(filter.fromAddress)
429
- ? await syncAddressFactory(filter.fromAddress, interval, context)
430
- : undefined;
431
- const toChildAddresses = isAddressFactory(filter.toAddress)
432
- ? await syncAddressFactory(filter.toAddress, interval, context)
433
- : undefined;
434
- // Note: Exit early when only the factory needs to be synced
435
- if ((filter.fromBlock ?? 0) > interval[1])
436
- return;
437
- const requiredBlocks = new Set();
438
- const traces = await Promise.all(intervalRange(interval).map(async (number) => {
439
- let traces = await syncTrace(number, context);
440
- // remove unmatched traces
441
- traces = traces.filter((trace) => {
442
- if (filter.type === "trace" &&
443
- isTraceFilterMatched({
444
- filter,
445
- trace: trace.trace,
446
- block: { number: BigInt(number) },
447
- }) === false) {
448
- return false;
449
- }
450
- if (filter.type === "transfer" &&
451
- isTransferFilterMatched({
452
- filter,
453
- trace: trace.trace,
454
- block: { number: BigInt(number) },
455
- }) === false) {
456
- return false;
457
- }
458
- if (isAddressFactory(filter.fromAddress) &&
459
- isAddressMatched({
460
- address: trace.trace.from,
461
- blockNumber: number,
462
- childAddresses: fromChildAddresses,
463
- }) === false) {
464
- return false;
465
- }
466
- if (isAddressFactory(filter.toAddress) &&
467
- isAddressMatched({
468
- address: trace.trace.to,
469
- blockNumber: number,
470
- childAddresses: toChildAddresses,
471
- }) === false) {
472
- return false;
473
- }
474
- return true;
475
- });
476
- if (traces.length === 0)
477
- return [];
478
- const block = await syncBlock(number, context);
479
- validateTransactionsAndBlock(block, "number");
480
- validateTracesAndBlock(traces, block, "number");
481
- requiredBlocks.add(block);
482
- const transactionsByHash = new Map();
483
- for (const transaction of block.transactions) {
484
- transactionsByHash.set(transaction.hash, transaction);
309
+ let childAddressCount = 0;
310
+ for (const { size } of childAddresses.values()) {
311
+ childAddressCount += size;
485
312
  }
486
- return traces.map((trace) => {
487
- const transaction = transactionsByHash.get(trace.transactionHash);
488
- transactionsCache.add(trace.transactionHash);
489
- return { trace, transaction, block };
490
- });
491
- })).then((traces) => traces.flat());
492
- await args.syncStore.insertTraces({
493
- traces,
494
- chainId: args.chain.id,
495
- }, context);
496
- if (filter.hasTransactionReceipt) {
497
- const transactionReceipts = await Promise.all(Array.from(requiredBlocks).map((block) => {
498
- const blockTransactionHashes = new Set(traces
499
- .filter((t) => t.block.hash === block.hash)
500
- .map((t) => t.transaction.hash));
501
- return syncTransactionReceipts(block, blockTransactionHashes, context);
502
- })).then((receipts) => receipts.flat());
503
- await args.syncStore.insertTransactionReceipts({
504
- transactionReceipts,
313
+ args.common.logger.debug({
314
+ msg: "Fetched block range data",
315
+ chain: args.chain.name,
316
+ chain_id: args.chain.id,
317
+ block_range: JSON.stringify(interval),
318
+ log_count: logs.length,
319
+ child_address_count: childAddressCount,
320
+ duration: endClock(),
321
+ }, ["chain", "block_range"]);
322
+ await promiseAllSettledWithThrow(Array.from(childAddresses.entries()).map(([factoryId, childAddresses]) => syncStore.insertChildAddresses({
323
+ factory: factoryIntervalsById.get(factoryId).factory,
324
+ childAddresses,
505
325
  chainId: args.chain.id,
506
- }, context);
507
- }
508
- };
509
- return {
510
- async sync(_interval) {
326
+ }, context)));
327
+ return logs;
328
+ },
329
+ async syncBlockData({ syncStore, interval, requiredIntervals, logs }) {
511
330
  const context = {
512
331
  logger: args.common.logger.child({ action: "fetch_block_data" }),
513
332
  };
514
333
  const endClock = startClock();
515
- const intervalsToSync = [];
516
- // Determine the requests that need to be made, and which intervals need to be inserted.
517
- // Fragments are used to create a minimal filter, to avoid refetching data even if a filter
518
- // is only partially synced.
519
- for (const { filter } of args.sources) {
520
- let filterIntervals = [
521
- [
522
- Math.max(filter.fromBlock ?? 0, _interval[0]),
523
- Math.min(filter.toBlock ?? Number.POSITIVE_INFINITY, _interval[1]),
524
- ],
525
- ];
334
+ const blockFilters = [];
335
+ const transactionFilters = [];
336
+ const traceFilters = [];
337
+ const logFilters = [];
338
+ const transferFilters = [];
339
+ for (const { filter } of requiredIntervals) {
526
340
  switch (filter.type) {
527
- case "log":
528
- if (isAddressFactory(filter.address)) {
529
- filterIntervals.push([
530
- Math.max(filter.address.fromBlock ?? 0, _interval[0]),
531
- Math.min(filter.address.toBlock ?? Number.POSITIVE_INFINITY, _interval[1]),
532
- ]);
533
- }
341
+ case "block": {
342
+ blockFilters.push(filter);
534
343
  break;
535
- case "trace":
536
- case "transaction":
537
- case "transfer":
538
- if (isAddressFactory(filter.fromAddress)) {
539
- filterIntervals.push([
540
- Math.max(filter.fromAddress.fromBlock ?? 0, _interval[0]),
541
- Math.min(filter.fromAddress.toBlock ?? Number.POSITIVE_INFINITY, _interval[1]),
542
- ]);
543
- }
544
- if (isAddressFactory(filter.toAddress)) {
545
- filterIntervals.push([
546
- Math.max(filter.toAddress.fromBlock ?? 0, _interval[0]),
547
- Math.min(filter.toAddress.toBlock ?? Number.POSITIVE_INFINITY, _interval[1]),
548
- ]);
549
- }
550
- }
551
- filterIntervals = filterIntervals.filter(([start, end]) => start <= end);
552
- if (filterIntervals.length === 0) {
553
- continue;
554
- }
555
- filterIntervals = intervalUnion(filterIntervals);
556
- const completedIntervals = args.cachedIntervals.get(filter);
557
- const requiredIntervals = [];
558
- for (const { fragment, intervals: fragmentIntervals, } of completedIntervals) {
559
- const requiredFragmentIntervals = intervalDifference(filterIntervals, fragmentIntervals);
560
- if (requiredFragmentIntervals.length > 0) {
561
- requiredIntervals.push({
562
- fragment,
563
- intervals: requiredFragmentIntervals,
564
- });
565
344
  }
566
- }
567
- if (requiredIntervals.length > 0) {
568
- const requiredInterval = intervalBounds(requiredIntervals.flatMap(({ intervals }) => intervals));
569
- const requiredFilter = recoverFilter(filter, requiredIntervals.map(({ fragment }) => fragment));
570
- intervalsToSync.push({
571
- filter: requiredFilter,
572
- interval: requiredInterval,
573
- });
574
- }
575
- }
576
- await Promise.all(intervalsToSync.map(async ({ filter, interval }) => {
577
- // Request last block of interval
578
- const blockPromise = syncBlock(interval[1], context);
579
- switch (filter.type) {
580
- case "log": {
581
- await syncLogFilter(filter, interval, context);
345
+ case "transaction": {
346
+ transactionFilters.push(filter);
582
347
  break;
583
348
  }
584
- case "block": {
585
- await syncBlockFilter(filter, interval, context);
349
+ case "trace": {
350
+ traceFilters.push(filter);
586
351
  break;
587
352
  }
588
- case "transaction": {
589
- await syncTransactionFilter(filter, interval, context);
353
+ case "log": {
354
+ logFilters.push(filter);
590
355
  break;
591
356
  }
592
- case "trace":
593
357
  case "transfer": {
594
- await syncTraceOrTransferFilter(filter, interval, context);
358
+ transferFilters.push(filter);
595
359
  break;
596
360
  }
597
361
  }
598
- await blockPromise;
599
- }));
600
- const blocks = await Promise.all(blockCache.values());
601
- await Promise.all([
602
- args.syncStore.insertBlocks({ blocks, chainId: args.chain.id }, context),
603
- args.syncStore.insertTransactions({
604
- transactions: blocks.flatMap((block) => block.transactions.filter(({ hash }) => transactionsCache.has(hash))),
605
- chainId: args.chain.id,
606
- }, context),
607
- ...Array.from(childAddressesCache.entries()).map(([factory, childAddresses]) => args.syncStore.insertChildAddresses({
608
- factory,
609
- childAddresses,
610
- chainId: args.chain.id,
611
- }, context)),
612
- ]);
613
- // Add corresponding intervals to the sync-store
614
- // Note: this should happen after so the database doesn't become corrupted
615
- if (args.chain.disableCache === false) {
616
- await args.syncStore.insertIntervals({
617
- intervals: intervalsToSync,
618
- chainId: args.chain.id,
619
- }, context);
620
362
  }
621
- let childAddressCount = 0;
622
- for (const childAddresses of childAddressesCache.values()) {
623
- childAddressCount += childAddresses.size;
363
+ const perBlockLogs = new Map();
364
+ for (const log of logs) {
365
+ const blockNumber = hexToNumber(log.blockNumber);
366
+ if (perBlockLogs.has(blockNumber) === false) {
367
+ perBlockLogs.set(blockNumber, []);
368
+ }
369
+ perBlockLogs.get(blockNumber).push(log);
370
+ }
371
+ let closestToTipBlock;
372
+ const syncBlockData = async (blockNumber) => {
373
+ let block;
374
+ const requiredTransactions = new Set();
375
+ const requiredTransactionReceipts = new Set();
376
+ ////////
377
+ // Logs
378
+ ////////
379
+ let logs = [];
380
+ if (perBlockLogs.has(blockNumber)) {
381
+ block = await _eth_getBlockByNumber(args.rpc, { blockNumber }, context);
382
+ logs = perBlockLogs.get(blockNumber).filter((log) => {
383
+ let isMatched = false;
384
+ for (const filter of logFilters) {
385
+ if (isLogFilterMatched({ filter, log }) &&
386
+ (isAddressFactory(filter.address)
387
+ ? isAddressMatched({
388
+ address: log.address,
389
+ blockNumber,
390
+ childAddresses: args.childAddresses.get(filter.address.id),
391
+ })
392
+ : true)) {
393
+ isMatched = true;
394
+ requiredTransactions.add(log.transactionHash);
395
+ if (filter.hasTransactionReceipt) {
396
+ requiredTransactionReceipts.add(log.transactionHash);
397
+ // skip to next log
398
+ break;
399
+ }
400
+ }
401
+ }
402
+ return isMatched;
403
+ });
404
+ if (logs.length > 0) {
405
+ validateLogsAndBlock(logs, block, "number");
406
+ }
407
+ }
408
+ ////////
409
+ // Traces
410
+ ////////
411
+ const shouldRequestTraces = traceFilters.some((filter) => isBlockInFilter(filter, blockNumber)) ||
412
+ transferFilters.some((filter) => isBlockInFilter(filter, blockNumber));
413
+ let traces = [];
414
+ if (shouldRequestTraces) {
415
+ if (block === undefined) {
416
+ [block, traces] = await Promise.all([
417
+ _eth_getBlockByNumber(args.rpc, { blockNumber }, context),
418
+ _debug_traceBlockByNumber(args.rpc, { blockNumber }, context),
419
+ ]);
420
+ }
421
+ else {
422
+ traces = await _debug_traceBlockByNumber(args.rpc, { blockNumber }, context);
423
+ }
424
+ traces = traces.filter((trace) => {
425
+ let isMatched = false;
426
+ for (const filter of transferFilters) {
427
+ if (isTransferFilterMatched({
428
+ filter,
429
+ trace: trace.trace,
430
+ block: { number: BigInt(blockNumber) },
431
+ }) &&
432
+ (isAddressFactory(filter.fromAddress)
433
+ ? isAddressMatched({
434
+ address: trace.trace.from,
435
+ blockNumber,
436
+ childAddresses: args.childAddresses.get(filter.fromAddress.id),
437
+ })
438
+ : true) &&
439
+ (isAddressFactory(filter.toAddress)
440
+ ? isAddressMatched({
441
+ address: trace.trace.to,
442
+ blockNumber,
443
+ childAddresses: args.childAddresses.get(filter.toAddress.id),
444
+ })
445
+ : true)) {
446
+ isMatched = true;
447
+ requiredTransactions.add(trace.transactionHash);
448
+ if (filter.hasTransactionReceipt) {
449
+ requiredTransactionReceipts.add(trace.transactionHash);
450
+ // skip to next trace
451
+ break;
452
+ }
453
+ }
454
+ }
455
+ for (const filter of traceFilters) {
456
+ if (isTraceFilterMatched({
457
+ filter,
458
+ trace: trace.trace,
459
+ block: { number: BigInt(blockNumber) },
460
+ }) &&
461
+ (isAddressFactory(filter.fromAddress)
462
+ ? isAddressMatched({
463
+ address: trace.trace.from,
464
+ blockNumber,
465
+ childAddresses: args.childAddresses.get(filter.fromAddress.id),
466
+ })
467
+ : true) &&
468
+ (isAddressFactory(filter.toAddress)
469
+ ? isAddressMatched({
470
+ address: trace.trace.to,
471
+ blockNumber,
472
+ childAddresses: args.childAddresses.get(filter.toAddress.id),
473
+ })
474
+ : true)) {
475
+ isMatched = true;
476
+ requiredTransactions.add(trace.transactionHash);
477
+ if (filter.hasTransactionReceipt) {
478
+ requiredTransactionReceipts.add(trace.transactionHash);
479
+ // skip to next trace
480
+ break;
481
+ }
482
+ }
483
+ }
484
+ return isMatched;
485
+ });
486
+ if (traces.length > 0) {
487
+ validateTracesAndBlock(traces, block, "number");
488
+ }
489
+ }
490
+ ////////
491
+ // Block
492
+ ////////
493
+ if (block === undefined &&
494
+ blockFilters.some((filter) => isBlockFilterMatched({
495
+ filter,
496
+ block: { number: BigInt(blockNumber) },
497
+ }))) {
498
+ block = await _eth_getBlockByNumber(args.rpc, { blockNumber }, context);
499
+ }
500
+ ////////
501
+ // Transactions
502
+ ////////
503
+ // Return early if no data is fetched
504
+ if (block === undefined &&
505
+ transactionFilters.some((filter) => isBlockInFilter(filter, blockNumber)) === false) {
506
+ return;
507
+ }
508
+ if (block === undefined) {
509
+ block = await _eth_getBlockByNumber(args.rpc, { blockNumber }, context);
510
+ }
511
+ if (closestToTipBlock === undefined ||
512
+ hexToNumber(block.number) > hexToNumber(closestToTipBlock.number)) {
513
+ closestToTipBlock = block;
514
+ }
515
+ const transactions = block.transactions.filter((transaction) => {
516
+ let isMatched = requiredTransactions.has(transaction.hash);
517
+ for (const filter of transactionFilters) {
518
+ if (isTransactionFilterMatched({ filter, transaction }) &&
519
+ (isAddressFactory(filter.fromAddress)
520
+ ? isAddressMatched({
521
+ address: transaction.from,
522
+ blockNumber,
523
+ childAddresses: args.childAddresses.get(filter.fromAddress.id),
524
+ })
525
+ : true) &&
526
+ (isAddressFactory(filter.toAddress)
527
+ ? isAddressMatched({
528
+ address: transaction.to ?? undefined,
529
+ blockNumber,
530
+ childAddresses: args.childAddresses.get(filter.toAddress.id),
531
+ })
532
+ : true)) {
533
+ requiredTransactionReceipts.add(transaction.hash);
534
+ isMatched = true;
535
+ }
536
+ }
537
+ return isMatched;
538
+ });
539
+ if (transactions.length > 0) {
540
+ validateTransactionsAndBlock(block, "number");
541
+ }
542
+ const transactionsByHash = new Map();
543
+ for (const transaction of transactions) {
544
+ transactionsByHash.set(transaction.hash, transaction);
545
+ }
546
+ ////////
547
+ // Transaction Receipts
548
+ ////////
549
+ const transactionReceipts = await syncTransactionReceipts(block, requiredTransactionReceipts);
550
+ blockCount += 1;
551
+ transactionCount += transactions.length;
552
+ receiptCount += transactionReceipts.length;
553
+ traceCount += traces.length;
554
+ // Free memory of all unused transactions
555
+ block.transactions = transactions;
556
+ await promiseAllSettledWithThrow([
557
+ syncStore.insertBlocks({ blocks: [block], chainId: args.chain.id }),
558
+ syncStore.insertTransactions({
559
+ transactions,
560
+ chainId: args.chain.id,
561
+ }),
562
+ syncStore.insertTransactionReceipts({
563
+ transactionReceipts,
564
+ chainId: args.chain.id,
565
+ }),
566
+ syncStore.insertTraces({
567
+ traces: traces.map((trace) => ({
568
+ trace,
569
+ block: block,
570
+ transaction: transactionsByHash.get(trace.transactionHash),
571
+ })),
572
+ chainId: args.chain.id,
573
+ }),
574
+ syncStore.insertLogs({ logs, chainId: args.chain.id }),
575
+ ]);
576
+ };
577
+ let blockCount = 0;
578
+ let transactionCount = 0;
579
+ let receiptCount = 0;
580
+ let traceCount = 0;
581
+ // Same memory usage as `sync-realtime`.
582
+ const MAX_BLOCKS_IN_MEM = Math.max(args.chain.finalityBlockCount * 2, 100);
583
+ if (requiredIntervals.length > 0) {
584
+ const queue = createQueue({
585
+ browser: false,
586
+ initialStart: true,
587
+ concurrency: MAX_BLOCKS_IN_MEM,
588
+ worker: syncBlockData,
589
+ });
590
+ await Promise.all(intervalRange(interval).map((blockNumber) => queue.add(blockNumber)));
624
591
  }
625
592
  args.common.logger.debug({
626
593
  msg: "Fetched block data",
627
594
  chain: args.chain.name,
628
595
  chain_id: args.chain.id,
629
- block_range: JSON.stringify(_interval),
630
- block_count: blockCache.size,
631
- transaction_count: transactionsCache.size,
632
- // receipt_count:
633
- // log_count:
634
- trace_count: traceCache.size,
635
- child_address_count: childAddressCount,
596
+ block_range: JSON.stringify(interval),
597
+ block_count: blockCount,
598
+ transaction_count: transactionCount,
599
+ receipt_count: receiptCount,
600
+ trace_count: traceCount,
636
601
  duration: endClock(),
637
602
  }, ["chain", "block_range"]);
638
- blockCache.clear();
639
- traceCache.clear();
640
- transactionsCache.clear();
641
- blockReceiptsCache.clear();
642
- transactionReceiptsCache.clear();
643
- childAddressesCache.clear();
644
- return latestBlock;
603
+ return closestToTipBlock;
645
604
  },
646
605
  };
647
606
  };