@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
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { access, constants } from "node:fs/promises";
|
|
2
|
-
import { deserializeIndexerState, loadBundledGenesisParameters } from "@cogcoin/indexer";
|
|
3
2
|
import { readPackageVersionFromDisk } from "../../package-version.js";
|
|
4
|
-
import {
|
|
5
|
-
import { deriveManagedBitcoindWalletStatus, resolveManagedBitcoindProbeDecision, } from "../../bitcoind/managed-runtime/bitcoind-policy.js";
|
|
6
|
-
import { deriveManagedIndexerWalletStatus, resolveIndexerDaemonProbeDecision, } from "../../bitcoind/managed-runtime/indexer-policy.js";
|
|
3
|
+
import { readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
|
|
7
4
|
import { createRpcClient } from "../../bitcoind/node.js";
|
|
8
5
|
import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
|
|
9
|
-
import { attachOrStartManagedBitcoindService
|
|
10
|
-
import { resolveCogcoinProcessingStartHeight } from "../../bitcoind/processing-start-height.js";
|
|
6
|
+
import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
|
|
11
7
|
import {} from "../../bitcoind/types.js";
|
|
12
|
-
import { verifyManagedCoreWalletReplica, } from "../lifecycle.js";
|
|
13
8
|
import { normalizeWalletStateRecord, persistWalletCoinControlStateIfNeeded } from "../coin-control.js";
|
|
14
9
|
import { persistNormalizedWalletDescriptorStateIfNeeded } from "../descriptor-normalization.js";
|
|
15
10
|
import { inspectMiningControlPlane } from "../mining/index.js";
|
|
@@ -19,11 +14,9 @@ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
|
|
|
19
14
|
import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, } from "../state/storage.js";
|
|
20
15
|
import { createDefaultWalletSecretProvider, createWalletSecretReference, inspectClientPasswordSetupReadiness, } from "../state/provider.js";
|
|
21
16
|
import { describeClientPasswordLockedMessage, describeClientPasswordMigrationMessage, describeClientPasswordSetupMessage, } from "../state/client-password.js";
|
|
17
|
+
import { openManagedWalletReadServiceBundle } from "./managed-services.js";
|
|
22
18
|
import { createWalletReadModel } from "./project.js";
|
|
23
|
-
const DEFAULT_SERVICE_START_TIMEOUT_MS =
|
|
24
|
-
const TOLERATED_NODE_HEADER_LEAD_BLOCKS = 2;
|
|
25
|
-
const TOLERATED_NODE_HEADER_LEAD_MESSAGE = "Bitcoin headers can briefly lead validated blocks; a short 1-2 block lead is normal and is being tolerated.";
|
|
26
|
-
const NODE_CATCHING_UP_MESSAGE = "Bitcoin Core is still catching up to headers.";
|
|
19
|
+
const DEFAULT_SERVICE_START_TIMEOUT_MS = 60_000;
|
|
27
20
|
function btcAmountToSats(value) {
|
|
28
21
|
return BigInt(Math.round(value * 100_000_000));
|
|
29
22
|
}
|
|
@@ -223,99 +216,6 @@ async function inspectWalletLocalState(options = {}) {
|
|
|
223
216
|
};
|
|
224
217
|
}
|
|
225
218
|
}
|
|
226
|
-
function deriveNodeHealth(status, bitcoindHealth) {
|
|
227
|
-
if (bitcoindHealth !== "ready" || status === null || !status.ready) {
|
|
228
|
-
return {
|
|
229
|
-
health: "catching-up",
|
|
230
|
-
message: NODE_CATCHING_UP_MESSAGE,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
const headerLead = status.nodeBestHeight !== null && status.nodeHeaderHeight !== null
|
|
234
|
-
? status.nodeHeaderHeight - status.nodeBestHeight
|
|
235
|
-
: null;
|
|
236
|
-
if (headerLead !== null && headerLead > 0) {
|
|
237
|
-
if (headerLead <= TOLERATED_NODE_HEADER_LEAD_BLOCKS) {
|
|
238
|
-
return {
|
|
239
|
-
health: "synced",
|
|
240
|
-
message: TOLERATED_NODE_HEADER_LEAD_MESSAGE,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
return {
|
|
244
|
-
health: "catching-up",
|
|
245
|
-
message: NODE_CATCHING_UP_MESSAGE,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
return {
|
|
249
|
-
health: "synced",
|
|
250
|
-
message: null,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
export function deriveNodeHealthForTesting(status, bitcoindHealth) {
|
|
254
|
-
return deriveNodeHealth(status, bitcoindHealth);
|
|
255
|
-
}
|
|
256
|
-
async function attachNodeStatus(options) {
|
|
257
|
-
try {
|
|
258
|
-
const probe = await probeManagedBitcoindService({
|
|
259
|
-
dataDir: options.dataDir,
|
|
260
|
-
chain: "main",
|
|
261
|
-
startHeight: 0,
|
|
262
|
-
walletRootId: options.walletRootId,
|
|
263
|
-
startupTimeoutMs: options.startupTimeoutMs,
|
|
264
|
-
});
|
|
265
|
-
const decision = resolveManagedBitcoindProbeDecision(probe);
|
|
266
|
-
if (decision.action === "reject") {
|
|
267
|
-
return {
|
|
268
|
-
handle: null,
|
|
269
|
-
rpc: null,
|
|
270
|
-
status: null,
|
|
271
|
-
observedStatus: probe.status,
|
|
272
|
-
error: decision.error,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
const genesis = await loadBundledGenesisParameters();
|
|
276
|
-
const handle = await attachOrStartManagedBitcoindService({
|
|
277
|
-
dataDir: options.dataDir,
|
|
278
|
-
chain: "main",
|
|
279
|
-
startHeight: resolveCogcoinProcessingStartHeight(genesis),
|
|
280
|
-
walletRootId: options.walletRootId,
|
|
281
|
-
startupTimeoutMs: options.startupTimeoutMs,
|
|
282
|
-
});
|
|
283
|
-
const rpc = createRpcClient(handle.rpc);
|
|
284
|
-
const [chainInfo, serviceStatus] = await Promise.all([
|
|
285
|
-
rpc.getBlockchainInfo(),
|
|
286
|
-
handle.refreshServiceStatus?.(),
|
|
287
|
-
]);
|
|
288
|
-
const status = {
|
|
289
|
-
ready: true,
|
|
290
|
-
chain: chainInfo.chain,
|
|
291
|
-
pid: handle.pid,
|
|
292
|
-
walletRootId: handle.walletRootId ?? null,
|
|
293
|
-
nodeBestHeight: chainInfo.blocks,
|
|
294
|
-
nodeBestHashHex: chainInfo.bestblockhash,
|
|
295
|
-
nodeHeaderHeight: chainInfo.headers,
|
|
296
|
-
serviceUpdatedAtUnixMs: serviceStatus?.updatedAtUnixMs ?? null,
|
|
297
|
-
serviceStatus: serviceStatus ?? null,
|
|
298
|
-
walletReplica: serviceStatus?.walletReplica ?? null,
|
|
299
|
-
walletReplicaMessage: serviceStatus?.walletReplica?.message ?? null,
|
|
300
|
-
};
|
|
301
|
-
return {
|
|
302
|
-
handle,
|
|
303
|
-
rpc,
|
|
304
|
-
status,
|
|
305
|
-
observedStatus: serviceStatus ?? null,
|
|
306
|
-
error: null,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
catch (error) {
|
|
310
|
-
return {
|
|
311
|
-
handle: null,
|
|
312
|
-
rpc: null,
|
|
313
|
-
status: null,
|
|
314
|
-
observedStatus: null,
|
|
315
|
-
error: error instanceof Error ? error.message : String(error),
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
219
|
async function readFundingSpendableSats(options) {
|
|
320
220
|
if (options.state === null || options.rpc === null) {
|
|
321
221
|
return null;
|
|
@@ -345,109 +245,26 @@ export async function openWalletReadContext(options) {
|
|
|
345
245
|
paths: options.paths,
|
|
346
246
|
});
|
|
347
247
|
const walletRootId = localState.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
|
|
348
|
-
const
|
|
248
|
+
const managedServices = await openManagedWalletReadServiceBundle({
|
|
349
249
|
dataDir: options.dataDir,
|
|
250
|
+
databasePath: options.databasePath,
|
|
350
251
|
walletRootId,
|
|
252
|
+
localState,
|
|
351
253
|
startupTimeoutMs,
|
|
352
|
-
|
|
353
|
-
if (localState.state !== null && node.status !== null) {
|
|
354
|
-
const verifiedReplica = await verifyManagedCoreWalletReplica(localState.state, options.dataDir, {
|
|
355
|
-
nodeHandle: node.handle ?? undefined,
|
|
356
|
-
});
|
|
357
|
-
node.status = {
|
|
358
|
-
...node.status,
|
|
359
|
-
walletReplica: verifiedReplica,
|
|
360
|
-
walletReplicaMessage: verifiedReplica.message ?? null,
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
const bitcoind = deriveManagedBitcoindWalletStatus({
|
|
364
|
-
status: node.observedStatus,
|
|
365
|
-
nodeStatus: node.status,
|
|
366
|
-
startupError: node.error,
|
|
367
|
-
});
|
|
368
|
-
const nodeDerived = deriveNodeHealth(node.status, bitcoind.health);
|
|
369
|
-
let daemonClient = null;
|
|
370
|
-
let daemonStatus = null;
|
|
371
|
-
let observedDaemonStatus = null;
|
|
372
|
-
let snapshot = null;
|
|
373
|
-
let indexerSource = "none";
|
|
374
|
-
let daemonError = null;
|
|
375
|
-
try {
|
|
376
|
-
const probe = await probeIndexerDaemon({
|
|
377
|
-
dataDir: options.dataDir,
|
|
378
|
-
walletRootId,
|
|
379
|
-
});
|
|
380
|
-
const probeDecision = resolveIndexerDaemonProbeDecision({
|
|
381
|
-
probe,
|
|
382
|
-
expectedBinaryVersion: expectedIndexerBinaryVersion,
|
|
383
|
-
});
|
|
384
|
-
if (probeDecision.action !== "reject") {
|
|
385
|
-
await probe.client?.close().catch(() => undefined);
|
|
386
|
-
daemonClient = await attachOrStartIndexerDaemon({
|
|
387
|
-
dataDir: options.dataDir,
|
|
388
|
-
databasePath: options.databasePath,
|
|
389
|
-
walletRootId,
|
|
390
|
-
startupTimeoutMs,
|
|
391
|
-
ensureBackgroundFollow: true,
|
|
392
|
-
expectedBinaryVersion: expectedIndexerBinaryVersion,
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
observedDaemonStatus = probe.status;
|
|
397
|
-
indexerSource = probe.status === null ? "none" : "probe";
|
|
398
|
-
daemonError = probeDecision.error;
|
|
399
|
-
}
|
|
400
|
-
if (daemonClient !== null) {
|
|
401
|
-
const lease = await readSnapshotWithRetry(daemonClient, walletRootId);
|
|
402
|
-
daemonStatus = lease.status;
|
|
403
|
-
observedDaemonStatus = lease.status;
|
|
404
|
-
snapshot = {
|
|
405
|
-
tip: lease.payload.tip,
|
|
406
|
-
state: deserializeIndexerState(Buffer.from(lease.payload.stateBase64, "base64")),
|
|
407
|
-
source: "lease",
|
|
408
|
-
daemonInstanceId: lease.payload.daemonInstanceId,
|
|
409
|
-
snapshotSeq: lease.payload.snapshotSeq,
|
|
410
|
-
openedAtUnixMs: lease.payload.openedAtUnixMs,
|
|
411
|
-
};
|
|
412
|
-
indexerSource = "lease";
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
catch (error) {
|
|
416
|
-
daemonError = error instanceof Error ? error.message : String(error);
|
|
417
|
-
if (daemonError === INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED) {
|
|
418
|
-
await daemonClient?.close().catch(() => undefined);
|
|
419
|
-
await node.handle?.stop().catch(() => undefined);
|
|
420
|
-
throw error;
|
|
421
|
-
}
|
|
422
|
-
if (observedDaemonStatus === null) {
|
|
423
|
-
observedDaemonStatus = await readObservedIndexerDaemonStatus({
|
|
424
|
-
dataDir: options.dataDir,
|
|
425
|
-
walletRootId,
|
|
426
|
-
}).catch(() => null);
|
|
427
|
-
if (observedDaemonStatus !== null) {
|
|
428
|
-
indexerSource = "status-file";
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
const indexer = deriveManagedIndexerWalletStatus({
|
|
433
|
-
daemonStatus,
|
|
434
|
-
observedStatus: observedDaemonStatus,
|
|
435
|
-
snapshot,
|
|
436
|
-
source: indexerSource,
|
|
254
|
+
expectedIndexerBinaryVersion,
|
|
437
255
|
now,
|
|
438
|
-
startupError: daemonError,
|
|
439
256
|
});
|
|
440
257
|
const fundingSpendableSats = await readFundingSpendableSats({
|
|
441
258
|
state: localState.state,
|
|
442
|
-
rpc: node.rpc,
|
|
259
|
+
rpc: managedServices.node.rpc,
|
|
443
260
|
});
|
|
444
261
|
const mining = await inspectMiningControlPlane({
|
|
445
262
|
provider: options.secretProvider,
|
|
446
263
|
localState,
|
|
447
|
-
bitcoind,
|
|
448
|
-
nodeStatus: node.status,
|
|
449
|
-
nodeHealth:
|
|
450
|
-
indexer,
|
|
264
|
+
bitcoind: managedServices.bitcoind,
|
|
265
|
+
nodeStatus: managedServices.node.status,
|
|
266
|
+
nodeHealth: managedServices.nodeHealth,
|
|
267
|
+
indexer: managedServices.indexer,
|
|
451
268
|
nowUnixMs: now,
|
|
452
269
|
paths: options.paths,
|
|
453
270
|
});
|
|
@@ -455,20 +272,19 @@ export async function openWalletReadContext(options) {
|
|
|
455
272
|
dataDir: options.dataDir,
|
|
456
273
|
databasePath: options.databasePath,
|
|
457
274
|
localState,
|
|
458
|
-
bitcoind,
|
|
459
|
-
nodeStatus: node.status,
|
|
460
|
-
nodeHealth:
|
|
461
|
-
nodeMessage:
|
|
462
|
-
indexer,
|
|
463
|
-
snapshot,
|
|
275
|
+
bitcoind: managedServices.bitcoind,
|
|
276
|
+
nodeStatus: managedServices.node.status,
|
|
277
|
+
nodeHealth: managedServices.nodeHealth,
|
|
278
|
+
nodeMessage: managedServices.nodeMessage,
|
|
279
|
+
indexer: managedServices.indexer,
|
|
280
|
+
snapshot: managedServices.snapshot,
|
|
464
281
|
model: localState.state === null
|
|
465
282
|
? null
|
|
466
|
-
: createWalletReadModel(localState.state, snapshot),
|
|
283
|
+
: createWalletReadModel(localState.state, managedServices.snapshot),
|
|
467
284
|
fundingSpendableSats,
|
|
468
285
|
mining,
|
|
469
286
|
async close() {
|
|
470
|
-
await
|
|
471
|
-
await node.handle?.stop().catch(() => undefined);
|
|
287
|
+
await managedServices.close();
|
|
472
288
|
},
|
|
473
289
|
};
|
|
474
290
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { loadBundledGenesisParameters } from "@cogcoin/indexer";
|
|
2
|
+
import { attachOrStartIndexerDaemon, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, type IndexerDaemonClient } from "../../bitcoind/indexer-daemon.js";
|
|
3
|
+
import type { ManagedWalletReadServiceBundle } from "../../bitcoind/managed-runtime/types.js";
|
|
4
|
+
import { createRpcClient } from "../../bitcoind/node.js";
|
|
5
|
+
import { attachOrStartManagedBitcoindService, probeManagedBitcoindService } from "../../bitcoind/service.js";
|
|
6
|
+
import type { ManagedBitcoindNodeHandle } from "../../bitcoind/types.js";
|
|
7
|
+
import { verifyManagedCoreWalletReplica } from "../lifecycle.js";
|
|
8
|
+
import type { WalletBitcoindStatus, WalletLocalStateStatus, WalletNodeStatus, WalletServiceHealth } from "./types.js";
|
|
9
|
+
type ManagedWalletReadServiceDeps = {
|
|
10
|
+
loadBundledGenesisParameters: typeof loadBundledGenesisParameters;
|
|
11
|
+
probeManagedBitcoindService: typeof probeManagedBitcoindService;
|
|
12
|
+
attachOrStartManagedBitcoindService: typeof attachOrStartManagedBitcoindService;
|
|
13
|
+
createRpcClient: typeof createRpcClient;
|
|
14
|
+
verifyManagedCoreWalletReplica: typeof verifyManagedCoreWalletReplica;
|
|
15
|
+
probeIndexerDaemon: typeof probeIndexerDaemon;
|
|
16
|
+
attachOrStartIndexerDaemon: typeof attachOrStartIndexerDaemon;
|
|
17
|
+
readSnapshotWithRetry: typeof readSnapshotWithRetry;
|
|
18
|
+
readObservedIndexerDaemonStatus: typeof readObservedIndexerDaemonStatus;
|
|
19
|
+
};
|
|
20
|
+
export declare function deriveNodeHealthForTesting(status: WalletNodeStatus | null, bitcoindHealth: WalletBitcoindStatus["health"]): {
|
|
21
|
+
health: WalletServiceHealth;
|
|
22
|
+
message: string | null;
|
|
23
|
+
};
|
|
24
|
+
export declare function openManagedWalletReadServiceBundle(options: {
|
|
25
|
+
dataDir: string;
|
|
26
|
+
databasePath: string;
|
|
27
|
+
walletRootId: string;
|
|
28
|
+
localState: WalletLocalStateStatus;
|
|
29
|
+
startupTimeoutMs: number;
|
|
30
|
+
expectedIndexerBinaryVersion: string | null;
|
|
31
|
+
now: number;
|
|
32
|
+
}, dependencies?: ManagedWalletReadServiceDeps): Promise<ManagedWalletReadServiceBundle<ManagedBitcoindNodeHandle, ReturnType<typeof createRpcClient>, IndexerDaemonClient>>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { deserializeIndexerState, loadBundledGenesisParameters } from "@cogcoin/indexer";
|
|
2
|
+
import { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
|
|
3
|
+
import { deriveManagedBitcoindWalletStatus, resolveManagedBitcoindProbeDecision, } from "../../bitcoind/managed-runtime/bitcoind-policy.js";
|
|
4
|
+
import { deriveManagedIndexerWalletStatus, resolveIndexerDaemonProbeDecision, } from "../../bitcoind/managed-runtime/indexer-policy.js";
|
|
5
|
+
import { createRpcClient } from "../../bitcoind/node.js";
|
|
6
|
+
import { resolveCogcoinProcessingStartHeight } from "../../bitcoind/processing-start-height.js";
|
|
7
|
+
import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
|
|
8
|
+
import { verifyManagedCoreWalletReplica } from "../lifecycle.js";
|
|
9
|
+
const TOLERATED_NODE_HEADER_LEAD_BLOCKS = 2;
|
|
10
|
+
const TOLERATED_NODE_HEADER_LEAD_MESSAGE = "Bitcoin headers can briefly lead validated blocks; a short 1-2 block lead is normal and is being tolerated.";
|
|
11
|
+
const NODE_CATCHING_UP_MESSAGE = "Bitcoin Core is still catching up to headers.";
|
|
12
|
+
const defaultManagedWalletReadServiceDeps = {
|
|
13
|
+
loadBundledGenesisParameters,
|
|
14
|
+
probeManagedBitcoindService,
|
|
15
|
+
attachOrStartManagedBitcoindService,
|
|
16
|
+
createRpcClient,
|
|
17
|
+
verifyManagedCoreWalletReplica,
|
|
18
|
+
probeIndexerDaemon,
|
|
19
|
+
attachOrStartIndexerDaemon,
|
|
20
|
+
readSnapshotWithRetry,
|
|
21
|
+
readObservedIndexerDaemonStatus,
|
|
22
|
+
};
|
|
23
|
+
function deriveNodeHealth(status, bitcoindHealth) {
|
|
24
|
+
if (bitcoindHealth !== "ready" || status === null || !status.ready) {
|
|
25
|
+
return {
|
|
26
|
+
health: "catching-up",
|
|
27
|
+
message: NODE_CATCHING_UP_MESSAGE,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const headerLead = status.nodeBestHeight !== null && status.nodeHeaderHeight !== null
|
|
31
|
+
? status.nodeHeaderHeight - status.nodeBestHeight
|
|
32
|
+
: null;
|
|
33
|
+
if (headerLead !== null && headerLead > 0) {
|
|
34
|
+
if (headerLead <= TOLERATED_NODE_HEADER_LEAD_BLOCKS) {
|
|
35
|
+
return {
|
|
36
|
+
health: "synced",
|
|
37
|
+
message: TOLERATED_NODE_HEADER_LEAD_MESSAGE,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
health: "catching-up",
|
|
42
|
+
message: NODE_CATCHING_UP_MESSAGE,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
health: "synced",
|
|
47
|
+
message: null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function deriveNodeHealthForTesting(status, bitcoindHealth) {
|
|
51
|
+
return deriveNodeHealth(status, bitcoindHealth);
|
|
52
|
+
}
|
|
53
|
+
async function attachNodeStatus(options, dependencies) {
|
|
54
|
+
try {
|
|
55
|
+
const probe = await dependencies.probeManagedBitcoindService({
|
|
56
|
+
dataDir: options.dataDir,
|
|
57
|
+
chain: "main",
|
|
58
|
+
startHeight: 0,
|
|
59
|
+
walletRootId: options.walletRootId,
|
|
60
|
+
startupTimeoutMs: options.startupTimeoutMs,
|
|
61
|
+
});
|
|
62
|
+
const decision = resolveManagedBitcoindProbeDecision(probe);
|
|
63
|
+
if (decision.action === "reject") {
|
|
64
|
+
return {
|
|
65
|
+
handle: null,
|
|
66
|
+
rpc: null,
|
|
67
|
+
status: null,
|
|
68
|
+
observedStatus: probe.status,
|
|
69
|
+
error: decision.error,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const genesis = await dependencies.loadBundledGenesisParameters();
|
|
73
|
+
const handle = await dependencies.attachOrStartManagedBitcoindService({
|
|
74
|
+
dataDir: options.dataDir,
|
|
75
|
+
chain: "main",
|
|
76
|
+
startHeight: resolveCogcoinProcessingStartHeight(genesis),
|
|
77
|
+
walletRootId: options.walletRootId,
|
|
78
|
+
startupTimeoutMs: options.startupTimeoutMs,
|
|
79
|
+
});
|
|
80
|
+
const rpc = dependencies.createRpcClient(handle.rpc);
|
|
81
|
+
const [chainInfo, serviceStatus] = await Promise.all([
|
|
82
|
+
rpc.getBlockchainInfo(),
|
|
83
|
+
handle.refreshServiceStatus?.(),
|
|
84
|
+
]);
|
|
85
|
+
const status = {
|
|
86
|
+
ready: true,
|
|
87
|
+
chain: chainInfo.chain,
|
|
88
|
+
pid: handle.pid,
|
|
89
|
+
walletRootId: handle.walletRootId ?? null,
|
|
90
|
+
nodeBestHeight: chainInfo.blocks,
|
|
91
|
+
nodeBestHashHex: chainInfo.bestblockhash,
|
|
92
|
+
nodeHeaderHeight: chainInfo.headers,
|
|
93
|
+
serviceUpdatedAtUnixMs: serviceStatus?.updatedAtUnixMs ?? null,
|
|
94
|
+
serviceStatus: serviceStatus ?? null,
|
|
95
|
+
walletReplica: serviceStatus?.walletReplica ?? null,
|
|
96
|
+
walletReplicaMessage: serviceStatus?.walletReplica?.message ?? null,
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
handle,
|
|
100
|
+
rpc,
|
|
101
|
+
status,
|
|
102
|
+
observedStatus: serviceStatus ?? null,
|
|
103
|
+
error: null,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
handle: null,
|
|
109
|
+
rpc: null,
|
|
110
|
+
status: null,
|
|
111
|
+
observedStatus: null,
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export async function openManagedWalletReadServiceBundle(options, dependencies = defaultManagedWalletReadServiceDeps) {
|
|
117
|
+
const node = await attachNodeStatus({
|
|
118
|
+
dataDir: options.dataDir,
|
|
119
|
+
walletRootId: options.walletRootId,
|
|
120
|
+
startupTimeoutMs: options.startupTimeoutMs,
|
|
121
|
+
}, dependencies);
|
|
122
|
+
if (options.localState.state !== null && node.status !== null) {
|
|
123
|
+
const verifiedReplica = await dependencies.verifyManagedCoreWalletReplica(options.localState.state, options.dataDir, {
|
|
124
|
+
nodeHandle: node.handle ?? undefined,
|
|
125
|
+
});
|
|
126
|
+
node.status = {
|
|
127
|
+
...node.status,
|
|
128
|
+
walletReplica: verifiedReplica,
|
|
129
|
+
walletReplicaMessage: verifiedReplica.message ?? null,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const bitcoind = deriveManagedBitcoindWalletStatus({
|
|
133
|
+
status: node.observedStatus,
|
|
134
|
+
nodeStatus: node.status,
|
|
135
|
+
startupError: node.error,
|
|
136
|
+
});
|
|
137
|
+
const nodeDerived = deriveNodeHealth(node.status, bitcoind.health);
|
|
138
|
+
let daemonClient = null;
|
|
139
|
+
let daemonStatus = null;
|
|
140
|
+
let observedDaemonStatus = null;
|
|
141
|
+
let snapshot = null;
|
|
142
|
+
let indexerSource = "none";
|
|
143
|
+
let daemonError = null;
|
|
144
|
+
try {
|
|
145
|
+
const probe = await dependencies.probeIndexerDaemon({
|
|
146
|
+
dataDir: options.dataDir,
|
|
147
|
+
walletRootId: options.walletRootId,
|
|
148
|
+
});
|
|
149
|
+
const probeDecision = resolveIndexerDaemonProbeDecision({
|
|
150
|
+
probe,
|
|
151
|
+
expectedBinaryVersion: options.expectedIndexerBinaryVersion,
|
|
152
|
+
});
|
|
153
|
+
if (probeDecision.action !== "reject") {
|
|
154
|
+
await probe.client?.close().catch(() => undefined);
|
|
155
|
+
daemonClient = await dependencies.attachOrStartIndexerDaemon({
|
|
156
|
+
dataDir: options.dataDir,
|
|
157
|
+
databasePath: options.databasePath,
|
|
158
|
+
walletRootId: options.walletRootId,
|
|
159
|
+
startupTimeoutMs: options.startupTimeoutMs,
|
|
160
|
+
ensureBackgroundFollow: true,
|
|
161
|
+
expectedBinaryVersion: options.expectedIndexerBinaryVersion,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
observedDaemonStatus = probe.status;
|
|
166
|
+
indexerSource = probe.status === null ? "none" : "probe";
|
|
167
|
+
daemonError = probeDecision.error;
|
|
168
|
+
}
|
|
169
|
+
if (daemonClient !== null) {
|
|
170
|
+
const lease = await dependencies.readSnapshotWithRetry(daemonClient, options.walletRootId);
|
|
171
|
+
daemonStatus = lease.status;
|
|
172
|
+
observedDaemonStatus = lease.status;
|
|
173
|
+
snapshot = {
|
|
174
|
+
tip: lease.payload.tip,
|
|
175
|
+
state: deserializeIndexerState(Buffer.from(lease.payload.stateBase64, "base64")),
|
|
176
|
+
source: "lease",
|
|
177
|
+
daemonInstanceId: lease.payload.daemonInstanceId,
|
|
178
|
+
snapshotSeq: lease.payload.snapshotSeq,
|
|
179
|
+
openedAtUnixMs: lease.payload.openedAtUnixMs,
|
|
180
|
+
};
|
|
181
|
+
indexerSource = "lease";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
daemonError = error instanceof Error ? error.message : String(error);
|
|
186
|
+
if (daemonError === INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED) {
|
|
187
|
+
await daemonClient?.close().catch(() => undefined);
|
|
188
|
+
await node.handle?.stop().catch(() => undefined);
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
if (observedDaemonStatus === null) {
|
|
192
|
+
observedDaemonStatus = await dependencies.readObservedIndexerDaemonStatus({
|
|
193
|
+
dataDir: options.dataDir,
|
|
194
|
+
walletRootId: options.walletRootId,
|
|
195
|
+
}).catch(() => null);
|
|
196
|
+
if (observedDaemonStatus !== null) {
|
|
197
|
+
indexerSource = "status-file";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const indexer = deriveManagedIndexerWalletStatus({
|
|
202
|
+
daemonStatus,
|
|
203
|
+
observedStatus: observedDaemonStatus,
|
|
204
|
+
snapshot,
|
|
205
|
+
source: indexerSource,
|
|
206
|
+
now: options.now,
|
|
207
|
+
startupError: daemonError,
|
|
208
|
+
});
|
|
209
|
+
return {
|
|
210
|
+
node,
|
|
211
|
+
bitcoind,
|
|
212
|
+
nodeHealth: nodeDerived.health,
|
|
213
|
+
nodeMessage: nodeDerived.message,
|
|
214
|
+
daemonClient,
|
|
215
|
+
indexer,
|
|
216
|
+
snapshot,
|
|
217
|
+
async close() {
|
|
218
|
+
await daemonClient?.close().catch(() => undefined);
|
|
219
|
+
await node.handle?.stop().catch(() => undefined);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WalletRuntimePaths } from "../runtime.js";
|
|
2
|
+
import type { StagedArtifact, WalletResetArtifactDependencies } from "./types.js";
|
|
3
|
+
export declare function pathExists(path: string, deps?: WalletResetArtifactDependencies): Promise<boolean>;
|
|
4
|
+
export declare function readJsonFileOrNull<T>(path: string, deps?: WalletResetArtifactDependencies): Promise<T | null>;
|
|
5
|
+
export declare function isPathWithin(root: string, target: string): boolean;
|
|
6
|
+
export declare function dedupeSortedPaths(candidates: readonly string[]): string[];
|
|
7
|
+
export declare function resolveDefaultRemovedRoots(paths: WalletRuntimePaths): string[];
|
|
8
|
+
export declare function resolveBitcoindPreservingRemovedRoots(paths: WalletRuntimePaths): string[];
|
|
9
|
+
export declare function resolveRemovedRoots(paths: WalletRuntimePaths, options?: {
|
|
10
|
+
preserveBitcoinDataDir: boolean;
|
|
11
|
+
}): string[];
|
|
12
|
+
export declare function isDeletedByRemovalPlan(removedRoots: readonly string[], targetPath: string): boolean;
|
|
13
|
+
export declare function stageArtifact(sourcePath: string, stagingRoot: string, label: string, deps?: WalletResetArtifactDependencies): Promise<StagedArtifact | null>;
|
|
14
|
+
export declare function restoreStagedArtifacts(artifacts: readonly StagedArtifact[], deps?: WalletResetArtifactDependencies): Promise<void>;
|
|
15
|
+
export declare function deleteRemovedRoots(roots: readonly string[], deps?: WalletResetArtifactDependencies): Promise<void>;
|
|
16
|
+
export declare function deleteBootstrapSnapshotArtifacts(dataDir: string, deps?: WalletResetArtifactDependencies): Promise<void>;
|