@cogcoin/client 0.5.3 → 0.5.5

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 (70) hide show
  1. package/README.md +11 -3
  2. package/dist/app-paths.d.ts +2 -0
  3. package/dist/app-paths.js +4 -0
  4. package/dist/art/wallet.txt +10 -0
  5. package/dist/bitcoind/bootstrap/chunk-manifest.d.ts +14 -0
  6. package/dist/bitcoind/bootstrap/chunk-manifest.js +85 -0
  7. package/dist/bitcoind/bootstrap/chunk-recovery.d.ts +4 -0
  8. package/dist/bitcoind/bootstrap/chunk-recovery.js +122 -0
  9. package/dist/bitcoind/bootstrap/constants.d.ts +3 -1
  10. package/dist/bitcoind/bootstrap/constants.js +3 -1
  11. package/dist/bitcoind/bootstrap/controller.d.ts +6 -1
  12. package/dist/bitcoind/bootstrap/controller.js +14 -7
  13. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.d.ts +2 -0
  14. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.js +2309 -0
  15. package/dist/bitcoind/bootstrap/download.js +177 -83
  16. package/dist/bitcoind/bootstrap/headers.d.ts +4 -2
  17. package/dist/bitcoind/bootstrap/headers.js +29 -4
  18. package/dist/bitcoind/bootstrap/state.d.ts +11 -1
  19. package/dist/bitcoind/bootstrap/state.js +50 -23
  20. package/dist/bitcoind/bootstrap/types.d.ts +12 -1
  21. package/dist/bitcoind/client/internal-types.d.ts +1 -0
  22. package/dist/bitcoind/client/managed-client.js +27 -13
  23. package/dist/bitcoind/client/sync-engine.js +42 -5
  24. package/dist/bitcoind/errors.js +9 -0
  25. package/dist/bitcoind/indexer-daemon.d.ts +9 -0
  26. package/dist/bitcoind/indexer-daemon.js +51 -14
  27. package/dist/bitcoind/service.d.ts +9 -0
  28. package/dist/bitcoind/service.js +65 -24
  29. package/dist/bitcoind/testing.d.ts +2 -2
  30. package/dist/bitcoind/testing.js +2 -2
  31. package/dist/bitcoind/types.d.ts +9 -0
  32. package/dist/cli/commands/service-runtime.d.ts +2 -0
  33. package/dist/cli/commands/service-runtime.js +432 -0
  34. package/dist/cli/commands/wallet-admin.js +227 -132
  35. package/dist/cli/commands/wallet-mutation.js +597 -580
  36. package/dist/cli/context.js +23 -1
  37. package/dist/cli/mutation-json.d.ts +17 -1
  38. package/dist/cli/mutation-json.js +42 -0
  39. package/dist/cli/output.js +113 -2
  40. package/dist/cli/parse.d.ts +1 -1
  41. package/dist/cli/parse.js +65 -0
  42. package/dist/cli/preview-json.d.ts +19 -1
  43. package/dist/cli/preview-json.js +31 -0
  44. package/dist/cli/prompt.js +40 -12
  45. package/dist/cli/runner.js +12 -0
  46. package/dist/cli/signals.d.ts +1 -0
  47. package/dist/cli/signals.js +44 -0
  48. package/dist/cli/types.d.ts +24 -2
  49. package/dist/cli/types.js +6 -0
  50. package/dist/cli/wallet-format.js +3 -0
  51. package/dist/cli/workflow-hints.d.ts +1 -0
  52. package/dist/cli/workflow-hints.js +3 -0
  53. package/dist/wallet/fs/lock.d.ts +2 -0
  54. package/dist/wallet/fs/lock.js +32 -0
  55. package/dist/wallet/lifecycle.d.ts +19 -1
  56. package/dist/wallet/lifecycle.js +315 -8
  57. package/dist/wallet/material.d.ts +2 -0
  58. package/dist/wallet/material.js +8 -1
  59. package/dist/wallet/mnemonic-art.d.ts +2 -0
  60. package/dist/wallet/mnemonic-art.js +54 -0
  61. package/dist/wallet/reset.d.ts +61 -0
  62. package/dist/wallet/reset.js +781 -0
  63. package/dist/wallet/runtime.d.ts +2 -0
  64. package/dist/wallet/runtime.js +2 -0
  65. package/dist/wallet/state/pending-init.d.ts +24 -0
  66. package/dist/wallet/state/pending-init.js +59 -0
  67. package/dist/wallet/state/provider.d.ts +1 -0
  68. package/dist/wallet/state/provider.js +7 -1
  69. package/dist/wallet/types.d.ts +8 -0
  70. package/package.json +6 -4
@@ -2,9 +2,39 @@ import { formatManagedSyncErrorMessage } from "../errors.js";
2
2
  import { normalizeRpcBlock } from "../normalize.js";
3
3
  import { estimateEtaSeconds } from "./rate-tracker.js";
4
4
  const DEFAULT_SYNC_CATCH_UP_POLL_MS = 2_000;
5
- function sleep(ms) {
6
- return new Promise((resolve) => {
7
- setTimeout(resolve, ms);
5
+ function createAbortError(signal) {
6
+ const reason = signal?.reason;
7
+ if (reason instanceof Error) {
8
+ return reason;
9
+ }
10
+ const error = new Error("managed_sync_aborted");
11
+ error.name = "AbortError";
12
+ return error;
13
+ }
14
+ function isAbortError(error, signal) {
15
+ if (signal?.aborted) {
16
+ return true;
17
+ }
18
+ return error instanceof Error
19
+ && (error.name === "AbortError" || error.message === "managed_sync_aborted");
20
+ }
21
+ function throwIfAborted(signal) {
22
+ if (signal?.aborted) {
23
+ throw createAbortError(signal);
24
+ }
25
+ }
26
+ function sleep(ms, signal) {
27
+ return new Promise((resolve, reject) => {
28
+ const timer = setTimeout(() => {
29
+ signal?.removeEventListener("abort", onAbort);
30
+ resolve();
31
+ }, ms);
32
+ const onAbort = () => {
33
+ clearTimeout(timer);
34
+ signal?.removeEventListener("abort", onAbort);
35
+ reject(createAbortError(signal));
36
+ };
37
+ signal?.addEventListener("abort", onAbort, { once: true });
8
38
  });
9
39
  }
10
40
  async function setBitcoinSyncProgress(dependencies, info) {
@@ -81,9 +111,12 @@ async function syncAgainstBestHeight(dependencies, bestHeight) {
81
111
  }
82
112
  export async function syncToTip(dependencies) {
83
113
  try {
114
+ throwIfAborted(dependencies.abortSignal);
84
115
  await dependencies.node.validate();
85
116
  const indexedTipBeforeBootstrap = await dependencies.client.getTip();
86
- await dependencies.bootstrap.ensureReady(indexedTipBeforeBootstrap, dependencies.node.expectedChain);
117
+ await dependencies.bootstrap.ensureReady(indexedTipBeforeBootstrap, dependencies.node.expectedChain, {
118
+ signal: dependencies.abortSignal,
119
+ });
87
120
  const startTip = await dependencies.client.getTip();
88
121
  const aggregate = {
89
122
  appliedBlocks: 0,
@@ -95,6 +128,7 @@ export async function syncToTip(dependencies) {
95
128
  bestHashHex: "",
96
129
  };
97
130
  while (true) {
131
+ throwIfAborted(dependencies.abortSignal);
98
132
  const startInfo = await dependencies.rpc.getBlockchainInfo();
99
133
  await setBitcoinSyncProgress(dependencies, startInfo);
100
134
  const pass = await syncAgainstBestHeight(dependencies, startInfo.blocks);
@@ -129,10 +163,13 @@ export async function syncToTip(dependencies) {
129
163
  if (endInfo.blocks >= dependencies.startHeight && finalTip?.height !== endInfo.blocks) {
130
164
  continue;
131
165
  }
132
- await sleep(DEFAULT_SYNC_CATCH_UP_POLL_MS);
166
+ await sleep(DEFAULT_SYNC_CATCH_UP_POLL_MS, dependencies.abortSignal);
133
167
  }
134
168
  }
135
169
  catch (error) {
170
+ if (isAbortError(error, dependencies.abortSignal)) {
171
+ throw createAbortError(dependencies.abortSignal);
172
+ }
136
173
  const message = formatManagedSyncErrorMessage(error instanceof Error ? error.message : String(error));
137
174
  await dependencies.progress.setPhase("error", {
138
175
  lastError: message,
@@ -18,6 +18,15 @@ export function formatManagedSyncErrorMessage(message) {
18
18
  if (message === "snapshot_response_body_missing") {
19
19
  return appendNextStep("Snapshot server returned an empty response body.", "Wait a moment, confirm the snapshot host is reachable, then rerun sync.");
20
20
  }
21
+ if (message === "snapshot_resume_requires_partial_content") {
22
+ return appendNextStep("Snapshot server ignored the resume request for a partial download.", "Wait a moment and rerun sync. If this keeps happening, confirm the snapshot host supports HTTP range requests.");
23
+ }
24
+ if (message.startsWith("snapshot_chunk_sha256_mismatch_")) {
25
+ return appendNextStep("A downloaded snapshot chunk was corrupted and was rolled back to the last verified checkpoint.", "Wait a moment and rerun sync. If this keeps happening, check local disk health and the stability of the snapshot connection.");
26
+ }
27
+ if (message.startsWith("snapshot_download_incomplete_")) {
28
+ return appendNextStep("Snapshot download ended before the expected file size was reached.", "Wait a moment and rerun sync. The downloader will resume from the last verified checkpoint.");
29
+ }
21
30
  if (message === "bitcoind_cookie_timeout") {
22
31
  return appendNextStep("The managed Bitcoin node did not finish starting in time.", "Check the node logs and local permissions for the Bitcoin data directory, then rerun sync.");
23
32
  }
@@ -73,6 +73,10 @@ export interface IndexerDaemonProbeResult {
73
73
  client: IndexerDaemonClient | null;
74
74
  error: string | null;
75
75
  }
76
+ export interface IndexerDaemonStopResult {
77
+ status: "stopped" | "not-running";
78
+ walletRootId: string;
79
+ }
76
80
  export interface CoherentIndexerSnapshotLease {
77
81
  payload: IndexerSnapshotPayload;
78
82
  status: ManagedIndexerDaemonStatus;
@@ -92,6 +96,11 @@ export declare function attachOrStartIndexerDaemon(options: {
92
96
  walletRootId?: string;
93
97
  startupTimeoutMs?: number;
94
98
  }): Promise<IndexerDaemonClient>;
99
+ export declare function stopIndexerDaemonService(options: {
100
+ dataDir: string;
101
+ walletRootId?: string;
102
+ shutdownTimeoutMs?: number;
103
+ }): Promise<IndexerDaemonStopResult>;
95
104
  export declare function shutdownIndexerDaemonForTesting(options: {
96
105
  dataDir: string;
97
106
  walletRootId?: string;
@@ -39,6 +39,20 @@ function sleep(ms) {
39
39
  setTimeout(resolve, ms);
40
40
  });
41
41
  }
42
+ async function waitForProcessExit(pid, timeoutMs, errorCode) {
43
+ const deadline = Date.now() + timeoutMs;
44
+ while (Date.now() < deadline) {
45
+ if (!await isProcessAlive(pid)) {
46
+ return;
47
+ }
48
+ await sleep(50);
49
+ }
50
+ throw new Error(errorCode);
51
+ }
52
+ async function clearIndexerDaemonRuntimeArtifacts(paths) {
53
+ await rm(paths.indexerDaemonStatusPath, { force: true }).catch(() => undefined);
54
+ await rm(paths.indexerDaemonSocketPath, { force: true }).catch(() => undefined);
55
+ }
42
56
  function createIndexerDaemonClient(socketPath) {
43
57
  async function sendRequest(request) {
44
58
  return new Promise((resolve, reject) => {
@@ -357,26 +371,49 @@ export async function attachOrStartIndexerDaemon(options) {
357
371
  throw error;
358
372
  }
359
373
  }
360
- export async function shutdownIndexerDaemonForTesting(options) {
374
+ export async function stopIndexerDaemonService(options) {
361
375
  const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
362
376
  const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
363
- const status = await readJsonFile(paths.indexerDaemonStatusPath);
364
- if (status?.processId !== null && status?.processId !== undefined) {
377
+ const lock = await acquireFileLock(paths.indexerDaemonLockPath, {
378
+ purpose: "indexer-daemon-stop",
379
+ walletRootId,
380
+ dataDir: options.dataDir,
381
+ });
382
+ try {
383
+ const status = await readJsonFile(paths.indexerDaemonStatusPath);
384
+ const processId = status?.processId ?? null;
385
+ if (status === null || processId === null || !await isProcessAlive(processId)) {
386
+ await clearIndexerDaemonRuntimeArtifacts(paths);
387
+ return {
388
+ status: "not-running",
389
+ walletRootId,
390
+ };
391
+ }
365
392
  try {
366
- process.kill(status.processId, "SIGTERM");
367
- const deadline = Date.now() + 5_000;
368
- while (Date.now() < deadline) {
369
- if (!await isProcessAlive(status.processId)) {
370
- break;
371
- }
372
- await sleep(50);
373
- }
393
+ process.kill(processId, "SIGTERM");
374
394
  }
375
- catch {
376
- // ignore stale pid cleanup failures
395
+ catch (error) {
396
+ if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
397
+ throw error;
398
+ }
377
399
  }
400
+ await waitForProcessExit(processId, options.shutdownTimeoutMs ?? 5_000, "indexer_daemon_stop_timeout");
401
+ await clearIndexerDaemonRuntimeArtifacts(paths);
402
+ return {
403
+ status: "stopped",
404
+ walletRootId,
405
+ };
378
406
  }
379
- await rm(paths.indexerDaemonSocketPath, { force: true }).catch(() => undefined);
407
+ finally {
408
+ await lock.release();
409
+ }
410
+ }
411
+ export async function shutdownIndexerDaemonForTesting(options) {
412
+ await stopIndexerDaemonService(options).catch(async () => {
413
+ const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
414
+ const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
415
+ await rm(paths.indexerDaemonSocketPath, { force: true }).catch(() => undefined);
416
+ });
380
417
  }
381
418
  export async function readIndexerDaemonStatusForTesting(options) {
382
419
  const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
@@ -25,11 +25,20 @@ export interface ManagedBitcoindServiceProbeResult {
25
25
  status: ManagedBitcoindObservedStatus | null;
26
26
  error: string | null;
27
27
  }
28
+ export interface ManagedBitcoindServiceStopResult {
29
+ status: "stopped" | "not-running";
30
+ walletRootId: string;
31
+ }
28
32
  export declare function createManagedWalletReplica(rpc: ManagedWalletReplicaRpc, walletRootId: string, options?: {
29
33
  managedWalletPassphrase?: string;
30
34
  }): Promise<ManagedCoreWalletReplicaStatus>;
31
35
  export declare function probeManagedBitcoindService(options: ManagedBitcoindServiceOptions): Promise<ManagedBitcoindServiceProbeResult>;
32
36
  export declare function attachOrStartManagedBitcoindService(options: ManagedBitcoindServiceOptions): Promise<ManagedBitcoindNodeHandle>;
37
+ export declare function stopManagedBitcoindService(options: {
38
+ dataDir: string;
39
+ walletRootId?: string;
40
+ shutdownTimeoutMs?: number;
41
+ }): Promise<ManagedBitcoindServiceStopResult>;
33
42
  export declare function readManagedBitcoindServiceStatusForTesting(dataDir: string, walletRootId?: string): Promise<ManagedBitcoindObservedStatus | null>;
34
43
  export declare function shutdownManagedBitcoindServiceForTesting(options: {
35
44
  dataDir: string;
@@ -21,6 +21,16 @@ function sleep(ms) {
21
21
  setTimeout(resolve, ms);
22
22
  });
23
23
  }
24
+ async function waitForProcessExit(pid, timeoutMs, errorCode) {
25
+ const deadline = Date.now() + timeoutMs;
26
+ while (Date.now() < deadline) {
27
+ if (!await isProcessAlive(pid)) {
28
+ return;
29
+ }
30
+ await sleep(250);
31
+ }
32
+ throw new Error(errorCode);
33
+ }
24
34
  function getWalletReplicaName(walletRootId) {
25
35
  return `cogcoin-${walletRootId}`.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 63);
26
36
  }
@@ -488,6 +498,12 @@ async function writeBitcoindStatus(paths, status) {
488
498
  p2pPort: status.p2pPort,
489
499
  });
490
500
  }
501
+ async function clearManagedBitcoindRuntimeArtifacts(paths) {
502
+ await rm(paths.bitcoindStatusPath, { force: true }).catch(() => undefined);
503
+ await rm(paths.bitcoindPidPath, { force: true }).catch(() => undefined);
504
+ await rm(paths.bitcoindReadyPath, { force: true }).catch(() => undefined);
505
+ await rm(paths.bitcoindWalletStatusPath, { force: true }).catch(() => undefined);
506
+ }
491
507
  async function refreshManagedBitcoindStatus(status, paths, options) {
492
508
  const nowUnixMs = Date.now();
493
509
  const rpc = createRpcClient(status.rpc);
@@ -699,37 +715,62 @@ export async function attachOrStartManagedBitcoindService(options) {
699
715
  throw error;
700
716
  }
701
717
  }
702
- export async function readManagedBitcoindServiceStatusForTesting(dataDir, walletRootId = UNINITIALIZED_WALLET_ROOT_ID) {
703
- const paths = resolveManagedServicePaths(dataDir, walletRootId);
704
- return readJsonFile(paths.bitcoindStatusPath);
705
- }
706
- export async function shutdownManagedBitcoindServiceForTesting(options) {
718
+ export async function stopManagedBitcoindService(options) {
707
719
  const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
708
720
  const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
709
- const status = await readJsonFile(paths.bitcoindStatusPath);
710
- if (status === null) {
711
- return;
712
- }
713
- const rpc = createRpcClient(status.rpc);
721
+ const lock = await acquireFileLock(paths.bitcoindLockPath, {
722
+ purpose: "managed-bitcoind-stop",
723
+ walletRootId,
724
+ dataDir: options.dataDir,
725
+ });
714
726
  try {
715
- await rpc.stop();
716
- }
717
- catch {
718
- if (status.processId !== null) {
727
+ const status = await readJsonFile(paths.bitcoindStatusPath);
728
+ const processId = status?.processId ?? null;
729
+ if (status === null || processId === null || !await isProcessAlive(processId)) {
730
+ await clearManagedBitcoindRuntimeArtifacts(paths);
731
+ return {
732
+ status: "not-running",
733
+ walletRootId,
734
+ };
735
+ }
736
+ const rpc = createRpcClient(status.rpc);
737
+ try {
738
+ await rpc.stop();
739
+ }
740
+ catch {
719
741
  try {
720
- process.kill(status.processId, "SIGTERM");
742
+ process.kill(processId, "SIGTERM");
721
743
  }
722
- catch {
723
- // ignore
744
+ catch (error) {
745
+ if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
746
+ throw error;
747
+ }
724
748
  }
725
749
  }
750
+ await waitForProcessExit(processId, options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS, "managed_bitcoind_service_stop_timeout");
751
+ await clearManagedBitcoindRuntimeArtifacts(paths);
752
+ return {
753
+ status: "stopped",
754
+ walletRootId,
755
+ };
726
756
  }
727
- const deadline = Date.now() + (options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS);
728
- while (Date.now() < deadline) {
729
- if (!await isProcessAlive(status.processId)) {
730
- break;
731
- }
732
- await sleep(250);
757
+ finally {
758
+ await lock.release();
733
759
  }
734
- await rm(paths.bitcoindReadyPath, { force: true }).catch(() => undefined);
760
+ }
761
+ export async function readManagedBitcoindServiceStatusForTesting(dataDir, walletRootId = UNINITIALIZED_WALLET_ROOT_ID) {
762
+ const paths = resolveManagedServicePaths(dataDir, walletRootId);
763
+ return readJsonFile(paths.bitcoindStatusPath);
764
+ }
765
+ export async function shutdownManagedBitcoindServiceForTesting(options) {
766
+ await stopManagedBitcoindService({
767
+ dataDir: options.dataDir,
768
+ walletRootId: options.walletRootId,
769
+ shutdownTimeoutMs: options.shutdownTimeoutMs,
770
+ }).catch(async (error) => {
771
+ const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
772
+ const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
773
+ await rm(paths.bitcoindReadyPath, { force: true }).catch(() => undefined);
774
+ throw error;
775
+ });
735
776
  }
@@ -1,8 +1,8 @@
1
1
  export { openManagedBitcoindClientInternal } from "./client.js";
2
- export { attachOrStartIndexerDaemon, readIndexerDaemonStatusForTesting, shutdownIndexerDaemonForTesting, } from "./indexer-daemon.js";
2
+ export { attachOrStartIndexerDaemon, readIndexerDaemonStatusForTesting, stopIndexerDaemonService, shutdownIndexerDaemonForTesting, } from "./indexer-daemon.js";
3
3
  export { normalizeRpcBlock } from "./normalize.js";
4
4
  export { BitcoinRpcClient } from "./rpc.js";
5
- export { attachOrStartManagedBitcoindService, readManagedBitcoindServiceStatusForTesting, shutdownManagedBitcoindServiceForTesting, } from "./service.js";
5
+ export { attachOrStartManagedBitcoindService, readManagedBitcoindServiceStatusForTesting, stopManagedBitcoindService, shutdownManagedBitcoindServiceForTesting, } from "./service.js";
6
6
  export { AssumeUtxoBootstrapController, DEFAULT_SNAPSHOT_METADATA, createBootstrapStateForTesting, downloadSnapshotFileForTesting, loadBootstrapStateForTesting, resolveBootstrapPathsForTesting, saveBootstrapStateForTesting, validateSnapshotFileForTesting, waitForHeadersForTesting, } from "./bootstrap.js";
7
7
  export { buildBitcoindArgsForTesting, createRpcClient, launchManagedBitcoindNode, resolveDefaultBitcoindDataDirForTesting, validateNodeConfigForTesting, } from "./node.js";
8
8
  export { ManagedProgressController, TtyProgressRenderer, advanceFollowSceneStateForTesting, createFollowSceneStateForTesting, createBootstrapProgressForTesting, formatCompactFollowAgeLabelForTesting, loadBannerArtForTesting, loadScrollArtForTesting, loadTrainCarArtForTesting, loadTrainArtForTesting, loadTrainSmokeArtForTesting, formatProgressLineForTesting, formatQuoteLineForTesting, renderArtFrameForTesting, renderCompletionFrameForTesting, renderFollowFrameForTesting, renderIntroFrameForTesting, resolveCompletionMessageForTesting, resolveIntroMessageForTesting, resolveStatusFieldTextForTesting, setFollowBlockTimeForTesting, setFollowBlockTimesForTesting, syncFollowSceneStateForTesting, } from "./progress.js";
@@ -1,8 +1,8 @@
1
1
  export { openManagedBitcoindClientInternal } from "./client.js";
2
- export { attachOrStartIndexerDaemon, readIndexerDaemonStatusForTesting, shutdownIndexerDaemonForTesting, } from "./indexer-daemon.js";
2
+ export { attachOrStartIndexerDaemon, readIndexerDaemonStatusForTesting, stopIndexerDaemonService, shutdownIndexerDaemonForTesting, } from "./indexer-daemon.js";
3
3
  export { normalizeRpcBlock } from "./normalize.js";
4
4
  export { BitcoinRpcClient } from "./rpc.js";
5
- export { attachOrStartManagedBitcoindService, readManagedBitcoindServiceStatusForTesting, shutdownManagedBitcoindServiceForTesting, } from "./service.js";
5
+ export { attachOrStartManagedBitcoindService, readManagedBitcoindServiceStatusForTesting, stopManagedBitcoindService, shutdownManagedBitcoindServiceForTesting, } from "./service.js";
6
6
  export { AssumeUtxoBootstrapController, DEFAULT_SNAPSHOT_METADATA, createBootstrapStateForTesting, downloadSnapshotFileForTesting, loadBootstrapStateForTesting, resolveBootstrapPathsForTesting, saveBootstrapStateForTesting, validateSnapshotFileForTesting, waitForHeadersForTesting, } from "./bootstrap.js";
7
7
  export { buildBitcoindArgsForTesting, createRpcClient, launchManagedBitcoindNode, resolveDefaultBitcoindDataDirForTesting, validateNodeConfigForTesting, } from "./node.js";
8
8
  export { ManagedProgressController, TtyProgressRenderer, advanceFollowSceneStateForTesting, createFollowSceneStateForTesting, createBootstrapProgressForTesting, formatCompactFollowAgeLabelForTesting, loadBannerArtForTesting, loadScrollArtForTesting, loadTrainCarArtForTesting, loadTrainArtForTesting, loadTrainSmokeArtForTesting, formatProgressLineForTesting, formatQuoteLineForTesting, renderArtFrameForTesting, renderCompletionFrameForTesting, renderFollowFrameForTesting, renderIntroFrameForTesting, resolveCompletionMessageForTesting, resolveIntroMessageForTesting, resolveStatusFieldTextForTesting, setFollowBlockTimeForTesting, setFollowBlockTimesForTesting, syncFollowSceneStateForTesting, } from "./progress.js";
@@ -8,6 +8,15 @@ export interface SnapshotMetadata {
8
8
  sha256: string;
9
9
  sizeBytes: number;
10
10
  }
11
+ export interface SnapshotChunkManifest {
12
+ formatVersion: number;
13
+ chunkSizeBytes: number;
14
+ snapshotFilename: string;
15
+ snapshotHeight: number;
16
+ snapshotSizeBytes: number;
17
+ snapshotSha256: string;
18
+ chunkSha256s: string[];
19
+ }
11
20
  export interface WritingQuote {
12
21
  quote: string;
13
22
  author: string;
@@ -0,0 +1,2 @@
1
+ import type { ParsedCliArgs, RequiredCliRunnerContext } from "../types.js";
2
+ export declare function runServiceRuntimeCommand(parsed: ParsedCliArgs, context: RequiredCliRunnerContext): Promise<number>;