@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
@@ -3,7 +3,6 @@ import type { createRpcClient } from "../../bitcoind/node.js";
3
3
  import type { attachOrStartManagedBitcoindService, probeManagedBitcoindService } from "../../bitcoind/service.js";
4
4
  import type { RpcListUnspentEntry } from "../../bitcoind/types.js";
5
5
  import type { requestMiningGenerationPreemption } from "../mining/coordination.js";
6
- import type { startBackgroundMining } from "../mining/runner.js";
7
6
  import type { WalletRuntimePaths } from "../runtime.js";
8
7
  import type { WalletSecretProvider } from "../state/provider.js";
9
8
  import type { WalletStateV1 } from "../types.js";
@@ -45,7 +44,7 @@ export interface WalletRepairResult {
45
44
  indexerCompatibilityIssue: "none" | "service-version-mismatch" | "wallet-root-mismatch" | "schema-mismatch";
46
45
  indexerPostRepairHealth: "starting" | "catching-up" | "synced" | "failed";
47
46
  miningPreRepairRunMode: "stopped" | "foreground" | "background";
48
- miningResumeAction: "none" | "skipped-not-resumable" | "skipped-post-repair-blocked" | "resumed-background" | "resume-failed";
47
+ miningResumeAction: "none" | "skipped-not-resumable" | "skipped-post-repair-blocked" | "skipped-background-mode-removed";
49
48
  miningPostRepairRunMode: "stopped" | "background";
50
49
  miningResumeError: string | null;
51
50
  note: string | null;
@@ -107,7 +106,18 @@ export interface WalletLifecycleResolvedContext {
107
106
  paths: WalletRuntimePaths;
108
107
  nowUnixMs: number;
109
108
  }
110
- export interface WalletSetupContext extends WalletLifecycleResolvedContext, WalletSetupDependencies {
109
+ export interface WalletManagedCoreContext extends WalletLifecycleResolvedContext {
110
+ attachService: NonNullable<WalletManagedCoreDependencies["attachService"]>;
111
+ rpcFactory: NonNullable<WalletManagedCoreDependencies["rpcFactory"]>;
112
+ }
113
+ export interface WalletAccessContext extends WalletManagedCoreContext {
114
+ dataDir?: string;
115
+ }
116
+ export interface WalletLoadedState {
117
+ state: WalletStateV1;
118
+ source: "primary" | "backup";
119
+ }
120
+ export interface WalletSetupContext extends WalletManagedCoreContext {
111
121
  dataDir: string;
112
122
  prompter: WalletPrompter;
113
123
  }
@@ -116,10 +126,28 @@ export interface WalletRepairDependencies extends WalletManagedCoreDependencies
116
126
  attachIndexerDaemon?: typeof attachOrStartIndexerDaemon;
117
127
  probeIndexerDaemon?: typeof probeIndexerDaemon;
118
128
  requestMiningPreemption?: typeof requestMiningGenerationPreemption;
119
- startBackgroundMining?: typeof startBackgroundMining;
120
129
  }
121
- export interface WalletRepairContext extends WalletLifecycleResolvedContext, WalletRepairDependencies {
130
+ export interface WalletRepairContext extends WalletManagedCoreContext {
122
131
  dataDir: string;
123
132
  databasePath: string;
124
133
  assumeYes: boolean;
134
+ probeBitcoindService: NonNullable<WalletRepairDependencies["probeBitcoindService"]>;
135
+ attachIndexerDaemon: NonNullable<WalletRepairDependencies["attachIndexerDaemon"]>;
136
+ probeIndexerDaemon: NonNullable<WalletRepairDependencies["probeIndexerDaemon"]>;
137
+ requestMiningPreemption?: WalletRepairDependencies["requestMiningPreemption"];
138
+ }
139
+ export interface WalletBitcoindRepairStageResult {
140
+ state: WalletStateV1;
141
+ repairStateNeedsPersist: boolean;
142
+ recreatedManagedCoreWallet: boolean;
143
+ bitcoindServiceAction: WalletRepairResult["bitcoindServiceAction"];
144
+ bitcoindCompatibilityIssue: WalletRepairResult["bitcoindCompatibilityIssue"];
145
+ managedCoreReplicaAction: WalletRepairResult["managedCoreReplicaAction"];
146
+ bitcoindPostRepairHealth: WalletRepairResult["bitcoindPostRepairHealth"];
147
+ }
148
+ export interface WalletIndexerRepairStageResult {
149
+ resetIndexerDatabase: boolean;
150
+ indexerDaemonAction: WalletRepairResult["indexerDaemonAction"];
151
+ indexerCompatibilityIssue: WalletRepairResult["indexerCompatibilityIssue"];
152
+ indexerPostRepairHealth: WalletRepairResult["indexerPostRepairHealth"];
125
153
  }
@@ -8,6 +8,8 @@ export declare function withUnlockedManagedCoreWallet<T>(options: {
8
8
  walletName: string;
9
9
  internalPassphrase: string;
10
10
  timeoutSeconds?: number;
11
+ recoverLockedWalletOnce?: boolean;
12
+ onLockedWalletRecoveryOutcome?: (outcome: "recovered" | "still-locked") => void;
11
13
  run: () => Promise<T>;
12
14
  }): Promise<T>;
13
15
  export declare function clearLegacyWalletLockArtifacts(walletRuntimeRoot: string): Promise<void>;
@@ -3,10 +3,36 @@ import { join } from "node:path";
3
3
  export const MANAGED_CORE_WALLET_UNLOCK_TIMEOUT_SECONDS = 10;
4
4
  const LEGACY_UNLOCK_SESSION_FILENAME = "wallet-unlock-session.enc";
5
5
  const LEGACY_EXPLICIT_LOCK_FILENAME = "wallet-explicit-lock.json";
6
+ const MANAGED_CORE_WALLET_PROCESS_PSBT_LOCKED_ERROR_PREFIX = "bitcoind_rpc_walletprocesspsbt_-13_";
7
+ const MANAGED_CORE_WALLET_PROCESS_PSBT_LOCKED_ERROR_SUFFIX = "Please enter the wallet passphrase with walletpassphrase first.";
8
+ function isManagedCoreWalletProcessPsbtLockedError(error) {
9
+ return error instanceof Error
10
+ && error.message.startsWith(MANAGED_CORE_WALLET_PROCESS_PSBT_LOCKED_ERROR_PREFIX)
11
+ && error.message.endsWith(MANAGED_CORE_WALLET_PROCESS_PSBT_LOCKED_ERROR_SUFFIX);
12
+ }
6
13
  export async function withUnlockedManagedCoreWallet(options) {
7
14
  await options.rpc.walletPassphrase(options.walletName, options.internalPassphrase, options.timeoutSeconds ?? MANAGED_CORE_WALLET_UNLOCK_TIMEOUT_SECONDS);
8
15
  try {
9
- return await options.run();
16
+ try {
17
+ return await options.run();
18
+ }
19
+ catch (error) {
20
+ if (!options.recoverLockedWalletOnce || !isManagedCoreWalletProcessPsbtLockedError(error)) {
21
+ throw error;
22
+ }
23
+ await options.rpc.walletPassphrase(options.walletName, options.internalPassphrase, options.timeoutSeconds ?? MANAGED_CORE_WALLET_UNLOCK_TIMEOUT_SECONDS);
24
+ try {
25
+ const result = await options.run();
26
+ options.onLockedWalletRecoveryOutcome?.("recovered");
27
+ return result;
28
+ }
29
+ catch (retryError) {
30
+ if (isManagedCoreWalletProcessPsbtLockedError(retryError)) {
31
+ options.onLockedWalletRecoveryOutcome?.("still-locked");
32
+ }
33
+ throw retryError;
34
+ }
35
+ }
10
36
  }
11
37
  finally {
12
38
  await options.rpc.walletLock(options.walletName).catch(() => undefined);
@@ -56,5 +56,6 @@ export declare function generateCandidatesForDomains(options: {
56
56
  indexerTruthKey: IndexerTruthKey | null;
57
57
  runId?: string | null;
58
58
  fetchImpl?: typeof fetch;
59
+ signal?: AbortSignal;
59
60
  }): Promise<MiningCandidate[]>;
60
61
  export declare function chooseBestLocalCandidate(candidates: MiningCandidate[]): Promise<MiningCandidate | null>;
@@ -5,6 +5,7 @@ import { loadClientConfig } from "./config.js";
5
5
  import { isMiningGenerationAbortRequested, markMiningGenerationActive, markMiningGenerationInactive, } from "./coordination.js";
6
6
  import { createMiningSentenceRequestLimits } from "./sentence-protocol.js";
7
7
  import { generateMiningSentences } from "./sentences.js";
8
+ import { createMiningStopRequestedError } from "./stop.js";
8
9
  import { isMineableWalletDomain } from "../read/index.js";
9
10
  import { lookupDomain } from "@cogcoin/indexer/queries";
10
11
  const BEST_BLOCK_POLL_INTERVAL_MS = 500;
@@ -142,6 +143,11 @@ export async function generateCandidatesForDomains(options) {
142
143
  let stale = false;
143
144
  let staleIndexerTruth = false;
144
145
  let preempted = false;
146
+ let stopRequested = false;
147
+ const handleStop = () => {
148
+ stopRequested = true;
149
+ abortController.abort(options.signal?.reason ?? createMiningStopRequestedError());
150
+ };
145
151
  const timer = setInterval(async () => {
146
152
  try {
147
153
  const [current, truthCurrent] = await Promise.all([
@@ -171,6 +177,12 @@ export async function generateCandidatesForDomains(options) {
171
177
  }
172
178
  }, BEST_BLOCK_POLL_INTERVAL_MS);
173
179
  try {
180
+ if (options.signal?.aborted) {
181
+ handleStop();
182
+ }
183
+ else {
184
+ options.signal?.addEventListener("abort", handleStop, { once: true });
185
+ }
174
186
  await markMiningGenerationActive({
175
187
  paths: options.paths,
176
188
  runId: options.runId ?? null,
@@ -185,12 +197,26 @@ export async function generateCandidatesForDomains(options) {
185
197
  });
186
198
  let generated;
187
199
  try {
188
- generated = await generateMiningSentences(generationRequest, {
189
- paths: options.paths,
190
- provider: options.provider,
191
- signal: abortController.signal,
192
- fetchImpl: options.fetchImpl,
193
- });
200
+ generated = await Promise.race([
201
+ generateMiningSentences(generationRequest, {
202
+ paths: options.paths,
203
+ provider: options.provider,
204
+ signal: abortController.signal,
205
+ fetchImpl: options.fetchImpl,
206
+ }),
207
+ new Promise((_resolve, reject) => {
208
+ const handleAbort = () => {
209
+ reject(abortController.signal.reason instanceof Error
210
+ ? abortController.signal.reason
211
+ : createMiningStopRequestedError());
212
+ };
213
+ if (abortController.signal.aborted) {
214
+ handleAbort();
215
+ return;
216
+ }
217
+ abortController.signal.addEventListener("abort", handleAbort, { once: true });
218
+ }),
219
+ ]);
194
220
  }
195
221
  catch (error) {
196
222
  if (stale) {
@@ -202,6 +228,11 @@ export async function generateCandidatesForDomains(options) {
202
228
  if (preempted) {
203
229
  throw new Error("mining_generation_preempted");
204
230
  }
231
+ if (stopRequested || options.signal?.aborted) {
232
+ throw options.signal?.reason instanceof Error
233
+ ? options.signal.reason
234
+ : createMiningStopRequestedError();
235
+ }
205
236
  throw error;
206
237
  }
207
238
  if (stale) {
@@ -253,6 +284,7 @@ export async function generateCandidatesForDomains(options) {
253
284
  }
254
285
  finally {
255
286
  clearInterval(timer);
287
+ options.signal?.removeEventListener("abort", handleStop);
256
288
  await markMiningGenerationInactive({
257
289
  paths: options.paths,
258
290
  runId: options.runId ?? null,
@@ -19,4 +19,5 @@ export declare function runCompetitivenessGate(options: {
19
19
  assaySentencesImpl?: typeof assaySentences;
20
20
  cooperativeYield?: MiningCooperativeYield;
21
21
  cooperativeYieldEvery?: number;
22
+ throwIfStopping?: () => void;
22
23
  }): Promise<CompetitivenessDecision>;
@@ -371,6 +371,7 @@ export async function runCompetitivenessGate(options) {
371
371
  let mempoolVerbose;
372
372
  try {
373
373
  mempoolVerbose = await options.rpc.getRawMempoolVerbose();
374
+ options.throwIfStopping?.();
374
375
  }
375
376
  catch {
376
377
  return createDecision({
@@ -415,6 +416,7 @@ export async function runCompetitivenessGate(options) {
415
416
  cooperativeYield: options.cooperativeYield,
416
417
  cooperativeYieldEvery: options.cooperativeYieldEvery,
417
418
  });
419
+ options.throwIfStopping?.();
418
420
  const txid = visibleTxids[index];
419
421
  if (txContexts.has(txid)) {
420
422
  continue;
@@ -423,6 +425,7 @@ export async function runCompetitivenessGate(options) {
423
425
  options.rpc.getRawTransaction(txid, true).catch(() => null),
424
426
  options.rpc.getMempoolEntry(txid).catch(() => null),
425
427
  ]);
428
+ options.throwIfStopping?.();
426
429
  if (tx === null || mempoolEntry === null) {
427
430
  continue;
428
431
  }
@@ -447,6 +450,7 @@ export async function runCompetitivenessGate(options) {
447
450
  cooperativeYield: options.cooperativeYield,
448
451
  cooperativeYieldEvery: options.cooperativeYieldEvery,
449
452
  });
453
+ options.throwIfStopping?.();
450
454
  const txid = visibleTxids[index];
451
455
  const context = txContexts.get(txid);
452
456
  if (context === undefined || context.payload === null || context.senderScriptHex === null) {
@@ -463,6 +467,7 @@ export async function runCompetitivenessGate(options) {
463
467
  domainId: decoded.domainId,
464
468
  senderScriptHex: context.senderScriptHex,
465
469
  });
470
+ options.throwIfStopping?.();
466
471
  if (overlayDomain === "indeterminate") {
467
472
  const decision = createDecision({
468
473
  competitivenessGateIndeterminate: true,
@@ -487,6 +492,7 @@ export async function runCompetitivenessGate(options) {
487
492
  continue;
488
493
  }
489
494
  const assayed = await assaySentencesImpl(decoded.domainId, options.candidate.referencedBlockHashInternal, [Buffer.from(decoded.sentenceBytes).toString("utf8")]).catch(() => []);
495
+ options.throwIfStopping?.();
490
496
  const scored = assayed[0];
491
497
  if (scored === undefined || !scored.gatesPass || scored.encodedSentenceBytes === null || scored.canonicalBlend === null) {
492
498
  continue;
@@ -27,6 +27,7 @@ export declare function runMiningPhaseMachine(options: {
27
27
  attachService: typeof attachOrStartManagedBitcoindService;
28
28
  rpcFactory: (config: Parameters<typeof createRpcClient>[0]) => MiningRpcClient;
29
29
  fetchImpl?: typeof fetch;
30
+ stopSignal?: AbortSignal;
30
31
  generateCandidatesForDomainsImpl?: typeof generateCandidatesForDomains;
31
32
  runCompetitivenessGateImpl?: typeof runCompetitivenessGate;
32
33
  assaySentencesImpl?: typeof assaySentences;
@@ -35,5 +36,6 @@ export declare function runMiningPhaseMachine(options: {
35
36
  nowImpl?: () => number;
36
37
  saveCycleStatus: (readContext: WalletReadContext, overrides: MiningRuntimeStatusOverrides) => Promise<MiningRuntimeStatusV1>;
37
38
  appendEvent: (event: MiningEventRecord) => Promise<void>;
39
+ throwIfStopping?: () => void;
38
40
  throwIfSuspendDetected?: () => void;
39
41
  }): Promise<void>;
@@ -59,7 +59,12 @@ export async function runMiningPhaseMachine(options) {
59
59
  return false;
60
60
  }
61
61
  };
62
+ const throwIfInterrupted = () => {
63
+ options.throwIfSuspendDetected?.();
64
+ options.throwIfStopping?.();
65
+ };
62
66
  while (true) {
67
+ throwIfInterrupted();
63
68
  switch (state.phase) {
64
69
  case "idle": {
65
70
  if (options.corePublishState !== "healthy") {
@@ -198,8 +203,9 @@ export async function runMiningPhaseMachine(options) {
198
203
  indexerTruthKey,
199
204
  runId: options.backgroundWorkerRunId,
200
205
  fetchImpl: options.fetchImpl,
206
+ signal: options.stopSignal,
201
207
  });
202
- options.throwIfSuspendDetected?.();
208
+ throwIfInterrupted();
203
209
  }
204
210
  catch (error) {
205
211
  if (error instanceof MiningProviderRequestError) {
@@ -341,8 +347,9 @@ export async function runMiningPhaseMachine(options) {
341
347
  assaySentencesImpl: options.assaySentencesImpl,
342
348
  cooperativeYield: options.cooperativeYieldImpl,
343
349
  cooperativeYieldEvery: options.cooperativeYieldEvery,
350
+ throwIfStopping: options.throwIfStopping,
344
351
  });
345
- options.throwIfSuspendDetected?.();
352
+ throwIfInterrupted();
346
353
  state.gateSnapshot = {
347
354
  higherRankedCompetitorDomainCount: gate.higherRankedCompetitorDomainCount,
348
355
  dedupedCompetitorDomainCount: gate.dedupedCompetitorDomainCount,
@@ -421,7 +428,7 @@ export async function runMiningPhaseMachine(options) {
421
428
  if (!await ensureCurrentIndexerTruthOrRestart()) {
422
429
  return;
423
430
  }
424
- options.throwIfSuspendDetected?.();
431
+ throwIfInterrupted();
425
432
  const published = await publishCandidate({
426
433
  dataDir: options.dataDir,
427
434
  databasePath: options.databasePath,
@@ -436,6 +443,7 @@ export async function runMiningPhaseMachine(options) {
436
443
  appendEventFn: async (_paths, event) => {
437
444
  await options.appendEvent(event);
438
445
  },
446
+ throwIfStopping: options.throwIfStopping,
439
447
  });
440
448
  if (state.tipKey !== null
441
449
  && published.retryable !== true
@@ -462,6 +470,7 @@ export async function runMiningPhaseMachine(options) {
462
470
  mempoolSequenceCacheStatus: state.gateSnapshot.mempoolSequenceCacheStatus,
463
471
  lastMempoolSequence: state.gateSnapshot.lastMempoolSequence,
464
472
  lastCompetitivenessGateAtUnixMs: now(),
473
+ lastError: published.lastError ?? null,
465
474
  note: published.note,
466
475
  livePublishInMempool: published.state.miningState.livePublishInMempool,
467
476
  });
@@ -473,7 +482,7 @@ export async function runMiningPhaseMachine(options) {
473
482
  options.loopState.waitingNote = published.note;
474
483
  const lastError = published.decision === "publish-paused-insufficient-funds"
475
484
  ? published.lastError ?? createInsufficientFundsMiningPublishErrorMessage()
476
- : undefined;
485
+ : null;
477
486
  await options.saveCycleStatus({
478
487
  ...options.readContext,
479
488
  localState: {
@@ -526,6 +535,7 @@ export async function runMiningPhaseMachine(options) {
526
535
  mempoolSequenceCacheStatus: state.gateSnapshot.mempoolSequenceCacheStatus,
527
536
  lastMempoolSequence: state.gateSnapshot.lastMempoolSequence,
528
537
  lastCompetitivenessGateAtUnixMs: now(),
538
+ lastError: null,
529
539
  note: options.loopState.waitingNote,
530
540
  livePublishInMempool: published.state.miningState.livePublishInMempool,
531
541
  });
@@ -103,6 +103,7 @@ export interface MiningPublishRetryResult {
103
103
  txid: null;
104
104
  decision: "publish-retry-pending";
105
105
  note: string;
106
+ lastError?: string | null;
106
107
  skipped?: false;
107
108
  retryable: true;
108
109
  candidate: MiningCandidate;
@@ -1,7 +1,7 @@
1
1
  export { isMiningGenerationAbortRequested, markMiningGenerationActive, markMiningGenerationInactive, readMiningGenerationActivity, readMiningPreemptionRequest, requestMiningGenerationPreemption, } from "./coordination.js";
2
2
  export { canonicalizeMiningDomainPromptName, inspectMiningDomainPromptState, updateMiningDomainPrompt, } from "./domain-prompts.js";
3
3
  export { followMiningLog, inspectMiningControlPlane, readMiningLog, refreshMiningRuntimeStatus, setupBuiltInMining, } from "./control.js";
4
- export { ensureBuiltInMiningSetupIfNeeded, runBackgroundMiningWorker, runForegroundMining, startBackgroundMining, stopBackgroundMining, type MiningStartResult, } from "./runner.js";
4
+ export { ensureBuiltInMiningSetupIfNeeded, runForegroundMining, } from "./runner.js";
5
5
  export { appendMiningEvent, loadMiningRuntimeStatus, readMiningEvents, resolveRotatedMiningEventsPath, saveMiningRuntimeStatus, } from "./runtime-artifacts.js";
6
6
  export type { MiningSentenceCandidateV1, MiningSentenceGenerationRequestV1, MiningSentenceGenerationResponseV1, } from "./sentence-protocol.js";
7
7
  export { loadClientConfig, saveBuiltInMiningProviderConfig, saveClientConfig, } from "./config.js";
@@ -1,6 +1,6 @@
1
1
  export { isMiningGenerationAbortRequested, markMiningGenerationActive, markMiningGenerationInactive, readMiningGenerationActivity, readMiningPreemptionRequest, requestMiningGenerationPreemption, } from "./coordination.js";
2
2
  export { canonicalizeMiningDomainPromptName, inspectMiningDomainPromptState, updateMiningDomainPrompt, } from "./domain-prompts.js";
3
3
  export { followMiningLog, inspectMiningControlPlane, readMiningLog, refreshMiningRuntimeStatus, setupBuiltInMining, } from "./control.js";
4
- export { ensureBuiltInMiningSetupIfNeeded, runBackgroundMiningWorker, runForegroundMining, startBackgroundMining, stopBackgroundMining, } from "./runner.js";
4
+ export { ensureBuiltInMiningSetupIfNeeded, runForegroundMining, } from "./runner.js";
5
5
  export { appendMiningEvent, loadMiningRuntimeStatus, readMiningEvents, resolveRotatedMiningEventsPath, saveMiningRuntimeStatus, } from "./runtime-artifacts.js";
6
6
  export { loadClientConfig, saveBuiltInMiningProviderConfig, saveClientConfig, } from "./config.js";
@@ -14,6 +14,7 @@ export declare class MiningPublishRejectedError extends Error {
14
14
  }
15
15
  export declare function createStaleMiningCandidateWaitingNote(): string;
16
16
  export declare function createRetryableMiningPublishWaitingNote(): string;
17
+ export declare function createManagedCoreWalletRelockRetryWaitingNote(): string;
17
18
  export declare function createInsufficientFundsMiningPublishWaitingNote(): string;
18
19
  export declare function createInsufficientFundsMiningPublishErrorMessage(): string;
19
20
  export declare function resolveMiningConflictOutpoint(options: {
@@ -56,6 +57,7 @@ export declare function publishCandidateOnce(options: {
56
57
  rpcFactory: (config: Parameters<typeof createRpcClient>[0]) => MiningRpcClient;
57
58
  runId: string | null;
58
59
  appendEventFn?: AppendMiningEventFn;
60
+ throwIfStopping?: () => void;
59
61
  }): Promise<{
60
62
  state: WalletStateV1;
61
63
  txid: string | null;
@@ -75,5 +77,6 @@ export declare function publishCandidate(options: {
75
77
  runId: string | null;
76
78
  publishAttempt?: typeof publishCandidateOnce;
77
79
  appendEventFn: AppendMiningEventFn;
80
+ throwIfStopping?: () => void;
78
81
  }): Promise<MiningPublishOutcome>;
79
82
  export {};
@@ -22,12 +22,21 @@ export class MiningPublishRejectedError extends Error {
22
22
  this.revertedState = revertedState;
23
23
  }
24
24
  }
25
+ class ManagedCoreWalletRelockPendingError extends Error {
26
+ constructor(message) {
27
+ super(message);
28
+ this.name = "ManagedCoreWalletRelockPendingError";
29
+ }
30
+ }
25
31
  export function createStaleMiningCandidateWaitingNote() {
26
32
  return "Mining candidate changed before broadcast: the selected root domain is no longer locally mineable. Skipping this tip and waiting for the next block.";
27
33
  }
28
34
  export function createRetryableMiningPublishWaitingNote() {
29
35
  return "Selected mining candidate did not reach mempool and will be retried on the current tip with refreshed wallet state.";
30
36
  }
37
+ export function createManagedCoreWalletRelockRetryWaitingNote() {
38
+ return "Mining temporarily lost the managed Bitcoin wallet unlock and is retrying.";
39
+ }
31
40
  export function createInsufficientFundsMiningPublishWaitingNote() {
32
41
  return "Insufficient BTC to mine.";
33
42
  }
@@ -114,6 +123,8 @@ async function buildMiningTransaction(options) {
114
123
  mempoolRejectPrefix: "wallet_mining_mempool_rejected",
115
124
  feeRate: options.plan.feeRateSatVb,
116
125
  availableFundingMinConf: MINING_FUNDING_MIN_CONF,
126
+ recoverManagedCoreWalletLockedOnce: options.recoverManagedCoreWalletLockedOnce,
127
+ onManagedCoreWalletLockedRecoveryOutcome: options.onManagedCoreWalletLockedRecoveryOutcome,
117
128
  });
118
129
  }
119
130
  export async function probeMiningFundingAvailability(options) {
@@ -301,6 +312,7 @@ export async function publishCandidateOnce(options) {
301
312
  startHeight: 0,
302
313
  walletRootId: options.readContext.localState.state.walletRootId,
303
314
  });
315
+ options.throwIfStopping?.();
304
316
  const rpc = options.rpcFactory(service.rpc);
305
317
  let state = (await reconcileLiveMiningState({
306
318
  state: options.readContext.localState.state,
@@ -309,7 +321,9 @@ export async function publishCandidateOnce(options) {
309
321
  nodeBestHeight: options.readContext.nodeStatus?.nodeBestHeight ?? null,
310
322
  snapshotState: options.readContext.snapshot.state,
311
323
  })).state;
324
+ options.throwIfStopping?.();
312
325
  const allUtxos = await rpc.listUnspent(state.managedCoreWallet.walletName, MINING_FUNDING_MIN_CONF);
326
+ options.throwIfStopping?.();
313
327
  const conflictOutpoint = resolveMiningConflictOutpoint({
314
328
  state,
315
329
  allUtxos,
@@ -330,6 +344,7 @@ export async function publishCandidateOnce(options) {
330
344
  const feeSelection = await resolveWalletMutationFeeSelection({
331
345
  rpc,
332
346
  });
347
+ options.throwIfStopping?.();
333
348
  const nextFeeRate = feeSelection.feeRateSatVb;
334
349
  const plan = createMiningPlan({
335
350
  state,
@@ -338,12 +353,40 @@ export async function publishCandidateOnce(options) {
338
353
  allUtxos,
339
354
  feeRateSatVb: nextFeeRate,
340
355
  });
341
- const built = await buildMiningTransaction({
342
- rpc,
343
- walletName: state.managedCoreWallet.walletName,
344
- state,
345
- plan,
346
- });
356
+ let managedCoreWalletRelockOutcome = null;
357
+ let built;
358
+ try {
359
+ built = await buildMiningTransaction({
360
+ rpc,
361
+ walletName: state.managedCoreWallet.walletName,
362
+ state,
363
+ plan,
364
+ recoverManagedCoreWalletLockedOnce: true,
365
+ onManagedCoreWalletLockedRecoveryOutcome: (outcome) => {
366
+ managedCoreWalletRelockOutcome = outcome;
367
+ },
368
+ });
369
+ }
370
+ catch (error) {
371
+ if (managedCoreWalletRelockOutcome === "still-locked" && error instanceof Error) {
372
+ throw new ManagedCoreWalletRelockPendingError(error.message);
373
+ }
374
+ throw error;
375
+ }
376
+ options.throwIfStopping?.();
377
+ if (managedCoreWalletRelockOutcome === "recovered" && appendEventFn !== undefined) {
378
+ await appendEventFn(options.paths, createMiningEventRecord("managed-core-wallet-relock-recovered", "Managed Bitcoin Core wallet relocked during signing and was recovered automatically.", {
379
+ level: "warn",
380
+ runId: options.runId,
381
+ targetBlockHeight: options.candidate.targetBlockHeight,
382
+ referencedBlockHashDisplay: options.candidate.referencedBlockHashDisplay,
383
+ domainId: options.candidate.domainId,
384
+ domainName: options.candidate.domainName,
385
+ feeRateSatVb: nextFeeRate,
386
+ score: options.candidate.canonicalBlend.toString(),
387
+ reason: "managed-core-wallet-locked",
388
+ }));
389
+ }
347
390
  const intentFingerprintHex = computeIntentFingerprint(state, options.candidate);
348
391
  state = defaultMiningStatePatch(state, {
349
392
  state: "live",
@@ -375,8 +418,10 @@ export async function publishCandidateOnce(options) {
375
418
  provider: options.provider,
376
419
  paths: options.paths,
377
420
  });
421
+ options.throwIfStopping?.();
378
422
  try {
379
423
  await rpc.sendRawTransaction(built.rawHex);
424
+ options.throwIfStopping?.();
380
425
  }
381
426
  catch (error) {
382
427
  if (isAlreadyAcceptedError(error)) {
@@ -521,6 +566,7 @@ export async function publishCandidate(options) {
521
566
  candidate: null,
522
567
  };
523
568
  };
569
+ options.throwIfStopping?.();
524
570
  const lockedReadContext = await options.openReadContext({
525
571
  dataDir: options.dataDir,
526
572
  databasePath: options.databasePath,
@@ -529,6 +575,7 @@ export async function publishCandidate(options) {
529
575
  paths: options.paths,
530
576
  });
531
577
  try {
578
+ options.throwIfStopping?.();
532
579
  if (lockedReadContext.localState.availability !== "ready"
533
580
  || lockedReadContext.localState.state === null
534
581
  || lockedReadContext.snapshot === null
@@ -541,6 +588,7 @@ export async function publishCandidate(options) {
541
588
  return await createStaleCandidateSkipResult(readyReadContext.localState.state);
542
589
  }
543
590
  try {
591
+ options.throwIfStopping?.();
544
592
  const published = await publishAttempt({
545
593
  readContext: readyReadContext,
546
594
  candidate: refreshedCandidate,
@@ -551,6 +599,7 @@ export async function publishCandidate(options) {
551
599
  rpcFactory: options.rpcFactory,
552
600
  runId: options.runId,
553
601
  appendEventFn: options.appendEventFn,
602
+ throwIfStopping: options.throwIfStopping,
554
603
  });
555
604
  return {
556
605
  ...published,
@@ -558,6 +607,29 @@ export async function publishCandidate(options) {
558
607
  };
559
608
  }
560
609
  catch (error) {
610
+ if (error instanceof ManagedCoreWalletRelockPendingError) {
611
+ const note = createManagedCoreWalletRelockRetryWaitingNote();
612
+ const lastError = error.message;
613
+ await options.appendEventFn(options.paths, createMiningEventRecord("publish-retry-pending", "Managed Bitcoin Core wallet relocked during mining publish and will be retried on the current tip.", {
614
+ level: "warn",
615
+ runId: options.runId,
616
+ targetBlockHeight: refreshedCandidate.targetBlockHeight,
617
+ referencedBlockHashDisplay: refreshedCandidate.referencedBlockHashDisplay,
618
+ domainId: refreshedCandidate.domainId,
619
+ domainName: refreshedCandidate.domainName,
620
+ score: refreshedCandidate.canonicalBlend.toString(),
621
+ reason: "managed-core-wallet-locked",
622
+ }));
623
+ return {
624
+ state: readyReadContext.localState.state,
625
+ txid: null,
626
+ decision: "publish-retry-pending",
627
+ note,
628
+ lastError,
629
+ retryable: true,
630
+ candidate: refreshedCandidate,
631
+ };
632
+ }
561
633
  if (error instanceof Error && error.message === "wallet_mining_mempool_rejected_missing-inputs") {
562
634
  const note = createRetryableMiningPublishWaitingNote();
563
635
  const revertedState = error instanceof MiningPublishRejectedError
@@ -1,4 +1,3 @@
1
- import { spawn } from "node:child_process";
2
1
  import { assaySentences } from "@cogcoin/scoring";
3
2
  import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, stopManagedBitcoindService } from "../../bitcoind/service.js";
4
3
  import { createRpcClient } from "../../bitcoind/node.js";
@@ -13,7 +12,6 @@ import { type MiningRuntimeLoopState } from "./engine-state.js";
13
12
  import { saveStopSnapshot } from "./lifecycle.js";
14
13
  import type { CompetitivenessDecision, MiningCandidate, MiningCooperativeYield, MiningRpcClient } from "./engine-types.js";
15
14
  import { requestMiningGenerationPreemption } from "./coordination.js";
16
- import type { MiningRuntimeStatusV1 } from "./types.js";
17
15
  import { MiningFollowVisualizer } from "./visualizer.js";
18
16
  interface RunnerDependencies {
19
17
  openReadContext?: typeof openWalletReadContext;
@@ -23,8 +21,6 @@ interface RunnerDependencies {
23
21
  requestMiningPreemption?: typeof requestMiningGenerationPreemption;
24
22
  runMiningLoopImpl?: typeof runMiningLoop;
25
23
  saveStopSnapshotImpl?: typeof saveStopSnapshot;
26
- spawnWorkerProcess?: typeof spawn;
27
- waitForBackgroundHealthyImpl?: typeof waitForBackgroundHealthy;
28
24
  shutdownGraceMs?: number;
29
25
  sleepImpl?: typeof sleep;
30
26
  }
@@ -63,24 +59,6 @@ export interface RunForegroundMiningOptions extends RunnerDependencies {
63
59
  paths?: WalletRuntimePaths;
64
60
  visualizer?: MiningFollowVisualizer;
65
61
  }
66
- export interface StartBackgroundMiningOptions extends RunnerDependencies {
67
- dataDir: string;
68
- databasePath: string;
69
- provider?: WalletSecretProvider;
70
- prompter: WalletPrompter;
71
- builtInSetupEnsured?: boolean;
72
- paths?: WalletRuntimePaths;
73
- }
74
- export interface StopBackgroundMiningOptions extends RunnerDependencies {
75
- dataDir: string;
76
- databasePath: string;
77
- provider?: WalletSecretProvider;
78
- paths?: WalletRuntimePaths;
79
- }
80
- export interface MiningStartResult {
81
- started: boolean;
82
- snapshot: MiningRuntimeStatusV1 | null;
83
- }
84
62
  declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
85
63
  declare function generateCandidatesForDomains(options: Parameters<typeof generateCandidatesForDomainsModule>[0]): Promise<MiningCandidate[]>;
86
64
  declare function runCompetitivenessGate(options: Parameters<typeof runCompetitivenessGateModule>[0]): Promise<CompetitivenessDecision>;
@@ -119,17 +97,7 @@ declare function runMiningLoop(options: {
119
97
  cooperativeYieldImpl?: MiningCooperativeYield;
120
98
  cooperativeYieldEvery?: number;
121
99
  }): Promise<void>;
122
- declare function waitForBackgroundHealthy(paths: WalletRuntimePaths): Promise<MiningRuntimeStatusV1 | null>;
123
100
  export declare function runForegroundMining(options: RunForegroundMiningOptions): Promise<void>;
124
- export declare function startBackgroundMining(options: StartBackgroundMiningOptions): Promise<MiningStartResult>;
125
- export declare function stopBackgroundMining(options: StopBackgroundMiningOptions): Promise<MiningRuntimeStatusV1 | null>;
126
- export declare function runBackgroundMiningWorker(options: RunnerDependencies & {
127
- dataDir: string;
128
- databasePath: string;
129
- runId: string;
130
- provider?: WalletSecretProvider;
131
- paths?: WalletRuntimePaths;
132
- }): Promise<void>;
133
101
  export declare function performMiningCycleForTesting(options: {
134
102
  dataDir: string;
135
103
  databasePath: string;