@cogcoin/client 1.1.10 → 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/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 +18 -1
- package/dist/bitcoind/managed-bitcoind-service-config.js +38 -1
- package/dist/bitcoind/managed-bitcoind-service-lifecycle.js +30 -24
- package/dist/bitcoind/managed-bitcoind-service-status.js +0 -8
- 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/projection.d.ts +1 -0
- package/dist/wallet/mining/projection.js +15 -1
- package/dist/wallet/mining/visualizer-sync.js +7 -9
- package/dist/wallet/mining/visualizer.js +2 -1
- package/dist/wallet/read/context.js +3 -2
- package/dist/wallet/read/local-state.d.ts +8 -0
- package/dist/wallet/read/local-state.js +32 -6
- package/dist/wallet/read/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ import { attachOrStartManagedBitcoindRuntime, probeManagedBitcoindRuntime } from
|
|
|
9
9
|
import { createRpcClient, validateNodeConfigForTesting } from "./node.js";
|
|
10
10
|
import { resolveManagedServicePaths, UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
|
|
11
11
|
import { DEFAULT_MANAGED_BITCOIND_FOLLOW_POLL_INTERVAL_MS, } from "./types.js";
|
|
12
|
-
import { buildManagedServiceArgsForTesting, LOCAL_HOST, resolveManagedBitcoindRuntimeConfig, SUPPORTED_BITCOIND_VERSION, verifyManagedBitcoindVersion, writeBitcoinConfForTesting, } from "./managed-bitcoind-service-config.js";
|
|
12
|
+
import { buildManagedServiceArgsForTesting, LOCAL_HOST, resolveManagedBitcoindRuntimeConfig, SUPPORTED_BITCOIND_VERSION, verifyManagedBitcoindVersion, writeManagedBitcoindRuntimeConfigFile, writeManagedBitcoindRuntimeConfigFileFromStatus, writeBitcoinConfForTesting, } from "./managed-bitcoind-service-config.js";
|
|
13
13
|
import { DEFAULT_MANAGED_BITCOIND_SHUTDOWN_TIMEOUT_MS, DEFAULT_MANAGED_BITCOIND_STARTUP_TIMEOUT_MS, FileLockBusyError, acquireManagedBitcoindFileLockWithRetry, isManagedBitcoindProcessAlive, waitForManagedBitcoindProcessExit, sleep, } from "./managed-bitcoind-service-process.js";
|
|
14
14
|
import { createManagedWalletReplica, loadManagedWalletReplicaIfPresent, } from "./managed-bitcoind-service-replica.js";
|
|
15
15
|
import { clearManagedBitcoindRuntimeArtifacts, createBitcoindServiceStatus, createManagedBitcoindNodeHandle, probeManagedBitcoindStatusCandidate, refreshManagedBitcoindStatus, waitForManagedBitcoindRpcReady, writeManagedBitcoindStatus, } from "./managed-bitcoind-service-status.js";
|
|
@@ -109,6 +109,7 @@ async function tryAttachExistingManagedBitcoindService(options) {
|
|
|
109
109
|
return null;
|
|
110
110
|
}
|
|
111
111
|
const refreshed = await refreshManagedBitcoindStatus(probe.status, paths, options);
|
|
112
|
+
await writeManagedBitcoindRuntimeConfigFileFromStatus(paths.bitcoindRuntimeConfigPath, refreshed);
|
|
112
113
|
return createManagedBitcoindNodeHandle({
|
|
113
114
|
status: refreshed,
|
|
114
115
|
paths,
|
|
@@ -207,42 +208,47 @@ export async function attachOrStartManagedBitcoindService(options) {
|
|
|
207
208
|
}
|
|
208
209
|
const nowUnixMs = Date.now();
|
|
209
210
|
const walletReplica = await loadManagedWalletReplicaIfPresent(rpc, startOptions.walletRootId, startOptions.dataDir);
|
|
210
|
-
return
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
211
|
+
return {
|
|
212
|
+
runtimeConfig,
|
|
213
|
+
status: createBitcoindServiceStatus({
|
|
214
|
+
binaryVersion,
|
|
215
|
+
serviceInstanceId: randomBytes(16).toString("hex"),
|
|
216
|
+
state: "ready",
|
|
217
|
+
processId: child.pid ?? null,
|
|
218
|
+
walletRootId: startOptions.walletRootId,
|
|
219
|
+
chain: startOptions.chain,
|
|
220
|
+
dataDir: startOptions.dataDir,
|
|
221
|
+
runtimeRoot: paths.walletRuntimeRoot,
|
|
222
|
+
startHeight: startOptions.startHeight,
|
|
223
|
+
rpc: rpcConfig,
|
|
224
|
+
zmq: zmqConfig,
|
|
225
|
+
p2pPort: runtimeConfig.p2pPort,
|
|
226
|
+
getblockArchiveEndHeight: runtimeConfig.getblockArchiveEndHeight ?? null,
|
|
227
|
+
getblockArchiveSha256: runtimeConfig.getblockArchiveSha256 ?? null,
|
|
228
|
+
walletReplica,
|
|
229
|
+
startedAtUnixMs: nowUnixMs,
|
|
230
|
+
heartbeatAtUnixMs: nowUnixMs,
|
|
231
|
+
lastError: walletReplica.message ?? null,
|
|
232
|
+
}),
|
|
233
|
+
};
|
|
230
234
|
};
|
|
235
|
+
let runtimeConfig;
|
|
231
236
|
let status;
|
|
232
237
|
try {
|
|
233
|
-
status = await startManagedProcess(runtimeOptions);
|
|
238
|
+
({ runtimeConfig, status } = await startManagedProcess(runtimeOptions));
|
|
234
239
|
}
|
|
235
240
|
catch (error) {
|
|
236
241
|
if (runtimeOptions.getblockArchivePath === undefined || runtimeOptions.getblockArchivePath === null) {
|
|
237
242
|
throw error;
|
|
238
243
|
}
|
|
239
|
-
status = await startManagedProcess({
|
|
244
|
+
({ runtimeConfig, status } = await startManagedProcess({
|
|
240
245
|
...runtimeOptions,
|
|
241
246
|
getblockArchivePath: null,
|
|
242
247
|
getblockArchiveEndHeight: null,
|
|
243
248
|
getblockArchiveSha256: null,
|
|
244
|
-
});
|
|
249
|
+
}));
|
|
245
250
|
}
|
|
251
|
+
await writeManagedBitcoindRuntimeConfigFile(paths.bitcoindRuntimeConfigPath, runtimeConfig);
|
|
246
252
|
await writeManagedBitcoindStatus(paths, status);
|
|
247
253
|
return createManagedBitcoindNodeHandle({
|
|
248
254
|
status,
|
|
@@ -83,14 +83,6 @@ export async function writeManagedBitcoindStatus(paths, status) {
|
|
|
83
83
|
await writeFileAtomic(paths.bitcoindPidPath, `${status.processId ?? ""}\n`, { mode: 0o600 });
|
|
84
84
|
await writeFileAtomic(paths.bitcoindReadyPath, `${status.updatedAtUnixMs}\n`, { mode: 0o600 });
|
|
85
85
|
await writeRuntimeStatusFile(paths.bitcoindWalletStatusPath, status.walletReplica ?? createMissingManagedWalletReplicaStatus(status.walletRootId, "Managed Core wallet replica is missing."));
|
|
86
|
-
await writeRuntimeStatusFile(paths.bitcoindRuntimeConfigPath, {
|
|
87
|
-
chain: status.chain,
|
|
88
|
-
rpc: status.rpc,
|
|
89
|
-
zmqPort: status.zmq.port,
|
|
90
|
-
p2pPort: status.p2pPort,
|
|
91
|
-
getblockArchiveEndHeight: status.getblockArchiveEndHeight,
|
|
92
|
-
getblockArchiveSha256: status.getblockArchiveSha256,
|
|
93
|
-
});
|
|
94
86
|
}
|
|
95
87
|
export async function clearManagedBitcoindRuntimeArtifacts(paths) {
|
|
96
88
|
await rm(paths.bitcoindStatusPath, { force: true }).catch(() => undefined);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveWaitingProviderNote } from "../wallet/mining/projection.js";
|
|
1
2
|
function formatMaybeIso(unixMs) {
|
|
2
3
|
return unixMs === null ? "none" : new Date(unixMs).toISOString();
|
|
3
4
|
}
|
|
@@ -33,7 +34,11 @@ function resolveInsufficientFundsNextStep() {
|
|
|
33
34
|
function resolveMiningRuntimeNote(mining) {
|
|
34
35
|
return mining.runtime.currentPublishDecision === "publish-paused-insufficient-funds"
|
|
35
36
|
? "Insufficient BTC to mine."
|
|
36
|
-
: mining.runtime.note
|
|
37
|
+
: mining.runtime.note !== null
|
|
38
|
+
? mining.runtime.note
|
|
39
|
+
: mining.runtime.currentPhase === "waiting-provider"
|
|
40
|
+
? resolveWaitingProviderNote(mining.runtime.providerState)
|
|
41
|
+
: null;
|
|
37
42
|
}
|
|
38
43
|
export function formatMiningSummaryLine(mining) {
|
|
39
44
|
const provider = mining.provider.configured
|
|
@@ -34,7 +34,7 @@ function renderBalanceArtCard(context) {
|
|
|
34
34
|
return renderBalanceArtLine(line, "Funding address:", ` ${fundingAddress}`);
|
|
35
35
|
}
|
|
36
36
|
if (line.includes("Bitcoin Balance:")) {
|
|
37
|
-
return renderBalanceArtLine(line, "Bitcoin Balance:", ` ${formatBitcoinAmount(context.
|
|
37
|
+
return renderBalanceArtLine(line, "Bitcoin Balance:", ` ${formatBitcoinAmount(context.fundingDisplaySats ?? null)}`);
|
|
38
38
|
}
|
|
39
39
|
if (line.includes("Cogcoin Balance:")) {
|
|
40
40
|
return renderBalanceArtLine(line, "Cogcoin Balance:", ` ${formatCogAmount(spendableCog)}`);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { BitcoinBlock, GenesisParameters, IndexerState } from "@cogcoin/indexer/types";
|
|
2
|
-
import type { ApplyBlockResult, Client, ClientCheckpoint, ClientStoreAdapter, ClientTip } from "../types.js";
|
|
2
|
+
import type { ApplyBlockResult, Client, ClientCheckpoint, ClientMirrorDelta, ClientMirrorSnapshot, ClientStoreAdapter, ClientTip } from "../types.js";
|
|
3
3
|
export declare class DefaultClient implements Client {
|
|
4
4
|
#private;
|
|
5
5
|
constructor(store: ClientStoreAdapter, genesisParameters: GenesisParameters, state: IndexerState, tip: ClientTip | null, snapshotInterval: number, blockRecordRetention: number);
|
|
6
6
|
getTip(): Promise<ClientTip | null>;
|
|
7
7
|
getState(): Promise<IndexerState>;
|
|
8
|
+
readMirrorSnapshot(): Promise<ClientMirrorSnapshot>;
|
|
9
|
+
readMirrorDelta(afterHeight: number): Promise<ClientMirrorDelta>;
|
|
8
10
|
applyBlock(block: BitcoinBlock): Promise<ApplyBlockResult>;
|
|
9
11
|
rewindToHeight(height: number): Promise<ClientTip | null>;
|
|
10
12
|
restoreCheckpoint(checkpoint: ClientCheckpoint): Promise<ClientTip>;
|
|
@@ -26,6 +26,28 @@ export class DefaultClient {
|
|
|
26
26
|
await this.#queue;
|
|
27
27
|
return this.#state;
|
|
28
28
|
}
|
|
29
|
+
async readMirrorSnapshot() {
|
|
30
|
+
return this.#enqueue(async () => {
|
|
31
|
+
this.#assertOpen();
|
|
32
|
+
return {
|
|
33
|
+
tip: this.#tip === null ? null : { ...this.#tip },
|
|
34
|
+
stateBytes: serializeIndexerState(this.#state),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async readMirrorDelta(afterHeight) {
|
|
39
|
+
return this.#enqueue(async () => {
|
|
40
|
+
this.#assertOpen();
|
|
41
|
+
const blockRecords = await this.#store.loadBlockRecordsAfter(afterHeight);
|
|
42
|
+
return {
|
|
43
|
+
tip: this.#tip === null ? null : { ...this.#tip },
|
|
44
|
+
blockRecords: blockRecords.map((record) => ({
|
|
45
|
+
...record,
|
|
46
|
+
recordBytes: new Uint8Array(record.recordBytes),
|
|
47
|
+
})),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
29
51
|
async applyBlock(block) {
|
|
30
52
|
return this.#enqueue(async () => {
|
|
31
53
|
this.#assertOpen();
|
package/dist/types.d.ts
CHANGED
|
@@ -19,6 +19,18 @@ export interface StoredBlockRecord {
|
|
|
19
19
|
recordBytes: Uint8Array;
|
|
20
20
|
createdAt: number;
|
|
21
21
|
}
|
|
22
|
+
export interface ClientMirrorSnapshot {
|
|
23
|
+
tip: ClientTip | null;
|
|
24
|
+
stateBytes: Uint8Array;
|
|
25
|
+
}
|
|
26
|
+
export interface ClientMirrorDelta {
|
|
27
|
+
tip: ClientTip | null;
|
|
28
|
+
blockRecords: StoredBlockRecord[];
|
|
29
|
+
}
|
|
30
|
+
export interface ClientMirrorReader {
|
|
31
|
+
readMirrorSnapshot(): Promise<ClientMirrorSnapshot>;
|
|
32
|
+
readMirrorDelta(afterHeight: number): Promise<ClientMirrorDelta>;
|
|
33
|
+
}
|
|
22
34
|
export interface WriteAppliedBlockEntry {
|
|
23
35
|
tip: ClientTip | null;
|
|
24
36
|
stateBytes: Uint8Array | null;
|
|
@@ -48,7 +60,7 @@ export interface ApplyBlockResult {
|
|
|
48
60
|
checkpoint: ClientCheckpoint | null;
|
|
49
61
|
applied: AppliedBlock;
|
|
50
62
|
}
|
|
51
|
-
export interface Client {
|
|
63
|
+
export interface Client extends ClientMirrorReader {
|
|
52
64
|
getTip(): Promise<ClientTip | null>;
|
|
53
65
|
getState(): Promise<IndexerState>;
|
|
54
66
|
applyBlock(block: BitcoinBlock): Promise<ApplyBlockResult>;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import { rename, rm } from "node:fs/promises";
|
|
1
2
|
export interface AtomicWriteOptions {
|
|
2
3
|
mode?: number;
|
|
3
4
|
encoding?: BufferEncoding;
|
|
4
5
|
}
|
|
5
|
-
export
|
|
6
|
-
|
|
6
|
+
export interface AtomicWriteDependencies {
|
|
7
|
+
platform?: NodeJS.Platform;
|
|
8
|
+
rename?: typeof rename;
|
|
9
|
+
rm?: typeof rm;
|
|
10
|
+
sleep?: (ms: number) => Promise<void>;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
}
|
|
13
|
+
export declare function replaceFileAtomicWithRetryForTesting(tempPath: string, filePath: string, dependencies?: AtomicWriteDependencies): Promise<void>;
|
|
14
|
+
export declare function writeFileAtomic(filePath: string, data: string | Uint8Array, options?: AtomicWriteOptions, dependencies?: AtomicWriteDependencies): Promise<void>;
|
|
15
|
+
export declare function writeJsonFileAtomic(filePath: string, value: unknown, options?: AtomicWriteOptions, dependencies?: AtomicWriteDependencies): Promise<void>;
|
package/dist/wallet/fs/atomic.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdir, open, rename } from "node:fs/promises";
|
|
2
|
+
import { mkdir, open, rename, rm } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
+
const WINDOWS_REPLACE_RETRY_DELAY_MS = 25;
|
|
5
|
+
const WINDOWS_REPLACE_RETRY_TIMEOUT_MS = 1_000;
|
|
4
6
|
async function fsyncDirectory(directoryPath) {
|
|
5
7
|
try {
|
|
6
8
|
const directoryHandle = await open(directoryPath, "r");
|
|
@@ -21,7 +23,39 @@ async function fsyncDirectory(directoryPath) {
|
|
|
21
23
|
throw error;
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
|
-
|
|
26
|
+
function isRetryableWindowsReplaceError(error) {
|
|
27
|
+
if (!(error instanceof Error) || !("code" in error)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const code = error.code;
|
|
31
|
+
return code === "EPERM" || code === "EACCES" || code === "EBUSY";
|
|
32
|
+
}
|
|
33
|
+
async function sleep(ms) {
|
|
34
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
35
|
+
}
|
|
36
|
+
export async function replaceFileAtomicWithRetryForTesting(tempPath, filePath, dependencies = {}) {
|
|
37
|
+
const renameImpl = dependencies.rename ?? rename;
|
|
38
|
+
if ((dependencies.platform ?? process.platform) !== "win32") {
|
|
39
|
+
await renameImpl(tempPath, filePath);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const nowImpl = dependencies.now ?? Date.now;
|
|
43
|
+
const sleepImpl = dependencies.sleep ?? sleep;
|
|
44
|
+
const deadline = nowImpl() + WINDOWS_REPLACE_RETRY_TIMEOUT_MS;
|
|
45
|
+
while (true) {
|
|
46
|
+
try {
|
|
47
|
+
await renameImpl(tempPath, filePath);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (!isRetryableWindowsReplaceError(error) || nowImpl() >= deadline) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
await sleepImpl(WINDOWS_REPLACE_RETRY_DELAY_MS);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function writeFileAtomic(filePath, data, options = {}, dependencies = {}) {
|
|
25
59
|
const directoryPath = dirname(filePath);
|
|
26
60
|
const tempPath = join(directoryPath, `${basename(filePath)}.tmp-${randomUUID()}`);
|
|
27
61
|
await mkdir(directoryPath, { recursive: true });
|
|
@@ -38,9 +72,15 @@ export async function writeFileAtomic(filePath, data, options = {}) {
|
|
|
38
72
|
finally {
|
|
39
73
|
await handle.close();
|
|
40
74
|
}
|
|
41
|
-
|
|
75
|
+
try {
|
|
76
|
+
await replaceFileAtomicWithRetryForTesting(tempPath, filePath, dependencies);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
await (dependencies.rm ?? rm)(tempPath, { force: true }).catch(() => undefined);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
42
82
|
await fsyncDirectory(directoryPath);
|
|
43
83
|
}
|
|
44
|
-
export async function writeJsonFileAtomic(filePath, value, options = {}) {
|
|
45
|
-
await writeFileAtomic(filePath, `${JSON.stringify(value, null, 2)}\n`, options);
|
|
84
|
+
export async function writeJsonFileAtomic(filePath, value, options = {}, dependencies = {}) {
|
|
85
|
+
await writeFileAtomic(filePath, `${JSON.stringify(value, null, 2)}\n`, options, dependencies);
|
|
46
86
|
}
|
|
@@ -10,7 +10,7 @@ import { MiningProviderRequestError } from "./sentences.js";
|
|
|
10
10
|
import { isInsufficientFundsError } from "../tx/common.js";
|
|
11
11
|
import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
|
|
12
12
|
import { createRpcClient } from "../../bitcoind/node.js";
|
|
13
|
-
import { buildPrePublishStatusOverrides, } from "./projection.js";
|
|
13
|
+
import { buildPrePublishStatusOverrides, resolveWaitingProviderNote, } from "./projection.js";
|
|
14
14
|
function createInitialState(options) {
|
|
15
15
|
return {
|
|
16
16
|
phase: "idle",
|
|
@@ -149,7 +149,7 @@ export async function runMiningPhaseMachine(options) {
|
|
|
149
149
|
currentPublishDecision: null,
|
|
150
150
|
providerState: options.loopState.providerWaitState,
|
|
151
151
|
lastError: options.loopState.providerWaitLastError,
|
|
152
|
-
note:
|
|
152
|
+
note: resolveWaitingProviderNote(options.loopState.providerWaitState),
|
|
153
153
|
});
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
@@ -162,7 +162,7 @@ export async function runMiningPhaseMachine(options) {
|
|
|
162
162
|
currentPublishDecision: null,
|
|
163
163
|
providerState: options.loopState.providerWaitState,
|
|
164
164
|
lastError: options.loopState.providerWaitLastError,
|
|
165
|
-
note:
|
|
165
|
+
note: resolveWaitingProviderNote(options.loopState.providerWaitState),
|
|
166
166
|
});
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
@@ -231,7 +231,7 @@ export async function runMiningPhaseMachine(options) {
|
|
|
231
231
|
currentPublishDecision: null,
|
|
232
232
|
providerState: options.loopState.providerWaitState ?? error.providerState,
|
|
233
233
|
lastError: error.message,
|
|
234
|
-
note:
|
|
234
|
+
note: resolveWaitingProviderNote(options.loopState.providerWaitState ?? error.providerState),
|
|
235
235
|
});
|
|
236
236
|
await options.appendEvent(createMiningEventRecord("publish-paused-provider", error.message, {
|
|
237
237
|
level: "warn",
|
|
@@ -37,6 +37,7 @@ export interface MiningRuntimeStatusOverrides {
|
|
|
37
37
|
note?: string | null;
|
|
38
38
|
livePublishInMempool?: boolean | null;
|
|
39
39
|
}
|
|
40
|
+
export declare function resolveWaitingProviderNote(providerState: MiningRuntimeStatusV1["providerState"] | null): string;
|
|
40
41
|
export declare function buildPrePublishStatusOverrides(options: {
|
|
41
42
|
state: WalletStateV1;
|
|
42
43
|
candidate: MiningCandidate;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { livePublishTargetsCandidateTip } from "./engine-state.js";
|
|
2
2
|
import { normalizeMiningPublishState, normalizeMiningStateRecord } from "./state.js";
|
|
3
3
|
import { MINING_WORKER_API_VERSION, MINING_WORKER_HEARTBEAT_STALE_MS, } from "./constants.js";
|
|
4
|
+
export function resolveWaitingProviderNote(providerState) {
|
|
5
|
+
switch (providerState) {
|
|
6
|
+
case "backoff":
|
|
7
|
+
return "Mining is waiting because the sentence provider had a transient failure and will be retried automatically.";
|
|
8
|
+
case "rate-limited":
|
|
9
|
+
return "Mining is waiting because the sentence provider is rate limited and will be retried automatically.";
|
|
10
|
+
case "auth-error":
|
|
11
|
+
return "Mining is waiting because the sentence provider rejected the configured API key.";
|
|
12
|
+
case "not-found":
|
|
13
|
+
return "Mining is waiting because the configured sentence-provider model was not found.";
|
|
14
|
+
default:
|
|
15
|
+
return "Mining is waiting for the sentence provider to recover.";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
4
18
|
export function buildPrePublishStatusOverrides(options) {
|
|
5
19
|
const replacing = options.state.miningState.currentTxid !== null;
|
|
6
20
|
const replacingAcrossTips = replacing && !livePublishTargetsCandidateTip({
|
|
@@ -249,7 +263,7 @@ export async function buildMiningRuntimeStatusSnapshot(options) {
|
|
|
249
263
|
: existing?.currentPhase === "resuming"
|
|
250
264
|
? "Mining discarded stale in-flight work after a large local runtime gap and is rechecking health."
|
|
251
265
|
: reuseExistingProviderWait
|
|
252
|
-
?
|
|
266
|
+
? resolveWaitingProviderNote(existing?.providerState ?? providerState)
|
|
253
267
|
: existing?.currentPhase === "waiting-indexer"
|
|
254
268
|
? "Mining is waiting for Bitcoin Core and the indexer to align."
|
|
255
269
|
: existing?.currentPhase === "waiting-bitcoin-network"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getBalance, getBlockWinners, lookupDomainById, } from "@cogcoin/indexer/queries";
|
|
2
2
|
import { displayToInternalBlockhash } from "@cogcoin/scoring";
|
|
3
3
|
import { FOLLOW_VISIBLE_PRIOR_BLOCKS } from "../../bitcoind/client/follow-block-times.js";
|
|
4
|
+
import { readFundingBalanceSummary } from "../read/local-state.js";
|
|
4
5
|
import { buildMiningTipKey, resetMiningUiForTip } from "./engine-state.js";
|
|
5
|
-
import { deriveMiningWordIndices,
|
|
6
|
+
import { deriveMiningWordIndices, resolveBip39WordsFromIndices, } from "./engine-utils.js";
|
|
6
7
|
import { createEmptyMiningFollowVisualizerState } from "./visualizer.js";
|
|
7
8
|
function cloneSettledBoardEntries(entries) {
|
|
8
9
|
return entries.map((entry) => ({
|
|
@@ -156,14 +157,11 @@ export function syncMiningUiForCurrentTip(options) {
|
|
|
156
157
|
};
|
|
157
158
|
}
|
|
158
159
|
export async function resolveFundingDisplaySats(state, rpc) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
return sum + numberToSats(entry.amount);
|
|
166
|
-
}, 0n);
|
|
160
|
+
const summary = await readFundingBalanceSummary({
|
|
161
|
+
state,
|
|
162
|
+
rpc,
|
|
163
|
+
});
|
|
164
|
+
return summary.fundingDisplaySats ?? 0n;
|
|
167
165
|
}
|
|
168
166
|
export async function loadMiningVisibleFollowBlockTimes(options) {
|
|
169
167
|
if (options.indexedTipHeight === null || options.indexedTipHashHex === null) {
|
|
@@ -3,6 +3,7 @@ import { centerLine, normalizeInlineText, truncateLine } from "../../bitcoind/pr
|
|
|
3
3
|
import { advanceFollowSceneState, createFollowSceneState, replaceFollowBlockTimes, syncFollowSceneState, } from "../../bitcoind/progress/follow-scene.js";
|
|
4
4
|
import { DEFAULT_RENDER_CLOCK, resolveTtyRenderPolicy, TtyRenderThrottle, } from "../../bitcoind/progress/render-policy.js";
|
|
5
5
|
import { TtyProgressRenderer } from "../../bitcoind/progress/tty-renderer.js";
|
|
6
|
+
import { resolveWaitingProviderNote } from "./projection.js";
|
|
6
7
|
const MINING_ARTWORK_COG_WIDTH = 22;
|
|
7
8
|
const MINING_SENTENCE_BOARD_SIZE = 5;
|
|
8
9
|
const MINING_SENTENCE_BOARD_WRAP_WIDTH = 80;
|
|
@@ -282,7 +283,7 @@ export function describeMiningVisualizerProgress(snapshot) {
|
|
|
282
283
|
case "resuming":
|
|
283
284
|
return "Mining discarded stale in-flight work after a large local runtime gap and is rechecking health.";
|
|
284
285
|
case "waiting-provider":
|
|
285
|
-
return
|
|
286
|
+
return resolveWaitingProviderNote(snapshot.providerState);
|
|
286
287
|
case "waiting-indexer":
|
|
287
288
|
return "Mining is waiting for Bitcoin Core and the indexer to align.";
|
|
288
289
|
case "waiting-bitcoin-network":
|
|
@@ -5,7 +5,7 @@ import { inspectMiningControlPlane } from "../mining/index.js";
|
|
|
5
5
|
import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
|
|
6
6
|
import { createDefaultWalletSecretProvider, } from "../state/provider.js";
|
|
7
7
|
import { openManagedWalletReadServiceBundle } from "./managed-services.js";
|
|
8
|
-
import { inspectWalletLocalState,
|
|
8
|
+
import { inspectWalletLocalState, readFundingBalanceSummary } from "./local-state.js";
|
|
9
9
|
import { createWalletReadModel } from "./project.js";
|
|
10
10
|
const DEFAULT_SERVICE_START_TIMEOUT_MS = 60_000;
|
|
11
11
|
export async function openWalletReadContext(options) {
|
|
@@ -31,7 +31,7 @@ export async function openWalletReadContext(options) {
|
|
|
31
31
|
expectedIndexerBinaryVersion,
|
|
32
32
|
now,
|
|
33
33
|
});
|
|
34
|
-
const fundingSpendableSats = await
|
|
34
|
+
const { fundingDisplaySats, fundingSpendableSats, } = await readFundingBalanceSummary({
|
|
35
35
|
state: localState.state,
|
|
36
36
|
rpc: managedServices.node.rpc,
|
|
37
37
|
});
|
|
@@ -58,6 +58,7 @@ export async function openWalletReadContext(options) {
|
|
|
58
58
|
model: localState.state === null
|
|
59
59
|
? null
|
|
60
60
|
: createWalletReadModel(localState.state, managedServices.snapshot),
|
|
61
|
+
fundingDisplaySats,
|
|
61
62
|
fundingSpendableSats,
|
|
62
63
|
mining,
|
|
63
64
|
async close() {
|
|
@@ -7,6 +7,10 @@ type WalletLocalStateDeps = {
|
|
|
7
7
|
attachOrStartManagedBitcoindService: typeof attachOrStartManagedBitcoindService;
|
|
8
8
|
createRpcClient: typeof createRpcClient;
|
|
9
9
|
};
|
|
10
|
+
export interface FundingBalanceSummary {
|
|
11
|
+
fundingDisplaySats: bigint | null;
|
|
12
|
+
fundingSpendableSats: bigint | null;
|
|
13
|
+
}
|
|
10
14
|
export declare function inspectWalletLocalStateWithDependencies(options?: {
|
|
11
15
|
dataDir?: string;
|
|
12
16
|
secretProvider?: WalletSecretProvider;
|
|
@@ -25,4 +29,8 @@ export declare function readFundingSpendableSats(options: {
|
|
|
25
29
|
state: WalletLocalStateStatus["state"];
|
|
26
30
|
rpc: ReturnType<typeof createRpcClient> | null;
|
|
27
31
|
}): Promise<bigint | null>;
|
|
32
|
+
export declare function readFundingBalanceSummary(options: {
|
|
33
|
+
state: WalletLocalStateStatus["state"];
|
|
34
|
+
rpc: Pick<ReturnType<typeof createRpcClient>, "listUnspent"> | null;
|
|
35
|
+
}): Promise<FundingBalanceSummary>;
|
|
28
36
|
export {};
|
|
@@ -22,6 +22,10 @@ function isSpendableFundingUtxo(entry, fundingScriptPubKeyHex) {
|
|
|
22
22
|
&& entry.spendable !== false
|
|
23
23
|
&& entry.safe !== false;
|
|
24
24
|
}
|
|
25
|
+
function isDisplayFundingUtxo(entry, fundingScriptPubKeyHex) {
|
|
26
|
+
return entry.scriptPubKey === fundingScriptPubKeyHex
|
|
27
|
+
&& entry.spendable !== false;
|
|
28
|
+
}
|
|
25
29
|
async function pathExists(path) {
|
|
26
30
|
try {
|
|
27
31
|
await access(path, constants.F_OK);
|
|
@@ -217,17 +221,39 @@ export async function inspectWalletLocalState(options = {}) {
|
|
|
217
221
|
return inspectWalletLocalStateWithDependencies(options);
|
|
218
222
|
}
|
|
219
223
|
export async function readFundingSpendableSats(options) {
|
|
224
|
+
return (await readFundingBalanceSummary(options)).fundingSpendableSats;
|
|
225
|
+
}
|
|
226
|
+
export async function readFundingBalanceSummary(options) {
|
|
220
227
|
if (options.state === null || options.rpc === null) {
|
|
221
|
-
return
|
|
228
|
+
return {
|
|
229
|
+
fundingDisplaySats: null,
|
|
230
|
+
fundingSpendableSats: null,
|
|
231
|
+
};
|
|
222
232
|
}
|
|
223
233
|
const state = options.state;
|
|
224
234
|
try {
|
|
225
|
-
const utxos = await options.rpc.listUnspent(state.managedCoreWallet.walletName,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
235
|
+
const utxos = await options.rpc.listUnspent(state.managedCoreWallet.walletName, 0);
|
|
236
|
+
let fundingDisplaySats = 0n;
|
|
237
|
+
let fundingSpendableSats = 0n;
|
|
238
|
+
for (const entry of utxos) {
|
|
239
|
+
if (!isDisplayFundingUtxo(entry, state.funding.scriptPubKeyHex)) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const amountSats = btcAmountToSats(entry.amount);
|
|
243
|
+
fundingDisplaySats += amountSats;
|
|
244
|
+
if (isSpendableFundingUtxo(entry, state.funding.scriptPubKeyHex)) {
|
|
245
|
+
fundingSpendableSats += amountSats;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
fundingDisplaySats,
|
|
250
|
+
fundingSpendableSats,
|
|
251
|
+
};
|
|
229
252
|
}
|
|
230
253
|
catch {
|
|
231
|
-
return
|
|
254
|
+
return {
|
|
255
|
+
fundingDisplaySats: null,
|
|
256
|
+
fundingSpendableSats: null,
|
|
257
|
+
};
|
|
232
258
|
}
|
|
233
259
|
}
|
|
@@ -94,6 +94,7 @@ export interface WalletReadContext {
|
|
|
94
94
|
indexer: WalletIndexerStatus;
|
|
95
95
|
snapshot: WalletSnapshotView | null;
|
|
96
96
|
model: WalletReadModel | null;
|
|
97
|
+
fundingDisplaySats: bigint | null;
|
|
97
98
|
fundingSpendableSats: bigint | null;
|
|
98
99
|
mining?: MiningControlPlaneView;
|
|
99
100
|
close(): Promise<void>;
|
package/package.json
CHANGED