ponder 0.9.2 → 0.9.3

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 (48) hide show
  1. package/dist/bin/ponder.js +1933 -1606
  2. package/dist/bin/ponder.js.map +1 -1
  3. package/dist/{chunk-IFTUFVCL.js → chunk-LHCA5XFV.js} +2 -5
  4. package/dist/chunk-LHCA5XFV.js.map +1 -0
  5. package/dist/index.d.ts +5 -2
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/bin/commands/codegen.ts +8 -10
  10. package/src/bin/commands/dev.ts +30 -42
  11. package/src/bin/commands/list.ts +9 -14
  12. package/src/bin/commands/serve.ts +26 -39
  13. package/src/bin/commands/start.ts +29 -42
  14. package/src/bin/utils/{shutdown.ts → exit.ts} +23 -37
  15. package/src/bin/utils/run.ts +275 -175
  16. package/src/bin/utils/runServer.ts +1 -5
  17. package/src/build/index.ts +3 -8
  18. package/src/build/pre.ts +3 -0
  19. package/src/config/index.ts +5 -2
  20. package/src/database/index.ts +72 -72
  21. package/src/drizzle/kit/index.ts +3 -3
  22. package/src/indexing/index.ts +0 -4
  23. package/src/indexing/service.ts +31 -93
  24. package/src/indexing-store/historical.ts +2 -4
  25. package/src/internal/common.ts +2 -0
  26. package/src/internal/errors.ts +9 -9
  27. package/src/internal/logger.ts +1 -1
  28. package/src/internal/metrics.ts +75 -103
  29. package/src/internal/shutdown.ts +25 -0
  30. package/src/internal/telemetry.ts +16 -18
  31. package/src/internal/types.ts +9 -1
  32. package/src/server/index.ts +3 -5
  33. package/src/sync/events.ts +4 -4
  34. package/src/sync/filter.ts +1 -0
  35. package/src/sync/index.ts +1046 -805
  36. package/src/sync-historical/index.ts +0 -37
  37. package/src/sync-realtime/index.ts +48 -48
  38. package/src/sync-store/encoding.ts +5 -5
  39. package/src/sync-store/index.ts +5 -23
  40. package/src/ui/index.ts +2 -11
  41. package/src/utils/checkpoint.ts +17 -3
  42. package/src/utils/chunk.ts +7 -0
  43. package/src/utils/generators.ts +66 -0
  44. package/src/utils/mutex.ts +34 -0
  45. package/src/utils/partition.ts +41 -0
  46. package/src/utils/requestQueue.ts +19 -10
  47. package/src/utils/zipper.ts +80 -0
  48. package/dist/chunk-IFTUFVCL.js.map +0 -1
@@ -61,7 +61,6 @@ export type HistoricalSync = {
61
61
  * that is synced.
62
62
  */
63
63
  sync(interval: Interval): Promise<SyncBlock | undefined>;
64
- kill(): void;
65
64
  };
66
65
 
67
66
  type CreateHistoricalSyncParameters = {
@@ -76,7 +75,6 @@ type CreateHistoricalSyncParameters = {
76
75
  export const createHistoricalSync = async (
77
76
  args: CreateHistoricalSyncParameters,
78
77
  ): Promise<HistoricalSync> => {
79
- let isKilled = false;
80
78
  /**
81
79
  * Flag to fetch transaction receipts through _eth_getBlockReceipts (true) or _eth_getTransactionReceipt (false)
82
80
  */
@@ -434,8 +432,6 @@ export const createHistoricalSync = async (
434
432
  address: filter.address,
435
433
  });
436
434
 
437
- if (isKilled) return;
438
-
439
435
  // Insert `logs` into the sync-store
440
436
  await args.syncStore.insertLogs({
441
437
  logs: logs.map((log) => ({ log })),
@@ -478,12 +474,8 @@ export const createHistoricalSync = async (
478
474
  ? await syncAddressFactory(filter.address, interval)
479
475
  : filter.address;
480
476
 
481
- if (isKilled) return;
482
-
483
477
  const logs = await syncLogsDynamic({ filter, interval, address });
484
478
 
485
- if (isKilled) return;
486
-
487
479
  const blocks = await Promise.all(
488
480
  logs.map((log) => syncBlock(hexToNumber(log.blockNumber))),
489
481
  );
@@ -523,16 +515,12 @@ export const createHistoricalSync = async (
523
515
  transactionsCache.add(hash);
524
516
  }
525
517
 
526
- if (isKilled) return;
527
-
528
518
  await args.syncStore.insertLogs({
529
519
  logs: logs.map((log, i) => ({ log, block: blocks[i]! })),
530
520
  shouldUpdateCheckpoint: true,
531
521
  chainId: args.network.chainId,
532
522
  });
533
523
 
534
- if (isKilled) return;
535
-
536
524
  if (shouldGetTransactionReceipt(filter)) {
537
525
  const transactionReceipts = await Promise.all(
538
526
  Array.from(requiredBlocks).map((blockHash) => {
@@ -555,8 +543,6 @@ export const createHistoricalSync = async (
555
543
  }),
556
544
  ).then((receipts) => receipts.flat());
557
545
 
558
- if (isKilled) return;
559
-
560
546
  await args.syncStore.insertTransactionReceipts({
561
547
  transactionReceipts,
562
548
  chainId: args.network.chainId,
@@ -595,14 +581,10 @@ export const createHistoricalSync = async (
595
581
  )
596
582
  : undefined;
597
583
 
598
- if (isKilled) return;
599
-
600
584
  const blocks = await Promise.all(
601
585
  intervalRange(interval).map((number) => syncBlock(number)),
602
586
  );
603
587
 
604
- if (isKilled) return;
605
-
606
588
  const transactionHashes: Set<Hash> = new Set();
607
589
  const requiredBlocks: Set<SyncBlock> = new Set();
608
590
 
@@ -627,8 +609,6 @@ export const createHistoricalSync = async (
627
609
  transactionsCache.add(hash);
628
610
  }
629
611
 
630
- if (isKilled) return;
631
-
632
612
  const transactionReceipts = await Promise.all(
633
613
  Array.from(requiredBlocks).map((block) => {
634
614
  const blockTransactionHashes = new Set(
@@ -640,8 +620,6 @@ export const createHistoricalSync = async (
640
620
  }),
641
621
  ).then((receipts) => receipts.flat());
642
622
 
643
- if (isKilled) return;
644
-
645
623
  await args.syncStore.insertTransactionReceipts({
646
624
  transactionReceipts,
647
625
  chainId: args.network.chainId,
@@ -715,15 +693,11 @@ export const createHistoricalSync = async (
715
693
  }),
716
694
  ).then((traces) => traces.flat());
717
695
 
718
- if (isKilled) return;
719
-
720
696
  await args.syncStore.insertTraces({
721
697
  traces,
722
698
  chainId: args.network.chainId,
723
699
  });
724
700
 
725
- if (isKilled) return;
726
-
727
701
  if (shouldGetTransactionReceipt(filter)) {
728
702
  const transactionReceipts = await Promise.all(
729
703
  Array.from(requiredBlocks).map((blockHash) => {
@@ -736,8 +710,6 @@ export const createHistoricalSync = async (
736
710
  }),
737
711
  ).then((receipts) => receipts.flat());
738
712
 
739
- if (isKilled) return;
740
-
741
713
  await args.syncStore.insertTransactionReceipts({
742
714
  transactionReceipts,
743
715
  chainId: args.network.chainId,
@@ -858,14 +830,10 @@ export const createHistoricalSync = async (
858
830
  return;
859
831
  }
860
832
 
861
- if (isKilled) return;
862
-
863
833
  await blockPromise;
864
834
  }),
865
835
  );
866
836
 
867
- if (isKilled) return;
868
-
869
837
  const blocks = await Promise.all(blockCache.values());
870
838
 
871
839
  await Promise.all([
@@ -883,8 +851,6 @@ export const createHistoricalSync = async (
883
851
  }),
884
852
  ]);
885
853
 
886
- if (isKilled) return;
887
-
888
854
  // Add corresponding intervals to the sync-store
889
855
  // Note: this should happen after so the database doesn't become corrupted
890
856
  if (args.network.disableCache === false) {
@@ -902,8 +868,5 @@ export const createHistoricalSync = async (
902
868
 
903
869
  return latestBlock;
904
870
  },
905
- kill() {
906
- isKilled = true;
907
- },
908
871
  };
909
872
  };
@@ -1,4 +1,5 @@
1
1
  import type { Common } from "@/internal/common.js";
2
+ import { ShutdownError } from "@/internal/errors.js";
2
3
  import type {
3
4
  BlockFilter,
4
5
  Factory,
@@ -30,6 +31,7 @@ import type {
30
31
  SyncTransaction,
31
32
  SyncTransactionReceipt,
32
33
  } from "@/types/sync.js";
34
+ import { mutex } from "@/utils/mutex.js";
33
35
  import { range } from "@/utils/range.js";
34
36
  import type { RequestQueue } from "@/utils/requestQueue.js";
35
37
  import {
@@ -42,7 +44,7 @@ import {
42
44
  } from "@/utils/rpc.js";
43
45
  import { startClock } from "@/utils/timer.js";
44
46
  import { wait } from "@/utils/wait.js";
45
- import { type Queue, createQueue } from "@ponder/common";
47
+ import type { Queue } from "@ponder/common";
46
48
  import { type Address, type Hash, hexToNumber, zeroHash } from "viem";
47
49
  import { isFilterInBloom, zeroLogsBloom } from "./bloom.js";
48
50
 
@@ -51,10 +53,10 @@ export type RealtimeSync = {
51
53
  syncProgress: Pick<SyncProgress, "finalized">;
52
54
  initialChildAddresses: Map<Factory, Set<Address>>;
53
55
  }): Promise<Queue<void, BlockWithEventData>>;
54
- kill(): Promise<void>;
55
56
  unfinalizedBlocks: LightBlock[];
56
57
  finalizedChildAddresses: Map<Factory, Set<Address>>;
57
58
  unfinalizedChildAddresses: Map<Factory, Set<Address>>;
59
+ kill: () => void;
58
60
  };
59
61
 
60
62
  type CreateRealtimeSyncParameters = {
@@ -102,7 +104,6 @@ export const createRealtimeSync = (
102
104
  ////////
103
105
  // state
104
106
  ////////
105
- let isKilled = false;
106
107
  let isBlockReceipts = true;
107
108
  let finalizedBlock: LightBlock;
108
109
  let finalizedChildAddresses: Map<Factory, Set<Address>>;
@@ -115,7 +116,7 @@ export const createRealtimeSync = (
115
116
  * `parentHash` => `hash`.
116
117
  */
117
118
  let unfinalizedBlocks: LightBlock[] = [];
118
- let queue: Queue<void, BlockWithEventData & { endClock?: () => number }>;
119
+ // let queue: Queue<void, BlockWithEventData & { endClock?: () => number }>;
119
120
  let consecutiveErrors = 0;
120
121
  let interval: NodeJS.Timeout | undefined;
121
122
 
@@ -433,7 +434,7 @@ export const createRealtimeSync = (
433
434
  service: "realtime",
434
435
  msg: `Finalized ${hexToNumber(pendingFinalizedBlock.number) - hexToNumber(finalizedBlock.number) + 1} '${
435
436
  args.network.name
436
- }' blocks from ${hexToNumber(finalizedBlock.number) + 1} to ${hexToNumber(pendingFinalizedBlock.number)}`,
437
+ }' blocks [${hexToNumber(finalizedBlock.number) + 1}, ${hexToNumber(pendingFinalizedBlock.number)}]`,
437
438
  });
438
439
 
439
440
  const finalizedBlocks = unfinalizedBlocks.filter(
@@ -490,11 +491,6 @@ export const createRealtimeSync = (
490
491
 
491
492
  await args.onEvent({ type: "finalize", block: pendingFinalizedBlock });
492
493
  }
493
-
494
- args.common.logger.debug({
495
- service: "realtime",
496
- msg: `Finished syncing '${args.network.name}' block ${hexToNumber(block.number)}`,
497
- });
498
494
  };
499
495
 
500
496
  /**
@@ -551,9 +547,9 @@ export const createRealtimeSync = (
551
547
 
552
548
  args.common.logger.warn({
553
549
  service: "realtime",
554
- msg: `Reconciled ${reorgedBlocks.length}-block reorg on '${
550
+ msg: `Reconciled ${reorgedBlocks.length}-block '${
555
551
  args.network.name
556
- }' with common ancestor block ${hexToNumber(commonAncestor.number)}`,
552
+ }' reorg with common ancestor block ${hexToNumber(commonAncestor.number)}`,
557
553
  });
558
554
 
559
555
  // recompute `unfinalizedChildAddresses`
@@ -691,11 +687,11 @@ export const createRealtimeSync = (
691
687
  if (log.transactionHash === zeroHash) {
692
688
  args.common.logger.warn({
693
689
  service: "sync",
694
- msg: `Detected log with empty transaction hash in block ${block.hash} at log index ${hexToNumber(log.logIndex)}. This is expected for some networks like ZKsync.`,
690
+ msg: `Detected '${args.network.name}' log with empty transaction hash in block ${block.hash} at log index ${hexToNumber(log.logIndex)}. This is expected for some networks like ZKsync.`,
695
691
  });
696
692
  } else {
697
693
  throw new Error(
698
- `Detected inconsistent RPC responses. 'log.transactionHash' ${log.transactionHash} not found in 'block.transactions' ${block.hash}`,
694
+ `Detected inconsistent '${args.network.name}' RPC responses. 'log.transactionHash' ${log.transactionHash} not found in 'block.transactions' ${block.hash}`,
699
695
  );
700
696
  }
701
697
  }
@@ -708,7 +704,7 @@ export const createRealtimeSync = (
708
704
  ) {
709
705
  args.common.logger.debug({
710
706
  service: "realtime",
711
- msg: `Skipped fetching logs for '${args.network.name}' block ${hexToNumber(block.number)} due to bloom filter result`,
707
+ msg: `Skipped fetching '${args.network.name}' logs for block ${hexToNumber(block.number)} due to bloom filter result`,
712
708
  });
713
709
  }
714
710
 
@@ -782,7 +778,7 @@ export const createRealtimeSync = (
782
778
  if (log.transactionHash === zeroHash) {
783
779
  args.common.logger.warn({
784
780
  service: "sync",
785
- msg: `Detected log with empty transaction hash in block ${block.hash} at log index ${hexToNumber(log.logIndex)}. This is expected for some networks like ZKsync.`,
781
+ msg: `Detected '${args.network.name}' log with empty transaction hash in block ${block.hash} at log index ${hexToNumber(log.logIndex)}. This is expected for some networks like ZKsync.`,
786
782
  });
787
783
  } else {
788
784
  requiredTransactions.add(log.transactionHash);
@@ -908,11 +904,11 @@ export const createRealtimeSync = (
908
904
  * 4) Block is behind the last processed. This is a sign that
909
905
  * a reorg has occurred.
910
906
  */
911
- queue = createQueue({
912
- browser: false,
913
- concurrency: 1,
914
- initialStart: true,
915
- worker: async ({ block, ...rest }) => {
907
+ const processBlock = mutex(
908
+ async ({
909
+ block,
910
+ ...rest
911
+ }: BlockWithEventData & { endClock?: () => number }) => {
916
912
  const latestBlock = getLatestUnfinalizedBlock();
917
913
 
918
914
  // We already saw and handled this block. No-op.
@@ -931,7 +927,7 @@ export const createRealtimeSync = (
931
927
  if (hexToNumber(latestBlock.number) >= hexToNumber(block.number)) {
932
928
  await handleReorg(block);
933
929
 
934
- queue.clear();
930
+ processBlock.clear();
935
931
  return;
936
932
  }
937
933
 
@@ -961,24 +957,19 @@ export const createRealtimeSync = (
961
957
  service: "realtime",
962
958
  msg: `Fetched ${missingBlockRange.length} missing '${
963
959
  args.network.name
964
- }' blocks from ${hexToNumber(latestBlock.number) + 1} to ${Math.min(
960
+ }' blocks [${hexToNumber(latestBlock.number) + 1}, ${Math.min(
965
961
  hexToNumber(block.number),
966
962
  hexToNumber(latestBlock.number) + MAX_QUEUED_BLOCKS,
967
- )}`,
963
+ )}]`,
968
964
  });
969
965
 
970
- // This is needed to ensure proper `kill()` behavior. When the service
971
- // is killed, nothing should be added to the queue, or else `onIdle()`
972
- // will never resolve.
973
- if (isKilled) return;
974
-
975
- queue.clear();
966
+ processBlock.clear();
976
967
 
977
968
  for (const pendingBlock of pendingBlocks) {
978
- queue.add(pendingBlock);
969
+ processBlock(pendingBlock);
979
970
  }
980
971
 
981
- queue.add({ block, ...rest });
972
+ processBlock({ block, ...rest });
982
973
 
983
974
  return;
984
975
  }
@@ -986,7 +977,7 @@ export const createRealtimeSync = (
986
977
  // Check if a reorg occurred by validating the chain of block hashes.
987
978
  if (block.parentHash !== latestBlock.hash) {
988
979
  await handleReorg(block);
989
- queue.clear();
980
+ processBlock.clear();
990
981
  return;
991
982
  }
992
983
 
@@ -999,10 +990,12 @@ export const createRealtimeSync = (
999
990
 
1000
991
  return;
1001
992
  } catch (_error) {
1002
- if (isKilled) return;
1003
-
1004
993
  const error = _error as Error;
1005
994
 
995
+ if (args.common.shutdown.isKilled) {
996
+ throw new ShutdownError();
997
+ }
998
+
1006
999
  args.common.logger.warn({
1007
1000
  service: "realtime",
1008
1001
  msg: `Failed to process '${args.network.name}' block ${hexToNumber(block.number)}`,
@@ -1022,7 +1015,7 @@ export const createRealtimeSync = (
1022
1015
 
1023
1016
  // Remove all blocks from the queue. This protects against an
1024
1017
  // erroneous block causing a fatal error.
1025
- queue.clear();
1018
+ processBlock.clear();
1026
1019
 
1027
1020
  // After a certain number of attempts, emit a fatal error.
1028
1021
  if (++consecutiveErrors === ERROR_TIMEOUT.length) {
@@ -1036,7 +1029,7 @@ export const createRealtimeSync = (
1036
1029
  }
1037
1030
  }
1038
1031
  },
1039
- });
1032
+ );
1040
1033
 
1041
1034
  const enqueue = async () => {
1042
1035
  try {
@@ -1044,6 +1037,11 @@ export const createRealtimeSync = (
1044
1037
  blockTag: "latest",
1045
1038
  });
1046
1039
 
1040
+ args.common.logger.debug({
1041
+ service: "realtime",
1042
+ msg: `Received latest '${args.network.name}' block ${hexToNumber(block.number)}`,
1043
+ });
1044
+
1047
1045
  const latestBlock = getLatestUnfinalizedBlock();
1048
1046
 
1049
1047
  // We already saw and handled this block. No-op.
@@ -1062,12 +1060,14 @@ export const createRealtimeSync = (
1062
1060
 
1063
1061
  consecutiveErrors = 0;
1064
1062
 
1065
- return queue.add({ ...blockWithEventData, endClock });
1063
+ return processBlock({ ...blockWithEventData, endClock });
1066
1064
  } catch (_error) {
1067
- if (isKilled) return;
1068
-
1069
1065
  const error = _error as Error;
1070
1066
 
1067
+ if (args.common.shutdown.isKilled) {
1068
+ throw new ShutdownError();
1069
+ }
1070
+
1071
1071
  args.common.logger.warn({
1072
1072
  service: "realtime",
1073
1073
  msg: `Failed to fetch latest '${args.network.name}' block`,
@@ -1089,15 +1089,12 @@ export const createRealtimeSync = (
1089
1089
 
1090
1090
  interval = setInterval(enqueue, args.network.pollingInterval);
1091
1091
 
1092
+ args.common.shutdown.add(() => {
1093
+ clearInterval(interval);
1094
+ });
1095
+
1092
1096
  // Note: this is done just for testing.
1093
- return enqueue().then(() => queue);
1094
- },
1095
- async kill() {
1096
- clearInterval(interval);
1097
- isKilled = true;
1098
- queue?.pause();
1099
- queue?.clear();
1100
- await queue?.onIdle();
1097
+ return enqueue().then(() => processBlock);
1101
1098
  },
1102
1099
  get unfinalizedBlocks() {
1103
1100
  return unfinalizedBlocks;
@@ -1108,5 +1105,8 @@ export const createRealtimeSync = (
1108
1105
  get unfinalizedChildAddresses() {
1109
1106
  return unfinalizedChildAddresses;
1110
1107
  },
1108
+ async kill() {
1109
+ clearInterval(interval);
1110
+ },
1111
1111
  };
1112
1112
  };
@@ -8,9 +8,9 @@ import type {
8
8
  } from "@/types/sync.js";
9
9
  import {
10
10
  EVENT_TYPES,
11
+ MAX_CHECKPOINT,
12
+ ZERO_CHECKPOINT,
11
13
  encodeCheckpoint,
12
- maxCheckpoint,
13
- zeroCheckpoint,
14
14
  } from "@/utils/checkpoint.js";
15
15
  import { toLowerCase } from "@/utils/lowercase.js";
16
16
  import type { ColumnType, Insertable } from "kysely";
@@ -55,9 +55,9 @@ export const encodeBlock = ({
55
55
  blockTimestamp: hexToNumber(block.timestamp),
56
56
  chainId: BigInt(chainId),
57
57
  blockNumber: hexToBigInt(block.number),
58
- transactionIndex: maxCheckpoint.transactionIndex,
58
+ transactionIndex: MAX_CHECKPOINT.transactionIndex,
59
59
  eventType: EVENT_TYPES.blocks,
60
- eventIndex: zeroCheckpoint.eventIndex,
60
+ eventIndex: ZERO_CHECKPOINT.eventIndex,
61
61
  }),
62
62
  baseFeePerGas: block.baseFeePerGas
63
63
  ? hexToBigInt(block.baseFeePerGas)
@@ -183,7 +183,7 @@ export const encodeTransaction = ({
183
183
  blockNumber: hexToBigInt(transaction.blockNumber),
184
184
  transactionIndex: hexToBigInt(transaction.transactionIndex),
185
185
  eventType: EVENT_TYPES.transactions,
186
- eventIndex: zeroCheckpoint.eventIndex,
186
+ eventIndex: ZERO_CHECKPOINT.eventIndex,
187
187
  }),
188
188
  chainId,
189
189
  blockHash: transaction.blockHash,
@@ -118,10 +118,7 @@ export type SyncStore = {
118
118
  blocks: Pick<LightBlock, "number">[];
119
119
  chainId: number;
120
120
  }): Promise<void>;
121
- pruneByChain(args: {
122
- fromBlock: number;
123
- chainId: number;
124
- }): Promise<void>;
121
+ pruneByChain(args: { chainId: number }): Promise<void>;
125
122
  };
126
123
 
127
124
  const logFactorySQL = (
@@ -902,38 +899,23 @@ export const createSyncStore = ({
902
899
  .execute();
903
900
  },
904
901
  ),
905
- pruneByChain: async ({ fromBlock, chainId }) =>
902
+ pruneByChain: async ({ chainId }) =>
906
903
  database.wrap({ method: "pruneByChain", includeTraceLogs: true }, () =>
907
904
  database.qb.sync.transaction().execute(async (tx) => {
908
- await tx
909
- .deleteFrom("logs")
910
- .where("chainId", "=", chainId)
911
- .where("blockNumber", ">=", fromBlock.toString())
912
- .execute();
913
- await tx
914
- .deleteFrom("blocks")
915
- .where("chainId", "=", chainId)
916
- .where("number", ">=", fromBlock.toString())
917
- .execute();
905
+ await tx.deleteFrom("logs").where("chainId", "=", chainId).execute();
906
+ await tx.deleteFrom("blocks").where("chainId", "=", chainId).execute();
918
907
  await tx
919
908
  .deleteFrom("rpc_request_results")
920
909
  .where("chain_id", "=", chainId)
921
- .where("block_number", ">=", fromBlock.toString())
922
- .execute();
923
- await tx
924
- .deleteFrom("traces")
925
- .where("chainId", "=", chainId)
926
- .where("blockNumber", ">=", fromBlock.toString())
927
910
  .execute();
911
+ await tx.deleteFrom("traces").where("chainId", "=", chainId).execute();
928
912
  await tx
929
913
  .deleteFrom("transactions")
930
914
  .where("chainId", "=", chainId)
931
- .where("blockNumber", ">=", fromBlock.toString())
932
915
  .execute();
933
916
  await tx
934
917
  .deleteFrom("transactionReceipts")
935
918
  .where("chainId", "=", chainId)
936
- .where("blockNumber", ">=", fromBlock.toString())
937
919
  .execute();
938
920
  }),
939
921
  ),
package/src/ui/index.ts CHANGED
@@ -10,11 +10,7 @@ export function createUi({ common }: { common: Common }) {
10
10
  const ui = buildUiState();
11
11
  const { render, unmount } = setupInkApp(ui);
12
12
 
13
- let isKilled = false;
14
-
15
13
  const renderInterval = setInterval(async () => {
16
- if (isKilled) return;
17
-
18
14
  ui.sync = await getSyncProgress(common.metrics);
19
15
  ui.indexing = await getIndexingProgress(common.metrics);
20
16
  ui.app = await getAppProgress(common.metrics);
@@ -26,13 +22,8 @@ export function createUi({ common }: { common: Common }) {
26
22
  render(ui);
27
23
  }, 100);
28
24
 
29
- const kill = () => {
30
- isKilled = true;
25
+ common.shutdown.add(async () => {
31
26
  clearInterval(renderInterval);
32
27
  unmount();
33
- };
34
-
35
- return {
36
- kill,
37
- };
28
+ });
38
29
  }
@@ -105,7 +105,7 @@ export const decodeCheckpoint = (checkpoint: string): Checkpoint => {
105
105
  };
106
106
  };
107
107
 
108
- export const zeroCheckpoint: Checkpoint = {
108
+ export const ZERO_CHECKPOINT: Checkpoint = {
109
109
  blockTimestamp: 0,
110
110
  chainId: 0n,
111
111
  blockNumber: 0n,
@@ -114,7 +114,7 @@ export const zeroCheckpoint: Checkpoint = {
114
114
  eventIndex: 0n,
115
115
  };
116
116
 
117
- export const maxCheckpoint: Checkpoint = {
117
+ export const MAX_CHECKPOINT: Checkpoint = {
118
118
  blockTimestamp: 99999_99999,
119
119
  chainId: 9999_9999_9999_9999n,
120
120
  blockNumber: 9999_9999_9999_9999n,
@@ -123,6 +123,10 @@ export const maxCheckpoint: Checkpoint = {
123
123
  eventIndex: 9999_9999_9999_9999n,
124
124
  };
125
125
 
126
+ export const ZERO_CHECKPOINT_STRING = encodeCheckpoint(ZERO_CHECKPOINT);
127
+ export const MAX_CHECKPOINT_STRING = encodeCheckpoint(MAX_CHECKPOINT);
128
+
129
+ /**
126
130
  /**
127
131
  * Returns true if two checkpoints are equal.
128
132
  */
@@ -154,4 +158,14 @@ export const checkpointMin = (...checkpoints: Checkpoint[]) =>
154
158
  return isCheckpointGreaterThan(min, checkpoint) ? checkpoint : min;
155
159
  });
156
160
 
157
- export const LATEST = encodeCheckpoint(maxCheckpoint);
161
+ export const LATEST = MAX_CHECKPOINT_STRING;
162
+
163
+ /** Compute the minimum checkpoint, filtering out undefined */
164
+ export const min = (...checkpoints: (string | undefined)[]) => {
165
+ return checkpoints.reduce((acc, cur) => {
166
+ if (cur === undefined) return acc;
167
+ if (acc === undefined) return cur;
168
+ if (acc < cur) return acc;
169
+ return cur;
170
+ })!;
171
+ };
@@ -0,0 +1,7 @@
1
+ export const chunk = <T>(array: T[], size: number): T[][] => {
2
+ const chunks = [];
3
+ for (let i = 0; i < array.length; i += size) {
4
+ chunks.push(array.slice(i, i + size));
5
+ }
6
+ return chunks;
7
+ };
@@ -1,5 +1,11 @@
1
1
  import { promiseWithResolvers } from "@ponder/common";
2
2
 
3
+ /**
4
+ * Merges multiple async generators into a single async generator.
5
+ *
6
+ * @param generators - The generators to merge.
7
+ * @returns A single async generator that yields results from all input generators.
8
+ */
3
9
  export async function* mergeAsyncGenerators<T>(
4
10
  generators: AsyncGenerator<T>[],
5
11
  ): AsyncGenerator<T> {
@@ -25,3 +31,63 @@ export async function* mergeAsyncGenerators<T>(
25
31
  }
26
32
  }
27
33
  }
34
+
35
+ /**
36
+ * Buffers the results of an async generator.
37
+ *
38
+ * @param generator - The generator to buffer.
39
+ * @param size - The size of the buffer.
40
+ * @returns An async generator that yields results from the input generator.
41
+ */
42
+ export async function* bufferAsyncGenerator<T>(
43
+ generator: AsyncGenerator<T>,
44
+ size: number,
45
+ ): AsyncGenerator<T> {
46
+ const buffer: T[] = [];
47
+ let done = false;
48
+
49
+ let pwr1 = promiseWithResolvers<void>();
50
+ let pwr2 = promiseWithResolvers<void>();
51
+
52
+ (async () => {
53
+ for await (const result of generator) {
54
+ buffer.push(result);
55
+
56
+ pwr1.resolve();
57
+
58
+ if (buffer.length > size) await pwr2.promise;
59
+ pwr2 = promiseWithResolvers<void>();
60
+ }
61
+ done = true;
62
+ pwr1.resolve();
63
+ })();
64
+
65
+ while (done === false || buffer.length > 0) {
66
+ if (buffer.length > 0) {
67
+ pwr2.resolve();
68
+
69
+ yield buffer.shift()!;
70
+ } else {
71
+ await pwr1.promise;
72
+ pwr1 = promiseWithResolvers<void>();
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Drains an async generator into an array.
79
+ *
80
+ * @param asyncGenerator - The async generator to drain.
81
+ * @returns An array of results from the input generator.
82
+ */
83
+ export async function drainAsyncGenerator<T>(
84
+ asyncGenerator: AsyncGenerator<T>,
85
+ ): Promise<T[]> {
86
+ const result: T[] = [];
87
+
88
+ for await (const events of asyncGenerator) {
89
+ result.push(events);
90
+ }
91
+
92
+ return result;
93
+ }