@cogcoin/client 1.1.6 → 1.1.8
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 +2 -2
- package/dist/bitcoind/indexer-daemon.js +29 -79
- package/dist/bitcoind/managed-runtime/bitcoind-runtime.d.ts +20 -0
- package/dist/bitcoind/managed-runtime/bitcoind-runtime.js +74 -0
- package/dist/bitcoind/managed-runtime/bitcoind-status.d.ts +11 -0
- package/dist/bitcoind/managed-runtime/bitcoind-status.js +44 -0
- package/dist/bitcoind/managed-runtime/indexer-runtime.d.ts +15 -0
- package/dist/bitcoind/managed-runtime/indexer-runtime.js +82 -0
- package/dist/bitcoind/managed-runtime/types.d.ts +40 -0
- package/dist/bitcoind/node.d.ts +2 -2
- package/dist/bitcoind/node.js +2 -2
- package/dist/bitcoind/rpc.d.ts +2 -1
- package/dist/bitcoind/rpc.js +53 -3
- package/dist/bitcoind/service.js +47 -127
- package/dist/cli/command-registry.d.ts +1 -1
- package/dist/cli/command-registry.js +2 -64
- package/dist/cli/commands/client-admin.js +3 -18
- package/dist/cli/commands/mining-runtime.js +4 -60
- package/dist/cli/commands/wallet-admin.js +6 -6
- package/dist/cli/context.js +1 -3
- package/dist/cli/mining-json.d.ts +1 -22
- package/dist/cli/mining-json.js +0 -23
- package/dist/cli/output.js +16 -2
- package/dist/cli/parse.js +0 -2
- package/dist/cli/preview-json.d.ts +1 -22
- package/dist/cli/preview-json.js +0 -19
- package/dist/cli/types.d.ts +1 -3
- package/dist/cli/wallet-format.js +1 -1
- package/dist/cli/workflow-hints.d.ts +1 -2
- package/dist/cli/workflow-hints.js +5 -8
- package/dist/wallet/lifecycle/context.js +0 -1
- package/dist/wallet/lifecycle/repair-mining.d.ts +1 -5
- package/dist/wallet/lifecycle/repair-mining.js +5 -39
- package/dist/wallet/lifecycle/repair.js +0 -3
- package/dist/wallet/lifecycle/setup.js +10 -8
- package/dist/wallet/lifecycle/types.d.ts +1 -4
- package/dist/wallet/managed-core-wallet.d.ts +2 -0
- package/dist/wallet/managed-core-wallet.js +27 -1
- package/dist/wallet/mining/candidate.d.ts +1 -0
- package/dist/wallet/mining/candidate.js +38 -6
- package/dist/wallet/mining/competitiveness.d.ts +1 -0
- package/dist/wallet/mining/competitiveness.js +6 -0
- package/dist/wallet/mining/cycle.d.ts +2 -0
- package/dist/wallet/mining/cycle.js +14 -4
- package/dist/wallet/mining/engine-state.js +10 -0
- package/dist/wallet/mining/engine-types.d.ts +1 -0
- package/dist/wallet/mining/index.d.ts +1 -1
- package/dist/wallet/mining/index.js +1 -1
- package/dist/wallet/mining/publish.d.ts +3 -0
- package/dist/wallet/mining/publish.js +78 -6
- package/dist/wallet/mining/runner.d.ts +0 -32
- package/dist/wallet/mining/runner.js +59 -104
- package/dist/wallet/mining/stop.d.ts +7 -0
- package/dist/wallet/mining/stop.js +23 -0
- package/dist/wallet/mining/supervisor.d.ts +2 -36
- package/dist/wallet/mining/supervisor.js +139 -246
- package/dist/wallet/mining/visualizer-sync.js +79 -15
- package/dist/wallet/read/context.d.ts +1 -5
- package/dist/wallet/read/context.js +21 -205
- package/dist/wallet/read/managed-services.d.ts +33 -0
- package/dist/wallet/read/managed-services.js +222 -0
- package/dist/wallet/reset/artifacts.d.ts +16 -0
- package/dist/wallet/reset/artifacts.js +141 -0
- package/dist/wallet/reset/execution.d.ts +38 -0
- package/dist/wallet/reset/execution.js +458 -0
- package/dist/wallet/reset/preflight.d.ts +7 -0
- package/dist/wallet/reset/preflight.js +116 -0
- package/dist/wallet/reset/preview.d.ts +2 -0
- package/dist/wallet/reset/preview.js +50 -0
- package/dist/wallet/reset/process-cleanup.d.ts +12 -0
- package/dist/wallet/reset/process-cleanup.js +179 -0
- package/dist/wallet/reset/types.d.ts +189 -0
- package/dist/wallet/reset/types.js +1 -0
- package/dist/wallet/reset.d.ts +4 -119
- package/dist/wallet/reset.js +4 -882
- package/dist/wallet/state/client-password/bootstrap.d.ts +2 -0
- package/dist/wallet/state/client-password/bootstrap.js +3 -0
- package/dist/wallet/state/client-password/context.d.ts +10 -0
- package/dist/wallet/state/client-password/context.js +46 -0
- package/dist/wallet/state/client-password/crypto.d.ts +34 -0
- package/dist/wallet/state/client-password/crypto.js +117 -0
- package/dist/wallet/state/client-password/files.d.ts +10 -0
- package/dist/wallet/state/client-password/files.js +109 -0
- package/dist/wallet/state/client-password/legacy-cleanup.d.ts +11 -0
- package/dist/wallet/state/client-password/legacy-cleanup.js +338 -0
- package/dist/wallet/state/client-password/messages.d.ts +3 -0
- package/dist/wallet/state/client-password/messages.js +9 -0
- package/dist/wallet/state/client-password/migration.d.ts +4 -0
- package/dist/wallet/state/client-password/migration.js +32 -0
- package/dist/wallet/state/client-password/prompts.d.ts +12 -0
- package/dist/wallet/state/client-password/prompts.js +79 -0
- package/dist/wallet/state/client-password/protected-secrets.d.ts +13 -0
- package/dist/wallet/state/client-password/protected-secrets.js +90 -0
- package/dist/wallet/state/client-password/readiness.d.ts +4 -0
- package/dist/wallet/state/client-password/readiness.js +48 -0
- package/dist/wallet/state/client-password/references.d.ts +1 -0
- package/dist/wallet/state/client-password/references.js +56 -0
- package/dist/wallet/state/client-password/rotation.d.ts +6 -0
- package/dist/wallet/state/client-password/rotation.js +98 -0
- package/dist/wallet/state/client-password/session-policy.d.ts +6 -0
- package/dist/wallet/state/client-password/session-policy.js +28 -0
- package/dist/wallet/state/client-password/session.d.ts +19 -0
- package/dist/wallet/state/client-password/session.js +170 -0
- package/dist/wallet/state/client-password/setup.d.ts +8 -0
- package/dist/wallet/state/client-password/setup.js +49 -0
- package/dist/wallet/state/client-password/types.d.ts +82 -0
- package/dist/wallet/state/client-password/types.js +5 -0
- package/dist/wallet/state/client-password.d.ts +7 -38
- package/dist/wallet/state/client-password.js +52 -937
- package/dist/wallet/tx/anchor.js +123 -216
- package/dist/wallet/tx/cog.js +294 -489
- package/dist/wallet/tx/common.d.ts +2 -0
- package/dist/wallet/tx/common.js +2 -0
- package/dist/wallet/tx/domain-admin.js +111 -220
- package/dist/wallet/tx/domain-market.js +401 -681
- package/dist/wallet/tx/executor.d.ts +176 -0
- package/dist/wallet/tx/executor.js +302 -0
- package/dist/wallet/tx/field.js +109 -215
- package/dist/wallet/tx/register.js +158 -269
- package/dist/wallet/tx/reputation.js +120 -227
- package/package.json +1 -1
- package/dist/wallet/mining/worker-main.d.ts +0 -1
- package/dist/wallet/mining/worker-main.js +0 -17
- package/dist/wallet/state/client-password-agent.d.ts +0 -1
- package/dist/wallet/state/client-password-agent.js +0 -211
package/dist/wallet/reset.js
CHANGED
|
@@ -1,886 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, join, relative } from "node:path";
|
|
4
|
-
import { DEFAULT_SNAPSHOT_METADATA } from "../bitcoind/bootstrap/constants.js";
|
|
5
|
-
import { createRpcClient } from "../bitcoind/node.js";
|
|
6
|
-
import { resolveBootstrapPathsForTesting } from "../bitcoind/bootstrap/paths.js";
|
|
7
|
-
import { validateSnapshotFileForTesting } from "../bitcoind/bootstrap/snapshot-file.js";
|
|
8
|
-
import { attachOrStartManagedBitcoindService, createManagedWalletReplica, } from "../bitcoind/service.js";
|
|
9
|
-
import { resolveLegacyHooksRootPath } from "../app-paths.js";
|
|
10
|
-
import { resolveNormalizedWalletDescriptorState } from "./descriptor-normalization.js";
|
|
11
|
-
import { acquireFileLock } from "./fs/lock.js";
|
|
12
|
-
import { createInternalCoreWalletPassphrase, deriveWalletIdentityMaterial, deriveWalletMaterialFromMnemonic, } from "./material.js";
|
|
13
|
-
import { loadMiningRuntimeStatus } from "./mining/runtime-artifacts.js";
|
|
14
|
-
import { clearLegacyWalletLockArtifacts, withUnlockedManagedCoreWallet } from "./managed-core-wallet.js";
|
|
15
|
-
import { resolveWalletRuntimePathsForTesting } from "./runtime.js";
|
|
16
|
-
import { createDefaultWalletSecretProvider, createWalletRootId, createWalletSecretReference, } from "./state/provider.js";
|
|
17
|
-
import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, saveWalletState, } from "./state/storage.js";
|
|
18
|
-
import { confirmTypedAcknowledgement } from "./tx/confirm.js";
|
|
19
|
-
function sanitizeWalletName(walletRootId) {
|
|
20
|
-
return `cogcoin-${walletRootId}`.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 63);
|
|
21
|
-
}
|
|
22
|
-
function providerUsesExternalSecretStore(provider) {
|
|
23
|
-
return provider.kind === "macos-keychain";
|
|
24
|
-
}
|
|
25
|
-
function isPathWithin(root, target) {
|
|
26
|
-
const rel = relative(root, target);
|
|
27
|
-
return rel === "" || (!rel.startsWith("..") && rel !== ".");
|
|
28
|
-
}
|
|
29
|
-
async function pathExists(path) {
|
|
30
|
-
try {
|
|
31
|
-
await access(path, constants.F_OK);
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
async function readJsonFileOrNull(path) {
|
|
42
|
-
try {
|
|
43
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async function collectLegacyImportedSeedSecretProviderKeyIds(paths) {
|
|
53
|
-
const seedsRoot = join(paths.stateRoot, "seeds");
|
|
54
|
-
const entries = await readdir(seedsRoot, { withFileTypes: true }).catch((error) => {
|
|
55
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
58
|
-
throw error;
|
|
59
|
-
});
|
|
60
|
-
const keyIds = new Set();
|
|
61
|
-
for (const entry of entries) {
|
|
62
|
-
if (!entry.isDirectory()) {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const seedRoot = join(seedsRoot, entry.name);
|
|
66
|
-
const candidatePaths = [
|
|
67
|
-
join(seedRoot, "wallet-state.enc"),
|
|
68
|
-
join(seedRoot, "wallet-state.enc.bak"),
|
|
69
|
-
join(seedRoot, "wallet-init-pending.enc"),
|
|
70
|
-
join(seedRoot, "wallet-init-pending.enc.bak"),
|
|
71
|
-
];
|
|
72
|
-
for (const candidatePath of candidatePaths) {
|
|
73
|
-
const envelope = await readJsonFileOrNull(candidatePath);
|
|
74
|
-
const keyId = envelope?.secretProvider?.keyId?.trim() ?? "";
|
|
75
|
-
if (keyId.length > 0) {
|
|
76
|
-
keyIds.add(keyId);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return [...keyIds].sort((left, right) => left.localeCompare(right));
|
|
81
|
-
}
|
|
82
|
-
async function isProcessAlive(pid) {
|
|
83
|
-
if (pid === null) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
process.kill(pid, 0);
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
if (error instanceof Error && "code" in error && error.code === "ESRCH") {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
async function waitForProcessExit(pid, timeoutMs = 15_000) {
|
|
98
|
-
const deadline = Date.now() + timeoutMs;
|
|
99
|
-
while (Date.now() < deadline) {
|
|
100
|
-
if (!await isProcessAlive(pid)) {
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
104
|
-
}
|
|
105
|
-
return !await isProcessAlive(pid);
|
|
106
|
-
}
|
|
107
|
-
async function terminateTrackedProcesses(trackedProcesses) {
|
|
108
|
-
const survivors = new Set();
|
|
109
|
-
for (const processInfo of trackedProcesses) {
|
|
110
|
-
try {
|
|
111
|
-
process.kill(processInfo.pid, "SIGTERM");
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
for (const processInfo of trackedProcesses) {
|
|
120
|
-
if (!await waitForProcessExit(processInfo.pid, 5_000)) {
|
|
121
|
-
survivors.add(processInfo.pid);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
for (const pid of survivors) {
|
|
125
|
-
try {
|
|
126
|
-
process.kill(pid, "SIGKILL");
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
|
|
130
|
-
throw error;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const remaining = new Set();
|
|
135
|
-
for (const pid of survivors) {
|
|
136
|
-
if (!await waitForProcessExit(pid, 5_000)) {
|
|
137
|
-
remaining.add(pid);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (remaining.size > 0) {
|
|
141
|
-
throw new Error("reset_process_shutdown_failed");
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
managedBitcoind: trackedProcesses.filter((processInfo) => processInfo.kind === "managed-bitcoind").length,
|
|
145
|
-
indexerDaemon: trackedProcesses.filter((processInfo) => processInfo.kind === "indexer-daemon").length,
|
|
146
|
-
backgroundMining: trackedProcesses.filter((processInfo) => processInfo.kind === "background-mining").length,
|
|
147
|
-
survivors: 0,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
async function moveFile(sourcePath, destinationPath) {
|
|
151
|
-
await mkdir(dirname(destinationPath), { recursive: true });
|
|
152
|
-
try {
|
|
153
|
-
await rename(sourcePath, destinationPath);
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
if (!(error instanceof Error) || !("code" in error) || error.code !== "EXDEV") {
|
|
157
|
-
throw error;
|
|
158
|
-
}
|
|
159
|
-
await copyFile(sourcePath, destinationPath);
|
|
160
|
-
await rm(sourcePath, { force: true });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
async function stageArtifact(sourcePath, stagingRoot, label) {
|
|
164
|
-
if (!await pathExists(sourcePath)) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
const stagedPath = join(stagingRoot, label);
|
|
168
|
-
await moveFile(sourcePath, stagedPath);
|
|
169
|
-
return {
|
|
170
|
-
originalPath: sourcePath,
|
|
171
|
-
stagedPath,
|
|
172
|
-
restorePath: sourcePath,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
async function restoreStagedArtifacts(artifacts) {
|
|
176
|
-
for (const artifact of artifacts) {
|
|
177
|
-
if (!await pathExists(artifact.stagedPath)) {
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
await moveFile(artifact.stagedPath, artifact.restorePath);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function createEntropyRetainedWalletState(previousState, nowUnixMs) {
|
|
184
|
-
const material = deriveWalletMaterialFromMnemonic(previousState.mnemonic.phrase);
|
|
185
|
-
const walletRootId = createWalletRootId();
|
|
186
|
-
void deriveWalletIdentityMaterial;
|
|
187
|
-
return {
|
|
188
|
-
schemaVersion: 5,
|
|
189
|
-
stateRevision: 1,
|
|
190
|
-
lastWrittenAtUnixMs: nowUnixMs,
|
|
191
|
-
walletRootId,
|
|
192
|
-
network: previousState.network,
|
|
193
|
-
localScriptPubKeyHexes: [material.funding.scriptPubKeyHex],
|
|
194
|
-
mnemonic: {
|
|
195
|
-
phrase: previousState.mnemonic.phrase,
|
|
196
|
-
language: previousState.mnemonic.language,
|
|
197
|
-
},
|
|
198
|
-
keys: {
|
|
199
|
-
masterFingerprintHex: material.keys.masterFingerprintHex,
|
|
200
|
-
accountPath: material.keys.accountPath,
|
|
201
|
-
accountXprv: material.keys.accountXprv,
|
|
202
|
-
accountXpub: material.keys.accountXpub,
|
|
203
|
-
},
|
|
204
|
-
descriptor: {
|
|
205
|
-
privateExternal: material.descriptor.privateExternal,
|
|
206
|
-
publicExternal: material.descriptor.publicExternal,
|
|
207
|
-
checksum: null,
|
|
208
|
-
rangeEnd: previousState.descriptor.rangeEnd,
|
|
209
|
-
safetyMargin: previousState.descriptor.safetyMargin,
|
|
210
|
-
},
|
|
211
|
-
funding: {
|
|
212
|
-
address: material.funding.address,
|
|
213
|
-
scriptPubKeyHex: material.funding.scriptPubKeyHex,
|
|
214
|
-
},
|
|
215
|
-
walletBirthTime: previousState.walletBirthTime,
|
|
216
|
-
managedCoreWallet: {
|
|
217
|
-
walletName: sanitizeWalletName(walletRootId),
|
|
218
|
-
internalPassphrase: createInternalCoreWalletPassphrase(),
|
|
219
|
-
descriptorChecksum: null,
|
|
220
|
-
walletAddress: null,
|
|
221
|
-
walletScriptPubKeyHex: null,
|
|
222
|
-
proofStatus: "not-proven",
|
|
223
|
-
lastImportedAtUnixMs: null,
|
|
224
|
-
lastVerifiedAtUnixMs: null,
|
|
225
|
-
},
|
|
226
|
-
domains: [],
|
|
227
|
-
miningState: {
|
|
228
|
-
runMode: "stopped",
|
|
229
|
-
state: "idle",
|
|
230
|
-
pauseReason: null,
|
|
231
|
-
currentPublishState: "none",
|
|
232
|
-
currentDomain: null,
|
|
233
|
-
currentDomainId: null,
|
|
234
|
-
currentDomainIndex: null,
|
|
235
|
-
currentSenderScriptPubKeyHex: null,
|
|
236
|
-
currentTxid: null,
|
|
237
|
-
currentWtxid: null,
|
|
238
|
-
currentFeeRateSatVb: null,
|
|
239
|
-
currentAbsoluteFeeSats: null,
|
|
240
|
-
currentScore: null,
|
|
241
|
-
currentSentence: null,
|
|
242
|
-
currentEncodedSentenceBytesHex: null,
|
|
243
|
-
currentBip39WordIndices: null,
|
|
244
|
-
currentBlendSeedHex: null,
|
|
245
|
-
currentBlockTargetHeight: null,
|
|
246
|
-
currentReferencedBlockHashDisplay: null,
|
|
247
|
-
currentIntentFingerprintHex: null,
|
|
248
|
-
livePublishInMempool: null,
|
|
249
|
-
currentPublishDecision: null,
|
|
250
|
-
replacementCount: 0,
|
|
251
|
-
currentBlockFeeSpentSats: "0",
|
|
252
|
-
sessionFeeSpentSats: "0",
|
|
253
|
-
lifetimeFeeSpentSats: "0",
|
|
254
|
-
sharedMiningConflictOutpoint: null,
|
|
255
|
-
},
|
|
256
|
-
pendingMutations: [],
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
async function recreateManagedCoreWalletReplicaForReset(options) {
|
|
260
|
-
const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
|
|
261
|
-
dataDir: options.dataDir,
|
|
262
|
-
chain: "main",
|
|
263
|
-
startHeight: 0,
|
|
264
|
-
walletRootId: options.state.walletRootId,
|
|
265
|
-
managedWalletPassphrase: options.state.managedCoreWallet.internalPassphrase,
|
|
266
|
-
});
|
|
267
|
-
const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
|
|
268
|
-
await createManagedWalletReplica(rpc, options.state.walletRootId, {
|
|
269
|
-
managedWalletPassphrase: options.state.managedCoreWallet.internalPassphrase,
|
|
270
|
-
});
|
|
271
|
-
const normalizedDescriptors = await resolveNormalizedWalletDescriptorState(options.state, rpc);
|
|
272
|
-
const walletName = sanitizeWalletName(options.state.walletRootId);
|
|
273
|
-
await withUnlockedManagedCoreWallet({
|
|
274
|
-
rpc,
|
|
275
|
-
walletName,
|
|
276
|
-
internalPassphrase: options.state.managedCoreWallet.internalPassphrase,
|
|
277
|
-
run: async () => {
|
|
278
|
-
const importResults = await rpc.importDescriptors(walletName, [{
|
|
279
|
-
desc: normalizedDescriptors.privateExternal,
|
|
280
|
-
timestamp: options.state.walletBirthTime,
|
|
281
|
-
active: false,
|
|
282
|
-
internal: false,
|
|
283
|
-
range: [0, options.state.descriptor.rangeEnd],
|
|
284
|
-
}]);
|
|
285
|
-
if (!importResults.every((result) => result.success)) {
|
|
286
|
-
throw new Error(`wallet_descriptor_import_failed_${JSON.stringify(importResults)}`);
|
|
287
|
-
}
|
|
288
|
-
},
|
|
289
|
-
});
|
|
290
|
-
const derivedFunding = await rpc.deriveAddresses(normalizedDescriptors.publicExternal, [0, 0]);
|
|
291
|
-
if (derivedFunding[0] !== options.state.funding.address) {
|
|
292
|
-
throw new Error("wallet_funding_address_verification_failed");
|
|
293
|
-
}
|
|
294
|
-
const descriptors = await rpc.listDescriptors(walletName);
|
|
295
|
-
const importedDescriptor = descriptors.descriptors.find((entry) => entry.desc === normalizedDescriptors.publicExternal);
|
|
296
|
-
if (importedDescriptor == null) {
|
|
297
|
-
throw new Error("wallet_descriptor_not_present_after_import");
|
|
298
|
-
}
|
|
299
|
-
const nextState = {
|
|
300
|
-
...options.state,
|
|
301
|
-
stateRevision: options.state.stateRevision + 1,
|
|
302
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
303
|
-
descriptor: {
|
|
304
|
-
...options.state.descriptor,
|
|
305
|
-
privateExternal: normalizedDescriptors.privateExternal,
|
|
306
|
-
publicExternal: normalizedDescriptors.publicExternal,
|
|
307
|
-
checksum: normalizedDescriptors.checksum,
|
|
308
|
-
},
|
|
309
|
-
managedCoreWallet: {
|
|
310
|
-
...options.state.managedCoreWallet,
|
|
311
|
-
walletName,
|
|
312
|
-
descriptorChecksum: normalizedDescriptors.checksum,
|
|
313
|
-
walletAddress: options.state.funding.address,
|
|
314
|
-
walletScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
315
|
-
proofStatus: "ready",
|
|
316
|
-
lastImportedAtUnixMs: options.nowUnixMs,
|
|
317
|
-
lastVerifiedAtUnixMs: options.nowUnixMs,
|
|
318
|
-
},
|
|
319
|
-
};
|
|
320
|
-
await saveWalletState({
|
|
321
|
-
primaryPath: options.paths.walletStatePath,
|
|
322
|
-
backupPath: options.paths.walletStateBackupPath,
|
|
323
|
-
}, nextState, options.access);
|
|
324
|
-
return nextState;
|
|
325
|
-
}
|
|
326
|
-
async function loadWalletForEntropyReset(options) {
|
|
327
|
-
if (options.wallet.rawEnvelope === null) {
|
|
328
|
-
throw new Error("reset_wallet_entropy_reset_unavailable");
|
|
329
|
-
}
|
|
330
|
-
if (options.wallet.mode === "provider-backed") {
|
|
331
|
-
try {
|
|
332
|
-
return {
|
|
333
|
-
loaded: await loadWalletState({
|
|
334
|
-
primaryPath: options.paths.walletStatePath,
|
|
335
|
-
backupPath: options.paths.walletStateBackupPath,
|
|
336
|
-
}, {
|
|
337
|
-
provider: options.provider,
|
|
338
|
-
}),
|
|
339
|
-
access: {
|
|
340
|
-
kind: "provider",
|
|
341
|
-
provider: options.provider,
|
|
342
|
-
},
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
catch {
|
|
346
|
-
throw new Error("reset_wallet_entropy_reset_unavailable");
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
throw new Error("reset_wallet_entropy_reset_unavailable");
|
|
350
|
-
}
|
|
351
|
-
async function collectTrackedManagedProcesses(paths) {
|
|
352
|
-
const trackedProcesses = [];
|
|
353
|
-
const trackedProcessKinds = new Set();
|
|
354
|
-
const serviceLockPaths = new Set();
|
|
355
|
-
const runtimeEntries = await readdir(paths.runtimeRoot, { withFileTypes: true }).catch((error) => {
|
|
356
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
357
|
-
return [];
|
|
358
|
-
}
|
|
359
|
-
throw error;
|
|
360
|
-
});
|
|
361
|
-
for (const entry of runtimeEntries) {
|
|
362
|
-
if (!entry.isDirectory()) {
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
const serviceRoot = join(paths.runtimeRoot, entry.name);
|
|
366
|
-
const bitcoindStatus = await readJsonFileOrNull(join(serviceRoot, "bitcoind-status.json"));
|
|
367
|
-
if (bitcoindStatus?.processId != null && await isProcessAlive(bitcoindStatus.processId)) {
|
|
368
|
-
trackedProcesses.push({
|
|
369
|
-
kind: "managed-bitcoind",
|
|
370
|
-
pid: bitcoindStatus.processId,
|
|
371
|
-
});
|
|
372
|
-
trackedProcessKinds.add("managed-bitcoind");
|
|
373
|
-
serviceLockPaths.add(join(serviceRoot, "bitcoind.lock"));
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
const indexerEntries = await readdir(paths.indexerRoot, { withFileTypes: true }).catch((error) => {
|
|
377
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
378
|
-
return [];
|
|
379
|
-
}
|
|
380
|
-
throw error;
|
|
381
|
-
});
|
|
382
|
-
for (const entry of indexerEntries) {
|
|
383
|
-
if (!entry.isDirectory()) {
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
386
|
-
const status = await readJsonFileOrNull(join(paths.indexerRoot, entry.name, "status.json"));
|
|
387
|
-
if (status?.processId != null && await isProcessAlive(status.processId)) {
|
|
388
|
-
trackedProcesses.push({
|
|
389
|
-
kind: "indexer-daemon",
|
|
390
|
-
pid: status.processId,
|
|
391
|
-
});
|
|
392
|
-
trackedProcessKinds.add("indexer-daemon");
|
|
393
|
-
serviceLockPaths.add(join(paths.runtimeRoot, entry.name, "indexer-daemon.lock"));
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
const miningRuntime = await loadMiningRuntimeStatus(paths.miningStatusPath).catch(() => null);
|
|
397
|
-
if (miningRuntime?.backgroundWorkerPid != null
|
|
398
|
-
&& await isProcessAlive(miningRuntime.backgroundWorkerPid)) {
|
|
399
|
-
trackedProcesses.push({
|
|
400
|
-
kind: "background-mining",
|
|
401
|
-
pid: miningRuntime.backgroundWorkerPid,
|
|
402
|
-
});
|
|
403
|
-
trackedProcessKinds.add("background-mining");
|
|
404
|
-
}
|
|
405
|
-
const seen = new Set();
|
|
406
|
-
const deduped = trackedProcesses.filter((processInfo) => {
|
|
407
|
-
const key = `${processInfo.kind}:${processInfo.pid}`;
|
|
408
|
-
if (seen.has(key)) {
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
411
|
-
seen.add(key);
|
|
412
|
-
return true;
|
|
413
|
-
});
|
|
414
|
-
return {
|
|
415
|
-
trackedProcesses: deduped,
|
|
416
|
-
trackedProcessKinds: [...trackedProcessKinds],
|
|
417
|
-
serviceLockPaths: [...serviceLockPaths].sort(),
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
function dedupeSortedPaths(candidates) {
|
|
421
|
-
return [...new Set(candidates)].sort((left, right) => right.length - left.length);
|
|
422
|
-
}
|
|
423
|
-
function resolveDefaultRemovedRoots(paths) {
|
|
424
|
-
const configRoot = dirname(paths.clientConfigPath);
|
|
425
|
-
return dedupeSortedPaths([
|
|
426
|
-
paths.dataRoot,
|
|
427
|
-
paths.stateRoot,
|
|
428
|
-
paths.runtimeRoot,
|
|
429
|
-
configRoot,
|
|
430
|
-
]);
|
|
431
|
-
}
|
|
432
|
-
function resolveBitcoindPreservingRemovedRoots(paths) {
|
|
433
|
-
const configRoot = dirname(paths.clientConfigPath);
|
|
434
|
-
return dedupeSortedPaths([
|
|
435
|
-
paths.clientDataDir,
|
|
436
|
-
paths.indexerRoot,
|
|
437
|
-
paths.stateRoot,
|
|
438
|
-
paths.runtimeRoot,
|
|
439
|
-
configRoot,
|
|
440
|
-
resolveLegacyHooksRootPath({
|
|
441
|
-
dataRoot: paths.dataRoot,
|
|
442
|
-
clientConfigPath: paths.clientConfigPath,
|
|
443
|
-
}),
|
|
444
|
-
]);
|
|
445
|
-
}
|
|
446
|
-
function resolveRemovedRoots(paths, options = {
|
|
447
|
-
preserveBitcoinDataDir: false,
|
|
448
|
-
}) {
|
|
449
|
-
return options.preserveBitcoinDataDir
|
|
450
|
-
? resolveBitcoindPreservingRemovedRoots(paths)
|
|
451
|
-
: resolveDefaultRemovedRoots(paths);
|
|
452
|
-
}
|
|
453
|
-
function isDeletedByRemovalPlan(removedRoots, targetPath) {
|
|
454
|
-
return removedRoots.some((root) => isPathWithin(root, targetPath));
|
|
455
|
-
}
|
|
456
|
-
async function preflightReset(options) {
|
|
457
|
-
const removedRoots = resolveRemovedRoots(options.paths);
|
|
458
|
-
const rawEnvelope = await loadRawWalletStateEnvelope({
|
|
459
|
-
primaryPath: options.paths.walletStatePath,
|
|
460
|
-
backupPath: options.paths.walletStateBackupPath,
|
|
461
|
-
});
|
|
462
|
-
const snapshotPaths = resolveBootstrapPathsForTesting(options.dataDir, DEFAULT_SNAPSHOT_METADATA);
|
|
463
|
-
const validateSnapshot = options.validateSnapshotFile
|
|
464
|
-
?? ((path) => validateSnapshotFileForTesting(path, DEFAULT_SNAPSHOT_METADATA));
|
|
465
|
-
const hasWalletState = await pathExists(options.paths.walletStatePath) || await pathExists(options.paths.walletStateBackupPath);
|
|
466
|
-
const hasBitcoinDataDir = await pathExists(options.dataDir);
|
|
467
|
-
const bitcoinDataDirWithinResetScope = hasBitcoinDataDir
|
|
468
|
-
&& isDeletedByRemovalPlan(removedRoots, options.dataDir);
|
|
469
|
-
const hasSnapshot = await pathExists(snapshotPaths.snapshotPath);
|
|
470
|
-
const hasPartialSnapshot = await pathExists(snapshotPaths.partialSnapshotPath);
|
|
471
|
-
let snapshotStatus = "not-present";
|
|
472
|
-
if (hasSnapshot) {
|
|
473
|
-
try {
|
|
474
|
-
await validateSnapshot(snapshotPaths.snapshotPath);
|
|
475
|
-
snapshotStatus = "valid";
|
|
476
|
-
}
|
|
477
|
-
catch {
|
|
478
|
-
snapshotStatus = "invalid";
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
else if (hasPartialSnapshot) {
|
|
482
|
-
snapshotStatus = "invalid";
|
|
483
|
-
}
|
|
484
|
-
const tracked = await collectTrackedManagedProcesses(options.paths);
|
|
485
|
-
const secretProviderKeyId = rawEnvelope?.envelope.secretProvider?.keyId ?? null;
|
|
486
|
-
const importedSeedSecretProviderKeyIds = await collectLegacyImportedSeedSecretProviderKeyIds(options.paths);
|
|
487
|
-
return {
|
|
488
|
-
dataRoot: options.paths.dataRoot,
|
|
489
|
-
removedRoots,
|
|
490
|
-
wallet: {
|
|
491
|
-
present: hasWalletState,
|
|
492
|
-
mode: rawEnvelope == null
|
|
493
|
-
? (hasWalletState ? "unknown" : "unknown")
|
|
494
|
-
: rawEnvelope.envelope.secretProvider != null
|
|
495
|
-
? "provider-backed"
|
|
496
|
-
: "unsupported-legacy",
|
|
497
|
-
envelopeSource: rawEnvelope?.source ?? null,
|
|
498
|
-
secretProviderKeyId,
|
|
499
|
-
importedSeedSecretProviderKeyIds,
|
|
500
|
-
rawEnvelope,
|
|
501
|
-
},
|
|
502
|
-
snapshot: {
|
|
503
|
-
status: snapshotStatus,
|
|
504
|
-
path: snapshotPaths.snapshotPath,
|
|
505
|
-
shouldPrompt: snapshotStatus === "valid",
|
|
506
|
-
withinResetScope: isDeletedByRemovalPlan(removedRoots, snapshotPaths.snapshotPath),
|
|
507
|
-
},
|
|
508
|
-
bitcoinDataDir: {
|
|
509
|
-
status: !hasBitcoinDataDir
|
|
510
|
-
? "not-present"
|
|
511
|
-
: bitcoinDataDirWithinResetScope
|
|
512
|
-
? "within-reset-scope"
|
|
513
|
-
: "outside-reset-scope",
|
|
514
|
-
path: options.dataDir,
|
|
515
|
-
shouldPrompt: bitcoinDataDirWithinResetScope,
|
|
516
|
-
},
|
|
517
|
-
trackedProcesses: tracked.trackedProcesses,
|
|
518
|
-
trackedProcessKinds: tracked.trackedProcessKinds,
|
|
519
|
-
serviceLockPaths: tracked.serviceLockPaths,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
async function acquireResetLocks(paths, serviceLockPaths) {
|
|
523
|
-
const lockPaths = [
|
|
524
|
-
paths.walletControlLockPath,
|
|
525
|
-
paths.miningControlLockPath,
|
|
526
|
-
...serviceLockPaths,
|
|
527
|
-
];
|
|
528
|
-
const handles = [];
|
|
529
|
-
try {
|
|
530
|
-
for (const lockPath of lockPaths) {
|
|
531
|
-
handles.push(await acquireFileLock(lockPath, {
|
|
532
|
-
purpose: "wallet-reset",
|
|
533
|
-
walletRootId: null,
|
|
534
|
-
}));
|
|
535
|
-
}
|
|
536
|
-
return handles;
|
|
537
|
-
}
|
|
538
|
-
catch (error) {
|
|
539
|
-
await Promise.all(handles.map(async (handle) => handle.release().catch(() => undefined)));
|
|
540
|
-
throw error;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
async function deleteRemovedRoots(roots) {
|
|
544
|
-
try {
|
|
545
|
-
for (const root of roots) {
|
|
546
|
-
await rm(root, {
|
|
547
|
-
recursive: true,
|
|
548
|
-
force: true,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
catch {
|
|
553
|
-
throw new Error("reset_data_root_delete_failed");
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
async function deleteBootstrapSnapshotArtifacts(dataDir) {
|
|
557
|
-
const snapshotPaths = resolveBootstrapPathsForTesting(dataDir, DEFAULT_SNAPSHOT_METADATA);
|
|
558
|
-
await Promise.all([
|
|
559
|
-
snapshotPaths.snapshotPath,
|
|
560
|
-
snapshotPaths.partialSnapshotPath,
|
|
561
|
-
snapshotPaths.statePath,
|
|
562
|
-
snapshotPaths.quoteStatePath,
|
|
563
|
-
].map(async (path) => rm(path, {
|
|
564
|
-
recursive: false,
|
|
565
|
-
force: true,
|
|
566
|
-
})));
|
|
567
|
-
}
|
|
568
|
-
async function resolveResetExecutionDecision(options) {
|
|
569
|
-
if (!options.prompter.isInteractive) {
|
|
570
|
-
throw new Error("reset_requires_tty");
|
|
571
|
-
}
|
|
572
|
-
await confirmTypedAcknowledgement(options.prompter, {
|
|
573
|
-
expected: "permanently reset",
|
|
574
|
-
prompt: "Type \"permanently reset\" to continue: ",
|
|
575
|
-
errorCode: "reset_typed_ack_required",
|
|
576
|
-
requiresTtyErrorCode: "reset_requires_tty",
|
|
577
|
-
typedAckRequiredErrorCode: "reset_typed_ack_required",
|
|
578
|
-
});
|
|
579
|
-
let walletChoice = "";
|
|
580
|
-
let loadedWalletForEntropyReset = null;
|
|
581
|
-
if (options.preflight.wallet.present) {
|
|
582
|
-
const answer = (await options.prompter.prompt("Wallet reset choice ([Enter] retain base entropy, \"skip\", or \"clear wallet entropy\"): ")).trim();
|
|
583
|
-
if (answer !== "" && answer !== "skip" && answer !== "clear wallet entropy") {
|
|
584
|
-
throw new Error("reset_wallet_choice_invalid");
|
|
585
|
-
}
|
|
586
|
-
walletChoice = answer;
|
|
587
|
-
if (walletChoice === "") {
|
|
588
|
-
loadedWalletForEntropyReset = await loadWalletForEntropyReset({
|
|
589
|
-
wallet: options.preflight.wallet,
|
|
590
|
-
paths: options.paths,
|
|
591
|
-
provider: options.provider,
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
let deleteSnapshot = false;
|
|
596
|
-
let deleteBitcoinDataDir = false;
|
|
597
|
-
if (options.preflight.snapshot.shouldPrompt) {
|
|
598
|
-
const answer = (await options.prompter.prompt("Delete downloaded 910000 UTXO snapshot too? [y/N]: ")).trim().toLowerCase();
|
|
599
|
-
deleteSnapshot = answer === "y" || answer === "yes";
|
|
600
|
-
if (!deleteSnapshot && options.preflight.bitcoinDataDir.shouldPrompt) {
|
|
601
|
-
const bitcoindAnswer = (await options.prompter.prompt("Delete managed Bitcoin datadir too? [y/N]: ")).trim().toLowerCase();
|
|
602
|
-
deleteBitcoinDataDir = bitcoindAnswer === "y" || bitcoindAnswer === "yes";
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
return {
|
|
606
|
-
walletChoice,
|
|
607
|
-
deleteSnapshot,
|
|
608
|
-
deleteBitcoinDataDir,
|
|
609
|
-
loadedWalletForEntropyReset,
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
function determineWalletAction(walletPresent, walletChoice) {
|
|
613
|
-
if (!walletPresent) {
|
|
614
|
-
return "not-present";
|
|
615
|
-
}
|
|
616
|
-
if (walletChoice === "skip") {
|
|
617
|
-
return "kept-unchanged";
|
|
618
|
-
}
|
|
619
|
-
if (walletChoice === "clear wallet entropy") {
|
|
620
|
-
return "deleted";
|
|
621
|
-
}
|
|
622
|
-
return "retain-mnemonic";
|
|
623
|
-
}
|
|
624
|
-
function determineSnapshotResultStatus(options) {
|
|
625
|
-
if (options.snapshotStatus === "not-present") {
|
|
626
|
-
return "not-present";
|
|
627
|
-
}
|
|
628
|
-
if (options.snapshotStatus === "invalid") {
|
|
629
|
-
return "invalid-removed";
|
|
630
|
-
}
|
|
631
|
-
return options.deleteSnapshot ? "deleted" : "preserved";
|
|
632
|
-
}
|
|
633
|
-
function determineBitcoinDataDirResultStatus(options) {
|
|
634
|
-
if (options.bitcoinDataDirStatus === "not-present") {
|
|
635
|
-
return "not-present";
|
|
636
|
-
}
|
|
637
|
-
if (options.bitcoinDataDirStatus === "outside-reset-scope") {
|
|
638
|
-
return "outside-reset-scope";
|
|
639
|
-
}
|
|
640
|
-
if (options.deleteSnapshot || options.deleteBitcoinDataDir) {
|
|
641
|
-
return "deleted";
|
|
642
|
-
}
|
|
643
|
-
return "preserved";
|
|
644
|
-
}
|
|
1
|
+
import { previewResetWallet as previewResetWalletInternal, } from "./reset/preview.js";
|
|
2
|
+
import { resetWallet as resetWalletInternal, } from "./reset/execution.js";
|
|
645
3
|
export async function previewResetWallet(options) {
|
|
646
|
-
|
|
647
|
-
const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
|
|
648
|
-
const preflight = await preflightReset({
|
|
649
|
-
dataDir: options.dataDir,
|
|
650
|
-
provider,
|
|
651
|
-
paths,
|
|
652
|
-
validateSnapshotFile: options.validateSnapshotFile,
|
|
653
|
-
});
|
|
654
|
-
const removedPaths = resolveRemovedRoots(paths, {
|
|
655
|
-
preserveBitcoinDataDir: preflight.snapshot.status === "valid" && preflight.bitcoinDataDir.shouldPrompt,
|
|
656
|
-
});
|
|
657
|
-
return {
|
|
658
|
-
dataRoot: preflight.dataRoot,
|
|
659
|
-
confirmationPhrase: "permanently reset",
|
|
660
|
-
walletPrompt: preflight.wallet.present
|
|
661
|
-
? {
|
|
662
|
-
defaultAction: "retain-mnemonic",
|
|
663
|
-
acceptedInputs: ["", "skip", "clear wallet entropy"],
|
|
664
|
-
entropyRetainingResetAvailable: preflight.wallet.mode === "provider-backed",
|
|
665
|
-
envelopeSource: preflight.wallet.envelopeSource,
|
|
666
|
-
}
|
|
667
|
-
: null,
|
|
668
|
-
bootstrapSnapshot: {
|
|
669
|
-
status: preflight.snapshot.status,
|
|
670
|
-
path: preflight.snapshot.path,
|
|
671
|
-
defaultAction: preflight.snapshot.status === "valid" ? "preserve" : "delete",
|
|
672
|
-
},
|
|
673
|
-
bitcoinDataDir: {
|
|
674
|
-
status: preflight.bitcoinDataDir.status,
|
|
675
|
-
path: preflight.bitcoinDataDir.path,
|
|
676
|
-
conditionalPrompt: preflight.bitcoinDataDir.shouldPrompt
|
|
677
|
-
? {
|
|
678
|
-
prompt: "Delete managed Bitcoin datadir too? [y/N]: ",
|
|
679
|
-
defaultAction: "preserve",
|
|
680
|
-
acceptedInputs: ["", "n", "no", "y", "yes"],
|
|
681
|
-
}
|
|
682
|
-
: null,
|
|
683
|
-
},
|
|
684
|
-
trackedProcessKinds: preflight.trackedProcessKinds,
|
|
685
|
-
willDeleteOsSecrets: providerUsesExternalSecretStore(provider)
|
|
686
|
-
&& (preflight.wallet.secretProviderKeyId !== null
|
|
687
|
-
|| preflight.wallet.importedSeedSecretProviderKeyIds.length > 0),
|
|
688
|
-
removedPaths,
|
|
689
|
-
};
|
|
4
|
+
return await previewResetWalletInternal(options);
|
|
690
5
|
}
|
|
691
6
|
export async function resetWallet(options) {
|
|
692
|
-
|
|
693
|
-
const nowUnixMs = options.nowUnixMs ?? Date.now();
|
|
694
|
-
const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
|
|
695
|
-
const preflight = await preflightReset({
|
|
696
|
-
dataDir: options.dataDir,
|
|
697
|
-
provider,
|
|
698
|
-
paths,
|
|
699
|
-
validateSnapshotFile: options.validateSnapshotFile,
|
|
700
|
-
});
|
|
701
|
-
const decision = await resolveResetExecutionDecision({
|
|
702
|
-
preflight,
|
|
703
|
-
provider,
|
|
704
|
-
prompter: options.prompter,
|
|
705
|
-
paths,
|
|
706
|
-
});
|
|
707
|
-
const walletAction = determineWalletAction(preflight.wallet.present, decision.walletChoice);
|
|
708
|
-
const snapshotResultStatus = determineSnapshotResultStatus({
|
|
709
|
-
snapshotStatus: preflight.snapshot.status,
|
|
710
|
-
deleteSnapshot: decision.deleteSnapshot,
|
|
711
|
-
});
|
|
712
|
-
const bitcoinDataDirResultStatus = determineBitcoinDataDirResultStatus({
|
|
713
|
-
bitcoinDataDirStatus: preflight.bitcoinDataDir.status,
|
|
714
|
-
deleteSnapshot: decision.deleteSnapshot,
|
|
715
|
-
deleteBitcoinDataDir: decision.deleteBitcoinDataDir,
|
|
716
|
-
});
|
|
717
|
-
const removedPaths = resolveRemovedRoots(paths, {
|
|
718
|
-
preserveBitcoinDataDir: bitcoinDataDirResultStatus === "preserved",
|
|
719
|
-
});
|
|
720
|
-
const locks = await acquireResetLocks(paths, preflight.serviceLockPaths);
|
|
721
|
-
await mkdir(dirname(paths.dataRoot), { recursive: true });
|
|
722
|
-
const stagingRoot = await mkdtemp(join(dirname(paths.dataRoot), ".cogcoin-reset-"));
|
|
723
|
-
const stagedWalletArtifacts = [];
|
|
724
|
-
const stagedSnapshotArtifacts = [];
|
|
725
|
-
let stoppedProcesses = {
|
|
726
|
-
managedBitcoind: 0,
|
|
727
|
-
indexerDaemon: 0,
|
|
728
|
-
backgroundMining: 0,
|
|
729
|
-
survivors: 0,
|
|
730
|
-
};
|
|
731
|
-
let rootsDeleted = false;
|
|
732
|
-
let committed = false;
|
|
733
|
-
let newProviderKeyId = null;
|
|
734
|
-
let secretCleanupStatus = "not-found";
|
|
735
|
-
const deletedSecretRefs = [];
|
|
736
|
-
const failedSecretRefs = [];
|
|
737
|
-
const preservedSecretRefs = [];
|
|
738
|
-
let walletOldRootId = extractWalletRootIdHintFromWalletStateEnvelope(preflight.wallet.rawEnvelope?.envelope ?? null)
|
|
739
|
-
?? null;
|
|
740
|
-
let walletNewRootId = null;
|
|
741
|
-
try {
|
|
742
|
-
stoppedProcesses = await terminateTrackedProcesses(preflight.trackedProcesses);
|
|
743
|
-
if (walletAction === "kept-unchanged" || walletAction === "retain-mnemonic") {
|
|
744
|
-
const stagedPrimary = await stageArtifact(paths.walletStatePath, stagingRoot, "wallet/wallet-state.enc");
|
|
745
|
-
const stagedBackup = await stageArtifact(paths.walletStateBackupPath, stagingRoot, "wallet/wallet-state.enc.bak");
|
|
746
|
-
if (stagedPrimary !== null) {
|
|
747
|
-
stagedWalletArtifacts.push(stagedPrimary);
|
|
748
|
-
}
|
|
749
|
-
if (stagedBackup !== null) {
|
|
750
|
-
stagedWalletArtifacts.push(stagedBackup);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
if (snapshotResultStatus === "preserved" && isDeletedByRemovalPlan(removedPaths, preflight.snapshot.path)) {
|
|
754
|
-
const stagedSnapshot = await stageArtifact(preflight.snapshot.path, stagingRoot, "snapshot/utxo-910000.dat");
|
|
755
|
-
if (stagedSnapshot !== null) {
|
|
756
|
-
stagedSnapshotArtifacts.push(stagedSnapshot);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
await deleteRemovedRoots(removedPaths);
|
|
760
|
-
rootsDeleted = true;
|
|
761
|
-
if ((snapshotResultStatus === "deleted" || snapshotResultStatus === "invalid-removed")
|
|
762
|
-
&& !isDeletedByRemovalPlan(removedPaths, preflight.snapshot.path)) {
|
|
763
|
-
await deleteBootstrapSnapshotArtifacts(options.dataDir);
|
|
764
|
-
}
|
|
765
|
-
if (walletAction === "kept-unchanged") {
|
|
766
|
-
await restoreStagedArtifacts(stagedWalletArtifacts);
|
|
767
|
-
}
|
|
768
|
-
else if (walletAction === "retain-mnemonic") {
|
|
769
|
-
if (decision.loadedWalletForEntropyReset === null) {
|
|
770
|
-
throw new Error("reset_wallet_entropy_reset_unavailable");
|
|
771
|
-
}
|
|
772
|
-
let nextState = createEntropyRetainedWalletState(decision.loadedWalletForEntropyReset.loaded.state, nowUnixMs);
|
|
773
|
-
walletOldRootId = decision.loadedWalletForEntropyReset.loaded.state.walletRootId;
|
|
774
|
-
walletNewRootId = nextState.walletRootId;
|
|
775
|
-
const secretReference = createWalletSecretReference(nextState.walletRootId);
|
|
776
|
-
newProviderKeyId = secretReference.keyId;
|
|
777
|
-
await provider.storeSecret(secretReference.keyId, randomBytes(32));
|
|
778
|
-
const nextAccess = {
|
|
779
|
-
provider,
|
|
780
|
-
secretReference,
|
|
781
|
-
};
|
|
782
|
-
await saveWalletState({
|
|
783
|
-
primaryPath: paths.walletStatePath,
|
|
784
|
-
backupPath: paths.walletStateBackupPath,
|
|
785
|
-
}, nextState, nextAccess);
|
|
786
|
-
preservedSecretRefs.push(secretReference.keyId);
|
|
787
|
-
nextState = await recreateManagedCoreWalletReplicaForReset({
|
|
788
|
-
state: nextState,
|
|
789
|
-
access: nextAccess,
|
|
790
|
-
paths,
|
|
791
|
-
dataDir: options.dataDir,
|
|
792
|
-
nowUnixMs,
|
|
793
|
-
attachService: options.attachService,
|
|
794
|
-
rpcFactory: options.rpcFactory,
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
if (snapshotResultStatus === "preserved") {
|
|
798
|
-
await restoreStagedArtifacts(stagedSnapshotArtifacts);
|
|
799
|
-
}
|
|
800
|
-
committed = true;
|
|
801
|
-
const deleteTrackedSecretReference = async (keyId) => {
|
|
802
|
-
try {
|
|
803
|
-
await provider.deleteSecret(keyId);
|
|
804
|
-
deletedSecretRefs.push(keyId);
|
|
805
|
-
}
|
|
806
|
-
catch {
|
|
807
|
-
failedSecretRefs.push(keyId);
|
|
808
|
-
secretCleanupStatus = "failed";
|
|
809
|
-
throw new Error("reset_secret_cleanup_failed");
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
for (const importedSecretKeyId of preflight.wallet.importedSeedSecretProviderKeyIds) {
|
|
813
|
-
await deleteTrackedSecretReference(importedSecretKeyId);
|
|
814
|
-
}
|
|
815
|
-
if (walletAction === "deleted") {
|
|
816
|
-
if (preflight.wallet.secretProviderKeyId !== null) {
|
|
817
|
-
await deleteTrackedSecretReference(preflight.wallet.secretProviderKeyId);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
else if (walletAction === "retain-mnemonic" && preflight.wallet.secretProviderKeyId !== null) {
|
|
821
|
-
if (preflight.wallet.secretProviderKeyId !== newProviderKeyId) {
|
|
822
|
-
await deleteTrackedSecretReference(preflight.wallet.secretProviderKeyId);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
else if (preflight.wallet.secretProviderKeyId !== null) {
|
|
826
|
-
preservedSecretRefs.push(preflight.wallet.secretProviderKeyId);
|
|
827
|
-
}
|
|
828
|
-
if (failedSecretRefs.length > 0) {
|
|
829
|
-
secretCleanupStatus = "failed";
|
|
830
|
-
}
|
|
831
|
-
else if (deletedSecretRefs.length > 0) {
|
|
832
|
-
secretCleanupStatus = "deleted";
|
|
833
|
-
}
|
|
834
|
-
else if (providerUsesExternalSecretStore(provider)
|
|
835
|
-
&& preflight.wallet.secretProviderKeyId === null
|
|
836
|
-
&& preflight.wallet.importedSeedSecretProviderKeyIds.length === 0
|
|
837
|
-
&& preflight.wallet.present
|
|
838
|
-
&& preflight.wallet.rawEnvelope === null) {
|
|
839
|
-
secretCleanupStatus = "unknown";
|
|
840
|
-
}
|
|
841
|
-
else if (preflight.wallet.secretProviderKeyId === null
|
|
842
|
-
&& preflight.wallet.importedSeedSecretProviderKeyIds.length === 0
|
|
843
|
-
&& preflight.wallet.present
|
|
844
|
-
&& preflight.wallet.rawEnvelope === null) {
|
|
845
|
-
secretCleanupStatus = "not-found";
|
|
846
|
-
}
|
|
847
|
-
else if (deletedSecretRefs.length === 0) {
|
|
848
|
-
secretCleanupStatus = "not-found";
|
|
849
|
-
}
|
|
850
|
-
return {
|
|
851
|
-
dataRoot: preflight.dataRoot,
|
|
852
|
-
factoryResetReady: true,
|
|
853
|
-
stoppedProcesses,
|
|
854
|
-
secretCleanupStatus,
|
|
855
|
-
deletedSecretRefs,
|
|
856
|
-
failedSecretRefs,
|
|
857
|
-
preservedSecretRefs,
|
|
858
|
-
walletAction,
|
|
859
|
-
walletOldRootId,
|
|
860
|
-
walletNewRootId,
|
|
861
|
-
bootstrapSnapshot: {
|
|
862
|
-
status: snapshotResultStatus,
|
|
863
|
-
path: preflight.snapshot.path,
|
|
864
|
-
},
|
|
865
|
-
bitcoinDataDir: {
|
|
866
|
-
status: bitcoinDataDirResultStatus,
|
|
867
|
-
path: preflight.bitcoinDataDir.path,
|
|
868
|
-
},
|
|
869
|
-
removedPaths,
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
catch (error) {
|
|
873
|
-
if (!committed && rootsDeleted) {
|
|
874
|
-
await restoreStagedArtifacts(stagedWalletArtifacts).catch(() => undefined);
|
|
875
|
-
await restoreStagedArtifacts(stagedSnapshotArtifacts).catch(() => undefined);
|
|
876
|
-
if (newProviderKeyId !== null) {
|
|
877
|
-
await provider.deleteSecret(newProviderKeyId).catch(() => undefined);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
throw error;
|
|
881
|
-
}
|
|
882
|
-
finally {
|
|
883
|
-
await rm(stagingRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
884
|
-
await Promise.all(locks.reverse().map(async (lock) => lock.release().catch(() => undefined)));
|
|
885
|
-
}
|
|
7
|
+
return await resetWalletInternal(options);
|
|
886
8
|
}
|