ponder 0.8.13 → 0.8.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/ponder.js +150 -24
- package/dist/bin/ponder.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/utils/run.ts +2 -2
- package/src/sync/index.ts +14 -4
- package/src/sync-historical/index.ts +133 -18
- package/src/sync-realtime/index.ts +57 -7
- package/src/utils/rpc.ts +14 -0
package/package.json
CHANGED
package/src/bin/utils/run.ts
CHANGED
|
@@ -87,7 +87,7 @@ export async function run({
|
|
|
87
87
|
case "block": {
|
|
88
88
|
// Events must be run block-by-block, so that `database.complete` can accurately
|
|
89
89
|
// update the temporary `checkpoint` value set in the trigger.
|
|
90
|
-
for (const events of splitEvents(event.events)) {
|
|
90
|
+
for (const { checkpoint, events } of splitEvents(event.events)) {
|
|
91
91
|
const result = await handleEvents(
|
|
92
92
|
decodeEvents(common, indexingBuild.sources, events),
|
|
93
93
|
event.checkpoint,
|
|
@@ -96,7 +96,7 @@ export async function run({
|
|
|
96
96
|
if (result.status === "error") onReloadableError(result.error);
|
|
97
97
|
|
|
98
98
|
// Set reorg table `checkpoint` column for newly inserted rows.
|
|
99
|
-
await database.complete({ checkpoint
|
|
99
|
+
await database.complete({ checkpoint });
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
await metadataStore.setStatus(event.status);
|
package/src/sync/index.ts
CHANGED
|
@@ -161,17 +161,27 @@ const min = (...checkpoints: (string | undefined)[]) => {
|
|
|
161
161
|
})!;
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
-
export const splitEvents = (
|
|
164
|
+
export const splitEvents = (
|
|
165
|
+
events: RawEvent[],
|
|
166
|
+
): { checkpoint: string; events: RawEvent[] }[] => {
|
|
165
167
|
let prevHash: Hash | undefined;
|
|
166
|
-
const result: RawEvent[][] = [];
|
|
168
|
+
const result: { checkpoint: string; events: RawEvent[] }[] = [];
|
|
167
169
|
|
|
168
170
|
for (const event of events) {
|
|
169
171
|
if (prevHash === undefined || prevHash !== event.block.hash) {
|
|
170
|
-
result.push(
|
|
172
|
+
result.push({
|
|
173
|
+
checkpoint: encodeCheckpoint({
|
|
174
|
+
...maxCheckpoint,
|
|
175
|
+
blockTimestamp: Number(event.block.timestamp),
|
|
176
|
+
chainId: BigInt(event.chainId),
|
|
177
|
+
blockNumber: event.block.number,
|
|
178
|
+
}),
|
|
179
|
+
events: [],
|
|
180
|
+
});
|
|
171
181
|
prevHash = event.block.hash;
|
|
172
182
|
}
|
|
173
183
|
|
|
174
|
-
result[result.length - 1]!.push(event);
|
|
184
|
+
result[result.length - 1]!.events.push(event);
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
return result;
|
|
@@ -18,7 +18,12 @@ import {
|
|
|
18
18
|
shouldGetTransactionReceipt,
|
|
19
19
|
} from "@/sync/source.js";
|
|
20
20
|
import type { Source, TransactionFilter } from "@/sync/source.js";
|
|
21
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
SyncBlock,
|
|
23
|
+
SyncLog,
|
|
24
|
+
SyncTrace,
|
|
25
|
+
SyncTransactionReceipt,
|
|
26
|
+
} from "@/types/sync.js";
|
|
22
27
|
import {
|
|
23
28
|
type Interval,
|
|
24
29
|
getChunks,
|
|
@@ -30,6 +35,7 @@ import type { RequestQueue } from "@/utils/requestQueue.js";
|
|
|
30
35
|
import {
|
|
31
36
|
_debug_traceBlockByNumber,
|
|
32
37
|
_eth_getBlockByNumber,
|
|
38
|
+
_eth_getBlockReceipts,
|
|
33
39
|
_eth_getLogs,
|
|
34
40
|
_eth_getTransactionReceipt,
|
|
35
41
|
} from "@/utils/rpc.js";
|
|
@@ -66,7 +72,10 @@ export const createHistoricalSync = async (
|
|
|
66
72
|
args: CreateHistoricalSyncParameters,
|
|
67
73
|
): Promise<HistoricalSync> => {
|
|
68
74
|
let isKilled = false;
|
|
69
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Flag to fetch transaction receipts through _eth_getBlockReceipts (true) or _eth_getTransactionReceipt (false)
|
|
77
|
+
*/
|
|
78
|
+
let isBlockReceipts = true;
|
|
70
79
|
/**
|
|
71
80
|
* Blocks that have already been extracted.
|
|
72
81
|
* Note: All entries are deleted at the end of each call to `sync()`.
|
|
@@ -82,6 +91,20 @@ export const createHistoricalSync = async (
|
|
|
82
91
|
* Note: All entries are deleted at the end of each call to `sync()`.
|
|
83
92
|
*/
|
|
84
93
|
const transactionsCache = new Set<Hash>();
|
|
94
|
+
/**
|
|
95
|
+
* Block transaction receipts that have already been fetched.
|
|
96
|
+
* Note: All entries are deleted at the end of each call to `sync()`.
|
|
97
|
+
*/
|
|
98
|
+
const blockReceiptsCache = new Map<Hash, Promise<SyncTransactionReceipt[]>>();
|
|
99
|
+
/**
|
|
100
|
+
* Transaction receipts that have already been fetched.
|
|
101
|
+
* Note: All entries are deleted at the end of each call to `sync()`.
|
|
102
|
+
*/
|
|
103
|
+
const transactionReceiptsCache = new Map<
|
|
104
|
+
Hash,
|
|
105
|
+
Promise<SyncTransactionReceipt>
|
|
106
|
+
>();
|
|
107
|
+
|
|
85
108
|
/**
|
|
86
109
|
* Data about the range passed to "eth_getLogs" for all log
|
|
87
110
|
* filters and log factories.
|
|
@@ -318,6 +341,79 @@ export const createHistoricalSync = async (
|
|
|
318
341
|
}
|
|
319
342
|
};
|
|
320
343
|
|
|
344
|
+
const syncTransactionReceipts = async (
|
|
345
|
+
block: Hash,
|
|
346
|
+
transactionHashes: Set<Hash>,
|
|
347
|
+
): Promise<SyncTransactionReceipt[]> => {
|
|
348
|
+
if (isBlockReceipts === false) {
|
|
349
|
+
const transactionReceipts = await Promise.all(
|
|
350
|
+
Array.from(transactionHashes).map((hash) =>
|
|
351
|
+
syncTransactionReceipt(hash),
|
|
352
|
+
),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
return transactionReceipts;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let blockReceipts: SyncTransactionReceipt[];
|
|
359
|
+
try {
|
|
360
|
+
blockReceipts = await syncBlockReceipts(block);
|
|
361
|
+
} catch (_error) {
|
|
362
|
+
const error = _error as Error;
|
|
363
|
+
args.common.logger.warn({
|
|
364
|
+
service: "sync",
|
|
365
|
+
msg: `Caught eth_getBlockReceipts error on '${
|
|
366
|
+
args.network.name
|
|
367
|
+
}', switching to eth_getTransactionReceipt method.`,
|
|
368
|
+
error,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
isBlockReceipts = false;
|
|
372
|
+
return syncTransactionReceipts(block, transactionHashes);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const blockReceiptsTransactionHashes = new Set(
|
|
376
|
+
blockReceipts.map((r) => r.transactionHash),
|
|
377
|
+
);
|
|
378
|
+
// Validate that block transaction receipts include all required transactions
|
|
379
|
+
for (const hash of Array.from(transactionHashes)) {
|
|
380
|
+
if (blockReceiptsTransactionHashes.has(hash) === false) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`Detected inconsistent RPC responses. 'transaction.hash' ${hash} not found in eth_getBlockReceipts response for block '${block}'`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const transactionReceipts = blockReceipts.filter((receipt) =>
|
|
387
|
+
transactionHashes.has(receipt.transactionHash),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
return transactionReceipts;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const syncTransactionReceipt = async (transaction: Hash) => {
|
|
394
|
+
if (transactionReceiptsCache.has(transaction)) {
|
|
395
|
+
return await transactionReceiptsCache.get(transaction)!;
|
|
396
|
+
} else {
|
|
397
|
+
const receipt = _eth_getTransactionReceipt(args.requestQueue, {
|
|
398
|
+
hash: transaction,
|
|
399
|
+
});
|
|
400
|
+
transactionReceiptsCache.set(transaction, receipt);
|
|
401
|
+
return await receipt;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const syncBlockReceipts = async (block: Hash) => {
|
|
406
|
+
if (blockReceiptsCache.has(block)) {
|
|
407
|
+
return await blockReceiptsCache.get(block)!;
|
|
408
|
+
} else {
|
|
409
|
+
const blockReceipts = _eth_getBlockReceipts(args.requestQueue, {
|
|
410
|
+
blockHash: block,
|
|
411
|
+
});
|
|
412
|
+
blockReceiptsCache.set(block, blockReceipts);
|
|
413
|
+
return await blockReceipts;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
321
417
|
/** Extract and insert the log-based addresses that match `filter` + `interval`. */
|
|
322
418
|
const syncLogFactory = async (filter: LogFactory, interval: Interval) => {
|
|
323
419
|
const logs = await syncLogsDynamic({
|
|
@@ -380,6 +476,8 @@ export const createHistoricalSync = async (
|
|
|
380
476
|
logs.map((log) => syncBlock(hexToNumber(log.blockNumber))),
|
|
381
477
|
);
|
|
382
478
|
|
|
479
|
+
const requiredBlocks = new Set(blocks.map((b) => b.hash));
|
|
480
|
+
|
|
383
481
|
// Validate that logs point to the valid transaction hash in the block
|
|
384
482
|
for (let i = 0; i < logs.length; i++) {
|
|
385
483
|
const log = logs[i]!;
|
|
@@ -418,10 +516,15 @@ export const createHistoricalSync = async (
|
|
|
418
516
|
|
|
419
517
|
if (shouldGetTransactionReceipt(filter)) {
|
|
420
518
|
const transactionReceipts = await Promise.all(
|
|
421
|
-
Array.from(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
519
|
+
Array.from(requiredBlocks).map((blockHash) => {
|
|
520
|
+
const blockTransactionHashes = new Set(
|
|
521
|
+
logs
|
|
522
|
+
.filter((l) => l.blockHash === blockHash)
|
|
523
|
+
.map((l) => l.transactionHash),
|
|
524
|
+
);
|
|
525
|
+
return syncTransactionReceipts(blockHash, blockTransactionHashes);
|
|
526
|
+
}),
|
|
527
|
+
).then((receipts) => receipts.flat());
|
|
425
528
|
|
|
426
529
|
if (isKilled) return;
|
|
427
530
|
|
|
@@ -472,6 +575,7 @@ export const createHistoricalSync = async (
|
|
|
472
575
|
if (isKilled) return;
|
|
473
576
|
|
|
474
577
|
const transactionHashes: Set<Hash> = new Set();
|
|
578
|
+
const requiredBlocks: Set<SyncBlock> = new Set();
|
|
475
579
|
|
|
476
580
|
for (const block of blocks) {
|
|
477
581
|
block.transactions.map((transaction) => {
|
|
@@ -485,6 +589,7 @@ export const createHistoricalSync = async (
|
|
|
485
589
|
})
|
|
486
590
|
) {
|
|
487
591
|
transactionHashes.add(transaction.hash);
|
|
592
|
+
requiredBlocks.add(block);
|
|
488
593
|
}
|
|
489
594
|
});
|
|
490
595
|
}
|
|
@@ -496,10 +601,15 @@ export const createHistoricalSync = async (
|
|
|
496
601
|
if (isKilled) return;
|
|
497
602
|
|
|
498
603
|
const transactionReceipts = await Promise.all(
|
|
499
|
-
Array.from(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
604
|
+
Array.from(requiredBlocks).map((block) => {
|
|
605
|
+
const blockTransactionHashes = new Set(
|
|
606
|
+
block.transactions
|
|
607
|
+
.filter((t) => transactionHashes.has(t.hash))
|
|
608
|
+
.map((t) => t.hash),
|
|
609
|
+
);
|
|
610
|
+
return syncTransactionReceipts(block.hash, blockTransactionHashes);
|
|
611
|
+
}),
|
|
612
|
+
).then((receipts) => receipts.flat());
|
|
503
613
|
|
|
504
614
|
if (isKilled) return;
|
|
505
615
|
|
|
@@ -521,6 +631,7 @@ export const createHistoricalSync = async (
|
|
|
521
631
|
? await syncAddressFactory(filter.toAddress, interval)
|
|
522
632
|
: undefined;
|
|
523
633
|
|
|
634
|
+
const requiredBlocks: Set<Hash> = new Set();
|
|
524
635
|
const traces = await Promise.all(
|
|
525
636
|
intervalRange(interval).map(async (number) => {
|
|
526
637
|
let traces = await syncTrace(number);
|
|
@@ -555,6 +666,7 @@ export const createHistoricalSync = async (
|
|
|
555
666
|
if (traces.length === 0) return [];
|
|
556
667
|
|
|
557
668
|
const block = await syncBlock(number);
|
|
669
|
+
requiredBlocks.add(block.hash);
|
|
558
670
|
|
|
559
671
|
return traces.map((trace) => {
|
|
560
672
|
const transaction = block.transactions.find(
|
|
@@ -576,10 +688,6 @@ export const createHistoricalSync = async (
|
|
|
576
688
|
|
|
577
689
|
if (isKilled) return;
|
|
578
690
|
|
|
579
|
-
const transactionHashes = new Set(
|
|
580
|
-
traces.map(({ transaction }) => transaction.hash),
|
|
581
|
-
);
|
|
582
|
-
|
|
583
691
|
await args.syncStore.insertTraces({
|
|
584
692
|
traces,
|
|
585
693
|
chainId: args.network.chainId,
|
|
@@ -589,10 +697,15 @@ export const createHistoricalSync = async (
|
|
|
589
697
|
|
|
590
698
|
if (shouldGetTransactionReceipt(filter)) {
|
|
591
699
|
const transactionReceipts = await Promise.all(
|
|
592
|
-
Array.from(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
700
|
+
Array.from(requiredBlocks).map((blockHash) => {
|
|
701
|
+
const blockTransactionHashes = new Set(
|
|
702
|
+
traces
|
|
703
|
+
.filter((t) => t.block.hash === blockHash)
|
|
704
|
+
.map((t) => t.transaction.hash),
|
|
705
|
+
);
|
|
706
|
+
return syncTransactionReceipts(blockHash, blockTransactionHashes);
|
|
707
|
+
}),
|
|
708
|
+
).then((receipts) => receipts.flat());
|
|
596
709
|
|
|
597
710
|
if (isKilled) return;
|
|
598
711
|
|
|
@@ -722,6 +835,8 @@ export const createHistoricalSync = async (
|
|
|
722
835
|
blockCache.clear();
|
|
723
836
|
traceCache.clear();
|
|
724
837
|
transactionsCache.clear();
|
|
838
|
+
blockReceiptsCache.clear();
|
|
839
|
+
transactionReceiptsCache.clear();
|
|
725
840
|
|
|
726
841
|
return latestBlock;
|
|
727
842
|
},
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
_debug_traceBlockByHash,
|
|
29
29
|
_eth_getBlockByHash,
|
|
30
30
|
_eth_getBlockByNumber,
|
|
31
|
+
_eth_getBlockReceipts,
|
|
31
32
|
_eth_getLogs,
|
|
32
33
|
_eth_getTransactionReceipt,
|
|
33
34
|
} from "@/utils/rpc.js";
|
|
@@ -100,6 +101,7 @@ export const createRealtimeSync = (
|
|
|
100
101
|
// state
|
|
101
102
|
////////
|
|
102
103
|
let isKilled = false;
|
|
104
|
+
let isBlockReceipts = true;
|
|
103
105
|
let finalizedBlock: LightBlock;
|
|
104
106
|
let finalizedChildAddresses: Map<Factory, Set<Address>>;
|
|
105
107
|
const unfinalizedChildAddresses = new Map<Factory, Set<Address>>();
|
|
@@ -575,6 +577,57 @@ export const createRealtimeSync = (
|
|
|
575
577
|
}
|
|
576
578
|
};
|
|
577
579
|
|
|
580
|
+
const syncTransactionReceipts = async (
|
|
581
|
+
blockHash: Hash,
|
|
582
|
+
transactionHashes: Set<Hash>,
|
|
583
|
+
): Promise<SyncTransactionReceipt[]> => {
|
|
584
|
+
if (isBlockReceipts === false) {
|
|
585
|
+
const transactionReceipts = await Promise.all(
|
|
586
|
+
Array.from(transactionHashes).map(async (hash) =>
|
|
587
|
+
_eth_getTransactionReceipt(args.requestQueue, { hash }),
|
|
588
|
+
),
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
return transactionReceipts;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
let blockReceipts: SyncTransactionReceipt[];
|
|
595
|
+
try {
|
|
596
|
+
blockReceipts = await _eth_getBlockReceipts(args.requestQueue, {
|
|
597
|
+
blockHash,
|
|
598
|
+
});
|
|
599
|
+
} catch (_error) {
|
|
600
|
+
const error = _error as Error;
|
|
601
|
+
args.common.logger.warn({
|
|
602
|
+
service: "realtime",
|
|
603
|
+
msg: `Caught eth_getBlockReceipts error on '${
|
|
604
|
+
args.network.name
|
|
605
|
+
}', switching to eth_getTransactionReceipt method.`,
|
|
606
|
+
error,
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
isBlockReceipts = false;
|
|
610
|
+
return syncTransactionReceipts(blockHash, transactionHashes);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const blockReceiptsTransactionHashes = new Set(
|
|
614
|
+
blockReceipts.map((r) => r.transactionHash),
|
|
615
|
+
);
|
|
616
|
+
// Validate that block transaction receipts include all required transactions
|
|
617
|
+
for (const hash of Array.from(transactionHashes)) {
|
|
618
|
+
if (blockReceiptsTransactionHashes.has(hash) === false) {
|
|
619
|
+
throw new Error(
|
|
620
|
+
`Detected inconsistent RPC responses. Transaction receipt with transactionHash ${hash} is missing in \`blockReceipts\`.`,
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const transactionReceipts = blockReceipts.filter((receipt) =>
|
|
625
|
+
transactionHashes.has(receipt.transactionHash),
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
return transactionReceipts;
|
|
629
|
+
};
|
|
630
|
+
|
|
578
631
|
/**
|
|
579
632
|
* Fetch all data (logs, traces, receipts) for the specified block required by `args.sources`
|
|
580
633
|
*
|
|
@@ -769,7 +822,7 @@ export const createRealtimeSync = (
|
|
|
769
822
|
for (const hash of Array.from(requiredTransactions)) {
|
|
770
823
|
if (blockTransactionsHashes.has(hash) === false) {
|
|
771
824
|
throw new Error(
|
|
772
|
-
`Detected inconsistent RPC responses.
|
|
825
|
+
`Detected inconsistent RPC responses. 'transaction.hash' ${hash} not found in eth_getBlockReceipts response for block '${block.hash}'.`,
|
|
773
826
|
);
|
|
774
827
|
}
|
|
775
828
|
}
|
|
@@ -778,12 +831,9 @@ export const createRealtimeSync = (
|
|
|
778
831
|
// Transaction Receipts
|
|
779
832
|
////////
|
|
780
833
|
|
|
781
|
-
const transactionReceipts = await
|
|
782
|
-
block.
|
|
783
|
-
|
|
784
|
-
.map(({ hash }) =>
|
|
785
|
-
_eth_getTransactionReceipt(args.requestQueue, { hash }),
|
|
786
|
-
),
|
|
834
|
+
const transactionReceipts = await syncTransactionReceipts(
|
|
835
|
+
block.hash,
|
|
836
|
+
requiredTransactionReceipts,
|
|
787
837
|
);
|
|
788
838
|
|
|
789
839
|
return {
|
package/src/utils/rpc.ts
CHANGED
|
@@ -146,6 +146,20 @@ export const _eth_getTransactionReceipt = (
|
|
|
146
146
|
return receipt as SyncTransactionReceipt;
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Helper function for "eth_getBlockReceipts" request.
|
|
151
|
+
*/
|
|
152
|
+
export const _eth_getBlockReceipts = (
|
|
153
|
+
requestQueue: RequestQueue,
|
|
154
|
+
{ blockHash }: { blockHash: Hash },
|
|
155
|
+
): Promise<SyncTransactionReceipt[]> =>
|
|
156
|
+
requestQueue
|
|
157
|
+
.request({
|
|
158
|
+
method: "eth_getBlockReceipts",
|
|
159
|
+
params: [blockHash],
|
|
160
|
+
} as any)
|
|
161
|
+
.then((receipts) => receipts as unknown as SyncTransactionReceipt[]);
|
|
162
|
+
|
|
149
163
|
/**
|
|
150
164
|
* Helper function for "debug_traceBlockByNumber" request.
|
|
151
165
|
*/
|