@cogcoin/client 1.1.5 → 1.1.6

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 (34) hide show
  1. package/README.md +1 -1
  2. package/dist/bitcoind/indexer-daemon.d.ts +3 -7
  3. package/dist/bitcoind/indexer-daemon.js +43 -158
  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/indexer-policy.d.ts +34 -0
  7. package/dist/bitcoind/managed-runtime/indexer-policy.js +200 -0
  8. package/dist/bitcoind/managed-runtime/status.d.ts +11 -0
  9. package/dist/bitcoind/managed-runtime/status.js +59 -0
  10. package/dist/bitcoind/managed-runtime/types.d.ts +37 -0
  11. package/dist/bitcoind/managed-runtime/types.js +1 -0
  12. package/dist/bitcoind/service.d.ts +2 -7
  13. package/dist/bitcoind/service.js +46 -94
  14. package/dist/wallet/lifecycle/access.d.ts +5 -0
  15. package/dist/wallet/lifecycle/access.js +79 -0
  16. package/dist/wallet/lifecycle/context.d.ts +26 -0
  17. package/dist/wallet/lifecycle/context.js +58 -0
  18. package/dist/wallet/lifecycle/managed-core.d.ts +1 -9
  19. package/dist/wallet/lifecycle/managed-core.js +3 -63
  20. package/dist/wallet/lifecycle/repair-bitcoind.d.ts +10 -0
  21. package/dist/wallet/lifecycle/repair-bitcoind.js +142 -0
  22. package/dist/wallet/lifecycle/repair-indexer.d.ts +8 -0
  23. package/dist/wallet/lifecycle/repair-indexer.js +117 -0
  24. package/dist/wallet/lifecycle/repair.d.ts +2 -4
  25. package/dist/wallet/lifecycle/repair.js +77 -318
  26. package/dist/wallet/lifecycle/setup-prompts.d.ts +7 -0
  27. package/dist/wallet/lifecycle/setup-prompts.js +88 -0
  28. package/dist/wallet/lifecycle/setup-state.d.ts +26 -0
  29. package/dist/wallet/lifecycle/setup-state.js +159 -0
  30. package/dist/wallet/lifecycle/setup.d.ts +3 -4
  31. package/dist/wallet/lifecycle/setup.js +45 -351
  32. package/dist/wallet/lifecycle/types.d.ts +33 -2
  33. package/dist/wallet/read/context.js +13 -188
  34. package/package.json +1 -1
@@ -107,7 +107,18 @@ export interface WalletLifecycleResolvedContext {
107
107
  paths: WalletRuntimePaths;
108
108
  nowUnixMs: number;
109
109
  }
110
- export interface WalletSetupContext extends WalletLifecycleResolvedContext, WalletSetupDependencies {
110
+ export interface WalletManagedCoreContext extends WalletLifecycleResolvedContext {
111
+ attachService: NonNullable<WalletManagedCoreDependencies["attachService"]>;
112
+ rpcFactory: NonNullable<WalletManagedCoreDependencies["rpcFactory"]>;
113
+ }
114
+ export interface WalletAccessContext extends WalletManagedCoreContext {
115
+ dataDir?: string;
116
+ }
117
+ export interface WalletLoadedState {
118
+ state: WalletStateV1;
119
+ source: "primary" | "backup";
120
+ }
121
+ export interface WalletSetupContext extends WalletManagedCoreContext {
111
122
  dataDir: string;
112
123
  prompter: WalletPrompter;
113
124
  }
@@ -118,8 +129,28 @@ export interface WalletRepairDependencies extends WalletManagedCoreDependencies
118
129
  requestMiningPreemption?: typeof requestMiningGenerationPreemption;
119
130
  startBackgroundMining?: typeof startBackgroundMining;
120
131
  }
121
- export interface WalletRepairContext extends WalletLifecycleResolvedContext, WalletRepairDependencies {
132
+ export interface WalletRepairContext extends WalletManagedCoreContext {
122
133
  dataDir: string;
123
134
  databasePath: string;
124
135
  assumeYes: boolean;
136
+ probeBitcoindService: NonNullable<WalletRepairDependencies["probeBitcoindService"]>;
137
+ attachIndexerDaemon: NonNullable<WalletRepairDependencies["attachIndexerDaemon"]>;
138
+ probeIndexerDaemon: NonNullable<WalletRepairDependencies["probeIndexerDaemon"]>;
139
+ requestMiningPreemption?: WalletRepairDependencies["requestMiningPreemption"];
140
+ startBackgroundMining?: WalletRepairDependencies["startBackgroundMining"];
141
+ }
142
+ export interface WalletBitcoindRepairStageResult {
143
+ state: WalletStateV1;
144
+ repairStateNeedsPersist: boolean;
145
+ recreatedManagedCoreWallet: boolean;
146
+ bitcoindServiceAction: WalletRepairResult["bitcoindServiceAction"];
147
+ bitcoindCompatibilityIssue: WalletRepairResult["bitcoindCompatibilityIssue"];
148
+ managedCoreReplicaAction: WalletRepairResult["managedCoreReplicaAction"];
149
+ bitcoindPostRepairHealth: WalletRepairResult["bitcoindPostRepairHealth"];
150
+ }
151
+ export interface WalletIndexerRepairStageResult {
152
+ resetIndexerDatabase: boolean;
153
+ indexerDaemonAction: WalletRepairResult["indexerDaemonAction"];
154
+ indexerCompatibilityIssue: WalletRepairResult["indexerCompatibilityIssue"];
155
+ indexerPostRepairHealth: WalletRepairResult["indexerPostRepairHealth"];
125
156
  }
@@ -2,6 +2,8 @@ import { access, constants } from "node:fs/promises";
2
2
  import { deserializeIndexerState, loadBundledGenesisParameters } from "@cogcoin/indexer";
3
3
  import { readPackageVersionFromDisk } from "../../package-version.js";
4
4
  import { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
5
+ import { deriveManagedBitcoindWalletStatus, resolveManagedBitcoindProbeDecision, } from "../../bitcoind/managed-runtime/bitcoind-policy.js";
6
+ import { deriveManagedIndexerWalletStatus, resolveIndexerDaemonProbeDecision, } from "../../bitcoind/managed-runtime/indexer-policy.js";
5
7
  import { createRpcClient } from "../../bitcoind/node.js";
6
8
  import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
7
9
  import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
@@ -19,7 +21,6 @@ import { createDefaultWalletSecretProvider, createWalletSecretReference, inspect
19
21
  import { describeClientPasswordLockedMessage, describeClientPasswordMigrationMessage, describeClientPasswordSetupMessage, } from "../state/client-password.js";
20
22
  import { createWalletReadModel } from "./project.js";
21
23
  const DEFAULT_SERVICE_START_TIMEOUT_MS = 10_000;
22
- const STALE_HEARTBEAT_THRESHOLD_MS = 15_000;
23
24
  const TOLERATED_NODE_HEADER_LEAD_BLOCKS = 2;
24
25
  const TOLERATED_NODE_HEADER_LEAD_MESSAGE = "Bitcoin headers can briefly lead validated blocks; a short 1-2 block lead is normal and is being tolerated.";
25
26
  const NODE_CATCHING_UP_MESSAGE = "Bitcoin Core is still catching up to headers.";
@@ -222,135 +223,6 @@ async function inspectWalletLocalState(options = {}) {
222
223
  };
223
224
  }
224
225
  }
225
- function mapIndexerStartupError(message) {
226
- switch (message) {
227
- case "indexer_daemon_start_timeout":
228
- return {
229
- health: "starting",
230
- message: "Indexer daemon is still starting.",
231
- };
232
- case "indexer_daemon_service_version_mismatch":
233
- return {
234
- health: "service-version-mismatch",
235
- message: "The live indexer daemon is running an incompatible service API version.",
236
- };
237
- case "indexer_daemon_schema_mismatch":
238
- return {
239
- health: "schema-mismatch",
240
- message: "The live indexer daemon is using an incompatible sqlite schema.",
241
- };
242
- case "indexer_daemon_wallet_root_mismatch":
243
- return {
244
- health: "wallet-root-mismatch",
245
- message: "The live indexer daemon belongs to a different wallet root.",
246
- };
247
- case "indexer_daemon_protocol_error":
248
- return {
249
- health: "unavailable",
250
- message: "The live indexer daemon socket responded with an invalid or incomplete protocol exchange.",
251
- };
252
- case INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED:
253
- return {
254
- health: "failed",
255
- message: "The managed indexer daemon could not recover automatic background follow.",
256
- };
257
- default:
258
- return {
259
- health: "unavailable",
260
- message,
261
- };
262
- }
263
- }
264
- function mapBitcoindStartupError(message) {
265
- switch (message) {
266
- case "managed_bitcoind_service_start_timeout":
267
- return {
268
- health: "starting",
269
- status: null,
270
- message: "Managed bitcoind service is still starting.",
271
- };
272
- case "managed_bitcoind_service_version_mismatch":
273
- return {
274
- health: "service-version-mismatch",
275
- status: null,
276
- message: "The live managed bitcoind service is running an incompatible service version.",
277
- };
278
- case "managed_bitcoind_wallet_root_mismatch":
279
- return {
280
- health: "wallet-root-mismatch",
281
- status: null,
282
- message: "The live managed bitcoind service belongs to a different wallet root.",
283
- };
284
- case "managed_bitcoind_runtime_mismatch":
285
- return {
286
- health: "runtime-mismatch",
287
- status: null,
288
- message: "The live managed bitcoind service runtime does not match this wallet's expected data directory or chain.",
289
- };
290
- case "managed_bitcoind_protocol_error":
291
- return {
292
- health: "unavailable",
293
- status: null,
294
- message: "The managed bitcoind runtime artifacts are invalid or incomplete.",
295
- };
296
- default:
297
- return {
298
- health: "unavailable",
299
- status: null,
300
- message,
301
- };
302
- }
303
- }
304
- function deriveBitcoindHealth(options) {
305
- if (options.startupError !== null) {
306
- const mapped = mapBitcoindStartupError(options.startupError);
307
- return {
308
- ...mapped,
309
- status: options.status,
310
- };
311
- }
312
- if (options.status === null) {
313
- return {
314
- health: "unavailable",
315
- status: null,
316
- message: "Managed bitcoind service is unavailable.",
317
- };
318
- }
319
- if (options.status.state === "starting") {
320
- return {
321
- health: "starting",
322
- status: options.status,
323
- message: options.status.lastError ?? "Managed bitcoind service is still starting.",
324
- };
325
- }
326
- if (options.status.state === "failed") {
327
- return {
328
- health: "failed",
329
- status: options.status,
330
- message: options.status.lastError ?? "Managed bitcoind service refresh failed.",
331
- };
332
- }
333
- const proofStatus = options.nodeStatus?.walletReplica?.proofStatus;
334
- if (proofStatus === "missing") {
335
- return {
336
- health: "replica-missing",
337
- status: options.status,
338
- message: options.nodeStatus?.walletReplicaMessage ?? "Managed Core wallet replica is missing.",
339
- };
340
- }
341
- if (proofStatus === "mismatch") {
342
- return {
343
- health: "replica-mismatch",
344
- status: options.status,
345
- message: options.nodeStatus?.walletReplicaMessage ?? "Managed Core wallet replica does not match trusted wallet state.",
346
- };
347
- }
348
- return {
349
- health: "ready",
350
- status: options.status,
351
- message: options.nodeStatus?.walletReplicaMessage ?? options.status.lastError,
352
- };
353
- }
354
226
  function deriveNodeHealth(status, bitcoindHealth) {
355
227
  if (bitcoindHealth !== "ready" || status === null || !status.ready) {
356
228
  return {
@@ -381,58 +253,6 @@ function deriveNodeHealth(status, bitcoindHealth) {
381
253
  export function deriveNodeHealthForTesting(status, bitcoindHealth) {
382
254
  return deriveNodeHealth(status, bitcoindHealth);
383
255
  }
384
- function deriveIndexerHealth(options) {
385
- const daemonStatus = options.source === "lease"
386
- ? options.daemonStatus
387
- : options.observedStatus ?? options.daemonStatus;
388
- const snapshotTip = options.snapshot?.tip ?? null;
389
- const daemonInstanceId = options.snapshot?.daemonInstanceId ?? daemonStatus?.daemonInstanceId ?? null;
390
- const snapshotSeq = options.snapshot?.snapshotSeq ?? daemonStatus?.snapshotSeq ?? null;
391
- const openedAtUnixMs = options.snapshot?.openedAtUnixMs ?? null;
392
- const source = daemonStatus === null && options.snapshot === null ? "none" : options.source;
393
- const createResult = (health, message) => ({
394
- health,
395
- status: daemonStatus,
396
- message,
397
- snapshotTip,
398
- source,
399
- daemonInstanceId,
400
- snapshotSeq,
401
- openedAtUnixMs,
402
- });
403
- if (options.startupError !== null) {
404
- const mapped = mapIndexerStartupError(options.startupError);
405
- return createResult(mapped.health, mapped.message);
406
- }
407
- if (daemonStatus === null) {
408
- return createResult("unavailable", "Indexer daemon is unavailable.");
409
- }
410
- if ((options.now - daemonStatus.heartbeatAtUnixMs) > STALE_HEARTBEAT_THRESHOLD_MS) {
411
- return createResult("stale-heartbeat", "Indexer daemon heartbeat is stale.");
412
- }
413
- if (daemonStatus.state === "schema-mismatch") {
414
- return createResult("schema-mismatch", daemonStatus.lastError ?? "Indexer daemon sqlite schema is incompatible.");
415
- }
416
- if (daemonStatus.state === "failed") {
417
- return createResult("failed", daemonStatus.lastError ?? "Indexer daemon refresh failed.");
418
- }
419
- if (daemonStatus.state === "service-version-mismatch") {
420
- return createResult("service-version-mismatch", "Indexer daemon service API is incompatible.");
421
- }
422
- if (options.snapshot === null) {
423
- if (daemonStatus.state === "reorging") {
424
- return createResult("reorging", "Indexer daemon is replaying a reorg and refreshing the coherent snapshot.");
425
- }
426
- return createResult(daemonStatus.state === "catching-up" ? "catching-up" : "starting", "Indexer snapshot is not ready yet.");
427
- }
428
- if (daemonStatus.state === "catching-up") {
429
- return createResult("catching-up", "Indexer daemon is still catching up to the managed Bitcoin tip.");
430
- }
431
- if (daemonStatus.state === "reorging") {
432
- return createResult("reorging", "Indexer daemon is replaying a reorg and refreshing the coherent snapshot.");
433
- }
434
- return createResult("synced", null);
435
- }
436
256
  async function attachNodeStatus(options) {
437
257
  try {
438
258
  const probe = await probeManagedBitcoindService({
@@ -442,13 +262,14 @@ async function attachNodeStatus(options) {
442
262
  walletRootId: options.walletRootId,
443
263
  startupTimeoutMs: options.startupTimeoutMs,
444
264
  });
445
- if (probe.compatibility !== "compatible" && probe.compatibility !== "unreachable") {
265
+ const decision = resolveManagedBitcoindProbeDecision(probe);
266
+ if (decision.action === "reject") {
446
267
  return {
447
268
  handle: null,
448
269
  rpc: null,
449
270
  status: null,
450
271
  observedStatus: probe.status,
451
- error: probe.error,
272
+ error: decision.error,
452
273
  };
453
274
  }
454
275
  const genesis = await loadBundledGenesisParameters();
@@ -539,7 +360,7 @@ export async function openWalletReadContext(options) {
539
360
  walletReplicaMessage: verifiedReplica.message ?? null,
540
361
  };
541
362
  }
542
- const bitcoind = deriveBitcoindHealth({
363
+ const bitcoind = deriveManagedBitcoindWalletStatus({
543
364
  status: node.observedStatus,
544
365
  nodeStatus: node.status,
545
366
  startupError: node.error,
@@ -556,7 +377,11 @@ export async function openWalletReadContext(options) {
556
377
  dataDir: options.dataDir,
557
378
  walletRootId,
558
379
  });
559
- if (probe.compatibility === "compatible" || probe.compatibility === "unreachable") {
380
+ const probeDecision = resolveIndexerDaemonProbeDecision({
381
+ probe,
382
+ expectedBinaryVersion: expectedIndexerBinaryVersion,
383
+ });
384
+ if (probeDecision.action !== "reject") {
560
385
  await probe.client?.close().catch(() => undefined);
561
386
  daemonClient = await attachOrStartIndexerDaemon({
562
387
  dataDir: options.dataDir,
@@ -570,7 +395,7 @@ export async function openWalletReadContext(options) {
570
395
  else {
571
396
  observedDaemonStatus = probe.status;
572
397
  indexerSource = probe.status === null ? "none" : "probe";
573
- daemonError = probe.error;
398
+ daemonError = probeDecision.error;
574
399
  }
575
400
  if (daemonClient !== null) {
576
401
  const lease = await readSnapshotWithRetry(daemonClient, walletRootId);
@@ -604,7 +429,7 @@ export async function openWalletReadContext(options) {
604
429
  }
605
430
  }
606
431
  }
607
- const indexer = deriveIndexerHealth({
432
+ const indexer = deriveManagedIndexerWalletStatus({
608
433
  daemonStatus,
609
434
  observedStatus: observedDaemonStatus,
610
435
  snapshot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
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",