@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,144 @@
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 } from "@effectstream/utils";
7
+ import {
8
+ type LastPage,
9
+ SyncState,
10
+ } from "../base/state.ts";
11
+ import type { RootOutput, RootPage } from "../types.ts";
12
+ import type { BitcoinFetcher } from "./fetcher.ts";
13
+ import {
14
+ toMsTimestamp,
15
+ type Input,
16
+ type Output,
17
+ type Page,
18
+ } from "./types.ts";
19
+ import { genInputRange } from "../common/page-helpers.ts";
20
+ import type {
21
+ ConfigNetworkType,
22
+ SyncProtocolWithNetwork,
23
+ } from "@effectstream/config";
24
+
25
+ export class BitcoinSyncState extends SyncState<
26
+ Input,
27
+ Output,
28
+ Page,
29
+ RootOutput,
30
+ RootPage,
31
+ BitcoinFetcher
32
+ > {
33
+ constructor(
34
+ lastPage: LastPage<Page, RootPage> | undefined,
35
+ readonly config: Extract<
36
+ SyncProtocolWithNetwork,
37
+ { networkType: ConfigNetworkType.BITCOIN }
38
+ >,
39
+ fetcher: BitcoinFetcher,
40
+ dbConn: PoolClient,
41
+ ) {
42
+ super(
43
+ config.syncProtocol.name,
44
+ lastPage,
45
+ fetcher,
46
+ blockNumberRelation,
47
+ dbConn,
48
+ );
49
+ }
50
+
51
+ @bound
52
+ override toRootOutput(data: Output): RootOutput {
53
+ return {
54
+ blockNumber: Number(data.raw.height),
55
+ timestamp: this.toRootPage(data),
56
+ blockInfo: data.blockHashes.map((hash) => ({
57
+ protocol_name: this.config.syncProtocol.name,
58
+ block_number: Number(data.raw.height),
59
+ blockHash: hash,
60
+ })),
61
+ primitives: data.primitives.map((primitive) => ({
62
+ ...primitive,
63
+ source: this.config.syncProtocol.name,
64
+ })),
65
+ };
66
+ }
67
+
68
+ @bound
69
+ override toRootPage(data: Output): RootPage {
70
+ return applyDelay(
71
+ toMsTimestamp(data.raw.time),
72
+ this.config.syncProtocol.delayMs ?? 0,
73
+ );
74
+ }
75
+
76
+ @bound
77
+ override *stateToInput(): Operation<Input | undefined> {
78
+ return yield* genInputRange(
79
+ this as BitcoinSyncState,
80
+ this.config.syncProtocol.startBlockHeight as Page,
81
+ {
82
+ name: this.config.syncProtocol.name,
83
+ startPage: this.config.syncProtocol.startBlockHeight as Page,
84
+ },
85
+ this.getNamespace(),
86
+ );
87
+ }
88
+
89
+ @bound
90
+ override toPage(
91
+ input: Input,
92
+ _data: Output[],
93
+ ): Page {
94
+ return input.to;
95
+ }
96
+
97
+ @bound
98
+ override mergeDatum(
99
+ ourOutput: Output,
100
+ rootOutput: RootOutput,
101
+ ): void {
102
+ const primitives = ourOutput.primitives.map((primitive) => ({
103
+ ...primitive,
104
+ source: this.config.syncProtocol.name,
105
+ }));
106
+ const blockInfo = ourOutput.blockHashes.map((hash) => ({
107
+ protocol_name: this.config.syncProtocol.name,
108
+ block_number: Number(ourOutput.raw.height),
109
+ blockHash: hash,
110
+ }));
111
+ rootOutput.primitives.push(...primitives);
112
+ rootOutput.blockInfo.push(...blockInfo);
113
+ }
114
+
115
+ @bound
116
+ override getNamespace(): string[] {
117
+ return [this.config.network.name, this.config.syncProtocol.name];
118
+ }
119
+
120
+ static *restoreState(
121
+ dbConn: PoolClient,
122
+ config: Extract<
123
+ SyncProtocolWithNetwork,
124
+ { networkType: ConfigNetworkType.BITCOIN }
125
+ >,
126
+ fetcher: BitcoinFetcher,
127
+ ): Operation<BitcoinSyncState> {
128
+ const [result] = yield* call(async () =>
129
+ await getPage.run({
130
+ protocol_name: config.syncProtocol.name,
131
+ }, dbConn)
132
+ );
133
+ const page = result
134
+ ? result.page as unknown as LastPage<Page, RootPage>
135
+ : undefined;
136
+ return new BitcoinSyncState(
137
+ page,
138
+ config,
139
+ fetcher,
140
+ dbConn,
141
+ );
142
+ }
143
+ }
144
+
@@ -0,0 +1,92 @@
1
+ import type {
2
+ BitcoinBlockHash,
3
+ BitcoinTxId,
4
+ BlockNumber,
5
+ TimestampMs,
6
+ } from "@effectstream/utils";
7
+ import type { PageSyncRange } from "../common/page-helpers.ts";
8
+ import type {
9
+ ConfigNetworkType,
10
+ ConfigSyncProtocolType,
11
+ FlattenSyncProtocolIOFor,
12
+ PrimitiveEntry,
13
+ SyncProtocolWithNetwork,
14
+ } from "@effectstream/config";
15
+
16
+ export type Page = BlockNumber;
17
+
18
+ export type ConfigType = Extract<
19
+ SyncProtocolWithNetwork,
20
+ { networkType: ConfigNetworkType.BITCOIN }
21
+ >;
22
+
23
+ export type PrimitiveEntryType = Extract<
24
+ PrimitiveEntry,
25
+ { syncProtocol: ConfigSyncProtocolType.BITCOIN_RPC_PARALLEL }
26
+ >;
27
+
28
+ export type PrimitiveType = FlattenSyncProtocolIOFor<
29
+ ConfigSyncProtocolType.BITCOIN_RPC_PARALLEL
30
+ >;
31
+
32
+ export type Input = PageSyncRange<Page>;
33
+
34
+ export type BitcoinScriptPubKey = {
35
+ asm: string;
36
+ hex: string;
37
+ type?: string;
38
+ address?: string;
39
+ addresses?: string[];
40
+ };
41
+
42
+ export type BitcoinVout = {
43
+ value: number;
44
+ n: number;
45
+ scriptPubKey: BitcoinScriptPubKey;
46
+ };
47
+
48
+ export type BitcoinVin = {
49
+ txid?: BitcoinTxId;
50
+ vout?: number;
51
+ coinbase?: string;
52
+ sequence: number;
53
+ };
54
+
55
+ export type BitcoinTransaction = {
56
+ txid: BitcoinTxId;
57
+ hash: BitcoinTxId;
58
+ version: number;
59
+ size: number;
60
+ vsize: number;
61
+ weight: number;
62
+ locktime: number;
63
+ vin: BitcoinVin[];
64
+ vout: BitcoinVout[];
65
+ };
66
+
67
+ export type BitcoinBlock = {
68
+ hash: BitcoinBlockHash;
69
+ confirmations: number;
70
+ size: number;
71
+ height: Page;
72
+ version: number;
73
+ time: number;
74
+ mediantime: number;
75
+ nonce: number;
76
+ bits: string;
77
+ difficulty: number;
78
+ previousblockhash?: BitcoinBlockHash;
79
+ nextblockhash?: BitcoinBlockHash;
80
+ tx: BitcoinTransaction[];
81
+ };
82
+
83
+ export type Output = {
84
+ raw: BitcoinBlock;
85
+ primitives: PrimitiveType[];
86
+ blockHashes: BitcoinBlockHash[];
87
+ };
88
+
89
+ export function toMsTimestamp(timestamp: number): TimestampMs {
90
+ return timestamp * 1000 as TimestampMs;
91
+ }
92
+
@@ -0,0 +1,139 @@
1
+ export type CelestiaBlob = {
2
+ /** Base64-encoded 29-byte namespace */
3
+ namespace: string;
4
+ /** Base64-encoded blob data */
5
+ data: string;
6
+ share_version: number;
7
+ /** Base64-encoded blob commitment */
8
+ commitment: string;
9
+ signer: string;
10
+ index: number;
11
+ };
12
+
13
+ export type CelestiaHeader = {
14
+ version: { block: string; app: string };
15
+ chain_id: string;
16
+ /** String-encoded block height */
17
+ height: string;
18
+ /** ISO 8601 timestamp */
19
+ time: string;
20
+ last_block_id: {
21
+ hash: string;
22
+ parts: { total: number; hash: string };
23
+ };
24
+ last_commit_hash: string;
25
+ data_hash: string;
26
+ validators_hash: string;
27
+ next_validators_hash: string;
28
+ consensus_hash: string;
29
+ app_hash: string;
30
+ last_results_hash: string;
31
+ evidence_hash: string;
32
+ proposer_address: string;
33
+ };
34
+
35
+ export type CelestiaCommit = {
36
+ height: string;
37
+ block_id: {
38
+ hash: string;
39
+ parts: { total: number; hash: string };
40
+ };
41
+ };
42
+
43
+ export type CelestiaExtendedHeader = {
44
+ header: CelestiaHeader;
45
+ commit: CelestiaCommit;
46
+ validator_set: unknown;
47
+ dah: unknown;
48
+ };
49
+
50
+ import { ENV } from "@effectstream/utils/node-env";
51
+
52
+ const CELESTIA_NODE_URL = ENV.getString(
53
+ "CELESTIA_NODE_URL",
54
+ "http://localhost:26658",
55
+ );
56
+
57
+ /**
58
+ * Converts a hex-encoded Celestia namespace to a base64-encoded 29-byte array.
59
+ * Format: 1 byte version (0x00) + 28 bytes namespace ID, right-aligned.
60
+ */
61
+ export function celestiaNamespaceToBase64(hex: string): string {
62
+ const cleanHex = hex.replace(/^0x/, "").padStart(56, "0"); // 28 bytes = 56 hex chars
63
+ const bytes = new Uint8Array(29); // version byte (0) + 28-byte ID
64
+ for (let i = 0; i < 28; i++) {
65
+ bytes[i + 1] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16);
66
+ }
67
+ let binary = "";
68
+ for (const byte of bytes) {
69
+ binary += String.fromCharCode(byte);
70
+ }
71
+ return btoa(binary);
72
+ }
73
+
74
+ export class CelestiaClient {
75
+ private readonly rpcUrl: string;
76
+
77
+ constructor(rpcUrl: string = CELESTIA_NODE_URL) {
78
+ this.rpcUrl = rpcUrl;
79
+ }
80
+
81
+ private async rpc<T>(method: string, params: unknown[]): Promise<T | null> {
82
+ let res: Response;
83
+ try {
84
+ res = await fetch(this.rpcUrl, {
85
+ method: "POST",
86
+ headers: { "Content-Type": "application/json" },
87
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
88
+ });
89
+ } catch (err: unknown) {
90
+ const msg = err instanceof Error ? err.message : String(err);
91
+ console.error(`[Celestia] RPC fetch error [${method}]: ${msg}`);
92
+ return null;
93
+ }
94
+
95
+ const json = await res.json();
96
+ if (json.error) {
97
+ const errMsg: string = json.error.message ?? String(json.error);
98
+ if (errMsg.includes("blob: not found")) return null;
99
+ console.error(`[Celestia] RPC error [${method}]:`, errMsg);
100
+ return null;
101
+ }
102
+ return json.result as T;
103
+ }
104
+
105
+ async getLatestBlockHeight(): Promise<number> {
106
+ const head = await this.rpc<CelestiaExtendedHeader>(
107
+ "header.LocalHead",
108
+ [],
109
+ );
110
+ if (!head) {
111
+ throw new Error("[Celestia] Could not retrieve latest block header");
112
+ }
113
+ return parseInt(head.header.height, 10);
114
+ }
115
+
116
+ async getHeaderAtHeight(height: number): Promise<CelestiaExtendedHeader> {
117
+ const header = await this.rpc<CelestiaExtendedHeader>(
118
+ "header.GetByHeight",
119
+ [height],
120
+ );
121
+ if (!header) {
122
+ throw new Error(
123
+ `[Celestia] Could not retrieve header at height ${height}`,
124
+ );
125
+ }
126
+ return header;
127
+ }
128
+
129
+ async getBlobsAtHeight(
130
+ height: number,
131
+ namespaceB64: string,
132
+ ): Promise<CelestiaBlob[]> {
133
+ const blobs = await this.rpc<CelestiaBlob[]>("blob.GetAll", [
134
+ height,
135
+ [namespaceB64],
136
+ ]);
137
+ return blobs ?? [];
138
+ }
139
+ }
@@ -0,0 +1,165 @@
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
+ CelestiaClient,
22
+ celestiaNamespaceToBase64,
23
+ } from "./CelestiaClient.ts";
24
+ import { all, call, type Operation } from "effection";
25
+ import { bound } from "@effectstream/utils";
26
+
27
+ export class CelestiaFetcher extends BaseDataFetcher<
28
+ Input,
29
+ Output,
30
+ RootOutput,
31
+ Page,
32
+ RootPage
33
+ > {
34
+ readonly client: CelestiaClient;
35
+
36
+ constructor(
37
+ readonly config: ConfigType,
38
+ ) {
39
+ super(config.syncProtocol.name);
40
+ this.client = new CelestiaClient(config.network.rpcUrl);
41
+ }
42
+
43
+ @bound
44
+ override *readData(
45
+ data: Input,
46
+ rootConversion: RootConversion<Output, RootOutput, RootPage>,
47
+ lastPage: LastPage<Page, RootPage> | undefined,
48
+ ): Operation<DataFetched<Output, Page, RootPage>> {
49
+ const outputs: OutputAndCleanup<Output>[] = [];
50
+
51
+ console.log(
52
+ `[Celestia] Fetching blocks from ${data.from} to ${data.to}.${
53
+ data.isPresync ? " [presync]" : ""
54
+ }`,
55
+ );
56
+
57
+ for (let height = data.from; height <= data.to; height++) {
58
+ let extHeader;
59
+ try {
60
+ extHeader = yield* call(() =>
61
+ this.client.getHeaderAtHeight(height)
62
+ );
63
+ } catch (_e) {
64
+ // Likely reached the chain tip; stop here
65
+ break;
66
+ }
67
+
68
+ const blockHash = extHeader.commit.block_id.hash;
69
+
70
+ const primitives = yield* this.readPrimitives(
71
+ height,
72
+ blockHash,
73
+ this.config.primitives,
74
+ );
75
+
76
+ outputs.push({
77
+ output: {
78
+ timestamp: extHeader.header.time,
79
+ height,
80
+ hash: blockHash,
81
+ primitives,
82
+ },
83
+ cleanup: () => {},
84
+ });
85
+ }
86
+
87
+ if (outputs.length === 0) {
88
+ if (!lastPage) {
89
+ throw new Error(
90
+ `[Celestia] Could not fetch any blocks from ${data.from} to ${data.to} and no previous page was found.`,
91
+ );
92
+ }
93
+ return {
94
+ output: [],
95
+ lastPage,
96
+ };
97
+ }
98
+
99
+ const lastOutput = outputs[outputs.length - 1].output;
100
+ return {
101
+ output: outputs,
102
+ lastPage: {
103
+ ownBlockNumber: lastOutput.height,
104
+ own: {
105
+ height: lastOutput.height,
106
+ hash: lastOutput.hash,
107
+ },
108
+ root: rootConversion.toRootPage(lastOutput),
109
+ },
110
+ };
111
+ }
112
+
113
+ @bound
114
+ *readPrimitives(
115
+ height: number,
116
+ blockHash: string,
117
+ primitiveEntries: Extract<
118
+ PrimitiveEntry,
119
+ { syncProtocol: ConfigSyncProtocolType.CELESTIA_PARALLEL }
120
+ >[],
121
+ ): Operation<PrimitiveType[]> {
122
+ const allOperations: Operation<PrimitiveType[]>[] = primitiveEntries.map(
123
+ (entry) => this.fetchBlobsForPrimitive(height, blockHash, entry),
124
+ );
125
+ const results = yield* all(allOperations);
126
+ return results.flat();
127
+ }
128
+
129
+ @bound
130
+ *fetchBlobsForPrimitive(
131
+ height: number,
132
+ blockHash: string,
133
+ primitiveEntry: Extract<
134
+ PrimitiveEntry,
135
+ { syncProtocol: ConfigSyncProtocolType.CELESTIA_PARALLEL }
136
+ >,
137
+ ): Operation<PrimitiveType[]> {
138
+ const namespace = primitiveEntry.primitive.namespace;
139
+ const namespaceB64 = celestiaNamespaceToBase64(namespace);
140
+
141
+ const blobs = yield* call(() =>
142
+ this.client.getBlobsAtHeight(height, namespaceB64)
143
+ );
144
+
145
+ return blobs.map((blob): PrimitiveType => ({
146
+ syncProtocol: {
147
+ name: primitiveEntry.syncProtocol,
148
+ blockNumber: height,
149
+ transactionHash: blockHash,
150
+ contractAddress: namespace,
151
+ logIndex: blob.index,
152
+ },
153
+ primitive: primitiveEntry.primitive.name,
154
+ output: {
155
+ payloadType: "celestia-blob",
156
+ payload: {
157
+ suppliedValue: atob(blob.data),
158
+ namespace,
159
+ commitment: blob.commitment,
160
+ blobIndex: blob.index,
161
+ },
162
+ },
163
+ }));
164
+ }
165
+ }
@@ -0,0 +1,131 @@
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 { CelestiaFetcher } from "./fetcher.ts";
9
+ import type { ConfigNetworkType, SyncProtocolWithNetwork } from "@effectstream/config";
10
+ import { getPage } from "@effectstream/db";
11
+ import { CelestiaClient } from "./CelestiaClient.ts";
12
+ import { applyDelay } from "../common/utils.ts";
13
+
14
+ export class CelestiaSyncState extends SyncState<
15
+ Input,
16
+ Output,
17
+ Page,
18
+ RootOutput,
19
+ RootPage,
20
+ CelestiaFetcher
21
+ > {
22
+ constructor(
23
+ lastPage: LastPage<Page, RootPage> | undefined,
24
+ readonly config: Extract<
25
+ SyncProtocolWithNetwork,
26
+ { networkType: ConfigNetworkType.CELESTIA }
27
+ >,
28
+ fetcher: CelestiaFetcher,
29
+ public readonly client: CelestiaClient,
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
+ // Celestia headers use ISO 8601 timestamps; convert to ms.
53
+ const timestampMs = new Date(data.timestamp).getTime();
54
+ return applyDelay(timestampMs, this.config.syncProtocol.delayMs);
55
+ }
56
+
57
+ @bound
58
+ override toRootOutput(_data: Output): RootOutput {
59
+ throw new Error("Only main chains create root outputs");
60
+ }
61
+
62
+ @bound
63
+ override *stateToInput(): Operation<Input | undefined> {
64
+ const latestHeight = yield* call(() =>
65
+ this.client.getLatestBlockHeight()
66
+ );
67
+ const startHeight = this.lastPage?.own.height ??
68
+ this.config.syncProtocol.startBlockHeight - 1;
69
+
70
+ if (startHeight >= latestHeight) {
71
+ return undefined;
72
+ }
73
+
74
+ const from = startHeight + 1;
75
+ const to = Math.min(
76
+ from + this.config.syncProtocol.stepSize - 1,
77
+ latestHeight,
78
+ );
79
+
80
+ return {
81
+ from,
82
+ to,
83
+ isPresync: false,
84
+ };
85
+ }
86
+
87
+ @bound
88
+ override mergeDatum(ourOutput: Output, rootOutput: RootOutput): void {
89
+ const primitives = ourOutput.primitives.map((p) => ({
90
+ ...p,
91
+ source: this.config.syncProtocol.name,
92
+ }));
93
+ const blockInfo = [{
94
+ protocol_name: this.config.syncProtocol.name,
95
+ block_number: ourOutput.height,
96
+ blockHash: ourOutput.hash,
97
+ }];
98
+ rootOutput.blockInfo.push(...blockInfo);
99
+ rootOutput.primitives.push(...primitives);
100
+ }
101
+
102
+ @bound
103
+ override getNamespace(): string[] {
104
+ return [this.config.network.name, this.config.syncProtocol.name];
105
+ }
106
+
107
+ static *restoreState(
108
+ dbConn: PoolClient,
109
+ config: Extract<
110
+ SyncProtocolWithNetwork,
111
+ { networkType: ConfigNetworkType.CELESTIA }
112
+ >,
113
+ fetcher: CelestiaFetcher,
114
+ ): Operation<CelestiaSyncState> {
115
+ const [result] = yield* call(async () =>
116
+ await getPage.run({
117
+ protocol_name: config.syncProtocol.name,
118
+ }, dbConn)
119
+ );
120
+ const page = result
121
+ ? result.page as unknown as LastPage<Page, RootPage>
122
+ : undefined;
123
+ return new CelestiaSyncState(
124
+ page,
125
+ config,
126
+ fetcher,
127
+ new CelestiaClient(config.network.rpcUrl),
128
+ dbConn,
129
+ );
130
+ }
131
+ }
@@ -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 Celestia: a block height + hash pair */
15
+ export type Page = {
16
+ height: BlockNumber;
17
+ hash: string;
18
+ };
19
+
20
+ export type PrimitiveType = FlattenSyncProtocolIOFor<
21
+ ConfigSyncProtocolType.CELESTIA_PARALLEL
22
+ >;
23
+
24
+ export type Input = PageSyncRange<BlockNumber>;
25
+
26
+ export type Output = {
27
+ /** ISO 8601 timestamp from the block header */
28
+ timestamp: string;
29
+ /** Block height */
30
+ height: number;
31
+ /** Hex block hash from last_block_id */
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.CELESTIA_PARALLEL }
46
+ >;