@effectstream/sync 0.100.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 (45) hide show
  1. package/package.json +29 -0
  2. package/src/mod.ts +3 -0
  3. package/src/sync-protocols/avail/AvailClient.ts +120 -0
  4. package/src/sync-protocols/avail/fetcher.ts +174 -0
  5. package/src/sync-protocols/avail/state.ts +134 -0
  6. package/src/sync-protocols/avail/types.ts +92 -0
  7. package/src/sync-protocols/base/fetcher.ts +43 -0
  8. package/src/sync-protocols/base/page.ts +89 -0
  9. package/src/sync-protocols/base/primitive.ts +24 -0
  10. package/src/sync-protocols/base/state.ts +143 -0
  11. package/src/sync-protocols/bitcoin/fetcher.ts +549 -0
  12. package/src/sync-protocols/bitcoin/state.ts +144 -0
  13. package/src/sync-protocols/bitcoin/types.ts +92 -0
  14. package/src/sync-protocols/celestia/CelestiaClient.ts +139 -0
  15. package/src/sync-protocols/celestia/fetcher.ts +165 -0
  16. package/src/sync-protocols/celestia/state.ts +131 -0
  17. package/src/sync-protocols/celestia/types.ts +46 -0
  18. package/src/sync-protocols/common/page-helpers.ts +65 -0
  19. package/src/sync-protocols/common/root.ts +35 -0
  20. package/src/sync-protocols/common/utils.ts +29 -0
  21. package/src/sync-protocols/evm/fetcher.ts +290 -0
  22. package/src/sync-protocols/evm/state.ts +122 -0
  23. package/src/sync-protocols/evm/types.ts +53 -0
  24. package/src/sync-protocols/midnight/MidnightClient.ts +210 -0
  25. package/src/sync-protocols/midnight/fetcher.ts +252 -0
  26. package/src/sync-protocols/midnight/state.ts +147 -0
  27. package/src/sync-protocols/midnight/types.ts +128 -0
  28. package/src/sync-protocols/midnight/zswap-decoder.ts +123 -0
  29. package/src/sync-protocols/mod.ts +29 -0
  30. package/src/sync-protocols/near/NearClient.ts +178 -0
  31. package/src/sync-protocols/near/fetcher.ts +274 -0
  32. package/src/sync-protocols/near/state.ts +130 -0
  33. package/src/sync-protocols/near/types.ts +46 -0
  34. package/src/sync-protocols/ntp/fetcher.ts +199 -0
  35. package/src/sync-protocols/ntp/state.ts +118 -0
  36. package/src/sync-protocols/ntp/types.ts +40 -0
  37. package/src/sync-protocols/orchestration/merge.ts +215 -0
  38. package/src/sync-protocols/orchestration/sync.ts +74 -0
  39. package/src/sync-protocols/types.ts +32 -0
  40. package/src/sync-protocols/utxorpc/BufferedRpc.ts +169 -0
  41. package/src/sync-protocols/utxorpc/fetcher.ts +116 -0
  42. package/src/sync-protocols/utxorpc/state.ts +186 -0
  43. package/src/sync-protocols/utxorpc/types.ts +53 -0
  44. package/src/sync-protocols/utxorpc/utils.ts +80 -0
  45. package/src/syncProtocolFactory.ts +149 -0
@@ -0,0 +1,65 @@
1
+ import type { Operation } from "effection";
2
+ import type { PageRange } from "../base/page.ts";
3
+ import { narrowResult } from "@effectstream/utils";
4
+ import { ComponentNames, log, SeverityNumber } from "@effectstream/log";
5
+ import type { PaginatedSyncProtocols } from "../types.ts";
6
+ import type { PageTypeOf } from "../base/state.ts";
7
+
8
+ type ConfigSubset<Page> = {
9
+ name: string;
10
+ /**
11
+ * inclusive
12
+ */
13
+ startPage: Page;
14
+ };
15
+
16
+ export type PageSyncRange<Page> = PageRange<Page> & { isPresync: boolean };
17
+
18
+ export function* genInputRange<SyncProtocol extends PaginatedSyncProtocols>(
19
+ state: SyncProtocol,
20
+ genesisPage: PageTypeOf<SyncProtocol>,
21
+ config: ConfigSubset<PageTypeOf<SyncProtocol>>,
22
+ namespace: string[],
23
+ ): Operation<undefined | PageSyncRange<PageTypeOf<SyncProtocol>>> {
24
+ const nextPage = state.lastPage == null
25
+ ? genesisPage
26
+ : state.fetcher.nextInterval(state.lastPage.own).from;
27
+ // TODO This will always be false as the genesis page is the startPage
28
+ const isPresync = state.pageRelation.compare(nextPage, config.startPage) < 0;
29
+
30
+ const maxPage = isPresync
31
+ ? state.fetcher.previousInterval(config.startPage).to
32
+ : yield* state.fetcher.getLatestPage(state.lastPage?.own);
33
+ const clampedMax = state.pageRelation.min(
34
+ maxPage,
35
+ state.fetcher.intervalFromStart(nextPage).to,
36
+ );
37
+ // this can happen if the genesis is not the exact start of the system
38
+ // and (esp. on local networks) the system hasn't reached the genesis page yet
39
+ if (state.pageRelation.compare(nextPage, clampedMax) > 0) {
40
+ return undefined;
41
+ }
42
+
43
+ const pageRange = state.pageRelation.equals(clampedMax, nextPage)
44
+ ? `[${nextPage}]`
45
+ : `[${nextPage}, ${clampedMax}]`;
46
+ log.remote(
47
+ ComponentNames.EFFECTSTREAM_SYNC,
48
+ namespace,
49
+ SeverityNumber.INFO,
50
+ (log) => log(pageRange),
51
+ );
52
+ return {
53
+ from: narrowResult<
54
+ PageTypeOf<PaginatedSyncProtocols>,
55
+ PageTypeOf<PaginatedSyncProtocols>,
56
+ PageTypeOf<SyncProtocol>
57
+ >(nextPage),
58
+ to: narrowResult<
59
+ PageTypeOf<PaginatedSyncProtocols>,
60
+ PageTypeOf<PaginatedSyncProtocols>,
61
+ PageTypeOf<SyncProtocol>
62
+ >(clampedMax),
63
+ isPresync,
64
+ };
65
+ }
@@ -0,0 +1,35 @@
1
+ import type {
2
+ BlockHash,
3
+ BlockNumber,
4
+ EffectstreamBlockNumber,
5
+ TimestampMs,
6
+ } from "@effectstream/utils";
7
+ import type { PageRelation } from "../base/page.ts";
8
+ import type {
9
+ ConfigSyncProtocolType,
10
+ FlattenSyncProtocolIOFor,
11
+ } from "@effectstream/config";
12
+
13
+ export type ChainPage = TimestampMs;
14
+ export type ChainBlock = {
15
+ blockNumber: EffectstreamBlockNumber;
16
+ timestamp: TimestampMs;
17
+ blockInfo: {
18
+ protocol_name: string;
19
+ block_number: BlockNumber;
20
+ blockHash: BlockHash;
21
+ }[];
22
+ primitives: (
23
+ & FlattenSyncProtocolIOFor<
24
+ ConfigSyncProtocolType
25
+ >
26
+ & { source: string }
27
+ )[];
28
+ };
29
+
30
+ export const chainPageRelation: PageRelation<ChainPage> = {
31
+ compare: (p1, p2) => p1 - p2,
32
+ equals: (p1, p2) => p1 === p2,
33
+ min: (p1, p2) => (p1 < p2 ? p1 : p2),
34
+ max: (p1, p2) => (p1 > p2 ? p1 : p2),
35
+ };
@@ -0,0 +1,29 @@
1
+ import type {
2
+ AbsoluteSlotNumber,
3
+ BlockNumber,
4
+ IntervalMs,
5
+ TimestampMs,
6
+ } from "@effectstream/utils";
7
+ import type { PageRelation } from "../base/page.ts";
8
+
9
+ export function applyDelay(
10
+ timestamp: TimestampMs,
11
+ delayMs: IntervalMs,
12
+ ): TimestampMs {
13
+ // note: plus ("+") here makes it look like this chain is further along than it is
14
+ // this is exactly what we want to make old blocks get merged into recent root data
15
+ return Math.max(0, timestamp + delayMs);
16
+ }
17
+
18
+ export const blockNumberRelation: PageRelation<BlockNumber> = {
19
+ compare: (p1, p2) => p1 - p2,
20
+ equals: (p1, p2) => p1 === p2,
21
+ min: (p1, p2) => (p1 < p2 ? p1 : p2),
22
+ max: (p1, p2) => (p1 > p2 ? p1 : p2),
23
+ };
24
+ export const absSlotRelation: PageRelation<AbsoluteSlotNumber> = {
25
+ compare: (p1, p2) => p1 - p2,
26
+ equals: (p1, p2) => p1 === p2,
27
+ min: (p1, p2) => (p1 < p2 ? p1 : p2),
28
+ max: (p1, p2) => (p1 > p2 ? p1 : p2),
29
+ };
@@ -0,0 +1,290 @@
1
+ import {
2
+ BaseDataFetcher,
3
+ type DataFetched,
4
+ type PaginatedFetcher,
5
+ } from "../base/fetcher.ts";
6
+ import type { RootOutput, RootPage } from "../types.ts";
7
+ import type { AbiEvent, Chain, GetBlockReturnType, PublicClient } from "viem";
8
+ import type { Operation } from "effection";
9
+ import { all, call } from "effection";
10
+ import {
11
+ bound,
12
+ type EvmBlockHash,
13
+ type EvmRpcPageJson,
14
+ keysOf,
15
+ } from "@effectstream/utils";
16
+ import { blockNumberRelation } from "../common/utils.ts";
17
+ import type {
18
+ ConfigType,
19
+ Input,
20
+ Output,
21
+ Page,
22
+ PrimitiveEntryType,
23
+ PrimitiveType,
24
+ } from "./types.ts";
25
+ import { PageSchema } from "./types.ts";
26
+ import {
27
+ fetchNewestPage,
28
+ genImmediatePageRequests,
29
+ type PageRange,
30
+ type PageRequest,
31
+ } from "../base/page.ts";
32
+ import type { PrimitiveFetcher } from "../base/primitive.ts";
33
+ import type { RootConversion } from "../base/state.ts";
34
+ import type {
35
+ ConfigNetworkType,
36
+ PrimitiveEntry,
37
+ SyncProtocolWithNetwork,
38
+ } from "@effectstream/config";
39
+ import type { ConfigSyncProtocolType } from "@effectstream/config";
40
+ import { Value } from "@sinclair/typebox/value";
41
+
42
+ export class EvmFetcher
43
+ extends BaseDataFetcher<Input, Output, RootOutput, Page, RootPage>
44
+ implements
45
+ PrimitiveFetcher<
46
+ Input,
47
+ Page,
48
+ GetBlockReturnType<Chain>,
49
+ PrimitiveType,
50
+ ConfigSyncProtocolType.EVM_RPC_PARALLEL
51
+ >,
52
+ PaginatedFetcher<Page> {
53
+ constructor(
54
+ readonly config: ConfigType,
55
+ readonly client: PublicClient<any, Chain, any, any>,
56
+ ) {
57
+ super(config.syncProtocol.name);
58
+ }
59
+
60
+ @bound
61
+ override *readData(
62
+ data: Input,
63
+ rootConversion: RootConversion<Output, RootOutput, RootPage>,
64
+ ): Operation<DataFetched<Output, Page, RootPage>> {
65
+ const allPages: Page[] = [];
66
+ for (let p = data.from; p <= data.to; p++) allPages.push(p);
67
+ const pageFetcher = genImmediatePageRequests(
68
+ allPages,
69
+ (page) => this.client.getBlock({ blockNumber: BigInt(page) }),
70
+ );
71
+
72
+ // TODO: if we expect multiple primitives per block
73
+ // it can, depending on the chain, be faster to just download the full block and parse it locally
74
+ const primitives = yield* this.readPrimitives(
75
+ data,
76
+ pageFetcher,
77
+ this.config.primitives,
78
+ );
79
+ const groupedByPage = this.groupByPage(primitives);
80
+
81
+ const [output, lastPage] = yield* all(
82
+ [
83
+ all(
84
+ keysOf(groupedByPage).map(function* (pageJson) {
85
+ const page = Value.Encode(PageSchema, pageJson);
86
+ const raw = yield* call(() => pageFetcher(page));
87
+ const blockHashes = [raw.hash] as EvmBlockHash[];
88
+ return {
89
+ raw,
90
+ primitives: groupedByPage[pageJson],
91
+ blockHashes,
92
+ };
93
+ }),
94
+ ),
95
+ // we always need to get the last page in the parallel case
96
+ // so we can use it for pagination
97
+ call(() => pageFetcher(Number(data.to))),
98
+ ],
99
+ );
100
+
101
+ // Build an output with all page info, as we need all the hashes to build the effectstream-block-hash
102
+ // All blocks are already pre-fetched in parallel by genImmediatePageRequests above
103
+ const allOutputs: Output[] = [];
104
+ for (let page = data.from; page <= data.to; page++) {
105
+ const _output = output.find((o) => o.raw.number === BigInt(page));
106
+ if (_output) {
107
+ allOutputs.push(_output);
108
+ } else {
109
+ const raw = yield* call(() => pageFetcher(page));
110
+ allOutputs.push({
111
+ raw,
112
+ primitives: [],
113
+ blockHashes: [raw.hash] as EvmBlockHash[],
114
+ });
115
+ }
116
+ }
117
+
118
+ return {
119
+ output: allOutputs.map((o) => ({
120
+ output: o,
121
+ cleanup: () => {}, // no cleanup required
122
+ })),
123
+ lastPage: {
124
+ own: Number(data.to),
125
+ ownBlockNumber: Number(data.to),
126
+ root: rootConversion.toRootPage({
127
+ blockHashes: [lastPage.hash] as EvmBlockHash[],
128
+ primitives: [], // unused in toRootPage
129
+ raw: lastPage,
130
+ }),
131
+ },
132
+ };
133
+ }
134
+
135
+ @bound
136
+ groupByPage(
137
+ primitives: PrimitiveType[],
138
+ ): Record<EvmRpcPageJson, PrimitiveType[]> {
139
+ return primitives.reduce((acc, primitive) => {
140
+ const key = Value.Decode(
141
+ PageSchema,
142
+ Number(primitive.syncProtocol.blockNumber),
143
+ );
144
+ (acc[key] ??= []).push(
145
+ primitive,
146
+ );
147
+ return acc;
148
+ }, {} as Record<EvmRpcPageJson, PrimitiveType[]>);
149
+ }
150
+
151
+ fetchLogsAndExtractPrimitiveData = function* (
152
+ fromBlock: bigint,
153
+ toBlock: bigint,
154
+ client: PublicClient<any, Chain, any, any>,
155
+ primitiveEntry: PrimitiveEntryType,
156
+ abi: AbiEvent,
157
+ pageRequest: PageRequest<Page, GetBlockReturnType<Chain>>,
158
+ ): Operation<
159
+ PrimitiveType[]
160
+ > {
161
+ // Fetch range of event-logs
162
+ const logs = yield* call(() =>
163
+ client.getLogs({
164
+ address: primitiveEntry.primitive.contractAddress as `0x${string}`,
165
+ event: abi,
166
+ fromBlock,
167
+ toBlock,
168
+ strict: true,
169
+ })
170
+ );
171
+
172
+ // Create PrimitiveType for each event.
173
+ const primitiveResponses: PrimitiveType[] = [];
174
+ for (const log of logs) {
175
+ // const block = yield* call(() => pageRequest(Number(log.blockNumber)));
176
+ const args = (log as unknown as {
177
+ args: Record<string, any>;
178
+ }).args;
179
+ const primitiveResponse: PrimitiveType = {
180
+ syncProtocol: {
181
+ name: primitiveEntry.syncProtocol,
182
+ blockNumber: Number(log.blockNumber),
183
+ transactionHash: log.transactionHash,
184
+ transactionIndex: log.transactionIndex,
185
+ contractAddress: log.address,
186
+ logIndex: log.logIndex,
187
+ },
188
+ primitive: primitiveEntry.primitive.name,
189
+ output: {
190
+ payloadType: abi.name,
191
+ payload: args,
192
+ },
193
+ };
194
+
195
+ primitiveResponses.push(primitiveResponse);
196
+ }
197
+
198
+ return primitiveResponses;
199
+ };
200
+
201
+ @bound
202
+ *readPrimitives(
203
+ data: Input,
204
+ pageRequest: PageRequest<Page, GetBlockReturnType<Chain>>,
205
+ primitiveEntries: Extract<
206
+ PrimitiveEntry,
207
+ { syncProtocol: ConfigSyncProtocolType.EVM_RPC_PARALLEL }
208
+ >[],
209
+ ): Operation<PrimitiveType[]> {
210
+ const client = this.client;
211
+ const allOperations: Operation<PrimitiveType[]>[] = [];
212
+
213
+ const fromBlock = yield* call(() => pageRequest(data.from));
214
+ const toBlock = yield* call(() => pageRequest(data.to));
215
+ for (const primitive of primitiveEntries) {
216
+ if (Array.isArray(primitive.primitive.abi)) {
217
+ const abis = primitive.primitive.abi as AbiEvent[];
218
+ for (const abi of abis) {
219
+ allOperations.push(
220
+ this.fetchLogsAndExtractPrimitiveData(
221
+ fromBlock.number,
222
+ toBlock.number,
223
+ client,
224
+ primitive,
225
+ abi,
226
+ pageRequest,
227
+ ),
228
+ );
229
+ }
230
+ } else {
231
+ allOperations.push(
232
+ this.fetchLogsAndExtractPrimitiveData(
233
+ fromBlock.number,
234
+ toBlock.number,
235
+ client,
236
+ primitive,
237
+ primitive.primitive.abi as AbiEvent,
238
+ pageRequest,
239
+ ),
240
+ );
241
+ }
242
+ }
243
+
244
+ return (yield* all(allOperations)).flat();
245
+ }
246
+
247
+ @bound
248
+ *getLatestPage(
249
+ knownLastPage: undefined | Page,
250
+ ): Operation<Page> {
251
+ return yield* fetchNewestPage<Page>(
252
+ blockNumberRelation,
253
+ // override Viem's cache logic with out own
254
+ () =>
255
+ call(() =>
256
+ this.client.getBlockNumber({ cacheTime: 0 }).then(Number).then(
257
+ (height) => {
258
+ const config = this.config.syncProtocol;
259
+ if ("confirmationDepth" in config) {
260
+ return height - config.confirmationDepth;
261
+ }
262
+ return height;
263
+ },
264
+ )
265
+ ),
266
+ knownLastPage,
267
+ this.config.syncProtocol.pollingInterval,
268
+ );
269
+ }
270
+
271
+ @bound
272
+ previousInterval(nextIntervalStart: Page): PageRange<Page> {
273
+ return this.intervalFromStart(
274
+ nextIntervalStart - this.config.syncProtocol.stepSize - 1,
275
+ );
276
+ }
277
+
278
+ @bound
279
+ nextInterval(prevIntervalEnd: Page): PageRange<Page> {
280
+ return this.intervalFromStart(prevIntervalEnd + 1);
281
+ }
282
+
283
+ @bound
284
+ intervalFromStart(start: Page): PageRange<Page> {
285
+ return {
286
+ from: start,
287
+ to: start + this.config.syncProtocol.stepSize,
288
+ };
289
+ }
290
+ }
@@ -0,0 +1,122 @@
1
+ import { applyDelay, blockNumberRelation } from "../common/utils.ts";
2
+ import type { Operation } from "effection";
3
+ import { call } from "effection";
4
+ import { getPage } from "@effectstream/db";
5
+ import type { PoolClient } from "pg";
6
+ import { bound, type EvmBlockNumber } from "@effectstream/utils";
7
+ import { type LastPage, SyncState } from "../base/state.ts";
8
+ import type { RootOutput, RootPage } from "../types.ts";
9
+ import type { EvmFetcher } from "./fetcher.ts";
10
+ import { genInputRange } from "../common/page-helpers.ts";
11
+ import type { Input, Output, Page } from "./types.ts";
12
+ import { toMsTimestamp } from "./types.ts";
13
+ import type { ConfigNetworkType, SyncProtocolWithNetwork } from "@effectstream/config";
14
+
15
+ export class EvmSyncState extends SyncState<
16
+ Input,
17
+ Output,
18
+ Page,
19
+ RootOutput,
20
+ RootPage,
21
+ EvmFetcher
22
+ > {
23
+ constructor(
24
+ lastPage: LastPage<Page, RootPage>,
25
+ readonly config: Extract<
26
+ SyncProtocolWithNetwork,
27
+ { networkType: ConfigNetworkType.EVM }
28
+ >,
29
+ fetcher: EvmFetcher,
30
+ dbConn: PoolClient,
31
+ ) {
32
+ super(
33
+ config.syncProtocol.name,
34
+ lastPage,
35
+ fetcher,
36
+ blockNumberRelation,
37
+ dbConn,
38
+ );
39
+ }
40
+
41
+ @bound
42
+ override toRootOutput(data: Output): RootOutput {
43
+ throw new Error("Only main chains create root outputs");
44
+ }
45
+
46
+ @bound
47
+ override toRootPage(data: Output): RootPage {
48
+ return applyDelay(
49
+ toMsTimestamp(data.raw.timestamp),
50
+ this.config.syncProtocol.delayMs,
51
+ );
52
+ }
53
+
54
+ @bound
55
+ override *stateToInput(): Operation<Input | undefined> {
56
+ return yield* genInputRange(
57
+ this as EvmSyncState,
58
+ this.config.syncProtocol.startBlockHeight as EvmBlockNumber,
59
+ {
60
+ name: this.config.syncProtocol.name,
61
+ startPage: this.config.syncProtocol.startBlockHeight as EvmBlockNumber,
62
+ },
63
+ this.getNamespace(),
64
+ );
65
+ }
66
+
67
+ @bound
68
+ override toPage(
69
+ input: Input,
70
+ data: Output[],
71
+ ): Page {
72
+ return Number(input.to);
73
+ }
74
+
75
+ @bound
76
+ override mergeDatum(
77
+ ourOutput: Output,
78
+ rootOutput: RootOutput,
79
+ ): void {
80
+ const primitives = ourOutput.primitives.map((p) => ({
81
+ ...p,
82
+ source: this.config.syncProtocol.name,
83
+ }));
84
+ const blockInfo = ourOutput.blockHashes.map((h) => ({
85
+ protocol_name: this.config.syncProtocol.name,
86
+ block_number: Number(ourOutput.raw.number),
87
+ blockHash: h,
88
+ }));
89
+ rootOutput.primitives.push(...primitives);
90
+ rootOutput.blockInfo.push(...blockInfo);
91
+ }
92
+
93
+ @bound
94
+ override getNamespace(): string[] {
95
+ return [this.config.network.name, this.config.syncProtocol.name];
96
+ }
97
+
98
+ static *restoreState(
99
+ dbConn: PoolClient,
100
+ config: Extract<
101
+ SyncProtocolWithNetwork,
102
+ { networkType: ConfigNetworkType.EVM }
103
+ >,
104
+ fetcher: EvmFetcher,
105
+ ): Operation<EvmSyncState> {
106
+ // TODO: move this DB query into page-helpers?
107
+ const [result] = yield* call(async () =>
108
+ await getPage.run({
109
+ protocol_name: config.syncProtocol.name,
110
+ }, dbConn)
111
+ );
112
+ const page: any = result
113
+ ? result.page as unknown as LastPage<Page, RootPage>
114
+ : undefined;
115
+ return new EvmSyncState(
116
+ page,
117
+ config,
118
+ fetcher,
119
+ dbConn,
120
+ );
121
+ }
122
+ }
@@ -0,0 +1,53 @@
1
+ import type {
2
+ BlockNumber,
3
+ EvmBlockHash,
4
+ EvmRpcPageJson,
5
+ TimestampMs,
6
+ } from "@effectstream/utils";
7
+ import { TypeboxHelpers } from "@effectstream/utils";
8
+ import type { Chain, GetBlockReturnType } from "viem";
9
+ import type { PageSyncRange } from "../common/page-helpers.ts";
10
+ import { Type } from "@sinclair/typebox";
11
+ import type {
12
+ ConfigNetworkType,
13
+ ConfigSyncProtocolType,
14
+ FlattenSyncProtocolIOFor,
15
+ PrimitiveEntry,
16
+ SyncProtocolWithNetwork,
17
+ } from "@effectstream/config";
18
+
19
+ export type Page = BlockNumber;
20
+ const PageJsonSchema = Type.Unsafe<
21
+ EvmRpcPageJson
22
+ >(Type.String());
23
+ export const PageSchema = TypeboxHelpers.SerializeObjAsJson<
24
+ Page,
25
+ typeof PageJsonSchema
26
+ >();
27
+
28
+ export type ConfigType = Extract<
29
+ SyncProtocolWithNetwork,
30
+ { networkType: ConfigNetworkType.EVM }
31
+ >;
32
+
33
+ export type PrimitiveEntryType = Extract<
34
+ PrimitiveEntry,
35
+ { syncProtocol: ConfigSyncProtocolType.EVM_RPC_PARALLEL }
36
+ >;
37
+
38
+ export type PrimitiveType = FlattenSyncProtocolIOFor<
39
+ ConfigSyncProtocolType.EVM_RPC_PARALLEL
40
+ >;
41
+ export type Input = PageSyncRange<Page>;
42
+ export type Output = {
43
+ raw: GetBlockReturnType<Chain>;
44
+ primitives: PrimitiveType[];
45
+ blockHashes: EvmBlockHash[];
46
+ };
47
+
48
+ /**
49
+ * recall: EVM blocks use second resolution, but we want ms resolution
50
+ */
51
+ export function toMsTimestamp(timestamp: bigint): TimestampMs {
52
+ return Number(timestamp) * 1000;
53
+ }