@cogcoin/client 1.0.2 → 1.1.0
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 +3 -2
- package/dist/bitcoind/client/factory.d.ts +0 -8
- package/dist/bitcoind/client/factory.js +1 -59
- package/dist/bitcoind/client/managed-client.d.ts +1 -3
- package/dist/bitcoind/client/managed-client.js +3 -47
- package/dist/bitcoind/indexer-daemon-main.js +173 -28
- package/dist/bitcoind/indexer-daemon.d.ts +11 -3
- package/dist/bitcoind/indexer-daemon.js +123 -57
- package/dist/bitcoind/indexer-monitor.d.ts +12 -0
- package/dist/bitcoind/indexer-monitor.js +89 -0
- package/dist/bitcoind/progress/follow-scene.d.ts +7 -1
- package/dist/bitcoind/progress/follow-scene.js +87 -4
- package/dist/bitcoind/progress/tty-renderer.d.ts +2 -0
- package/dist/bitcoind/progress/tty-renderer.js +2 -0
- package/dist/bitcoind/testing.d.ts +0 -1
- package/dist/bitcoind/testing.js +0 -1
- package/dist/bitcoind/types.d.ts +5 -2
- package/dist/cli/commands/follow.js +44 -49
- package/dist/cli/commands/mining-admin.js +56 -2
- package/dist/cli/commands/mining-read.js +43 -3
- package/dist/cli/commands/mining-runtime.js +91 -73
- package/dist/cli/commands/service-runtime.js +42 -2
- package/dist/cli/commands/status.js +3 -1
- package/dist/cli/commands/sync.js +50 -90
- package/dist/cli/commands/wallet-admin.js +21 -3
- package/dist/cli/commands/wallet-read.js +2 -0
- package/dist/cli/context.js +5 -1
- package/dist/cli/managed-indexer-observer.d.ts +33 -0
- package/dist/cli/managed-indexer-observer.js +163 -0
- package/dist/cli/mining-format.d.ts +3 -1
- package/dist/cli/mining-format.js +35 -0
- package/dist/cli/mining-json.d.ts +11 -1
- package/dist/cli/mining-json.js +9 -0
- package/dist/cli/output.js +24 -0
- package/dist/cli/parse.d.ts +1 -1
- package/dist/cli/parse.js +23 -0
- package/dist/cli/read-json.d.ts +13 -1
- package/dist/cli/read-json.js +31 -0
- package/dist/cli/runner.js +4 -2
- package/dist/cli/signals.d.ts +12 -0
- package/dist/cli/signals.js +31 -13
- package/dist/cli/types.d.ts +8 -4
- package/dist/cli/update-service.d.ts +2 -12
- package/dist/cli/update-service.js +2 -68
- package/dist/semver.d.ts +12 -0
- package/dist/semver.js +68 -0
- package/dist/wallet/lifecycle.js +0 -6
- package/dist/wallet/mining/config.js +54 -3
- package/dist/wallet/mining/control.d.ts +5 -2
- package/dist/wallet/mining/control.js +153 -34
- package/dist/wallet/mining/domain-prompts.d.ts +17 -0
- package/dist/wallet/mining/domain-prompts.js +130 -0
- package/dist/wallet/mining/index.d.ts +2 -1
- package/dist/wallet/mining/index.js +1 -0
- package/dist/wallet/mining/runner.d.ts +58 -2
- package/dist/wallet/mining/runner.js +553 -331
- package/dist/wallet/mining/sentence-protocol.d.ts +1 -0
- package/dist/wallet/mining/sentences.js +7 -4
- package/dist/wallet/mining/types.d.ts +26 -0
- package/dist/wallet/mining/visualizer.d.ts +3 -0
- package/dist/wallet/mining/visualizer.js +106 -12
- package/dist/wallet/read/context.d.ts +1 -0
- package/dist/wallet/read/context.js +15 -10
- package/dist/wallet/reset.js +0 -1
- package/dist/wallet/state/client-password-agent.js +4 -1
- package/dist/wallet/state/client-password.js +15 -8
- package/dist/wallet/tx/anchor.js +0 -1
- package/dist/wallet/tx/bitcoin-transfer.js +0 -1
- package/dist/wallet/tx/cog.js +0 -3
- package/dist/wallet/tx/common.js +1 -1
- package/dist/wallet/tx/domain-admin.js +0 -1
- package/dist/wallet/tx/domain-market.js +0 -3
- package/dist/wallet/tx/field.js +0 -1
- package/dist/wallet/tx/register.js +0 -1
- package/dist/wallet/tx/reputation.js +0 -1
- package/package.json +1 -1
|
@@ -1,83 +1,78 @@
|
|
|
1
1
|
import { dirname } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_SNAPSHOT_METADATA, resolveBootstrapPathsForTesting } from "../../bitcoind/bootstrap.js";
|
|
3
3
|
import { resolveWalletRootIdFromLocalArtifacts } from "../../wallet/root-resolution.js";
|
|
4
|
+
import { followManagedIndexerStatus, ManagedIndexerProgressObserver, } from "../managed-indexer-observer.js";
|
|
4
5
|
import { usesTtyProgress, writeLine } from "../io.js";
|
|
5
|
-
import { classifyCliError
|
|
6
|
-
import {
|
|
6
|
+
import { classifyCliError } from "../output.js";
|
|
7
|
+
import { createCloseSignalWatcher, waitForCompletionOrStop } from "../signals.js";
|
|
7
8
|
export async function runFollowCommand(parsed, context) {
|
|
8
9
|
const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
|
|
9
10
|
const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
|
|
11
|
+
const packageVersion = await context.readPackageVersion();
|
|
10
12
|
const runtimePaths = context.resolveWalletRuntimePaths();
|
|
11
|
-
let
|
|
12
|
-
let
|
|
13
|
-
let storeOwned = true;
|
|
13
|
+
let monitor = null;
|
|
14
|
+
let observer = null;
|
|
14
15
|
try {
|
|
15
16
|
const walletRoot = await resolveWalletRootIdFromLocalArtifacts({
|
|
16
17
|
paths: runtimePaths,
|
|
17
18
|
provider: context.walletSecretProvider,
|
|
18
19
|
loadRawWalletStateEnvelope: context.loadRawWalletStateEnvelope,
|
|
19
20
|
});
|
|
20
|
-
try {
|
|
21
|
-
controlLock = await acquireFileLock(runtimePaths.walletControlLockPath, {
|
|
22
|
-
purpose: "managed-follow",
|
|
23
|
-
walletRootId: walletRoot.walletRootId,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
if (error instanceof FileLockBusyError) {
|
|
28
|
-
throw new Error("wallet_control_lock_busy");
|
|
29
|
-
}
|
|
30
|
-
throw error;
|
|
31
|
-
}
|
|
32
21
|
await context.ensureDirectory(dirname(dbPath));
|
|
33
|
-
|
|
34
|
-
const client = await context.openManagedBitcoindClient({
|
|
35
|
-
store,
|
|
36
|
-
databasePath: dbPath,
|
|
22
|
+
monitor = await context.openManagedIndexerMonitor({
|
|
37
23
|
dataDir,
|
|
24
|
+
databasePath: dbPath,
|
|
38
25
|
walletRootId: walletRoot.walletRootId,
|
|
26
|
+
expectedBinaryVersion: packageVersion,
|
|
27
|
+
});
|
|
28
|
+
observer = new ManagedIndexerProgressObserver({
|
|
29
|
+
quoteStatePath: resolveBootstrapPathsForTesting(dataDir, DEFAULT_SNAPSHOT_METADATA).quoteStatePath,
|
|
30
|
+
stream: context.stderr,
|
|
39
31
|
progressOutput: parsed.progressOutput,
|
|
32
|
+
followVisualMode: true,
|
|
33
|
+
});
|
|
34
|
+
const abortController = new AbortController();
|
|
35
|
+
const stopWatcher = createCloseSignalWatcher({
|
|
36
|
+
signalSource: context.signalSource,
|
|
37
|
+
stderr: context.stderr,
|
|
38
|
+
closeable: {
|
|
39
|
+
close: async () => {
|
|
40
|
+
abortController.abort(new Error("managed_indexer_follow_aborted"));
|
|
41
|
+
await observer?.close().catch(() => undefined);
|
|
42
|
+
await monitor?.close().catch(() => undefined);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
forceExit: context.forceExit,
|
|
46
|
+
firstMessage: "Stopping managed Cogcoin tip observation...",
|
|
47
|
+
successMessage: "Stopped observing managed Cogcoin tip.",
|
|
48
|
+
failureMessage: "Managed Cogcoin tip observation cleanup failed.",
|
|
40
49
|
});
|
|
41
|
-
storeOwned = false;
|
|
42
|
-
const stopWatcher = createStopSignalWatcher(context.signalSource, context.stderr, client, context.forceExit, [runtimePaths.walletControlLockPath]);
|
|
43
50
|
try {
|
|
44
|
-
await client.startFollowingTip();
|
|
45
51
|
if (!usesTtyProgress(parsed.progressOutput, context.stderr)) {
|
|
46
52
|
writeLine(context.stdout, "Following managed Cogcoin tip. Press Ctrl-C to stop.");
|
|
47
53
|
}
|
|
48
|
-
|
|
54
|
+
const followOutcome = await waitForCompletionOrStop(followManagedIndexerStatus({
|
|
55
|
+
monitor,
|
|
56
|
+
observer,
|
|
57
|
+
signal: abortController.signal,
|
|
58
|
+
}), stopWatcher);
|
|
59
|
+
if (followOutcome.kind === "stopped") {
|
|
60
|
+
return followOutcome.code;
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
49
63
|
}
|
|
50
64
|
catch (error) {
|
|
51
65
|
writeLine(context.stderr, `follow failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
-
await client.close().catch(() => undefined);
|
|
53
66
|
return classifyCliError(error).exitCode;
|
|
54
67
|
}
|
|
55
68
|
finally {
|
|
56
69
|
stopWatcher.cleanup();
|
|
70
|
+
await observer?.close().catch(() => undefined);
|
|
71
|
+
await monitor?.close().catch(() => undefined);
|
|
57
72
|
}
|
|
58
73
|
}
|
|
59
74
|
catch (error) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const formatted = formatCliTextError(error);
|
|
63
|
-
if (formatted !== null) {
|
|
64
|
-
for (const line of formatted) {
|
|
65
|
-
writeLine(context.stderr, line);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
writeLine(context.stderr, classified.message);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
writeLine(context.stderr, `follow failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
74
|
-
}
|
|
75
|
-
if (storeOwned && store !== null) {
|
|
76
|
-
await store.close().catch(() => undefined);
|
|
77
|
-
}
|
|
78
|
-
return classified.exitCode;
|
|
79
|
-
}
|
|
80
|
-
finally {
|
|
81
|
-
await controlLock?.release().catch(() => undefined);
|
|
75
|
+
writeLine(context.stderr, `follow failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
76
|
+
return classifyCliError(error).exitCode;
|
|
82
77
|
}
|
|
83
78
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { buildMinePromptData, buildMineSetupData, } from "../mining-json.js";
|
|
2
3
|
import { buildMineSetupPreviewData } from "../preview-json.js";
|
|
4
|
+
import { formatMiningPromptMutationReport } from "../mining-format.js";
|
|
3
5
|
import { writeLine } from "../io.js";
|
|
4
6
|
import { createTerminalPrompter } from "../prompt.js";
|
|
5
7
|
import { createPreviewSuccessEnvelope, createMutationSuccessEnvelope, describeCanonicalCommand, resolvePreviewJsonSchema, resolveStableMiningControlJsonSchema, writeHandledCliError, writeJsonValue, } from "../output.js";
|
|
6
|
-
import { formatNextStepLines, getMineSetupNextSteps
|
|
8
|
+
import { formatNextStepLines, getMineSetupNextSteps } from "../workflow-hints.js";
|
|
7
9
|
import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
|
|
8
10
|
function createCommandPrompter(parsed, context) {
|
|
9
11
|
return parsed.outputMode !== "text"
|
|
@@ -13,6 +15,9 @@ function createCommandPrompter(parsed, context) {
|
|
|
13
15
|
export async function runMiningAdminCommand(parsed, context) {
|
|
14
16
|
try {
|
|
15
17
|
const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
|
|
18
|
+
const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
|
|
19
|
+
const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
|
|
20
|
+
const packageVersion = await context.readPackageVersion();
|
|
16
21
|
if (parsed.command === "mine-setup") {
|
|
17
22
|
const prompter = createCommandPrompter(parsed, context);
|
|
18
23
|
const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, prompter);
|
|
@@ -50,6 +55,55 @@ export async function runMiningAdminCommand(parsed, context) {
|
|
|
50
55
|
}
|
|
51
56
|
return 0;
|
|
52
57
|
}
|
|
58
|
+
if (parsed.command === "mine-prompt") {
|
|
59
|
+
const prompter = createCommandPrompter(parsed, context);
|
|
60
|
+
if (!prompter.isInteractive) {
|
|
61
|
+
throw new Error("mine_prompt_requires_tty");
|
|
62
|
+
}
|
|
63
|
+
const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, prompter);
|
|
64
|
+
await context.ensureDirectory(dirname(dbPath));
|
|
65
|
+
const readContext = await context.openWalletReadContext({
|
|
66
|
+
dataDir,
|
|
67
|
+
databasePath: dbPath,
|
|
68
|
+
secretProvider: provider,
|
|
69
|
+
expectedIndexerBinaryVersion: packageVersion,
|
|
70
|
+
paths: runtimePaths,
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const targetDomain = parsed.args[0].trim().toLowerCase();
|
|
74
|
+
const promptState = await context.inspectMiningDomainPromptState({
|
|
75
|
+
paths: runtimePaths,
|
|
76
|
+
provider,
|
|
77
|
+
readContext,
|
|
78
|
+
});
|
|
79
|
+
const currentEntry = promptState.prompts.find((entry) => entry.domain.name === targetDomain);
|
|
80
|
+
if (currentEntry === undefined) {
|
|
81
|
+
throw new Error("mine_prompt_domain_not_mineable");
|
|
82
|
+
}
|
|
83
|
+
if (parsed.outputMode === "text") {
|
|
84
|
+
writeLine(context.stdout, `Domain: ${currentEntry.domain.name}`);
|
|
85
|
+
writeLine(context.stdout, `Current domain prompt: ${currentEntry.prompt ?? "none"}`);
|
|
86
|
+
writeLine(context.stdout, `Global fallback prompt: ${promptState.fallbackPromptConfigured ? "configured" : "not configured"}`);
|
|
87
|
+
}
|
|
88
|
+
const nextPrompt = await prompter.prompt("Domain prompt (blank to clear and use the global fallback): ");
|
|
89
|
+
const result = await context.updateMiningDomainPrompt({
|
|
90
|
+
paths: runtimePaths,
|
|
91
|
+
provider,
|
|
92
|
+
readContext,
|
|
93
|
+
domainName: targetDomain,
|
|
94
|
+
prompt: nextPrompt,
|
|
95
|
+
});
|
|
96
|
+
if (parsed.outputMode === "json") {
|
|
97
|
+
writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMiningControlJsonSchema(parsed), describeCanonicalCommand(parsed), result.status, buildMinePromptData(result)));
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
writeLine(context.stdout, formatMiningPromptMutationReport(result));
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
await readContext.close();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
53
107
|
writeLine(context.stderr, `mining admin command not implemented: ${parsed.command}`);
|
|
54
108
|
return 1;
|
|
55
109
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { dirname } from "node:path";
|
|
2
2
|
import { stat } from "node:fs/promises";
|
|
3
|
-
import { formatMineStatusReport, formatMiningEventRecord } from "../mining-format.js";
|
|
3
|
+
import { formatMineStatusReport, formatMiningEventRecord, formatMiningPromptListReport, } from "../mining-format.js";
|
|
4
4
|
import { writeLine } from "../io.js";
|
|
5
5
|
import { createErrorEnvelope, createSuccessEnvelope, describeCanonicalCommand, normalizeListPage, writeJsonValue, } from "../output.js";
|
|
6
|
-
import { buildMineLogJson, buildMineStatusJson } from "../read-json.js";
|
|
6
|
+
import { buildMineLogJson, buildMinePromptListJson, buildMineStatusJson } from "../read-json.js";
|
|
7
|
+
import { formatNextStepLines } from "../workflow-hints.js";
|
|
7
8
|
import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
|
|
8
9
|
async function readRotationIndices(paths) {
|
|
9
10
|
const rotation = [];
|
|
@@ -24,6 +25,7 @@ export async function runMiningReadCommand(parsed, context) {
|
|
|
24
25
|
try {
|
|
25
26
|
const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
|
|
26
27
|
const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
|
|
28
|
+
const packageVersion = await context.readPackageVersion();
|
|
27
29
|
const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
|
|
28
30
|
await context.ensureDirectory(dirname(dbPath));
|
|
29
31
|
if (parsed.command === "mine-log") {
|
|
@@ -88,6 +90,41 @@ export async function runMiningReadCommand(parsed, context) {
|
|
|
88
90
|
}
|
|
89
91
|
return 0;
|
|
90
92
|
}
|
|
93
|
+
if (parsed.command === "mine-prompt-list") {
|
|
94
|
+
const provider = parsed.outputMode === "text"
|
|
95
|
+
? withInteractiveWalletSecretProvider(context.walletSecretProvider, context.createPrompter())
|
|
96
|
+
: context.walletSecretProvider;
|
|
97
|
+
const readContext = await context.openWalletReadContext({
|
|
98
|
+
dataDir,
|
|
99
|
+
databasePath: dbPath,
|
|
100
|
+
secretProvider: provider,
|
|
101
|
+
expectedIndexerBinaryVersion: packageVersion,
|
|
102
|
+
paths: runtimePaths,
|
|
103
|
+
});
|
|
104
|
+
try {
|
|
105
|
+
const result = buildMinePromptListJson(await context.inspectMiningDomainPromptState({
|
|
106
|
+
paths: runtimePaths,
|
|
107
|
+
provider,
|
|
108
|
+
readContext,
|
|
109
|
+
}));
|
|
110
|
+
if (parsed.outputMode === "json") {
|
|
111
|
+
writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/mine-prompt-list/v1", describeCanonicalCommand(parsed), result.data, {
|
|
112
|
+
warnings: result.warnings,
|
|
113
|
+
explanations: result.explanations,
|
|
114
|
+
nextSteps: result.nextSteps,
|
|
115
|
+
}));
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
writeLine(context.stdout, formatMiningPromptListReport(result.data));
|
|
119
|
+
for (const line of formatNextStepLines(result.nextSteps)) {
|
|
120
|
+
writeLine(context.stdout, line);
|
|
121
|
+
}
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await readContext.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
91
128
|
const provider = parsed.outputMode === "text"
|
|
92
129
|
? withInteractiveWalletSecretProvider(context.walletSecretProvider, context.createPrompter())
|
|
93
130
|
: context.walletSecretProvider;
|
|
@@ -95,6 +132,7 @@ export async function runMiningReadCommand(parsed, context) {
|
|
|
95
132
|
dataDir,
|
|
96
133
|
databasePath: dbPath,
|
|
97
134
|
secretProvider: provider,
|
|
135
|
+
expectedIndexerBinaryVersion: packageVersion,
|
|
98
136
|
paths: runtimePaths,
|
|
99
137
|
});
|
|
100
138
|
try {
|
|
@@ -128,7 +166,9 @@ export async function runMiningReadCommand(parsed, context) {
|
|
|
128
166
|
if (parsed.outputMode === "json") {
|
|
129
167
|
writeJsonValue(context.stdout, createErrorEnvelope(parsed.command === "mine-log"
|
|
130
168
|
? "cogcoin/mine-log/v1"
|
|
131
|
-
: "
|
|
169
|
+
: parsed.command === "mine-prompt-list"
|
|
170
|
+
? "cogcoin/mine-prompt-list/v1"
|
|
171
|
+
: "cogcoin/mine-status/v1", describeCanonicalCommand(parsed), message, message));
|
|
132
172
|
return 5;
|
|
133
173
|
}
|
|
134
174
|
writeLine(context.stderr, message);
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { dirname } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_SNAPSHOT_METADATA, resolveBootstrapPathsForTesting } from "../../bitcoind/bootstrap.js";
|
|
3
3
|
import { resolveWalletRootIdFromLocalArtifacts } from "../../wallet/root-resolution.js";
|
|
4
4
|
import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
|
|
5
|
+
import { ManagedIndexerProgressObserver, pollManagedIndexerUntilCaughtUp, } from "../managed-indexer-observer.js";
|
|
5
6
|
import { buildMineStartData, buildMineStopData, } from "../mining-json.js";
|
|
6
7
|
import { buildMineStartPreviewData, buildMineStopPreviewData, } from "../preview-json.js";
|
|
7
8
|
import { usesTtyProgress, writeLine } from "../io.js";
|
|
8
9
|
import { createTerminalPrompter } from "../prompt.js";
|
|
9
10
|
import { createPreviewSuccessEnvelope, createMutationSuccessEnvelope, describeCanonicalCommand, resolvePreviewJsonSchema, resolveStableMiningControlJsonSchema, writeHandledCliError, writeJsonValue, } from "../output.js";
|
|
10
11
|
import { formatNextStepLines, getMineStopNextSteps, } from "../workflow-hints.js";
|
|
11
|
-
import {
|
|
12
|
+
import { createCloseSignalWatcher, waitForCompletionOrStop } from "../signals.js";
|
|
12
13
|
import { createSyncProgressReporter } from "../sync-progress.js";
|
|
14
|
+
import { PASSIVE_UPDATE_CHECK_TIMEOUT_MS, applyUpdateCheckResult, compareSemver, createEmptyUpdateCheckCache, fetchLatestPublishedVersion, isUpdateCheckDisabled, loadUpdateCheckCache, persistUpdateCheckCache, shouldRefreshUpdateCheck, } from "../update-service.js";
|
|
13
15
|
function createCommandPrompter(parsed, context) {
|
|
14
16
|
return parsed.outputMode !== "text"
|
|
15
17
|
? createTerminalPrompter(context.stdin, context.stderr)
|
|
@@ -27,89 +29,98 @@ async function ensureMiningProviderSetup(options) {
|
|
|
27
29
|
}
|
|
28
30
|
async function syncManagedMiningReadiness(options) {
|
|
29
31
|
const ttyProgressActive = usesTtyProgress(options.parsed.progressOutput, options.context.stderr);
|
|
30
|
-
let
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
let monitor = null;
|
|
33
|
+
let observer = null;
|
|
34
|
+
const walletRoot = await resolveWalletRootIdFromLocalArtifacts({
|
|
35
|
+
paths: options.runtimePaths,
|
|
36
|
+
provider: options.provider,
|
|
37
|
+
loadRawWalletStateEnvelope: options.context.loadRawWalletStateEnvelope,
|
|
38
|
+
});
|
|
39
|
+
await options.context.ensureDirectory(dirname(options.databasePath));
|
|
40
|
+
monitor = await options.context.openManagedIndexerMonitor({
|
|
41
|
+
dataDir: options.dataDir,
|
|
42
|
+
databasePath: options.databasePath,
|
|
43
|
+
walletRootId: walletRoot.walletRootId,
|
|
44
|
+
expectedBinaryVersion: options.expectedBinaryVersion,
|
|
45
|
+
});
|
|
46
|
+
observer = new ManagedIndexerProgressObserver({
|
|
47
|
+
quoteStatePath: resolveBootstrapPathsForTesting(options.dataDir, DEFAULT_SNAPSHOT_METADATA).quoteStatePath,
|
|
48
|
+
stream: options.context.stderr,
|
|
49
|
+
progressOutput: options.parsed.progressOutput,
|
|
50
|
+
onProgress: ttyProgressActive ? undefined : createSyncProgressReporter({
|
|
51
|
+
progressOutput: options.parsed.progressOutput,
|
|
52
|
+
write: (line) => {
|
|
53
|
+
writeLine(options.context.stderr, line);
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
const abortController = new AbortController();
|
|
58
|
+
const stopWatcher = createCloseSignalWatcher({
|
|
59
|
+
signalSource: options.context.signalSource,
|
|
60
|
+
stderr: options.context.stderr,
|
|
61
|
+
closeable: {
|
|
62
|
+
close: async () => {
|
|
63
|
+
abortController.abort(new Error("managed_indexer_preflight_aborted"));
|
|
64
|
+
await observer?.close().catch(() => undefined);
|
|
65
|
+
await monitor?.close().catch(() => undefined);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
forceExit: options.context.forceExit,
|
|
69
|
+
firstMessage: "Stopping managed mining readiness observation...",
|
|
70
|
+
successMessage: "Stopped observing managed mining readiness.",
|
|
71
|
+
failureMessage: "Managed mining readiness observation cleanup failed.",
|
|
72
|
+
});
|
|
35
73
|
try {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
purpose: "managed-sync",
|
|
44
|
-
walletRootId: walletRoot.walletRootId,
|
|
45
|
-
});
|
|
74
|
+
const syncOutcome = await waitForCompletionOrStop(pollManagedIndexerUntilCaughtUp({
|
|
75
|
+
monitor,
|
|
76
|
+
observer,
|
|
77
|
+
signal: abortController.signal,
|
|
78
|
+
}), stopWatcher);
|
|
79
|
+
if (syncOutcome.kind === "stopped") {
|
|
80
|
+
return syncOutcome.code;
|
|
46
81
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
stopWatcher.cleanup();
|
|
86
|
+
await observer?.close().catch(() => undefined);
|
|
87
|
+
await monitor?.close().catch(() => undefined);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function resolveMineUpdateAvailable(currentVersion, context) {
|
|
91
|
+
if (isUpdateCheckDisabled(context.env)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const cachePath = context.resolveUpdateCheckStatePath();
|
|
96
|
+
const now = context.now();
|
|
97
|
+
let cache = await loadUpdateCheckCache(cachePath) ?? createEmptyUpdateCheckCache();
|
|
98
|
+
let cacheChanged = false;
|
|
99
|
+
if (shouldRefreshUpdateCheck(cache, now)) {
|
|
100
|
+
const updateResult = await fetchLatestPublishedVersion(context.fetchImpl, {
|
|
101
|
+
timeoutMs: PASSIVE_UPDATE_CHECK_TIMEOUT_MS,
|
|
102
|
+
});
|
|
103
|
+
cache = applyUpdateCheckResult(cache, updateResult, now);
|
|
104
|
+
cacheChanged = true;
|
|
52
105
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
client = await options.context.openManagedBitcoindClient({
|
|
56
|
-
store,
|
|
57
|
-
databasePath: options.databasePath,
|
|
58
|
-
dataDir: options.dataDir,
|
|
59
|
-
walletRootId: walletRoot.walletRootId,
|
|
60
|
-
progressOutput: options.parsed.progressOutput,
|
|
61
|
-
onProgress: ttyProgressActive ? undefined : createSyncProgressReporter({
|
|
62
|
-
progressOutput: options.parsed.progressOutput,
|
|
63
|
-
write: (line) => {
|
|
64
|
-
writeLine(options.context.stderr, line);
|
|
65
|
-
},
|
|
66
|
-
}),
|
|
67
|
-
});
|
|
68
|
-
storeOwned = false;
|
|
69
|
-
const stopWatcher = createStopSignalWatcher(options.context.signalSource, options.context.stderr, client, options.context.forceExit, [options.runtimePaths.walletControlLockPath]);
|
|
70
|
-
try {
|
|
71
|
-
const syncOutcome = await waitForCompletionOrStop(client.syncToTip(), stopWatcher);
|
|
72
|
-
if (syncOutcome.kind === "stopped") {
|
|
73
|
-
return syncOutcome.code;
|
|
74
|
-
}
|
|
75
|
-
const result = syncOutcome.value;
|
|
76
|
-
if (result.endingHeight !== null && result.endingHeight === result.bestHeight) {
|
|
77
|
-
stopWatcher.cleanup();
|
|
78
|
-
const detachPromise = typeof client.detachToBackgroundFollow === "function"
|
|
79
|
-
? client.detachToBackgroundFollow()
|
|
80
|
-
: Promise.resolve();
|
|
81
|
-
try {
|
|
82
|
-
await detachPromise;
|
|
83
|
-
await client.close();
|
|
84
|
-
clientClosed = true;
|
|
85
|
-
writeLine(options.context.stderr, "Detached cleanly; background indexer follow resumed.");
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
writeLine(options.context.stderr, "Detach failed before background indexer follow was confirmed.");
|
|
90
|
-
return 1;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
throw new Error("Managed sync did not reach the current Bitcoin tip.");
|
|
106
|
+
if (cacheChanged) {
|
|
107
|
+
await persistUpdateCheckCache(cachePath, cache);
|
|
94
108
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (!clientClosed) {
|
|
98
|
-
await client.close();
|
|
99
|
-
}
|
|
109
|
+
if (cache.latestVersion === null) {
|
|
110
|
+
return false;
|
|
100
111
|
}
|
|
112
|
+
const comparison = compareSemver(cache.latestVersion, currentVersion);
|
|
113
|
+
return comparison !== null && comparison > 0;
|
|
101
114
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await store.close().catch(() => undefined);
|
|
105
|
-
}
|
|
106
|
-
await controlLock?.release().catch(() => undefined);
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
107
117
|
}
|
|
108
118
|
}
|
|
109
119
|
export async function runMiningRuntimeCommand(parsed, context) {
|
|
110
120
|
try {
|
|
111
121
|
const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
|
|
112
122
|
const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
|
|
123
|
+
const packageVersion = await context.readPackageVersion();
|
|
113
124
|
const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
|
|
114
125
|
if (parsed.command === "mine") {
|
|
115
126
|
const prompter = context.createPrompter();
|
|
@@ -125,12 +136,16 @@ export async function runMiningRuntimeCommand(parsed, context) {
|
|
|
125
136
|
context,
|
|
126
137
|
dataDir,
|
|
127
138
|
databasePath: dbPath,
|
|
139
|
+
expectedBinaryVersion: packageVersion,
|
|
128
140
|
provider,
|
|
129
141
|
runtimePaths,
|
|
130
142
|
});
|
|
131
143
|
if (preflightCode !== null) {
|
|
132
144
|
return preflightCode;
|
|
133
145
|
}
|
|
146
|
+
const updateAvailable = usesTtyProgress(parsed.progressOutput, context.stderr)
|
|
147
|
+
? await resolveMineUpdateAvailable(packageVersion, context)
|
|
148
|
+
: false;
|
|
134
149
|
const abortController = new AbortController();
|
|
135
150
|
const onStop = () => {
|
|
136
151
|
abortController.abort();
|
|
@@ -139,6 +154,8 @@ export async function runMiningRuntimeCommand(parsed, context) {
|
|
|
139
154
|
context.signalSource.on("SIGTERM", onStop);
|
|
140
155
|
try {
|
|
141
156
|
await context.runForegroundMining({
|
|
157
|
+
clientVersion: packageVersion,
|
|
158
|
+
updateAvailable,
|
|
142
159
|
dataDir,
|
|
143
160
|
databasePath: dbPath,
|
|
144
161
|
provider,
|
|
@@ -171,6 +188,7 @@ export async function runMiningRuntimeCommand(parsed, context) {
|
|
|
171
188
|
context,
|
|
172
189
|
dataDir,
|
|
173
190
|
databasePath: dbPath,
|
|
191
|
+
expectedBinaryVersion: packageVersion,
|
|
174
192
|
provider,
|
|
175
193
|
runtimePaths,
|
|
176
194
|
});
|
|
@@ -2,6 +2,7 @@ import { dirname } from "node:path";
|
|
|
2
2
|
import { loadBundledGenesisParameters } from "@cogcoin/indexer";
|
|
3
3
|
import { resolveCogcoinProcessingStartHeight } from "../../bitcoind/processing-start-height.js";
|
|
4
4
|
import { UNINITIALIZED_WALLET_ROOT_ID, resolveManagedServicePaths } from "../../bitcoind/service-paths.js";
|
|
5
|
+
import { INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, } from "../../bitcoind/indexer-daemon.js";
|
|
5
6
|
import { resolveWalletRootIdFromLocalArtifacts, } from "../../wallet/root-resolution.js";
|
|
6
7
|
import { writeLine } from "../io.js";
|
|
7
8
|
import { createSuccessEnvelope, describeCanonicalCommand, writeHandledCliError, writeJsonValue, } from "../output.js";
|
|
@@ -86,13 +87,49 @@ async function inspectManagedBitcoindStatus(dataDir, context) {
|
|
|
86
87
|
nodeError,
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
|
-
async function inspectManagedIndexerStatus(dataDir, context) {
|
|
90
|
+
async function inspectManagedIndexerStatus(dataDir, databasePath, expectedBinaryVersion, context) {
|
|
90
91
|
const resolution = await resolveEffectiveWalletRootId(context);
|
|
91
92
|
const runtimeRoot = resolveManagedServicePaths(dataDir, resolution.walletRootId).walletRuntimeRoot;
|
|
92
93
|
const probe = await context.probeIndexerDaemon({
|
|
93
94
|
dataDir,
|
|
94
95
|
walletRootId: resolution.walletRootId,
|
|
95
96
|
});
|
|
97
|
+
await probe.client?.close().catch(() => undefined);
|
|
98
|
+
if (probe.compatibility === "compatible" || probe.compatibility === "unreachable") {
|
|
99
|
+
try {
|
|
100
|
+
const daemonClient = await context.attachIndexerDaemon({
|
|
101
|
+
dataDir,
|
|
102
|
+
databasePath,
|
|
103
|
+
walletRootId: resolution.walletRootId,
|
|
104
|
+
ensureBackgroundFollow: true,
|
|
105
|
+
expectedBinaryVersion,
|
|
106
|
+
});
|
|
107
|
+
try {
|
|
108
|
+
const daemon = await daemonClient.getStatus();
|
|
109
|
+
return {
|
|
110
|
+
dataDir,
|
|
111
|
+
walletRootId: resolution.walletRootId,
|
|
112
|
+
walletRootSource: resolution.source,
|
|
113
|
+
compatibility: "compatible",
|
|
114
|
+
source: "probe",
|
|
115
|
+
daemon: {
|
|
116
|
+
...daemon,
|
|
117
|
+
runtimeRoot,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
await daemonClient.close().catch(() => undefined);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error instanceof Error
|
|
127
|
+
&& error.message === INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED) {
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
// Preserve the status-file fallback when the refresh path fails.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
96
133
|
let source = "probe";
|
|
97
134
|
let daemon = probe.status;
|
|
98
135
|
if (probe.compatibility === "unreachable") {
|
|
@@ -291,7 +328,10 @@ export async function runServiceRuntimeCommand(parsed, context) {
|
|
|
291
328
|
return 0;
|
|
292
329
|
}
|
|
293
330
|
if (parsed.command === "indexer-status") {
|
|
294
|
-
const
|
|
331
|
+
const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
|
|
332
|
+
const packageVersion = await context.readPackageVersion();
|
|
333
|
+
await context.ensureDirectory(dirname(dbPath));
|
|
334
|
+
const payload = await inspectManagedIndexerStatus(dataDir, dbPath, packageVersion, context);
|
|
295
335
|
const messages = buildStatusMessages(payload);
|
|
296
336
|
if (parsed.outputMode === "json") {
|
|
297
337
|
writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/indexer-status/v1", describeCanonicalCommand(parsed), payload, messages));
|
|
@@ -8,6 +8,7 @@ import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider
|
|
|
8
8
|
export async function runStatusCommand(parsed, context) {
|
|
9
9
|
const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
|
|
10
10
|
const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
|
|
11
|
+
const packageVersion = await context.readPackageVersion();
|
|
11
12
|
const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
|
|
12
13
|
await context.ensureDirectory(dirname(dbPath));
|
|
13
14
|
const provider = parsed.outputMode === "text"
|
|
@@ -17,6 +18,7 @@ export async function runStatusCommand(parsed, context) {
|
|
|
17
18
|
dataDir,
|
|
18
19
|
databasePath: dbPath,
|
|
19
20
|
secretProvider: provider,
|
|
21
|
+
expectedIndexerBinaryVersion: packageVersion,
|
|
20
22
|
paths: runtimePaths,
|
|
21
23
|
});
|
|
22
24
|
try {
|
|
@@ -29,7 +31,7 @@ export async function runStatusCommand(parsed, context) {
|
|
|
29
31
|
}));
|
|
30
32
|
return 0;
|
|
31
33
|
}
|
|
32
|
-
writeLine(context.stdout, formatWalletOverviewReport(readContext,
|
|
34
|
+
writeLine(context.stdout, formatWalletOverviewReport(readContext, packageVersion));
|
|
33
35
|
writeLine(context.stdout, formatBalanceReport(readContext));
|
|
34
36
|
return 0;
|
|
35
37
|
}
|