@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,274 @@
1
+ import {
2
+ ConfigSyncProtocolType,
3
+ type PrimitiveEntry,
4
+ } from "@effectstream/config";
5
+ import { BaseDataFetcher } from "../base/fetcher.ts";
6
+ import type { DataFetched } from "../base/fetcher.ts";
7
+ import type {
8
+ LastPage,
9
+ OutputAndCleanup,
10
+ RootConversion,
11
+ } from "../base/state.ts";
12
+ import type { RootOutput, RootPage } from "../types.ts";
13
+ import type {
14
+ ConfigType,
15
+ Input,
16
+ Output,
17
+ Page,
18
+ PrimitiveType,
19
+ } from "./types.ts";
20
+ import {
21
+ NearClient,
22
+ parseNep297Log,
23
+ type Nep297Event,
24
+ type NearExecutionOutcomeWithId,
25
+ } from "./NearClient.ts";
26
+ import { call, type Operation } from "effection";
27
+ import { bound } from "@effectstream/utils";
28
+
29
+ export class NearFetcher extends BaseDataFetcher<
30
+ Input,
31
+ Output,
32
+ RootOutput,
33
+ Page,
34
+ RootPage
35
+ > {
36
+ readonly client: NearClient;
37
+
38
+ constructor(
39
+ readonly config: ConfigType,
40
+ ) {
41
+ super(config.syncProtocol.name);
42
+ this.client = new NearClient(config.network.rpcUrl);
43
+ }
44
+
45
+ @bound
46
+ override *readData(
47
+ data: Input,
48
+ rootConversion: RootConversion<Output, RootOutput, RootPage>,
49
+ lastPage: LastPage<Page, RootPage> | undefined,
50
+ ): Operation<DataFetched<Output, Page, RootPage>> {
51
+ const outputs: OutputAndCleanup<Output>[] = [];
52
+
53
+ console.log(
54
+ `[NEAR] Fetching blocks from ${data.from} to ${data.to}.${
55
+ data.isPresync ? " [presync]" : ""
56
+ }`,
57
+ );
58
+
59
+ for (let height = data.from; height <= data.to; height++) {
60
+ let block;
61
+ try {
62
+ block = yield* call(() =>
63
+ this.client.getBlock({ block_id: height })
64
+ );
65
+ } catch (_e) {
66
+ // Likely reached the chain tip; stop here
67
+ break;
68
+ }
69
+
70
+ const blockHash = block.header.hash;
71
+ // NEAR timestamps are in nanoseconds — convert to milliseconds
72
+ const timestampMs = Math.floor(
73
+ Number(block.header.timestamp_nanosec ?? block.header.timestamp) / 1_000_000,
74
+ );
75
+
76
+ const primitives = yield* this.readPrimitives(
77
+ height,
78
+ blockHash,
79
+ block.chunks,
80
+ this.config.primitives,
81
+ );
82
+
83
+ outputs.push({
84
+ output: {
85
+ timestamp: timestampMs,
86
+ height,
87
+ hash: blockHash,
88
+ primitives,
89
+ },
90
+ cleanup: () => {},
91
+ });
92
+ }
93
+
94
+ if (outputs.length === 0) {
95
+ if (!lastPage) {
96
+ throw new Error(
97
+ `[NEAR] Could not fetch any blocks from ${data.from} to ${data.to} and no previous page was found.`,
98
+ );
99
+ }
100
+ return {
101
+ output: [],
102
+ lastPage,
103
+ };
104
+ }
105
+
106
+ const lastOutput = outputs[outputs.length - 1].output;
107
+ return {
108
+ output: outputs,
109
+ lastPage: {
110
+ ownBlockNumber: lastOutput.height,
111
+ own: {
112
+ height: lastOutput.height,
113
+ hash: lastOutput.hash,
114
+ },
115
+ root: rootConversion.toRootPage(lastOutput),
116
+ },
117
+ };
118
+ }
119
+
120
+ @bound
121
+ *readPrimitives(
122
+ height: number,
123
+ blockHash: string,
124
+ chunkHeaders: { chunk_hash: string; shard_id: number }[],
125
+ primitiveEntries: Extract<
126
+ PrimitiveEntry,
127
+ { syncProtocol: ConfigSyncProtocolType.NEAR_RPC_PARALLEL }
128
+ >[],
129
+ ): Operation<PrimitiveType[]> {
130
+ if (primitiveEntries.length === 0) return [];
131
+
132
+ const allPrimitives: PrimitiveType[] = [];
133
+
134
+ // Fetch each chunk and extract events from transactions
135
+ for (const chunkHeader of chunkHeaders) {
136
+ let chunk;
137
+ try {
138
+ chunk = yield* call(() =>
139
+ this.client.getChunk(chunkHeader.chunk_hash)
140
+ );
141
+ } catch (_e) {
142
+ // Chunk may be empty or unavailable
143
+ continue;
144
+ }
145
+
146
+ for (const tx of chunk.transactions) {
147
+ let txStatus;
148
+ try {
149
+ txStatus = yield* call(() =>
150
+ this.client.getTxStatus(tx.hash, tx.signer_id)
151
+ );
152
+ } catch (_e) {
153
+ continue;
154
+ }
155
+
156
+ // Collect all execution outcomes (transaction outcome + receipt outcomes)
157
+ const allOutcomes: NearExecutionOutcomeWithId[] = [
158
+ txStatus.transaction_outcome,
159
+ ...txStatus.receipts_outcome,
160
+ ];
161
+
162
+ // Event-based primitives (Generic, Intent, NEP-141/171/245) match
163
+ // NEP-297 logs emitted during execution.
164
+ for (const outcomeWithId of allOutcomes) {
165
+ const { outcome } = outcomeWithId;
166
+
167
+ for (const log of outcome.logs) {
168
+ const event = parseNep297Log(log);
169
+ if (!event) continue;
170
+
171
+ for (const entry of primitiveEntries) {
172
+ const prim = entry.primitive;
173
+ // AccountWatch primitives have no event metadata; skip them here.
174
+ if (prim.eventStandard == null) continue;
175
+ if (!this.matchesPrimitive(outcome.executor_id, event, prim)) {
176
+ continue;
177
+ }
178
+
179
+ for (let i = 0; i < event.data.length; i++) {
180
+ allPrimitives.push({
181
+ syncProtocol: {
182
+ name: entry.syncProtocol,
183
+ blockNumber: height,
184
+ transactionHash: tx.hash,
185
+ contractAddress: outcome.executor_id,
186
+ logIndex: i,
187
+ },
188
+ primitive: prim.name,
189
+ output: {
190
+ payloadType: `${event.standard}:${event.event}`,
191
+ payload: {
192
+ ...event.data[i] as Record<string, unknown>,
193
+ _standard: event.standard,
194
+ _version: event.version,
195
+ _event: event.event,
196
+ },
197
+ },
198
+ });
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ // AccountWatch primitives capture raw execution-outcome data for any
205
+ // FunctionCall whose receiver matches the watched contractId, whether
206
+ // or not a NEP-297 event was logged. This is the non-event path.
207
+ const txStatusKind = txStatus.transaction_outcome.outcome.status;
208
+ const txSucceeded =
209
+ (txStatusKind as { SuccessValue?: string }).SuccessValue !== undefined ||
210
+ (txStatusKind as { SuccessReceiptId?: string }).SuccessReceiptId !== undefined;
211
+ const txStatusStr = txSucceeded ? "success" : "failure";
212
+
213
+ for (const entry of primitiveEntries) {
214
+ const prim = entry.primitive;
215
+ // Identify outcome-based primitives by the absence of event metadata.
216
+ if (prim.eventStandard != null) continue;
217
+ if (prim.contractId !== tx.receiver_id) continue;
218
+
219
+ for (let actionIndex = 0; actionIndex < tx.actions.length; actionIndex++) {
220
+ const action = tx.actions[actionIndex];
221
+ const fc = action.FunctionCall;
222
+ if (!fc) continue;
223
+
224
+ let argsDecoded = "";
225
+ try {
226
+ argsDecoded = Buffer.from(fc.args, "base64").toString("utf8");
227
+ } catch {
228
+ argsDecoded = fc.args;
229
+ }
230
+
231
+ allPrimitives.push({
232
+ syncProtocol: {
233
+ name: entry.syncProtocol,
234
+ blockNumber: height,
235
+ transactionHash: tx.hash,
236
+ contractAddress: tx.receiver_id,
237
+ logIndex: actionIndex,
238
+ },
239
+ primitive: prim.name,
240
+ output: {
241
+ payloadType: "account-watch:function-call",
242
+ payload: {
243
+ signer_id: tx.signer_id,
244
+ receiver_id: tx.receiver_id,
245
+ method_name: fc.method_name,
246
+ args: argsDecoded,
247
+ deposit: fc.deposit,
248
+ status: txStatusStr,
249
+ },
250
+ },
251
+ });
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ return allPrimitives;
258
+ }
259
+
260
+ private matchesPrimitive(
261
+ executorId: string,
262
+ event: Nep297Event,
263
+ prim: {
264
+ contractId: string;
265
+ eventStandard?: string;
266
+ eventType?: string;
267
+ },
268
+ ): boolean {
269
+ if (prim.contractId !== executorId) return false;
270
+ if (prim.eventStandard && prim.eventStandard !== event.standard) return false;
271
+ if (prim.eventType && prim.eventType !== event.event) return false;
272
+ return true;
273
+ }
274
+ }
@@ -0,0 +1,130 @@
1
+ import { call, type Operation } from "effection";
2
+ import { bound } from "@effectstream/utils";
3
+ import type { PoolClient } from "pg";
4
+ import { type LastPage, SyncState } from "../base/state.ts";
5
+ import type { RootOutput, RootPage } from "../types.ts";
6
+ import type { Input, Output, Page } from "./types.ts";
7
+ import { pageRelation } from "./types.ts";
8
+ import type { NearFetcher } from "./fetcher.ts";
9
+ import type { ConfigNetworkType, SyncProtocolWithNetwork } from "@effectstream/config";
10
+ import { getPage } from "@effectstream/db";
11
+ import { NearClient } from "./NearClient.ts";
12
+ import { applyDelay } from "../common/utils.ts";
13
+
14
+ export class NearSyncState extends SyncState<
15
+ Input,
16
+ Output,
17
+ Page,
18
+ RootOutput,
19
+ RootPage,
20
+ NearFetcher
21
+ > {
22
+ constructor(
23
+ lastPage: LastPage<Page, RootPage> | undefined,
24
+ readonly config: Extract<
25
+ SyncProtocolWithNetwork,
26
+ { networkType: ConfigNetworkType.NEAR }
27
+ >,
28
+ fetcher: NearFetcher,
29
+ public readonly client: NearClient,
30
+ dbConn: PoolClient,
31
+ ) {
32
+ super(
33
+ config.syncProtocol.name,
34
+ lastPage,
35
+ fetcher,
36
+ pageRelation,
37
+ dbConn,
38
+ );
39
+ }
40
+
41
+ @bound
42
+ override toPage(_input: Input, data: Output[]): Page {
43
+ const lastBlock = data[data.length - 1];
44
+ return {
45
+ height: lastBlock.height,
46
+ hash: lastBlock.hash,
47
+ };
48
+ }
49
+
50
+ @bound
51
+ override toRootPage(data: Output): RootPage {
52
+ // NEAR timestamps are already in ms (converted in fetcher)
53
+ return applyDelay(data.timestamp, this.config.syncProtocol.delayMs);
54
+ }
55
+
56
+ @bound
57
+ override toRootOutput(_data: Output): RootOutput {
58
+ throw new Error("Only main chains create root outputs");
59
+ }
60
+
61
+ @bound
62
+ override *stateToInput(): Operation<Input | undefined> {
63
+ const latestHeight = yield* call(() =>
64
+ this.client.getLatestBlockHeight()
65
+ );
66
+ const startHeight = this.lastPage?.own.height ??
67
+ this.config.syncProtocol.startBlockHeight - 1;
68
+
69
+ if (startHeight >= latestHeight) {
70
+ return undefined;
71
+ }
72
+
73
+ const from = startHeight + 1;
74
+ const to = Math.min(
75
+ from + this.config.syncProtocol.stepSize - 1,
76
+ latestHeight,
77
+ );
78
+
79
+ return {
80
+ from,
81
+ to,
82
+ isPresync: false,
83
+ };
84
+ }
85
+
86
+ @bound
87
+ override mergeDatum(ourOutput: Output, rootOutput: RootOutput): void {
88
+ const primitives = ourOutput.primitives.map((p) => ({
89
+ ...p,
90
+ source: this.config.syncProtocol.name,
91
+ }));
92
+ const blockInfo = [{
93
+ protocol_name: this.config.syncProtocol.name,
94
+ block_number: ourOutput.height,
95
+ blockHash: ourOutput.hash,
96
+ }];
97
+ rootOutput.blockInfo.push(...blockInfo);
98
+ rootOutput.primitives.push(...primitives);
99
+ }
100
+
101
+ @bound
102
+ override getNamespace(): string[] {
103
+ return [this.config.network.name, this.config.syncProtocol.name];
104
+ }
105
+
106
+ static *restoreState(
107
+ dbConn: PoolClient,
108
+ config: Extract<
109
+ SyncProtocolWithNetwork,
110
+ { networkType: ConfigNetworkType.NEAR }
111
+ >,
112
+ fetcher: NearFetcher,
113
+ ): Operation<NearSyncState> {
114
+ const [result] = yield* call(async () =>
115
+ await getPage.run({
116
+ protocol_name: config.syncProtocol.name,
117
+ }, dbConn)
118
+ );
119
+ const page = result
120
+ ? result.page as unknown as LastPage<Page, RootPage>
121
+ : undefined;
122
+ return new NearSyncState(
123
+ page,
124
+ config,
125
+ fetcher,
126
+ new NearClient(config.network.rpcUrl),
127
+ dbConn,
128
+ );
129
+ }
130
+ }
@@ -0,0 +1,46 @@
1
+ import type {
2
+ ConfigSyncProtocolType,
3
+ FlattenSyncProtocolIOFor,
4
+ SyncProtocolWithNetwork,
5
+ } from "@effectstream/config";
6
+ import type { BlockNumber } from "@effectstream/utils";
7
+ import type { PageSyncRange } from "../common/page-helpers.ts";
8
+ import type { PageRelation } from "../base/page.ts";
9
+
10
+ // =====================
11
+ // Sync protocol typings
12
+ // =====================
13
+
14
+ /** The page identifier for NEAR: a block height + hash pair */
15
+ export type Page = {
16
+ height: BlockNumber;
17
+ hash: string;
18
+ };
19
+
20
+ export type PrimitiveType = FlattenSyncProtocolIOFor<
21
+ ConfigSyncProtocolType.NEAR_RPC_PARALLEL
22
+ >;
23
+
24
+ export type Input = PageSyncRange<BlockNumber>;
25
+
26
+ export type Output = {
27
+ /** Block timestamp in milliseconds (converted from NEAR nanoseconds) */
28
+ timestamp: number;
29
+ /** Block height */
30
+ height: number;
31
+ /** Base58 block hash */
32
+ hash: string;
33
+ primitives: PrimitiveType[];
34
+ };
35
+
36
+ export const pageRelation: PageRelation<Page> = {
37
+ compare: (p1, p2) => p1.height - p2.height,
38
+ equals: (p1, p2) => p1.height === p2.height,
39
+ min: (p1, p2) => (p1.height < p2.height ? p1 : p2),
40
+ max: (p1, p2) => (p1.height > p2.height ? p1 : p2),
41
+ };
42
+
43
+ export type ConfigType = Extract<
44
+ SyncProtocolWithNetwork,
45
+ { syncProtocolType: ConfigSyncProtocolType.NEAR_RPC_PARALLEL }
46
+ >;
@@ -0,0 +1,199 @@
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 { Operation } from "effection";
8
+ import { all, call, until } from "effection";
9
+ import { bound, type NtpBlockHash, type NtpPageJson } from "@effectstream/utils";
10
+ import { blockNumberRelation } from "../common/utils.ts";
11
+ import type { Input, Output, Page, PrimitiveType } from "./types.ts";
12
+ import {
13
+ fetchNewestPage,
14
+ genImmediatePageRequests,
15
+ genOnDemandPageRequests,
16
+ type PageRange,
17
+ type PageRequest,
18
+ } from "../base/page.ts";
19
+ import type { PrimitiveFetcher } from "../base/primitive.ts";
20
+ import type { RootConversion } from "../base/state.ts";
21
+ import type {
22
+ ConfigNetworkType,
23
+ PrimitiveEntry,
24
+ SyncProtocolWithNetwork,
25
+ } from "@effectstream/config";
26
+ import type { ConfigSyncProtocolType } from "@effectstream/config";
27
+ import { NtpTimeSync, type NtpTimeSyncDefaultOptions } from "ntp-time-sync";
28
+
29
+ type RecursivePartial<T> = {
30
+ [P in keyof T]?: RecursivePartial<T[P]>;
31
+ };
32
+
33
+ export class NtpFetcher
34
+ extends BaseDataFetcher<Input, Output, RootOutput, Page, RootPage>
35
+ implements
36
+ PrimitiveFetcher<
37
+ Input,
38
+ Page,
39
+ { blockNumber: number; timestamp: bigint; hash: string },
40
+ PrimitiveType,
41
+ ConfigSyncProtocolType.NTP_MAIN
42
+ >,
43
+ PaginatedFetcher<Page> {
44
+ readonly ntpTimeSync: NtpTimeSync;
45
+ constructor(
46
+ readonly config: Extract<
47
+ SyncProtocolWithNetwork,
48
+ { networkType: ConfigNetworkType.NTP }
49
+ >,
50
+ ) {
51
+ super(config.syncProtocol.name);
52
+
53
+ const options: RecursivePartial<typeof NtpTimeSyncDefaultOptions> = {
54
+ ntpDefaults: {
55
+ minPoll: 10,
56
+ },
57
+ };
58
+ if (config.network.servers && config.network.servers.length) {
59
+ options.servers = config.network.servers;
60
+ }
61
+ // Cannot select options
62
+ // https://github.com/buffcode/ntp-time-sync/issues/104
63
+ this.ntpTimeSync = NtpTimeSync.getInstance();
64
+ }
65
+
66
+ @bound
67
+ override *readData(
68
+ data: Input,
69
+ rootConversion: RootConversion<Output, RootOutput, RootPage>,
70
+ ): Operation<DataFetched<Output, Page, RootPage>> {
71
+ const pageFetcher = (() => {
72
+ if (data.isPresync) {
73
+ return genOnDemandPageRequests(
74
+ data.from,
75
+ data.to,
76
+ async (page) => {
77
+ const diff = BigInt(this.config.network.blockTimeMS * page);
78
+ const timestamp = BigInt(this.config.network.startTime) + diff;
79
+ return {
80
+ blockNumber: page,
81
+ timestamp,
82
+ hash: `0x${timestamp.toString(16)}`,
83
+ };
84
+ },
85
+ blockNumberRelation,
86
+ );
87
+ }
88
+ return genImmediatePageRequests(
89
+ Array.from(
90
+ { length: data.to - data.from + 1 },
91
+ (_, i) => i + data.from,
92
+ ),
93
+ async (page) => {
94
+ const diff = BigInt(this.config.network.blockTimeMS * page);
95
+ const timestamp = BigInt(this.config.network.startTime) + diff;
96
+ return {
97
+ blockNumber: page,
98
+ timestamp,
99
+ hash: `0x${timestamp.toString(16)}`,
100
+ };
101
+ },
102
+ );
103
+ })();
104
+
105
+ const output = yield* all(
106
+ Array.from(
107
+ { length: data.to - data.from + 1 },
108
+ (_, i) => i + data.from,
109
+ ).map(function* (page: Page) {
110
+ const raw = yield* call(() => pageFetcher(page));
111
+ const blockHashes = [raw.hash] as NtpBlockHash[];
112
+ return {
113
+ raw,
114
+ blockHashes,
115
+ };
116
+ }),
117
+ );
118
+ return {
119
+ output: output.map((o) => ({
120
+ output: o,
121
+ cleanup: () => {}, // no cleanup required
122
+ })),
123
+ lastPage: {
124
+ ownBlockNumber: Number(data.to),
125
+ own: Number(data.to),
126
+ root: rootConversion.toRootPage({
127
+ blockHashes: [] as NtpBlockHash[],
128
+ raw: (yield* call(() => pageFetcher(Number(data.to)))),
129
+ }),
130
+ },
131
+ };
132
+ }
133
+
134
+ @bound
135
+ groupByPage(
136
+ primitives: PrimitiveType[],
137
+ ): Record<NtpPageJson, PrimitiveType[]> {
138
+ // Not used in NTP
139
+ return {};
140
+ }
141
+
142
+ @bound
143
+ *readPrimitives(
144
+ data: Input,
145
+ pageRequest: PageRequest<Page, { timestamp: bigint; hash: string }>,
146
+ primitiveEntries: Extract<
147
+ PrimitiveEntry,
148
+ { syncProtocol: ConfigSyncProtocolType.NTP_MAIN }
149
+ >[],
150
+ ): Operation<PrimitiveType[]> {
151
+ return [];
152
+ }
153
+
154
+ @bound
155
+ *getLatestPage(
156
+ knownLastPage: undefined | Page,
157
+ ): Operation<Page> {
158
+ const self = this;
159
+ return yield* fetchNewestPage<Page>(
160
+ blockNumberRelation,
161
+ function* (): Operation<Page> {
162
+ const result = yield* until(self.ntpTimeSync.getTime());
163
+ if (result.offset > 5000) {
164
+ console.error(
165
+ `NTP offset is higher than 5[s]. Please check your NTP server & system time configuration.
166
+ NTP time: ${result.now}
167
+ Local time: ${new Date()}
168
+ Offset [ms]: ${result.offset}`,
169
+ );
170
+ }
171
+ const diff = result.now.getTime() - self.config.network.startTime;
172
+ const blockNumber = Math.floor(diff / self.config.network.blockTimeMS);
173
+ return blockNumber;
174
+ },
175
+ knownLastPage,
176
+ this.config.syncProtocol.pollingInterval,
177
+ );
178
+ }
179
+
180
+ @bound
181
+ previousInterval(nextIntervalStart: Page): PageRange<Page> {
182
+ return this.intervalFromStart(
183
+ nextIntervalStart - this.config.syncProtocol.stepSize - 1,
184
+ );
185
+ }
186
+
187
+ @bound
188
+ nextInterval(prevIntervalEnd: Page): PageRange<Page> {
189
+ return this.intervalFromStart(prevIntervalEnd + 1);
190
+ }
191
+
192
+ @bound
193
+ intervalFromStart(start: Page): PageRange<Page> {
194
+ return {
195
+ from: start,
196
+ to: start + this.config.syncProtocol.stepSize,
197
+ };
198
+ }
199
+ }