@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
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { access, constants, mkdir, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { attachOrStartIndexerDaemon, probeIndexerDaemon, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
|
|
4
|
+
import { probeManagedBitcoindService } from "../../bitcoind/service.js";
|
|
5
|
+
import { resolveManagedServicePaths } from "../../bitcoind/service-paths.js";
|
|
6
|
+
import { openClient } from "../../client.js";
|
|
7
|
+
import { openSqliteStore } from "../../sqlite/index.js";
|
|
8
|
+
import { clearOrphanedFileLock } from "../fs/lock.js";
|
|
9
|
+
async function pathExists(path) {
|
|
10
|
+
try {
|
|
11
|
+
await access(path, constants.F_OK);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function ensureIndexerDatabaseHealthy(options) {
|
|
19
|
+
try {
|
|
20
|
+
if (await pathExists(options.databasePath)) {
|
|
21
|
+
const header = await readFile(options.databasePath).then((buffer) => buffer.subarray(0, 16).toString("utf8"));
|
|
22
|
+
if (header.length > 0 && header !== "SQLite format 3\u0000") {
|
|
23
|
+
throw new Error("indexer_database_not_sqlite");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const store = await openSqliteStore({ filename: options.databasePath });
|
|
27
|
+
try {
|
|
28
|
+
const client = await openClient({ store });
|
|
29
|
+
try {
|
|
30
|
+
await client.getTip();
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
await client.close();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await store.close();
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
if (!options.resetIfNeeded) {
|
|
43
|
+
throw new Error("wallet_repair_indexer_reset_requires_yes");
|
|
44
|
+
}
|
|
45
|
+
await rm(options.databasePath, { force: true }).catch(() => undefined);
|
|
46
|
+
await rm(`${options.databasePath}-wal`, { force: true }).catch(() => undefined);
|
|
47
|
+
await rm(`${options.databasePath}-shm`, { force: true }).catch(() => undefined);
|
|
48
|
+
await mkdir(dirname(options.databasePath), { recursive: true });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function mapIndexerCompatibilityToRepairIssue(compatibility) {
|
|
53
|
+
switch (compatibility) {
|
|
54
|
+
case "service-version-mismatch":
|
|
55
|
+
return "service-version-mismatch";
|
|
56
|
+
case "wallet-root-mismatch":
|
|
57
|
+
return "wallet-root-mismatch";
|
|
58
|
+
case "schema-mismatch":
|
|
59
|
+
return "schema-mismatch";
|
|
60
|
+
default:
|
|
61
|
+
return "none";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function mapBitcoindCompatibilityToRepairIssue(compatibility) {
|
|
65
|
+
switch (compatibility) {
|
|
66
|
+
case "service-version-mismatch":
|
|
67
|
+
return "service-version-mismatch";
|
|
68
|
+
case "wallet-root-mismatch":
|
|
69
|
+
return "wallet-root-mismatch";
|
|
70
|
+
case "runtime-mismatch":
|
|
71
|
+
return "runtime-mismatch";
|
|
72
|
+
default:
|
|
73
|
+
return "none";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function mapBitcoindRepairHealth(options) {
|
|
77
|
+
if (options.serviceState === null) {
|
|
78
|
+
return "unavailable";
|
|
79
|
+
}
|
|
80
|
+
if (options.serviceState === "starting" || options.serviceState === "stopping") {
|
|
81
|
+
return "starting";
|
|
82
|
+
}
|
|
83
|
+
if (options.serviceState !== "ready") {
|
|
84
|
+
return "failed";
|
|
85
|
+
}
|
|
86
|
+
if (options.replica?.proofStatus === "missing" || options.replica?.proofStatus === "mismatch") {
|
|
87
|
+
return "failed";
|
|
88
|
+
}
|
|
89
|
+
if (options.catchingUp) {
|
|
90
|
+
return "catching-up";
|
|
91
|
+
}
|
|
92
|
+
return "ready";
|
|
93
|
+
}
|
|
94
|
+
function mapLeaseStateToRepairHealth(state) {
|
|
95
|
+
switch (state) {
|
|
96
|
+
case "synced":
|
|
97
|
+
return "synced";
|
|
98
|
+
case "catching-up":
|
|
99
|
+
case "reorging":
|
|
100
|
+
return "catching-up";
|
|
101
|
+
case "starting":
|
|
102
|
+
case "stopping":
|
|
103
|
+
return "starting";
|
|
104
|
+
default:
|
|
105
|
+
return "failed";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const INDEXER_DAEMON_HEARTBEAT_STALE_MS = 15_000;
|
|
109
|
+
export async function verifyIndexerPostRepairHealth(options) {
|
|
110
|
+
try {
|
|
111
|
+
const lease = await readSnapshotWithRetry(options.daemon, options.walletRootId);
|
|
112
|
+
return {
|
|
113
|
+
health: mapLeaseStateToRepairHealth(lease.status.state),
|
|
114
|
+
daemonInstanceId: lease.status.daemonInstanceId,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (leaseError) {
|
|
118
|
+
const probe = await options.probeIndexerDaemon({
|
|
119
|
+
dataDir: options.dataDir,
|
|
120
|
+
walletRootId: options.walletRootId,
|
|
121
|
+
});
|
|
122
|
+
try {
|
|
123
|
+
if (probe.compatibility === "compatible"
|
|
124
|
+
&& probe.status !== null
|
|
125
|
+
&& (options.nowUnixMs - probe.status.heartbeatAtUnixMs) <= INDEXER_DAEMON_HEARTBEAT_STALE_MS
|
|
126
|
+
&& (probe.status.state === "starting" || probe.status.state === "catching-up" || probe.status.state === "reorging")) {
|
|
127
|
+
return {
|
|
128
|
+
health: mapLeaseStateToRepairHealth(probe.status.state),
|
|
129
|
+
daemonInstanceId: probe.status.daemonInstanceId,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
await probe.client?.close().catch(() => undefined);
|
|
135
|
+
}
|
|
136
|
+
throw leaseError;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export async function isProcessAlive(pid) {
|
|
140
|
+
if (pid === null) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
process.kill(pid, 0);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
if (error instanceof Error && "code" in error && error.code === "ESRCH") {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export async function waitForProcessExit(pid, timeoutMs = 15_000, errorCode = "indexer_daemon_stop_timeout") {
|
|
155
|
+
const deadline = Date.now() + timeoutMs;
|
|
156
|
+
while (Date.now() < deadline) {
|
|
157
|
+
if (!await isProcessAlive(pid)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
161
|
+
}
|
|
162
|
+
throw new Error(errorCode);
|
|
163
|
+
}
|
|
164
|
+
export async function clearIndexerDaemonArtifacts(servicePaths) {
|
|
165
|
+
await rm(servicePaths.indexerDaemonStatusPath, { force: true }).catch(() => undefined);
|
|
166
|
+
await rm(servicePaths.indexerDaemonSocketPath, { force: true }).catch(() => undefined);
|
|
167
|
+
}
|
|
168
|
+
export async function clearManagedBitcoindArtifacts(servicePaths) {
|
|
169
|
+
await rm(servicePaths.bitcoindStatusPath, { force: true }).catch(() => undefined);
|
|
170
|
+
await rm(servicePaths.bitcoindPidPath, { force: true }).catch(() => undefined);
|
|
171
|
+
await rm(servicePaths.bitcoindReadyPath, { force: true }).catch(() => undefined);
|
|
172
|
+
await rm(servicePaths.bitcoindWalletStatusPath, { force: true }).catch(() => undefined);
|
|
173
|
+
}
|
|
174
|
+
export async function stopRecordedManagedProcess(pid, errorCode) {
|
|
175
|
+
if (pid === null || !await isProcessAlive(pid)) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
process.kill(pid, "SIGTERM");
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
await waitForProcessExit(pid, 5_000, errorCode);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
try {
|
|
192
|
+
process.kill(pid, "SIGKILL");
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
await waitForProcessExit(pid, 5_000, errorCode);
|
|
201
|
+
}
|
|
202
|
+
export async function clearOrphanedRepairLocks(lockPaths) {
|
|
203
|
+
for (const lockPath of lockPaths) {
|
|
204
|
+
await clearOrphanedFileLock(lockPath, isProcessAlive);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WalletRepairDependencies, WalletRepairResult } from "./types.js";
|
|
2
|
+
export declare function repairWallet(options: {
|
|
3
|
+
dataDir: string;
|
|
4
|
+
databasePath: string;
|
|
5
|
+
provider?: import("../state/provider.js").WalletSecretProvider;
|
|
6
|
+
assumeYes?: boolean;
|
|
7
|
+
nowUnixMs?: number;
|
|
8
|
+
paths?: import("../runtime.js").WalletRuntimePaths;
|
|
9
|
+
} & WalletRepairDependencies): Promise<WalletRepairResult>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { resolveManagedServicePaths } from "../../bitcoind/service-paths.js";
|
|
2
|
+
import { clearLegacyWalletLockArtifacts } from "../managed-core-wallet.js";
|
|
3
|
+
import { loadMiningRuntimeStatus } from "../mining/runtime-artifacts.js";
|
|
4
|
+
import { loadWalletState } from "../state/storage.js";
|
|
5
|
+
import { acquireWalletControlLock, resolveWalletRepairContext, } from "./context.js";
|
|
6
|
+
import { repairManagedBitcoindStage } from "./repair-bitcoind.js";
|
|
7
|
+
import { repairManagedIndexerStage } from "./repair-indexer.js";
|
|
8
|
+
import { applyRepairStoppedMiningState, cleanupMiningForRepair, persistRepairState, resumeBackgroundMiningAfterRepair, } from "./repair-mining.js";
|
|
9
|
+
import { clearOrphanedRepairLocks, ensureIndexerDatabaseHealthy, } from "./repair-runtime.js";
|
|
10
|
+
export async function repairWallet(options) {
|
|
11
|
+
const context = resolveWalletRepairContext(options);
|
|
12
|
+
await clearOrphanedRepairLocks([
|
|
13
|
+
context.paths.walletControlLockPath,
|
|
14
|
+
context.paths.miningControlLockPath,
|
|
15
|
+
]);
|
|
16
|
+
const controlLock = await acquireWalletControlLock(context.paths, "wallet-repair");
|
|
17
|
+
try {
|
|
18
|
+
let loaded;
|
|
19
|
+
try {
|
|
20
|
+
loaded = await loadWalletState({
|
|
21
|
+
primaryPath: context.paths.walletStatePath,
|
|
22
|
+
backupPath: context.paths.walletStateBackupPath,
|
|
23
|
+
}, {
|
|
24
|
+
provider: context.provider,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error("local-state-corrupt");
|
|
29
|
+
}
|
|
30
|
+
const recoveredFromBackup = loaded.source === "backup";
|
|
31
|
+
let repairedState = loaded.state;
|
|
32
|
+
let repairStateNeedsPersist = false;
|
|
33
|
+
const servicePaths = resolveManagedServicePaths(context.dataDir, repairedState.walletRootId);
|
|
34
|
+
await clearOrphanedRepairLocks([
|
|
35
|
+
servicePaths.bitcoindLockPath,
|
|
36
|
+
servicePaths.indexerDaemonLockPath,
|
|
37
|
+
]);
|
|
38
|
+
const preRepairMiningRuntime = await loadMiningRuntimeStatus(context.paths.miningStatusPath).catch(() => null);
|
|
39
|
+
const miningCleanup = await cleanupMiningForRepair({
|
|
40
|
+
paths: context.paths,
|
|
41
|
+
state: repairedState,
|
|
42
|
+
snapshot: preRepairMiningRuntime,
|
|
43
|
+
nowUnixMs: context.nowUnixMs,
|
|
44
|
+
});
|
|
45
|
+
const miningPreRepairRunMode = miningCleanup.preRepairRunMode;
|
|
46
|
+
if (miningPreRepairRunMode !== "stopped" || preRepairMiningRuntime?.runMode !== "stopped") {
|
|
47
|
+
repairedState = applyRepairStoppedMiningState(repairedState);
|
|
48
|
+
repairStateNeedsPersist = true;
|
|
49
|
+
}
|
|
50
|
+
if (!context.assumeYes) {
|
|
51
|
+
await ensureIndexerDatabaseHealthy({
|
|
52
|
+
databasePath: context.databasePath,
|
|
53
|
+
dataDir: context.dataDir,
|
|
54
|
+
walletRootId: repairedState.walletRootId,
|
|
55
|
+
resetIfNeeded: false,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const bitcoindStage = await repairManagedBitcoindStage({
|
|
59
|
+
context,
|
|
60
|
+
servicePaths,
|
|
61
|
+
state: repairedState,
|
|
62
|
+
recoveredFromBackup,
|
|
63
|
+
repairStateNeedsPersist,
|
|
64
|
+
});
|
|
65
|
+
repairedState = bitcoindStage.state;
|
|
66
|
+
repairStateNeedsPersist = bitcoindStage.repairStateNeedsPersist;
|
|
67
|
+
if (recoveredFromBackup) {
|
|
68
|
+
repairedState = await persistRepairState({
|
|
69
|
+
state: repairedState,
|
|
70
|
+
provider: context.provider,
|
|
71
|
+
paths: context.paths,
|
|
72
|
+
nowUnixMs: context.nowUnixMs,
|
|
73
|
+
replacePrimary: true,
|
|
74
|
+
});
|
|
75
|
+
repairStateNeedsPersist = false;
|
|
76
|
+
}
|
|
77
|
+
else if (repairStateNeedsPersist) {
|
|
78
|
+
repairedState = await persistRepairState({
|
|
79
|
+
state: repairedState,
|
|
80
|
+
provider: context.provider,
|
|
81
|
+
paths: context.paths,
|
|
82
|
+
nowUnixMs: context.nowUnixMs,
|
|
83
|
+
});
|
|
84
|
+
repairStateNeedsPersist = false;
|
|
85
|
+
}
|
|
86
|
+
const indexerStage = await repairManagedIndexerStage({
|
|
87
|
+
context,
|
|
88
|
+
servicePaths,
|
|
89
|
+
state: repairedState,
|
|
90
|
+
});
|
|
91
|
+
const miningResume = await resumeBackgroundMiningAfterRepair({
|
|
92
|
+
miningPreRepairRunMode,
|
|
93
|
+
provider: context.provider,
|
|
94
|
+
paths: context.paths,
|
|
95
|
+
repairedState,
|
|
96
|
+
bitcoindPostRepairHealth: bitcoindStage.bitcoindPostRepairHealth,
|
|
97
|
+
indexerPostRepairHealth: indexerStage.indexerPostRepairHealth,
|
|
98
|
+
dataDir: context.dataDir,
|
|
99
|
+
databasePath: context.databasePath,
|
|
100
|
+
startBackgroundMining: context.startBackgroundMining,
|
|
101
|
+
});
|
|
102
|
+
await clearLegacyWalletLockArtifacts(context.paths.walletRuntimeRoot);
|
|
103
|
+
return {
|
|
104
|
+
walletRootId: repairedState.walletRootId,
|
|
105
|
+
recoveredFromBackup,
|
|
106
|
+
recreatedManagedCoreWallet: bitcoindStage.recreatedManagedCoreWallet,
|
|
107
|
+
resetIndexerDatabase: indexerStage.resetIndexerDatabase,
|
|
108
|
+
bitcoindServiceAction: bitcoindStage.bitcoindServiceAction,
|
|
109
|
+
bitcoindCompatibilityIssue: bitcoindStage.bitcoindCompatibilityIssue,
|
|
110
|
+
managedCoreReplicaAction: bitcoindStage.managedCoreReplicaAction,
|
|
111
|
+
bitcoindPostRepairHealth: bitcoindStage.bitcoindPostRepairHealth,
|
|
112
|
+
indexerDaemonAction: indexerStage.indexerDaemonAction,
|
|
113
|
+
indexerCompatibilityIssue: indexerStage.indexerCompatibilityIssue,
|
|
114
|
+
indexerPostRepairHealth: indexerStage.indexerPostRepairHealth,
|
|
115
|
+
miningPreRepairRunMode,
|
|
116
|
+
miningResumeAction: miningResume.miningResumeAction,
|
|
117
|
+
miningPostRepairRunMode: miningResume.miningPostRepairRunMode,
|
|
118
|
+
miningResumeError: miningResume.miningResumeError,
|
|
119
|
+
note: indexerStage.resetIndexerDatabase
|
|
120
|
+
? "Indexer artifacts were reset and may still be catching up."
|
|
121
|
+
: null,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await controlLock.release();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WalletInitializationResult, WalletPrompter } from "./types.js";
|
|
2
|
+
export declare function promptForRestoreMnemonic(prompter: WalletPrompter): Promise<string>;
|
|
3
|
+
export declare function promptForInitializationMode(prompter: WalletPrompter): Promise<Exclude<WalletInitializationResult["setupMode"], "existing">>;
|
|
4
|
+
export declare function confirmTypedAcknowledgement(prompter: WalletPrompter, expected: string, message: string, errorCode?: string): Promise<void>;
|
|
5
|
+
export declare function writeMnemonicReveal(prompter: WalletPrompter, phrase: string, introLines: readonly string[]): void;
|
|
6
|
+
export declare function confirmMnemonic(prompter: WalletPrompter, words: readonly string[]): Promise<void>;
|
|
7
|
+
export declare function clearSensitiveDisplay(prompter: WalletPrompter, scope: "mnemonic-reveal" | "restore-mnemonic-entry"): Promise<void>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createMnemonicConfirmationChallenge, isEnglishMnemonicWord, validateEnglishMnemonic, } from "../material.js";
|
|
2
|
+
import { renderWalletMnemonicRevealArt } from "../mnemonic-art.js";
|
|
3
|
+
async function promptRequiredValue(prompter, message) {
|
|
4
|
+
const value = (await prompter.prompt(message)).trim();
|
|
5
|
+
if (value === "") {
|
|
6
|
+
throw new Error("wallet_prompt_value_required");
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
export async function promptForRestoreMnemonic(prompter) {
|
|
11
|
+
const words = [];
|
|
12
|
+
for (let index = 0; index < 24; index += 1) {
|
|
13
|
+
const word = (await promptRequiredValue(prompter, `Word ${index + 1} of 24: `)).toLowerCase();
|
|
14
|
+
if (!isEnglishMnemonicWord(word)) {
|
|
15
|
+
throw new Error("wallet_restore_mnemonic_invalid");
|
|
16
|
+
}
|
|
17
|
+
words.push(word);
|
|
18
|
+
}
|
|
19
|
+
const phrase = words.join(" ");
|
|
20
|
+
if (!validateEnglishMnemonic(phrase)) {
|
|
21
|
+
throw new Error("wallet_restore_mnemonic_invalid");
|
|
22
|
+
}
|
|
23
|
+
return phrase;
|
|
24
|
+
}
|
|
25
|
+
export async function promptForInitializationMode(prompter) {
|
|
26
|
+
if (prompter.selectOption != null) {
|
|
27
|
+
return await prompter.selectOption({
|
|
28
|
+
message: "How should Cogcoin set up this wallet?",
|
|
29
|
+
options: [
|
|
30
|
+
{
|
|
31
|
+
label: "Create new wallet",
|
|
32
|
+
description: "Generate a fresh 24-word recovery phrase.",
|
|
33
|
+
value: "generated",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: "Restore existing wallet",
|
|
37
|
+
description: "Enter an existing 24-word recovery phrase.",
|
|
38
|
+
value: "restored",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
initialValue: "generated",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
prompter.writeLine("How should Cogcoin set up this wallet?");
|
|
45
|
+
prompter.writeLine("1. Create new wallet");
|
|
46
|
+
prompter.writeLine("2. Restore existing wallet");
|
|
47
|
+
while (true) {
|
|
48
|
+
const answer = (await prompter.prompt("Choice [1-2]: ")).trim();
|
|
49
|
+
if (answer === "1") {
|
|
50
|
+
return "generated";
|
|
51
|
+
}
|
|
52
|
+
if (answer === "2") {
|
|
53
|
+
return "restored";
|
|
54
|
+
}
|
|
55
|
+
prompter.writeLine("Enter 1 or 2.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function confirmTypedAcknowledgement(prompter, expected, message, errorCode = "wallet_typed_confirmation_rejected") {
|
|
59
|
+
const answer = (await prompter.prompt(message)).trim();
|
|
60
|
+
if (answer !== expected) {
|
|
61
|
+
throw new Error(errorCode);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function writeMnemonicReveal(prompter, phrase, introLines) {
|
|
65
|
+
const words = phrase.trim().split(/\s+/);
|
|
66
|
+
for (const line of introLines) {
|
|
67
|
+
prompter.writeLine(line);
|
|
68
|
+
}
|
|
69
|
+
for (const line of renderWalletMnemonicRevealArt(words)) {
|
|
70
|
+
prompter.writeLine(line);
|
|
71
|
+
}
|
|
72
|
+
prompter.writeLine("Single-line copy:");
|
|
73
|
+
prompter.writeLine(phrase);
|
|
74
|
+
}
|
|
75
|
+
export async function confirmMnemonic(prompter, words) {
|
|
76
|
+
const challenge = createMnemonicConfirmationChallenge([...words]);
|
|
77
|
+
for (const entry of challenge) {
|
|
78
|
+
const answer = (await prompter.prompt(`Confirm word #${entry.index + 1}: `)).trim().toLowerCase();
|
|
79
|
+
if (answer !== entry.word) {
|
|
80
|
+
throw new Error(`wallet_init_confirmation_failed_word_${entry.index + 1}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function clearSensitiveDisplay(prompter, scope) {
|
|
85
|
+
await Promise.resolve()
|
|
86
|
+
.then(() => prompter.clearSensitiveDisplay?.(scope))
|
|
87
|
+
.catch(() => undefined);
|
|
88
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { deriveWalletMaterialFromMnemonic } from "../material.js";
|
|
2
|
+
import type { WalletRuntimePaths } from "../runtime.js";
|
|
3
|
+
import { type WalletSecretProvider } from "../state/provider.js";
|
|
4
|
+
import type { WalletStateV1 } from "../types.js";
|
|
5
|
+
import type { WalletSetupContext } from "./types.js";
|
|
6
|
+
export type WalletMaterial = ReturnType<typeof deriveWalletMaterialFromMnemonic>;
|
|
7
|
+
export declare function clearPendingInitialization(paths: WalletRuntimePaths, provider: WalletSecretProvider): Promise<void>;
|
|
8
|
+
export declare function loadOrCreatePendingInitializationMaterial(options: {
|
|
9
|
+
provider: WalletSecretProvider;
|
|
10
|
+
paths: WalletRuntimePaths;
|
|
11
|
+
nowUnixMs: number;
|
|
12
|
+
}): Promise<WalletMaterial>;
|
|
13
|
+
export declare function createInitialWalletState(options: {
|
|
14
|
+
walletRootId: string;
|
|
15
|
+
nowUnixMs: number;
|
|
16
|
+
material: WalletMaterial;
|
|
17
|
+
internalCoreWalletPassphrase: string;
|
|
18
|
+
}): WalletStateV1;
|
|
19
|
+
export declare function persistInitializedWallet(options: {
|
|
20
|
+
context: WalletSetupContext;
|
|
21
|
+
provider: WalletSecretProvider;
|
|
22
|
+
material: WalletMaterial;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
walletRootId: string;
|
|
25
|
+
state: WalletStateV1;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { withClaimedUninitializedManagedRuntime } from "../../bitcoind/service.js";
|
|
3
|
+
import { createInternalCoreWalletPassphrase, deriveWalletMaterialFromMnemonic, generateWalletMaterial, } from "../material.js";
|
|
4
|
+
import { clearLegacyWalletLockArtifacts } from "../managed-core-wallet.js";
|
|
5
|
+
import { clearWalletPendingInitializationState, loadWalletPendingInitializationStateOrNull, saveWalletPendingInitializationState, } from "../state/pending-init.js";
|
|
6
|
+
import { createWalletPendingInitSecretReference, createWalletRootId, createWalletSecretReference, } from "../state/provider.js";
|
|
7
|
+
import { saveWalletState } from "../state/storage.js";
|
|
8
|
+
import { importDescriptorIntoManagedCoreWallet, sanitizeWalletName } from "./managed-core.js";
|
|
9
|
+
function resolvePendingInitializationStoragePaths(paths) {
|
|
10
|
+
return {
|
|
11
|
+
primaryPath: paths.walletInitPendingPath,
|
|
12
|
+
backupPath: paths.walletInitPendingBackupPath,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export async function clearPendingInitialization(paths, provider) {
|
|
16
|
+
await clearWalletPendingInitializationState(resolvePendingInitializationStoragePaths(paths), {
|
|
17
|
+
provider,
|
|
18
|
+
secretReference: createWalletPendingInitSecretReference(paths.walletStateRoot),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export async function loadOrCreatePendingInitializationMaterial(options) {
|
|
22
|
+
try {
|
|
23
|
+
const loaded = await loadWalletPendingInitializationStateOrNull(resolvePendingInitializationStoragePaths(options.paths), {
|
|
24
|
+
provider: options.provider,
|
|
25
|
+
});
|
|
26
|
+
if (loaded !== null) {
|
|
27
|
+
return deriveWalletMaterialFromMnemonic(loaded.state.mnemonic.phrase);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
await clearPendingInitialization(options.paths, options.provider);
|
|
32
|
+
}
|
|
33
|
+
const material = generateWalletMaterial();
|
|
34
|
+
const secretReference = createWalletPendingInitSecretReference(options.paths.walletStateRoot);
|
|
35
|
+
const pendingState = {
|
|
36
|
+
schemaVersion: 1,
|
|
37
|
+
createdAtUnixMs: options.nowUnixMs,
|
|
38
|
+
mnemonic: {
|
|
39
|
+
phrase: material.mnemonic.phrase,
|
|
40
|
+
language: material.mnemonic.language,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
await options.provider.storeSecret(secretReference.keyId, randomBytes(32));
|
|
44
|
+
try {
|
|
45
|
+
await saveWalletPendingInitializationState(resolvePendingInitializationStoragePaths(options.paths), pendingState, {
|
|
46
|
+
provider: options.provider,
|
|
47
|
+
secretReference,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
await options.provider.deleteSecret(secretReference.keyId).catch(() => undefined);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
return material;
|
|
55
|
+
}
|
|
56
|
+
export function createInitialWalletState(options) {
|
|
57
|
+
return {
|
|
58
|
+
schemaVersion: 5,
|
|
59
|
+
stateRevision: 1,
|
|
60
|
+
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
61
|
+
walletRootId: options.walletRootId,
|
|
62
|
+
network: "mainnet",
|
|
63
|
+
localScriptPubKeyHexes: [options.material.funding.scriptPubKeyHex],
|
|
64
|
+
mnemonic: {
|
|
65
|
+
phrase: options.material.mnemonic.phrase,
|
|
66
|
+
language: options.material.mnemonic.language,
|
|
67
|
+
},
|
|
68
|
+
keys: {
|
|
69
|
+
masterFingerprintHex: options.material.keys.masterFingerprintHex,
|
|
70
|
+
accountPath: options.material.keys.accountPath,
|
|
71
|
+
accountXprv: options.material.keys.accountXprv,
|
|
72
|
+
accountXpub: options.material.keys.accountXpub,
|
|
73
|
+
},
|
|
74
|
+
descriptor: {
|
|
75
|
+
privateExternal: options.material.descriptor.privateExternal,
|
|
76
|
+
publicExternal: options.material.descriptor.publicExternal,
|
|
77
|
+
checksum: options.material.descriptor.checksum,
|
|
78
|
+
rangeEnd: options.material.descriptor.rangeEnd,
|
|
79
|
+
safetyMargin: options.material.descriptor.safetyMargin,
|
|
80
|
+
},
|
|
81
|
+
funding: {
|
|
82
|
+
address: options.material.funding.address,
|
|
83
|
+
scriptPubKeyHex: options.material.funding.scriptPubKeyHex,
|
|
84
|
+
},
|
|
85
|
+
walletBirthTime: Math.floor(options.nowUnixMs / 1000),
|
|
86
|
+
managedCoreWallet: {
|
|
87
|
+
walletName: sanitizeWalletName(options.walletRootId),
|
|
88
|
+
internalPassphrase: options.internalCoreWalletPassphrase,
|
|
89
|
+
descriptorChecksum: null,
|
|
90
|
+
walletAddress: null,
|
|
91
|
+
walletScriptPubKeyHex: null,
|
|
92
|
+
proofStatus: "not-proven",
|
|
93
|
+
lastImportedAtUnixMs: null,
|
|
94
|
+
lastVerifiedAtUnixMs: null,
|
|
95
|
+
},
|
|
96
|
+
domains: [],
|
|
97
|
+
miningState: {
|
|
98
|
+
runMode: "stopped",
|
|
99
|
+
state: "idle",
|
|
100
|
+
pauseReason: null,
|
|
101
|
+
currentPublishState: "none",
|
|
102
|
+
currentDomain: null,
|
|
103
|
+
currentDomainId: null,
|
|
104
|
+
currentDomainIndex: null,
|
|
105
|
+
currentSenderScriptPubKeyHex: null,
|
|
106
|
+
currentTxid: null,
|
|
107
|
+
currentWtxid: null,
|
|
108
|
+
currentFeeRateSatVb: null,
|
|
109
|
+
currentAbsoluteFeeSats: null,
|
|
110
|
+
currentScore: null,
|
|
111
|
+
currentSentence: null,
|
|
112
|
+
currentEncodedSentenceBytesHex: null,
|
|
113
|
+
currentBip39WordIndices: null,
|
|
114
|
+
currentBlendSeedHex: null,
|
|
115
|
+
currentBlockTargetHeight: null,
|
|
116
|
+
currentReferencedBlockHashDisplay: null,
|
|
117
|
+
currentIntentFingerprintHex: null,
|
|
118
|
+
livePublishInMempool: null,
|
|
119
|
+
currentPublishDecision: null,
|
|
120
|
+
replacementCount: 0,
|
|
121
|
+
currentBlockFeeSpentSats: "0",
|
|
122
|
+
sessionFeeSpentSats: "0",
|
|
123
|
+
lifetimeFeeSpentSats: "0",
|
|
124
|
+
sharedMiningConflictOutpoint: null,
|
|
125
|
+
},
|
|
126
|
+
pendingMutations: [],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export async function persistInitializedWallet(options) {
|
|
130
|
+
const walletRootId = createWalletRootId();
|
|
131
|
+
const internalCoreWalletPassphrase = createInternalCoreWalletPassphrase();
|
|
132
|
+
const secretReference = createWalletSecretReference(walletRootId);
|
|
133
|
+
await options.provider.storeSecret(secretReference.keyId, randomBytes(32));
|
|
134
|
+
const initialState = createInitialWalletState({
|
|
135
|
+
walletRootId,
|
|
136
|
+
nowUnixMs: options.context.nowUnixMs,
|
|
137
|
+
material: options.material,
|
|
138
|
+
internalCoreWalletPassphrase,
|
|
139
|
+
});
|
|
140
|
+
const verifiedState = await withClaimedUninitializedManagedRuntime({
|
|
141
|
+
dataDir: options.context.dataDir,
|
|
142
|
+
walletRootId,
|
|
143
|
+
}, async () => {
|
|
144
|
+
await saveWalletState({
|
|
145
|
+
primaryPath: options.context.paths.walletStatePath,
|
|
146
|
+
backupPath: options.context.paths.walletStateBackupPath,
|
|
147
|
+
}, initialState, {
|
|
148
|
+
provider: options.provider,
|
|
149
|
+
secretReference,
|
|
150
|
+
});
|
|
151
|
+
return await importDescriptorIntoManagedCoreWallet(initialState, options.provider, options.context.paths, options.context.dataDir, options.context.nowUnixMs, options.context.attachService, options.context.rpcFactory);
|
|
152
|
+
});
|
|
153
|
+
await clearLegacyWalletLockArtifacts(options.context.paths.walletRuntimeRoot);
|
|
154
|
+
await clearPendingInitialization(options.context.paths, options.provider);
|
|
155
|
+
return {
|
|
156
|
+
walletRootId,
|
|
157
|
+
state: verifiedState,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type WalletSecretProvider } from "../state/provider.js";
|
|
2
|
+
import type { WalletInitializationResult, WalletPrompter, WalletSetupDependencies } from "./types.js";
|
|
3
|
+
export declare function initializeWallet(options: {
|
|
4
|
+
dataDir: string;
|
|
5
|
+
provider?: WalletSecretProvider;
|
|
6
|
+
prompter: WalletPrompter;
|
|
7
|
+
nowUnixMs?: number;
|
|
8
|
+
paths?: import("../runtime.js").WalletRuntimePaths;
|
|
9
|
+
} & WalletSetupDependencies): Promise<WalletInitializationResult>;
|
|
10
|
+
export declare function showWalletMnemonic(options: {
|
|
11
|
+
provider?: WalletSecretProvider;
|
|
12
|
+
prompter: WalletPrompter;
|
|
13
|
+
nowUnixMs?: number;
|
|
14
|
+
paths?: import("../runtime.js").WalletRuntimePaths;
|
|
15
|
+
} & WalletSetupDependencies): Promise<void>;
|