@cogcoin/client 1.1.0 → 1.1.2

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.
@@ -317,6 +317,6 @@ export async function syncToTip(dependencies) {
317
317
  lastError: message,
318
318
  message: "Managed sync can be resumed after the last error.",
319
319
  });
320
- throw new Error(message);
320
+ throw new Error(message, { cause: error instanceof Error ? error : undefined });
321
321
  }
322
322
  }
@@ -5,6 +5,7 @@ import { loadBundledGenesisParameters, serializeIndexerState } from "@cogcoin/in
5
5
  import { openManagedBitcoindClientInternal } from "./client.js";
6
6
  import { DEFAULT_SNAPSHOT_METADATA } from "./bootstrap.js";
7
7
  import { openClient } from "../client.js";
8
+ import { readPackageVersionFromDisk } from "../package-version.js";
8
9
  import { openSqliteStore } from "../sqlite/index.js";
9
10
  import { writeRuntimeStatusFile } from "../wallet/fs/status-file.js";
10
11
  import { createRpcClient } from "./node.js";
@@ -55,16 +56,6 @@ async function withTimeout(promise, timeoutMs, errorCode) {
55
56
  }
56
57
  }
57
58
  }
58
- async function readPackageVersionFromDisk() {
59
- try {
60
- const raw = await readFile(new URL("../../package.json", import.meta.url), "utf8");
61
- const parsed = JSON.parse(raw);
62
- return parsed.version ?? "0.0.0";
63
- }
64
- catch {
65
- return "0.0.0";
66
- }
67
- }
68
59
  function createSnapshotKey(appliedTip) {
69
60
  return appliedTip === null
70
61
  ? "__null__"
@@ -177,7 +168,7 @@ async function main() {
177
168
  const walletRootId = parseArg("wallet-root-id") || UNINITIALIZED_WALLET_ROOT_ID;
178
169
  const paths = resolveManagedServicePaths(dataDir, walletRootId);
179
170
  const daemonInstanceId = randomUUID();
180
- const binaryVersion = await readPackageVersionFromDisk();
171
+ const binaryVersion = await readPackageVersionFromDisk().catch(() => "0.0.0");
181
172
  const genesisParameters = await loadBundledGenesisParameters();
182
173
  const startedAtUnixMs = Date.now();
183
174
  const snapshots = new Map();
@@ -3,12 +3,14 @@ import { spawn } from "node:child_process";
3
3
  import { mkdir, readFile, rm } from "node:fs/promises";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import net from "node:net";
6
- import { compareSemver } from "../semver.js";
6
+ import { compareSemver, parseSemver } from "../semver.js";
7
7
  import { acquireFileLock, FileLockBusyError } from "../wallet/fs/lock.js";
8
8
  import { writeRuntimeStatusFile } from "../wallet/fs/status-file.js";
9
9
  import { INDEXER_DAEMON_SCHEMA_VERSION, INDEXER_DAEMON_SERVICE_API_VERSION, } from "./types.js";
10
10
  import { resolveManagedServicePaths, UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
11
11
  const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
12
+ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 5_000;
13
+ const FORCE_KILL_TIMEOUT_MS = 5_000;
12
14
  const INDEXER_DAEMON_REQUEST_TIMEOUT_MS = 15_000;
13
15
  const INDEXER_DAEMON_RESUME_BACKGROUND_FOLLOW_REQUEST_TIMEOUT_MS = 35_000;
14
16
  const INDEXER_DAEMON_BACKGROUND_FOLLOW_NOT_ACTIVE = "indexer_daemon_background_follow_not_active";
@@ -58,6 +60,11 @@ async function clearIndexerDaemonRuntimeArtifacts(paths) {
58
60
  await rm(paths.indexerDaemonStatusPath, { force: true }).catch(() => undefined);
59
61
  await rm(paths.indexerDaemonSocketPath, { force: true }).catch(() => undefined);
60
62
  }
63
+ function ignoreProcessNotFound(error) {
64
+ if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
65
+ throw error;
66
+ }
67
+ }
61
68
  export async function stopIndexerDaemonServiceWithLockHeld(options) {
62
69
  const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
63
70
  const paths = options.paths ?? resolveManagedServicePaths(options.dataDir, walletRootId);
@@ -74,11 +81,23 @@ export async function stopIndexerDaemonServiceWithLockHeld(options) {
74
81
  process.kill(processId, "SIGTERM");
75
82
  }
76
83
  catch (error) {
77
- if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
84
+ ignoreProcessNotFound(error);
85
+ }
86
+ try {
87
+ await waitForProcessExit(processId, options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS, "indexer_daemon_stop_timeout");
88
+ }
89
+ catch (error) {
90
+ if (!(error instanceof Error) || error.message !== "indexer_daemon_stop_timeout") {
78
91
  throw error;
79
92
  }
93
+ try {
94
+ process.kill(processId, "SIGKILL");
95
+ }
96
+ catch (killError) {
97
+ ignoreProcessNotFound(killError);
98
+ }
99
+ await waitForProcessExit(processId, FORCE_KILL_TIMEOUT_MS, "indexer_daemon_stop_timeout");
80
100
  }
81
- await waitForProcessExit(processId, options.shutdownTimeoutMs ?? 5_000, "indexer_daemon_stop_timeout");
82
101
  await clearIndexerDaemonRuntimeArtifacts(paths);
83
102
  return {
84
103
  status: "stopped",
@@ -283,8 +302,11 @@ function isStaleIndexerDaemonVersion(status, expectedBinaryVersion) {
283
302
  if (status === null || expectedBinaryVersion === null || expectedBinaryVersion === undefined) {
284
303
  return false;
285
304
  }
305
+ if (parseSemver(expectedBinaryVersion) === null) {
306
+ return false;
307
+ }
286
308
  const comparison = compareSemver(status.binaryVersion, expectedBinaryVersion);
287
- return comparison !== null && comparison < 0;
309
+ return comparison === null || comparison < 0;
288
310
  }
289
311
  async function probeIndexerDaemonAtSocket(socketPath, expectedWalletRootId) {
290
312
  const client = createIndexerDaemonClient(socketPath);
@@ -1,5 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { loadBundledGenesisParameters } from "@cogcoin/indexer";
3
+ import { readPackageVersionFromDisk } from "../package-version.js";
3
4
  import { attachOrStartIndexerDaemon, readObservedIndexerDaemonStatus, } from "./indexer-daemon.js";
4
5
  import { resolveCogcoinProcessingStartHeight } from "./processing-start-height.js";
5
6
  import { resolveManagedServicePaths, UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
@@ -31,6 +32,9 @@ async function resolveStartOptions(options) {
31
32
  };
32
33
  }
33
34
  export async function openManagedIndexerMonitor(options) {
35
+ const expectedBinaryVersion = options.expectedBinaryVersion === undefined
36
+ ? await readPackageVersionFromDisk()
37
+ : options.expectedBinaryVersion;
34
38
  const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
35
39
  const startOptions = await resolveStartOptions({
36
40
  dataDir: options.dataDir,
@@ -49,7 +53,7 @@ export async function openManagedIndexerMonitor(options) {
49
53
  walletRootId,
50
54
  startupTimeoutMs: options.startupTimeoutMs,
51
55
  ensureBackgroundFollow: true,
52
- expectedBinaryVersion: options.expectedBinaryVersion,
56
+ expectedBinaryVersion,
53
57
  });
54
58
  return createManagedIndexerMonitor({
55
59
  daemon,
@@ -128,8 +128,11 @@ export class ManagedProgressController {
128
128
  this.#cogcoinSyncTargetHeight = null;
129
129
  }
130
130
  if (this.#followVisualMode) {
131
+ const followIndexedHeight = phase === "follow_tip"
132
+ ? this.#cogcoinSyncHeight ?? this.#progress.blocks ?? this.#followScene.indexedHeight
133
+ : undefined;
131
134
  syncFollowSceneState(this.#followScene, {
132
- indexedHeight: phase === "follow_tip" ? this.#cogcoinSyncHeight : undefined,
135
+ indexedHeight: followIndexedHeight,
133
136
  nodeHeight: this.#progress.blocks,
134
137
  liveActivated: phase === "follow_tip" || this.#followScene.liveActivated,
135
138
  });
@@ -110,7 +110,13 @@ function renderFollowStatusField(statusFieldText, options) {
110
110
  return field;
111
111
  }
112
112
  function highestTrackedFollowHeight(state) {
113
- return Math.max(state.indexedHeight ?? Number.NEGATIVE_INFINITY, state.displayedCenterHeight ?? Number.NEGATIVE_INFINITY, state.animation?.height ?? Number.NEGATIVE_INFINITY, ...state.queuedHeights);
113
+ let highest = Math.max(state.indexedHeight ?? Number.NEGATIVE_INFINITY, state.displayedCenterHeight ?? Number.NEGATIVE_INFINITY, state.animation?.height ?? Number.NEGATIVE_INFINITY);
114
+ for (const height of state.queuedHeights) {
115
+ if (height > highest) {
116
+ highest = height;
117
+ }
118
+ }
119
+ return highest;
114
120
  }
115
121
  function resolveLatestAuthoritativeFollowHeight(indexedHeight, nodeHeight) {
116
122
  if (indexedHeight === null) {
@@ -1,3 +1,2 @@
1
1
  import type { CliRunnerContext, RequiredCliRunnerContext } from "./types.js";
2
- export declare function readPackageVersionFromDisk(): Promise<string>;
3
2
  export declare function createDefaultContext(overrides?: CliRunnerContext): RequiredCliRunnerContext;
@@ -1,11 +1,12 @@
1
1
  import { spawn } from "node:child_process";
2
- import { mkdir, readFile } from "node:fs/promises";
2
+ import { mkdir } from "node:fs/promises";
3
3
  import { attachOrStartIndexerDaemon, probeIndexerDaemon, readObservedIndexerDaemonStatus, stopIndexerDaemonService, } from "../bitcoind/indexer-daemon.js";
4
4
  import { createRpcClient } from "../bitcoind/node.js";
5
5
  import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, stopManagedBitcoindService, } from "../bitcoind/service.js";
6
6
  import { resolveDefaultBitcoindDataDirForTesting, resolveDefaultClientDatabasePathForTesting, resolveDefaultUpdateCheckStatePathForTesting, } from "../app-paths.js";
7
7
  import { openManagedBitcoindClient } from "../bitcoind/index.js";
8
8
  import { openManagedIndexerMonitor } from "../bitcoind/indexer-monitor.js";
9
+ import { readPackageVersionFromDisk } from "../package-version.js";
9
10
  import { inspectPassiveClientStatus } from "../passive-status.js";
10
11
  import { openSqliteStore } from "../sqlite/index.js";
11
12
  import { initializeWallet, deleteImportedWalletSeed, previewResetWallet, repairWallet, resetWallet, restoreWalletFromMnemonic, showWalletMnemonic, } from "../wallet/lifecycle.js";
@@ -16,28 +17,6 @@ import { ensureBuiltInMiningSetupIfNeeded, followMiningLog, inspectMiningControl
16
17
  import { createLazyDefaultWalletSecretProvider } from "../wallet/state/provider.js";
17
18
  import { anchorDomain, transferBitcoin, buyDomain, claimCogLock, clearDomainDelegate, clearDomainEndpoint, clearDomainMiner, clearField, createField, giveReputation, lockCogToDomain, registerDomain, reclaimCogLock, revokeReputation, sendCog, setField, setDomainCanonical, setDomainDelegate, setDomainEndpoint, setDomainMiner, sellDomain, transferDomain, } from "../wallet/tx/index.js";
18
19
  import { createTerminalPrompter } from "./prompt.js";
19
- export async function readPackageVersionFromDisk() {
20
- const packageUrls = [
21
- new URL("../../package.json", import.meta.url),
22
- new URL("../../../package.json", import.meta.url),
23
- ];
24
- for (const packageUrl of packageUrls) {
25
- try {
26
- const raw = await readFile(packageUrl, "utf8");
27
- const parsed = JSON.parse(raw);
28
- return parsed.version ?? "0.0.0";
29
- }
30
- catch (error) {
31
- const code = typeof error === "object" && error !== null && "code" in error
32
- ? String(error.code)
33
- : null;
34
- if (code !== "ENOENT") {
35
- throw error;
36
- }
37
- }
38
- }
39
- return "0.0.0";
40
- }
41
20
  async function runGlobalClientUpdateInstall(options) {
42
21
  const binary = process.platform === "win32" ? "npm.cmd" : "npm";
43
22
  await new Promise((resolve, reject) => {
@@ -0,0 +1 @@
1
+ export declare function readPackageVersionFromDisk(): Promise<string>;
@@ -0,0 +1,17 @@
1
+ import { readFile } from "node:fs/promises";
2
+ export async function readPackageVersionFromDisk() {
3
+ try {
4
+ const raw = await readFile(new URL("../package.json", import.meta.url), "utf8");
5
+ const parsed = JSON.parse(raw);
6
+ return parsed.version ?? "0.0.0";
7
+ }
8
+ catch (error) {
9
+ const code = typeof error === "object" && error !== null && "code" in error
10
+ ? String(error.code)
11
+ : null;
12
+ if (code === "ENOENT") {
13
+ return "0.0.0";
14
+ }
15
+ throw error;
16
+ }
17
+ }
@@ -105,7 +105,9 @@ function mapProviderState(provider, localState, existingRuntime) {
105
105
  const miningState = localState.state?.miningState === undefined
106
106
  ? null
107
107
  : normalizeMiningStateRecord(localState.state.miningState);
108
- if (existingRuntime?.currentPhase === "waiting-provider" && existingRuntime.providerState !== null) {
108
+ if (existingRuntime?.currentPhase === "waiting-provider"
109
+ && existingRuntime.providerState !== null
110
+ && (miningState === null || miningState.state === "idle")) {
109
111
  return existingRuntime.providerState;
110
112
  }
111
113
  if (miningState?.state === "paused" && miningState.pauseReason?.includes("rate-limit")) {
@@ -122,6 +124,11 @@ function mapProviderState(provider, localState, existingRuntime) {
122
124
  }
123
125
  return "unavailable";
124
126
  }
127
+ function shouldReuseExistingProviderWait(options) {
128
+ return options.existingRuntime?.currentPhase === "waiting-provider"
129
+ && options.existingRuntime.providerState !== null
130
+ && (options.miningState === null || options.miningState.state === "idle");
131
+ }
125
132
  function mapIndexerDaemonState(indexer) {
126
133
  if (indexer.health === "wallet-root-mismatch") {
127
134
  return "wallet-root-mismatch";
@@ -214,6 +221,10 @@ async function buildMiningRuntimeSnapshot(options) {
214
221
  const indexerDaemonState = mapIndexerDaemonState(options.indexer);
215
222
  const corePublishState = mapCorePublishState(options.nodeHealth, options.nodeStatus);
216
223
  const existing = options.existingRuntime;
224
+ const reuseExistingProviderWait = shouldReuseExistingProviderWait({
225
+ existingRuntime: existing,
226
+ miningState: state,
227
+ });
217
228
  return {
218
229
  schemaVersion: 1,
219
230
  walletRootId: options.localState.walletRootId,
@@ -278,12 +289,16 @@ async function buildMiningRuntimeSnapshot(options) {
278
289
  indexerHealth: options.indexer.health,
279
290
  tipsAligned: options.tipsAligned,
280
291
  lastEventAtUnixMs: options.lastEventAtUnixMs,
281
- lastError: existing?.lastError ?? options.provider.message ?? options.indexer.message ?? null,
292
+ lastError: reuseExistingProviderWait
293
+ ? existing?.lastError ?? null
294
+ : existing?.currentPhase === "waiting-bitcoin-network" || existing?.currentPhase === "waiting-indexer"
295
+ ? existing?.lastError ?? options.provider.message ?? options.indexer.message ?? null
296
+ : options.provider.message ?? options.indexer.message ?? null,
282
297
  note: state?.pauseReason === "zero-reward"
283
298
  ? "Mining is disabled because the target block reward is zero."
284
299
  : existing?.currentPhase === "resuming"
285
300
  ? "Mining discarded stale in-flight work after a large local runtime gap and is rechecking health."
286
- : existing?.currentPhase === "waiting-provider"
301
+ : reuseExistingProviderWait
287
302
  ? "Mining is waiting for the sentence provider to recover."
288
303
  : existing?.currentPhase === "waiting-indexer"
289
304
  ? "Mining is waiting for Bitcoin Core and the indexer to align."
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { assaySentences } from "@cogcoin/scoring";
2
3
  import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, stopManagedBitcoindService } from "../../bitcoind/service.js";
3
4
  import { createRpcClient } from "../../bitcoind/node.js";
4
5
  import type { ProgressOutputMode } from "../../bitcoind/types.js";
@@ -158,6 +159,18 @@ type MiningPublishOutcome = ({
158
159
  note?: null;
159
160
  candidate: MiningCandidate;
160
161
  } & Awaited<ReturnType<typeof publishCandidateOnce>>) | MiningPublishSkipResult | MiningPublishRetryResult;
162
+ interface CompetitivenessDecision {
163
+ allowed: boolean;
164
+ decision: string;
165
+ sameDomainCompetitorSuppressed: boolean;
166
+ higherRankedCompetitorDomainCount: number;
167
+ dedupedCompetitorDomainCount: number;
168
+ competitivenessGateIndeterminate: boolean;
169
+ mempoolSequenceCacheStatus: MiningRuntimeStatusV1["mempoolSequenceCacheStatus"];
170
+ lastMempoolSequence: string | null;
171
+ visibleBoardEntries: MiningSentenceBoardEntry[];
172
+ candidateRank: number | null;
173
+ }
161
174
  interface RunnerDependencies {
162
175
  openReadContext?: typeof openWalletReadContext;
163
176
  attachService?: typeof attachOrStartManagedBitcoindService;
@@ -171,6 +184,11 @@ interface RunnerDependencies {
171
184
  shutdownGraceMs?: number;
172
185
  sleepImpl?: typeof sleep;
173
186
  }
187
+ interface IndexerTruthKey {
188
+ walletRootId: string;
189
+ daemonInstanceId: string;
190
+ snapshotSeq: string;
191
+ }
174
192
  interface MiningLoopState {
175
193
  attemptedTipKey: string | null;
176
194
  currentTipKey: string | null;
@@ -178,6 +196,10 @@ interface MiningLoopState {
178
196
  selectedCandidate: MiningCandidate | null;
179
197
  ui: MiningFollowVisualizerState;
180
198
  waitingNote: string | null;
199
+ providerWaitState: "backoff" | "rate-limited" | "auth-error" | "not-found" | null;
200
+ providerWaitLastError: string | null;
201
+ providerWaitNextRetryAtUnixMs: number | null;
202
+ providerTransientFailureCount: number;
181
203
  bitcoinRecoveryFirstFailureAtUnixMs: number | null;
182
204
  bitcoinRecoveryFirstUnreachableAtUnixMs: number | null;
183
205
  bitcoinRecoveryLastRestartAttemptAtUnixMs: number | null;
@@ -186,6 +208,20 @@ interface MiningLoopState {
186
208
  reconnectSettledUntilUnixMs: number | null;
187
209
  tipSettledUntilUnixMs: number | null;
188
210
  }
211
+ interface MiningSuspendDetector {
212
+ lastHeartbeatMonotonicMs: number;
213
+ detectedAtUnixMs: number | null;
214
+ monotonicNow: () => number;
215
+ nowUnixMs: () => number;
216
+ stop(): void;
217
+ }
218
+ interface MiningSuspendHeartbeatHandle {
219
+ clear(): void;
220
+ }
221
+ interface MiningSuspendScheduler {
222
+ every(intervalMs: number, callback: () => void): MiningSuspendHeartbeatHandle;
223
+ }
224
+ type MiningCooperativeYield = () => Promise<void>;
189
225
  export interface RunForegroundMiningOptions extends RunnerDependencies {
190
226
  dataDir: string;
191
227
  databasePath: string;
@@ -243,7 +279,7 @@ export declare function resolveSettledBoardForTesting(options: {
243
279
  settledBoardEntries: MiningSentenceBoardEntry[];
244
280
  };
245
281
  export declare function getSelectedCandidateForTipForTesting(loopState: MiningLoopState, tipKey: string | null): MiningCandidate | null;
246
- export declare function cacheSelectedCandidateForTipForTesting(loopState: MiningLoopState, tipKey: string | null, candidate: MiningCandidate): void;
282
+ export declare function cacheSelectedCandidateForTipForTesting(loopState: MiningLoopState, tipKey: string | null, candidate: MiningCandidate, liveState?: MiningStateRecord | null): void;
247
283
  export declare function resolveFundingDisplaySatsForTesting(state: WalletStateV1, rpc: MiningRpcClient): Promise<bigint>;
248
284
  export declare function loadMiningVisibleFollowBlockTimesForTesting(options: {
249
285
  rpc: MiningRpcClient;
@@ -302,6 +338,12 @@ export declare function createMiningPlanForTesting(options: {
302
338
  feeRateSatVb: number;
303
339
  };
304
340
  export declare function validateMiningDraftForTesting(decoded: Awaited<ReturnType<MiningRpcClient["decodePsbt"]>>, funded: Awaited<ReturnType<MiningRpcClient["walletCreateFundedPsbt"]>>, plan: ReturnType<typeof createMiningPlan>): void;
341
+ declare function resolveEligibleAnchoredRoots(context: WalletReadContext): Array<{
342
+ domainId: number;
343
+ domainName: string;
344
+ localIndex: number;
345
+ sender: MutationSender;
346
+ }>;
305
347
  export declare function refreshMiningCandidateFromCurrentStateForTesting(context: ReadyMiningReadContext, candidate: MiningCandidate): MiningCandidate | null;
306
348
  export declare function resolveMiningConflictOutpointForTesting(options: {
307
349
  state: WalletStateV1;
@@ -320,6 +362,34 @@ export declare function buildMiningGenerationRequestForTesting(options: {
320
362
  domainExtraPrompts?: Record<string, string>;
321
363
  extraPrompt?: string | null;
322
364
  }): MiningSentenceGenerationRequest;
365
+ declare function generateCandidatesForDomains(options: {
366
+ rpc: MiningRpcClient;
367
+ readContext: WalletReadContext & {
368
+ localState: {
369
+ availability: "ready";
370
+ state: WalletStateV1;
371
+ };
372
+ snapshot: NonNullable<WalletReadContext["snapshot"]>;
373
+ model: NonNullable<WalletReadContext["model"]>;
374
+ };
375
+ domains: ReturnType<typeof resolveEligibleAnchoredRoots>;
376
+ provider: WalletSecretProvider;
377
+ paths: WalletRuntimePaths;
378
+ indexerTruthKey: IndexerTruthKey | null;
379
+ runId?: string | null;
380
+ fetchImpl?: typeof fetch;
381
+ }): Promise<MiningCandidate[]>;
382
+ declare function runCompetitivenessGate(options: {
383
+ rpc: MiningRpcClient;
384
+ readContext: WalletReadContext & {
385
+ snapshot: NonNullable<WalletReadContext["snapshot"]>;
386
+ };
387
+ candidate: MiningCandidate;
388
+ currentTxid: string | null;
389
+ assaySentencesImpl?: typeof assaySentences;
390
+ cooperativeYield?: MiningCooperativeYield;
391
+ cooperativeYieldEvery?: number;
392
+ }): Promise<CompetitivenessDecision>;
323
393
  declare function publishCandidateOnce(options: {
324
394
  readContext: WalletReadContext & {
325
395
  localState: {
@@ -388,9 +458,17 @@ declare function runMiningLoop(options: {
388
458
  stdout?: {
389
459
  write(chunk: string): void;
390
460
  };
461
+ loopState?: MiningLoopState;
391
462
  visualizer?: MiningFollowVisualizer;
392
463
  nowImpl?: () => number;
393
464
  sleepImpl?: typeof sleep;
465
+ suspendMonotonicNowImpl?: () => number;
466
+ suspendScheduler?: MiningSuspendScheduler;
467
+ generateCandidatesForDomainsImpl?: typeof generateCandidatesForDomains;
468
+ runCompetitivenessGateImpl?: typeof runCompetitivenessGate;
469
+ assaySentencesImpl?: typeof assaySentences;
470
+ cooperativeYieldImpl?: MiningCooperativeYield;
471
+ cooperativeYieldEvery?: number;
394
472
  }): Promise<void>;
395
473
  declare function waitForBackgroundHealthy(paths: WalletRuntimePaths): Promise<MiningRuntimeStatusV1 | null>;
396
474
  export declare function runForegroundMining(options: RunForegroundMiningOptions): Promise<void>;
@@ -445,6 +523,11 @@ export declare function performMiningCycleForTesting(options: {
445
523
  };
446
524
  loopState?: MiningLoopState;
447
525
  nowImpl?: () => number;
526
+ generateCandidatesForDomainsImpl?: typeof generateCandidatesForDomains;
527
+ runCompetitivenessGateImpl?: typeof runCompetitivenessGate;
528
+ assaySentencesImpl?: typeof assaySentences;
529
+ cooperativeYieldImpl?: MiningCooperativeYield;
530
+ cooperativeYieldEvery?: number;
448
531
  }): Promise<void>;
449
532
  export declare function runMiningLoopForTesting(options: {
450
533
  dataDir: string;
@@ -464,10 +547,42 @@ export declare function runMiningLoopForTesting(options: {
464
547
  stdout?: {
465
548
  write(chunk: string): void;
466
549
  };
550
+ loopState?: MiningLoopState;
467
551
  visualizer?: MiningFollowVisualizer;
468
552
  nowImpl?: () => number;
469
553
  sleepImpl?: typeof sleep;
554
+ suspendMonotonicNowImpl?: () => number;
555
+ suspendScheduler?: MiningSuspendScheduler;
556
+ generateCandidatesForDomainsImpl?: typeof generateCandidatesForDomains;
557
+ runCompetitivenessGateImpl?: typeof runCompetitivenessGate;
558
+ assaySentencesImpl?: typeof assaySentences;
559
+ cooperativeYieldImpl?: MiningCooperativeYield;
560
+ cooperativeYieldEvery?: number;
470
561
  }): Promise<void>;
562
+ export declare function runCompetitivenessGateForTesting(options: {
563
+ rpc: MiningRpcClient;
564
+ readContext: WalletReadContext & {
565
+ snapshot: NonNullable<WalletReadContext["snapshot"]>;
566
+ };
567
+ candidate: MiningCandidate;
568
+ currentTxid: string | null;
569
+ assaySentencesImpl?: typeof assaySentences;
570
+ cooperativeYieldImpl?: MiningCooperativeYield;
571
+ cooperativeYieldEvery?: number;
572
+ }): Promise<CompetitivenessDecision>;
573
+ export declare function createMiningSuspendDetectorForTesting(options?: {
574
+ monotonicNow?: () => number;
575
+ nowUnixMs?: () => number;
576
+ scheduler?: MiningSuspendScheduler;
577
+ }): MiningSuspendDetector;
578
+ export declare function throwIfMiningSuspendDetectedForTesting(detector: MiningSuspendDetector): void;
579
+ export declare function topologicallyOrderAncestorTxidsForTesting(options: {
580
+ txid: string;
581
+ txContexts: Map<string, {
582
+ txid: string;
583
+ rawTransaction: Awaited<ReturnType<MiningRpcClient["getRawTransaction"]>>;
584
+ }>;
585
+ }): string[] | null;
471
586
  export declare function buildPrePublishStatusOverridesForTesting(options: {
472
587
  state: WalletStateV1;
473
588
  candidate: MiningCandidate;