@cogcoin/client 1.1.9 → 1.1.11
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/client/managed-client.d.ts +2 -0
- package/dist/bitcoind/client/managed-client.js +6 -0
- package/dist/bitcoind/indexer-daemon/background-follow.d.ts +23 -0
- package/dist/bitcoind/indexer-daemon/background-follow.js +132 -0
- package/dist/bitcoind/indexer-daemon/client.d.ts +12 -0
- package/dist/bitcoind/indexer-daemon/client.js +137 -0
- package/dist/bitcoind/indexer-daemon/lifecycle.d.ts +30 -0
- package/dist/bitcoind/indexer-daemon/lifecycle.js +153 -0
- package/dist/bitcoind/indexer-daemon/process.d.ts +35 -0
- package/dist/bitcoind/indexer-daemon/process.js +140 -0
- package/dist/bitcoind/indexer-daemon/runtime.d.ts +23 -0
- package/dist/bitcoind/indexer-daemon/runtime.js +204 -0
- package/dist/bitcoind/indexer-daemon/server.d.ts +12 -0
- package/dist/bitcoind/indexer-daemon/server.js +87 -0
- package/dist/bitcoind/indexer-daemon/snapshot-leases.d.ts +23 -0
- package/dist/bitcoind/indexer-daemon/snapshot-leases.js +139 -0
- package/dist/bitcoind/indexer-daemon/status.d.ts +23 -0
- package/dist/bitcoind/indexer-daemon/status.js +282 -0
- package/dist/bitcoind/indexer-daemon/types.d.ts +141 -0
- package/dist/bitcoind/indexer-daemon/types.js +1 -0
- package/dist/bitcoind/indexer-daemon-main.js +14 -665
- package/dist/bitcoind/indexer-daemon.d.ts +4 -132
- package/dist/bitcoind/indexer-daemon.js +2 -417
- package/dist/bitcoind/managed-bitcoind-service-config.d.ts +30 -0
- package/dist/bitcoind/managed-bitcoind-service-config.js +202 -0
- package/dist/bitcoind/managed-bitcoind-service-lifecycle.d.ts +28 -0
- package/dist/bitcoind/managed-bitcoind-service-lifecycle.js +296 -0
- package/dist/bitcoind/managed-bitcoind-service-process.d.ts +8 -0
- package/dist/bitcoind/managed-bitcoind-service-process.js +48 -0
- package/dist/bitcoind/managed-bitcoind-service-replica.d.ts +8 -0
- package/dist/bitcoind/managed-bitcoind-service-replica.js +142 -0
- package/dist/bitcoind/managed-bitcoind-service-status.d.ts +42 -0
- package/dist/bitcoind/managed-bitcoind-service-status.js +170 -0
- package/dist/bitcoind/managed-bitcoind-service-types.d.ts +36 -0
- package/dist/bitcoind/managed-bitcoind-service-types.js +1 -0
- package/dist/bitcoind/service.d.ts +7 -63
- package/dist/bitcoind/service.js +7 -797
- package/dist/cli/mining-format.js +6 -1
- package/dist/cli/wallet-format/balance.js +1 -1
- package/dist/client/default-client.d.ts +3 -1
- package/dist/client/default-client.js +22 -0
- package/dist/types.d.ts +13 -1
- package/dist/wallet/fs/atomic.d.ts +11 -2
- package/dist/wallet/fs/atomic.js +45 -5
- package/dist/wallet/mining/cycle.js +4 -4
- package/dist/wallet/mining/engine-types.d.ts +1 -0
- package/dist/wallet/mining/engine-types.js +9 -1
- package/dist/wallet/mining/projection.d.ts +1 -0
- package/dist/wallet/mining/projection.js +15 -1
- package/dist/wallet/mining/publish.js +3 -6
- package/dist/wallet/mining/runner.js +30 -18
- package/dist/wallet/mining/visualizer-sync.js +7 -9
- package/dist/wallet/mining/visualizer.js +9 -7
- package/dist/wallet/read/context.d.ts +4 -10
- package/dist/wallet/read/context.js +6 -228
- package/dist/wallet/read/local-state.d.ts +36 -0
- package/dist/wallet/read/local-state.js +259 -0
- package/dist/wallet/read/managed-bitcoind.d.ts +30 -0
- package/dist/wallet/read/managed-bitcoind.js +138 -0
- package/dist/wallet/read/managed-indexer.d.ts +23 -0
- package/dist/wallet/read/managed-indexer.js +87 -0
- package/dist/wallet/read/managed-services.d.ts +6 -21
- package/dist/wallet/read/managed-services.js +23 -196
- package/dist/wallet/read/types.d.ts +1 -0
- package/package.json +1 -1
package/dist/bitcoind/service.js
CHANGED
|
@@ -1,803 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import net from "node:net";
|
|
8
|
-
import { getBitcoindPath } from "@cogcoin/bitcoin";
|
|
9
|
-
import { acquireFileLock, FileLockBusyError } from "../wallet/fs/lock.js";
|
|
10
|
-
import { writeFileAtomic } from "../wallet/fs/atomic.js";
|
|
11
|
-
import { writeRuntimeStatusFile } from "../wallet/fs/status-file.js";
|
|
12
|
-
import { stopIndexerDaemonServiceWithLockHeld } from "./indexer-daemon.js";
|
|
13
|
-
import { mapManagedBitcoindRuntimeProbeFailure, mapManagedBitcoindValidationError, validateManagedBitcoindObservedStatus, } from "./managed-runtime/bitcoind-policy.js";
|
|
14
|
-
import { listManagedBitcoindStatusCandidates, readManagedBitcoindObservedStatus, } from "./managed-runtime/bitcoind-status.js";
|
|
15
|
-
import { attachOrStartManagedBitcoindRuntime, probeManagedBitcoindRuntime, } from "./managed-runtime/bitcoind-runtime.js";
|
|
16
|
-
import { readJsonFileIfPresent } from "./managed-runtime/status.js";
|
|
17
|
-
import { createRpcClient, validateNodeConfigForTesting } from "./node.js";
|
|
18
|
-
import { resolveManagedServicePaths, UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
|
|
19
|
-
import { DEFAULT_MANAGED_BITCOIND_FOLLOW_POLL_INTERVAL_MS, MANAGED_BITCOIND_SERVICE_API_VERSION as MANAGED_BITCOIND_SERVICE_API_VERSION_VALUE, } from "./types.js";
|
|
20
|
-
const execFileAsync = promisify(execFile);
|
|
21
|
-
const LOCAL_HOST = "127.0.0.1";
|
|
22
|
-
const SUPPORTED_BITCOIND_VERSION = "30.2.0";
|
|
23
|
-
const DEFAULT_STARTUP_TIMEOUT_MS = 60_000;
|
|
24
|
-
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 15_000;
|
|
25
|
-
const DEFAULT_DBCACHE_MIB = 450;
|
|
26
|
-
const claimedUninitializedRuntimeKeys = new Set();
|
|
27
|
-
const GIB = 1024 ** 3;
|
|
28
|
-
export function resolveManagedBitcoindDbcacheMiB(totalRamBytes) {
|
|
29
|
-
if (!Number.isFinite(totalRamBytes) || totalRamBytes <= 0) {
|
|
30
|
-
return DEFAULT_DBCACHE_MIB;
|
|
31
|
-
}
|
|
32
|
-
if (totalRamBytes < 8 * GIB) {
|
|
33
|
-
return 450;
|
|
34
|
-
}
|
|
35
|
-
if (totalRamBytes < 16 * GIB) {
|
|
36
|
-
return 768;
|
|
37
|
-
}
|
|
38
|
-
if (totalRamBytes < 32 * GIB) {
|
|
39
|
-
return 1024;
|
|
40
|
-
}
|
|
41
|
-
return 2048;
|
|
42
|
-
}
|
|
43
|
-
function detectManagedBitcoindDbcacheMiB() {
|
|
44
|
-
try {
|
|
45
|
-
return resolveManagedBitcoindDbcacheMiB(totalmem());
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return DEFAULT_DBCACHE_MIB;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function sleep(ms) {
|
|
52
|
-
return new Promise((resolve) => {
|
|
53
|
-
setTimeout(resolve, ms);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
async function waitForProcessExit(pid, timeoutMs, errorCode) {
|
|
57
|
-
const deadline = Date.now() + timeoutMs;
|
|
58
|
-
while (Date.now() < deadline) {
|
|
59
|
-
if (!await isProcessAlive(pid)) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
await sleep(250);
|
|
63
|
-
}
|
|
64
|
-
throw new Error(errorCode);
|
|
65
|
-
}
|
|
66
|
-
async function acquireFileLockWithRetry(lockPath, metadata, timeoutMs) {
|
|
67
|
-
const deadline = Date.now() + timeoutMs;
|
|
68
|
-
while (true) {
|
|
69
|
-
try {
|
|
70
|
-
return await acquireFileLock(lockPath, metadata);
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
if (!(error instanceof FileLockBusyError) || Date.now() >= deadline) {
|
|
74
|
-
throw error;
|
|
75
|
-
}
|
|
76
|
-
await sleep(250);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function getWalletReplicaName(walletRootId) {
|
|
81
|
-
return `cogcoin-${walletRootId}`.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 63);
|
|
82
|
-
}
|
|
83
|
-
async function allocatePort() {
|
|
84
|
-
return new Promise((resolve, reject) => {
|
|
85
|
-
const server = net.createServer();
|
|
86
|
-
server.listen(0, LOCAL_HOST, () => {
|
|
87
|
-
const address = server.address();
|
|
88
|
-
if (!address || typeof address === "string") {
|
|
89
|
-
server.close();
|
|
90
|
-
reject(new Error("bitcoind_port_allocation_failed"));
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const { port } = address;
|
|
94
|
-
server.close((error) => {
|
|
95
|
-
if (error) {
|
|
96
|
-
reject(error);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
resolve(port);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
server.on("error", reject);
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
async function allocateDistinctPort(reserved) {
|
|
106
|
-
while (true) {
|
|
107
|
-
const port = await allocatePort();
|
|
108
|
-
if (!reserved.has(port)) {
|
|
109
|
-
reserved.add(port);
|
|
110
|
-
return port;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
async function verifyBitcoindVersion(bitcoindPath) {
|
|
115
|
-
const { stdout } = await execFileAsync(bitcoindPath, ["-nosettings=1", "-version"]);
|
|
116
|
-
if (!stdout.includes("Bitcoin Core") || !stdout.includes(`v${SUPPORTED_BITCOIND_VERSION}`)) {
|
|
117
|
-
throw new Error("bitcoind_version_unsupported");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function getCookieFile(dataDir, chain) {
|
|
121
|
-
return chain === "main" ? join(dataDir, ".cookie") : join(dataDir, chain, ".cookie");
|
|
122
|
-
}
|
|
123
|
-
function createMissingManagedWalletReplicaStatus(walletRootId, message) {
|
|
124
|
-
return {
|
|
125
|
-
walletRootId,
|
|
126
|
-
walletName: getWalletReplicaName(walletRootId),
|
|
127
|
-
loaded: false,
|
|
128
|
-
descriptors: false,
|
|
129
|
-
privateKeysEnabled: false,
|
|
130
|
-
created: false,
|
|
131
|
-
proofStatus: "missing",
|
|
132
|
-
descriptorChecksum: null,
|
|
133
|
-
fundingAddress0: null,
|
|
134
|
-
fundingScriptPubKeyHex0: null,
|
|
135
|
-
message,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
async function isProcessAlive(pid) {
|
|
139
|
-
if (pid === null) {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
process.kill(pid, 0);
|
|
144
|
-
return true;
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
if (error instanceof Error && "code" in error && error.code === "ESRCH") {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
async function waitForCookie(cookieFile, timeoutMs) {
|
|
154
|
-
const deadline = Date.now() + timeoutMs;
|
|
155
|
-
while (Date.now() < deadline) {
|
|
156
|
-
try {
|
|
157
|
-
await access(cookieFile, constants.R_OK);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
await sleep(250);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
throw new Error("bitcoind_cookie_timeout");
|
|
165
|
-
}
|
|
166
|
-
async function waitForRpcReady(rpc, cookieFile, expectedChain, timeoutMs) {
|
|
167
|
-
await waitForCookie(cookieFile, timeoutMs);
|
|
168
|
-
const deadline = Date.now() + timeoutMs;
|
|
169
|
-
let lastError = null;
|
|
170
|
-
while (Date.now() < deadline) {
|
|
171
|
-
try {
|
|
172
|
-
const info = await rpc.getBlockchainInfo();
|
|
173
|
-
if (info.chain !== expectedChain) {
|
|
174
|
-
throw new Error(`bitcoind_chain_expected_${expectedChain}_got_${info.chain}`);
|
|
175
|
-
}
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
lastError = error;
|
|
180
|
-
await sleep(250);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
throw lastError instanceof Error ? lastError : new Error("bitcoind_rpc_timeout");
|
|
184
|
-
}
|
|
185
|
-
function createBitcoindServiceStatus(options) {
|
|
186
|
-
return {
|
|
187
|
-
serviceApiVersion: MANAGED_BITCOIND_SERVICE_API_VERSION_VALUE,
|
|
188
|
-
binaryVersion: options.binaryVersion,
|
|
189
|
-
buildId: null,
|
|
190
|
-
serviceInstanceId: options.serviceInstanceId,
|
|
191
|
-
state: options.state,
|
|
192
|
-
processId: options.processId,
|
|
193
|
-
walletRootId: options.walletRootId,
|
|
194
|
-
chain: options.chain,
|
|
195
|
-
dataDir: options.dataDir,
|
|
196
|
-
runtimeRoot: options.runtimeRoot,
|
|
197
|
-
startHeight: options.startHeight,
|
|
198
|
-
rpc: options.rpc,
|
|
199
|
-
zmq: options.zmq,
|
|
200
|
-
p2pPort: options.p2pPort,
|
|
201
|
-
getblockArchiveEndHeight: options.getblockArchiveEndHeight,
|
|
202
|
-
getblockArchiveSha256: options.getblockArchiveSha256,
|
|
203
|
-
walletReplica: options.walletReplica,
|
|
204
|
-
startedAtUnixMs: options.startedAtUnixMs,
|
|
205
|
-
heartbeatAtUnixMs: options.heartbeatAtUnixMs,
|
|
206
|
-
updatedAtUnixMs: options.heartbeatAtUnixMs,
|
|
207
|
-
lastError: options.lastError,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
async function probeManagedBitcoindStatusCandidate(status, options, runtimeRoot) {
|
|
211
|
-
try {
|
|
212
|
-
validateManagedBitcoindObservedStatus(status, {
|
|
213
|
-
chain: options.chain,
|
|
214
|
-
dataDir: options.dataDir ?? "",
|
|
215
|
-
runtimeRoot,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
catch (error) {
|
|
219
|
-
return mapManagedBitcoindValidationError(error, status);
|
|
220
|
-
}
|
|
221
|
-
const rpc = createRpcClient(status.rpc);
|
|
222
|
-
try {
|
|
223
|
-
await waitForRpcReady(rpc, status.rpc.cookieFile, status.chain, options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS);
|
|
224
|
-
await validateNodeConfigForTesting(rpc, status.chain, status.zmq.endpoint);
|
|
225
|
-
return {
|
|
226
|
-
compatibility: "compatible",
|
|
227
|
-
status,
|
|
228
|
-
error: null,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
return mapManagedBitcoindRuntimeProbeFailure(error, status);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
async function resolveRuntimeConfig(statusPath, configPath, options) {
|
|
236
|
-
const previousStatus = await readJsonFileIfPresent(statusPath);
|
|
237
|
-
const previousConfig = await readJsonFileIfPresent(configPath);
|
|
238
|
-
const reserved = new Set();
|
|
239
|
-
const rpcPort = options.rpcPort
|
|
240
|
-
?? previousStatus?.rpc.port
|
|
241
|
-
?? previousConfig?.rpc?.port
|
|
242
|
-
?? await allocateDistinctPort(reserved);
|
|
243
|
-
reserved.add(rpcPort);
|
|
244
|
-
const zmqPort = options.zmqPort
|
|
245
|
-
?? previousStatus?.zmq.port
|
|
246
|
-
?? previousConfig?.zmqPort
|
|
247
|
-
?? await allocateDistinctPort(reserved);
|
|
248
|
-
reserved.add(zmqPort);
|
|
249
|
-
const p2pPort = options.p2pPort
|
|
250
|
-
?? previousStatus?.p2pPort
|
|
251
|
-
?? previousConfig?.p2pPort
|
|
252
|
-
?? await allocateDistinctPort(reserved);
|
|
253
|
-
return {
|
|
254
|
-
chain: options.chain,
|
|
255
|
-
rpc: {
|
|
256
|
-
url: `http://${LOCAL_HOST}:${rpcPort}`,
|
|
257
|
-
cookieFile: getCookieFile(options.dataDir ?? "", options.chain),
|
|
258
|
-
port: rpcPort,
|
|
259
|
-
},
|
|
260
|
-
zmqPort,
|
|
261
|
-
p2pPort,
|
|
262
|
-
dbcacheMiB: detectManagedBitcoindDbcacheMiB(),
|
|
263
|
-
getblockArchiveEndHeight: options.getblockArchiveEndHeight ?? null,
|
|
264
|
-
getblockArchiveSha256: options.getblockArchiveSha256 ?? null,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
async function writeBitcoinConf(filePath, options, runtimeConfig) {
|
|
268
|
-
const walletDir = join(options.dataDir ?? "", "wallets");
|
|
269
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
270
|
-
await mkdir(walletDir, { recursive: true });
|
|
271
|
-
const lines = [
|
|
272
|
-
"server=1",
|
|
273
|
-
"prune=0",
|
|
274
|
-
"dnsseed=1",
|
|
275
|
-
"listen=0",
|
|
276
|
-
`dbcache=${runtimeConfig.dbcacheMiB}`,
|
|
277
|
-
`rpcbind=${LOCAL_HOST}`,
|
|
278
|
-
`rpcallowip=${LOCAL_HOST}`,
|
|
279
|
-
`rpcport=${runtimeConfig.rpc.port}`,
|
|
280
|
-
`port=${runtimeConfig.p2pPort}`,
|
|
281
|
-
`zmqpubhashblock=tcp://${LOCAL_HOST}:${runtimeConfig.zmqPort}`,
|
|
282
|
-
`walletdir=${walletDir}`,
|
|
283
|
-
];
|
|
284
|
-
await writeFileAtomic(filePath, `${lines.join("\n")}\n`, { mode: 0o600 });
|
|
285
|
-
}
|
|
286
|
-
function buildManagedServiceArgs(options, runtimeConfig) {
|
|
287
|
-
const walletDir = join(options.dataDir ?? "", "wallets");
|
|
288
|
-
const args = [
|
|
289
|
-
"-nosettings=1",
|
|
290
|
-
`-datadir=${options.dataDir}`,
|
|
291
|
-
`-rpcbind=${LOCAL_HOST}`,
|
|
292
|
-
`-rpcallowip=${LOCAL_HOST}`,
|
|
293
|
-
`-rpcport=${runtimeConfig.rpc.port}`,
|
|
294
|
-
`-port=${runtimeConfig.p2pPort}`,
|
|
295
|
-
`-zmqpubhashblock=tcp://${LOCAL_HOST}:${runtimeConfig.zmqPort}`,
|
|
296
|
-
`-walletdir=${walletDir}`,
|
|
297
|
-
"-server=1",
|
|
298
|
-
"-prune=0",
|
|
299
|
-
"-dnsseed=1",
|
|
300
|
-
"-listen=0",
|
|
301
|
-
`-dbcache=${runtimeConfig.dbcacheMiB}`,
|
|
302
|
-
];
|
|
303
|
-
if (options.chain === "regtest") {
|
|
304
|
-
args.push("-chain=regtest");
|
|
305
|
-
}
|
|
306
|
-
if (options.getblockArchivePath !== undefined && options.getblockArchivePath !== null) {
|
|
307
|
-
args.push(`-loadblock=${options.getblockArchivePath}`);
|
|
308
|
-
}
|
|
309
|
-
return args;
|
|
310
|
-
}
|
|
311
|
-
export async function writeBitcoinConfForTesting(filePath, options, runtimeConfig) {
|
|
312
|
-
await writeBitcoinConf(filePath, options, runtimeConfig);
|
|
313
|
-
}
|
|
314
|
-
export function buildManagedServiceArgsForTesting(options, runtimeConfig) {
|
|
315
|
-
return buildManagedServiceArgs(options, runtimeConfig);
|
|
316
|
-
}
|
|
317
|
-
function isMissingWalletError(message) {
|
|
318
|
-
return message.includes("bitcoind_rpc_loadwallet_-18_")
|
|
319
|
-
|| message.includes("Path does not exist")
|
|
320
|
-
|| message.includes("not found");
|
|
321
|
-
}
|
|
322
|
-
async function loadManagedWalletReplicaIfPresent(rpc, walletRootId, dataDir) {
|
|
323
|
-
const walletName = getWalletReplicaName(walletRootId);
|
|
324
|
-
const loadedWallets = await rpc.listWallets();
|
|
325
|
-
let loaded = loadedWallets.includes(walletName);
|
|
326
|
-
if (!loaded) {
|
|
327
|
-
try {
|
|
328
|
-
await rpc.loadWallet(walletName, false);
|
|
329
|
-
loaded = true;
|
|
330
|
-
}
|
|
331
|
-
catch (error) {
|
|
332
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
333
|
-
if (!isMissingWalletError(message)) {
|
|
334
|
-
return {
|
|
335
|
-
walletRootId,
|
|
336
|
-
walletName,
|
|
337
|
-
loaded: false,
|
|
338
|
-
descriptors: false,
|
|
339
|
-
privateKeysEnabled: false,
|
|
340
|
-
created: false,
|
|
341
|
-
proofStatus: "mismatch",
|
|
342
|
-
descriptorChecksum: null,
|
|
343
|
-
fundingAddress0: null,
|
|
344
|
-
fundingScriptPubKeyHex0: null,
|
|
345
|
-
message,
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
const walletDir = join(dataDir, "wallets", walletName);
|
|
349
|
-
const walletDirExists = await access(walletDir, constants.F_OK).then(() => true).catch(() => false);
|
|
350
|
-
return createMissingManagedWalletReplicaStatus(walletRootId, walletDirExists
|
|
351
|
-
? "Managed Core wallet replica exists on disk but is not loaded."
|
|
352
|
-
: "Managed Core wallet replica is missing.");
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
const info = await rpc.getWalletInfo(walletName);
|
|
356
|
-
if (!info.descriptors || !info.private_keys_enabled) {
|
|
357
|
-
return {
|
|
358
|
-
walletRootId,
|
|
359
|
-
walletName,
|
|
360
|
-
loaded: true,
|
|
361
|
-
descriptors: info.descriptors,
|
|
362
|
-
privateKeysEnabled: info.private_keys_enabled,
|
|
363
|
-
created: false,
|
|
364
|
-
proofStatus: "mismatch",
|
|
365
|
-
descriptorChecksum: null,
|
|
366
|
-
fundingAddress0: null,
|
|
367
|
-
fundingScriptPubKeyHex0: null,
|
|
368
|
-
message: "Managed Core wallet replica is not an encrypted descriptor wallet with private keys enabled.",
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
try {
|
|
372
|
-
await rpc.walletLock(walletName);
|
|
373
|
-
}
|
|
374
|
-
catch {
|
|
375
|
-
// A freshly created encrypted wallet may already be locked.
|
|
376
|
-
}
|
|
377
|
-
return {
|
|
378
|
-
walletRootId,
|
|
379
|
-
walletName,
|
|
380
|
-
loaded: true,
|
|
381
|
-
descriptors: info.descriptors,
|
|
382
|
-
privateKeysEnabled: info.private_keys_enabled,
|
|
383
|
-
created: false,
|
|
384
|
-
proofStatus: "not-proven",
|
|
385
|
-
descriptorChecksum: null,
|
|
386
|
-
fundingAddress0: null,
|
|
387
|
-
fundingScriptPubKeyHex0: null,
|
|
388
|
-
message: null,
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
export async function createManagedWalletReplica(rpc, walletRootId, options = {}) {
|
|
392
|
-
const walletName = getWalletReplicaName(walletRootId);
|
|
393
|
-
const loadedWallets = await rpc.listWallets();
|
|
394
|
-
let created = false;
|
|
395
|
-
if (!loadedWallets.includes(walletName)) {
|
|
396
|
-
try {
|
|
397
|
-
await rpc.loadWallet(walletName, false);
|
|
398
|
-
}
|
|
399
|
-
catch (error) {
|
|
400
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
401
|
-
if (!isMissingWalletError(message)) {
|
|
402
|
-
throw error;
|
|
403
|
-
}
|
|
404
|
-
await rpc.createWallet(walletName, {
|
|
405
|
-
blank: true,
|
|
406
|
-
descriptors: true,
|
|
407
|
-
disablePrivateKeys: false,
|
|
408
|
-
loadOnStartup: false,
|
|
409
|
-
passphrase: options.managedWalletPassphrase ?? randomBytes(32).toString("hex"),
|
|
410
|
-
});
|
|
411
|
-
created = true;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
const info = await rpc.getWalletInfo(walletName);
|
|
415
|
-
if (!info.descriptors || !info.private_keys_enabled) {
|
|
416
|
-
throw new Error("managed_bitcoind_wallet_replica_invalid");
|
|
417
|
-
}
|
|
418
|
-
try {
|
|
419
|
-
await rpc.walletLock(walletName);
|
|
420
|
-
}
|
|
421
|
-
catch {
|
|
422
|
-
// A freshly created encrypted wallet may already be locked.
|
|
423
|
-
}
|
|
424
|
-
return {
|
|
425
|
-
walletRootId,
|
|
426
|
-
walletName,
|
|
427
|
-
loaded: true,
|
|
428
|
-
descriptors: info.descriptors,
|
|
429
|
-
privateKeysEnabled: info.private_keys_enabled,
|
|
430
|
-
created,
|
|
431
|
-
proofStatus: "not-proven",
|
|
432
|
-
descriptorChecksum: null,
|
|
433
|
-
fundingAddress0: null,
|
|
434
|
-
fundingScriptPubKeyHex0: null,
|
|
435
|
-
message: null,
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
async function writeBitcoindStatus(paths, status) {
|
|
439
|
-
await mkdir(paths.walletRuntimeRoot, { recursive: true });
|
|
440
|
-
await writeRuntimeStatusFile(paths.bitcoindStatusPath, status);
|
|
441
|
-
await writeFileAtomic(paths.bitcoindPidPath, `${status.processId ?? ""}\n`, { mode: 0o600 });
|
|
442
|
-
await writeFileAtomic(paths.bitcoindReadyPath, `${status.updatedAtUnixMs}\n`, { mode: 0o600 });
|
|
443
|
-
await writeRuntimeStatusFile(paths.bitcoindWalletStatusPath, status.walletReplica ?? createMissingManagedWalletReplicaStatus(status.walletRootId, "Managed Core wallet replica is missing."));
|
|
444
|
-
await writeRuntimeStatusFile(paths.bitcoindRuntimeConfigPath, {
|
|
445
|
-
chain: status.chain,
|
|
446
|
-
rpc: status.rpc,
|
|
447
|
-
zmqPort: status.zmq.port,
|
|
448
|
-
p2pPort: status.p2pPort,
|
|
449
|
-
getblockArchiveEndHeight: status.getblockArchiveEndHeight,
|
|
450
|
-
getblockArchiveSha256: status.getblockArchiveSha256,
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
async function clearManagedBitcoindRuntimeArtifacts(paths) {
|
|
454
|
-
await rm(paths.bitcoindStatusPath, { force: true }).catch(() => undefined);
|
|
455
|
-
await rm(paths.bitcoindPidPath, { force: true }).catch(() => undefined);
|
|
456
|
-
await rm(paths.bitcoindReadyPath, { force: true }).catch(() => undefined);
|
|
457
|
-
await rm(paths.bitcoindWalletStatusPath, { force: true }).catch(() => undefined);
|
|
458
|
-
}
|
|
459
|
-
export async function stopManagedBitcoindServiceWithLockHeld(options) {
|
|
460
|
-
const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
461
|
-
const paths = options.paths ?? resolveManagedServicePaths(options.dataDir, walletRootId);
|
|
462
|
-
const status = await readJsonFileIfPresent(paths.bitcoindStatusPath);
|
|
463
|
-
const processId = status?.processId ?? null;
|
|
464
|
-
if (status === null || processId === null || !await isProcessAlive(processId)) {
|
|
465
|
-
await clearManagedBitcoindRuntimeArtifacts(paths);
|
|
466
|
-
return {
|
|
467
|
-
status: "not-running",
|
|
468
|
-
walletRootId,
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
const rpc = createRpcClient(status.rpc);
|
|
472
|
-
try {
|
|
473
|
-
await rpc.stop();
|
|
474
|
-
}
|
|
475
|
-
catch {
|
|
476
|
-
try {
|
|
477
|
-
process.kill(processId, "SIGTERM");
|
|
478
|
-
}
|
|
479
|
-
catch (error) {
|
|
480
|
-
if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
|
|
481
|
-
throw error;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
await waitForProcessExit(processId, options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS, "managed_bitcoind_service_stop_timeout");
|
|
486
|
-
await clearManagedBitcoindRuntimeArtifacts(paths);
|
|
487
|
-
return {
|
|
488
|
-
status: "stopped",
|
|
489
|
-
walletRootId,
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
export async function withClaimedUninitializedManagedRuntime(options, callback) {
|
|
493
|
-
const targetWalletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
494
|
-
const targetPaths = resolveManagedServicePaths(options.dataDir, targetWalletRootId);
|
|
495
|
-
const uninitializedPaths = resolveManagedServicePaths(options.dataDir, UNINITIALIZED_WALLET_ROOT_ID);
|
|
496
|
-
if (targetPaths.walletRuntimeRoot === uninitializedPaths.walletRuntimeRoot) {
|
|
497
|
-
return callback();
|
|
498
|
-
}
|
|
499
|
-
if (targetWalletRootId === UNINITIALIZED_WALLET_ROOT_ID) {
|
|
500
|
-
return callback();
|
|
501
|
-
}
|
|
502
|
-
const claimKey = `${options.dataDir}\n${targetWalletRootId}`;
|
|
503
|
-
if (claimedUninitializedRuntimeKeys.has(claimKey)) {
|
|
504
|
-
return callback();
|
|
505
|
-
}
|
|
506
|
-
claimedUninitializedRuntimeKeys.add(claimKey);
|
|
507
|
-
const lockTimeoutMs = options.shutdownTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
508
|
-
const bitcoindLock = await acquireFileLockWithRetry(uninitializedPaths.bitcoindLockPath, {
|
|
509
|
-
purpose: "managed-bitcoind-claim-uninitialized",
|
|
510
|
-
walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
|
|
511
|
-
dataDir: options.dataDir,
|
|
512
|
-
}, lockTimeoutMs);
|
|
513
|
-
try {
|
|
514
|
-
const indexerLock = await acquireFileLockWithRetry(uninitializedPaths.indexerDaemonLockPath, {
|
|
515
|
-
purpose: "managed-indexer-claim-uninitialized",
|
|
516
|
-
walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
|
|
517
|
-
dataDir: options.dataDir,
|
|
518
|
-
}, lockTimeoutMs);
|
|
519
|
-
try {
|
|
520
|
-
await stopIndexerDaemonServiceWithLockHeld({
|
|
521
|
-
dataDir: options.dataDir,
|
|
522
|
-
walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
|
|
523
|
-
shutdownTimeoutMs: options.shutdownTimeoutMs,
|
|
524
|
-
paths: uninitializedPaths,
|
|
525
|
-
});
|
|
526
|
-
await stopManagedBitcoindServiceWithLockHeld({
|
|
527
|
-
dataDir: options.dataDir,
|
|
528
|
-
walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
|
|
529
|
-
shutdownTimeoutMs: options.shutdownTimeoutMs,
|
|
530
|
-
paths: uninitializedPaths,
|
|
531
|
-
});
|
|
532
|
-
return await callback();
|
|
533
|
-
}
|
|
534
|
-
finally {
|
|
535
|
-
await indexerLock.release();
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
finally {
|
|
539
|
-
claimedUninitializedRuntimeKeys.delete(claimKey);
|
|
540
|
-
await bitcoindLock.release();
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
async function refreshManagedBitcoindStatus(status, paths, options) {
|
|
544
|
-
const nowUnixMs = Date.now();
|
|
545
|
-
const rpc = createRpcClient(status.rpc);
|
|
546
|
-
const targetWalletRootId = options.walletRootId ?? status.walletRootId;
|
|
547
|
-
try {
|
|
548
|
-
await waitForRpcReady(rpc, status.rpc.cookieFile, status.chain, options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS);
|
|
549
|
-
await validateNodeConfigForTesting(rpc, status.chain, status.zmq.endpoint);
|
|
550
|
-
const walletReplica = await loadManagedWalletReplicaIfPresent(rpc, targetWalletRootId, status.dataDir);
|
|
551
|
-
const nextStatus = {
|
|
552
|
-
...status,
|
|
553
|
-
walletRootId: targetWalletRootId,
|
|
554
|
-
runtimeRoot: paths.walletRuntimeRoot,
|
|
555
|
-
state: "ready",
|
|
556
|
-
processId: await isProcessAlive(status.processId) ? status.processId : null,
|
|
557
|
-
walletReplica,
|
|
558
|
-
heartbeatAtUnixMs: nowUnixMs,
|
|
559
|
-
updatedAtUnixMs: nowUnixMs,
|
|
560
|
-
lastError: walletReplica.message ?? null,
|
|
561
|
-
};
|
|
562
|
-
await writeBitcoindStatus(paths, nextStatus);
|
|
563
|
-
return nextStatus;
|
|
564
|
-
}
|
|
565
|
-
catch (error) {
|
|
566
|
-
const nextStatus = {
|
|
567
|
-
...status,
|
|
568
|
-
walletRootId: targetWalletRootId,
|
|
569
|
-
runtimeRoot: paths.walletRuntimeRoot,
|
|
570
|
-
state: "failed",
|
|
571
|
-
processId: await isProcessAlive(status.processId) ? status.processId : null,
|
|
572
|
-
heartbeatAtUnixMs: nowUnixMs,
|
|
573
|
-
updatedAtUnixMs: nowUnixMs,
|
|
574
|
-
lastError: error instanceof Error ? error.message : String(error),
|
|
575
|
-
};
|
|
576
|
-
await writeBitcoindStatus(paths, nextStatus);
|
|
577
|
-
return nextStatus;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
function createNodeHandle(status, paths, options, ownership) {
|
|
581
|
-
let currentStatus = status;
|
|
582
|
-
const rpc = createRpcClient(currentStatus.rpc);
|
|
583
|
-
let stopped = false;
|
|
584
|
-
return {
|
|
585
|
-
rpc: currentStatus.rpc,
|
|
586
|
-
zmq: currentStatus.zmq,
|
|
587
|
-
pid: currentStatus.processId,
|
|
588
|
-
expectedChain: currentStatus.chain,
|
|
589
|
-
startHeight: currentStatus.startHeight,
|
|
590
|
-
dataDir: currentStatus.dataDir,
|
|
591
|
-
getblockArchiveEndHeight: currentStatus.getblockArchiveEndHeight ?? null,
|
|
592
|
-
getblockArchiveSha256: currentStatus.getblockArchiveSha256 ?? null,
|
|
593
|
-
walletRootId: currentStatus.walletRootId,
|
|
594
|
-
runtimeRoot: paths.walletRuntimeRoot,
|
|
595
|
-
async validate() {
|
|
596
|
-
await validateNodeConfigForTesting(rpc, currentStatus.chain, currentStatus.zmq.endpoint);
|
|
597
|
-
},
|
|
598
|
-
async refreshServiceStatus() {
|
|
599
|
-
currentStatus = await refreshManagedBitcoindStatus(currentStatus, paths, options);
|
|
600
|
-
this.getblockArchiveEndHeight = currentStatus.getblockArchiveEndHeight ?? null;
|
|
601
|
-
this.getblockArchiveSha256 = currentStatus.getblockArchiveSha256 ?? null;
|
|
602
|
-
this.walletRootId = currentStatus.walletRootId;
|
|
603
|
-
return currentStatus;
|
|
604
|
-
},
|
|
605
|
-
async stop() {
|
|
606
|
-
if (stopped) {
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
stopped = true;
|
|
610
|
-
if (options.serviceLifetime !== "ephemeral" || ownership === "attached") {
|
|
611
|
-
// Public managed clients detach from persistent services, and ephemeral
|
|
612
|
-
// attach callers must not shut down services they did not launch.
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
await stopManagedBitcoindService({
|
|
616
|
-
dataDir: currentStatus.dataDir,
|
|
617
|
-
walletRootId: currentStatus.walletRootId,
|
|
618
|
-
shutdownTimeoutMs: options.shutdownTimeoutMs,
|
|
619
|
-
});
|
|
620
|
-
},
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
async function tryAttachExistingManagedBitcoindService(options) {
|
|
624
|
-
const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
625
|
-
const paths = resolveManagedServicePaths(options.dataDir ?? "", walletRootId);
|
|
626
|
-
const probe = await probeManagedBitcoindService(options);
|
|
627
|
-
if (probe.compatibility !== "compatible" || probe.status === null) {
|
|
628
|
-
return null;
|
|
629
|
-
}
|
|
630
|
-
const refreshed = await refreshManagedBitcoindStatus(probe.status, paths, options);
|
|
631
|
-
return createNodeHandle(refreshed, paths, options, "attached");
|
|
632
|
-
}
|
|
633
|
-
export async function probeManagedBitcoindService(options) {
|
|
634
|
-
const resolvedOptions = {
|
|
635
|
-
...options,
|
|
636
|
-
dataDir: options.dataDir ?? "",
|
|
637
|
-
walletRootId: options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID,
|
|
638
|
-
startupTimeoutMs: options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS,
|
|
639
|
-
};
|
|
640
|
-
return probeManagedBitcoindRuntime(resolvedOptions, {
|
|
641
|
-
getPaths: (runtimeOptions) => resolveManagedServicePaths(runtimeOptions.dataDir, runtimeOptions.walletRootId),
|
|
642
|
-
listStatusCandidates: listManagedBitcoindStatusCandidates,
|
|
643
|
-
isProcessAlive,
|
|
644
|
-
probeStatusCandidate: probeManagedBitcoindStatusCandidate,
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
export async function attachOrStartManagedBitcoindService(options) {
|
|
648
|
-
const resolvedOptions = {
|
|
649
|
-
...options,
|
|
650
|
-
dataDir: options.dataDir,
|
|
651
|
-
serviceLifetime: options.serviceLifetime ?? "persistent",
|
|
652
|
-
walletRootId: options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID,
|
|
653
|
-
};
|
|
654
|
-
const startupTimeoutMs = resolvedOptions.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
655
|
-
return withClaimedUninitializedManagedRuntime({
|
|
656
|
-
dataDir: resolvedOptions.dataDir ?? "",
|
|
657
|
-
walletRootId: resolvedOptions.walletRootId,
|
|
658
|
-
shutdownTimeoutMs: resolvedOptions.shutdownTimeoutMs,
|
|
659
|
-
}, async () => {
|
|
660
|
-
return attachOrStartManagedBitcoindRuntime({
|
|
661
|
-
...resolvedOptions,
|
|
662
|
-
dataDir: resolvedOptions.dataDir ?? "",
|
|
663
|
-
walletRootId: resolvedOptions.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID,
|
|
664
|
-
startupTimeoutMs,
|
|
665
|
-
}, {
|
|
666
|
-
getPaths: (runtimeOptions) => resolveManagedServicePaths(runtimeOptions.dataDir, runtimeOptions.walletRootId),
|
|
667
|
-
listStatusCandidates: listManagedBitcoindStatusCandidates,
|
|
668
|
-
isProcessAlive,
|
|
669
|
-
probeStatusCandidate: probeManagedBitcoindStatusCandidate,
|
|
670
|
-
attachExisting: tryAttachExistingManagedBitcoindService,
|
|
671
|
-
acquireStartLock: async (runtimeOptions, paths) => acquireFileLock(paths.bitcoindLockPath, {
|
|
672
|
-
purpose: "managed-bitcoind-start",
|
|
673
|
-
walletRootId: runtimeOptions.walletRootId,
|
|
674
|
-
dataDir: runtimeOptions.dataDir,
|
|
675
|
-
}),
|
|
676
|
-
startService: async (runtimeOptions, paths) => {
|
|
677
|
-
const bitcoindPath = await getBitcoindPath();
|
|
678
|
-
await verifyBitcoindVersion(bitcoindPath);
|
|
679
|
-
const binaryVersion = SUPPORTED_BITCOIND_VERSION;
|
|
680
|
-
await mkdir(runtimeOptions.dataDir, { recursive: true });
|
|
681
|
-
const startManagedProcess = async (startOptions) => {
|
|
682
|
-
const runtimeConfig = await resolveRuntimeConfig(paths.bitcoindStatusPath, paths.bitcoindRuntimeConfigPath, startOptions);
|
|
683
|
-
await writeBitcoinConf(paths.bitcoinConfPath, startOptions, runtimeConfig);
|
|
684
|
-
const rpcConfig = runtimeConfig.rpc;
|
|
685
|
-
const zmqConfig = {
|
|
686
|
-
endpoint: `tcp://${LOCAL_HOST}:${runtimeConfig.zmqPort}`,
|
|
687
|
-
topic: "hashblock",
|
|
688
|
-
port: runtimeConfig.zmqPort,
|
|
689
|
-
pollIntervalMs: startOptions.pollIntervalMs ?? DEFAULT_MANAGED_BITCOIND_FOLLOW_POLL_INTERVAL_MS,
|
|
690
|
-
};
|
|
691
|
-
const spawnOptions = startOptions.serviceLifetime === "ephemeral"
|
|
692
|
-
? {
|
|
693
|
-
stdio: "ignore",
|
|
694
|
-
}
|
|
695
|
-
: {
|
|
696
|
-
detached: true,
|
|
697
|
-
stdio: "ignore",
|
|
698
|
-
};
|
|
699
|
-
const child = spawn(bitcoindPath, buildManagedServiceArgs(startOptions, runtimeConfig), {
|
|
700
|
-
...spawnOptions,
|
|
701
|
-
});
|
|
702
|
-
if (startOptions.serviceLifetime !== "ephemeral") {
|
|
703
|
-
child.unref();
|
|
704
|
-
}
|
|
705
|
-
const rpc = createRpcClient(rpcConfig);
|
|
706
|
-
try {
|
|
707
|
-
await waitForRpcReady(rpc, rpcConfig.cookieFile, startOptions.chain, startupTimeoutMs);
|
|
708
|
-
await validateNodeConfigForTesting(rpc, startOptions.chain, zmqConfig.endpoint);
|
|
709
|
-
}
|
|
710
|
-
catch (error) {
|
|
711
|
-
if (child.pid !== undefined) {
|
|
712
|
-
try {
|
|
713
|
-
process.kill(child.pid, "SIGTERM");
|
|
714
|
-
}
|
|
715
|
-
catch {
|
|
716
|
-
// ignore kill failures during startup cleanup
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
throw error;
|
|
720
|
-
}
|
|
721
|
-
const nowUnixMs = Date.now();
|
|
722
|
-
const walletReplica = await loadManagedWalletReplicaIfPresent(rpc, startOptions.walletRootId, startOptions.dataDir);
|
|
723
|
-
return createBitcoindServiceStatus({
|
|
724
|
-
binaryVersion,
|
|
725
|
-
serviceInstanceId: randomBytes(16).toString("hex"),
|
|
726
|
-
state: "ready",
|
|
727
|
-
processId: child.pid ?? null,
|
|
728
|
-
walletRootId: startOptions.walletRootId,
|
|
729
|
-
chain: startOptions.chain,
|
|
730
|
-
dataDir: startOptions.dataDir,
|
|
731
|
-
runtimeRoot: paths.walletRuntimeRoot,
|
|
732
|
-
startHeight: startOptions.startHeight,
|
|
733
|
-
rpc: rpcConfig,
|
|
734
|
-
zmq: zmqConfig,
|
|
735
|
-
p2pPort: runtimeConfig.p2pPort,
|
|
736
|
-
getblockArchiveEndHeight: runtimeConfig.getblockArchiveEndHeight ?? null,
|
|
737
|
-
getblockArchiveSha256: runtimeConfig.getblockArchiveSha256 ?? null,
|
|
738
|
-
walletReplica,
|
|
739
|
-
startedAtUnixMs: nowUnixMs,
|
|
740
|
-
heartbeatAtUnixMs: nowUnixMs,
|
|
741
|
-
lastError: walletReplica.message ?? null,
|
|
742
|
-
});
|
|
743
|
-
};
|
|
744
|
-
let status;
|
|
745
|
-
try {
|
|
746
|
-
status = await startManagedProcess(runtimeOptions);
|
|
747
|
-
}
|
|
748
|
-
catch (error) {
|
|
749
|
-
if (runtimeOptions.getblockArchivePath === undefined || runtimeOptions.getblockArchivePath === null) {
|
|
750
|
-
throw error;
|
|
751
|
-
}
|
|
752
|
-
status = await startManagedProcess({
|
|
753
|
-
...runtimeOptions,
|
|
754
|
-
getblockArchivePath: null,
|
|
755
|
-
getblockArchiveEndHeight: null,
|
|
756
|
-
getblockArchiveSha256: null,
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
await writeBitcoindStatus(paths, status);
|
|
760
|
-
return createNodeHandle(status, resolveManagedServicePaths(runtimeOptions.dataDir, runtimeOptions.walletRootId), runtimeOptions, "started");
|
|
761
|
-
},
|
|
762
|
-
isLockBusyError: (error) => error instanceof FileLockBusyError,
|
|
763
|
-
sleep,
|
|
764
|
-
});
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
export async function stopManagedBitcoindService(options) {
|
|
768
|
-
const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
769
|
-
const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
|
|
770
|
-
const lock = await acquireFileLock(paths.bitcoindLockPath, {
|
|
771
|
-
purpose: "managed-bitcoind-stop",
|
|
772
|
-
walletRootId,
|
|
773
|
-
dataDir: options.dataDir,
|
|
774
|
-
});
|
|
775
|
-
try {
|
|
776
|
-
return stopManagedBitcoindServiceWithLockHeld({
|
|
777
|
-
...options,
|
|
778
|
-
walletRootId,
|
|
779
|
-
paths,
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
finally {
|
|
783
|
-
await lock.release();
|
|
784
|
-
}
|
|
785
|
-
}
|
|
1
|
+
import { readManagedBitcoindObservedStatus } from "./managed-runtime/bitcoind-status.js";
|
|
2
|
+
import { UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
|
|
3
|
+
import { buildManagedServiceArgsForTesting, resolveManagedBitcoindDbcacheMiB, writeBitcoinConfForTesting, } from "./managed-bitcoind-service-config.js";
|
|
4
|
+
import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, shutdownManagedBitcoindServiceForTesting, stopManagedBitcoindService, stopManagedBitcoindServiceWithLockHeld, withClaimedUninitializedManagedRuntime, } from "./managed-bitcoind-service-lifecycle.js";
|
|
5
|
+
import { createManagedWalletReplica } from "./managed-bitcoind-service-replica.js";
|
|
6
|
+
export { attachOrStartManagedBitcoindService, createManagedWalletReplica, probeManagedBitcoindService, resolveManagedBitcoindDbcacheMiB, stopManagedBitcoindService, stopManagedBitcoindServiceWithLockHeld, withClaimedUninitializedManagedRuntime, writeBitcoinConfForTesting, buildManagedServiceArgsForTesting, };
|
|
786
7
|
export async function readManagedBitcoindServiceStatusForTesting(dataDir, walletRootId = UNINITIALIZED_WALLET_ROOT_ID) {
|
|
787
8
|
return readManagedBitcoindObservedStatus({
|
|
788
9
|
dataDir,
|
|
789
10
|
walletRootId,
|
|
790
11
|
});
|
|
791
12
|
}
|
|
792
|
-
export
|
|
793
|
-
await stopManagedBitcoindService({
|
|
794
|
-
dataDir: options.dataDir,
|
|
795
|
-
walletRootId: options.walletRootId,
|
|
796
|
-
shutdownTimeoutMs: options.shutdownTimeoutMs,
|
|
797
|
-
}).catch(async (error) => {
|
|
798
|
-
const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
799
|
-
const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
|
|
800
|
-
await rm(paths.bitcoindReadyPath, { force: true }).catch(() => undefined);
|
|
801
|
-
throw error;
|
|
802
|
-
});
|
|
803
|
-
}
|
|
13
|
+
export { shutdownManagedBitcoindServiceForTesting, };
|