@cogcoin/client 1.1.5 → 1.1.7

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 (132) hide show
  1. package/README.md +2 -2
  2. package/dist/bitcoind/indexer-daemon.d.ts +3 -7
  3. package/dist/bitcoind/indexer-daemon.js +39 -204
  4. package/dist/bitcoind/managed-runtime/bitcoind-policy.d.ts +16 -0
  5. package/dist/bitcoind/managed-runtime/bitcoind-policy.js +177 -0
  6. package/dist/bitcoind/managed-runtime/bitcoind-runtime.d.ts +20 -0
  7. package/dist/bitcoind/managed-runtime/bitcoind-runtime.js +74 -0
  8. package/dist/bitcoind/managed-runtime/bitcoind-status.d.ts +11 -0
  9. package/dist/bitcoind/managed-runtime/bitcoind-status.js +44 -0
  10. package/dist/bitcoind/managed-runtime/indexer-policy.d.ts +34 -0
  11. package/dist/bitcoind/managed-runtime/indexer-policy.js +200 -0
  12. package/dist/bitcoind/managed-runtime/indexer-runtime.d.ts +15 -0
  13. package/dist/bitcoind/managed-runtime/indexer-runtime.js +82 -0
  14. package/dist/bitcoind/managed-runtime/status.d.ts +11 -0
  15. package/dist/bitcoind/managed-runtime/status.js +59 -0
  16. package/dist/bitcoind/managed-runtime/types.d.ts +77 -0
  17. package/dist/bitcoind/node.d.ts +2 -2
  18. package/dist/bitcoind/node.js +2 -2
  19. package/dist/bitcoind/rpc.d.ts +2 -1
  20. package/dist/bitcoind/rpc.js +53 -3
  21. package/dist/bitcoind/service.d.ts +2 -7
  22. package/dist/bitcoind/service.js +79 -207
  23. package/dist/cli/command-registry.d.ts +1 -1
  24. package/dist/cli/command-registry.js +2 -64
  25. package/dist/cli/commands/client-admin.js +3 -18
  26. package/dist/cli/commands/mining-runtime.js +4 -60
  27. package/dist/cli/commands/wallet-admin.js +6 -6
  28. package/dist/cli/context.js +1 -3
  29. package/dist/cli/mining-json.d.ts +1 -22
  30. package/dist/cli/mining-json.js +0 -23
  31. package/dist/cli/output.js +16 -2
  32. package/dist/cli/parse.js +0 -2
  33. package/dist/cli/preview-json.d.ts +1 -22
  34. package/dist/cli/preview-json.js +0 -19
  35. package/dist/cli/types.d.ts +1 -3
  36. package/dist/cli/wallet-format.js +1 -1
  37. package/dist/cli/workflow-hints.d.ts +1 -2
  38. package/dist/cli/workflow-hints.js +5 -8
  39. package/dist/wallet/lifecycle/access.d.ts +5 -0
  40. package/dist/wallet/lifecycle/access.js +79 -0
  41. package/dist/wallet/lifecycle/context.d.ts +26 -0
  42. package/dist/wallet/lifecycle/context.js +57 -0
  43. package/dist/wallet/lifecycle/managed-core.d.ts +1 -9
  44. package/dist/wallet/lifecycle/managed-core.js +3 -63
  45. package/dist/wallet/lifecycle/repair-bitcoind.d.ts +10 -0
  46. package/dist/wallet/lifecycle/repair-bitcoind.js +142 -0
  47. package/dist/wallet/lifecycle/repair-indexer.d.ts +8 -0
  48. package/dist/wallet/lifecycle/repair-indexer.js +117 -0
  49. package/dist/wallet/lifecycle/repair-mining.d.ts +1 -5
  50. package/dist/wallet/lifecycle/repair-mining.js +5 -39
  51. package/dist/wallet/lifecycle/repair.d.ts +2 -4
  52. package/dist/wallet/lifecycle/repair.js +74 -318
  53. package/dist/wallet/lifecycle/setup-prompts.d.ts +7 -0
  54. package/dist/wallet/lifecycle/setup-prompts.js +88 -0
  55. package/dist/wallet/lifecycle/setup-state.d.ts +26 -0
  56. package/dist/wallet/lifecycle/setup-state.js +159 -0
  57. package/dist/wallet/lifecycle/setup.d.ts +3 -4
  58. package/dist/wallet/lifecycle/setup.js +47 -351
  59. package/dist/wallet/lifecycle/types.d.ts +33 -5
  60. package/dist/wallet/managed-core-wallet.d.ts +2 -0
  61. package/dist/wallet/managed-core-wallet.js +27 -1
  62. package/dist/wallet/mining/candidate.d.ts +1 -0
  63. package/dist/wallet/mining/candidate.js +38 -6
  64. package/dist/wallet/mining/competitiveness.d.ts +1 -0
  65. package/dist/wallet/mining/competitiveness.js +6 -0
  66. package/dist/wallet/mining/cycle.d.ts +2 -0
  67. package/dist/wallet/mining/cycle.js +14 -4
  68. package/dist/wallet/mining/engine-types.d.ts +1 -0
  69. package/dist/wallet/mining/index.d.ts +1 -1
  70. package/dist/wallet/mining/index.js +1 -1
  71. package/dist/wallet/mining/publish.d.ts +3 -0
  72. package/dist/wallet/mining/publish.js +78 -6
  73. package/dist/wallet/mining/runner.d.ts +0 -32
  74. package/dist/wallet/mining/runner.js +59 -104
  75. package/dist/wallet/mining/stop.d.ts +7 -0
  76. package/dist/wallet/mining/stop.js +23 -0
  77. package/dist/wallet/mining/supervisor.d.ts +2 -36
  78. package/dist/wallet/mining/supervisor.js +139 -246
  79. package/dist/wallet/read/context.d.ts +1 -5
  80. package/dist/wallet/read/context.js +20 -379
  81. package/dist/wallet/read/managed-services.d.ts +33 -0
  82. package/dist/wallet/read/managed-services.js +222 -0
  83. package/dist/wallet/state/client-password/bootstrap.d.ts +2 -0
  84. package/dist/wallet/state/client-password/bootstrap.js +3 -0
  85. package/dist/wallet/state/client-password/context.d.ts +10 -0
  86. package/dist/wallet/state/client-password/context.js +46 -0
  87. package/dist/wallet/state/client-password/crypto.d.ts +34 -0
  88. package/dist/wallet/state/client-password/crypto.js +117 -0
  89. package/dist/wallet/state/client-password/files.d.ts +10 -0
  90. package/dist/wallet/state/client-password/files.js +109 -0
  91. package/dist/wallet/state/client-password/legacy-cleanup.d.ts +11 -0
  92. package/dist/wallet/state/client-password/legacy-cleanup.js +338 -0
  93. package/dist/wallet/state/client-password/messages.d.ts +3 -0
  94. package/dist/wallet/state/client-password/messages.js +9 -0
  95. package/dist/wallet/state/client-password/migration.d.ts +4 -0
  96. package/dist/wallet/state/client-password/migration.js +32 -0
  97. package/dist/wallet/state/client-password/prompts.d.ts +12 -0
  98. package/dist/wallet/state/client-password/prompts.js +79 -0
  99. package/dist/wallet/state/client-password/protected-secrets.d.ts +13 -0
  100. package/dist/wallet/state/client-password/protected-secrets.js +90 -0
  101. package/dist/wallet/state/client-password/readiness.d.ts +4 -0
  102. package/dist/wallet/state/client-password/readiness.js +48 -0
  103. package/dist/wallet/state/client-password/references.d.ts +1 -0
  104. package/dist/wallet/state/client-password/references.js +56 -0
  105. package/dist/wallet/state/client-password/rotation.d.ts +6 -0
  106. package/dist/wallet/state/client-password/rotation.js +98 -0
  107. package/dist/wallet/state/client-password/session-policy.d.ts +6 -0
  108. package/dist/wallet/state/client-password/session-policy.js +28 -0
  109. package/dist/wallet/state/client-password/session.d.ts +19 -0
  110. package/dist/wallet/state/client-password/session.js +170 -0
  111. package/dist/wallet/state/client-password/setup.d.ts +8 -0
  112. package/dist/wallet/state/client-password/setup.js +49 -0
  113. package/dist/wallet/state/client-password/types.d.ts +82 -0
  114. package/dist/wallet/state/client-password/types.js +5 -0
  115. package/dist/wallet/state/client-password.d.ts +7 -38
  116. package/dist/wallet/state/client-password.js +52 -937
  117. package/dist/wallet/tx/anchor.js +123 -216
  118. package/dist/wallet/tx/cog.js +294 -489
  119. package/dist/wallet/tx/common.d.ts +2 -0
  120. package/dist/wallet/tx/common.js +2 -0
  121. package/dist/wallet/tx/domain-admin.js +111 -220
  122. package/dist/wallet/tx/domain-market.js +401 -681
  123. package/dist/wallet/tx/executor.d.ts +176 -0
  124. package/dist/wallet/tx/executor.js +302 -0
  125. package/dist/wallet/tx/field.js +109 -215
  126. package/dist/wallet/tx/register.js +158 -269
  127. package/dist/wallet/tx/reputation.js +120 -227
  128. package/package.json +1 -1
  129. package/dist/wallet/mining/worker-main.js +0 -17
  130. package/dist/wallet/state/client-password-agent.d.ts +0 -1
  131. package/dist/wallet/state/client-password-agent.js +0 -211
  132. /package/dist/{wallet/mining/worker-main.d.ts → bitcoind/managed-runtime/types.js} +0 -0
@@ -1,5 +1,7 @@
1
+ import type { ManagedBitcoindServiceProbeResult } from "./managed-runtime/types.js";
1
2
  import { resolveManagedServicePaths } from "./service-paths.js";
2
3
  import type { InternalManagedBitcoindOptions, ManagedBitcoindObservedStatus, ManagedBitcoindRuntimeConfig, ManagedBitcoindNodeHandle, ManagedCoreWalletReplicaStatus } from "./types.js";
4
+ export type { ManagedBitcoindServiceCompatibility, ManagedBitcoindServiceProbeResult, } from "./managed-runtime/types.js";
3
5
  export declare function resolveManagedBitcoindDbcacheMiB(totalRamBytes: number): number;
4
6
  interface ManagedWalletReplicaRpc {
5
7
  listWallets(): Promise<string[]>;
@@ -26,12 +28,6 @@ type ManagedBitcoindServiceOptions = Pick<InternalManagedBitcoindOptions, "dataD
26
28
  getblockArchiveSha256?: string | null;
27
29
  serviceLifetime?: "persistent" | "ephemeral";
28
30
  };
29
- export type ManagedBitcoindServiceCompatibility = "compatible" | "service-version-mismatch" | "wallet-root-mismatch" | "runtime-mismatch" | "unreachable" | "protocol-error";
30
- export interface ManagedBitcoindServiceProbeResult {
31
- compatibility: ManagedBitcoindServiceCompatibility;
32
- status: ManagedBitcoindObservedStatus | null;
33
- error: string | null;
34
- }
35
31
  export interface ManagedBitcoindServiceStopResult {
36
32
  status: "stopped" | "not-running";
37
33
  walletRootId: string;
@@ -66,4 +62,3 @@ export declare function shutdownManagedBitcoindServiceForTesting(options: {
66
62
  walletRootId?: string;
67
63
  shutdownTimeoutMs?: number;
68
64
  }): Promise<void>;
69
- export {};
@@ -1,6 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { execFile, spawn } from "node:child_process";
3
- import { access, constants, mkdir, readFile, readdir, rm } from "node:fs/promises";
3
+ import { access, constants, mkdir, readFile, rm } from "node:fs/promises";
4
+ import { totalmem } from "node:os";
4
5
  import { dirname, join } from "node:path";
5
6
  import { promisify } from "node:util";
6
7
  import net from "node:net";
@@ -9,6 +10,10 @@ import { acquireFileLock, FileLockBusyError } from "../wallet/fs/lock.js";
9
10
  import { writeFileAtomic } from "../wallet/fs/atomic.js";
10
11
  import { writeRuntimeStatusFile } from "../wallet/fs/status-file.js";
11
12
  import { stopIndexerDaemonServiceWithLockHeld } from "./indexer-daemon.js";
13
+ import { mapManagedBitcoindRuntimeProbeFailure, mapManagedBitcoindValidationError, validateManagedBitcoindObservedStatus, } from "./managed-runtime/bitcoind-policy.js";
14
+ import { listManagedBitcoindStatusCandidates, readManagedBitcoindObservedStatus, } from "./managed-runtime/bitcoind-status.js";
15
+ import { attachOrStartManagedBitcoindRuntime, probeManagedBitcoindRuntime, } from "./managed-runtime/bitcoind-runtime.js";
16
+ import { readJsonFileIfPresent } from "./managed-runtime/status.js";
12
17
  import { createRpcClient, validateNodeConfigForTesting } from "./node.js";
13
18
  import { resolveManagedServicePaths, UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
14
19
  import { DEFAULT_MANAGED_BITCOIND_FOLLOW_POLL_INTERVAL_MS, MANAGED_BITCOIND_SERVICE_API_VERSION as MANAGED_BITCOIND_SERVICE_API_VERSION_VALUE, } from "./types.js";
@@ -17,14 +22,31 @@ const LOCAL_HOST = "127.0.0.1";
17
22
  const SUPPORTED_BITCOIND_VERSION = "30.2.0";
18
23
  const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
19
24
  const DEFAULT_SHUTDOWN_TIMEOUT_MS = 15_000;
20
- const DEFAULT_DBCACHE_MIB = 2048;
25
+ const DEFAULT_DBCACHE_MIB = 450;
21
26
  const claimedUninitializedRuntimeKeys = new Set();
27
+ const GIB = 1024 ** 3;
22
28
  export function resolveManagedBitcoindDbcacheMiB(totalRamBytes) {
23
- void totalRamBytes;
24
- return DEFAULT_DBCACHE_MIB;
29
+ if (!Number.isFinite(totalRamBytes) || totalRamBytes <= 0) {
30
+ return DEFAULT_DBCACHE_MIB;
31
+ }
32
+ if (totalRamBytes < 8 * GIB) {
33
+ return 450;
34
+ }
35
+ if (totalRamBytes < 16 * GIB) {
36
+ return 768;
37
+ }
38
+ if (totalRamBytes < 32 * GIB) {
39
+ return 1024;
40
+ }
41
+ return 2048;
25
42
  }
26
43
  function detectManagedBitcoindDbcacheMiB() {
27
- return DEFAULT_DBCACHE_MIB;
44
+ try {
45
+ return resolveManagedBitcoindDbcacheMiB(totalmem());
46
+ }
47
+ catch {
48
+ return DEFAULT_DBCACHE_MIB;
49
+ }
28
50
  }
29
51
  function sleep(ms) {
30
52
  return new Promise((resolve) => {
@@ -58,53 +80,6 @@ async function acquireFileLockWithRetry(lockPath, metadata, timeoutMs) {
58
80
  function getWalletReplicaName(walletRootId) {
59
81
  return `cogcoin-${walletRootId}`.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 63);
60
82
  }
61
- async function readJsonFile(filePath) {
62
- try {
63
- return JSON.parse(await readFile(filePath, "utf8"));
64
- }
65
- catch (error) {
66
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
67
- return null;
68
- }
69
- throw error;
70
- }
71
- }
72
- async function listManagedBitcoindStatusCandidates(options) {
73
- const candidates = new Map();
74
- const addCandidate = async (statusPath, allowDataDirMismatch = false) => {
75
- const status = await readJsonFile(statusPath);
76
- if (status === null) {
77
- return;
78
- }
79
- if (!allowDataDirMismatch && status.dataDir !== options.dataDir) {
80
- return;
81
- }
82
- candidates.set(statusPath, status);
83
- };
84
- await addCandidate(options.expectedStatusPath, true);
85
- try {
86
- const entries = await readdir(options.runtimeRoot, {
87
- withFileTypes: true,
88
- });
89
- for (const entry of entries) {
90
- if (!entry.isDirectory()) {
91
- continue;
92
- }
93
- const statusPath = join(options.runtimeRoot, entry.name, "bitcoind-status.json");
94
- if (statusPath === options.expectedStatusPath) {
95
- continue;
96
- }
97
- await addCandidate(statusPath);
98
- }
99
- }
100
- catch {
101
- // Missing runtime roots are handled by returning no candidates.
102
- }
103
- return [...candidates.entries()].map(([statusPath, status]) => ({
104
- statusPath,
105
- status,
106
- }));
107
- }
108
83
  async function allocatePort() {
109
84
  return new Promise((resolve, reject) => {
110
85
  const server = net.createServer();
@@ -207,38 +182,6 @@ async function waitForRpcReady(rpc, cookieFile, expectedChain, timeoutMs) {
207
182
  }
208
183
  throw lastError instanceof Error ? lastError : new Error("bitcoind_rpc_timeout");
209
184
  }
210
- function validateManagedBitcoindStatus(status, options, runtimeRoot) {
211
- const legacyRuntimeRoot = join(resolveManagedServicePaths(options.dataDir ?? "", options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID).runtimeRoot, status.walletRootId);
212
- if (status.serviceApiVersion !== MANAGED_BITCOIND_SERVICE_API_VERSION_VALUE) {
213
- throw new Error("managed_bitcoind_service_version_mismatch");
214
- }
215
- if (status.chain !== options.chain
216
- || status.dataDir !== (options.dataDir ?? "")
217
- || (status.runtimeRoot !== runtimeRoot && status.runtimeRoot !== legacyRuntimeRoot)) {
218
- throw new Error("managed_bitcoind_runtime_mismatch");
219
- }
220
- }
221
- function isRuntimeMismatchError(error) {
222
- if (!(error instanceof Error)) {
223
- return false;
224
- }
225
- return error.message.startsWith("bitcoind_chain_expected_")
226
- || error.message === "managed_bitcoind_runtime_mismatch";
227
- }
228
- function isUnreachableManagedBitcoindError(error) {
229
- if (error instanceof Error) {
230
- if ("code" in error) {
231
- const code = error.code;
232
- return code === "ENOENT" || code === "ECONNREFUSED" || code === "ECONNRESET";
233
- }
234
- return error.message === "bitcoind_cookie_timeout"
235
- || error.message.includes("cookie file is unavailable")
236
- || error.message.includes("ECONNREFUSED")
237
- || error.message.includes("ECONNRESET")
238
- || error.message.includes("socket hang up");
239
- }
240
- return false;
241
- }
242
185
  function createBitcoindServiceStatus(options) {
243
186
  return {
244
187
  serviceApiVersion: MANAGED_BITCOIND_SERVICE_API_VERSION_VALUE,
@@ -264,27 +207,16 @@ function createBitcoindServiceStatus(options) {
264
207
  lastError: options.lastError,
265
208
  };
266
209
  }
267
- function mapManagedBitcoindValidationError(error) {
268
- return {
269
- compatibility: error instanceof Error
270
- ? error.message === "managed_bitcoind_service_version_mismatch"
271
- ? "service-version-mismatch"
272
- : "runtime-mismatch"
273
- : "protocol-error",
274
- status: null,
275
- error: error instanceof Error ? error.message : "managed_bitcoind_protocol_error",
276
- };
277
- }
278
210
  async function probeManagedBitcoindStatusCandidate(status, options, runtimeRoot) {
279
211
  try {
280
- validateManagedBitcoindStatus(status, options, runtimeRoot);
212
+ validateManagedBitcoindObservedStatus(status, {
213
+ chain: options.chain,
214
+ dataDir: options.dataDir ?? "",
215
+ runtimeRoot,
216
+ });
281
217
  }
282
218
  catch (error) {
283
- const mapped = mapManagedBitcoindValidationError(error);
284
- return {
285
- ...mapped,
286
- status,
287
- };
219
+ return mapManagedBitcoindValidationError(error, status);
288
220
  }
289
221
  const rpc = createRpcClient(status.rpc);
290
222
  try {
@@ -297,30 +229,12 @@ async function probeManagedBitcoindStatusCandidate(status, options, runtimeRoot)
297
229
  };
298
230
  }
299
231
  catch (error) {
300
- if (isRuntimeMismatchError(error)) {
301
- return {
302
- compatibility: "runtime-mismatch",
303
- status,
304
- error: "managed_bitcoind_runtime_mismatch",
305
- };
306
- }
307
- if (isUnreachableManagedBitcoindError(error)) {
308
- return {
309
- compatibility: "unreachable",
310
- status,
311
- error: null,
312
- };
313
- }
314
- return {
315
- compatibility: "protocol-error",
316
- status,
317
- error: "managed_bitcoind_protocol_error",
318
- };
232
+ return mapManagedBitcoindRuntimeProbeFailure(error, status);
319
233
  }
320
234
  }
321
235
  async function resolveRuntimeConfig(statusPath, configPath, options) {
322
- const previousStatus = await readJsonFile(statusPath);
323
- const previousConfig = await readJsonFile(configPath);
236
+ const previousStatus = await readJsonFileIfPresent(statusPath);
237
+ const previousConfig = await readJsonFileIfPresent(configPath);
324
238
  const reserved = new Set();
325
239
  const rpcPort = options.rpcPort
326
240
  ?? previousStatus?.rpc.port
@@ -545,7 +459,7 @@ async function clearManagedBitcoindRuntimeArtifacts(paths) {
545
459
  export async function stopManagedBitcoindServiceWithLockHeld(options) {
546
460
  const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
547
461
  const paths = options.paths ?? resolveManagedServicePaths(options.dataDir, walletRootId);
548
- const status = await readJsonFile(paths.bitcoindStatusPath);
462
+ const status = await readJsonFileIfPresent(paths.bitcoindStatusPath);
549
463
  const processId = status?.processId ?? null;
550
464
  if (status === null || processId === null || !await isProcessAlive(processId)) {
551
465
  await clearManagedBitcoindRuntimeArtifacts(paths);
@@ -716,44 +630,19 @@ async function tryAttachExistingManagedBitcoindService(options) {
716
630
  const refreshed = await refreshManagedBitcoindStatus(probe.status, paths, options);
717
631
  return createNodeHandle(refreshed, paths, options, "attached");
718
632
  }
719
- async function waitForManagedBitcoindService(options, timeoutMs) {
720
- const deadline = Date.now() + timeoutMs;
721
- while (Date.now() < deadline) {
722
- const attached = await tryAttachExistingManagedBitcoindService(options).catch(() => null);
723
- if (attached !== null) {
724
- return attached;
725
- }
726
- await sleep(250);
727
- }
728
- throw new Error("managed_bitcoind_service_start_timeout");
729
- }
730
633
  export async function probeManagedBitcoindService(options) {
731
- const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
732
- const paths = resolveManagedServicePaths(options.dataDir ?? "", walletRootId);
733
- const candidates = await listManagedBitcoindStatusCandidates({
634
+ const resolvedOptions = {
635
+ ...options,
734
636
  dataDir: options.dataDir ?? "",
735
- runtimeRoot: paths.runtimeRoot,
736
- expectedStatusPath: paths.bitcoindStatusPath,
737
- });
738
- const expectedCandidate = candidates.find((candidate) => candidate.statusPath === paths.bitcoindStatusPath) ?? null;
739
- for (const candidate of candidates) {
740
- if (!await isProcessAlive(candidate.status.processId)) {
741
- continue;
742
- }
743
- return probeManagedBitcoindStatusCandidate(candidate.status, options, paths.walletRuntimeRoot);
744
- }
745
- if (expectedCandidate !== null) {
746
- return {
747
- compatibility: "unreachable",
748
- status: expectedCandidate.status,
749
- error: null,
750
- };
751
- }
752
- return {
753
- compatibility: "unreachable",
754
- status: candidates[0]?.status ?? null,
755
- error: null,
637
+ walletRootId: options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID,
638
+ startupTimeoutMs: options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS,
756
639
  };
640
+ return probeManagedBitcoindRuntime(resolvedOptions, {
641
+ getPaths: (runtimeOptions) => resolveManagedServicePaths(runtimeOptions.dataDir, runtimeOptions.walletRootId),
642
+ listStatusCandidates: listManagedBitcoindStatusCandidates,
643
+ isProcessAlive,
644
+ probeStatusCandidate: probeManagedBitcoindStatusCandidate,
645
+ });
757
646
  }
758
647
  export async function attachOrStartManagedBitcoindService(options) {
759
648
  const resolvedOptions = {
@@ -768,38 +657,27 @@ export async function attachOrStartManagedBitcoindService(options) {
768
657
  walletRootId: resolvedOptions.walletRootId,
769
658
  shutdownTimeoutMs: resolvedOptions.shutdownTimeoutMs,
770
659
  }, async () => {
771
- const existingProbe = await probeManagedBitcoindService(resolvedOptions);
772
- if (existingProbe.compatibility === "compatible") {
773
- const existing = await tryAttachExistingManagedBitcoindService(resolvedOptions);
774
- if (existing !== null) {
775
- return existing;
776
- }
777
- }
778
- if (existingProbe.compatibility !== "unreachable") {
779
- throw new Error(existingProbe.error ?? "managed_bitcoind_protocol_error");
780
- }
781
- const paths = resolveManagedServicePaths(resolvedOptions.dataDir ?? "", resolvedOptions.walletRootId);
782
- try {
783
- const lock = await acquireFileLock(paths.bitcoindLockPath, {
660
+ return attachOrStartManagedBitcoindRuntime({
661
+ ...resolvedOptions,
662
+ dataDir: resolvedOptions.dataDir ?? "",
663
+ walletRootId: resolvedOptions.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID,
664
+ startupTimeoutMs,
665
+ }, {
666
+ getPaths: (runtimeOptions) => resolveManagedServicePaths(runtimeOptions.dataDir, runtimeOptions.walletRootId),
667
+ listStatusCandidates: listManagedBitcoindStatusCandidates,
668
+ isProcessAlive,
669
+ probeStatusCandidate: probeManagedBitcoindStatusCandidate,
670
+ attachExisting: tryAttachExistingManagedBitcoindService,
671
+ acquireStartLock: async (runtimeOptions, paths) => acquireFileLock(paths.bitcoindLockPath, {
784
672
  purpose: "managed-bitcoind-start",
785
- walletRootId: resolvedOptions.walletRootId,
786
- dataDir: resolvedOptions.dataDir,
787
- });
788
- try {
789
- const liveProbe = await probeManagedBitcoindService(resolvedOptions);
790
- if (liveProbe.compatibility === "compatible") {
791
- const reattached = await tryAttachExistingManagedBitcoindService(resolvedOptions);
792
- if (reattached !== null) {
793
- return reattached;
794
- }
795
- }
796
- if (liveProbe.compatibility !== "unreachable") {
797
- throw new Error(liveProbe.error ?? "managed_bitcoind_protocol_error");
798
- }
673
+ walletRootId: runtimeOptions.walletRootId,
674
+ dataDir: runtimeOptions.dataDir,
675
+ }),
676
+ startService: async (runtimeOptions, paths) => {
799
677
  const bitcoindPath = await getBitcoindPath();
800
678
  await verifyBitcoindVersion(bitcoindPath);
801
679
  const binaryVersion = SUPPORTED_BITCOIND_VERSION;
802
- await mkdir(resolvedOptions.dataDir ?? "", { recursive: true });
680
+ await mkdir(runtimeOptions.dataDir, { recursive: true });
803
681
  const startManagedProcess = async (startOptions) => {
804
682
  const runtimeConfig = await resolveRuntimeConfig(paths.bitcoindStatusPath, paths.bitcoindRuntimeConfigPath, startOptions);
805
683
  await writeBitcoinConf(paths.bitcoinConfPath, startOptions, runtimeConfig);
@@ -841,16 +719,15 @@ export async function attachOrStartManagedBitcoindService(options) {
841
719
  throw error;
842
720
  }
843
721
  const nowUnixMs = Date.now();
844
- const walletRootId = startOptions.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
845
- const walletReplica = await loadManagedWalletReplicaIfPresent(rpc, walletRootId, startOptions.dataDir ?? "");
722
+ const walletReplica = await loadManagedWalletReplicaIfPresent(rpc, startOptions.walletRootId, startOptions.dataDir);
846
723
  return createBitcoindServiceStatus({
847
724
  binaryVersion,
848
725
  serviceInstanceId: randomBytes(16).toString("hex"),
849
726
  state: "ready",
850
727
  processId: child.pid ?? null,
851
- walletRootId,
728
+ walletRootId: startOptions.walletRootId,
852
729
  chain: startOptions.chain,
853
- dataDir: startOptions.dataDir ?? "",
730
+ dataDir: startOptions.dataDir,
854
731
  runtimeRoot: paths.walletRuntimeRoot,
855
732
  startHeight: startOptions.startHeight,
856
733
  rpc: rpcConfig,
@@ -866,32 +743,25 @@ export async function attachOrStartManagedBitcoindService(options) {
866
743
  };
867
744
  let status;
868
745
  try {
869
- status = await startManagedProcess(resolvedOptions);
746
+ status = await startManagedProcess(runtimeOptions);
870
747
  }
871
748
  catch (error) {
872
- if (resolvedOptions.getblockArchivePath === undefined || resolvedOptions.getblockArchivePath === null) {
749
+ if (runtimeOptions.getblockArchivePath === undefined || runtimeOptions.getblockArchivePath === null) {
873
750
  throw error;
874
751
  }
875
752
  status = await startManagedProcess({
876
- ...resolvedOptions,
753
+ ...runtimeOptions,
877
754
  getblockArchivePath: null,
878
755
  getblockArchiveEndHeight: null,
879
756
  getblockArchiveSha256: null,
880
757
  });
881
758
  }
882
759
  await writeBitcoindStatus(paths, status);
883
- return createNodeHandle(status, paths, resolvedOptions, "started");
884
- }
885
- finally {
886
- await lock.release();
887
- }
888
- }
889
- catch (error) {
890
- if (error instanceof FileLockBusyError) {
891
- return waitForManagedBitcoindService(resolvedOptions, startupTimeoutMs);
892
- }
893
- throw error;
894
- }
760
+ return createNodeHandle(status, resolveManagedServicePaths(runtimeOptions.dataDir, runtimeOptions.walletRootId), runtimeOptions, "started");
761
+ },
762
+ isLockBusyError: (error) => error instanceof FileLockBusyError,
763
+ sleep,
764
+ });
895
765
  });
896
766
  }
897
767
  export async function stopManagedBitcoindService(options) {
@@ -914,8 +784,10 @@ export async function stopManagedBitcoindService(options) {
914
784
  }
915
785
  }
916
786
  export async function readManagedBitcoindServiceStatusForTesting(dataDir, walletRootId = UNINITIALIZED_WALLET_ROOT_ID) {
917
- const paths = resolveManagedServicePaths(dataDir, walletRootId);
918
- return readJsonFile(paths.bitcoindStatusPath);
787
+ return readManagedBitcoindObservedStatus({
788
+ dataDir,
789
+ walletRootId,
790
+ });
919
791
  }
920
792
  export async function shutdownManagedBitcoindServiceForTesting(options) {
921
793
  await stopManagedBitcoindService({
@@ -1,5 +1,5 @@
1
1
  export type CommandHandlerFamily = "status" | "update" | "sync" | "follow" | "client-admin" | "service-runtime" | "wallet-admin" | "wallet-read" | "wallet-mutation" | "mining-admin" | "mining-runtime" | "mining-read";
2
- export type CommandName = "init" | "reset" | "repair" | "update" | "sync" | "status" | "client-lock" | "client-change-password" | "client-unlock" | "follow" | "bitcoin-start" | "bitcoin-stop" | "bitcoin-status" | "bitcoin-transfer" | "indexer-start" | "indexer-stop" | "indexer-status" | "anchor" | "register" | "transfer" | "sell" | "unsell" | "buy" | "domain-endpoint-set" | "domain-endpoint-clear" | "domain-delegate-set" | "domain-delegate-clear" | "domain-miner-set" | "domain-miner-clear" | "domain-canonical" | "fields" | "field" | "field-create" | "field-set" | "field-clear" | "send" | "claim" | "reclaim" | "cog-lock" | "rep-give" | "rep-revoke" | "mine" | "mine-start" | "mine-stop" | "mine-setup" | "mine-prompt" | "mine-prompt-list" | "mine-status" | "mine-log" | "wallet-show-mnemonic" | "wallet-status" | "address" | "ids" | "balance" | "locks" | "domains" | "show";
2
+ export type CommandName = "init" | "reset" | "repair" | "update" | "sync" | "status" | "client-change-password" | "follow" | "bitcoin-start" | "bitcoin-stop" | "bitcoin-status" | "bitcoin-transfer" | "indexer-start" | "indexer-stop" | "indexer-status" | "anchor" | "register" | "transfer" | "sell" | "unsell" | "buy" | "domain-endpoint-set" | "domain-endpoint-clear" | "domain-delegate-set" | "domain-delegate-clear" | "domain-miner-set" | "domain-miner-clear" | "domain-canonical" | "fields" | "field" | "field-create" | "field-set" | "field-clear" | "send" | "claim" | "reclaim" | "cog-lock" | "rep-give" | "rep-revoke" | "mine" | "mine-setup" | "mine-prompt" | "mine-prompt-list" | "mine-status" | "mine-log" | "wallet-show-mnemonic" | "wallet-status" | "address" | "ids" | "balance" | "locks" | "domains" | "show";
3
3
  type AliasMatchMode = "always" | "requires-arg" | "end-or-flag";
4
4
  interface HelpEntry {
5
5
  usage: string;
@@ -31,38 +31,6 @@ const commandSpecs = [
31
31
  return "cogcoin update";
32
32
  },
33
33
  },
34
- {
35
- id: "client-unlock",
36
- handlerFamily: "client-admin",
37
- supportsYes: false,
38
- supportsSatvb: false,
39
- aliases: [{ tokens: ["client", "unlock"] }],
40
- helpEntries: [
41
- {
42
- usage: "client unlock",
43
- description: "Unlock password-protected local wallet secrets for a limited time",
44
- },
45
- ],
46
- describeCommand() {
47
- return "cogcoin client unlock";
48
- },
49
- },
50
- {
51
- id: "client-lock",
52
- handlerFamily: "client-admin",
53
- supportsYes: false,
54
- supportsSatvb: false,
55
- aliases: [{ tokens: ["client", "lock"] }],
56
- helpEntries: [
57
- {
58
- usage: "client lock",
59
- description: "Flush the cached client password unlock session",
60
- },
61
- ],
62
- describeCommand() {
63
- return "cogcoin client lock";
64
- },
65
- },
66
34
  {
67
35
  id: "client-change-password",
68
36
  handlerFamily: "client-admin",
@@ -308,38 +276,6 @@ const commandSpecs = [
308
276
  return "cogcoin mine";
309
277
  },
310
278
  },
311
- {
312
- id: "mine-start",
313
- handlerFamily: "mining-runtime",
314
- supportsYes: false,
315
- supportsSatvb: false,
316
- aliases: [{ tokens: ["mine", "start"] }],
317
- helpEntries: [
318
- {
319
- usage: "mine start",
320
- description: "Start the miner as a background worker",
321
- },
322
- ],
323
- describeCommand() {
324
- return "cogcoin mine start";
325
- },
326
- },
327
- {
328
- id: "mine-stop",
329
- handlerFamily: "mining-runtime",
330
- supportsYes: false,
331
- supportsSatvb: false,
332
- aliases: [{ tokens: ["mine", "stop"] }],
333
- helpEntries: [
334
- {
335
- usage: "mine stop",
336
- description: "Stop the active background miner",
337
- },
338
- ],
339
- describeCommand() {
340
- return "cogcoin mine stop";
341
- },
342
- },
343
279
  {
344
280
  id: "mine-setup",
345
281
  handlerFamily: "mining-admin",
@@ -949,6 +885,8 @@ const commandSpecs = [
949
885
  },
950
886
  ];
951
887
  const removedPathSpecs = [
888
+ { tokens: ["client", "unlock"], errorCode: "cli_client_unlock_removed" },
889
+ { tokens: ["client", "lock"], errorCode: "cli_client_lock_removed" },
952
890
  { tokens: ["restore"], errorCode: "cli_restore_removed" },
953
891
  { tokens: ["wallet", "delete"], errorCode: "cli_wallet_delete_removed" },
954
892
  { tokens: ["wallet", "restore"], errorCode: "cli_wallet_restore_removed" },
@@ -1,30 +1,15 @@
1
1
  import { writeLine } from "../io.js";
2
2
  import { writeHandledCliError } from "../output.js";
3
- import { changeClientPassword, lockClientPassword, unlockClientPassword, } from "../../wallet/state/provider.js";
3
+ import { changeClientPassword, } from "../../wallet/state/provider.js";
4
4
  function createCommandPrompter(context) {
5
5
  return context.createPrompter();
6
6
  }
7
7
  export async function runClientAdminCommand(parsed, context) {
8
8
  try {
9
- if (parsed.command === "client-lock") {
10
- await lockClientPassword(context.walletSecretProvider);
11
- writeLine(context.stdout, "Client locked.");
12
- return 0;
13
- }
14
- if (parsed.command === "client-unlock") {
15
- const prompter = createCommandPrompter(context);
16
- const status = await unlockClientPassword(context.walletSecretProvider, prompter);
17
- writeLine(context.stdout, status.unlockUntilUnixMs === null
18
- ? "Client unlocked."
19
- : `Client unlocked until ${new Date(status.unlockUntilUnixMs).toISOString()}.`);
20
- return 0;
21
- }
22
9
  if (parsed.command === "client-change-password") {
23
10
  const prompter = createCommandPrompter(context);
24
- const status = await changeClientPassword(context.walletSecretProvider, prompter);
25
- writeLine(context.stdout, status.unlockUntilUnixMs === null
26
- ? "Client password changed."
27
- : `Client password changed. Client unlocked until ${new Date(status.unlockUntilUnixMs).toISOString()}.`);
11
+ await changeClientPassword(context.walletSecretProvider, prompter);
12
+ writeLine(context.stdout, "Client password changed.");
28
13
  return 0;
29
14
  }
30
15
  writeLine(context.stderr, `client admin command not implemented: ${parsed.command}`);
@@ -1,12 +1,13 @@
1
1
  import { dirname } from "node:path";
2
2
  import { DEFAULT_SNAPSHOT_METADATA, resolveBootstrapPathsForTesting } from "../../bitcoind/bootstrap.js";
3
3
  import { createEmptyMiningFollowVisualizerState, MiningFollowVisualizer, } from "../../wallet/mining/visualizer.js";
4
+ import { createMiningStopRequestedError } from "../../wallet/mining/stop.js";
4
5
  import { resolveWalletRootIdFromLocalArtifacts } from "../../wallet/root-resolution.js";
5
6
  import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
7
+ import { bindClientPasswordPromptSessionPolicy } from "../../wallet/state/client-password/session-policy.js";
6
8
  import { ManagedIndexerProgressObserver, assertManagedIndexerStatusRecoverable, isManagedIndexerCaughtUp, pollManagedIndexerUntilCaughtUp, } from "../managed-indexer-observer.js";
7
9
  import { usesTtyProgress, writeLine } from "../io.js";
8
10
  import { writeHandledCliError } from "../output.js";
9
- import { formatNextStepLines, getMineStopNextSteps, } from "../workflow-hints.js";
10
11
  import { createCloseSignalWatcher, waitForCompletionOrStop } from "../signals.js";
11
12
  import { createSyncProgressReporter } from "../sync-progress.js";
12
13
  import { PASSIVE_UPDATE_CHECK_TIMEOUT_MS, applyUpdateCheckResult, compareSemver, createEmptyUpdateCheckCache, fetchLatestPublishedVersion, isUpdateCheckDisabled, loadUpdateCheckCache, persistUpdateCheckCache, shouldRefreshUpdateCheck, } from "../update-service.js";
@@ -317,7 +318,7 @@ export async function runMiningRuntimeCommand(parsed, context) {
317
318
  const packageVersion = await context.readPackageVersion();
318
319
  const runtimePaths = context.resolveWalletRuntimePaths();
319
320
  if (parsed.command === "mine") {
320
- const prompter = context.createPrompter();
321
+ const prompter = bindClientPasswordPromptSessionPolicy(context.createPrompter(), "mining-indefinite");
321
322
  const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, prompter);
322
323
  const ttyProgressActive = usesTtyProgress(parsed.progressOutput, context.stderr);
323
324
  await ensureMiningProviderSetup({
@@ -368,7 +369,7 @@ export async function runMiningRuntimeCommand(parsed, context) {
368
369
  : false;
369
370
  abortController = new AbortController();
370
371
  onStop = () => {
371
- abortController?.abort();
372
+ abortController?.abort(createMiningStopRequestedError());
372
373
  };
373
374
  context.signalSource.on("SIGINT", onStop);
374
375
  context.signalSource.on("SIGTERM", onStop);
@@ -397,63 +398,6 @@ export async function runMiningRuntimeCommand(parsed, context) {
397
398
  }
398
399
  return 0;
399
400
  }
400
- if (parsed.command === "mine-start") {
401
- const prompter = createCommandPrompter(context);
402
- const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, prompter);
403
- await ensureMiningProviderSetup({
404
- context,
405
- provider,
406
- prompter,
407
- runtimePaths,
408
- });
409
- const preflightCode = await syncManagedMiningReadiness({
410
- parsed,
411
- context,
412
- dataDir,
413
- databasePath: dbPath,
414
- expectedBinaryVersion: packageVersion,
415
- provider,
416
- runtimePaths,
417
- });
418
- if (preflightCode !== null) {
419
- return preflightCode;
420
- }
421
- const result = await context.startBackgroundMining({
422
- dataDir,
423
- databasePath: dbPath,
424
- provider,
425
- prompter,
426
- builtInSetupEnsured: true,
427
- paths: runtimePaths,
428
- });
429
- if (!result.started) {
430
- writeLine(context.stdout, "Background mining is already active.");
431
- if (result.snapshot?.backgroundWorkerPid !== null && result.snapshot?.backgroundWorkerPid !== undefined) {
432
- writeLine(context.stdout, `Worker pid: ${result.snapshot.backgroundWorkerPid}`);
433
- }
434
- return 0;
435
- }
436
- writeLine(context.stdout, "Started background mining.");
437
- if (result.snapshot?.backgroundWorkerPid !== null && result.snapshot?.backgroundWorkerPid !== undefined) {
438
- writeLine(context.stdout, `Worker pid: ${result.snapshot.backgroundWorkerPid}`);
439
- }
440
- return 0;
441
- }
442
- if (parsed.command === "mine-stop") {
443
- const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, context.createPrompter());
444
- const snapshot = await context.stopBackgroundMining({
445
- dataDir,
446
- databasePath: dbPath,
447
- provider,
448
- paths: runtimePaths,
449
- });
450
- const nextSteps = getMineStopNextSteps();
451
- writeLine(context.stdout, snapshot?.note ?? "Background mining was not active.");
452
- for (const line of formatNextStepLines(nextSteps)) {
453
- writeLine(context.stdout, line);
454
- }
455
- return 0;
456
- }
457
401
  writeLine(context.stderr, `mining runtime command not implemented: ${parsed.command}`);
458
402
  return 1;
459
403
  }