@cogcoin/client 1.1.0 → 1.1.1

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
+ }
@@ -1,5 +1,6 @@
1
1
  import { access, constants } from "node:fs/promises";
2
2
  import { deserializeIndexerState, loadBundledGenesisParameters } from "@cogcoin/indexer";
3
+ import { readPackageVersionFromDisk } from "../../package-version.js";
3
4
  import { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
4
5
  import { createRpcClient } from "../../bitcoind/node.js";
5
6
  import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
@@ -495,6 +496,9 @@ async function readFundingSpendableSats(options) {
495
496
  }
496
497
  }
497
498
  export async function openWalletReadContext(options) {
499
+ const expectedIndexerBinaryVersion = options.expectedIndexerBinaryVersion === undefined
500
+ ? await readPackageVersionFromDisk()
501
+ : options.expectedIndexerBinaryVersion;
498
502
  const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_SERVICE_START_TIMEOUT_MS;
499
503
  const now = options.now ?? Date.now();
500
504
  const localState = await inspectWalletLocalState({
@@ -545,7 +549,7 @@ export async function openWalletReadContext(options) {
545
549
  walletRootId,
546
550
  startupTimeoutMs,
547
551
  ensureBackgroundFollow: true,
548
- expectedBinaryVersion: options.expectedIndexerBinaryVersion,
552
+ expectedBinaryVersion: expectedIndexerBinaryVersion,
549
553
  });
550
554
  }
551
555
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
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",