@cogcoin/client 1.1.5 → 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 +1 -1
- 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/service.d.ts +2 -7
- package/dist/bitcoind/service.js +46 -94
- 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 +1 -9
- package/dist/wallet/lifecycle/managed-core.js +3 -63
- 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.d.ts +2 -4
- package/dist/wallet/lifecycle/repair.js +77 -318
- 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 +3 -4
- package/dist/wallet/lifecycle/setup.js +45 -351
- package/dist/wallet/lifecycle/types.d.ts +33 -2
- package/dist/wallet/read/context.js +13 -188
- package/package.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type WalletRuntimePaths } from "../runtime.js";
|
|
2
|
+
import { type WalletSecretProvider } from "../state/provider.js";
|
|
3
|
+
import type { WalletManagedCoreContext, WalletManagedCoreDependencies, WalletPrompter, WalletRepairContext, WalletRepairDependencies, WalletSetupContext, WalletSetupDependencies } from "./types.js";
|
|
4
|
+
export declare function pathExists(path: string): Promise<boolean>;
|
|
5
|
+
export declare function walletStateExists(paths: WalletRuntimePaths): Promise<boolean>;
|
|
6
|
+
export declare function resolveWalletManagedCoreContext(options: {
|
|
7
|
+
provider?: WalletSecretProvider;
|
|
8
|
+
nowUnixMs?: number;
|
|
9
|
+
paths?: WalletRuntimePaths;
|
|
10
|
+
} & WalletManagedCoreDependencies): WalletManagedCoreContext;
|
|
11
|
+
export declare function resolveWalletSetupContext(options: {
|
|
12
|
+
dataDir: string;
|
|
13
|
+
prompter: WalletPrompter;
|
|
14
|
+
provider?: WalletSecretProvider;
|
|
15
|
+
nowUnixMs?: number;
|
|
16
|
+
paths?: WalletRuntimePaths;
|
|
17
|
+
} & WalletSetupDependencies): WalletSetupContext;
|
|
18
|
+
export declare function resolveWalletRepairContext(options: {
|
|
19
|
+
dataDir: string;
|
|
20
|
+
databasePath: string;
|
|
21
|
+
provider?: WalletSecretProvider;
|
|
22
|
+
assumeYes?: boolean;
|
|
23
|
+
nowUnixMs?: number;
|
|
24
|
+
paths?: WalletRuntimePaths;
|
|
25
|
+
} & WalletRepairDependencies): WalletRepairContext;
|
|
26
|
+
export declare function acquireWalletControlLock(paths: WalletRuntimePaths, purpose: "wallet-init" | "wallet-show-mnemonic" | "wallet-repair"): Promise<import("../fs/lock.js").FileLockHandle>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { access, constants } from "node:fs/promises";
|
|
2
|
+
import { attachOrStartIndexerDaemon, probeIndexerDaemon, } from "../../bitcoind/indexer-daemon.js";
|
|
3
|
+
import { createRpcClient } from "../../bitcoind/node.js";
|
|
4
|
+
import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
|
|
5
|
+
import { acquireFileLock } from "../fs/lock.js";
|
|
6
|
+
import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
|
|
7
|
+
import { createDefaultWalletSecretProvider, } from "../state/provider.js";
|
|
8
|
+
export async function pathExists(path) {
|
|
9
|
+
try {
|
|
10
|
+
await access(path, constants.F_OK);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function walletStateExists(paths) {
|
|
18
|
+
const [hasPrimaryState, hasBackupState] = await Promise.all([
|
|
19
|
+
pathExists(paths.walletStatePath),
|
|
20
|
+
pathExists(paths.walletStateBackupPath),
|
|
21
|
+
]);
|
|
22
|
+
return hasPrimaryState || hasBackupState;
|
|
23
|
+
}
|
|
24
|
+
export function resolveWalletManagedCoreContext(options) {
|
|
25
|
+
return {
|
|
26
|
+
provider: options.provider ?? createDefaultWalletSecretProvider(),
|
|
27
|
+
paths: options.paths ?? resolveWalletRuntimePathsForTesting(),
|
|
28
|
+
nowUnixMs: options.nowUnixMs ?? Date.now(),
|
|
29
|
+
attachService: options.attachService ?? attachOrStartManagedBitcoindService,
|
|
30
|
+
rpcFactory: options.rpcFactory ?? createRpcClient,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function resolveWalletSetupContext(options) {
|
|
34
|
+
return {
|
|
35
|
+
...resolveWalletManagedCoreContext(options),
|
|
36
|
+
dataDir: options.dataDir,
|
|
37
|
+
prompter: options.prompter,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function resolveWalletRepairContext(options) {
|
|
41
|
+
return {
|
|
42
|
+
...resolveWalletManagedCoreContext(options),
|
|
43
|
+
dataDir: options.dataDir,
|
|
44
|
+
databasePath: options.databasePath,
|
|
45
|
+
assumeYes: options.assumeYes ?? false,
|
|
46
|
+
probeBitcoindService: options.probeBitcoindService ?? probeManagedBitcoindService,
|
|
47
|
+
attachIndexerDaemon: options.attachIndexerDaemon ?? attachOrStartIndexerDaemon,
|
|
48
|
+
probeIndexerDaemon: options.probeIndexerDaemon ?? probeIndexerDaemon,
|
|
49
|
+
requestMiningPreemption: options.requestMiningPreemption,
|
|
50
|
+
startBackgroundMining: options.startBackgroundMining,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export async function acquireWalletControlLock(paths, purpose) {
|
|
54
|
+
return await acquireFileLock(paths.walletControlLockPath, {
|
|
55
|
+
purpose,
|
|
56
|
+
walletRootId: null,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -4,16 +4,8 @@ import type { ManagedCoreWalletReplicaStatus } from "../../bitcoind/types.js";
|
|
|
4
4
|
import type { WalletRuntimePaths } from "../runtime.js";
|
|
5
5
|
import { type WalletSecretProvider } from "../state/provider.js";
|
|
6
6
|
import type { WalletStateV1 } from "../types.js";
|
|
7
|
-
import type { WalletLifecycleRpcClient, WalletManagedCoreDependencies
|
|
7
|
+
import type { WalletLifecycleRpcClient, WalletManagedCoreDependencies } from "./types.js";
|
|
8
8
|
export declare function sanitizeWalletName(walletRootId: string): string;
|
|
9
|
-
export declare function normalizeLoadedWalletStateIfNeeded(options: WalletLifecycleResolvedContext & {
|
|
10
|
-
state: WalletStateV1;
|
|
11
|
-
source: "primary" | "backup";
|
|
12
|
-
dataDir?: string;
|
|
13
|
-
} & WalletManagedCoreDependencies): Promise<{
|
|
14
|
-
state: WalletStateV1;
|
|
15
|
-
source: "primary" | "backup";
|
|
16
|
-
}>;
|
|
17
9
|
export declare function importDescriptorIntoManagedCoreWallet(state: WalletStateV1, provider: WalletSecretProvider, paths: WalletRuntimePaths, dataDir: string, nowUnixMs: number, attachService?: typeof attachOrStartManagedBitcoindService, rpcFactory?: (config: Parameters<typeof createRpcClient>[0]) => WalletLifecycleRpcClient): Promise<WalletStateV1>;
|
|
18
10
|
export declare function recreateManagedCoreWalletReplica(state: WalletStateV1, provider: WalletSecretProvider, paths: WalletRuntimePaths, dataDir: string, nowUnixMs: number, options?: WalletManagedCoreDependencies): Promise<WalletStateV1>;
|
|
19
11
|
export declare function verifyManagedCoreWalletReplica(state: WalletStateV1, dataDir: string, dependencies?: WalletManagedCoreDependencies & {
|
|
@@ -1,75 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { rename } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { attachOrStartManagedBitcoindService, createManagedWalletReplica } from "../../bitcoind/service.js";
|
|
4
4
|
import { createRpcClient } from "../../bitcoind/node.js";
|
|
5
|
-
import {
|
|
6
|
-
import { persistNormalizedWalletDescriptorStateIfNeeded, resolveNormalizedWalletDescriptorState, } from "../descriptor-normalization.js";
|
|
7
|
-
import { normalizeMiningStateRecord } from "../mining/state.js";
|
|
5
|
+
import { resolveNormalizedWalletDescriptorState, } from "../descriptor-normalization.js";
|
|
8
6
|
import { createWalletSecretReference } from "../state/provider.js";
|
|
9
7
|
import { saveWalletState } from "../state/storage.js";
|
|
10
8
|
import { withUnlockedManagedCoreWallet } from "../managed-core-wallet.js";
|
|
9
|
+
import { pathExists } from "./context.js";
|
|
11
10
|
export function sanitizeWalletName(walletRootId) {
|
|
12
11
|
return `cogcoin-${walletRootId}`.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 63);
|
|
13
12
|
}
|
|
14
|
-
async function pathExists(path) {
|
|
15
|
-
try {
|
|
16
|
-
await access(path, constants.F_OK);
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export async function normalizeLoadedWalletStateIfNeeded(options) {
|
|
24
|
-
let state = options.state;
|
|
25
|
-
let source = options.source;
|
|
26
|
-
if (options.dataDir !== undefined) {
|
|
27
|
-
const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
|
|
28
|
-
dataDir: options.dataDir,
|
|
29
|
-
chain: "main",
|
|
30
|
-
startHeight: 0,
|
|
31
|
-
walletRootId: state.walletRootId,
|
|
32
|
-
});
|
|
33
|
-
try {
|
|
34
|
-
const normalized = await persistNormalizedWalletDescriptorStateIfNeeded({
|
|
35
|
-
state,
|
|
36
|
-
access: {
|
|
37
|
-
provider: options.provider,
|
|
38
|
-
secretReference: createWalletSecretReference(state.walletRootId),
|
|
39
|
-
},
|
|
40
|
-
paths: options.paths,
|
|
41
|
-
nowUnixMs: options.nowUnixMs,
|
|
42
|
-
replacePrimary: options.source === "backup",
|
|
43
|
-
rpc: (options.rpcFactory ?? createRpcClient)(node.rpc),
|
|
44
|
-
});
|
|
45
|
-
state = normalized.state;
|
|
46
|
-
source = normalized.changed ? "primary" : options.source;
|
|
47
|
-
const coinControl = await persistWalletCoinControlStateIfNeeded({
|
|
48
|
-
state,
|
|
49
|
-
access: {
|
|
50
|
-
provider: options.provider,
|
|
51
|
-
secretReference: createWalletSecretReference(state.walletRootId),
|
|
52
|
-
},
|
|
53
|
-
paths: options.paths,
|
|
54
|
-
nowUnixMs: options.nowUnixMs,
|
|
55
|
-
replacePrimary: source === "backup",
|
|
56
|
-
rpc: createRpcClient(node.rpc),
|
|
57
|
-
});
|
|
58
|
-
state = coinControl.state;
|
|
59
|
-
source = coinControl.changed ? "primary" : source;
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
await node.stop?.().catch(() => undefined);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
state: normalizeWalletStateRecord({
|
|
67
|
-
...state,
|
|
68
|
-
miningState: normalizeMiningStateRecord(state.miningState),
|
|
69
|
-
}),
|
|
70
|
-
source,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
13
|
export async function importDescriptorIntoManagedCoreWallet(state, provider, paths, dataDir, nowUnixMs, attachService = attachOrStartManagedBitcoindService, rpcFactory = createRpcClient) {
|
|
74
14
|
const node = await attachService({
|
|
75
15
|
dataDir,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ManagedServicePaths } from "../../bitcoind/service-paths.js";
|
|
2
|
+
import type { WalletStateV1 } from "../types.js";
|
|
3
|
+
import type { WalletBitcoindRepairStageResult, WalletRepairContext } from "./types.js";
|
|
4
|
+
export declare function repairManagedBitcoindStage(options: {
|
|
5
|
+
context: WalletRepairContext;
|
|
6
|
+
servicePaths: ManagedServicePaths;
|
|
7
|
+
state: WalletStateV1;
|
|
8
|
+
recoveredFromBackup: boolean;
|
|
9
|
+
repairStateNeedsPersist: boolean;
|
|
10
|
+
}): Promise<WalletBitcoindRepairStageResult>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { normalizeWalletDescriptorState } from "../descriptor-normalization.js";
|
|
2
|
+
import { acquireFileLock } from "../fs/lock.js";
|
|
3
|
+
import { persistWalletCoinControlStateIfNeeded } from "../coin-control.js";
|
|
4
|
+
import { createWalletSecretReference } from "../state/provider.js";
|
|
5
|
+
import { recreateManagedCoreWalletReplica, verifyManagedCoreWalletReplica } from "./managed-core.js";
|
|
6
|
+
import { pathExists } from "./context.js";
|
|
7
|
+
import { clearManagedBitcoindArtifacts, mapBitcoindCompatibilityToRepairIssue, mapBitcoindRepairHealth, waitForProcessExit, } from "./repair-runtime.js";
|
|
8
|
+
export async function repairManagedBitcoindStage(options) {
|
|
9
|
+
let state = options.state;
|
|
10
|
+
let repairStateNeedsPersist = options.repairStateNeedsPersist;
|
|
11
|
+
let bitcoindServiceAction = "none";
|
|
12
|
+
let bitcoindCompatibilityIssue = "none";
|
|
13
|
+
let managedCoreReplicaAction = "none";
|
|
14
|
+
let recreatedManagedCoreWallet = false;
|
|
15
|
+
let initialBitcoindProbe = {
|
|
16
|
+
compatibility: "unreachable",
|
|
17
|
+
status: null,
|
|
18
|
+
error: null,
|
|
19
|
+
};
|
|
20
|
+
let bitcoindPostRepairHealth = "unavailable";
|
|
21
|
+
const bitcoindLock = await acquireFileLock(options.servicePaths.bitcoindLockPath, {
|
|
22
|
+
purpose: "managed-bitcoind-repair",
|
|
23
|
+
walletRootId: state.walletRootId,
|
|
24
|
+
dataDir: options.context.dataDir,
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
initialBitcoindProbe = await options.context.probeBitcoindService({
|
|
28
|
+
dataDir: options.context.dataDir,
|
|
29
|
+
chain: "main",
|
|
30
|
+
startHeight: 0,
|
|
31
|
+
walletRootId: state.walletRootId,
|
|
32
|
+
});
|
|
33
|
+
bitcoindCompatibilityIssue = mapBitcoindCompatibilityToRepairIssue(initialBitcoindProbe.compatibility);
|
|
34
|
+
if (initialBitcoindProbe.compatibility === "service-version-mismatch"
|
|
35
|
+
|| initialBitcoindProbe.compatibility === "wallet-root-mismatch"
|
|
36
|
+
|| initialBitcoindProbe.compatibility === "runtime-mismatch") {
|
|
37
|
+
const processId = initialBitcoindProbe.status?.processId ?? null;
|
|
38
|
+
if (processId === null) {
|
|
39
|
+
throw new Error("managed_bitcoind_process_id_unavailable");
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
process.kill(processId, "SIGTERM");
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
await waitForProcessExit(processId, 15_000, "managed_bitcoind_stop_timeout");
|
|
50
|
+
await clearManagedBitcoindArtifacts(options.servicePaths);
|
|
51
|
+
bitcoindServiceAction = "stopped-incompatible-service";
|
|
52
|
+
}
|
|
53
|
+
else if (initialBitcoindProbe.compatibility === "unreachable") {
|
|
54
|
+
const hasStaleArtifacts = await Promise.all([
|
|
55
|
+
options.servicePaths.bitcoindStatusPath,
|
|
56
|
+
options.servicePaths.bitcoindPidPath,
|
|
57
|
+
options.servicePaths.bitcoindReadyPath,
|
|
58
|
+
options.servicePaths.bitcoindWalletStatusPath,
|
|
59
|
+
].map(pathExists));
|
|
60
|
+
if (hasStaleArtifacts.some(Boolean)) {
|
|
61
|
+
await clearManagedBitcoindArtifacts(options.servicePaths);
|
|
62
|
+
bitcoindServiceAction = "cleared-stale-artifacts";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (initialBitcoindProbe.compatibility === "protocol-error") {
|
|
66
|
+
throw new Error(initialBitcoindProbe.error ?? "managed_bitcoind_protocol_error");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await bitcoindLock.release();
|
|
71
|
+
}
|
|
72
|
+
const bitcoindHandle = await options.context.attachService({
|
|
73
|
+
dataDir: options.context.dataDir,
|
|
74
|
+
chain: "main",
|
|
75
|
+
startHeight: 0,
|
|
76
|
+
walletRootId: state.walletRootId,
|
|
77
|
+
});
|
|
78
|
+
try {
|
|
79
|
+
const rpc = options.context.rpcFactory(bitcoindHandle.rpc);
|
|
80
|
+
const normalizedDescriptorState = await normalizeWalletDescriptorState(state, rpc);
|
|
81
|
+
if (normalizedDescriptorState.changed) {
|
|
82
|
+
state = normalizedDescriptorState.state;
|
|
83
|
+
repairStateNeedsPersist = true;
|
|
84
|
+
}
|
|
85
|
+
const reconciledCoinControl = await persistWalletCoinControlStateIfNeeded({
|
|
86
|
+
state,
|
|
87
|
+
access: {
|
|
88
|
+
provider: options.context.provider,
|
|
89
|
+
secretReference: createWalletSecretReference(state.walletRootId),
|
|
90
|
+
},
|
|
91
|
+
paths: options.context.paths,
|
|
92
|
+
nowUnixMs: options.context.nowUnixMs,
|
|
93
|
+
replacePrimary: options.recoveredFromBackup && !repairStateNeedsPersist,
|
|
94
|
+
rpc,
|
|
95
|
+
});
|
|
96
|
+
state = reconciledCoinControl.state;
|
|
97
|
+
if (reconciledCoinControl.changed) {
|
|
98
|
+
repairStateNeedsPersist = false;
|
|
99
|
+
}
|
|
100
|
+
let replica = await verifyManagedCoreWalletReplica(state, options.context.dataDir, {
|
|
101
|
+
nodeHandle: bitcoindHandle,
|
|
102
|
+
attachService: options.context.attachService,
|
|
103
|
+
rpcFactory: options.context.rpcFactory,
|
|
104
|
+
});
|
|
105
|
+
if (replica.proofStatus !== "ready") {
|
|
106
|
+
state = await recreateManagedCoreWalletReplica(state, options.context.provider, options.context.paths, options.context.dataDir, options.context.nowUnixMs, {
|
|
107
|
+
attachService: options.context.attachService,
|
|
108
|
+
rpcFactory: options.context.rpcFactory,
|
|
109
|
+
});
|
|
110
|
+
recreatedManagedCoreWallet = true;
|
|
111
|
+
managedCoreReplicaAction = "recreated";
|
|
112
|
+
repairStateNeedsPersist = false;
|
|
113
|
+
replica = await verifyManagedCoreWalletReplica(state, options.context.dataDir, {
|
|
114
|
+
nodeHandle: bitcoindHandle,
|
|
115
|
+
attachService: options.context.attachService,
|
|
116
|
+
rpcFactory: options.context.rpcFactory,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const finalBitcoindStatus = await bitcoindHandle.refreshServiceStatus?.() ?? null;
|
|
120
|
+
const chainInfo = await rpc.getBlockchainInfo();
|
|
121
|
+
bitcoindPostRepairHealth = mapBitcoindRepairHealth({
|
|
122
|
+
serviceState: finalBitcoindStatus?.state ?? null,
|
|
123
|
+
catchingUp: chainInfo.blocks < chainInfo.headers,
|
|
124
|
+
replica,
|
|
125
|
+
});
|
|
126
|
+
if (bitcoindServiceAction === "none" && initialBitcoindProbe.compatibility === "unreachable") {
|
|
127
|
+
bitcoindServiceAction = "restarted-compatible-service";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
await bitcoindHandle.stop?.().catch(() => undefined);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
state,
|
|
135
|
+
repairStateNeedsPersist,
|
|
136
|
+
recreatedManagedCoreWallet,
|
|
137
|
+
bitcoindServiceAction,
|
|
138
|
+
bitcoindCompatibilityIssue,
|
|
139
|
+
managedCoreReplicaAction,
|
|
140
|
+
bitcoindPostRepairHealth,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ManagedServicePaths } from "../../bitcoind/service-paths.js";
|
|
2
|
+
import type { WalletStateV1 } from "../types.js";
|
|
3
|
+
import type { WalletIndexerRepairStageResult, WalletRepairContext } from "./types.js";
|
|
4
|
+
export declare function repairManagedIndexerStage(options: {
|
|
5
|
+
context: WalletRepairContext;
|
|
6
|
+
servicePaths: ManagedServicePaths;
|
|
7
|
+
state: WalletStateV1;
|
|
8
|
+
}): Promise<WalletIndexerRepairStageResult>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { acquireFileLock } from "../fs/lock.js";
|
|
2
|
+
import { pathExists } from "./context.js";
|
|
3
|
+
import { clearIndexerDaemonArtifacts, ensureIndexerDatabaseHealthy, mapIndexerCompatibilityToRepairIssue, verifyIndexerPostRepairHealth, waitForProcessExit, } from "./repair-runtime.js";
|
|
4
|
+
export async function repairManagedIndexerStage(options) {
|
|
5
|
+
let indexerDaemonAction = "none";
|
|
6
|
+
let indexerCompatibilityIssue = "none";
|
|
7
|
+
let initialIndexerDaemonInstanceId = null;
|
|
8
|
+
const indexerLock = await acquireFileLock(options.servicePaths.indexerDaemonLockPath, {
|
|
9
|
+
purpose: "indexer-daemon-repair",
|
|
10
|
+
walletRootId: options.state.walletRootId,
|
|
11
|
+
dataDir: options.context.dataDir,
|
|
12
|
+
databasePath: options.context.databasePath,
|
|
13
|
+
});
|
|
14
|
+
let resetIndexerDatabase = false;
|
|
15
|
+
try {
|
|
16
|
+
const initialProbe = await options.context.probeIndexerDaemon({
|
|
17
|
+
dataDir: options.context.dataDir,
|
|
18
|
+
walletRootId: options.state.walletRootId,
|
|
19
|
+
});
|
|
20
|
+
indexerCompatibilityIssue = mapIndexerCompatibilityToRepairIssue(initialProbe.compatibility);
|
|
21
|
+
initialIndexerDaemonInstanceId = initialProbe.status?.daemonInstanceId ?? null;
|
|
22
|
+
if (initialProbe.compatibility === "compatible") {
|
|
23
|
+
await initialProbe.client?.close().catch(() => undefined);
|
|
24
|
+
}
|
|
25
|
+
else if (initialProbe.compatibility === "service-version-mismatch"
|
|
26
|
+
|| initialProbe.compatibility === "wallet-root-mismatch"
|
|
27
|
+
|| initialProbe.compatibility === "schema-mismatch") {
|
|
28
|
+
const processId = initialProbe.status?.processId ?? null;
|
|
29
|
+
if (processId === null) {
|
|
30
|
+
throw new Error("indexer_daemon_process_id_unavailable");
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
process.kill(processId, "SIGTERM");
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
await waitForProcessExit(processId);
|
|
41
|
+
await clearIndexerDaemonArtifacts(options.servicePaths);
|
|
42
|
+
indexerDaemonAction = "stopped-incompatible-daemon";
|
|
43
|
+
}
|
|
44
|
+
else if (initialProbe.compatibility === "unreachable") {
|
|
45
|
+
const hasStaleArtifacts = await Promise.all([
|
|
46
|
+
options.servicePaths.indexerDaemonSocketPath,
|
|
47
|
+
options.servicePaths.indexerDaemonStatusPath,
|
|
48
|
+
].map(pathExists));
|
|
49
|
+
if (hasStaleArtifacts.some(Boolean)) {
|
|
50
|
+
await clearIndexerDaemonArtifacts(options.servicePaths);
|
|
51
|
+
indexerDaemonAction = "cleared-stale-artifacts";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
throw new Error(initialProbe.error ?? "indexer_daemon_protocol_error");
|
|
56
|
+
}
|
|
57
|
+
resetIndexerDatabase = await ensureIndexerDatabaseHealthy({
|
|
58
|
+
databasePath: options.context.databasePath,
|
|
59
|
+
dataDir: options.context.dataDir,
|
|
60
|
+
walletRootId: options.state.walletRootId,
|
|
61
|
+
resetIfNeeded: options.context.assumeYes,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
await indexerLock.release();
|
|
66
|
+
}
|
|
67
|
+
let preAttachIndexerDaemonInstanceId = null;
|
|
68
|
+
const preAttachProbe = await options.context.probeIndexerDaemon({
|
|
69
|
+
dataDir: options.context.dataDir,
|
|
70
|
+
walletRootId: options.state.walletRootId,
|
|
71
|
+
});
|
|
72
|
+
if (preAttachProbe.compatibility === "compatible") {
|
|
73
|
+
preAttachIndexerDaemonInstanceId = preAttachProbe.status?.daemonInstanceId ?? null;
|
|
74
|
+
await preAttachProbe.client?.close().catch(() => undefined);
|
|
75
|
+
}
|
|
76
|
+
else if (preAttachProbe.compatibility !== "unreachable") {
|
|
77
|
+
throw new Error(preAttachProbe.error ?? "indexer_daemon_protocol_error");
|
|
78
|
+
}
|
|
79
|
+
const daemon = await options.context.attachIndexerDaemon({
|
|
80
|
+
dataDir: options.context.dataDir,
|
|
81
|
+
databasePath: options.context.databasePath,
|
|
82
|
+
walletRootId: options.state.walletRootId,
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
const { health: indexerPostRepairHealth, daemonInstanceId: postRepairDaemonInstanceId, } = await verifyIndexerPostRepairHealth({
|
|
86
|
+
daemon,
|
|
87
|
+
probeIndexerDaemon: options.context.probeIndexerDaemon,
|
|
88
|
+
dataDir: options.context.dataDir,
|
|
89
|
+
walletRootId: options.state.walletRootId,
|
|
90
|
+
nowUnixMs: options.context.nowUnixMs,
|
|
91
|
+
});
|
|
92
|
+
const restartedIndexerDaemon = indexerDaemonAction !== "none" || preAttachProbe.compatibility === "unreachable";
|
|
93
|
+
if (restartedIndexerDaemon
|
|
94
|
+
&& initialIndexerDaemonInstanceId !== null
|
|
95
|
+
&& postRepairDaemonInstanceId === initialIndexerDaemonInstanceId) {
|
|
96
|
+
throw new Error("indexer_daemon_repair_identity_not_rotated");
|
|
97
|
+
}
|
|
98
|
+
if (!restartedIndexerDaemon
|
|
99
|
+
&& preAttachProbe.compatibility === "compatible"
|
|
100
|
+
&& preAttachIndexerDaemonInstanceId !== null
|
|
101
|
+
&& postRepairDaemonInstanceId !== preAttachIndexerDaemonInstanceId) {
|
|
102
|
+
throw new Error("indexer_daemon_repair_identity_changed");
|
|
103
|
+
}
|
|
104
|
+
if (indexerDaemonAction === "none" && preAttachProbe.compatibility === "unreachable") {
|
|
105
|
+
indexerDaemonAction = "restarted-compatible-daemon";
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
resetIndexerDatabase,
|
|
109
|
+
indexerDaemonAction,
|
|
110
|
+
indexerCompatibilityIssue,
|
|
111
|
+
indexerPostRepairHealth,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await daemon.close().catch(() => undefined);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { type WalletRuntimePaths } from "../runtime.js";
|
|
2
|
-
import { type WalletSecretProvider } from "../state/provider.js";
|
|
3
1
|
import type { WalletRepairDependencies, WalletRepairResult } from "./types.js";
|
|
4
2
|
export declare function repairWallet(options: {
|
|
5
3
|
dataDir: string;
|
|
6
4
|
databasePath: string;
|
|
7
|
-
provider?: WalletSecretProvider;
|
|
5
|
+
provider?: import("../state/provider.js").WalletSecretProvider;
|
|
8
6
|
assumeYes?: boolean;
|
|
9
7
|
nowUnixMs?: number;
|
|
10
|
-
paths?: WalletRuntimePaths;
|
|
8
|
+
paths?: import("../runtime.js").WalletRuntimePaths;
|
|
11
9
|
} & WalletRepairDependencies): Promise<WalletRepairResult>;
|