@cogcoin/client 1.1.4 → 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.
- package/README.md +4 -5
- package/dist/bitcoind/indexer-daemon.d.ts +3 -7
- package/dist/bitcoind/indexer-daemon.js +43 -158
- package/dist/bitcoind/managed-runtime/bitcoind-policy.d.ts +16 -0
- package/dist/bitcoind/managed-runtime/bitcoind-policy.js +177 -0
- package/dist/bitcoind/managed-runtime/indexer-policy.d.ts +34 -0
- package/dist/bitcoind/managed-runtime/indexer-policy.js +200 -0
- package/dist/bitcoind/managed-runtime/status.d.ts +11 -0
- package/dist/bitcoind/managed-runtime/status.js +59 -0
- package/dist/bitcoind/managed-runtime/types.d.ts +37 -0
- package/dist/bitcoind/managed-runtime/types.js +1 -0
- package/dist/bitcoind/progress/tty-renderer.js +3 -2
- package/dist/bitcoind/service.d.ts +2 -7
- package/dist/bitcoind/service.js +46 -94
- package/dist/cli/command-registry.d.ts +39 -0
- package/dist/cli/command-registry.js +1132 -0
- package/dist/cli/commands/client-admin.js +6 -56
- package/dist/cli/commands/mining-admin.js +9 -32
- package/dist/cli/commands/mining-read.js +15 -56
- package/dist/cli/commands/mining-runtime.js +258 -57
- package/dist/cli/commands/service-runtime.js +1 -64
- package/dist/cli/commands/status.js +2 -15
- package/dist/cli/commands/update.js +6 -21
- package/dist/cli/commands/wallet-admin.js +18 -120
- package/dist/cli/commands/wallet-mutation.js +4 -7
- package/dist/cli/commands/wallet-read.js +31 -138
- package/dist/cli/context.js +2 -4
- package/dist/cli/mining-format.js +8 -2
- package/dist/cli/mutation-command-groups.d.ts +11 -11
- package/dist/cli/mutation-command-groups.js +9 -18
- package/dist/cli/mutation-json.d.ts +1 -17
- package/dist/cli/mutation-json.js +1 -28
- package/dist/cli/mutation-success.d.ts +0 -1
- package/dist/cli/mutation-success.js +0 -19
- package/dist/cli/output.d.ts +1 -10
- package/dist/cli/output.js +52 -481
- package/dist/cli/parse.d.ts +1 -1
- package/dist/cli/parse.js +38 -695
- package/dist/cli/runner.js +28 -113
- package/dist/cli/types.d.ts +7 -8
- package/dist/cli/update-notifier.js +1 -1
- package/dist/cli/wallet-format.js +1 -1
- package/dist/wallet/lifecycle/access.d.ts +5 -0
- package/dist/wallet/lifecycle/access.js +79 -0
- package/dist/wallet/lifecycle/context.d.ts +26 -0
- package/dist/wallet/lifecycle/context.js +58 -0
- package/dist/wallet/lifecycle/managed-core.d.ts +15 -0
- package/dist/wallet/lifecycle/managed-core.js +197 -0
- package/dist/wallet/lifecycle/repair-bitcoind.d.ts +10 -0
- package/dist/wallet/lifecycle/repair-bitcoind.js +142 -0
- package/dist/wallet/lifecycle/repair-indexer.d.ts +8 -0
- package/dist/wallet/lifecycle/repair-indexer.js +117 -0
- package/dist/wallet/lifecycle/repair-mining.d.ts +49 -0
- package/dist/wallet/lifecycle/repair-mining.js +304 -0
- package/dist/wallet/lifecycle/repair-runtime.d.ts +36 -0
- package/dist/wallet/lifecycle/repair-runtime.js +206 -0
- package/dist/wallet/lifecycle/repair.d.ts +9 -0
- package/dist/wallet/lifecycle/repair.js +127 -0
- package/dist/wallet/lifecycle/setup-prompts.d.ts +7 -0
- package/dist/wallet/lifecycle/setup-prompts.js +88 -0
- package/dist/wallet/lifecycle/setup-state.d.ts +26 -0
- package/dist/wallet/lifecycle/setup-state.js +159 -0
- package/dist/wallet/lifecycle/setup.d.ts +15 -0
- package/dist/wallet/lifecycle/setup.js +124 -0
- package/dist/wallet/lifecycle/types.d.ts +156 -0
- package/dist/wallet/lifecycle/types.js +1 -0
- package/dist/wallet/lifecycle.d.ts +4 -165
- package/dist/wallet/lifecycle.js +3 -1656
- package/dist/wallet/mining/candidate.d.ts +60 -0
- package/dist/wallet/mining/candidate.js +290 -0
- package/dist/wallet/mining/competitiveness.d.ts +22 -0
- package/dist/wallet/mining/competitiveness.js +640 -0
- package/dist/wallet/mining/control.js +7 -251
- package/dist/wallet/mining/cycle.d.ts +39 -0
- package/dist/wallet/mining/cycle.js +542 -0
- package/dist/wallet/mining/engine-state.d.ts +66 -0
- package/dist/wallet/mining/engine-state.js +211 -0
- package/dist/wallet/mining/engine-types.d.ts +173 -0
- package/dist/wallet/mining/engine-types.js +1 -0
- package/dist/wallet/mining/engine-utils.d.ts +7 -0
- package/dist/wallet/mining/engine-utils.js +75 -0
- package/dist/wallet/mining/events.d.ts +2 -0
- package/dist/wallet/mining/events.js +19 -0
- package/dist/wallet/mining/lifecycle.d.ts +71 -0
- package/dist/wallet/mining/lifecycle.js +355 -0
- package/dist/wallet/mining/projection.d.ts +61 -0
- package/dist/wallet/mining/projection.js +319 -0
- package/dist/wallet/mining/publish.d.ts +79 -0
- package/dist/wallet/mining/publish.js +614 -0
- package/dist/wallet/mining/runner.d.ts +12 -418
- package/dist/wallet/mining/runner.js +274 -3433
- package/dist/wallet/mining/supervisor.d.ts +134 -0
- package/dist/wallet/mining/supervisor.js +558 -0
- package/dist/wallet/mining/visualizer-sync.d.ts +42 -0
- package/dist/wallet/mining/visualizer-sync.js +166 -0
- package/dist/wallet/mining/visualizer.d.ts +1 -0
- package/dist/wallet/mining/visualizer.js +33 -18
- package/dist/wallet/read/context.js +13 -188
- package/dist/wallet/reset.d.ts +1 -1
- package/dist/wallet/reset.js +35 -11
- package/dist/wallet/runtime.d.ts +0 -6
- package/dist/wallet/runtime.js +2 -38
- package/dist/wallet/tx/common.d.ts +18 -0
- package/dist/wallet/tx/common.js +40 -26
- package/package.json +1 -1
- package/dist/wallet/state/seed-index.d.ts +0 -43
- package/dist/wallet/state/seed-index.js +0 -151
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@cogcoin/client`
|
|
2
2
|
|
|
3
|
-
`@cogcoin/client@1.1.
|
|
3
|
+
`@cogcoin/client@1.1.6` is the reference Cogcoin client package for applications that want a local wallet, durable SQLite-backed state, and a managed Bitcoin Core integration around `@cogcoin/indexer`. It publishes the reusable client APIs, the SQLite adapter, the managed `bitcoind` integration, and the first-party `cogcoin` CLI in one package.
|
|
4
4
|
|
|
5
5
|
Use Node 22 or newer.
|
|
6
6
|
|
|
@@ -131,21 +131,20 @@ Managed node subpath:
|
|
|
131
131
|
The installed `cogcoin` command covers the first-party local wallet and node workflow:
|
|
132
132
|
|
|
133
133
|
- update commands such as `update` to compare the current CLI version with the latest npm release and install it
|
|
134
|
-
- wallet lifecycle commands such as `init`, `
|
|
134
|
+
- wallet lifecycle commands such as `init`, `reset`, `wallet show-mnemonic`, and `repair`
|
|
135
135
|
- sync and service commands such as `status`, `sync`, `follow`, `bitcoin start`, `bitcoin stop`, `bitcoin status`, `indexer start`, `indexer stop`, and `indexer status`
|
|
136
136
|
- domain and field commands such as `register`, `anchor`, `show`, `domains`, `fields`, `buy`, `sell`, and `transfer`
|
|
137
137
|
- COG and reputation commands such as `send`, `cog lock`, `claim`, `reclaim`, `rep give`, and `rep revoke`
|
|
138
138
|
- mining commands such as `mine`, `mine start`, `mine stop`, `mine status`, `mine log`, `mine setup`, `mine prompt`, and `mine prompt list`
|
|
139
139
|
|
|
140
|
-
The CLI also supports stable `--output json` and `--output preview-json` envelopes on the commands that advertise machine-readable output.
|
|
141
140
|
Use `cogcoin mine prompt <domain>` to set or clear a per-domain mining prompt override for one anchored root domain, and `cogcoin mine prompt list` to inspect the current per-domain prompt state alongside the global fallback prompt.
|
|
142
141
|
Interactive text invocations periodically check the npm registry for newer `@cogcoin/client` releases and print `npm install -g @cogcoin/client` when a newer version is available.
|
|
143
142
|
Set `COGCOIN_DISABLE_UPDATE_CHECK=1` to disable the CLI update notice entirely.
|
|
144
143
|
Ordinary `sync`, `follow`, and wallet-aware read/status flows detach from the managed Bitcoin and indexer services on exit instead of stopping them.
|
|
145
144
|
Use the explicit `bitcoin ...` and `indexer ...` commands when you want direct service inspection or start/stop control.
|
|
146
145
|
For provider-backed local wallets, normal reads, mutations, and mining setup flows load local wallet state on demand whenever the local secret provider is available.
|
|
147
|
-
`cogcoin
|
|
148
|
-
|
|
146
|
+
When no wallet exists yet, `cogcoin init` interactively lets you either create a new wallet or restore an existing one from a 24-word English BIP39 mnemonic, then continues into sync.
|
|
147
|
+
To replace an existing wallet with a different mnemonic, run `cogcoin reset`, choose `clear wallet entropy`, and then rerun `cogcoin init`.
|
|
149
148
|
|
|
150
149
|
## SQLite Store
|
|
151
150
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { ManagedIndexerDaemonProbeResult } from "./managed-runtime/types.js";
|
|
1
2
|
import { type BootstrapPhase, type BootstrapProgress, type ManagedIndexerDaemonObservedStatus, type ManagedIndexerDaemonStatus } from "./types.js";
|
|
2
3
|
import { resolveManagedServicePaths } from "./service-paths.js";
|
|
4
|
+
export type { IndexerDaemonCompatibility } from "./managed-runtime/types.js";
|
|
3
5
|
export declare const INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED = "indexer_daemon_background_follow_recovery_failed";
|
|
4
6
|
interface DaemonRequest {
|
|
5
7
|
id: string;
|
|
@@ -74,13 +76,7 @@ export interface IndexerDaemonClient {
|
|
|
74
76
|
resumeBackgroundFollow(): Promise<void>;
|
|
75
77
|
close(): Promise<void>;
|
|
76
78
|
}
|
|
77
|
-
export type
|
|
78
|
-
export interface IndexerDaemonProbeResult {
|
|
79
|
-
compatibility: IndexerDaemonCompatibility;
|
|
80
|
-
status: ManagedIndexerDaemonObservedStatus | null;
|
|
81
|
-
client: IndexerDaemonClient | null;
|
|
82
|
-
error: string | null;
|
|
83
|
-
}
|
|
79
|
+
export type IndexerDaemonProbeResult = ManagedIndexerDaemonProbeResult<IndexerDaemonClient>;
|
|
84
80
|
export interface IndexerDaemonStopResult {
|
|
85
81
|
status: "stopped" | "not-running";
|
|
86
82
|
walletRootId: string;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { mkdir,
|
|
3
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import net from "node:net";
|
|
6
|
-
import { compareSemver, parseSemver } from "../semver.js";
|
|
7
6
|
import { acquireFileLock, FileLockBusyError } from "../wallet/fs/lock.js";
|
|
8
7
|
import { writeRuntimeStatusFile } from "../wallet/fs/status-file.js";
|
|
9
|
-
import {
|
|
8
|
+
import { buildManagedIndexerStatusFromSnapshotHandle, mapIndexerDaemonTransportError, mapIndexerDaemonValidationError, resolveIndexerDaemonProbeDecision, validateIndexerDaemonStatus, validateIndexerSnapshotHandle, validateIndexerSnapshotPayload, } from "./managed-runtime/indexer-policy.js";
|
|
9
|
+
import { readJsonFileIfPresent } from "./managed-runtime/status.js";
|
|
10
|
+
import {} from "./types.js";
|
|
10
11
|
import { resolveManagedServicePaths, UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
|
|
11
12
|
const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
|
|
12
13
|
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
@@ -15,17 +16,6 @@ const INDEXER_DAEMON_REQUEST_TIMEOUT_MS = 15_000;
|
|
|
15
16
|
const INDEXER_DAEMON_RESUME_BACKGROUND_FOLLOW_REQUEST_TIMEOUT_MS = 35_000;
|
|
16
17
|
const INDEXER_DAEMON_BACKGROUND_FOLLOW_NOT_ACTIVE = "indexer_daemon_background_follow_not_active";
|
|
17
18
|
export const INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED = "indexer_daemon_background_follow_recovery_failed";
|
|
18
|
-
async function readJsonFile(filePath) {
|
|
19
|
-
try {
|
|
20
|
-
return JSON.parse(await readFile(filePath, "utf8"));
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
19
|
async function isProcessAlive(pid) {
|
|
30
20
|
if (pid === null) {
|
|
31
21
|
return false;
|
|
@@ -68,7 +58,7 @@ function ignoreProcessNotFound(error) {
|
|
|
68
58
|
export async function stopIndexerDaemonServiceWithLockHeld(options) {
|
|
69
59
|
const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
70
60
|
const paths = options.paths ?? resolveManagedServicePaths(options.dataDir, walletRootId);
|
|
71
|
-
const status = await
|
|
61
|
+
const status = await readJsonFileIfPresent(paths.indexerDaemonStatusPath);
|
|
72
62
|
const processId = options.processId ?? status?.processId ?? null;
|
|
73
63
|
if (status === null || processId === null || !await isProcessAlive(processId)) {
|
|
74
64
|
await clearIndexerDaemonRuntimeArtifacts(paths);
|
|
@@ -217,97 +207,6 @@ function createIndexerDaemonClient(socketPath, closeOptions = null) {
|
|
|
217
207
|
},
|
|
218
208
|
};
|
|
219
209
|
}
|
|
220
|
-
function validateIndexerRuntimeIdentity(identity, expectedWalletRootId) {
|
|
221
|
-
if (identity.serviceApiVersion !== INDEXER_DAEMON_SERVICE_API_VERSION) {
|
|
222
|
-
throw new Error("indexer_daemon_service_version_mismatch");
|
|
223
|
-
}
|
|
224
|
-
if (identity.schemaVersion !== INDEXER_DAEMON_SCHEMA_VERSION || identity.state === "schema-mismatch") {
|
|
225
|
-
throw new Error("indexer_daemon_schema_mismatch");
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
function validateIndexerDaemonStatus(status, expectedWalletRootId) {
|
|
229
|
-
validateIndexerRuntimeIdentity(status, expectedWalletRootId);
|
|
230
|
-
}
|
|
231
|
-
function validateIndexerSnapshotHandle(handle, expectedWalletRootId) {
|
|
232
|
-
validateIndexerRuntimeIdentity(handle, expectedWalletRootId);
|
|
233
|
-
}
|
|
234
|
-
function validateIndexerSnapshotPayload(payload, handle, expectedWalletRootId) {
|
|
235
|
-
validateIndexerRuntimeIdentity(payload, expectedWalletRootId);
|
|
236
|
-
if (payload.token !== handle.token
|
|
237
|
-
|| payload.daemonInstanceId !== handle.daemonInstanceId
|
|
238
|
-
|| payload.processId !== handle.processId
|
|
239
|
-
|| payload.startedAtUnixMs !== handle.startedAtUnixMs
|
|
240
|
-
|| payload.snapshotSeq !== handle.snapshotSeq
|
|
241
|
-
|| payload.tipHeight !== handle.tipHeight
|
|
242
|
-
|| payload.tipHash !== handle.tipHash
|
|
243
|
-
|| payload.openedAtUnixMs !== handle.openedAtUnixMs) {
|
|
244
|
-
throw new Error("indexer_daemon_snapshot_identity_mismatch");
|
|
245
|
-
}
|
|
246
|
-
if (payload.tip === null) {
|
|
247
|
-
if (payload.tipHeight !== null || payload.tipHash !== null) {
|
|
248
|
-
throw new Error("indexer_daemon_snapshot_identity_mismatch");
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
else if (payload.tip.height !== payload.tipHeight || payload.tip.blockHashHex !== payload.tipHash) {
|
|
252
|
-
throw new Error("indexer_daemon_snapshot_identity_mismatch");
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
function isUnreachableIndexerDaemonError(error) {
|
|
256
|
-
if (error instanceof Error) {
|
|
257
|
-
if (error.message === "indexer_daemon_connection_closed"
|
|
258
|
-
|| error.message === "indexer_daemon_request_timeout"
|
|
259
|
-
|| error.message === "indexer_daemon_protocol_error") {
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
if ("code" in error) {
|
|
263
|
-
const code = error.code;
|
|
264
|
-
return code === "ENOENT" || code === "ECONNREFUSED" || code === "ECONNRESET";
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
function buildStatusFromSnapshotHandle(handle) {
|
|
270
|
-
return {
|
|
271
|
-
serviceApiVersion: INDEXER_DAEMON_SERVICE_API_VERSION,
|
|
272
|
-
binaryVersion: handle.binaryVersion,
|
|
273
|
-
buildId: handle.buildId,
|
|
274
|
-
updatedAtUnixMs: Math.max(handle.heartbeatAtUnixMs, handle.openedAtUnixMs),
|
|
275
|
-
walletRootId: handle.walletRootId,
|
|
276
|
-
daemonInstanceId: handle.daemonInstanceId,
|
|
277
|
-
schemaVersion: INDEXER_DAEMON_SCHEMA_VERSION,
|
|
278
|
-
state: handle.state,
|
|
279
|
-
processId: handle.processId,
|
|
280
|
-
startedAtUnixMs: handle.startedAtUnixMs,
|
|
281
|
-
heartbeatAtUnixMs: handle.heartbeatAtUnixMs,
|
|
282
|
-
ipcReady: true,
|
|
283
|
-
rpcReachable: handle.rpcReachable,
|
|
284
|
-
coreBestHeight: handle.coreBestHeight,
|
|
285
|
-
coreBestHash: handle.coreBestHash,
|
|
286
|
-
appliedTipHeight: handle.appliedTipHeight,
|
|
287
|
-
appliedTipHash: handle.appliedTipHash,
|
|
288
|
-
snapshotSeq: handle.snapshotSeq,
|
|
289
|
-
backlogBlocks: handle.backlogBlocks,
|
|
290
|
-
reorgDepth: handle.reorgDepth,
|
|
291
|
-
lastAppliedAtUnixMs: handle.lastAppliedAtUnixMs,
|
|
292
|
-
activeSnapshotCount: handle.activeSnapshotCount,
|
|
293
|
-
lastError: handle.lastError,
|
|
294
|
-
backgroundFollowActive: handle.backgroundFollowActive,
|
|
295
|
-
bootstrapPhase: handle.bootstrapPhase,
|
|
296
|
-
bootstrapProgress: handle.bootstrapProgress,
|
|
297
|
-
cogcoinSyncHeight: handle.cogcoinSyncHeight,
|
|
298
|
-
cogcoinSyncTargetHeight: handle.cogcoinSyncTargetHeight,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
function isStaleIndexerDaemonVersion(status, expectedBinaryVersion) {
|
|
302
|
-
if (status === null || expectedBinaryVersion === null || expectedBinaryVersion === undefined) {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
if (parseSemver(expectedBinaryVersion) === null) {
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
const comparison = compareSemver(status.binaryVersion, expectedBinaryVersion);
|
|
309
|
-
return comparison === null || comparison < 0;
|
|
310
|
-
}
|
|
311
210
|
async function probeIndexerDaemonAtSocket(socketPath, expectedWalletRootId) {
|
|
312
211
|
const client = createIndexerDaemonClient(socketPath);
|
|
313
212
|
try {
|
|
@@ -323,30 +222,12 @@ async function probeIndexerDaemonAtSocket(socketPath, expectedWalletRootId) {
|
|
|
323
222
|
}
|
|
324
223
|
catch (error) {
|
|
325
224
|
await client.close().catch(() => undefined);
|
|
326
|
-
return
|
|
327
|
-
compatibility: error instanceof Error
|
|
328
|
-
? error.message === "indexer_daemon_service_version_mismatch"
|
|
329
|
-
? "service-version-mismatch"
|
|
330
|
-
: "schema-mismatch"
|
|
331
|
-
: "protocol-error",
|
|
332
|
-
status,
|
|
333
|
-
client: null,
|
|
334
|
-
error: error instanceof Error ? error.message : "indexer_daemon_protocol_error",
|
|
335
|
-
};
|
|
225
|
+
return mapIndexerDaemonValidationError(error, status);
|
|
336
226
|
}
|
|
337
227
|
}
|
|
338
228
|
catch (error) {
|
|
339
229
|
await client.close().catch(() => undefined);
|
|
340
|
-
return
|
|
341
|
-
compatibility: isUnreachableIndexerDaemonError(error) ? "unreachable" : "protocol-error",
|
|
342
|
-
status: null,
|
|
343
|
-
client: null,
|
|
344
|
-
error: isUnreachableIndexerDaemonError(error)
|
|
345
|
-
? null
|
|
346
|
-
: error instanceof Error
|
|
347
|
-
? "indexer_daemon_protocol_error"
|
|
348
|
-
: "indexer_daemon_protocol_error",
|
|
349
|
-
};
|
|
230
|
+
return mapIndexerDaemonTransportError(error);
|
|
350
231
|
}
|
|
351
232
|
}
|
|
352
233
|
async function waitForIndexerDaemon(dataDir, walletRootId, timeoutMs) {
|
|
@@ -380,7 +261,7 @@ export async function readSnapshotWithRetry(daemon, expectedWalletRootId) {
|
|
|
380
261
|
validateIndexerSnapshotPayload(payload, handle, expectedWalletRootId);
|
|
381
262
|
return {
|
|
382
263
|
payload,
|
|
383
|
-
status:
|
|
264
|
+
status: buildManagedIndexerStatusFromSnapshotHandle(handle),
|
|
384
265
|
};
|
|
385
266
|
}
|
|
386
267
|
catch (error) {
|
|
@@ -400,7 +281,7 @@ export async function readSnapshotWithRetry(daemon, expectedWalletRootId) {
|
|
|
400
281
|
export async function readObservedIndexerDaemonStatus(options) {
|
|
401
282
|
const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
402
283
|
const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
|
|
403
|
-
return
|
|
284
|
+
return readJsonFileIfPresent(paths.indexerDaemonStatusPath);
|
|
404
285
|
}
|
|
405
286
|
export async function attachOrStartIndexerDaemon(options) {
|
|
406
287
|
const requestBackgroundFollow = async (client, observedStatus = null) => {
|
|
@@ -467,21 +348,23 @@ export async function attachOrStartIndexerDaemon(options) {
|
|
|
467
348
|
});
|
|
468
349
|
};
|
|
469
350
|
const existingProbe = await probeIndexerDaemonAtSocket(paths.indexerDaemonSocketPath, walletRootId);
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
351
|
+
const existingDecision = resolveIndexerDaemonProbeDecision({
|
|
352
|
+
probe: existingProbe,
|
|
353
|
+
expectedBinaryVersion,
|
|
354
|
+
});
|
|
355
|
+
if (existingDecision.action === "attach" && existingProbe.client !== null) {
|
|
356
|
+
try {
|
|
357
|
+
return await requestBackgroundFollow(existingProbe.client, existingProbe.status);
|
|
478
358
|
}
|
|
479
|
-
|
|
359
|
+
catch {
|
|
480
360
|
await existingProbe.client.close().catch(() => undefined);
|
|
481
361
|
}
|
|
482
362
|
}
|
|
483
|
-
if (
|
|
484
|
-
|
|
363
|
+
if (existingDecision.action === "replace" && existingProbe.client !== null) {
|
|
364
|
+
await existingProbe.client.close().catch(() => undefined);
|
|
365
|
+
}
|
|
366
|
+
if (existingDecision.action === "reject") {
|
|
367
|
+
throw new Error(existingDecision.error ?? "indexer_daemon_protocol_error");
|
|
485
368
|
}
|
|
486
369
|
try {
|
|
487
370
|
const lock = await acquireFileLock(paths.indexerDaemonLockPath, {
|
|
@@ -492,23 +375,15 @@ export async function attachOrStartIndexerDaemon(options) {
|
|
|
492
375
|
});
|
|
493
376
|
try {
|
|
494
377
|
const liveProbe = await probeIndexerDaemonAtSocket(paths.indexerDaemonSocketPath, walletRootId);
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
await stopIndexerDaemonServiceWithLockHeld({
|
|
503
|
-
dataDir: options.dataDir,
|
|
504
|
-
walletRootId,
|
|
505
|
-
shutdownTimeoutMs: options.shutdownTimeoutMs,
|
|
506
|
-
paths,
|
|
507
|
-
processId: liveProbe.status?.processId ?? null,
|
|
508
|
-
});
|
|
509
|
-
}
|
|
378
|
+
const liveDecision = resolveIndexerDaemonProbeDecision({
|
|
379
|
+
probe: liveProbe,
|
|
380
|
+
expectedBinaryVersion,
|
|
381
|
+
});
|
|
382
|
+
if (liveDecision.action === "attach" && liveProbe.client !== null) {
|
|
383
|
+
try {
|
|
384
|
+
return await requestBackgroundFollow(liveProbe.client, liveProbe.status);
|
|
510
385
|
}
|
|
511
|
-
|
|
386
|
+
catch {
|
|
512
387
|
await liveProbe.client.close().catch(() => undefined);
|
|
513
388
|
await stopIndexerDaemonServiceWithLockHeld({
|
|
514
389
|
dataDir: options.dataDir,
|
|
@@ -519,8 +394,18 @@ export async function attachOrStartIndexerDaemon(options) {
|
|
|
519
394
|
});
|
|
520
395
|
}
|
|
521
396
|
}
|
|
522
|
-
else if (liveProbe.
|
|
523
|
-
|
|
397
|
+
else if (liveDecision.action === "replace" && liveProbe.client !== null) {
|
|
398
|
+
await liveProbe.client.close().catch(() => undefined);
|
|
399
|
+
await stopIndexerDaemonServiceWithLockHeld({
|
|
400
|
+
dataDir: options.dataDir,
|
|
401
|
+
walletRootId,
|
|
402
|
+
shutdownTimeoutMs: options.shutdownTimeoutMs,
|
|
403
|
+
paths,
|
|
404
|
+
processId: liveProbe.status?.processId ?? null,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
else if (liveDecision.action === "reject") {
|
|
408
|
+
throw new Error(liveDecision.error ?? "indexer_daemon_protocol_error");
|
|
524
409
|
}
|
|
525
410
|
const daemon = await startDaemon();
|
|
526
411
|
try {
|
|
@@ -572,7 +457,7 @@ export async function shutdownIndexerDaemonForTesting(options) {
|
|
|
572
457
|
export async function readIndexerDaemonStatusForTesting(options) {
|
|
573
458
|
const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
574
459
|
const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
|
|
575
|
-
return
|
|
460
|
+
return readJsonFileIfPresent(paths.indexerDaemonStatusPath);
|
|
576
461
|
}
|
|
577
462
|
export async function writeIndexerDaemonStatusForTesting(options, status) {
|
|
578
463
|
const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ManagedBitcoindObservedStatus } from "../types.js";
|
|
2
|
+
import type { WalletBitcoindStatus, WalletNodeStatus } from "../../wallet/read/types.js";
|
|
3
|
+
import type { ManagedBitcoindProbeDecision, ManagedBitcoindServiceProbeResult } from "./types.js";
|
|
4
|
+
export declare function validateManagedBitcoindObservedStatus(status: ManagedBitcoindObservedStatus, options: {
|
|
5
|
+
chain: "main" | "regtest";
|
|
6
|
+
dataDir: string;
|
|
7
|
+
runtimeRoot: string;
|
|
8
|
+
}): void;
|
|
9
|
+
export declare function mapManagedBitcoindValidationError(error: unknown, status: ManagedBitcoindObservedStatus): ManagedBitcoindServiceProbeResult;
|
|
10
|
+
export declare function mapManagedBitcoindRuntimeProbeFailure(error: unknown, status: ManagedBitcoindObservedStatus): ManagedBitcoindServiceProbeResult;
|
|
11
|
+
export declare function resolveManagedBitcoindProbeDecision(probe: ManagedBitcoindServiceProbeResult): ManagedBitcoindProbeDecision;
|
|
12
|
+
export declare function deriveManagedBitcoindWalletStatus(options: {
|
|
13
|
+
status: ManagedBitcoindObservedStatus | null;
|
|
14
|
+
nodeStatus: WalletNodeStatus | null;
|
|
15
|
+
startupError: string | null;
|
|
16
|
+
}): WalletBitcoindStatus;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { resolveManagedServicePaths } from "../service-paths.js";
|
|
3
|
+
import { MANAGED_BITCOIND_SERVICE_API_VERSION } from "../types.js";
|
|
4
|
+
function isRuntimeMismatchError(error) {
|
|
5
|
+
if (!(error instanceof Error)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
return error.message.startsWith("bitcoind_chain_expected_")
|
|
9
|
+
|| error.message === "managed_bitcoind_runtime_mismatch";
|
|
10
|
+
}
|
|
11
|
+
function isUnreachableManagedBitcoindError(error) {
|
|
12
|
+
if (error instanceof Error) {
|
|
13
|
+
if ("code" in error) {
|
|
14
|
+
const code = error.code;
|
|
15
|
+
return code === "ENOENT" || code === "ECONNREFUSED" || code === "ECONNRESET";
|
|
16
|
+
}
|
|
17
|
+
return error.message === "bitcoind_cookie_timeout"
|
|
18
|
+
|| error.message.includes("cookie file is unavailable")
|
|
19
|
+
|| error.message.includes("ECONNREFUSED")
|
|
20
|
+
|| error.message.includes("ECONNRESET")
|
|
21
|
+
|| error.message.includes("socket hang up");
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
export function validateManagedBitcoindObservedStatus(status, options) {
|
|
26
|
+
const legacyRuntimeRoot = join(resolveManagedServicePaths(options.dataDir, status.walletRootId).runtimeRoot, status.walletRootId);
|
|
27
|
+
if (status.serviceApiVersion !== MANAGED_BITCOIND_SERVICE_API_VERSION) {
|
|
28
|
+
throw new Error("managed_bitcoind_service_version_mismatch");
|
|
29
|
+
}
|
|
30
|
+
// Managed bitcoind runtimes are adopted across wallet roots when the live
|
|
31
|
+
// runtime still points at the expected data dir and chain.
|
|
32
|
+
if (status.chain !== options.chain
|
|
33
|
+
|| status.dataDir !== options.dataDir
|
|
34
|
+
|| (status.runtimeRoot !== options.runtimeRoot && status.runtimeRoot !== legacyRuntimeRoot)) {
|
|
35
|
+
throw new Error("managed_bitcoind_runtime_mismatch");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function mapManagedBitcoindValidationError(error, status) {
|
|
39
|
+
return {
|
|
40
|
+
compatibility: error instanceof Error
|
|
41
|
+
? error.message === "managed_bitcoind_service_version_mismatch"
|
|
42
|
+
? "service-version-mismatch"
|
|
43
|
+
: "runtime-mismatch"
|
|
44
|
+
: "protocol-error",
|
|
45
|
+
status,
|
|
46
|
+
error: error instanceof Error ? error.message : "managed_bitcoind_protocol_error",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function mapManagedBitcoindRuntimeProbeFailure(error, status) {
|
|
50
|
+
if (isRuntimeMismatchError(error)) {
|
|
51
|
+
return {
|
|
52
|
+
compatibility: "runtime-mismatch",
|
|
53
|
+
status,
|
|
54
|
+
error: "managed_bitcoind_runtime_mismatch",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (isUnreachableManagedBitcoindError(error)) {
|
|
58
|
+
return {
|
|
59
|
+
compatibility: "unreachable",
|
|
60
|
+
status,
|
|
61
|
+
error: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
compatibility: "protocol-error",
|
|
66
|
+
status,
|
|
67
|
+
error: "managed_bitcoind_protocol_error",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function resolveManagedBitcoindProbeDecision(probe) {
|
|
71
|
+
if (probe.compatibility === "compatible") {
|
|
72
|
+
return {
|
|
73
|
+
action: "attach",
|
|
74
|
+
error: null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (probe.compatibility === "unreachable") {
|
|
78
|
+
return {
|
|
79
|
+
action: "start",
|
|
80
|
+
error: null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
action: "reject",
|
|
85
|
+
error: probe.error ?? "managed_bitcoind_protocol_error",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function mapManagedBitcoindStartupError(message) {
|
|
89
|
+
switch (message) {
|
|
90
|
+
case "managed_bitcoind_service_start_timeout":
|
|
91
|
+
return {
|
|
92
|
+
health: "starting",
|
|
93
|
+
status: null,
|
|
94
|
+
message: "Managed bitcoind service is still starting.",
|
|
95
|
+
};
|
|
96
|
+
case "managed_bitcoind_service_version_mismatch":
|
|
97
|
+
return {
|
|
98
|
+
health: "service-version-mismatch",
|
|
99
|
+
status: null,
|
|
100
|
+
message: "The live managed bitcoind service is running an incompatible service version.",
|
|
101
|
+
};
|
|
102
|
+
case "managed_bitcoind_wallet_root_mismatch":
|
|
103
|
+
return {
|
|
104
|
+
health: "wallet-root-mismatch",
|
|
105
|
+
status: null,
|
|
106
|
+
message: "The live managed bitcoind service belongs to a different wallet root.",
|
|
107
|
+
};
|
|
108
|
+
case "managed_bitcoind_runtime_mismatch":
|
|
109
|
+
return {
|
|
110
|
+
health: "runtime-mismatch",
|
|
111
|
+
status: null,
|
|
112
|
+
message: "The live managed bitcoind service runtime does not match this wallet's expected data directory or chain.",
|
|
113
|
+
};
|
|
114
|
+
case "managed_bitcoind_protocol_error":
|
|
115
|
+
return {
|
|
116
|
+
health: "unavailable",
|
|
117
|
+
status: null,
|
|
118
|
+
message: "The managed bitcoind runtime artifacts are invalid or incomplete.",
|
|
119
|
+
};
|
|
120
|
+
default:
|
|
121
|
+
return {
|
|
122
|
+
health: "unavailable",
|
|
123
|
+
status: null,
|
|
124
|
+
message,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export function deriveManagedBitcoindWalletStatus(options) {
|
|
129
|
+
if (options.startupError !== null) {
|
|
130
|
+
const mapped = mapManagedBitcoindStartupError(options.startupError);
|
|
131
|
+
return {
|
|
132
|
+
...mapped,
|
|
133
|
+
status: options.status,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (options.status === null) {
|
|
137
|
+
return {
|
|
138
|
+
health: "unavailable",
|
|
139
|
+
status: null,
|
|
140
|
+
message: "Managed bitcoind service is unavailable.",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (options.status.state === "starting") {
|
|
144
|
+
return {
|
|
145
|
+
health: "starting",
|
|
146
|
+
status: options.status,
|
|
147
|
+
message: options.status.lastError ?? "Managed bitcoind service is still starting.",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (options.status.state === "failed") {
|
|
151
|
+
return {
|
|
152
|
+
health: "failed",
|
|
153
|
+
status: options.status,
|
|
154
|
+
message: options.status.lastError ?? "Managed bitcoind service refresh failed.",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const proofStatus = options.nodeStatus?.walletReplica?.proofStatus;
|
|
158
|
+
if (proofStatus === "missing") {
|
|
159
|
+
return {
|
|
160
|
+
health: "replica-missing",
|
|
161
|
+
status: options.status,
|
|
162
|
+
message: options.nodeStatus?.walletReplicaMessage ?? "Managed Core wallet replica is missing.",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (proofStatus === "mismatch") {
|
|
166
|
+
return {
|
|
167
|
+
health: "replica-mismatch",
|
|
168
|
+
status: options.status,
|
|
169
|
+
message: options.nodeStatus?.walletReplicaMessage ?? "Managed Core wallet replica does not match trusted wallet state.",
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
health: "ready",
|
|
174
|
+
status: options.status,
|
|
175
|
+
message: options.nodeStatus?.walletReplicaMessage ?? options.status.lastError,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { WalletIndexerStatus } from "../../wallet/read/types.js";
|
|
2
|
+
import type { IndexerSnapshotHandle, IndexerSnapshotPayload } from "../indexer-daemon.js";
|
|
3
|
+
import { type ManagedIndexerDaemonObservedStatus, type ManagedIndexerDaemonStatus, type ManagedIndexerTruthSource } from "../types.js";
|
|
4
|
+
import { buildManagedIndexerStatusFromSnapshotHandle } from "./status.js";
|
|
5
|
+
import type { IndexerDaemonProbeDecision, ManagedIndexerDaemonProbeResult, ManagedIndexerSnapshotLike } from "./types.js";
|
|
6
|
+
type IndexerRuntimeIdentityLike = {
|
|
7
|
+
serviceApiVersion: string;
|
|
8
|
+
schemaVersion: string;
|
|
9
|
+
walletRootId: string;
|
|
10
|
+
daemonInstanceId: string;
|
|
11
|
+
processId: number | null;
|
|
12
|
+
startedAtUnixMs: number;
|
|
13
|
+
state?: ManagedIndexerDaemonStatus["state"] | string;
|
|
14
|
+
};
|
|
15
|
+
export declare function validateIndexerRuntimeIdentity(identity: IndexerRuntimeIdentityLike, expectedWalletRootId: string): void;
|
|
16
|
+
export declare function validateIndexerDaemonStatus(status: ManagedIndexerDaemonObservedStatus, expectedWalletRootId: string): void;
|
|
17
|
+
export declare function validateIndexerSnapshotHandle(handle: IndexerSnapshotHandle, expectedWalletRootId: string): void;
|
|
18
|
+
export declare function validateIndexerSnapshotPayload(payload: IndexerSnapshotPayload, handle: IndexerSnapshotHandle, expectedWalletRootId: string): void;
|
|
19
|
+
export declare function mapIndexerDaemonValidationError<TClient>(error: unknown, status: ManagedIndexerDaemonObservedStatus): ManagedIndexerDaemonProbeResult<TClient>;
|
|
20
|
+
export declare function mapIndexerDaemonTransportError<TClient>(error: unknown): ManagedIndexerDaemonProbeResult<TClient>;
|
|
21
|
+
export declare function isStaleIndexerDaemonVersion(status: ManagedIndexerDaemonObservedStatus | null, expectedBinaryVersion: string | null | undefined): boolean;
|
|
22
|
+
export declare function resolveIndexerDaemonProbeDecision<TClient>(options: {
|
|
23
|
+
probe: ManagedIndexerDaemonProbeResult<TClient>;
|
|
24
|
+
expectedBinaryVersion: string | null | undefined;
|
|
25
|
+
}): IndexerDaemonProbeDecision;
|
|
26
|
+
export declare function deriveManagedIndexerWalletStatus(options: {
|
|
27
|
+
daemonStatus: ManagedIndexerDaemonStatus | null;
|
|
28
|
+
observedStatus?: ManagedIndexerDaemonObservedStatus | null;
|
|
29
|
+
snapshot: ManagedIndexerSnapshotLike | null;
|
|
30
|
+
source: ManagedIndexerTruthSource;
|
|
31
|
+
now: number;
|
|
32
|
+
startupError: string | null;
|
|
33
|
+
}): WalletIndexerStatus;
|
|
34
|
+
export { buildManagedIndexerStatusFromSnapshotHandle };
|