@cogcoin/client 1.1.9 → 1.1.11

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 (66) hide show
  1. package/README.md +1 -1
  2. package/dist/bitcoind/client/managed-client.d.ts +2 -0
  3. package/dist/bitcoind/client/managed-client.js +6 -0
  4. package/dist/bitcoind/indexer-daemon/background-follow.d.ts +23 -0
  5. package/dist/bitcoind/indexer-daemon/background-follow.js +132 -0
  6. package/dist/bitcoind/indexer-daemon/client.d.ts +12 -0
  7. package/dist/bitcoind/indexer-daemon/client.js +137 -0
  8. package/dist/bitcoind/indexer-daemon/lifecycle.d.ts +30 -0
  9. package/dist/bitcoind/indexer-daemon/lifecycle.js +153 -0
  10. package/dist/bitcoind/indexer-daemon/process.d.ts +35 -0
  11. package/dist/bitcoind/indexer-daemon/process.js +140 -0
  12. package/dist/bitcoind/indexer-daemon/runtime.d.ts +23 -0
  13. package/dist/bitcoind/indexer-daemon/runtime.js +204 -0
  14. package/dist/bitcoind/indexer-daemon/server.d.ts +12 -0
  15. package/dist/bitcoind/indexer-daemon/server.js +87 -0
  16. package/dist/bitcoind/indexer-daemon/snapshot-leases.d.ts +23 -0
  17. package/dist/bitcoind/indexer-daemon/snapshot-leases.js +139 -0
  18. package/dist/bitcoind/indexer-daemon/status.d.ts +23 -0
  19. package/dist/bitcoind/indexer-daemon/status.js +282 -0
  20. package/dist/bitcoind/indexer-daemon/types.d.ts +141 -0
  21. package/dist/bitcoind/indexer-daemon/types.js +1 -0
  22. package/dist/bitcoind/indexer-daemon-main.js +14 -665
  23. package/dist/bitcoind/indexer-daemon.d.ts +4 -132
  24. package/dist/bitcoind/indexer-daemon.js +2 -417
  25. package/dist/bitcoind/managed-bitcoind-service-config.d.ts +30 -0
  26. package/dist/bitcoind/managed-bitcoind-service-config.js +202 -0
  27. package/dist/bitcoind/managed-bitcoind-service-lifecycle.d.ts +28 -0
  28. package/dist/bitcoind/managed-bitcoind-service-lifecycle.js +296 -0
  29. package/dist/bitcoind/managed-bitcoind-service-process.d.ts +8 -0
  30. package/dist/bitcoind/managed-bitcoind-service-process.js +48 -0
  31. package/dist/bitcoind/managed-bitcoind-service-replica.d.ts +8 -0
  32. package/dist/bitcoind/managed-bitcoind-service-replica.js +142 -0
  33. package/dist/bitcoind/managed-bitcoind-service-status.d.ts +42 -0
  34. package/dist/bitcoind/managed-bitcoind-service-status.js +170 -0
  35. package/dist/bitcoind/managed-bitcoind-service-types.d.ts +36 -0
  36. package/dist/bitcoind/managed-bitcoind-service-types.js +1 -0
  37. package/dist/bitcoind/service.d.ts +7 -63
  38. package/dist/bitcoind/service.js +7 -797
  39. package/dist/cli/mining-format.js +6 -1
  40. package/dist/cli/wallet-format/balance.js +1 -1
  41. package/dist/client/default-client.d.ts +3 -1
  42. package/dist/client/default-client.js +22 -0
  43. package/dist/types.d.ts +13 -1
  44. package/dist/wallet/fs/atomic.d.ts +11 -2
  45. package/dist/wallet/fs/atomic.js +45 -5
  46. package/dist/wallet/mining/cycle.js +4 -4
  47. package/dist/wallet/mining/engine-types.d.ts +1 -0
  48. package/dist/wallet/mining/engine-types.js +9 -1
  49. package/dist/wallet/mining/projection.d.ts +1 -0
  50. package/dist/wallet/mining/projection.js +15 -1
  51. package/dist/wallet/mining/publish.js +3 -6
  52. package/dist/wallet/mining/runner.js +30 -18
  53. package/dist/wallet/mining/visualizer-sync.js +7 -9
  54. package/dist/wallet/mining/visualizer.js +9 -7
  55. package/dist/wallet/read/context.d.ts +4 -10
  56. package/dist/wallet/read/context.js +6 -228
  57. package/dist/wallet/read/local-state.d.ts +36 -0
  58. package/dist/wallet/read/local-state.js +259 -0
  59. package/dist/wallet/read/managed-bitcoind.d.ts +30 -0
  60. package/dist/wallet/read/managed-bitcoind.js +138 -0
  61. package/dist/wallet/read/managed-indexer.d.ts +23 -0
  62. package/dist/wallet/read/managed-indexer.js +87 -0
  63. package/dist/wallet/read/managed-services.d.ts +6 -21
  64. package/dist/wallet/read/managed-services.js +23 -196
  65. package/dist/wallet/read/types.d.ts +1 -0
  66. package/package.json +1 -1
@@ -1,132 +1,4 @@
1
- import type { ManagedIndexerDaemonProbeResult } from "./managed-runtime/types.js";
2
- import { type BootstrapPhase, type BootstrapProgress, type ManagedIndexerDaemonObservedStatus, type ManagedIndexerDaemonStatus } from "./types.js";
3
- import { resolveManagedServicePaths } from "./service-paths.js";
4
- export type { IndexerDaemonCompatibility } from "./managed-runtime/types.js";
5
- export declare const INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED = "indexer_daemon_background_follow_recovery_failed";
6
- interface DaemonRequest {
7
- id: string;
8
- method: "GetStatus" | "OpenSnapshot" | "ReadSnapshot" | "CloseSnapshot" | "ResumeBackgroundFollow";
9
- token?: string;
10
- }
11
- interface DaemonResponse {
12
- id: string;
13
- ok: boolean;
14
- result?: unknown;
15
- error?: string;
16
- }
17
- export interface IndexerSnapshotHandle {
18
- token: string;
19
- expiresAtUnixMs: number;
20
- serviceApiVersion: string;
21
- binaryVersion: string;
22
- buildId: string | null;
23
- walletRootId: string;
24
- daemonInstanceId: string;
25
- schemaVersion: string;
26
- processId: number | null;
27
- startedAtUnixMs: number;
28
- state: ManagedIndexerDaemonStatus["state"];
29
- heartbeatAtUnixMs: number;
30
- rpcReachable: boolean;
31
- coreBestHeight: number | null;
32
- coreBestHash: string | null;
33
- appliedTipHeight: number | null;
34
- appliedTipHash: string | null;
35
- snapshotSeq: string | null;
36
- backlogBlocks: number | null;
37
- reorgDepth: number | null;
38
- lastAppliedAtUnixMs: number | null;
39
- activeSnapshotCount: number;
40
- lastError: string | null;
41
- backgroundFollowActive: boolean;
42
- bootstrapPhase: BootstrapPhase | null;
43
- bootstrapProgress: BootstrapProgress | null;
44
- cogcoinSyncHeight: number | null;
45
- cogcoinSyncTargetHeight: number | null;
46
- tipHeight: number | null;
47
- tipHash: string | null;
48
- openedAtUnixMs: number;
49
- }
50
- export interface IndexerSnapshotPayload {
51
- token: string;
52
- stateBase64: string;
53
- serviceApiVersion: string;
54
- schemaVersion: string;
55
- walletRootId: string;
56
- daemonInstanceId: string;
57
- processId: number | null;
58
- startedAtUnixMs: number;
59
- snapshotSeq: string | null;
60
- tipHeight: number | null;
61
- tipHash: string | null;
62
- openedAtUnixMs: number;
63
- tip: {
64
- height: number;
65
- blockHashHex: string;
66
- previousHashHex: string | null;
67
- stateHashHex: string | null;
68
- } | null;
69
- expiresAtUnixMs: number;
70
- }
71
- export interface IndexerDaemonClient {
72
- getStatus(): Promise<ManagedIndexerDaemonObservedStatus>;
73
- openSnapshot(): Promise<IndexerSnapshotHandle>;
74
- readSnapshot(token: string): Promise<IndexerSnapshotPayload>;
75
- closeSnapshot(token: string): Promise<void>;
76
- resumeBackgroundFollow(): Promise<void>;
77
- close(): Promise<void>;
78
- }
79
- export type IndexerDaemonProbeResult = ManagedIndexerDaemonProbeResult<IndexerDaemonClient>;
80
- export interface IndexerDaemonStopResult {
81
- status: "stopped" | "not-running";
82
- walletRootId: string;
83
- }
84
- export interface CoherentIndexerSnapshotLease {
85
- payload: IndexerSnapshotPayload;
86
- status: ManagedIndexerDaemonStatus;
87
- }
88
- type ManagedIndexerDaemonServiceLifetime = "persistent" | "ephemeral";
89
- export declare function stopIndexerDaemonServiceWithLockHeld(options: {
90
- dataDir: string;
91
- walletRootId?: string;
92
- shutdownTimeoutMs?: number;
93
- paths?: ReturnType<typeof resolveManagedServicePaths>;
94
- processId?: number | null;
95
- }): Promise<IndexerDaemonStopResult>;
96
- export declare function probeIndexerDaemon(options: {
97
- dataDir: string;
98
- walletRootId?: string;
99
- }): Promise<IndexerDaemonProbeResult>;
100
- export declare function readSnapshotWithRetry(daemon: IndexerDaemonClient, expectedWalletRootId: string): Promise<CoherentIndexerSnapshotLease>;
101
- export declare function readObservedIndexerDaemonStatus(options: {
102
- dataDir: string;
103
- walletRootId?: string;
104
- }): Promise<ManagedIndexerDaemonObservedStatus | null>;
105
- export declare function attachOrStartIndexerDaemon(options: {
106
- dataDir: string;
107
- databasePath: string;
108
- walletRootId?: string;
109
- startupTimeoutMs?: number;
110
- shutdownTimeoutMs?: number;
111
- serviceLifetime?: ManagedIndexerDaemonServiceLifetime;
112
- ensureBackgroundFollow?: boolean;
113
- expectedBinaryVersion?: string | null;
114
- }): Promise<IndexerDaemonClient>;
115
- export declare function stopIndexerDaemonService(options: {
116
- dataDir: string;
117
- walletRootId?: string;
118
- shutdownTimeoutMs?: number;
119
- }): Promise<IndexerDaemonStopResult>;
120
- export declare function shutdownIndexerDaemonForTesting(options: {
121
- dataDir: string;
122
- walletRootId?: string;
123
- }): Promise<void>;
124
- export declare function readIndexerDaemonStatusForTesting(options: {
125
- dataDir: string;
126
- walletRootId?: string;
127
- }): Promise<ManagedIndexerDaemonStatus | null>;
128
- export declare function writeIndexerDaemonStatusForTesting(options: {
129
- dataDir: string;
130
- walletRootId?: string;
131
- }, status: ManagedIndexerDaemonStatus): Promise<void>;
132
- export type { DaemonRequest, DaemonResponse };
1
+ export { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, stopIndexerDaemonService, } from "./indexer-daemon/lifecycle.js";
2
+ export { readIndexerDaemonStatusForTesting, shutdownIndexerDaemonForTesting, stopIndexerDaemonServiceWithLockHeld, writeIndexerDaemonStatusForTesting, } from "./indexer-daemon/process.js";
3
+ export type { CoherentIndexerSnapshotLease, DaemonRequest, DaemonResponse, IndexerDaemonClient, IndexerDaemonStopResult, IndexerSnapshotHandle, IndexerSnapshotPayload, ManagedIndexerDaemonOwnership, ManagedIndexerDaemonServiceLifetime, } from "./indexer-daemon/types.js";
4
+ export type { IndexerDaemonCompatibility, IndexerDaemonProbeResult, } from "./indexer-daemon/lifecycle.js";
@@ -1,417 +1,2 @@
1
- import { randomUUID } from "node:crypto";
2
- import { spawn } from "node:child_process";
3
- import { mkdir, rm } from "node:fs/promises";
4
- import { fileURLToPath } from "node:url";
5
- import net from "node:net";
6
- import { acquireFileLock, FileLockBusyError } from "../wallet/fs/lock.js";
7
- import { writeRuntimeStatusFile } from "../wallet/fs/status-file.js";
8
- import { buildManagedIndexerStatusFromSnapshotHandle, mapIndexerDaemonTransportError, mapIndexerDaemonValidationError, validateIndexerDaemonStatus, validateIndexerSnapshotHandle, validateIndexerSnapshotPayload, } from "./managed-runtime/indexer-policy.js";
9
- import { attachOrStartManagedIndexerRuntime } from "./managed-runtime/indexer-runtime.js";
10
- import { readJsonFileIfPresent } from "./managed-runtime/status.js";
11
- import {} from "./types.js";
12
- import { resolveManagedServicePaths, UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
13
- const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
14
- const DEFAULT_SHUTDOWN_TIMEOUT_MS = 5_000;
15
- const FORCE_KILL_TIMEOUT_MS = 5_000;
16
- const INDEXER_DAEMON_REQUEST_TIMEOUT_MS = 15_000;
17
- const INDEXER_DAEMON_RESUME_BACKGROUND_FOLLOW_REQUEST_TIMEOUT_MS = 35_000;
18
- const INDEXER_DAEMON_BACKGROUND_FOLLOW_NOT_ACTIVE = "indexer_daemon_background_follow_not_active";
19
- export const INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED = "indexer_daemon_background_follow_recovery_failed";
20
- async function isProcessAlive(pid) {
21
- if (pid === null) {
22
- return false;
23
- }
24
- try {
25
- process.kill(pid, 0);
26
- return true;
27
- }
28
- catch (error) {
29
- if (error instanceof Error && "code" in error && error.code === "ESRCH") {
30
- return false;
31
- }
32
- return true;
33
- }
34
- }
35
- function sleep(ms) {
36
- return new Promise((resolve) => {
37
- setTimeout(resolve, ms);
38
- });
39
- }
40
- async function waitForProcessExit(pid, timeoutMs, errorCode) {
41
- const deadline = Date.now() + timeoutMs;
42
- while (Date.now() < deadline) {
43
- if (!await isProcessAlive(pid)) {
44
- return;
45
- }
46
- await sleep(50);
47
- }
48
- throw new Error(errorCode);
49
- }
50
- async function clearIndexerDaemonRuntimeArtifacts(paths) {
51
- await rm(paths.indexerDaemonStatusPath, { force: true }).catch(() => undefined);
52
- await rm(paths.indexerDaemonSocketPath, { force: true }).catch(() => undefined);
53
- }
54
- function ignoreProcessNotFound(error) {
55
- if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
56
- throw error;
57
- }
58
- }
59
- export async function stopIndexerDaemonServiceWithLockHeld(options) {
60
- const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
61
- const paths = options.paths ?? resolveManagedServicePaths(options.dataDir, walletRootId);
62
- const status = await readJsonFileIfPresent(paths.indexerDaemonStatusPath);
63
- const processId = options.processId ?? status?.processId ?? null;
64
- if (status === null || processId === null || !await isProcessAlive(processId)) {
65
- await clearIndexerDaemonRuntimeArtifacts(paths);
66
- return {
67
- status: "not-running",
68
- walletRootId,
69
- };
70
- }
71
- try {
72
- process.kill(processId, "SIGTERM");
73
- }
74
- catch (error) {
75
- ignoreProcessNotFound(error);
76
- }
77
- try {
78
- await waitForProcessExit(processId, options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS, "indexer_daemon_stop_timeout");
79
- }
80
- catch (error) {
81
- if (!(error instanceof Error) || error.message !== "indexer_daemon_stop_timeout") {
82
- throw error;
83
- }
84
- try {
85
- process.kill(processId, "SIGKILL");
86
- }
87
- catch (killError) {
88
- ignoreProcessNotFound(killError);
89
- }
90
- await waitForProcessExit(processId, FORCE_KILL_TIMEOUT_MS, "indexer_daemon_stop_timeout");
91
- }
92
- await clearIndexerDaemonRuntimeArtifacts(paths);
93
- return {
94
- status: "stopped",
95
- walletRootId,
96
- };
97
- }
98
- function createIndexerDaemonClient(socketPath, closeOptions = null) {
99
- let closed = false;
100
- async function sendRequest(request) {
101
- return new Promise((resolve, reject) => {
102
- const socket = net.createConnection(socketPath);
103
- let buffer = "";
104
- let settled = false;
105
- const finish = (handler) => {
106
- if (settled) {
107
- return;
108
- }
109
- settled = true;
110
- socket.destroy();
111
- handler();
112
- };
113
- socket.setTimeout(request.method === "ResumeBackgroundFollow"
114
- ? INDEXER_DAEMON_RESUME_BACKGROUND_FOLLOW_REQUEST_TIMEOUT_MS
115
- : INDEXER_DAEMON_REQUEST_TIMEOUT_MS);
116
- socket.on("connect", () => {
117
- socket.write(`${JSON.stringify(request)}\n`);
118
- });
119
- socket.on("data", (chunk) => {
120
- buffer += chunk.toString("utf8");
121
- let newlineIndex = buffer.indexOf("\n");
122
- while (newlineIndex >= 0) {
123
- const line = buffer.slice(0, newlineIndex);
124
- buffer = buffer.slice(newlineIndex + 1);
125
- if (line.trim().length === 0) {
126
- newlineIndex = buffer.indexOf("\n");
127
- continue;
128
- }
129
- let response;
130
- try {
131
- response = JSON.parse(line);
132
- }
133
- catch (error) {
134
- finish(() => reject(error));
135
- return;
136
- }
137
- if (response.id !== request.id) {
138
- newlineIndex = buffer.indexOf("\n");
139
- continue;
140
- }
141
- if (!response.ok) {
142
- finish(() => reject(new Error(response.error ?? "indexer_daemon_request_failed")));
143
- return;
144
- }
145
- finish(() => resolve(response.result));
146
- return;
147
- }
148
- });
149
- socket.on("timeout", () => {
150
- finish(() => reject(new Error("indexer_daemon_request_timeout")));
151
- });
152
- socket.on("error", (error) => {
153
- finish(() => reject(error));
154
- });
155
- socket.on("end", () => {
156
- if (!settled) {
157
- finish(() => reject(new Error("indexer_daemon_connection_closed")));
158
- }
159
- });
160
- });
161
- }
162
- return {
163
- getStatus() {
164
- return sendRequest({
165
- id: randomUUID(),
166
- method: "GetStatus",
167
- });
168
- },
169
- openSnapshot() {
170
- return sendRequest({
171
- id: randomUUID(),
172
- method: "OpenSnapshot",
173
- });
174
- },
175
- readSnapshot(token) {
176
- return sendRequest({
177
- id: randomUUID(),
178
- method: "ReadSnapshot",
179
- token,
180
- });
181
- },
182
- async closeSnapshot(token) {
183
- await sendRequest({
184
- id: randomUUID(),
185
- method: "CloseSnapshot",
186
- token,
187
- });
188
- },
189
- async resumeBackgroundFollow() {
190
- await sendRequest({
191
- id: randomUUID(),
192
- method: "ResumeBackgroundFollow",
193
- });
194
- },
195
- async close() {
196
- if (closed) {
197
- return;
198
- }
199
- closed = true;
200
- if (closeOptions === null || closeOptions.serviceLifetime !== "ephemeral" || closeOptions.ownership === "attached") {
201
- return;
202
- }
203
- await stopIndexerDaemonService({
204
- dataDir: closeOptions.dataDir,
205
- walletRootId: closeOptions.walletRootId,
206
- shutdownTimeoutMs: closeOptions.shutdownTimeoutMs,
207
- });
208
- },
209
- };
210
- }
211
- async function probeIndexerDaemonAtSocket(socketPath, expectedWalletRootId) {
212
- const client = createIndexerDaemonClient(socketPath);
213
- try {
214
- const status = await client.getStatus();
215
- try {
216
- validateIndexerDaemonStatus(status, expectedWalletRootId);
217
- return {
218
- compatibility: "compatible",
219
- status,
220
- client,
221
- error: null,
222
- };
223
- }
224
- catch (error) {
225
- await client.close().catch(() => undefined);
226
- return mapIndexerDaemonValidationError(error, status);
227
- }
228
- }
229
- catch (error) {
230
- await client.close().catch(() => undefined);
231
- return mapIndexerDaemonTransportError(error);
232
- }
233
- }
234
- async function waitForIndexerDaemon(dataDir, walletRootId, timeoutMs) {
235
- const paths = resolveManagedServicePaths(dataDir, walletRootId);
236
- const deadline = Date.now() + timeoutMs;
237
- while (Date.now() < deadline) {
238
- const probe = await probeIndexerDaemonAtSocket(paths.indexerDaemonSocketPath, walletRootId);
239
- if (probe.compatibility === "compatible" && probe.client !== null) {
240
- await probe.client.close().catch(() => undefined);
241
- return;
242
- }
243
- if (probe.compatibility !== "unreachable") {
244
- throw new Error(probe.error ?? "indexer_daemon_protocol_error");
245
- }
246
- await sleep(250);
247
- }
248
- throw new Error("indexer_daemon_start_timeout");
249
- }
250
- export async function probeIndexerDaemon(options) {
251
- const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
252
- const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
253
- return probeIndexerDaemonAtSocket(paths.indexerDaemonSocketPath, walletRootId);
254
- }
255
- export async function readSnapshotWithRetry(daemon, expectedWalletRootId) {
256
- let lastError = null;
257
- for (let attempt = 0; attempt < 2; attempt += 1) {
258
- const handle = await daemon.openSnapshot();
259
- try {
260
- validateIndexerSnapshotHandle(handle, expectedWalletRootId);
261
- const payload = await daemon.readSnapshot(handle.token);
262
- validateIndexerSnapshotPayload(payload, handle, expectedWalletRootId);
263
- return {
264
- payload,
265
- status: buildManagedIndexerStatusFromSnapshotHandle(handle),
266
- };
267
- }
268
- catch (error) {
269
- lastError = error;
270
- if (!(error instanceof Error)
271
- || (error.message !== "indexer_daemon_snapshot_invalid" && error.message !== "indexer_daemon_snapshot_rotated")
272
- || attempt > 0) {
273
- throw error;
274
- }
275
- }
276
- finally {
277
- await daemon.closeSnapshot(handle.token).catch(() => undefined);
278
- }
279
- }
280
- throw lastError instanceof Error ? lastError : new Error("indexer_daemon_snapshot_invalid");
281
- }
282
- export async function readObservedIndexerDaemonStatus(options) {
283
- const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
284
- const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
285
- return readJsonFileIfPresent(paths.indexerDaemonStatusPath);
286
- }
287
- export async function attachOrStartIndexerDaemon(options) {
288
- const requestBackgroundFollow = async (client, observedStatus = null) => {
289
- if (options.ensureBackgroundFollow !== true) {
290
- return client;
291
- }
292
- if (observedStatus?.backgroundFollowActive === true) {
293
- return client;
294
- }
295
- await client.resumeBackgroundFollow();
296
- const status = await client.getStatus();
297
- if (status.backgroundFollowActive !== true) {
298
- throw new Error(INDEXER_DAEMON_BACKGROUND_FOLLOW_NOT_ACTIVE);
299
- }
300
- return client;
301
- };
302
- const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
303
- const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
304
- const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
305
- const serviceLifetime = options.serviceLifetime ?? "persistent";
306
- const expectedBinaryVersion = options.expectedBinaryVersion ?? null;
307
- const startDaemon = async () => {
308
- await mkdir(paths.indexerServiceRoot, { recursive: true });
309
- const daemonEntryPath = fileURLToPath(new URL("./indexer-daemon-main.js", import.meta.url));
310
- const spawnOptions = serviceLifetime === "ephemeral"
311
- ? {
312
- stdio: "ignore",
313
- }
314
- : {
315
- detached: true,
316
- stdio: "ignore",
317
- };
318
- const child = spawn(process.execPath, [
319
- daemonEntryPath,
320
- `--data-dir=${options.dataDir}`,
321
- `--database-path=${options.databasePath}`,
322
- `--wallet-root-id=${walletRootId}`,
323
- ], {
324
- ...spawnOptions,
325
- });
326
- if (serviceLifetime !== "ephemeral") {
327
- child.unref();
328
- }
329
- try {
330
- await waitForIndexerDaemon(options.dataDir, walletRootId, startupTimeoutMs);
331
- }
332
- catch (error) {
333
- if (child.pid !== undefined) {
334
- try {
335
- process.kill(child.pid, "SIGTERM");
336
- }
337
- catch {
338
- // ignore shutdown failures while unwinding startup errors
339
- }
340
- }
341
- throw error;
342
- }
343
- return createIndexerDaemonClient(paths.indexerDaemonSocketPath, {
344
- dataDir: options.dataDir,
345
- walletRootId,
346
- serviceLifetime,
347
- ownership: "started",
348
- shutdownTimeoutMs: options.shutdownTimeoutMs,
349
- });
350
- };
351
- return attachOrStartManagedIndexerRuntime({
352
- ...options,
353
- walletRootId,
354
- startupTimeoutMs,
355
- expectedBinaryVersion,
356
- }, {
357
- getPaths: (runtimeOptions) => resolveManagedServicePaths(runtimeOptions.dataDir, runtimeOptions.walletRootId),
358
- probeDaemon: async (runtimeOptions, runtimePaths) => probeIndexerDaemonAtSocket(runtimePaths.indexerDaemonSocketPath, runtimeOptions.walletRootId),
359
- requestBackgroundFollow,
360
- closeClient: async (client) => {
361
- await client.close();
362
- },
363
- acquireStartLock: async (runtimeOptions, runtimePaths) => acquireFileLock(runtimePaths.indexerDaemonLockPath, {
364
- purpose: "indexer-daemon-start",
365
- walletRootId: runtimeOptions.walletRootId,
366
- dataDir: runtimeOptions.dataDir,
367
- databasePath: runtimeOptions.databasePath,
368
- }),
369
- startDaemon: async () => startDaemon(),
370
- stopWithLockHeld: async (runtimeOptions, runtimePaths, processId) => stopIndexerDaemonServiceWithLockHeld({
371
- dataDir: runtimeOptions.dataDir,
372
- walletRootId: runtimeOptions.walletRootId,
373
- shutdownTimeoutMs: runtimeOptions.shutdownTimeoutMs,
374
- paths: resolveManagedServicePaths(runtimeOptions.dataDir, runtimeOptions.walletRootId),
375
- processId,
376
- }),
377
- isLockBusyError: (error) => error instanceof FileLockBusyError,
378
- sleep,
379
- });
380
- }
381
- export async function stopIndexerDaemonService(options) {
382
- const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
383
- const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
384
- const lock = await acquireFileLock(paths.indexerDaemonLockPath, {
385
- purpose: "indexer-daemon-stop",
386
- walletRootId,
387
- dataDir: options.dataDir,
388
- });
389
- try {
390
- return await stopIndexerDaemonServiceWithLockHeld({
391
- ...options,
392
- walletRootId,
393
- paths,
394
- });
395
- }
396
- finally {
397
- await lock.release();
398
- }
399
- }
400
- export async function shutdownIndexerDaemonForTesting(options) {
401
- await stopIndexerDaemonService(options).catch(async () => {
402
- const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
403
- const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
404
- await rm(paths.indexerDaemonSocketPath, { force: true }).catch(() => undefined);
405
- });
406
- }
407
- export async function readIndexerDaemonStatusForTesting(options) {
408
- const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
409
- const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
410
- return readJsonFileIfPresent(paths.indexerDaemonStatusPath);
411
- }
412
- export async function writeIndexerDaemonStatusForTesting(options, status) {
413
- const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
414
- const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
415
- await mkdir(paths.indexerServiceRoot, { recursive: true });
416
- await writeRuntimeStatusFile(paths.indexerDaemonStatusPath, status);
417
- }
1
+ export { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, stopIndexerDaemonService, } from "./indexer-daemon/lifecycle.js";
2
+ export { readIndexerDaemonStatusForTesting, shutdownIndexerDaemonForTesting, stopIndexerDaemonServiceWithLockHeld, writeIndexerDaemonStatusForTesting, } from "./indexer-daemon/process.js";
@@ -0,0 +1,30 @@
1
+ import { writeJsonFileAtomic } from "../wallet/fs/atomic.js";
2
+ import type { BitcoindRpcConfig, BitcoindZmqConfig, ManagedBitcoindRuntimeConfig, ManagedBitcoindServiceStatus } from "./types.js";
3
+ import type { ManagedBitcoindServiceOptions } from "./managed-bitcoind-service-types.js";
4
+ export declare const LOCAL_HOST = "127.0.0.1";
5
+ export declare const SUPPORTED_BITCOIND_VERSION = "30.2.0";
6
+ export interface ManagedBitcoindRuntimeConfigFile {
7
+ chain: ManagedBitcoindRuntimeConfig["chain"];
8
+ rpc: ManagedBitcoindRuntimeConfig["rpc"];
9
+ zmqPort: ManagedBitcoindRuntimeConfig["zmqPort"];
10
+ p2pPort: ManagedBitcoindRuntimeConfig["p2pPort"];
11
+ getblockArchiveEndHeight: ManagedBitcoindRuntimeConfig["getblockArchiveEndHeight"];
12
+ getblockArchiveSha256: ManagedBitcoindRuntimeConfig["getblockArchiveSha256"];
13
+ }
14
+ type ManagedBitcoindRuntimeConfigFileDeps = {
15
+ readJsonFileIfPresent: (filePath: string) => Promise<ManagedBitcoindRuntimeConfigFile | null>;
16
+ writeJsonFileAtomic: typeof writeJsonFileAtomic;
17
+ };
18
+ export declare function resolveManagedBitcoindDbcacheMiB(totalRamBytes: number): number;
19
+ export declare function detectManagedBitcoindDbcacheMiB(): number;
20
+ export declare function verifyManagedBitcoindVersion(bitcoindPath: string): Promise<void>;
21
+ export declare function getManagedBitcoindCookieFile(dataDir: string, chain: "main" | "regtest"): string;
22
+ export declare function resolveManagedBitcoindRuntimeConfig(statusPath: string, configPath: string, options: ManagedBitcoindServiceOptions): Promise<ManagedBitcoindRuntimeConfig>;
23
+ export declare function createManagedBitcoindRuntimeConfigFilePayload(runtimeConfig: ManagedBitcoindRuntimeConfig): ManagedBitcoindRuntimeConfigFile;
24
+ export declare function createManagedBitcoindRuntimeConfigFilePayloadFromStatus(status: Pick<ManagedBitcoindServiceStatus, "chain" | "rpc" | "zmq" | "p2pPort" | "getblockArchiveEndHeight" | "getblockArchiveSha256">): ManagedBitcoindRuntimeConfigFile;
25
+ export declare function writeManagedBitcoindRuntimeConfigFile(filePath: string, runtimeConfig: ManagedBitcoindRuntimeConfig, dependencies?: ManagedBitcoindRuntimeConfigFileDeps): Promise<void>;
26
+ export declare function writeManagedBitcoindRuntimeConfigFileFromStatus(filePath: string, status: Pick<ManagedBitcoindServiceStatus, "chain" | "rpc" | "zmq" | "p2pPort" | "getblockArchiveEndHeight" | "getblockArchiveSha256">, dependencies?: ManagedBitcoindRuntimeConfigFileDeps): Promise<void>;
27
+ export declare function writeBitcoinConfForTesting(filePath: string, options: ManagedBitcoindServiceOptions, runtimeConfig: ManagedBitcoindRuntimeConfig): Promise<void>;
28
+ export declare function buildManagedServiceArgsForTesting(options: ManagedBitcoindServiceOptions, runtimeConfig: ManagedBitcoindRuntimeConfig): string[];
29
+ export declare function waitForManagedBitcoindCookie(cookieFile: string, timeoutMs: number, sleepImpl: (ms: number) => Promise<void>): Promise<void>;
30
+ export type { BitcoindRpcConfig, BitcoindZmqConfig, };