@effectstream/runtime 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.
@@ -0,0 +1,297 @@
1
+ import {
2
+ encodeFunctionData,
3
+ type RpcBlock,
4
+ type RpcLog,
5
+ type RpcTransaction,
6
+ type RpcTransactionReceipt,
7
+ stringToHex,
8
+ } from "viem";
9
+ import { add0x } from "./rpc-utils.ts";
10
+
11
+ export function toRpcTransaction(
12
+ data: {
13
+ blockHash: string;
14
+ blockNumber: number;
15
+ from: string;
16
+ inputData: string;
17
+ txHash: string;
18
+ txIndex: number;
19
+ },
20
+ ): RpcTransaction {
21
+ const hexMsg = stringToHex(data.inputData);
22
+ /**
23
+ * Paima is not EVM so it doesn't have the same encoding of "inputData" in the EVM sense
24
+ * To convert between the two, we encode the Paima data with this fictional ABI
25
+ */
26
+ const input = encodeFunctionData({
27
+ // TODO: this string should be exposed somewhere 3rd parties can access
28
+ // https://github.com/wevm/viem/issues/2591
29
+ abi: [
30
+ {
31
+ inputs: [{ internalType: "string", name: "input", type: "string" }],
32
+ name: "convertedPaimaData",
33
+ outputs: [],
34
+ stateMutability: "nonpayable",
35
+ type: "function",
36
+ },
37
+ ],
38
+ args: [hexMsg],
39
+ });
40
+
41
+ return {
42
+ blockHash: add0x(data.blockHash),
43
+ blockNumber: `0x${data.blockNumber.toString(16)}`,
44
+ hash: add0x(data.txHash),
45
+ input,
46
+ transactionIndex: `0x${data.txIndex.toString(16)}`,
47
+ from: data.from as any, // we can't guarantee EVM address format here
48
+ value: "0x0", // TODO
49
+ nonce: "0x0", // TODO
50
+ ...mockTxType,
51
+ ...mockTxRecipient,
52
+ ...mockTxGasPre,
53
+ ...mockEvmEcdsaSignature,
54
+ };
55
+ }
56
+
57
+ export function toRpcBlock(
58
+ data: {
59
+ blockHeight: number;
60
+ blockHash: string;
61
+ prevBlockHash: string;
62
+ msTimestamp: number;
63
+ txs: ({
64
+ blockHeight: number;
65
+ blockHash: string;
66
+ prevBlockHash: string;
67
+ msTimestamp: number;
68
+ txs: any[];
69
+ } | string)[];
70
+ },
71
+ txDetails: boolean,
72
+ ): RpcBlock {
73
+ return {
74
+ number: `0x${data.blockHeight.toString(16)}`,
75
+ hash: add0x(data.blockHash),
76
+ parentHash: data.prevBlockHash == null ? "0x0" : add0x(data.prevBlockHash),
77
+
78
+ timestamp: `0x${Math.floor(data.msTimestamp / 1000).toString(16)}`,
79
+ size: "0x0", // TODO: do we really need this? I doubt it
80
+
81
+ transactions: txDetails
82
+ ? (data
83
+ .txs as any[]).map((tx) => toRpcTransaction(tx))
84
+ : (data.txs as string[]).map((tx) => add0x(tx)),
85
+ ...mockExtraData,
86
+ ...mockRoots,
87
+ ...mockUncles,
88
+ ...mockMiner,
89
+ ...mockBlockGas,
90
+ ...mockLogBloom,
91
+ ...mockSealFields,
92
+ ...mockBlob,
93
+ ...mockEip1559Gas,
94
+ };
95
+ }
96
+
97
+ export function toRpcTransactionReceipt(
98
+ txData: {
99
+ result: {
100
+ txHash: string;
101
+ blockHash: string;
102
+ blockNumber: number;
103
+ txIndex: number;
104
+ from: string;
105
+ success: boolean;
106
+ };
107
+ },
108
+ ): RpcTransactionReceipt {
109
+ return {
110
+ transactionHash: add0x(txData.result.txHash),
111
+ blockHash: add0x(txData.result.blockHash),
112
+ blockNumber: `0x${txData.result.blockNumber.toString(16)}`,
113
+ transactionIndex: `0x${txData.result.txIndex.toString(16)}`,
114
+ from: txData.result.from as any, // we can't guarantee EVM address format here
115
+ logs: [], // TODO
116
+ status: txData.result.success ? "0x1" : "0x0",
117
+ ...mockTxGasPost,
118
+ ...mockTxRecipient,
119
+ ...mockTxType,
120
+ ...mockLogBloom,
121
+ ...mockContractAddress, // TODO: remove
122
+ // contractAddress: TODO // TODO
123
+ };
124
+ }
125
+
126
+ export function toRpcLog(
127
+ log: any,
128
+ ): RpcLog {
129
+ return {
130
+ address: log.address as any, // can't guarantee address format matches
131
+ topics: [], // TODO: this should be all topics, not just the ones passed in
132
+ data: "0x", // TODO
133
+ blockNumber: `0x${log.blockNumber.toString(16)}`,
134
+ transactionIndex: add0x(log.txIndex.toString(16)),
135
+ transactionHash: add0x(log.transactionHash),
136
+ blockHash: add0x(log.blockHash),
137
+ logIndex: add0x(log.logIndex.toString(16)),
138
+ removed: false,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * We can't truly provide this for a few reasons:
144
+ * 1. Transactions aren't guaranteed to be on Ethereum (could be from a non-EVM chain)
145
+ * 2. Even on Ethereum, Paima txs aren't guaranteed to be from an EOA accounts
146
+ * i.e. it could be a game tick, it could be an internal tx (EVM internal txs are regular txs in Paima), etc.
147
+ *
148
+ * TODO: we could decide to add this for historical_game_inputs for EVM chains if we really want to
149
+ */
150
+ const mockEvmEcdsaSignature = {
151
+ /** ECDSA signature r */
152
+ r: "0x0",
153
+ /** ECDSA signature s */
154
+ s: "0x0",
155
+ /**
156
+ * ECDSA recovery ID
157
+ * note: replaced by yParity if type != 0x0
158
+ */
159
+ v: "0x0",
160
+ } as const;
161
+
162
+ /**
163
+ * Gas specified before tx lands onchain (as part of the tx input given by the user)
164
+ * Note: no concept of gas on Paima
165
+ */
166
+ const mockTxGasPre = {
167
+ gas: "0x0",
168
+ gasPrice: "0x0",
169
+ // TODO: some other gas fields needed if we ever use type != 0x0
170
+ } as const;
171
+
172
+ const mockEip1559Gas = {
173
+ baseFeePerGas: "0x0",
174
+ maxFeePerGas: "0x0",
175
+ } as const;
176
+
177
+ /**
178
+ * Gas specified after tx lands onchain (after calculating how much gas is consumed in reality)
179
+ * Note: no concept of gas on Paima
180
+ */
181
+ const mockTxGasPost = {
182
+ gasUsed: "0x0",
183
+ effectiveGasPrice: "0x0",
184
+ cumulativeGasUsed: "0x0",
185
+ } as const;
186
+ /**
187
+ * Gas consumed for a block
188
+ * Note: no concept of gas on Paima
189
+ */
190
+ const mockBlockGas = {
191
+ gasLimit: "0x0",
192
+ gasUsed: "0x0",
193
+ } as const;
194
+
195
+ /**
196
+ * Mock info about blobs (doesn't exist in Paima)
197
+ */
198
+ const mockBlob = {
199
+ blobGasUsed: "0x0",
200
+ excessBlobGas: "0x0",
201
+ } as const;
202
+
203
+ /**
204
+ * There is no concept of recipients in Paima since txs are to the state machine
205
+ * TODO: there are some cases where, after processing the STF, we could determine if this tx was *to* a specific address
206
+ * or conversely, make that the STF fails if the state transition doesn't match some asserted *to* address
207
+ * so we could support some limited form of this if needed
208
+ */
209
+ const mockTxRecipient = {
210
+ to: "0x0",
211
+ } as const;
212
+
213
+ /**
214
+ * EVM has multiple tx types
215
+ * - 0x0 for legacy transactions
216
+ * - 0x1 for access list types
217
+ * - 0x2 for dynamic fees
218
+ *
219
+ * Not all chains use 0x2, so it's not clear which we should use for mock data in Paima
220
+ * Note: making this an ENV var doesn't make sense either since it's not something the node can know ahead of time
221
+ * since it depends on which tool is making the RPC query, not the node itself
222
+ * We pick 0x0 for best chance at compatibility
223
+ *
224
+ * Note: if we change this to something other than 0x0, we also have to
225
+ * 1. change `v` to `yParity` in the signature
226
+ * 2. change the gas fields
227
+ */
228
+ const mockTxType = {
229
+ type: "0x0",
230
+ } as const;
231
+
232
+ /**
233
+ * Theoretically we could implement this in Paima, but it takes time to calculate and basically 0 dApps and tools use this
234
+ * In fact, it's being set to empty-string with an EIP from 2024
235
+ * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7668.md
236
+ */
237
+ const mockLogBloom = {
238
+ // TODO: unclear how this should be handled post-7668, but I assume `0x0` is the right approach
239
+ // https://github.com/wevm/viem/pull/2587
240
+ logsBloom: "0x0",
241
+ } as const;
242
+
243
+ /**
244
+ * This is non-null when the transaction created a new contract
245
+ * However, Paima doesn't support creating new contracts on the L2 side, so it's always null
246
+ * Note: you could argue that maybe we might want this in a few cases:
247
+ * 1. Dynamic primitives (so this would be the address in an underlying chain)
248
+ * 2. Dynamically creating new precompiles
249
+ */
250
+ const mockContractAddress = {
251
+ contractAddress: null,
252
+ } as const;
253
+
254
+ /**
255
+ * No concept of miners in Paima (it's a based rollup) nor PoW
256
+ */
257
+ const mockMiner = {
258
+ miner: "0x0",
259
+ mixHash: "0x0",
260
+ /** note: `nonce` here isn't a transaction nonce, but rather a nonce for PoW */
261
+ nonce: "0x0",
262
+ difficulty: "0x0",
263
+ totalDifficulty: "0x0",
264
+ } as const;
265
+
266
+ /**
267
+ * No concept of uncles in Paima as we only consider finalized blocks
268
+ */
269
+ const mockUncles = {
270
+ sha3Uncles: "0x0" as const,
271
+ uncles: [] as `0x${string}`[],
272
+ };
273
+
274
+ /**
275
+ * Paima, similar to other chains like Solana, does not Merklize state for performance reasons
276
+ * TODO: we could expose this as an ENV var if we really want/need to
277
+ */
278
+ const mockRoots = {
279
+ transactionsRoot: "0x0",
280
+ stateRoot: "0x0",
281
+ receiptsRoot: "0x0",
282
+ } as const;
283
+
284
+ /**
285
+ * This has no purpose in Ethereum, but block creators can stuff whatever they want in here
286
+ * No similar concept in Paima, but we could introduce a similar concept in theory when submitting to the L2 contract
287
+ */
288
+ const mockExtraData = {
289
+ extraData: "0x0",
290
+ } as const;
291
+
292
+ /**
293
+ * used for Pow, but not used anymore
294
+ */
295
+ const mockSealFields = {
296
+ sealFields: ["0x0" as const],
297
+ };
@@ -0,0 +1,87 @@
1
+ import {
2
+ InternalRpcError,
3
+ InvalidParamsRpcError,
4
+ MethodNotFoundRpcError,
5
+ MethodNotSupportedRpcError,
6
+ type PublicRpcSchema,
7
+ RpcRequestError,
8
+ } from "viem";
9
+ import { ENV } from "@effectstream/utils/node-env";
10
+
11
+ export type PaimaEvmRpcSchema = [
12
+ ...PublicRpcSchema,
13
+ {
14
+ Method: "eth_syncing";
15
+ Parameters?: undefined;
16
+ /**
17
+ * different EVM clients return different values for this
18
+ * but the two seemingly common fields are
19
+ * - startingBlock
20
+ * - currentBlock
21
+ */
22
+ ReturnType: boolean | {
23
+ startingBlock: `0x${string}`;
24
+ currentBlock: `0x${string}`;
25
+ };
26
+ },
27
+ ];
28
+
29
+ export type EvmRpcReturn<Method extends string> = Extract<
30
+ PaimaEvmRpcSchema[number],
31
+ { Method: Method }
32
+ >["ReturnType"];
33
+
34
+ export function createRpcRequestError(
35
+ data: Record<string, unknown>,
36
+ code: number,
37
+ msg?: string,
38
+ ): RpcRequestError {
39
+ return new RpcRequestError({
40
+ body: data,
41
+ url: `http://localhost:${ENV.EFFECTSTREAM_API_PORT}/rpc/evm`,
42
+ error: {
43
+ code: code,
44
+ message: msg ?? "No additional information provided",
45
+ },
46
+ });
47
+ }
48
+
49
+ export function createInternalRpcError(
50
+ data: Record<string, unknown>,
51
+ msg?: string,
52
+ ): InternalRpcError {
53
+ return new InternalRpcError(
54
+ createRpcRequestError(data, InternalRpcError.code, msg),
55
+ );
56
+ }
57
+
58
+ export function createMethodNotSupportedRpcError(
59
+ method: string,
60
+ msg?: string,
61
+ ): MethodNotFoundRpcError {
62
+ return new MethodNotFoundRpcError(
63
+ createRpcRequestError({}, MethodNotFoundRpcError.code, msg),
64
+ {
65
+ method,
66
+ },
67
+ );
68
+ }
69
+
70
+ export function createMethodNotFoundRpcError(
71
+ method: string,
72
+ msg?: string,
73
+ ): MethodNotSupportedRpcError {
74
+ return new MethodNotSupportedRpcError(
75
+ createRpcRequestError({}, MethodNotSupportedRpcError.code, msg),
76
+ { method },
77
+ );
78
+ }
79
+
80
+ export function createInvalidParamsError(
81
+ data: Record<string, unknown>,
82
+ msg?: string,
83
+ ): InvalidParamsRpcError {
84
+ return new InvalidParamsRpcError(
85
+ createRpcRequestError(data, InvalidParamsRpcError.code, msg),
86
+ );
87
+ }
@@ -0,0 +1,50 @@
1
+ export function isValidHexadecimal(str: string): boolean {
2
+ // Regular expression to match a valid hexadecimal string
3
+ const hexRegExp = /^[0-9a-fA-F]+$/;
4
+
5
+ // Test the string against the regular expression
6
+ return hexRegExp.test(str);
7
+ }
8
+
9
+ export function isValidBlockHash(str: string): boolean {
10
+ // Regular expression to match a valid hexadecimal string
11
+ const hexRegExp = /^0x[0-9a-fA-F]{64}$/;
12
+
13
+ // Test the string against the regular expression
14
+ return hexRegExp.test(str);
15
+ }
16
+
17
+ export function isValidTxHash(str: string): boolean {
18
+ // block hashes & tx hashes have the same format
19
+ return isValidBlockHash(str);
20
+ }
21
+
22
+ export function add0x(str: string): `0x${string}` {
23
+ if (str.startsWith("0x")) {
24
+ return str as `0x{string}`;
25
+ }
26
+ return `0x${str}`;
27
+ }
28
+
29
+ export function strip0x(str: string): string {
30
+ if (str.startsWith("0x")) {
31
+ return str.substring(2);
32
+ }
33
+ return str;
34
+ }
35
+
36
+ export const stringify: typeof JSON.stringify = (value, replacer, space) =>
37
+ JSON.stringify(
38
+ value,
39
+ (key, value_) => {
40
+ const value = typeof value_ === "bigint" ? value_.toString() : value_;
41
+ return typeof replacer === "function" ? replacer(key, value) : value;
42
+ },
43
+ space,
44
+ );
45
+
46
+ export type Prettify<T> =
47
+ & {
48
+ [K in keyof T]: T[K];
49
+ }
50
+ & {};
@@ -0,0 +1,192 @@
1
+ import type { Client } from "pg";
2
+ import type { Operation } from "effection";
3
+ import { until } from "effection";
4
+ import {
5
+ getSyncProtocolConfigSnapshot,
6
+ upsertSyncProtocolConfigSnapshot,
7
+ } from "@effectstream/db";
8
+ import { ConfigNetworkType, ConfigSyncProtocolType } from "@effectstream/config";
9
+ import type { SyncProtocolWithNetwork } from "@effectstream/config";
10
+ import { log, ComponentNames, SeverityNumber } from "@effectstream/log";
11
+ import { getEnv } from "@effectstream/utils/runtime";
12
+
13
+ /**
14
+ * Fields that must not change between restarts for each protocol type.
15
+ * Changing these would cause the node to sync from a different point in time/block,
16
+ * leading to inconsistent state.
17
+ *
18
+ * Field names by protocol:
19
+ * NTP → network.startTime, network.blockTimeMS
20
+ * Cardano CARP → syncProtocol.startSlot
21
+ * Cardano UTXOrpc → syncProtocol.startChainPoint
22
+ * EVM, Mina, Avail, Midnight,
23
+ * Bitcoin, Celestia → syncProtocol.startBlockHeight
24
+ */
25
+ function extractImmutableConfig(protocol: SyncProtocolWithNetwork): Record<string, unknown> {
26
+ if (protocol.networkType === ConfigNetworkType.NTP) {
27
+ const ntpNetwork = protocol.network as { startTime: number; blockTimeMS: number };
28
+ return {
29
+ startTime: ntpNetwork.startTime,
30
+ blockTimeMS: ntpNetwork.blockTimeMS,
31
+ };
32
+ }
33
+
34
+ if (protocol.syncProtocolType === ConfigSyncProtocolType.CARDANO_CARP_PARALLEL) {
35
+ const sp = protocol.syncProtocol as { startSlot: number };
36
+ return { startSlot: sp.startSlot };
37
+ }
38
+
39
+ if (protocol.syncProtocolType === ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL) {
40
+ const sp = protocol.syncProtocol as { startChainPoint: unknown };
41
+ return { startChainPoint: sp.startChainPoint };
42
+ }
43
+
44
+ // EVM, Mina, Avail, Midnight, Bitcoin, Celestia
45
+ const knownBlockHeightProtocols: string[] = [
46
+ ConfigSyncProtocolType.EVM_RPC_PARALLEL,
47
+ ConfigSyncProtocolType.MINA_PARALLEL,
48
+ ConfigSyncProtocolType.AVAIL_PARALLEL,
49
+ ConfigSyncProtocolType.MIDNIGHT_PARALLEL,
50
+ ConfigSyncProtocolType.BITCOIN_RPC_PARALLEL,
51
+ ConfigSyncProtocolType.CELESTIA_PARALLEL,
52
+ ConfigSyncProtocolType.NEAR_RPC_PARALLEL,
53
+ ];
54
+ if (!knownBlockHeightProtocols.includes(protocol.syncProtocolType)) {
55
+ throw new Error(
56
+ `[config-snapshot] Unhandled sync protocol type "${protocol.syncProtocolType}". ` +
57
+ `extractImmutableConfig must be updated to support this protocol.`,
58
+ );
59
+ }
60
+
61
+ const sp = protocol.syncProtocol as { startBlockHeight: number };
62
+ return { startBlockHeight: sp.startBlockHeight };
63
+ }
64
+
65
+ /**
66
+ * Checks the saved config snapshot for each sync protocol against the current config.
67
+ *
68
+ * On the first run (no snapshot in DB), the current config is persisted as the canonical snapshot.
69
+ *
70
+ * On subsequent runs, if an immutable field has changed:
71
+ * - If the `USE_DB_STARTHEIGHT` environment variable is set, the DB value is used and a warning is logged.
72
+ * - Otherwise, an error is thrown to prevent the node from starting with an inconsistent config.
73
+ *
74
+ * The function mutates `syncInfo` in place when `USE_DB_STARTHEIGHT` overrides values.
75
+ */
76
+ export function* validateAndSnapshotConfig(
77
+ syncInfo: SyncProtocolWithNetwork[],
78
+ dbConn: Client,
79
+ ): Operation<void> {
80
+ console.log("validateAndSnapshotConfig");
81
+ const useDbStartHeight = getEnv("USE_DB_STARTHEIGHT") !== undefined;
82
+
83
+ for (const protocol of syncInfo) {
84
+ const protocolName: string = (protocol.syncProtocol as { name: string }).name;
85
+ const currentImmutable = extractImmutableConfig(protocol);
86
+
87
+ const rows = yield* until(
88
+ getSyncProtocolConfigSnapshot.run({ protocolName }, dbConn),
89
+ );
90
+
91
+ if (rows.length === 0) {
92
+ // First run: persist the snapshot.
93
+ yield* until(
94
+ upsertSyncProtocolConfigSnapshot.run(
95
+ {
96
+ protocolName,
97
+ networkType: protocol.networkType,
98
+ immutableConfig: JSON.stringify(currentImmutable),
99
+ },
100
+ dbConn,
101
+ ),
102
+ );
103
+ log.remote(
104
+ ComponentNames.EFFECTSTREAM_RUNTIME,
105
+ [],
106
+ SeverityNumber.INFO,
107
+ (l) => l(`[config-snapshot] Saved initial config snapshot for protocol "${protocolName}"`),
108
+ );
109
+ continue;
110
+ }
111
+
112
+ const saved = rows[0].immutable_config as Record<string, unknown>;
113
+ const mismatches: string[] = [];
114
+ console.log("saved", saved);
115
+ for (const [key, savedValue] of Object.entries(saved)) {
116
+ console.log("key", key, savedValue, currentImmutable[key]);
117
+ // if (currentImmutable[key] !== savedValue) {
118
+ // mismatches.push(` ${key}: saved=${JSON.stringify(savedValue)}, current=${JSON.stringify(currentImmutable[key])}`);
119
+ // }
120
+ if (typeof savedValue === "object" && savedValue !== null) {
121
+ for (const [k, v] of Object.entries(savedValue)) {
122
+ if ((currentImmutable[key] as any)[k] !== v) {
123
+ mismatches.push(` ${key}.${k}: saved=${JSON.stringify(v)},\n current=${JSON.stringify((currentImmutable[key] as any)[k])}`);
124
+ }
125
+ }
126
+ } else if (currentImmutable[key] !== savedValue) {
127
+ mismatches.push(` ${key}: saved=${JSON.stringify(savedValue)},\n current=${JSON.stringify(currentImmutable[key])}`);
128
+ }
129
+ }
130
+
131
+ if (mismatches.length === 0) {
132
+ continue;
133
+ }
134
+
135
+ const mismatchDetails = mismatches.join("\n");
136
+
137
+ if (!useDbStartHeight) {
138
+ throw new Error(
139
+ `[config-snapshot] CRITICAL: Immutable config fields have changed for protocol "${protocolName}".\n` +
140
+ `${mismatchDetails}\n\n` +
141
+ `If this is intentional (e.g. you want to resume from the original start height/time stored in the DB), ` +
142
+ `set the USE_DB_STARTHEIGHT environment variable and restart the service.\n` +
143
+ `WARNING: Only set USE_DB_STARTHEIGHT if you understand the implications — mismatched start values ` +
144
+ `can cause the node to skip blocks or produce inconsistent state.`,
145
+ );
146
+ }
147
+
148
+ // USE_DB_STARTHEIGHT is set — apply the DB values and warn.
149
+ log.remote(
150
+ ComponentNames.EFFECTSTREAM_RUNTIME,
151
+ [],
152
+ SeverityNumber.WARN,
153
+ (l) =>
154
+ l(
155
+ `[config-snapshot] WARNING: Immutable config mismatch for protocol "${protocolName}". ` +
156
+ `USE_DB_STARTHEIGHT is set; overriding in-memory config with DB snapshot values.\n${mismatchDetails}`,
157
+ ),
158
+ );
159
+
160
+ applySnapshotOverrides(protocol, saved);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Applies the saved immutable values from `snapshot` onto the in-memory protocol config.
166
+ * This mutates the protocol objects so the rest of the startup uses the corrected values.
167
+ */
168
+ function applySnapshotOverrides(
169
+ protocol: SyncProtocolWithNetwork,
170
+ snapshot: Record<string, unknown>,
171
+ ): void {
172
+ if (protocol.networkType === ConfigNetworkType.NTP) {
173
+ const ntpNetwork = protocol.network as Record<string, unknown>;
174
+ if ("startTime" in snapshot) ntpNetwork["startTime"] = snapshot["startTime"];
175
+ if ("blockTimeMS" in snapshot) ntpNetwork["blockTimeMS"] = snapshot["blockTimeMS"];
176
+ return;
177
+ }
178
+
179
+ const sp = protocol.syncProtocol as Record<string, unknown>;
180
+
181
+ if (protocol.syncProtocolType === ConfigSyncProtocolType.CARDANO_CARP_PARALLEL) {
182
+ if ("startSlot" in snapshot) sp["startSlot"] = snapshot["startSlot"];
183
+ return;
184
+ }
185
+
186
+ if (protocol.syncProtocolType === ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL) {
187
+ if ("startChainPoint" in snapshot) sp["startChainPoint"] = snapshot["startChainPoint"];
188
+ return;
189
+ }
190
+
191
+ if ("startBlockHeight" in snapshot) sp["startBlockHeight"] = snapshot["startBlockHeight"];
192
+ }