@cogcoin/client 1.1.10 → 1.1.12

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 (51) 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 +18 -1
  26. package/dist/bitcoind/managed-bitcoind-service-config.js +38 -1
  27. package/dist/bitcoind/managed-bitcoind-service-lifecycle.js +30 -24
  28. package/dist/bitcoind/managed-bitcoind-service-status.js +0 -8
  29. package/dist/bitcoind/rpc.d.ts +2 -1
  30. package/dist/bitcoind/rpc.js +3 -0
  31. package/dist/bitcoind/types.d.ts +1 -0
  32. package/dist/cli/mining-format.js +6 -1
  33. package/dist/cli/wallet-format/balance.js +1 -1
  34. package/dist/client/default-client.d.ts +3 -1
  35. package/dist/client/default-client.js +22 -0
  36. package/dist/types.d.ts +13 -1
  37. package/dist/wallet/fs/atomic.d.ts +11 -2
  38. package/dist/wallet/fs/atomic.js +45 -5
  39. package/dist/wallet/mining/competitiveness.d.ts +6 -0
  40. package/dist/wallet/mining/competitiveness.js +137 -74
  41. package/dist/wallet/mining/cycle.js +32 -4
  42. package/dist/wallet/mining/engine-types.d.ts +10 -0
  43. package/dist/wallet/mining/projection.d.ts +1 -0
  44. package/dist/wallet/mining/projection.js +15 -1
  45. package/dist/wallet/mining/visualizer-sync.js +7 -9
  46. package/dist/wallet/mining/visualizer.js +2 -1
  47. package/dist/wallet/read/context.js +3 -2
  48. package/dist/wallet/read/local-state.d.ts +8 -0
  49. package/dist/wallet/read/local-state.js +32 -6
  50. package/dist/wallet/read/types.d.ts +1 -0
  51. package/package.json +1 -1
@@ -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",
@@ -339,6 +339,9 @@ export async function runMiningPhaseMachine(options) {
339
339
  score: best.canonicalBlend.toString(),
340
340
  runId: options.backgroundWorkerRunId,
341
341
  }));
342
+ let lastScoringProgressProcessed = -1;
343
+ let lastScoringProgressSavedAtUnixMs = 0;
344
+ let scoringProgressWrite = Promise.resolve();
342
345
  const gate = await runGateImpl({
343
346
  rpc: options.rpc,
344
347
  readContext: options.readContext,
@@ -348,7 +351,32 @@ export async function runMiningPhaseMachine(options) {
348
351
  cooperativeYield: options.cooperativeYieldImpl,
349
352
  cooperativeYieldEvery: options.cooperativeYieldEvery,
350
353
  throwIfStopping: options.throwIfStopping,
354
+ onWarmupProgress: async (progress) => {
355
+ if (progress.total <= 0) {
356
+ return;
357
+ }
358
+ const nowUnixMs = now();
359
+ if (progress.processed === lastScoringProgressProcessed
360
+ || (progress.processed !== 0
361
+ && progress.processed !== progress.total
362
+ && (nowUnixMs - lastScoringProgressSavedAtUnixMs) < 500)) {
363
+ return;
364
+ }
365
+ lastScoringProgressProcessed = progress.processed;
366
+ lastScoringProgressSavedAtUnixMs = nowUnixMs;
367
+ scoringProgressWrite = scoringProgressWrite.then(async () => {
368
+ await options.saveCycleStatus(options.readContext, {
369
+ runMode: options.runMode,
370
+ currentPhase: "scoring",
371
+ currentPublishDecision: null,
372
+ lastError: null,
373
+ note: `Scoring mining candidates for the current tip (mempool ${progress.processed}/${progress.total}).`,
374
+ });
375
+ });
376
+ await scoringProgressWrite;
377
+ },
351
378
  });
379
+ await scoringProgressWrite;
352
380
  throwIfInterrupted();
353
381
  state.gateSnapshot = {
354
382
  higherRankedCompetitorDomainCount: gate.higherRankedCompetitorDomainCount,
@@ -28,6 +28,16 @@ export type MiningRpcClient = WalletMutationRpcClient & {
28
28
  txids: string[];
29
29
  mempool_sequence: string | number;
30
30
  }>;
31
+ getRawMempoolEntries(): Promise<Record<string, {
32
+ vsize: number;
33
+ fees: {
34
+ base: number;
35
+ ancestor: number;
36
+ descendant: number;
37
+ };
38
+ ancestorsize?: number;
39
+ descendantsize?: number;
40
+ }>>;
31
41
  getMempoolEntry(txid: string): Promise<{
32
42
  vsize: number;
33
43
  fees: {
@@ -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"
@@ -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;
@@ -282,7 +283,7 @@ export function describeMiningVisualizerProgress(snapshot) {
282
283
  case "resuming":
283
284
  return "Mining discarded stale in-flight work after a large local runtime gap and is rechecking health.";
284
285
  case "waiting-provider":
285
- return "Mining is waiting for the sentence provider to recover.";
286
+ return resolveWaitingProviderNote(snapshot.providerState);
286
287
  case "waiting-indexer":
287
288
  return "Mining is waiting for Bitcoin Core and the indexer to align.";
288
289
  case "waiting-bitcoin-network":
@@ -5,7 +5,7 @@ import { inspectMiningControlPlane } from "../mining/index.js";
5
5
  import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
6
6
  import { createDefaultWalletSecretProvider, } from "../state/provider.js";
7
7
  import { openManagedWalletReadServiceBundle } from "./managed-services.js";
8
- import { inspectWalletLocalState, readFundingSpendableSats } from "./local-state.js";
8
+ import { inspectWalletLocalState, readFundingBalanceSummary } from "./local-state.js";
9
9
  import { createWalletReadModel } from "./project.js";
10
10
  const DEFAULT_SERVICE_START_TIMEOUT_MS = 60_000;
11
11
  export async function openWalletReadContext(options) {
@@ -31,7 +31,7 @@ export async function openWalletReadContext(options) {
31
31
  expectedIndexerBinaryVersion,
32
32
  now,
33
33
  });
34
- const fundingSpendableSats = await readFundingSpendableSats({
34
+ const { fundingDisplaySats, fundingSpendableSats, } = await readFundingBalanceSummary({
35
35
  state: localState.state,
36
36
  rpc: managedServices.node.rpc,
37
37
  });
@@ -58,6 +58,7 @@ export async function openWalletReadContext(options) {
58
58
  model: localState.state === null
59
59
  ? null
60
60
  : createWalletReadModel(localState.state, managedServices.snapshot),
61
+ fundingDisplaySats,
61
62
  fundingSpendableSats,
62
63
  mining,
63
64
  async close() {
@@ -7,6 +7,10 @@ type WalletLocalStateDeps = {
7
7
  attachOrStartManagedBitcoindService: typeof attachOrStartManagedBitcoindService;
8
8
  createRpcClient: typeof createRpcClient;
9
9
  };
10
+ export interface FundingBalanceSummary {
11
+ fundingDisplaySats: bigint | null;
12
+ fundingSpendableSats: bigint | null;
13
+ }
10
14
  export declare function inspectWalletLocalStateWithDependencies(options?: {
11
15
  dataDir?: string;
12
16
  secretProvider?: WalletSecretProvider;
@@ -25,4 +29,8 @@ export declare function readFundingSpendableSats(options: {
25
29
  state: WalletLocalStateStatus["state"];
26
30
  rpc: ReturnType<typeof createRpcClient> | null;
27
31
  }): Promise<bigint | null>;
32
+ export declare function readFundingBalanceSummary(options: {
33
+ state: WalletLocalStateStatus["state"];
34
+ rpc: Pick<ReturnType<typeof createRpcClient>, "listUnspent"> | null;
35
+ }): Promise<FundingBalanceSummary>;
28
36
  export {};
@@ -22,6 +22,10 @@ function isSpendableFundingUtxo(entry, fundingScriptPubKeyHex) {
22
22
  && entry.spendable !== false
23
23
  && entry.safe !== false;
24
24
  }
25
+ function isDisplayFundingUtxo(entry, fundingScriptPubKeyHex) {
26
+ return entry.scriptPubKey === fundingScriptPubKeyHex
27
+ && entry.spendable !== false;
28
+ }
25
29
  async function pathExists(path) {
26
30
  try {
27
31
  await access(path, constants.F_OK);
@@ -217,17 +221,39 @@ export async function inspectWalletLocalState(options = {}) {
217
221
  return inspectWalletLocalStateWithDependencies(options);
218
222
  }
219
223
  export async function readFundingSpendableSats(options) {
224
+ return (await readFundingBalanceSummary(options)).fundingSpendableSats;
225
+ }
226
+ export async function readFundingBalanceSummary(options) {
220
227
  if (options.state === null || options.rpc === null) {
221
- return null;
228
+ return {
229
+ fundingDisplaySats: null,
230
+ fundingSpendableSats: null,
231
+ };
222
232
  }
223
233
  const state = options.state;
224
234
  try {
225
- const utxos = await options.rpc.listUnspent(state.managedCoreWallet.walletName, 1);
226
- return utxos.reduce((sum, entry) => isSpendableFundingUtxo(entry, state.funding.scriptPubKeyHex)
227
- ? sum + btcAmountToSats(entry.amount)
228
- : sum, 0n);
235
+ const utxos = await options.rpc.listUnspent(state.managedCoreWallet.walletName, 0);
236
+ let fundingDisplaySats = 0n;
237
+ let fundingSpendableSats = 0n;
238
+ for (const entry of utxos) {
239
+ if (!isDisplayFundingUtxo(entry, state.funding.scriptPubKeyHex)) {
240
+ continue;
241
+ }
242
+ const amountSats = btcAmountToSats(entry.amount);
243
+ fundingDisplaySats += amountSats;
244
+ if (isSpendableFundingUtxo(entry, state.funding.scriptPubKeyHex)) {
245
+ fundingSpendableSats += amountSats;
246
+ }
247
+ }
248
+ return {
249
+ fundingDisplaySats,
250
+ fundingSpendableSats,
251
+ };
229
252
  }
230
253
  catch {
231
- return null;
254
+ return {
255
+ fundingDisplaySats: null,
256
+ fundingSpendableSats: null,
257
+ };
232
258
  }
233
259
  }
@@ -94,6 +94,7 @@ export interface WalletReadContext {
94
94
  indexer: WalletIndexerStatus;
95
95
  snapshot: WalletSnapshotView | null;
96
96
  model: WalletReadModel | null;
97
+ fundingDisplaySats: bigint | null;
97
98
  fundingSpendableSats: bigint | null;
98
99
  mining?: MiningControlPlaneView;
99
100
  close(): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "1.1.10",
3
+ "version": "1.1.12",
4
4
  "description": "Store-backed Cogcoin client with wallet flows, SQLite persistence, and managed Bitcoin Core integration.",
5
5
  "license": "MIT",
6
6
  "type": "module",