@cogcoin/client 0.5.5 → 0.5.7

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 (91) hide show
  1. package/README.md +11 -2
  2. package/dist/bitcoind/bootstrap/chainstate.d.ts +2 -1
  3. package/dist/bitcoind/bootstrap/chainstate.js +4 -1
  4. package/dist/bitcoind/bootstrap/controller.d.ts +4 -1
  5. package/dist/bitcoind/bootstrap/controller.js +42 -5
  6. package/dist/bitcoind/bootstrap/getblock-archive.d.ts +39 -0
  7. package/dist/bitcoind/bootstrap/getblock-archive.js +548 -0
  8. package/dist/bitcoind/bootstrap/headers.d.ts +12 -0
  9. package/dist/bitcoind/bootstrap/headers.js +95 -10
  10. package/dist/bitcoind/bootstrap.d.ts +1 -0
  11. package/dist/bitcoind/bootstrap.js +1 -0
  12. package/dist/bitcoind/client/factory.js +91 -28
  13. package/dist/bitcoind/client/managed-client.d.ts +1 -1
  14. package/dist/bitcoind/client/managed-client.js +4 -3
  15. package/dist/bitcoind/client/sync-engine.js +55 -13
  16. package/dist/bitcoind/errors.js +18 -0
  17. package/dist/bitcoind/indexer-daemon-main.js +78 -0
  18. package/dist/bitcoind/indexer-daemon.d.ts +10 -1
  19. package/dist/bitcoind/indexer-daemon.js +44 -28
  20. package/dist/bitcoind/node.js +2 -0
  21. package/dist/bitcoind/processing-start-height.d.ts +7 -0
  22. package/dist/bitcoind/processing-start-height.js +9 -0
  23. package/dist/bitcoind/progress/constants.d.ts +1 -0
  24. package/dist/bitcoind/progress/constants.js +1 -0
  25. package/dist/bitcoind/progress/controller.d.ts +22 -0
  26. package/dist/bitcoind/progress/controller.js +49 -23
  27. package/dist/bitcoind/progress/formatting.js +29 -1
  28. package/dist/bitcoind/progress/render-policy.d.ts +35 -0
  29. package/dist/bitcoind/progress/render-policy.js +81 -0
  30. package/dist/bitcoind/retryable-rpc.d.ts +11 -0
  31. package/dist/bitcoind/retryable-rpc.js +30 -0
  32. package/dist/bitcoind/service-paths.js +2 -6
  33. package/dist/bitcoind/service.d.ts +21 -2
  34. package/dist/bitcoind/service.js +274 -122
  35. package/dist/bitcoind/testing.d.ts +2 -2
  36. package/dist/bitcoind/testing.js +2 -2
  37. package/dist/bitcoind/types.d.ts +36 -1
  38. package/dist/cli/commands/follow.js +11 -0
  39. package/dist/cli/commands/getblock-archive-restart.d.ts +5 -0
  40. package/dist/cli/commands/getblock-archive-restart.js +15 -0
  41. package/dist/cli/commands/mining-admin.js +4 -0
  42. package/dist/cli/commands/mining-read.js +8 -5
  43. package/dist/cli/commands/mining-runtime.js +4 -0
  44. package/dist/cli/commands/service-runtime.js +150 -134
  45. package/dist/cli/commands/status.js +2 -0
  46. package/dist/cli/commands/sync.js +11 -0
  47. package/dist/cli/commands/wallet-admin.js +106 -24
  48. package/dist/cli/commands/wallet-mutation.js +57 -4
  49. package/dist/cli/commands/wallet-read.js +2 -0
  50. package/dist/cli/context.js +8 -4
  51. package/dist/cli/mutation-command-groups.d.ts +2 -1
  52. package/dist/cli/mutation-command-groups.js +5 -0
  53. package/dist/cli/mutation-json.d.ts +18 -2
  54. package/dist/cli/mutation-json.js +49 -0
  55. package/dist/cli/mutation-success.d.ts +1 -0
  56. package/dist/cli/mutation-success.js +2 -2
  57. package/dist/cli/output.js +86 -1
  58. package/dist/cli/parse.d.ts +1 -1
  59. package/dist/cli/parse.js +133 -3
  60. package/dist/cli/preview-json.d.ts +10 -1
  61. package/dist/cli/preview-json.js +32 -0
  62. package/dist/cli/prompt.js +1 -1
  63. package/dist/cli/runner.js +4 -0
  64. package/dist/cli/types.d.ts +15 -5
  65. package/dist/cli/types.js +1 -1
  66. package/dist/cli/wallet-format.js +140 -14
  67. package/dist/wallet/lifecycle.d.ts +21 -1
  68. package/dist/wallet/lifecycle.js +252 -116
  69. package/dist/wallet/mining/visualizer.d.ts +11 -6
  70. package/dist/wallet/mining/visualizer.js +32 -15
  71. package/dist/wallet/read/context.js +10 -4
  72. package/dist/wallet/reset.d.ts +61 -2
  73. package/dist/wallet/reset.js +246 -89
  74. package/dist/wallet/root-resolution.d.ts +20 -0
  75. package/dist/wallet/root-resolution.js +37 -0
  76. package/dist/wallet/runtime.d.ts +13 -1
  77. package/dist/wallet/runtime.js +54 -11
  78. package/dist/wallet/state/crypto.d.ts +3 -0
  79. package/dist/wallet/state/crypto.js +3 -0
  80. package/dist/wallet/state/provider.d.ts +1 -0
  81. package/dist/wallet/state/provider.js +119 -3
  82. package/dist/wallet/state/seed-index.d.ts +43 -0
  83. package/dist/wallet/state/seed-index.js +151 -0
  84. package/dist/wallet/state/storage.d.ts +7 -1
  85. package/dist/wallet/state/storage.js +39 -0
  86. package/dist/wallet/tx/anchor.d.ts +22 -0
  87. package/dist/wallet/tx/anchor.js +215 -8
  88. package/dist/wallet/tx/index.d.ts +1 -1
  89. package/dist/wallet/tx/index.js +1 -1
  90. package/dist/wallet/types.d.ts +1 -0
  91. package/package.json +1 -1
@@ -1,10 +1,19 @@
1
1
  import { dirname } from "node:path";
2
+ import { resolveWalletRootIdFromLocalArtifacts } from "../../wallet/root-resolution.js";
2
3
  import { usesTtyProgress, writeLine } from "../io.js";
3
4
  import { classifyCliError } from "../output.js";
4
5
  import { createStopSignalWatcher } from "../signals.js";
6
+ import { confirmGetblockArchiveRestart } from "./getblock-archive-restart.js";
5
7
  export async function runFollowCommand(parsed, context) {
6
8
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
7
9
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
10
+ const walletRoot = await resolveWalletRootIdFromLocalArtifacts({
11
+ paths: context.resolveWalletRuntimePaths(),
12
+ provider: context.walletSecretProvider,
13
+ loadRawWalletStateEnvelope: context.loadRawWalletStateEnvelope,
14
+ loadUnlockSession: context.loadUnlockSession,
15
+ loadWalletExplicitLock: context.loadWalletExplicitLock,
16
+ });
8
17
  await context.ensureDirectory(dirname(dbPath));
9
18
  const store = await context.openSqliteStore({ filename: dbPath });
10
19
  let storeOwned = true;
@@ -13,7 +22,9 @@ export async function runFollowCommand(parsed, context) {
13
22
  store,
14
23
  databasePath: dbPath,
15
24
  dataDir,
25
+ walletRootId: walletRoot.walletRootId,
16
26
  progressOutput: parsed.progressOutput,
27
+ confirmGetblockArchiveRestart: async (options) => confirmGetblockArchiveRestart(parsed, context, options),
17
28
  });
18
29
  storeOwned = false;
19
30
  const stopWatcher = createStopSignalWatcher(context.signalSource, context.stderr, client, context.forceExit);
@@ -0,0 +1,5 @@
1
+ import type { ParsedCliArgs, RequiredCliRunnerContext } from "../types.js";
2
+ export declare function confirmGetblockArchiveRestart(parsed: ParsedCliArgs, context: RequiredCliRunnerContext, options: {
3
+ currentArchiveEndHeight: number | null;
4
+ nextArchiveEndHeight: number;
5
+ }): Promise<boolean>;
@@ -0,0 +1,15 @@
1
+ export async function confirmGetblockArchiveRestart(parsed, context, options) {
2
+ if (parsed.assumeYes) {
3
+ return true;
4
+ }
5
+ const prompter = context.createPrompter();
6
+ if (!prompter.isInteractive) {
7
+ return false;
8
+ }
9
+ const currentLabel = options.currentArchiveEndHeight === null
10
+ ? "without a getblock archive"
11
+ : `with a getblock archive through height ${options.currentArchiveEndHeight.toLocaleString()}`;
12
+ prompter.writeLine(`Managed bitcoind is already running ${currentLabel}. A newer getblock archive through height ${options.nextArchiveEndHeight.toLocaleString()} is available.`);
13
+ const answer = (await prompter.prompt(`Restart managed bitcoind to load the getblock archive through height ${options.nextArchiveEndHeight.toLocaleString()}? [y/N]: `)).trim().toLowerCase();
14
+ return answer === "y" || answer === "yes";
15
+ }
@@ -12,11 +12,13 @@ function createCommandPrompter(parsed, context) {
12
12
  export async function runMiningAdminCommand(parsed, context) {
13
13
  try {
14
14
  const provider = context.walletSecretProvider;
15
+ const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
15
16
  if (parsed.command === "hooks-mining-enable") {
16
17
  const prompter = createCommandPrompter(parsed, context);
17
18
  const view = await context.enableMiningHooks({
18
19
  provider,
19
20
  prompter,
21
+ paths: runtimePaths,
20
22
  });
21
23
  const nextSteps = getHooksEnableMiningNextSteps();
22
24
  if (parsed.outputMode === "preview-json") {
@@ -40,6 +42,7 @@ export async function runMiningAdminCommand(parsed, context) {
40
42
  if (parsed.command === "hooks-mining-disable") {
41
43
  const view = await context.disableMiningHooks({
42
44
  provider,
45
+ paths: runtimePaths,
43
46
  });
44
47
  if (parsed.outputMode === "preview-json") {
45
48
  writeJsonValue(context.stdout, createPreviewSuccessEnvelope(resolvePreviewJsonSchema(parsed), describeCanonicalCommand(parsed), "disabled", buildHooksPreviewData("hooks-disable-mining", view)));
@@ -57,6 +60,7 @@ export async function runMiningAdminCommand(parsed, context) {
57
60
  const view = await context.setupBuiltInMining({
58
61
  provider,
59
62
  prompter,
63
+ paths: runtimePaths,
60
64
  });
61
65
  const nextSteps = getMineSetupNextSteps();
62
66
  if (parsed.outputMode === "preview-json") {
@@ -5,9 +5,7 @@ import { writeLine } from "../io.js";
5
5
  import { inspectWalletLocalState } from "../../wallet/read/index.js";
6
6
  import { createErrorEnvelope, createSuccessEnvelope, describeCanonicalCommand, normalizeListPage, writeJsonValue, } from "../output.js";
7
7
  import { buildHooksStatusJson, buildMineLogJson, buildMineStatusJson } from "../read-json.js";
8
- import { resolveWalletRuntimePathsForTesting } from "../../wallet/runtime.js";
9
- async function readRotationIndices() {
10
- const paths = resolveWalletRuntimePathsForTesting();
8
+ async function readRotationIndices(paths) {
11
9
  const rotation = [];
12
10
  for (let index = 1; index <= 4; index += 1) {
13
11
  try {
@@ -26,10 +24,12 @@ export async function runMiningReadCommand(parsed, context) {
26
24
  try {
27
25
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
28
26
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
27
+ const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
29
28
  await context.ensureDirectory(dirname(dbPath));
30
29
  if (parsed.command === "hooks-mining-status") {
31
30
  const localState = await inspectWalletLocalState({
32
31
  secretProvider: context.walletSecretProvider,
32
+ paths: runtimePaths,
33
33
  });
34
34
  const view = await context.inspectMiningControlPlane({
35
35
  provider: context.walletSecretProvider,
@@ -52,6 +52,7 @@ export async function runMiningReadCommand(parsed, context) {
52
52
  openedAtUnixMs: null,
53
53
  },
54
54
  verify: parsed.verify,
55
+ paths: runtimePaths,
55
56
  });
56
57
  if (parsed.outputMode === "json") {
57
58
  const result = buildHooksStatusJson(view);
@@ -79,7 +80,7 @@ export async function runMiningReadCommand(parsed, context) {
79
80
  const events = normalized.items.slice().reverse();
80
81
  if (events.length === 0) {
81
82
  if (parsed.outputMode === "json") {
82
- const result = buildMineLogJson(events, normalized.page, await readRotationIndices());
83
+ const result = buildMineLogJson(events, normalized.page, await readRotationIndices(runtimePaths));
83
84
  writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/mine-log/v1", describeCanonicalCommand(parsed), result.data, {
84
85
  warnings: result.warnings,
85
86
  explanations: result.explanations,
@@ -91,7 +92,7 @@ export async function runMiningReadCommand(parsed, context) {
91
92
  return 0;
92
93
  }
93
94
  if (parsed.outputMode === "json") {
94
- const result = buildMineLogJson(events, normalized.page, await readRotationIndices());
95
+ const result = buildMineLogJson(events, normalized.page, await readRotationIndices(runtimePaths));
95
96
  writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/mine-log/v1", describeCanonicalCommand(parsed), result.data, {
96
97
  warnings: result.warnings,
97
98
  explanations: result.explanations,
@@ -131,6 +132,7 @@ export async function runMiningReadCommand(parsed, context) {
131
132
  dataDir,
132
133
  databasePath: dbPath,
133
134
  secretProvider: context.walletSecretProvider,
135
+ paths: runtimePaths,
134
136
  });
135
137
  try {
136
138
  const mining = readContext.mining ?? await context.inspectMiningControlPlane({
@@ -140,6 +142,7 @@ export async function runMiningReadCommand(parsed, context) {
140
142
  nodeStatus: readContext.nodeStatus,
141
143
  nodeHealth: readContext.nodeHealth,
142
144
  indexer: readContext.indexer,
145
+ paths: runtimePaths,
143
146
  });
144
147
  if (parsed.outputMode === "json") {
145
148
  const result = buildMineStatusJson(mining);
@@ -15,6 +15,7 @@ export async function runMiningRuntimeCommand(parsed, context) {
15
15
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
16
16
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
17
17
  const provider = context.walletSecretProvider;
18
+ const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
18
19
  await context.ensureDirectory(dirname(dbPath));
19
20
  if (parsed.command === "mine") {
20
21
  const abortController = new AbortController();
@@ -33,6 +34,7 @@ export async function runMiningRuntimeCommand(parsed, context) {
33
34
  stdout: context.stdout,
34
35
  stderr: context.stderr,
35
36
  progressOutput: parsed.progressOutput,
37
+ paths: runtimePaths,
36
38
  });
37
39
  }
38
40
  finally {
@@ -47,6 +49,7 @@ export async function runMiningRuntimeCommand(parsed, context) {
47
49
  databasePath: dbPath,
48
50
  provider,
49
51
  prompter: createCommandPrompter(parsed, context),
52
+ paths: runtimePaths,
50
53
  });
51
54
  if (parsed.outputMode === "preview-json") {
52
55
  writeJsonValue(context.stdout, createPreviewSuccessEnvelope(resolvePreviewJsonSchema(parsed), describeCanonicalCommand(parsed), result.started ? "started" : "already-active", buildMineStartPreviewData(result)));
@@ -74,6 +77,7 @@ export async function runMiningRuntimeCommand(parsed, context) {
74
77
  dataDir,
75
78
  databasePath: dbPath,
76
79
  provider,
80
+ paths: runtimePaths,
77
81
  });
78
82
  const nextSteps = getMineStopNextSteps();
79
83
  if (parsed.outputMode === "preview-json") {
@@ -1,6 +1,8 @@
1
1
  import { dirname } from "node:path";
2
2
  import { loadBundledGenesisParameters } from "@cogcoin/indexer";
3
+ import { resolveCogcoinProcessingStartHeight } from "../../bitcoind/processing-start-height.js";
3
4
  import { UNINITIALIZED_WALLET_ROOT_ID, resolveManagedServicePaths } from "../../bitcoind/service-paths.js";
5
+ import { resolveWalletRootIdFromLocalArtifacts, } from "../../wallet/root-resolution.js";
4
6
  import { writeLine } from "../io.js";
5
7
  import { createSuccessEnvelope, describeCanonicalCommand, writeHandledCliError, writeJsonValue, } from "../output.js";
6
8
  function formatBool(value) {
@@ -12,66 +14,39 @@ function formatMaybe(value) {
12
14
  function formatCompatibility(value) {
13
15
  return value.replaceAll("-", " ");
14
16
  }
15
- async function resolveEffectiveWalletRootId(dataDir, context) {
16
- const paths = context.resolveWalletRuntimePaths();
17
- try {
18
- const loaded = await context.loadWalletState({
19
- primaryPath: paths.walletStatePath,
20
- backupPath: paths.walletStateBackupPath,
21
- }, {
22
- provider: context.walletSecretProvider,
23
- });
24
- return {
25
- walletRootId: loaded.state.walletRootId,
26
- source: "wallet-state",
27
- };
28
- }
29
- catch {
30
- // fall through
31
- }
32
- try {
33
- const unlockSession = await context.loadUnlockSession(paths.walletUnlockSessionPath, {
34
- provider: context.walletSecretProvider,
35
- });
36
- return {
37
- walletRootId: unlockSession.walletRootId,
38
- source: "unlock-session",
39
- };
40
- }
41
- catch {
42
- // fall through
43
- }
44
- try {
45
- const explicitLock = await context.loadWalletExplicitLock(paths.walletExplicitLockPath);
46
- if (explicitLock?.walletRootId) {
47
- return {
48
- walletRootId: explicitLock.walletRootId,
49
- source: "explicit-lock",
50
- };
51
- }
52
- }
53
- catch {
54
- // fall through
55
- }
56
- const fallbackProbe = await context.probeManagedBitcoindService({
57
- dataDir,
58
- chain: "main",
59
- startHeight: 0,
60
- walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
61
- });
62
- if (fallbackProbe.status?.walletRootId) {
63
- return {
64
- walletRootId: fallbackProbe.status.walletRootId,
65
- source: "bitcoind-status",
66
- };
67
- }
17
+ function serviceStatusEntry(label, value, ok) {
68
18
  return {
19
+ text: `${label}: ${value}`,
20
+ ok,
21
+ };
22
+ }
23
+ function formatServiceStatusSection(header, entries) {
24
+ return [header, ...entries.map((entry) => `${entry.ok ? "✓" : "✗"} ${entry.text}`)].join("\n");
25
+ }
26
+ function formatSectionedServiceStatusReport(options) {
27
+ const parts = [
28
+ `\n⛭ ${options.title} ⛭`,
29
+ ...options.sections.map((section) => formatServiceStatusSection(section.header, section.entries)),
30
+ ];
31
+ if (options.nextStep !== null) {
32
+ parts.push(`Next step: ${options.nextStep}`);
33
+ }
34
+ return parts.join("\n\n");
35
+ }
36
+ async function resolveEffectiveWalletRootId(context) {
37
+ return resolveWalletRootIdFromLocalArtifacts({
38
+ paths: context.resolveWalletRuntimePaths(),
39
+ provider: context.walletSecretProvider,
40
+ loadRawWalletStateEnvelope: context.loadRawWalletStateEnvelope,
41
+ loadUnlockSession: context.loadUnlockSession,
42
+ loadWalletExplicitLock: context.loadWalletExplicitLock,
43
+ }).catch(() => ({
69
44
  walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
70
45
  source: "default-uninitialized",
71
- };
46
+ }));
72
47
  }
73
48
  async function inspectManagedBitcoindStatus(dataDir, context) {
74
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
49
+ const resolution = await resolveEffectiveWalletRootId(context);
75
50
  const probe = await context.probeManagedBitcoindService({
76
51
  dataDir,
77
52
  chain: "main",
@@ -114,7 +89,7 @@ async function inspectManagedBitcoindStatus(dataDir, context) {
114
89
  };
115
90
  }
116
91
  async function inspectManagedIndexerStatus(dataDir, context) {
117
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
92
+ const resolution = await resolveEffectiveWalletRootId(context);
118
93
  const runtimeRoot = resolveManagedServicePaths(dataDir, resolution.walletRootId).walletRuntimeRoot;
119
94
  const probe = await context.probeIndexerDaemon({
120
95
  dataDir,
@@ -144,96 +119,137 @@ async function inspectManagedIndexerStatus(dataDir, context) {
144
119
  };
145
120
  }
146
121
  function formatBitcoinStatusReport(payload) {
147
- const lines = [
148
- "Managed Bitcoind Status",
149
- `Bitcoin datadir: ${payload.dataDir}`,
150
- `Wallet root: ${payload.walletRootId}`,
151
- `Wallet root source: ${payload.walletRootSource}`,
152
- `Compatibility: ${formatCompatibility(payload.compatibility)}`,
122
+ const compatibilityOk = payload.compatibility === "compatible";
123
+ const serviceStateOk = payload.service?.state === "ready";
124
+ const nodeOk = payload.node !== null && payload.nodeError === null;
125
+ const managedServiceEntries = [
126
+ serviceStatusEntry("Compatibility", formatCompatibility(payload.compatibility), compatibilityOk),
153
127
  ];
154
128
  if (payload.service !== null) {
155
- lines.push(`Service state: ${payload.service.state}`);
156
- lines.push(`Process id: ${formatMaybe(payload.service.processId)}`);
157
- lines.push(`Service instance: ${payload.service.serviceInstanceId}`);
158
- lines.push(`Runtime root: ${payload.service.runtimeRoot}`);
159
- lines.push(`Chain: ${payload.service.chain}`);
160
- lines.push(`RPC: ${payload.service.rpc.url}`);
161
- lines.push(`RPC cookie: ${payload.service.rpc.cookieFile}`);
162
- lines.push(`ZMQ: ${payload.service.zmq.endpoint}`);
163
- lines.push(`P2P port: ${payload.service.p2pPort}`);
164
- lines.push(`Started at: ${payload.service.startedAtUnixMs}`);
165
- lines.push(`Heartbeat at: ${payload.service.heartbeatAtUnixMs}`);
166
- lines.push(`Updated at: ${payload.service.updatedAtUnixMs}`);
167
- lines.push(`Managed Core wallet: ${payload.service.walletReplica?.proofStatus ?? "unavailable"}`);
129
+ managedServiceEntries.push(serviceStatusEntry("Service state", payload.service.state, serviceStateOk));
130
+ managedServiceEntries.push(serviceStatusEntry("Process id", formatMaybe(payload.service.processId), serviceStateOk));
131
+ managedServiceEntries.push(serviceStatusEntry("Service instance", payload.service.serviceInstanceId, serviceStateOk));
132
+ managedServiceEntries.push(serviceStatusEntry("Runtime root", payload.service.runtimeRoot, serviceStateOk));
133
+ managedServiceEntries.push(serviceStatusEntry("Chain", payload.service.chain, serviceStateOk));
134
+ managedServiceEntries.push(serviceStatusEntry("RPC", payload.service.rpc.url, serviceStateOk));
135
+ managedServiceEntries.push(serviceStatusEntry("RPC cookie", payload.service.rpc.cookieFile, serviceStateOk));
136
+ managedServiceEntries.push(serviceStatusEntry("ZMQ", payload.service.zmq.endpoint, serviceStateOk));
137
+ managedServiceEntries.push(serviceStatusEntry("P2P port", String(payload.service.p2pPort), serviceStateOk));
138
+ managedServiceEntries.push(serviceStatusEntry("Started at", String(payload.service.startedAtUnixMs), serviceStateOk));
139
+ managedServiceEntries.push(serviceStatusEntry("Heartbeat at", String(payload.service.heartbeatAtUnixMs), serviceStateOk));
140
+ managedServiceEntries.push(serviceStatusEntry("Updated at", String(payload.service.updatedAtUnixMs), serviceStateOk));
141
+ managedServiceEntries.push(serviceStatusEntry("Managed Core wallet", payload.service.walletReplica?.proofStatus ?? "unavailable", payload.service.walletReplica?.proofStatus === "ready"));
168
142
  if (payload.service.lastError !== null) {
169
- lines.push(`Service error: ${payload.service.lastError}`);
143
+ managedServiceEntries.push(serviceStatusEntry("Service error", payload.service.lastError, false));
170
144
  }
171
145
  }
172
146
  else {
173
- lines.push("Service state: unavailable");
174
- }
175
- if (payload.node !== null) {
176
- lines.push(`Bitcoin best height: ${payload.node.bestHeight}`);
177
- lines.push(`Bitcoin headers: ${payload.node.headerHeight}`);
178
- lines.push(`Bitcoin best hash: ${payload.node.bestHash}`);
179
- lines.push(`Verification progress: ${formatMaybe(payload.node.verificationProgress)}`);
180
- lines.push(`Initial block download: ${formatBool(payload.node.initialBlockDownload)}`);
181
- lines.push(`Network active: ${formatBool(payload.node.networkActive)}`);
182
- lines.push(`Connections: ${payload.node.connections}`);
183
- lines.push(`Inbound connections: ${formatMaybe(payload.node.inboundConnections)}`);
184
- lines.push(`Outbound connections: ${formatMaybe(payload.node.outboundConnections)}`);
185
- }
186
- else {
187
- lines.push("Bitcoin node: unavailable");
147
+ managedServiceEntries.push(serviceStatusEntry("Service state", "unavailable", false));
188
148
  }
149
+ const bitcoinNodeEntries = payload.node !== null
150
+ ? [
151
+ serviceStatusEntry("Best height", String(payload.node.bestHeight), nodeOk),
152
+ serviceStatusEntry("Headers", String(payload.node.headerHeight), nodeOk),
153
+ serviceStatusEntry("Best hash", payload.node.bestHash, nodeOk),
154
+ serviceStatusEntry("Verification progress", formatMaybe(payload.node.verificationProgress), nodeOk),
155
+ serviceStatusEntry("Initial block download", formatBool(payload.node.initialBlockDownload), nodeOk),
156
+ serviceStatusEntry("Network active", formatBool(payload.node.networkActive), nodeOk),
157
+ serviceStatusEntry("Connections", String(payload.node.connections), nodeOk),
158
+ serviceStatusEntry("Inbound connections", formatMaybe(payload.node.inboundConnections), nodeOk),
159
+ serviceStatusEntry("Outbound connections", formatMaybe(payload.node.outboundConnections), nodeOk),
160
+ ]
161
+ : [serviceStatusEntry("Node state", "unavailable", false)];
189
162
  if (payload.nodeError !== null) {
190
- lines.push(`Node error: ${payload.nodeError}`);
191
- }
192
- if (payload.compatibility === "unreachable") {
193
- lines.push("Recommended next step: Run `cogcoin bitcoin start` to start the managed Bitcoin service.");
163
+ bitcoinNodeEntries.push(serviceStatusEntry("Node error", payload.nodeError, false));
194
164
  }
195
- return `${lines.join("\n")}\n`;
165
+ return formatSectionedServiceStatusReport({
166
+ title: "Bitcoin Status",
167
+ sections: [
168
+ {
169
+ header: "Paths",
170
+ entries: [
171
+ serviceStatusEntry("Bitcoin datadir", payload.dataDir, true),
172
+ serviceStatusEntry("Wallet root", payload.walletRootId, true),
173
+ serviceStatusEntry("Wallet root source", payload.walletRootSource, true),
174
+ ],
175
+ },
176
+ {
177
+ header: "Managed Service",
178
+ entries: managedServiceEntries,
179
+ },
180
+ {
181
+ header: "Bitcoin Node",
182
+ entries: bitcoinNodeEntries,
183
+ },
184
+ ],
185
+ nextStep: payload.compatibility === "unreachable"
186
+ ? "Run `cogcoin bitcoin start` to start the managed Bitcoin service."
187
+ : null,
188
+ });
196
189
  }
197
190
  function formatIndexerStatusReport(payload) {
198
- const lines = [
199
- "Managed Indexer Status",
200
- `Bitcoin datadir: ${payload.dataDir}`,
201
- `Wallet root: ${payload.walletRootId}`,
202
- `Wallet root source: ${payload.walletRootSource}`,
203
- `Compatibility: ${formatCompatibility(payload.compatibility)}`,
204
- `Observed source: ${payload.source}`,
191
+ const compatibilityOk = payload.compatibility === "compatible";
192
+ const observedSourceOk = payload.source === "probe";
193
+ const daemonStateOk = payload.daemon?.state === "synced";
194
+ const managedServiceEntries = [
195
+ serviceStatusEntry("Compatibility", formatCompatibility(payload.compatibility), compatibilityOk),
196
+ serviceStatusEntry("Observed source", payload.source, observedSourceOk),
205
197
  ];
206
198
  if (payload.daemon !== null) {
207
- lines.push(`Daemon state: ${payload.daemon.state}`);
208
- lines.push(`Process id: ${formatMaybe(payload.daemon.processId)}`);
209
- lines.push(`Daemon instance: ${payload.daemon.daemonInstanceId}`);
210
- lines.push(`Runtime root: ${payload.daemon.runtimeRoot}`);
211
- lines.push(`Schema version: ${payload.daemon.schemaVersion}`);
212
- lines.push(`Started at: ${payload.daemon.startedAtUnixMs}`);
213
- lines.push(`Heartbeat at: ${payload.daemon.heartbeatAtUnixMs}`);
214
- lines.push(`Updated at: ${payload.daemon.updatedAtUnixMs}`);
215
- lines.push(`IPC ready: ${formatBool(payload.daemon.ipcReady)}`);
216
- lines.push(`RPC reachable: ${formatBool(payload.daemon.rpcReachable)}`);
217
- lines.push(`Core best height: ${formatMaybe(payload.daemon.coreBestHeight)}`);
218
- lines.push(`Core best hash: ${formatMaybe(payload.daemon.coreBestHash)}`);
219
- lines.push(`Applied tip height: ${formatMaybe(payload.daemon.appliedTipHeight)}`);
220
- lines.push(`Applied tip hash: ${formatMaybe(payload.daemon.appliedTipHash)}`);
221
- lines.push(`Snapshot sequence: ${formatMaybe(payload.daemon.snapshotSeq)}`);
222
- lines.push(`Backlog blocks: ${formatMaybe(payload.daemon.backlogBlocks)}`);
223
- lines.push(`Reorg depth: ${formatMaybe(payload.daemon.reorgDepth)}`);
224
- lines.push(`Active snapshots: ${payload.daemon.activeSnapshotCount}`);
225
- lines.push(`Last applied at: ${formatMaybe(payload.daemon.lastAppliedAtUnixMs)}`);
199
+ managedServiceEntries.push(serviceStatusEntry("Daemon state", payload.daemon.state, daemonStateOk));
200
+ managedServiceEntries.push(serviceStatusEntry("Process id", formatMaybe(payload.daemon.processId), daemonStateOk));
201
+ managedServiceEntries.push(serviceStatusEntry("Daemon instance", payload.daemon.daemonInstanceId, daemonStateOk));
202
+ managedServiceEntries.push(serviceStatusEntry("Runtime root", payload.daemon.runtimeRoot, daemonStateOk));
203
+ managedServiceEntries.push(serviceStatusEntry("Schema version", payload.daemon.schemaVersion, daemonStateOk));
204
+ managedServiceEntries.push(serviceStatusEntry("Started at", String(payload.daemon.startedAtUnixMs), daemonStateOk));
205
+ managedServiceEntries.push(serviceStatusEntry("Heartbeat at", String(payload.daemon.heartbeatAtUnixMs), daemonStateOk));
206
+ managedServiceEntries.push(serviceStatusEntry("Updated at", String(payload.daemon.updatedAtUnixMs), daemonStateOk));
207
+ managedServiceEntries.push(serviceStatusEntry("IPC ready", formatBool(payload.daemon.ipcReady), payload.daemon.ipcReady));
208
+ managedServiceEntries.push(serviceStatusEntry("RPC reachable", formatBool(payload.daemon.rpcReachable), payload.daemon.rpcReachable));
226
209
  if (payload.daemon.lastError !== null) {
227
- lines.push(`Daemon error: ${payload.daemon.lastError}`);
210
+ managedServiceEntries.push(serviceStatusEntry("Daemon error", payload.daemon.lastError, false));
228
211
  }
229
212
  }
230
213
  else {
231
- lines.push("Daemon state: unavailable");
232
- }
233
- if (payload.compatibility === "unreachable") {
234
- lines.push("Recommended next step: Run `cogcoin indexer start` to start the managed Cogcoin indexer.");
214
+ managedServiceEntries.push(serviceStatusEntry("Daemon state", "unavailable", false));
235
215
  }
236
- return `${lines.join("\n")}\n`;
216
+ const indexerStateEntries = payload.daemon !== null
217
+ ? [
218
+ serviceStatusEntry("Core best height", formatMaybe(payload.daemon.coreBestHeight), daemonStateOk),
219
+ serviceStatusEntry("Core best hash", formatMaybe(payload.daemon.coreBestHash), daemonStateOk),
220
+ serviceStatusEntry("Applied tip height", formatMaybe(payload.daemon.appliedTipHeight), daemonStateOk),
221
+ serviceStatusEntry("Applied tip hash", formatMaybe(payload.daemon.appliedTipHash), daemonStateOk),
222
+ serviceStatusEntry("Snapshot sequence", formatMaybe(payload.daemon.snapshotSeq), daemonStateOk),
223
+ serviceStatusEntry("Backlog blocks", formatMaybe(payload.daemon.backlogBlocks), daemonStateOk),
224
+ serviceStatusEntry("Reorg depth", formatMaybe(payload.daemon.reorgDepth), daemonStateOk),
225
+ serviceStatusEntry("Active snapshots", String(payload.daemon.activeSnapshotCount), daemonStateOk),
226
+ serviceStatusEntry("Last applied at", formatMaybe(payload.daemon.lastAppliedAtUnixMs), daemonStateOk),
227
+ ]
228
+ : [serviceStatusEntry("Daemon state", "unavailable", false)];
229
+ return formatSectionedServiceStatusReport({
230
+ title: "Indexer Status",
231
+ sections: [
232
+ {
233
+ header: "Paths",
234
+ entries: [
235
+ serviceStatusEntry("Bitcoin datadir", payload.dataDir, true),
236
+ serviceStatusEntry("Wallet root", payload.walletRootId, true),
237
+ serviceStatusEntry("Wallet root source", payload.walletRootSource, true),
238
+ ],
239
+ },
240
+ {
241
+ header: "Managed Service",
242
+ entries: managedServiceEntries,
243
+ },
244
+ {
245
+ header: "Indexer State",
246
+ entries: indexerStateEntries,
247
+ },
248
+ ],
249
+ nextStep: payload.compatibility === "unreachable"
250
+ ? "Run `cogcoin indexer start` to start the managed Cogcoin indexer."
251
+ : null,
252
+ });
237
253
  }
238
254
  function buildStatusMessages(payload) {
239
255
  const warnings = [];
@@ -287,7 +303,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
287
303
  return 0;
288
304
  }
289
305
  if (parsed.command === "bitcoin-start") {
290
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
306
+ const resolution = await resolveEffectiveWalletRootId(context);
291
307
  const probe = await context.probeManagedBitcoindService({
292
308
  dataDir,
293
309
  chain: "main",
@@ -298,7 +314,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
298
314
  await context.attachManagedBitcoindService({
299
315
  dataDir,
300
316
  chain: "main",
301
- startHeight: genesis.genesisBlock,
317
+ startHeight: resolveCogcoinProcessingStartHeight(genesis),
302
318
  walletRootId: resolution.walletRootId,
303
319
  });
304
320
  const bitcoindStatus = probe.compatibility === "compatible" ? "already-running" : "started";
@@ -324,7 +340,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
324
340
  return 0;
325
341
  }
326
342
  if (parsed.command === "bitcoin-stop") {
327
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
343
+ const resolution = await resolveEffectiveWalletRootId(context);
328
344
  const indexer = await context.stopIndexerDaemonService({
329
345
  dataDir,
330
346
  walletRootId: resolution.walletRootId,
@@ -349,7 +365,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
349
365
  return 0;
350
366
  }
351
367
  if (parsed.command === "indexer-start") {
352
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
368
+ const resolution = await resolveEffectiveWalletRootId(context);
353
369
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
354
370
  await context.ensureDirectory(dirname(dbPath));
355
371
  const genesis = await loadBundledGenesisParameters();
@@ -362,7 +378,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
362
378
  await context.attachManagedBitcoindService({
363
379
  dataDir,
364
380
  chain: "main",
365
- startHeight: genesis.genesisBlock,
381
+ startHeight: resolveCogcoinProcessingStartHeight(genesis),
366
382
  walletRootId: resolution.walletRootId,
367
383
  });
368
384
  const indexerProbe = await context.probeIndexerDaemon({
@@ -401,7 +417,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
401
417
  return 0;
402
418
  }
403
419
  if (parsed.command === "indexer-stop") {
404
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
420
+ const resolution = await resolveEffectiveWalletRootId(context);
405
421
  const indexer = await context.stopIndexerDaemonService({
406
422
  dataDir,
407
423
  walletRootId: resolution.walletRootId,
@@ -6,11 +6,13 @@ import { createSuccessEnvelope, describeCanonicalCommand, writeJsonValue } from
6
6
  export async function runStatusCommand(parsed, context) {
7
7
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
8
8
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
9
+ const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
9
10
  await context.ensureDirectory(dirname(dbPath));
10
11
  const readContext = await context.openWalletReadContext({
11
12
  dataDir,
12
13
  databasePath: dbPath,
13
14
  secretProvider: context.walletSecretProvider,
15
+ paths: runtimePaths,
14
16
  });
15
17
  try {
16
18
  if (parsed.outputMode === "json") {
@@ -1,11 +1,20 @@
1
1
  import { dirname } from "node:path";
2
2
  import { formatManagedSyncErrorMessage } from "../../bitcoind/errors.js";
3
+ import { resolveWalletRootIdFromLocalArtifacts } from "../../wallet/root-resolution.js";
3
4
  import { writeLine } from "../io.js";
4
5
  import { classifyCliError } from "../output.js";
5
6
  import { createStopSignalWatcher, waitForCompletionOrStop } from "../signals.js";
7
+ import { confirmGetblockArchiveRestart } from "./getblock-archive-restart.js";
6
8
  export async function runSyncCommand(parsed, context) {
7
9
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
8
10
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
11
+ const walletRoot = await resolveWalletRootIdFromLocalArtifacts({
12
+ paths: context.resolveWalletRuntimePaths(),
13
+ provider: context.walletSecretProvider,
14
+ loadRawWalletStateEnvelope: context.loadRawWalletStateEnvelope,
15
+ loadUnlockSession: context.loadUnlockSession,
16
+ loadWalletExplicitLock: context.loadWalletExplicitLock,
17
+ });
9
18
  await context.ensureDirectory(dirname(dbPath));
10
19
  const store = await context.openSqliteStore({ filename: dbPath });
11
20
  let storeOwned = true;
@@ -14,7 +23,9 @@ export async function runSyncCommand(parsed, context) {
14
23
  store,
15
24
  databasePath: dbPath,
16
25
  dataDir,
26
+ walletRootId: walletRoot.walletRootId,
17
27
  progressOutput: parsed.progressOutput,
28
+ confirmGetblockArchiveRestart: async (options) => confirmGetblockArchiveRestart(parsed, context, options),
18
29
  });
19
30
  storeOwned = false;
20
31
  const stopWatcher = createStopSignalWatcher(context.signalSource, context.stderr, client, context.forceExit);