@cogcoin/client 1.1.4 → 1.1.6

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 (107) hide show
  1. package/README.md +4 -5
  2. package/dist/bitcoind/indexer-daemon.d.ts +3 -7
  3. package/dist/bitcoind/indexer-daemon.js +43 -158
  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/indexer-policy.d.ts +34 -0
  7. package/dist/bitcoind/managed-runtime/indexer-policy.js +200 -0
  8. package/dist/bitcoind/managed-runtime/status.d.ts +11 -0
  9. package/dist/bitcoind/managed-runtime/status.js +59 -0
  10. package/dist/bitcoind/managed-runtime/types.d.ts +37 -0
  11. package/dist/bitcoind/managed-runtime/types.js +1 -0
  12. package/dist/bitcoind/progress/tty-renderer.js +3 -2
  13. package/dist/bitcoind/service.d.ts +2 -7
  14. package/dist/bitcoind/service.js +46 -94
  15. package/dist/cli/command-registry.d.ts +39 -0
  16. package/dist/cli/command-registry.js +1132 -0
  17. package/dist/cli/commands/client-admin.js +6 -56
  18. package/dist/cli/commands/mining-admin.js +9 -32
  19. package/dist/cli/commands/mining-read.js +15 -56
  20. package/dist/cli/commands/mining-runtime.js +258 -57
  21. package/dist/cli/commands/service-runtime.js +1 -64
  22. package/dist/cli/commands/status.js +2 -15
  23. package/dist/cli/commands/update.js +6 -21
  24. package/dist/cli/commands/wallet-admin.js +18 -120
  25. package/dist/cli/commands/wallet-mutation.js +4 -7
  26. package/dist/cli/commands/wallet-read.js +31 -138
  27. package/dist/cli/context.js +2 -4
  28. package/dist/cli/mining-format.js +8 -2
  29. package/dist/cli/mutation-command-groups.d.ts +11 -11
  30. package/dist/cli/mutation-command-groups.js +9 -18
  31. package/dist/cli/mutation-json.d.ts +1 -17
  32. package/dist/cli/mutation-json.js +1 -28
  33. package/dist/cli/mutation-success.d.ts +0 -1
  34. package/dist/cli/mutation-success.js +0 -19
  35. package/dist/cli/output.d.ts +1 -10
  36. package/dist/cli/output.js +52 -481
  37. package/dist/cli/parse.d.ts +1 -1
  38. package/dist/cli/parse.js +38 -695
  39. package/dist/cli/runner.js +28 -113
  40. package/dist/cli/types.d.ts +7 -8
  41. package/dist/cli/update-notifier.js +1 -1
  42. package/dist/cli/wallet-format.js +1 -1
  43. package/dist/wallet/lifecycle/access.d.ts +5 -0
  44. package/dist/wallet/lifecycle/access.js +79 -0
  45. package/dist/wallet/lifecycle/context.d.ts +26 -0
  46. package/dist/wallet/lifecycle/context.js +58 -0
  47. package/dist/wallet/lifecycle/managed-core.d.ts +15 -0
  48. package/dist/wallet/lifecycle/managed-core.js +197 -0
  49. package/dist/wallet/lifecycle/repair-bitcoind.d.ts +10 -0
  50. package/dist/wallet/lifecycle/repair-bitcoind.js +142 -0
  51. package/dist/wallet/lifecycle/repair-indexer.d.ts +8 -0
  52. package/dist/wallet/lifecycle/repair-indexer.js +117 -0
  53. package/dist/wallet/lifecycle/repair-mining.d.ts +49 -0
  54. package/dist/wallet/lifecycle/repair-mining.js +304 -0
  55. package/dist/wallet/lifecycle/repair-runtime.d.ts +36 -0
  56. package/dist/wallet/lifecycle/repair-runtime.js +206 -0
  57. package/dist/wallet/lifecycle/repair.d.ts +9 -0
  58. package/dist/wallet/lifecycle/repair.js +127 -0
  59. package/dist/wallet/lifecycle/setup-prompts.d.ts +7 -0
  60. package/dist/wallet/lifecycle/setup-prompts.js +88 -0
  61. package/dist/wallet/lifecycle/setup-state.d.ts +26 -0
  62. package/dist/wallet/lifecycle/setup-state.js +159 -0
  63. package/dist/wallet/lifecycle/setup.d.ts +15 -0
  64. package/dist/wallet/lifecycle/setup.js +124 -0
  65. package/dist/wallet/lifecycle/types.d.ts +156 -0
  66. package/dist/wallet/lifecycle/types.js +1 -0
  67. package/dist/wallet/lifecycle.d.ts +4 -165
  68. package/dist/wallet/lifecycle.js +3 -1656
  69. package/dist/wallet/mining/candidate.d.ts +60 -0
  70. package/dist/wallet/mining/candidate.js +290 -0
  71. package/dist/wallet/mining/competitiveness.d.ts +22 -0
  72. package/dist/wallet/mining/competitiveness.js +640 -0
  73. package/dist/wallet/mining/control.js +7 -251
  74. package/dist/wallet/mining/cycle.d.ts +39 -0
  75. package/dist/wallet/mining/cycle.js +542 -0
  76. package/dist/wallet/mining/engine-state.d.ts +66 -0
  77. package/dist/wallet/mining/engine-state.js +211 -0
  78. package/dist/wallet/mining/engine-types.d.ts +173 -0
  79. package/dist/wallet/mining/engine-types.js +1 -0
  80. package/dist/wallet/mining/engine-utils.d.ts +7 -0
  81. package/dist/wallet/mining/engine-utils.js +75 -0
  82. package/dist/wallet/mining/events.d.ts +2 -0
  83. package/dist/wallet/mining/events.js +19 -0
  84. package/dist/wallet/mining/lifecycle.d.ts +71 -0
  85. package/dist/wallet/mining/lifecycle.js +355 -0
  86. package/dist/wallet/mining/projection.d.ts +61 -0
  87. package/dist/wallet/mining/projection.js +319 -0
  88. package/dist/wallet/mining/publish.d.ts +79 -0
  89. package/dist/wallet/mining/publish.js +614 -0
  90. package/dist/wallet/mining/runner.d.ts +12 -418
  91. package/dist/wallet/mining/runner.js +274 -3433
  92. package/dist/wallet/mining/supervisor.d.ts +134 -0
  93. package/dist/wallet/mining/supervisor.js +558 -0
  94. package/dist/wallet/mining/visualizer-sync.d.ts +42 -0
  95. package/dist/wallet/mining/visualizer-sync.js +166 -0
  96. package/dist/wallet/mining/visualizer.d.ts +1 -0
  97. package/dist/wallet/mining/visualizer.js +33 -18
  98. package/dist/wallet/read/context.js +13 -188
  99. package/dist/wallet/reset.d.ts +1 -1
  100. package/dist/wallet/reset.js +35 -11
  101. package/dist/wallet/runtime.d.ts +0 -6
  102. package/dist/wallet/runtime.js +2 -38
  103. package/dist/wallet/tx/common.d.ts +18 -0
  104. package/dist/wallet/tx/common.js +40 -26
  105. package/package.json +1 -1
  106. package/dist/wallet/state/seed-index.d.ts +0 -43
  107. package/dist/wallet/state/seed-index.js +0 -151
@@ -0,0 +1,142 @@
1
+ import { normalizeWalletDescriptorState } from "../descriptor-normalization.js";
2
+ import { acquireFileLock } from "../fs/lock.js";
3
+ import { persistWalletCoinControlStateIfNeeded } from "../coin-control.js";
4
+ import { createWalletSecretReference } from "../state/provider.js";
5
+ import { recreateManagedCoreWalletReplica, verifyManagedCoreWalletReplica } from "./managed-core.js";
6
+ import { pathExists } from "./context.js";
7
+ import { clearManagedBitcoindArtifacts, mapBitcoindCompatibilityToRepairIssue, mapBitcoindRepairHealth, waitForProcessExit, } from "./repair-runtime.js";
8
+ export async function repairManagedBitcoindStage(options) {
9
+ let state = options.state;
10
+ let repairStateNeedsPersist = options.repairStateNeedsPersist;
11
+ let bitcoindServiceAction = "none";
12
+ let bitcoindCompatibilityIssue = "none";
13
+ let managedCoreReplicaAction = "none";
14
+ let recreatedManagedCoreWallet = false;
15
+ let initialBitcoindProbe = {
16
+ compatibility: "unreachable",
17
+ status: null,
18
+ error: null,
19
+ };
20
+ let bitcoindPostRepairHealth = "unavailable";
21
+ const bitcoindLock = await acquireFileLock(options.servicePaths.bitcoindLockPath, {
22
+ purpose: "managed-bitcoind-repair",
23
+ walletRootId: state.walletRootId,
24
+ dataDir: options.context.dataDir,
25
+ });
26
+ try {
27
+ initialBitcoindProbe = await options.context.probeBitcoindService({
28
+ dataDir: options.context.dataDir,
29
+ chain: "main",
30
+ startHeight: 0,
31
+ walletRootId: state.walletRootId,
32
+ });
33
+ bitcoindCompatibilityIssue = mapBitcoindCompatibilityToRepairIssue(initialBitcoindProbe.compatibility);
34
+ if (initialBitcoindProbe.compatibility === "service-version-mismatch"
35
+ || initialBitcoindProbe.compatibility === "wallet-root-mismatch"
36
+ || initialBitcoindProbe.compatibility === "runtime-mismatch") {
37
+ const processId = initialBitcoindProbe.status?.processId ?? null;
38
+ if (processId === null) {
39
+ throw new Error("managed_bitcoind_process_id_unavailable");
40
+ }
41
+ try {
42
+ process.kill(processId, "SIGTERM");
43
+ }
44
+ catch (error) {
45
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
46
+ throw error;
47
+ }
48
+ }
49
+ await waitForProcessExit(processId, 15_000, "managed_bitcoind_stop_timeout");
50
+ await clearManagedBitcoindArtifacts(options.servicePaths);
51
+ bitcoindServiceAction = "stopped-incompatible-service";
52
+ }
53
+ else if (initialBitcoindProbe.compatibility === "unreachable") {
54
+ const hasStaleArtifacts = await Promise.all([
55
+ options.servicePaths.bitcoindStatusPath,
56
+ options.servicePaths.bitcoindPidPath,
57
+ options.servicePaths.bitcoindReadyPath,
58
+ options.servicePaths.bitcoindWalletStatusPath,
59
+ ].map(pathExists));
60
+ if (hasStaleArtifacts.some(Boolean)) {
61
+ await clearManagedBitcoindArtifacts(options.servicePaths);
62
+ bitcoindServiceAction = "cleared-stale-artifacts";
63
+ }
64
+ }
65
+ else if (initialBitcoindProbe.compatibility === "protocol-error") {
66
+ throw new Error(initialBitcoindProbe.error ?? "managed_bitcoind_protocol_error");
67
+ }
68
+ }
69
+ finally {
70
+ await bitcoindLock.release();
71
+ }
72
+ const bitcoindHandle = await options.context.attachService({
73
+ dataDir: options.context.dataDir,
74
+ chain: "main",
75
+ startHeight: 0,
76
+ walletRootId: state.walletRootId,
77
+ });
78
+ try {
79
+ const rpc = options.context.rpcFactory(bitcoindHandle.rpc);
80
+ const normalizedDescriptorState = await normalizeWalletDescriptorState(state, rpc);
81
+ if (normalizedDescriptorState.changed) {
82
+ state = normalizedDescriptorState.state;
83
+ repairStateNeedsPersist = true;
84
+ }
85
+ const reconciledCoinControl = await persistWalletCoinControlStateIfNeeded({
86
+ state,
87
+ access: {
88
+ provider: options.context.provider,
89
+ secretReference: createWalletSecretReference(state.walletRootId),
90
+ },
91
+ paths: options.context.paths,
92
+ nowUnixMs: options.context.nowUnixMs,
93
+ replacePrimary: options.recoveredFromBackup && !repairStateNeedsPersist,
94
+ rpc,
95
+ });
96
+ state = reconciledCoinControl.state;
97
+ if (reconciledCoinControl.changed) {
98
+ repairStateNeedsPersist = false;
99
+ }
100
+ let replica = await verifyManagedCoreWalletReplica(state, options.context.dataDir, {
101
+ nodeHandle: bitcoindHandle,
102
+ attachService: options.context.attachService,
103
+ rpcFactory: options.context.rpcFactory,
104
+ });
105
+ if (replica.proofStatus !== "ready") {
106
+ state = await recreateManagedCoreWalletReplica(state, options.context.provider, options.context.paths, options.context.dataDir, options.context.nowUnixMs, {
107
+ attachService: options.context.attachService,
108
+ rpcFactory: options.context.rpcFactory,
109
+ });
110
+ recreatedManagedCoreWallet = true;
111
+ managedCoreReplicaAction = "recreated";
112
+ repairStateNeedsPersist = false;
113
+ replica = await verifyManagedCoreWalletReplica(state, options.context.dataDir, {
114
+ nodeHandle: bitcoindHandle,
115
+ attachService: options.context.attachService,
116
+ rpcFactory: options.context.rpcFactory,
117
+ });
118
+ }
119
+ const finalBitcoindStatus = await bitcoindHandle.refreshServiceStatus?.() ?? null;
120
+ const chainInfo = await rpc.getBlockchainInfo();
121
+ bitcoindPostRepairHealth = mapBitcoindRepairHealth({
122
+ serviceState: finalBitcoindStatus?.state ?? null,
123
+ catchingUp: chainInfo.blocks < chainInfo.headers,
124
+ replica,
125
+ });
126
+ if (bitcoindServiceAction === "none" && initialBitcoindProbe.compatibility === "unreachable") {
127
+ bitcoindServiceAction = "restarted-compatible-service";
128
+ }
129
+ }
130
+ finally {
131
+ await bitcoindHandle.stop?.().catch(() => undefined);
132
+ }
133
+ return {
134
+ state,
135
+ repairStateNeedsPersist,
136
+ recreatedManagedCoreWallet,
137
+ bitcoindServiceAction,
138
+ bitcoindCompatibilityIssue,
139
+ managedCoreReplicaAction,
140
+ bitcoindPostRepairHealth,
141
+ };
142
+ }
@@ -0,0 +1,8 @@
1
+ import type { ManagedServicePaths } from "../../bitcoind/service-paths.js";
2
+ import type { WalletStateV1 } from "../types.js";
3
+ import type { WalletIndexerRepairStageResult, WalletRepairContext } from "./types.js";
4
+ export declare function repairManagedIndexerStage(options: {
5
+ context: WalletRepairContext;
6
+ servicePaths: ManagedServicePaths;
7
+ state: WalletStateV1;
8
+ }): Promise<WalletIndexerRepairStageResult>;
@@ -0,0 +1,117 @@
1
+ import { acquireFileLock } from "../fs/lock.js";
2
+ import { pathExists } from "./context.js";
3
+ import { clearIndexerDaemonArtifacts, ensureIndexerDatabaseHealthy, mapIndexerCompatibilityToRepairIssue, verifyIndexerPostRepairHealth, waitForProcessExit, } from "./repair-runtime.js";
4
+ export async function repairManagedIndexerStage(options) {
5
+ let indexerDaemonAction = "none";
6
+ let indexerCompatibilityIssue = "none";
7
+ let initialIndexerDaemonInstanceId = null;
8
+ const indexerLock = await acquireFileLock(options.servicePaths.indexerDaemonLockPath, {
9
+ purpose: "indexer-daemon-repair",
10
+ walletRootId: options.state.walletRootId,
11
+ dataDir: options.context.dataDir,
12
+ databasePath: options.context.databasePath,
13
+ });
14
+ let resetIndexerDatabase = false;
15
+ try {
16
+ const initialProbe = await options.context.probeIndexerDaemon({
17
+ dataDir: options.context.dataDir,
18
+ walletRootId: options.state.walletRootId,
19
+ });
20
+ indexerCompatibilityIssue = mapIndexerCompatibilityToRepairIssue(initialProbe.compatibility);
21
+ initialIndexerDaemonInstanceId = initialProbe.status?.daemonInstanceId ?? null;
22
+ if (initialProbe.compatibility === "compatible") {
23
+ await initialProbe.client?.close().catch(() => undefined);
24
+ }
25
+ else if (initialProbe.compatibility === "service-version-mismatch"
26
+ || initialProbe.compatibility === "wallet-root-mismatch"
27
+ || initialProbe.compatibility === "schema-mismatch") {
28
+ const processId = initialProbe.status?.processId ?? null;
29
+ if (processId === null) {
30
+ throw new Error("indexer_daemon_process_id_unavailable");
31
+ }
32
+ try {
33
+ process.kill(processId, "SIGTERM");
34
+ }
35
+ catch (error) {
36
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
37
+ throw error;
38
+ }
39
+ }
40
+ await waitForProcessExit(processId);
41
+ await clearIndexerDaemonArtifacts(options.servicePaths);
42
+ indexerDaemonAction = "stopped-incompatible-daemon";
43
+ }
44
+ else if (initialProbe.compatibility === "unreachable") {
45
+ const hasStaleArtifacts = await Promise.all([
46
+ options.servicePaths.indexerDaemonSocketPath,
47
+ options.servicePaths.indexerDaemonStatusPath,
48
+ ].map(pathExists));
49
+ if (hasStaleArtifacts.some(Boolean)) {
50
+ await clearIndexerDaemonArtifacts(options.servicePaths);
51
+ indexerDaemonAction = "cleared-stale-artifacts";
52
+ }
53
+ }
54
+ else {
55
+ throw new Error(initialProbe.error ?? "indexer_daemon_protocol_error");
56
+ }
57
+ resetIndexerDatabase = await ensureIndexerDatabaseHealthy({
58
+ databasePath: options.context.databasePath,
59
+ dataDir: options.context.dataDir,
60
+ walletRootId: options.state.walletRootId,
61
+ resetIfNeeded: options.context.assumeYes,
62
+ });
63
+ }
64
+ finally {
65
+ await indexerLock.release();
66
+ }
67
+ let preAttachIndexerDaemonInstanceId = null;
68
+ const preAttachProbe = await options.context.probeIndexerDaemon({
69
+ dataDir: options.context.dataDir,
70
+ walletRootId: options.state.walletRootId,
71
+ });
72
+ if (preAttachProbe.compatibility === "compatible") {
73
+ preAttachIndexerDaemonInstanceId = preAttachProbe.status?.daemonInstanceId ?? null;
74
+ await preAttachProbe.client?.close().catch(() => undefined);
75
+ }
76
+ else if (preAttachProbe.compatibility !== "unreachable") {
77
+ throw new Error(preAttachProbe.error ?? "indexer_daemon_protocol_error");
78
+ }
79
+ const daemon = await options.context.attachIndexerDaemon({
80
+ dataDir: options.context.dataDir,
81
+ databasePath: options.context.databasePath,
82
+ walletRootId: options.state.walletRootId,
83
+ });
84
+ try {
85
+ const { health: indexerPostRepairHealth, daemonInstanceId: postRepairDaemonInstanceId, } = await verifyIndexerPostRepairHealth({
86
+ daemon,
87
+ probeIndexerDaemon: options.context.probeIndexerDaemon,
88
+ dataDir: options.context.dataDir,
89
+ walletRootId: options.state.walletRootId,
90
+ nowUnixMs: options.context.nowUnixMs,
91
+ });
92
+ const restartedIndexerDaemon = indexerDaemonAction !== "none" || preAttachProbe.compatibility === "unreachable";
93
+ if (restartedIndexerDaemon
94
+ && initialIndexerDaemonInstanceId !== null
95
+ && postRepairDaemonInstanceId === initialIndexerDaemonInstanceId) {
96
+ throw new Error("indexer_daemon_repair_identity_not_rotated");
97
+ }
98
+ if (!restartedIndexerDaemon
99
+ && preAttachProbe.compatibility === "compatible"
100
+ && preAttachIndexerDaemonInstanceId !== null
101
+ && postRepairDaemonInstanceId !== preAttachIndexerDaemonInstanceId) {
102
+ throw new Error("indexer_daemon_repair_identity_changed");
103
+ }
104
+ if (indexerDaemonAction === "none" && preAttachProbe.compatibility === "unreachable") {
105
+ indexerDaemonAction = "restarted-compatible-daemon";
106
+ }
107
+ return {
108
+ resetIndexerDatabase,
109
+ indexerDaemonAction,
110
+ indexerCompatibilityIssue,
111
+ indexerPostRepairHealth,
112
+ };
113
+ }
114
+ finally {
115
+ await daemon.close().catch(() => undefined);
116
+ }
117
+ }
@@ -0,0 +1,49 @@
1
+ import type { MiningRuntimeStatusV1 } from "../mining/types.js";
2
+ import type { WalletRuntimePaths } from "../runtime.js";
3
+ import { type WalletSecretProvider } from "../state/provider.js";
4
+ import type { WalletStateV1 } from "../types.js";
5
+ import type { WalletPrompter, WalletRepairResult } from "./types.js";
6
+ export declare function createSilentNonInteractivePrompter(): WalletPrompter;
7
+ export declare function applyRepairStoppedMiningState(state: WalletStateV1): WalletStateV1;
8
+ export declare function createStoppedMiningRuntimeSnapshotForRepair(options: {
9
+ state: WalletStateV1;
10
+ snapshot: MiningRuntimeStatusV1 | null;
11
+ nowUnixMs: number;
12
+ }): MiningRuntimeStatusV1;
13
+ export declare function persistRepairState(options: {
14
+ state: WalletStateV1;
15
+ provider: WalletSecretProvider;
16
+ paths: WalletRuntimePaths;
17
+ nowUnixMs: number;
18
+ replacePrimary?: boolean;
19
+ }): Promise<WalletStateV1>;
20
+ export declare function cleanupMiningForRepair(options: {
21
+ paths: WalletRuntimePaths;
22
+ state: WalletStateV1;
23
+ snapshot: MiningRuntimeStatusV1 | null;
24
+ nowUnixMs: number;
25
+ }): Promise<{
26
+ preRepairRunMode: WalletRepairResult["miningPreRepairRunMode"];
27
+ }>;
28
+ export declare function canResumeBackgroundMiningAfterRepair(options: {
29
+ provider: WalletSecretProvider;
30
+ paths: WalletRuntimePaths;
31
+ repairedState: WalletStateV1;
32
+ bitcoindPostRepairHealth: WalletRepairResult["bitcoindPostRepairHealth"];
33
+ indexerPostRepairHealth: WalletRepairResult["indexerPostRepairHealth"];
34
+ }): Promise<boolean>;
35
+ export declare function resumeBackgroundMiningAfterRepair(options: {
36
+ miningPreRepairRunMode: WalletRepairResult["miningPreRepairRunMode"];
37
+ provider: WalletSecretProvider;
38
+ paths: WalletRuntimePaths;
39
+ repairedState: WalletStateV1;
40
+ bitcoindPostRepairHealth: WalletRepairResult["bitcoindPostRepairHealth"];
41
+ indexerPostRepairHealth: WalletRepairResult["indexerPostRepairHealth"];
42
+ dataDir: string;
43
+ databasePath: string;
44
+ startBackgroundMining?: typeof import("../mining/runner.js").startBackgroundMining;
45
+ }): Promise<{
46
+ miningResumeAction: WalletRepairResult["miningResumeAction"];
47
+ miningPostRepairRunMode: WalletRepairResult["miningPostRepairRunMode"];
48
+ miningResumeError: string | null;
49
+ }>;
@@ -0,0 +1,304 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { readLockMetadata } from "../fs/lock.js";
4
+ import { readMiningGenerationActivity } from "../mining/coordination.js";
5
+ import { loadClientConfig } from "../mining/config.js";
6
+ import { saveMiningRuntimeStatus } from "../mining/runtime-artifacts.js";
7
+ import { normalizeMiningStateRecord } from "../mining/state.js";
8
+ import { createWalletSecretReference } from "../state/provider.js";
9
+ import { persistWalletStateUpdate } from "../descriptor-normalization.js";
10
+ import { isProcessAlive, stopRecordedManagedProcess } from "./repair-runtime.js";
11
+ export function createSilentNonInteractivePrompter() {
12
+ return {
13
+ isInteractive: false,
14
+ writeLine() { },
15
+ async prompt() {
16
+ return "";
17
+ },
18
+ };
19
+ }
20
+ export function applyRepairStoppedMiningState(state) {
21
+ const miningState = normalizeMiningStateRecord(state.miningState);
22
+ return {
23
+ ...state,
24
+ miningState: {
25
+ ...miningState,
26
+ runMode: "stopped",
27
+ state: miningState.livePublishInMempool
28
+ ? miningState.state === "paused-stale"
29
+ ? "paused-stale"
30
+ : "paused"
31
+ : miningState.state === "repair-required"
32
+ ? "repair-required"
33
+ : "idle",
34
+ pauseReason: miningState.livePublishInMempool
35
+ ? miningState.state === "paused-stale"
36
+ ? "stale-block-context"
37
+ : "wallet-repair"
38
+ : miningState.state === "repair-required"
39
+ ? miningState.pauseReason
40
+ : null,
41
+ },
42
+ };
43
+ }
44
+ function createStoppedBackgroundRuntimeSnapshot(snapshot, nowUnixMs) {
45
+ return {
46
+ ...snapshot,
47
+ updatedAtUnixMs: nowUnixMs,
48
+ runMode: "stopped",
49
+ backgroundWorkerPid: null,
50
+ backgroundWorkerRunId: null,
51
+ backgroundWorkerHeartbeatAtUnixMs: null,
52
+ backgroundWorkerHealth: null,
53
+ currentPhase: "idle",
54
+ note: snapshot.livePublishInMempool
55
+ ? "Background mining stopped for wallet repair. The last mining transaction may still confirm from mempool."
56
+ : "Background mining stopped for wallet repair.",
57
+ };
58
+ }
59
+ function resolveMiningGenerationRequestPath(paths) {
60
+ return join(paths.miningRoot, "generation-request.json");
61
+ }
62
+ function resolveMiningGenerationActivityPath(paths) {
63
+ return join(paths.miningRoot, "generation-activity.json");
64
+ }
65
+ function normalizeRepairMiningPid(value) {
66
+ return typeof value === "number" && Number.isInteger(value) && value > 0
67
+ ? value
68
+ : null;
69
+ }
70
+ function createRepairStoppedMiningNote(livePublishInMempool) {
71
+ return livePublishInMempool
72
+ ? "Background mining stopped for wallet repair. The last mining transaction may still confirm from mempool."
73
+ : "Background mining stopped for wallet repair.";
74
+ }
75
+ export function createStoppedMiningRuntimeSnapshotForRepair(options) {
76
+ const stoppedMiningState = normalizeMiningStateRecord(applyRepairStoppedMiningState(options.state).miningState);
77
+ const note = createRepairStoppedMiningNote(stoppedMiningState.livePublishInMempool);
78
+ if (options.snapshot !== null) {
79
+ return {
80
+ ...createStoppedBackgroundRuntimeSnapshot(options.snapshot, options.nowUnixMs),
81
+ miningState: stoppedMiningState.state,
82
+ currentPublishState: stoppedMiningState.currentPublishState,
83
+ targetBlockHeight: stoppedMiningState.currentBlockTargetHeight,
84
+ referencedBlockHashDisplay: stoppedMiningState.currentReferencedBlockHashDisplay,
85
+ currentDomainId: stoppedMiningState.currentDomainId,
86
+ currentDomainName: stoppedMiningState.currentDomain,
87
+ currentSentenceDisplay: stoppedMiningState.currentSentence,
88
+ currentTxid: stoppedMiningState.currentTxid,
89
+ currentWtxid: stoppedMiningState.currentWtxid,
90
+ livePublishInMempool: stoppedMiningState.livePublishInMempool,
91
+ currentFeeRateSatVb: stoppedMiningState.currentFeeRateSatVb,
92
+ currentAbsoluteFeeSats: stoppedMiningState.currentAbsoluteFeeSats,
93
+ currentBlockFeeSpentSats: stoppedMiningState.currentBlockFeeSpentSats,
94
+ sessionFeeSpentSats: stoppedMiningState.sessionFeeSpentSats,
95
+ lifetimeFeeSpentSats: stoppedMiningState.lifetimeFeeSpentSats,
96
+ currentPublishDecision: stoppedMiningState.currentPublishDecision,
97
+ pauseReason: stoppedMiningState.pauseReason,
98
+ note,
99
+ };
100
+ }
101
+ return {
102
+ schemaVersion: 1,
103
+ walletRootId: options.state.walletRootId,
104
+ workerApiVersion: null,
105
+ workerBinaryVersion: null,
106
+ workerBuildId: null,
107
+ updatedAtUnixMs: options.nowUnixMs,
108
+ runMode: "stopped",
109
+ backgroundWorkerPid: null,
110
+ backgroundWorkerRunId: null,
111
+ backgroundWorkerHeartbeatAtUnixMs: null,
112
+ backgroundWorkerHealth: null,
113
+ indexerDaemonState: null,
114
+ indexerDaemonInstanceId: null,
115
+ indexerSnapshotSeq: null,
116
+ indexerSnapshotOpenedAtUnixMs: null,
117
+ indexerTruthSource: undefined,
118
+ indexerHeartbeatAtUnixMs: null,
119
+ coreBestHeight: null,
120
+ coreBestHash: null,
121
+ indexerTipHeight: null,
122
+ indexerTipHash: null,
123
+ indexerReorgDepth: null,
124
+ indexerTipAligned: null,
125
+ corePublishState: null,
126
+ providerState: null,
127
+ lastSuspendDetectedAtUnixMs: null,
128
+ reconnectSettledUntilUnixMs: null,
129
+ tipSettledUntilUnixMs: null,
130
+ miningState: stoppedMiningState.state,
131
+ currentPhase: "idle",
132
+ currentPublishState: stoppedMiningState.currentPublishState,
133
+ targetBlockHeight: stoppedMiningState.currentBlockTargetHeight,
134
+ referencedBlockHashDisplay: stoppedMiningState.currentReferencedBlockHashDisplay,
135
+ currentDomainId: stoppedMiningState.currentDomainId,
136
+ currentDomainName: stoppedMiningState.currentDomain,
137
+ currentSentenceDisplay: stoppedMiningState.currentSentence,
138
+ currentCanonicalBlend: null,
139
+ currentTxid: stoppedMiningState.currentTxid,
140
+ currentWtxid: stoppedMiningState.currentWtxid,
141
+ livePublishInMempool: stoppedMiningState.livePublishInMempool,
142
+ currentFeeRateSatVb: stoppedMiningState.currentFeeRateSatVb,
143
+ currentAbsoluteFeeSats: stoppedMiningState.currentAbsoluteFeeSats,
144
+ currentBlockFeeSpentSats: stoppedMiningState.currentBlockFeeSpentSats,
145
+ sessionFeeSpentSats: stoppedMiningState.sessionFeeSpentSats,
146
+ lifetimeFeeSpentSats: stoppedMiningState.lifetimeFeeSpentSats,
147
+ sameDomainCompetitorSuppressed: null,
148
+ higherRankedCompetitorDomainCount: null,
149
+ dedupedCompetitorDomainCount: null,
150
+ competitivenessGateIndeterminate: null,
151
+ mempoolSequenceCacheStatus: null,
152
+ currentPublishDecision: stoppedMiningState.currentPublishDecision,
153
+ lastMempoolSequence: null,
154
+ lastCompetitivenessGateAtUnixMs: null,
155
+ pauseReason: stoppedMiningState.pauseReason,
156
+ providerConfigured: false,
157
+ providerKind: null,
158
+ bitcoindHealth: "unavailable",
159
+ bitcoindServiceState: null,
160
+ bitcoindReplicaStatus: null,
161
+ nodeHealth: "unavailable",
162
+ indexerHealth: "unavailable",
163
+ tipsAligned: null,
164
+ lastEventAtUnixMs: null,
165
+ lastError: null,
166
+ note,
167
+ };
168
+ }
169
+ export async function persistRepairState(options) {
170
+ return await persistWalletStateUpdate({
171
+ state: options.state,
172
+ access: {
173
+ provider: options.provider,
174
+ secretReference: createWalletSecretReference(options.state.walletRootId),
175
+ },
176
+ paths: options.paths,
177
+ nowUnixMs: options.nowUnixMs,
178
+ replacePrimary: options.replacePrimary,
179
+ });
180
+ }
181
+ export async function cleanupMiningForRepair(options) {
182
+ const controlLockMetadata = await readLockMetadata(options.paths.miningControlLockPath).catch(() => null);
183
+ const generationActivity = await readMiningGenerationActivity(options.paths).catch(() => null);
184
+ const controlLockPid = normalizeRepairMiningPid(controlLockMetadata?.processId);
185
+ const backgroundWorkerPid = normalizeRepairMiningPid(options.snapshot?.backgroundWorkerPid);
186
+ const generationOwnerPid = normalizeRepairMiningPid(generationActivity?.generationOwnerPid);
187
+ const discoveredPids = new Set();
188
+ let backgroundWorkerAlive = false;
189
+ let foregroundWorkerAlive = false;
190
+ const pidSources = [
191
+ { pid: backgroundWorkerPid, source: "background" },
192
+ { pid: controlLockPid, source: "foreground" },
193
+ { pid: generationOwnerPid, source: "foreground" },
194
+ ];
195
+ for (const source of pidSources) {
196
+ if (source.pid === null || source.pid === process.pid || !await isProcessAlive(source.pid)) {
197
+ continue;
198
+ }
199
+ discoveredPids.add(source.pid);
200
+ if (source.source === "background") {
201
+ backgroundWorkerAlive = true;
202
+ }
203
+ else {
204
+ foregroundWorkerAlive = true;
205
+ }
206
+ }
207
+ for (const pid of discoveredPids) {
208
+ await stopRecordedManagedProcess(pid, "mining_process_stop_timeout");
209
+ }
210
+ await rm(options.paths.miningControlLockPath, { force: true }).catch(() => undefined);
211
+ await rm(resolveMiningGenerationRequestPath(options.paths), { force: true }).catch(() => undefined);
212
+ await rm(resolveMiningGenerationActivityPath(options.paths), { force: true }).catch(() => undefined);
213
+ await saveMiningRuntimeStatus(options.paths.miningStatusPath, createStoppedMiningRuntimeSnapshotForRepair({
214
+ state: options.state,
215
+ snapshot: options.snapshot,
216
+ nowUnixMs: options.nowUnixMs,
217
+ }));
218
+ return {
219
+ preRepairRunMode: backgroundWorkerAlive
220
+ ? "background"
221
+ : foregroundWorkerAlive
222
+ ? "foreground"
223
+ : "stopped",
224
+ };
225
+ }
226
+ export async function canResumeBackgroundMiningAfterRepair(options) {
227
+ if (options.bitcoindPostRepairHealth !== "ready"
228
+ || options.indexerPostRepairHealth !== "synced"
229
+ || normalizeMiningStateRecord(options.repairedState.miningState).state === "repair-required") {
230
+ return false;
231
+ }
232
+ try {
233
+ const config = await loadClientConfig({
234
+ path: options.paths.clientConfigPath,
235
+ provider: options.provider,
236
+ });
237
+ return config?.mining.builtIn != null;
238
+ }
239
+ catch {
240
+ return false;
241
+ }
242
+ }
243
+ export async function resumeBackgroundMiningAfterRepair(options) {
244
+ const miningWasResumable = options.miningPreRepairRunMode === "background"
245
+ && normalizeMiningStateRecord(options.repairedState.miningState).state !== "repair-required";
246
+ if (options.miningPreRepairRunMode !== "background") {
247
+ return {
248
+ miningResumeAction: "none",
249
+ miningPostRepairRunMode: "stopped",
250
+ miningResumeError: null,
251
+ };
252
+ }
253
+ if (!miningWasResumable) {
254
+ return {
255
+ miningResumeAction: "skipped-not-resumable",
256
+ miningPostRepairRunMode: "stopped",
257
+ miningResumeError: null,
258
+ };
259
+ }
260
+ const postRepairResumeReady = await canResumeBackgroundMiningAfterRepair({
261
+ provider: options.provider,
262
+ paths: options.paths,
263
+ repairedState: options.repairedState,
264
+ bitcoindPostRepairHealth: options.bitcoindPostRepairHealth,
265
+ indexerPostRepairHealth: options.indexerPostRepairHealth,
266
+ });
267
+ if (!postRepairResumeReady) {
268
+ return {
269
+ miningResumeAction: "skipped-post-repair-blocked",
270
+ miningPostRepairRunMode: "stopped",
271
+ miningResumeError: null,
272
+ };
273
+ }
274
+ try {
275
+ const startBackgroundMining = options.startBackgroundMining
276
+ ?? (await import("../mining/runner.js")).startBackgroundMining;
277
+ const resumed = await startBackgroundMining({
278
+ dataDir: options.dataDir,
279
+ databasePath: options.databasePath,
280
+ provider: options.provider,
281
+ paths: options.paths,
282
+ prompter: createSilentNonInteractivePrompter(),
283
+ });
284
+ if (resumed.snapshot?.runMode === "background") {
285
+ return {
286
+ miningResumeAction: "resumed-background",
287
+ miningPostRepairRunMode: "background",
288
+ miningResumeError: null,
289
+ };
290
+ }
291
+ return {
292
+ miningResumeAction: "resume-failed",
293
+ miningPostRepairRunMode: "stopped",
294
+ miningResumeError: "Background mining did not report a background runtime after repair.",
295
+ };
296
+ }
297
+ catch (error) {
298
+ return {
299
+ miningResumeAction: "resume-failed",
300
+ miningPostRepairRunMode: "stopped",
301
+ miningResumeError: error instanceof Error ? error.message : String(error),
302
+ };
303
+ }
304
+ }
@@ -0,0 +1,36 @@
1
+ import { attachOrStartIndexerDaemon, probeIndexerDaemon } from "../../bitcoind/indexer-daemon.js";
2
+ import { probeManagedBitcoindService } from "../../bitcoind/service.js";
3
+ import { resolveManagedServicePaths } from "../../bitcoind/service-paths.js";
4
+ import type { ManagedBitcoindServiceStatus } from "../../bitcoind/types.js";
5
+ import type { WalletRepairResult } from "./types.js";
6
+ export declare function ensureIndexerDatabaseHealthy(options: {
7
+ databasePath: string;
8
+ dataDir: string;
9
+ walletRootId: string;
10
+ resetIfNeeded: boolean;
11
+ }): Promise<boolean>;
12
+ export declare function mapIndexerCompatibilityToRepairIssue(compatibility: Awaited<ReturnType<typeof probeIndexerDaemon>>["compatibility"]): WalletRepairResult["indexerCompatibilityIssue"];
13
+ export declare function mapBitcoindCompatibilityToRepairIssue(compatibility: Awaited<ReturnType<typeof probeManagedBitcoindService>>["compatibility"]): WalletRepairResult["bitcoindCompatibilityIssue"];
14
+ export declare function mapBitcoindRepairHealth(options: {
15
+ serviceState: ManagedBitcoindServiceStatus["state"] | null;
16
+ catchingUp: boolean;
17
+ replica: {
18
+ proofStatus?: "missing" | "mismatch" | "ready" | "not-proven";
19
+ } | null;
20
+ }): WalletRepairResult["bitcoindPostRepairHealth"];
21
+ export declare function verifyIndexerPostRepairHealth(options: {
22
+ daemon: Awaited<ReturnType<typeof attachOrStartIndexerDaemon>>;
23
+ probeIndexerDaemon: typeof probeIndexerDaemon;
24
+ dataDir: string;
25
+ walletRootId: string;
26
+ nowUnixMs: number;
27
+ }): Promise<{
28
+ health: WalletRepairResult["indexerPostRepairHealth"];
29
+ daemonInstanceId: string;
30
+ }>;
31
+ export declare function isProcessAlive(pid: number | null): Promise<boolean>;
32
+ export declare function waitForProcessExit(pid: number, timeoutMs?: number, errorCode?: string): Promise<void>;
33
+ export declare function clearIndexerDaemonArtifacts(servicePaths: ReturnType<typeof resolveManagedServicePaths>): Promise<void>;
34
+ export declare function clearManagedBitcoindArtifacts(servicePaths: ReturnType<typeof resolveManagedServicePaths>): Promise<void>;
35
+ export declare function stopRecordedManagedProcess(pid: number | null, errorCode: string): Promise<void>;
36
+ export declare function clearOrphanedRepairLocks(lockPaths: readonly string[]): Promise<void>;