@cogcoin/client 1.1.1 → 1.1.3

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.
@@ -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;