@cogcoin/client 1.1.9 → 1.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/dist/bitcoind/client/managed-client.d.ts +2 -0
  3. package/dist/bitcoind/client/managed-client.js +6 -0
  4. package/dist/bitcoind/indexer-daemon/background-follow.d.ts +23 -0
  5. package/dist/bitcoind/indexer-daemon/background-follow.js +132 -0
  6. package/dist/bitcoind/indexer-daemon/client.d.ts +12 -0
  7. package/dist/bitcoind/indexer-daemon/client.js +137 -0
  8. package/dist/bitcoind/indexer-daemon/lifecycle.d.ts +30 -0
  9. package/dist/bitcoind/indexer-daemon/lifecycle.js +153 -0
  10. package/dist/bitcoind/indexer-daemon/process.d.ts +35 -0
  11. package/dist/bitcoind/indexer-daemon/process.js +140 -0
  12. package/dist/bitcoind/indexer-daemon/runtime.d.ts +23 -0
  13. package/dist/bitcoind/indexer-daemon/runtime.js +204 -0
  14. package/dist/bitcoind/indexer-daemon/server.d.ts +12 -0
  15. package/dist/bitcoind/indexer-daemon/server.js +87 -0
  16. package/dist/bitcoind/indexer-daemon/snapshot-leases.d.ts +23 -0
  17. package/dist/bitcoind/indexer-daemon/snapshot-leases.js +139 -0
  18. package/dist/bitcoind/indexer-daemon/status.d.ts +23 -0
  19. package/dist/bitcoind/indexer-daemon/status.js +282 -0
  20. package/dist/bitcoind/indexer-daemon/types.d.ts +141 -0
  21. package/dist/bitcoind/indexer-daemon/types.js +1 -0
  22. package/dist/bitcoind/indexer-daemon-main.js +14 -665
  23. package/dist/bitcoind/indexer-daemon.d.ts +4 -132
  24. package/dist/bitcoind/indexer-daemon.js +2 -417
  25. package/dist/bitcoind/managed-bitcoind-service-config.d.ts +30 -0
  26. package/dist/bitcoind/managed-bitcoind-service-config.js +202 -0
  27. package/dist/bitcoind/managed-bitcoind-service-lifecycle.d.ts +28 -0
  28. package/dist/bitcoind/managed-bitcoind-service-lifecycle.js +296 -0
  29. package/dist/bitcoind/managed-bitcoind-service-process.d.ts +8 -0
  30. package/dist/bitcoind/managed-bitcoind-service-process.js +48 -0
  31. package/dist/bitcoind/managed-bitcoind-service-replica.d.ts +8 -0
  32. package/dist/bitcoind/managed-bitcoind-service-replica.js +142 -0
  33. package/dist/bitcoind/managed-bitcoind-service-status.d.ts +42 -0
  34. package/dist/bitcoind/managed-bitcoind-service-status.js +170 -0
  35. package/dist/bitcoind/managed-bitcoind-service-types.d.ts +36 -0
  36. package/dist/bitcoind/managed-bitcoind-service-types.js +1 -0
  37. package/dist/bitcoind/service.d.ts +7 -63
  38. package/dist/bitcoind/service.js +7 -797
  39. package/dist/cli/mining-format.js +6 -1
  40. package/dist/cli/wallet-format/balance.js +1 -1
  41. package/dist/client/default-client.d.ts +3 -1
  42. package/dist/client/default-client.js +22 -0
  43. package/dist/types.d.ts +13 -1
  44. package/dist/wallet/fs/atomic.d.ts +11 -2
  45. package/dist/wallet/fs/atomic.js +45 -5
  46. package/dist/wallet/mining/cycle.js +4 -4
  47. package/dist/wallet/mining/engine-types.d.ts +1 -0
  48. package/dist/wallet/mining/engine-types.js +9 -1
  49. package/dist/wallet/mining/projection.d.ts +1 -0
  50. package/dist/wallet/mining/projection.js +15 -1
  51. package/dist/wallet/mining/publish.js +3 -6
  52. package/dist/wallet/mining/runner.js +30 -18
  53. package/dist/wallet/mining/visualizer-sync.js +7 -9
  54. package/dist/wallet/mining/visualizer.js +9 -7
  55. package/dist/wallet/read/context.d.ts +4 -10
  56. package/dist/wallet/read/context.js +6 -228
  57. package/dist/wallet/read/local-state.d.ts +36 -0
  58. package/dist/wallet/read/local-state.js +259 -0
  59. package/dist/wallet/read/managed-bitcoind.d.ts +30 -0
  60. package/dist/wallet/read/managed-bitcoind.js +138 -0
  61. package/dist/wallet/read/managed-indexer.d.ts +23 -0
  62. package/dist/wallet/read/managed-indexer.js +87 -0
  63. package/dist/wallet/read/managed-services.d.ts +6 -21
  64. package/dist/wallet/read/managed-services.js +23 -196
  65. package/dist/wallet/read/types.d.ts +1 -0
  66. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { resolveWaitingProviderNote } from "../wallet/mining/projection.js";
1
2
  function formatMaybeIso(unixMs) {
2
3
  return unixMs === null ? "none" : new Date(unixMs).toISOString();
3
4
  }
@@ -33,7 +34,11 @@ function resolveInsufficientFundsNextStep() {
33
34
  function resolveMiningRuntimeNote(mining) {
34
35
  return mining.runtime.currentPublishDecision === "publish-paused-insufficient-funds"
35
36
  ? "Insufficient BTC to mine."
36
- : mining.runtime.note;
37
+ : mining.runtime.note !== null
38
+ ? mining.runtime.note
39
+ : mining.runtime.currentPhase === "waiting-provider"
40
+ ? resolveWaitingProviderNote(mining.runtime.providerState)
41
+ : null;
37
42
  }
38
43
  export function formatMiningSummaryLine(mining) {
39
44
  const provider = mining.provider.configured
@@ -34,7 +34,7 @@ function renderBalanceArtCard(context) {
34
34
  return renderBalanceArtLine(line, "Funding address:", ` ${fundingAddress}`);
35
35
  }
36
36
  if (line.includes("Bitcoin Balance:")) {
37
- return renderBalanceArtLine(line, "Bitcoin Balance:", ` ${formatBitcoinAmount(context.fundingSpendableSats ?? null)}`);
37
+ return renderBalanceArtLine(line, "Bitcoin Balance:", ` ${formatBitcoinAmount(context.fundingDisplaySats ?? null)}`);
38
38
  }
39
39
  if (line.includes("Cogcoin Balance:")) {
40
40
  return renderBalanceArtLine(line, "Cogcoin Balance:", ` ${formatCogAmount(spendableCog)}`);
@@ -1,10 +1,12 @@
1
1
  import type { BitcoinBlock, GenesisParameters, IndexerState } from "@cogcoin/indexer/types";
2
- import type { ApplyBlockResult, Client, ClientCheckpoint, ClientStoreAdapter, ClientTip } from "../types.js";
2
+ import type { ApplyBlockResult, Client, ClientCheckpoint, ClientMirrorDelta, ClientMirrorSnapshot, ClientStoreAdapter, ClientTip } from "../types.js";
3
3
  export declare class DefaultClient implements Client {
4
4
  #private;
5
5
  constructor(store: ClientStoreAdapter, genesisParameters: GenesisParameters, state: IndexerState, tip: ClientTip | null, snapshotInterval: number, blockRecordRetention: number);
6
6
  getTip(): Promise<ClientTip | null>;
7
7
  getState(): Promise<IndexerState>;
8
+ readMirrorSnapshot(): Promise<ClientMirrorSnapshot>;
9
+ readMirrorDelta(afterHeight: number): Promise<ClientMirrorDelta>;
8
10
  applyBlock(block: BitcoinBlock): Promise<ApplyBlockResult>;
9
11
  rewindToHeight(height: number): Promise<ClientTip | null>;
10
12
  restoreCheckpoint(checkpoint: ClientCheckpoint): Promise<ClientTip>;
@@ -26,6 +26,28 @@ export class DefaultClient {
26
26
  await this.#queue;
27
27
  return this.#state;
28
28
  }
29
+ async readMirrorSnapshot() {
30
+ return this.#enqueue(async () => {
31
+ this.#assertOpen();
32
+ return {
33
+ tip: this.#tip === null ? null : { ...this.#tip },
34
+ stateBytes: serializeIndexerState(this.#state),
35
+ };
36
+ });
37
+ }
38
+ async readMirrorDelta(afterHeight) {
39
+ return this.#enqueue(async () => {
40
+ this.#assertOpen();
41
+ const blockRecords = await this.#store.loadBlockRecordsAfter(afterHeight);
42
+ return {
43
+ tip: this.#tip === null ? null : { ...this.#tip },
44
+ blockRecords: blockRecords.map((record) => ({
45
+ ...record,
46
+ recordBytes: new Uint8Array(record.recordBytes),
47
+ })),
48
+ };
49
+ });
50
+ }
29
51
  async applyBlock(block) {
30
52
  return this.#enqueue(async () => {
31
53
  this.#assertOpen();
package/dist/types.d.ts CHANGED
@@ -19,6 +19,18 @@ export interface StoredBlockRecord {
19
19
  recordBytes: Uint8Array;
20
20
  createdAt: number;
21
21
  }
22
+ export interface ClientMirrorSnapshot {
23
+ tip: ClientTip | null;
24
+ stateBytes: Uint8Array;
25
+ }
26
+ export interface ClientMirrorDelta {
27
+ tip: ClientTip | null;
28
+ blockRecords: StoredBlockRecord[];
29
+ }
30
+ export interface ClientMirrorReader {
31
+ readMirrorSnapshot(): Promise<ClientMirrorSnapshot>;
32
+ readMirrorDelta(afterHeight: number): Promise<ClientMirrorDelta>;
33
+ }
22
34
  export interface WriteAppliedBlockEntry {
23
35
  tip: ClientTip | null;
24
36
  stateBytes: Uint8Array | null;
@@ -48,7 +60,7 @@ export interface ApplyBlockResult {
48
60
  checkpoint: ClientCheckpoint | null;
49
61
  applied: AppliedBlock;
50
62
  }
51
- export interface Client {
63
+ export interface Client extends ClientMirrorReader {
52
64
  getTip(): Promise<ClientTip | null>;
53
65
  getState(): Promise<IndexerState>;
54
66
  applyBlock(block: BitcoinBlock): Promise<ApplyBlockResult>;
@@ -1,6 +1,15 @@
1
+ import { rename, rm } from "node:fs/promises";
1
2
  export interface AtomicWriteOptions {
2
3
  mode?: number;
3
4
  encoding?: BufferEncoding;
4
5
  }
5
- export declare function writeFileAtomic(filePath: string, data: string | Uint8Array, options?: AtomicWriteOptions): Promise<void>;
6
- export declare function writeJsonFileAtomic(filePath: string, value: unknown, options?: AtomicWriteOptions): Promise<void>;
6
+ export interface AtomicWriteDependencies {
7
+ platform?: NodeJS.Platform;
8
+ rename?: typeof rename;
9
+ rm?: typeof rm;
10
+ sleep?: (ms: number) => Promise<void>;
11
+ now?: () => number;
12
+ }
13
+ export declare function replaceFileAtomicWithRetryForTesting(tempPath: string, filePath: string, dependencies?: AtomicWriteDependencies): Promise<void>;
14
+ export declare function writeFileAtomic(filePath: string, data: string | Uint8Array, options?: AtomicWriteOptions, dependencies?: AtomicWriteDependencies): Promise<void>;
15
+ export declare function writeJsonFileAtomic(filePath: string, value: unknown, options?: AtomicWriteOptions, dependencies?: AtomicWriteDependencies): Promise<void>;
@@ -1,6 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdir, open, rename } from "node:fs/promises";
2
+ import { mkdir, open, rename, rm } from "node:fs/promises";
3
3
  import { basename, dirname, join } from "node:path";
4
+ const WINDOWS_REPLACE_RETRY_DELAY_MS = 25;
5
+ const WINDOWS_REPLACE_RETRY_TIMEOUT_MS = 1_000;
4
6
  async function fsyncDirectory(directoryPath) {
5
7
  try {
6
8
  const directoryHandle = await open(directoryPath, "r");
@@ -21,7 +23,39 @@ async function fsyncDirectory(directoryPath) {
21
23
  throw error;
22
24
  }
23
25
  }
24
- export async function writeFileAtomic(filePath, data, options = {}) {
26
+ function isRetryableWindowsReplaceError(error) {
27
+ if (!(error instanceof Error) || !("code" in error)) {
28
+ return false;
29
+ }
30
+ const code = error.code;
31
+ return code === "EPERM" || code === "EACCES" || code === "EBUSY";
32
+ }
33
+ async function sleep(ms) {
34
+ await new Promise((resolve) => setTimeout(resolve, ms));
35
+ }
36
+ export async function replaceFileAtomicWithRetryForTesting(tempPath, filePath, dependencies = {}) {
37
+ const renameImpl = dependencies.rename ?? rename;
38
+ if ((dependencies.platform ?? process.platform) !== "win32") {
39
+ await renameImpl(tempPath, filePath);
40
+ return;
41
+ }
42
+ const nowImpl = dependencies.now ?? Date.now;
43
+ const sleepImpl = dependencies.sleep ?? sleep;
44
+ const deadline = nowImpl() + WINDOWS_REPLACE_RETRY_TIMEOUT_MS;
45
+ while (true) {
46
+ try {
47
+ await renameImpl(tempPath, filePath);
48
+ return;
49
+ }
50
+ catch (error) {
51
+ if (!isRetryableWindowsReplaceError(error) || nowImpl() >= deadline) {
52
+ throw error;
53
+ }
54
+ await sleepImpl(WINDOWS_REPLACE_RETRY_DELAY_MS);
55
+ }
56
+ }
57
+ }
58
+ export async function writeFileAtomic(filePath, data, options = {}, dependencies = {}) {
25
59
  const directoryPath = dirname(filePath);
26
60
  const tempPath = join(directoryPath, `${basename(filePath)}.tmp-${randomUUID()}`);
27
61
  await mkdir(directoryPath, { recursive: true });
@@ -38,9 +72,15 @@ export async function writeFileAtomic(filePath, data, options = {}) {
38
72
  finally {
39
73
  await handle.close();
40
74
  }
41
- await rename(tempPath, filePath);
75
+ try {
76
+ await replaceFileAtomicWithRetryForTesting(tempPath, filePath, dependencies);
77
+ }
78
+ catch (error) {
79
+ await (dependencies.rm ?? rm)(tempPath, { force: true }).catch(() => undefined);
80
+ throw error;
81
+ }
42
82
  await fsyncDirectory(directoryPath);
43
83
  }
44
- export async function writeJsonFileAtomic(filePath, value, options = {}) {
45
- await writeFileAtomic(filePath, `${JSON.stringify(value, null, 2)}\n`, options);
84
+ export async function writeJsonFileAtomic(filePath, value, options = {}, dependencies = {}) {
85
+ await writeFileAtomic(filePath, `${JSON.stringify(value, null, 2)}\n`, options, dependencies);
46
86
  }
@@ -10,7 +10,7 @@ import { MiningProviderRequestError } from "./sentences.js";
10
10
  import { isInsufficientFundsError } from "../tx/common.js";
11
11
  import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
12
12
  import { createRpcClient } from "../../bitcoind/node.js";
13
- import { buildPrePublishStatusOverrides, } from "./projection.js";
13
+ import { buildPrePublishStatusOverrides, resolveWaitingProviderNote, } from "./projection.js";
14
14
  function createInitialState(options) {
15
15
  return {
16
16
  phase: "idle",
@@ -149,7 +149,7 @@ export async function runMiningPhaseMachine(options) {
149
149
  currentPublishDecision: null,
150
150
  providerState: options.loopState.providerWaitState,
151
151
  lastError: options.loopState.providerWaitLastError,
152
- note: "Mining is waiting for the sentence provider to recover.",
152
+ note: resolveWaitingProviderNote(options.loopState.providerWaitState),
153
153
  });
154
154
  return;
155
155
  }
@@ -162,7 +162,7 @@ export async function runMiningPhaseMachine(options) {
162
162
  currentPublishDecision: null,
163
163
  providerState: options.loopState.providerWaitState,
164
164
  lastError: options.loopState.providerWaitLastError,
165
- note: "Mining is waiting for the sentence provider to recover.",
165
+ note: resolveWaitingProviderNote(options.loopState.providerWaitState),
166
166
  });
167
167
  return;
168
168
  }
@@ -231,7 +231,7 @@ export async function runMiningPhaseMachine(options) {
231
231
  currentPublishDecision: null,
232
232
  providerState: options.loopState.providerWaitState ?? error.providerState,
233
233
  lastError: error.message,
234
- note: "Mining is waiting for the sentence provider to recover.",
234
+ note: resolveWaitingProviderNote(options.loopState.providerWaitState ?? error.providerState),
235
235
  });
236
236
  await options.appendEvent(createMiningEventRecord("publish-paused-provider", error.message, {
237
237
  level: "warn",
@@ -88,6 +88,7 @@ export type ReadyMiningReadContext = WalletReadContext & {
88
88
  snapshot: NonNullable<WalletReadContext["snapshot"]>;
89
89
  model: NonNullable<WalletReadContext["model"]>;
90
90
  };
91
+ export declare function resolveReadyMiningReadContext(readContext: WalletReadContext): ReadyMiningReadContext | null;
91
92
  export interface MiningPublishSkipResult {
92
93
  state: WalletStateV1;
93
94
  txid: null;
@@ -1 +1,9 @@
1
- export {};
1
+ export function resolveReadyMiningReadContext(readContext) {
2
+ if (readContext.localState.availability !== "ready"
3
+ || readContext.localState.state === null
4
+ || readContext.snapshot === null
5
+ || readContext.model === null) {
6
+ return null;
7
+ }
8
+ return readContext;
9
+ }
@@ -37,6 +37,7 @@ export interface MiningRuntimeStatusOverrides {
37
37
  note?: string | null;
38
38
  livePublishInMempool?: boolean | null;
39
39
  }
40
+ export declare function resolveWaitingProviderNote(providerState: MiningRuntimeStatusV1["providerState"] | null): string;
40
41
  export declare function buildPrePublishStatusOverrides(options: {
41
42
  state: WalletStateV1;
42
43
  candidate: MiningCandidate;
@@ -1,6 +1,20 @@
1
1
  import { livePublishTargetsCandidateTip } from "./engine-state.js";
2
2
  import { normalizeMiningPublishState, normalizeMiningStateRecord } from "./state.js";
3
3
  import { MINING_WORKER_API_VERSION, MINING_WORKER_HEARTBEAT_STALE_MS, } from "./constants.js";
4
+ export function resolveWaitingProviderNote(providerState) {
5
+ switch (providerState) {
6
+ case "backoff":
7
+ return "Mining is waiting because the sentence provider had a transient failure and will be retried automatically.";
8
+ case "rate-limited":
9
+ return "Mining is waiting because the sentence provider is rate limited and will be retried automatically.";
10
+ case "auth-error":
11
+ return "Mining is waiting because the sentence provider rejected the configured API key.";
12
+ case "not-found":
13
+ return "Mining is waiting because the configured sentence-provider model was not found.";
14
+ default:
15
+ return "Mining is waiting for the sentence provider to recover.";
16
+ }
17
+ }
4
18
  export function buildPrePublishStatusOverrides(options) {
5
19
  const replacing = options.state.miningState.currentTxid !== null;
6
20
  const replacingAcrossTips = replacing && !livePublishTargetsCandidateTip({
@@ -249,7 +263,7 @@ export async function buildMiningRuntimeStatusSnapshot(options) {
249
263
  : existing?.currentPhase === "resuming"
250
264
  ? "Mining discarded stale in-flight work after a large local runtime gap and is rechecking health."
251
265
  : reuseExistingProviderWait
252
- ? "Mining is waiting for the sentence provider to recover."
266
+ ? resolveWaitingProviderNote(existing?.providerState ?? providerState)
253
267
  : existing?.currentPhase === "waiting-indexer"
254
268
  ? "Mining is waiting for Bitcoin Core and the indexer to align."
255
269
  : existing?.currentPhase === "waiting-bitcoin-network"
@@ -6,7 +6,7 @@ import { serializeMine } from "../cogop/index.js";
6
6
  import { openWalletReadContext } from "../read/index.js";
7
7
  import { assertFixedInputPrefixMatches, buildWalletMutationTransaction, fundAndValidateWalletMutationDraft, isAlreadyAcceptedError, isBroadcastUnknownError, isInsufficientFundsError, outpointKey as walletMutationOutpointKey, reconcilePersistentPolicyLocks, resolveWalletMutationFeeSelection, saveWalletStatePreservingUnlock, } from "../tx/common.js";
8
8
  import { createMiningEventRecord } from "./events.js";
9
- import {} from "./engine-types.js";
9
+ import { resolveReadyMiningReadContext, } from "./engine-types.js";
10
10
  import { cloneMiningState, defaultMiningStatePatch, livePublishTargetsCandidateTip, miningCandidateIsCurrent, resolveSharedMiningConflictOutpoint, } from "./engine-state.js";
11
11
  import { deriveMiningWordIndices, numberToSats, resolveBip39WordsFromIndices, } from "./engine-utils.js";
12
12
  import { clearMiningPublishState, miningPublishMayStillExist, } from "./state.js";
@@ -576,13 +576,10 @@ export async function publishCandidate(options) {
576
576
  });
577
577
  try {
578
578
  options.throwIfStopping?.();
579
- if (lockedReadContext.localState.availability !== "ready"
580
- || lockedReadContext.localState.state === null
581
- || lockedReadContext.snapshot === null
582
- || lockedReadContext.model === null) {
579
+ const readyReadContext = resolveReadyMiningReadContext(lockedReadContext);
580
+ if (readyReadContext === null) {
583
581
  return await createStaleCandidateSkipResult(options.fallbackState);
584
582
  }
585
- const readyReadContext = lockedReadContext;
586
583
  const refreshedCandidate = refreshMiningCandidateFromCurrentState(readyReadContext, options.candidate);
587
584
  if (refreshedCandidate === null) {
588
585
  return await createStaleCandidateSkipResult(readyReadContext.localState.state);
@@ -27,6 +27,7 @@ import { createInsufficientFundsMiningPublishErrorMessage as createInsufficientF
27
27
  import { runMiningPhaseMachine } from "./cycle.js";
28
28
  import { attemptSaveMempool, handleDetectedMiningRuntimeResume, handleRecoverableMiningBitcoindFailure, isRecoverableMiningBitcoindError, refreshAndSaveMiningRuntimeStatus, resetMiningBitcoindRecoveryState, saveStopSnapshot, } from "./lifecycle.js";
29
29
  import { compareLexicographically, deriveMiningWordIndices, getBlockRewardCogtoshi, numberToSats, resolveBip39WordsFromIndices, rootDomain, tieBreakHash, } from "./engine-utils.js";
30
+ import { resolveReadyMiningReadContext } from "./engine-types.js";
30
31
  import { isMiningGenerationAbortRequested, markMiningGenerationActive, markMiningGenerationInactive, readMiningGenerationActivity, readMiningPreemptionRequest, requestMiningGenerationPreemption, } from "./coordination.js";
31
32
  import { clearMiningPublishState, miningPublishIsInMempool, miningPublishMayStillExist, normalizeMiningPublishState, normalizeMiningStateRecord, } from "./state.js";
32
33
  import { runForegroundMining as runForegroundMiningSupervisor, } from "./supervisor.js";
@@ -292,9 +293,20 @@ async function performMiningCycle(options) {
292
293
  }
293
294
  const displaySats = await resolveFundingDisplaySats(effectiveReadContext.localState.state, rpc).catch(() => null);
294
295
  syncMiningVisualizerBalances(options.loopState, effectiveReadContext, displaySats);
295
- if (effectiveReadContext.localState.state.miningState.state === "repair-required") {
296
+ const readyReadContext = resolveReadyMiningReadContext(effectiveReadContext);
297
+ if (readyReadContext === null) {
296
298
  clearMiningProviderWait(options.loopState);
297
299
  await saveCycleStatus(effectiveReadContext, {
300
+ runMode: options.runMode,
301
+ currentPhase: "waiting-indexer",
302
+ lastError: null,
303
+ note: "Mining is waiting for Bitcoin Core and the indexer to align.",
304
+ });
305
+ return;
306
+ }
307
+ if (readyReadContext.localState.state.miningState.state === "repair-required") {
308
+ clearMiningProviderWait(options.loopState);
309
+ await saveCycleStatus(readyReadContext, {
298
310
  runMode: options.runMode,
299
311
  currentPhase: "waiting",
300
312
  lastError: null,
@@ -302,9 +314,9 @@ async function performMiningCycle(options) {
302
314
  });
303
315
  return;
304
316
  }
305
- if (hasBlockingMutation(effectiveReadContext.localState.state)) {
317
+ if (hasBlockingMutation(readyReadContext.localState.state)) {
306
318
  clearMiningProviderWait(options.loopState);
307
- const nextState = defaultMiningStatePatch(effectiveReadContext.localState.state, {
319
+ const nextState = defaultMiningStatePatch(readyReadContext.localState.state, {
308
320
  state: "paused",
309
321
  pauseReason: "wallet-busy",
310
322
  });
@@ -313,15 +325,15 @@ async function performMiningCycle(options) {
313
325
  provider: options.provider,
314
326
  paths: options.paths,
315
327
  });
316
- effectiveReadContext = {
317
- ...effectiveReadContext,
328
+ const blockedReadContext = {
329
+ ...readyReadContext,
318
330
  localState: {
319
- ...effectiveReadContext.localState,
331
+ ...readyReadContext.localState,
320
332
  availability: "ready",
321
333
  state: nextState,
322
334
  },
323
335
  };
324
- await saveCycleStatus(effectiveReadContext, {
336
+ await saveCycleStatus(blockedReadContext, {
325
337
  runMode: options.runMode,
326
338
  currentPhase: "waiting",
327
339
  lastError: null,
@@ -332,9 +344,9 @@ async function performMiningCycle(options) {
332
344
  const preemptionRequest = await readMiningPreemptionRequest(options.paths);
333
345
  if (preemptionRequest !== null) {
334
346
  clearMiningProviderWait(options.loopState);
335
- const nextState = defaultMiningStatePatch(effectiveReadContext.localState.state, {
336
- state: effectiveReadContext.localState.state.miningState.livePublishInMempool
337
- && effectiveReadContext.localState.state.miningState.state === "paused-stale"
347
+ const nextState = defaultMiningStatePatch(readyReadContext.localState.state, {
348
+ state: readyReadContext.localState.state.miningState.livePublishInMempool
349
+ && readyReadContext.localState.state.miningState.state === "paused-stale"
338
350
  ? "paused-stale"
339
351
  : "paused",
340
352
  pauseReason: preemptionRequest.reason,
@@ -345,9 +357,9 @@ async function performMiningCycle(options) {
345
357
  paths: options.paths,
346
358
  });
347
359
  await saveCycleStatus({
348
- ...effectiveReadContext,
360
+ ...readyReadContext,
349
361
  localState: {
350
- ...effectiveReadContext.localState,
362
+ ...readyReadContext.localState,
351
363
  state: nextState,
352
364
  },
353
365
  }, {
@@ -370,10 +382,10 @@ async function performMiningCycle(options) {
370
382
  network: networkInfo,
371
383
  mempool: mempoolInfo,
372
384
  });
373
- clearRecoveredBitcoindError = resetMiningBitcoindRecoveryState(options.loopState, effectiveReadContext.nodeStatus?.serviceStatus ?? { pid: service.pid });
385
+ clearRecoveredBitcoindError = resetMiningBitcoindRecoveryState(options.loopState, readyReadContext.nodeStatus?.serviceStatus ?? { pid: service.pid });
374
386
  if (targetBlockHeight !== null && getBlockRewardCogtoshi(targetBlockHeight) === 0n) {
375
387
  clearMiningProviderWait(options.loopState);
376
- const nextState = defaultMiningStatePatch(effectiveReadContext.localState.state, {
388
+ const nextState = defaultMiningStatePatch(readyReadContext.localState.state, {
377
389
  state: "paused",
378
390
  pauseReason: "zero-reward",
379
391
  });
@@ -383,9 +395,9 @@ async function performMiningCycle(options) {
383
395
  paths: options.paths,
384
396
  });
385
397
  await saveCycleStatus({
386
- ...effectiveReadContext,
398
+ ...readyReadContext,
387
399
  localState: {
388
- ...effectiveReadContext.localState,
400
+ ...readyReadContext.localState,
389
401
  state: nextState,
390
402
  },
391
403
  }, {
@@ -397,7 +409,7 @@ async function performMiningCycle(options) {
397
409
  });
398
410
  await appendEvent(options.paths, createEvent("publish-skipped-zero-reward", "Skipped mining because the target block reward is zero.", {
399
411
  targetBlockHeight,
400
- referencedBlockHashDisplay: effectiveReadContext.nodeStatus?.nodeBestHashHex ?? null,
412
+ referencedBlockHashDisplay: readyReadContext.nodeStatus?.nodeBestHashHex ?? null,
401
413
  runId: options.backgroundWorkerRunId,
402
414
  }));
403
415
  return;
@@ -409,7 +421,7 @@ async function performMiningCycle(options) {
409
421
  paths: options.paths,
410
422
  runMode: options.runMode,
411
423
  backgroundWorkerRunId: options.backgroundWorkerRunId,
412
- readContext: effectiveReadContext,
424
+ readContext: readyReadContext,
413
425
  rpc,
414
426
  targetBlockHeight,
415
427
  tipKey,
@@ -1,8 +1,9 @@
1
1
  import { getBalance, getBlockWinners, lookupDomainById, } from "@cogcoin/indexer/queries";
2
2
  import { displayToInternalBlockhash } from "@cogcoin/scoring";
3
3
  import { FOLLOW_VISIBLE_PRIOR_BLOCKS } from "../../bitcoind/client/follow-block-times.js";
4
+ import { readFundingBalanceSummary } from "../read/local-state.js";
4
5
  import { buildMiningTipKey, resetMiningUiForTip } from "./engine-state.js";
5
- import { deriveMiningWordIndices, numberToSats, resolveBip39WordsFromIndices, } from "./engine-utils.js";
6
+ import { deriveMiningWordIndices, resolveBip39WordsFromIndices, } from "./engine-utils.js";
6
7
  import { createEmptyMiningFollowVisualizerState } from "./visualizer.js";
7
8
  function cloneSettledBoardEntries(entries) {
8
9
  return entries.map((entry) => ({
@@ -156,14 +157,11 @@ export function syncMiningUiForCurrentTip(options) {
156
157
  };
157
158
  }
158
159
  export async function resolveFundingDisplaySats(state, rpc) {
159
- const utxos = await rpc.listUnspent(state.managedCoreWallet.walletName, 0);
160
- return utxos.reduce((sum, entry) => {
161
- if (entry.scriptPubKey !== state.funding.scriptPubKeyHex
162
- || entry.spendable === false) {
163
- return sum;
164
- }
165
- return sum + numberToSats(entry.amount);
166
- }, 0n);
160
+ const summary = await readFundingBalanceSummary({
161
+ state,
162
+ rpc,
163
+ });
164
+ return summary.fundingDisplaySats ?? 0n;
167
165
  }
168
166
  export async function loadMiningVisibleFollowBlockTimes(options) {
169
167
  if (options.indexedTipHeight === null || options.indexedTipHashHex === null) {
@@ -3,6 +3,7 @@ import { centerLine, normalizeInlineText, truncateLine } from "../../bitcoind/pr
3
3
  import { advanceFollowSceneState, createFollowSceneState, replaceFollowBlockTimes, syncFollowSceneState, } from "../../bitcoind/progress/follow-scene.js";
4
4
  import { DEFAULT_RENDER_CLOCK, resolveTtyRenderPolicy, TtyRenderThrottle, } from "../../bitcoind/progress/render-policy.js";
5
5
  import { TtyProgressRenderer } from "../../bitcoind/progress/tty-renderer.js";
6
+ import { resolveWaitingProviderNote } from "./projection.js";
6
7
  const MINING_ARTWORK_COG_WIDTH = 22;
7
8
  const MINING_SENTENCE_BOARD_SIZE = 5;
8
9
  const MINING_SENTENCE_BOARD_WRAP_WIDTH = 80;
@@ -64,9 +65,6 @@ function formatRewardCogAmount(value) {
64
65
  minFractionDigits: 1,
65
66
  })} COG`;
66
67
  }
67
- function escapeRegExp(value) {
68
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
69
- }
70
68
  function consumeWrappedLine(text, capacity) {
71
69
  const remaining = text.trimStart();
72
70
  if (remaining.length <= capacity) {
@@ -91,12 +89,16 @@ function consumeWrappedLine(text, capacity) {
91
89
  function highlightRequiredWords(sentence, requiredWords) {
92
90
  const uniqueWords = [...new Set(requiredWords
93
91
  .map((word) => word.trim().toLowerCase())
94
- .filter((word) => word.length > 0))].sort((left, right) => right.length - left.length);
92
+ .filter((word) => word.length > 0))];
95
93
  if (uniqueWords.length === 0) {
96
94
  return sentence;
97
95
  }
98
- const pattern = new RegExp(`\\b(?:${uniqueWords.map(escapeRegExp).join("|")})\\b`, "gi");
99
- return sentence.replace(pattern, (match) => match.toUpperCase());
96
+ return sentence.replace(/[A-Za-z]+/g, (match) => {
97
+ const normalizedToken = match.toLowerCase();
98
+ return uniqueWords.some((word) => normalizedToken.includes(word))
99
+ ? match.toUpperCase()
100
+ : match;
101
+ });
100
102
  }
101
103
  function formatSentenceSlot(prefix, sentence, requiredWords, lineCount) {
102
104
  if (sentence === null) {
@@ -281,7 +283,7 @@ export function describeMiningVisualizerProgress(snapshot) {
281
283
  case "resuming":
282
284
  return "Mining discarded stale in-flight work after a large local runtime gap and is rechecking health.";
283
285
  case "waiting-provider":
284
- return "Mining is waiting for the sentence provider to recover.";
286
+ return resolveWaitingProviderNote(snapshot.providerState);
285
287
  case "waiting-indexer":
286
288
  return "Mining is waiting for Bitcoin Core and the indexer to align.";
287
289
  case "waiting-bitcoin-network":
@@ -1,14 +1,8 @@
1
- import { readSnapshotWithRetry } from "../../bitcoind/indexer-daemon.js";
1
+ export { readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
2
2
  import { type WalletSecretProvider } from "../state/provider.js";
3
- import type { WalletLocalStateStatus, WalletReadContext } from "./types.js";
3
+ import { inspectWalletLocalState } from "./local-state.js";
4
+ import type { WalletReadContext } from "./types.js";
4
5
  import type { WalletRuntimePaths } from "../runtime.js";
5
- declare function inspectWalletLocalState(options?: {
6
- dataDir?: string;
7
- secretProvider?: WalletSecretProvider;
8
- now?: number;
9
- paths?: WalletRuntimePaths;
10
- walletControlLockHeld?: boolean;
11
- }): Promise<WalletLocalStateStatus>;
12
6
  export declare function openWalletReadContext(options: {
13
7
  dataDir: string;
14
8
  databasePath: string;
@@ -19,4 +13,4 @@ export declare function openWalletReadContext(options: {
19
13
  now?: number;
20
14
  paths?: WalletRuntimePaths;
21
15
  }): Promise<WalletReadContext>;
22
- export { inspectWalletLocalState, readSnapshotWithRetry, };
16
+ export { inspectWalletLocalState, };