@cogcoin/client 1.0.2 → 1.1.0
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 +3 -2
- package/dist/bitcoind/client/factory.d.ts +0 -8
- package/dist/bitcoind/client/factory.js +1 -59
- package/dist/bitcoind/client/managed-client.d.ts +1 -3
- package/dist/bitcoind/client/managed-client.js +3 -47
- package/dist/bitcoind/indexer-daemon-main.js +173 -28
- package/dist/bitcoind/indexer-daemon.d.ts +11 -3
- package/dist/bitcoind/indexer-daemon.js +123 -57
- package/dist/bitcoind/indexer-monitor.d.ts +12 -0
- package/dist/bitcoind/indexer-monitor.js +89 -0
- package/dist/bitcoind/progress/follow-scene.d.ts +7 -1
- package/dist/bitcoind/progress/follow-scene.js +87 -4
- package/dist/bitcoind/progress/tty-renderer.d.ts +2 -0
- package/dist/bitcoind/progress/tty-renderer.js +2 -0
- package/dist/bitcoind/testing.d.ts +0 -1
- package/dist/bitcoind/testing.js +0 -1
- package/dist/bitcoind/types.d.ts +5 -2
- package/dist/cli/commands/follow.js +44 -49
- package/dist/cli/commands/mining-admin.js +56 -2
- package/dist/cli/commands/mining-read.js +43 -3
- package/dist/cli/commands/mining-runtime.js +91 -73
- package/dist/cli/commands/service-runtime.js +42 -2
- package/dist/cli/commands/status.js +3 -1
- package/dist/cli/commands/sync.js +50 -90
- package/dist/cli/commands/wallet-admin.js +21 -3
- package/dist/cli/commands/wallet-read.js +2 -0
- package/dist/cli/context.js +5 -1
- package/dist/cli/managed-indexer-observer.d.ts +33 -0
- package/dist/cli/managed-indexer-observer.js +163 -0
- package/dist/cli/mining-format.d.ts +3 -1
- package/dist/cli/mining-format.js +35 -0
- package/dist/cli/mining-json.d.ts +11 -1
- package/dist/cli/mining-json.js +9 -0
- package/dist/cli/output.js +24 -0
- package/dist/cli/parse.d.ts +1 -1
- package/dist/cli/parse.js +23 -0
- package/dist/cli/read-json.d.ts +13 -1
- package/dist/cli/read-json.js +31 -0
- package/dist/cli/runner.js +4 -2
- package/dist/cli/signals.d.ts +12 -0
- package/dist/cli/signals.js +31 -13
- package/dist/cli/types.d.ts +8 -4
- package/dist/cli/update-service.d.ts +2 -12
- package/dist/cli/update-service.js +2 -68
- package/dist/semver.d.ts +12 -0
- package/dist/semver.js +68 -0
- package/dist/wallet/lifecycle.js +0 -6
- package/dist/wallet/mining/config.js +54 -3
- package/dist/wallet/mining/control.d.ts +5 -2
- package/dist/wallet/mining/control.js +153 -34
- package/dist/wallet/mining/domain-prompts.d.ts +17 -0
- package/dist/wallet/mining/domain-prompts.js +130 -0
- package/dist/wallet/mining/index.d.ts +2 -1
- package/dist/wallet/mining/index.js +1 -0
- package/dist/wallet/mining/runner.d.ts +58 -2
- package/dist/wallet/mining/runner.js +553 -331
- package/dist/wallet/mining/sentence-protocol.d.ts +1 -0
- package/dist/wallet/mining/sentences.js +7 -4
- package/dist/wallet/mining/types.d.ts +26 -0
- package/dist/wallet/mining/visualizer.d.ts +3 -0
- package/dist/wallet/mining/visualizer.js +106 -12
- package/dist/wallet/read/context.d.ts +1 -0
- package/dist/wallet/read/context.js +15 -10
- package/dist/wallet/reset.js +0 -1
- package/dist/wallet/state/client-password-agent.js +4 -1
- package/dist/wallet/state/client-password.js +15 -8
- package/dist/wallet/tx/anchor.js +0 -1
- package/dist/wallet/tx/bitcoin-transfer.js +0 -1
- package/dist/wallet/tx/cog.js +0 -3
- package/dist/wallet/tx/common.js +1 -1
- package/dist/wallet/tx/domain-admin.js +0 -1
- package/dist/wallet/tx/domain-market.js +0 -3
- package/dist/wallet/tx/field.js +0 -1
- package/dist/wallet/tx/register.js +0 -1
- package/dist/wallet/tx/reputation.js +0 -1
- package/package.json +1 -1
|
@@ -5,9 +5,11 @@ import { join } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { getBalance, getBlockWinners, lookupDomain, lookupDomainById, } from "@cogcoin/indexer/queries";
|
|
7
7
|
import { assaySentences, deriveBlendSeed, displayToInternalBlockhash, getWords, settleBlock, } from "@cogcoin/scoring";
|
|
8
|
+
import { wordlist as englishWordlist } from "@scure/bip39/wordlists/english.js";
|
|
8
9
|
import { probeIndexerDaemon } from "../../bitcoind/indexer-daemon.js";
|
|
10
|
+
import { isRetryableManagedRpcError } from "../../bitcoind/retryable-rpc.js";
|
|
9
11
|
import { FOLLOW_VISIBLE_PRIOR_BLOCKS } from "../../bitcoind/client/follow-block-times.js";
|
|
10
|
-
import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
|
|
12
|
+
import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, stopManagedBitcoindService, } from "../../bitcoind/service.js";
|
|
11
13
|
import { createRpcClient } from "../../bitcoind/node.js";
|
|
12
14
|
import { COG_OPCODES, COG_PREFIX } from "../cogop/constants.js";
|
|
13
15
|
import { extractOpReturnPayloadFromScriptHex } from "../tx/register.js";
|
|
@@ -28,6 +30,35 @@ import { generateMiningSentences, MiningProviderRequestError } from "./sentences
|
|
|
28
30
|
import { createEmptyMiningFollowVisualizerState, MiningFollowVisualizer, } from "./visualizer.js";
|
|
29
31
|
const BEST_BLOCK_POLL_INTERVAL_MS = 500;
|
|
30
32
|
const BACKGROUND_START_TIMEOUT_MS = 15_000;
|
|
33
|
+
const MINING_BITCOIN_RECOVERY_GRACE_MS = 15_000;
|
|
34
|
+
const MINING_BITCOIN_RECOVERY_RESTART_COOLDOWN_MS = 60_000;
|
|
35
|
+
const MINING_BITCOIN_RECOVERY_NOTE = "Mining lost contact with the local Bitcoin RPC service and is waiting for it to recover.";
|
|
36
|
+
function resolveBip39WordsFromIndices(indices) {
|
|
37
|
+
if (indices === null || indices === undefined) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const words = [];
|
|
41
|
+
for (const index of indices) {
|
|
42
|
+
if (!Number.isInteger(index) || index < 0 || index >= englishWordlist.length) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
words.push(englishWordlist[index]);
|
|
46
|
+
}
|
|
47
|
+
return words;
|
|
48
|
+
}
|
|
49
|
+
function resolveSettledWinnerRequiredWords(options) {
|
|
50
|
+
const storedWords = resolveBip39WordsFromIndices(options.bip39WordIndices);
|
|
51
|
+
if (storedWords.length > 0) {
|
|
52
|
+
return storedWords;
|
|
53
|
+
}
|
|
54
|
+
if (options.snapshotTipPreviousHashHex === null
|
|
55
|
+
|| options.snapshotTipPreviousHashHex === undefined
|
|
56
|
+
|| !Number.isInteger(options.domainId)
|
|
57
|
+
|| options.domainId <= 0) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
return resolveBip39WordsFromIndices(deriveMiningWordIndices(Buffer.from(displayToInternalBlockhash(options.snapshotTipPreviousHashHex), "hex"), options.domainId));
|
|
61
|
+
}
|
|
31
62
|
function resolveSnapshotOverride(override, fallback) {
|
|
32
63
|
return override === undefined ? fallback : override;
|
|
33
64
|
}
|
|
@@ -442,11 +473,41 @@ function createMiningLoopState() {
|
|
|
442
473
|
selectedCandidate: null,
|
|
443
474
|
ui: createEmptyMiningFollowVisualizerState(),
|
|
444
475
|
waitingNote: null,
|
|
476
|
+
bitcoinRecoveryFirstFailureAtUnixMs: null,
|
|
477
|
+
bitcoinRecoveryFirstUnreachableAtUnixMs: null,
|
|
478
|
+
bitcoinRecoveryLastRestartAttemptAtUnixMs: null,
|
|
479
|
+
bitcoinRecoveryServiceInstanceId: null,
|
|
480
|
+
bitcoinRecoveryProcessId: null,
|
|
481
|
+
reconnectSettledUntilUnixMs: null,
|
|
482
|
+
tipSettledUntilUnixMs: null,
|
|
445
483
|
};
|
|
446
484
|
}
|
|
447
485
|
export function createMiningLoopStateForTesting() {
|
|
448
486
|
return createMiningLoopState();
|
|
449
487
|
}
|
|
488
|
+
function expireMiningSettleWindows(loopState, nowUnixMs) {
|
|
489
|
+
if (loopState.reconnectSettledUntilUnixMs !== null
|
|
490
|
+
&& loopState.reconnectSettledUntilUnixMs <= nowUnixMs) {
|
|
491
|
+
loopState.reconnectSettledUntilUnixMs = null;
|
|
492
|
+
}
|
|
493
|
+
if (loopState.tipSettledUntilUnixMs !== null
|
|
494
|
+
&& loopState.tipSettledUntilUnixMs <= nowUnixMs) {
|
|
495
|
+
loopState.tipSettledUntilUnixMs = null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function setMiningReconnectSettleWindow(loopState, nowUnixMs) {
|
|
499
|
+
loopState.reconnectSettledUntilUnixMs = nowUnixMs + MINING_NETWORK_SETTLE_WINDOW_MS;
|
|
500
|
+
}
|
|
501
|
+
function setMiningTipSettleWindow(loopState, nowUnixMs) {
|
|
502
|
+
loopState.tipSettledUntilUnixMs = nowUnixMs + MINING_TIP_SETTLE_WINDOW_MS;
|
|
503
|
+
}
|
|
504
|
+
function buildMiningSettleWindowStatusOverrides(loopState, nowUnixMs) {
|
|
505
|
+
expireMiningSettleWindows(loopState, nowUnixMs);
|
|
506
|
+
return {
|
|
507
|
+
reconnectSettledUntilUnixMs: loopState.reconnectSettledUntilUnixMs,
|
|
508
|
+
tipSettledUntilUnixMs: loopState.tipSettledUntilUnixMs,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
450
511
|
function buildMiningTipKey(bestBlockHash, targetBlockHeight) {
|
|
451
512
|
if (bestBlockHash === null || targetBlockHeight === null) {
|
|
452
513
|
return null;
|
|
@@ -491,6 +552,11 @@ function resolveCurrentMinedBlockBoard(options) {
|
|
|
491
552
|
rank: winner.rank,
|
|
492
553
|
domainName: lookupDomainById(options.snapshotState, winner.domainId)?.name ?? fallbackSettledWinnerDomainName(winner.domainId),
|
|
493
554
|
sentence: winner.sentenceText ?? "[unavailable]",
|
|
555
|
+
requiredWords: resolveSettledWinnerRequiredWords({
|
|
556
|
+
domainId: winner.domainId,
|
|
557
|
+
bip39WordIndices: winner.bip39WordIndices,
|
|
558
|
+
snapshotTipPreviousHashHex: options.snapshotTipPreviousHashHex,
|
|
559
|
+
}),
|
|
494
560
|
}));
|
|
495
561
|
return {
|
|
496
562
|
settledBlockHeight,
|
|
@@ -498,12 +564,16 @@ function resolveCurrentMinedBlockBoard(options) {
|
|
|
498
564
|
};
|
|
499
565
|
}
|
|
500
566
|
export function resolveSettledBoardForTesting(options) {
|
|
501
|
-
return resolveCurrentMinedBlockBoard(
|
|
567
|
+
return resolveCurrentMinedBlockBoard({
|
|
568
|
+
...options,
|
|
569
|
+
snapshotTipPreviousHashHex: options.snapshotTipPreviousHashHex ?? null,
|
|
570
|
+
});
|
|
502
571
|
}
|
|
503
|
-
function syncMiningUiSettledBoard(loopState, snapshotState, snapshotTipHeight) {
|
|
572
|
+
function syncMiningUiSettledBoard(loopState, snapshotState, snapshotTipHeight, snapshotTipPreviousHashHex) {
|
|
504
573
|
const settledBoard = resolveCurrentMinedBlockBoard({
|
|
505
574
|
snapshotState,
|
|
506
575
|
snapshotTipHeight,
|
|
576
|
+
snapshotTipPreviousHashHex,
|
|
507
577
|
nodeBestHeight: null,
|
|
508
578
|
});
|
|
509
579
|
loopState.ui.settledBlockHeight = settledBoard.settledBlockHeight;
|
|
@@ -514,17 +584,20 @@ function syncMiningUiForCurrentTip(options) {
|
|
|
514
584
|
? null
|
|
515
585
|
: options.nodeBestHeight + 1;
|
|
516
586
|
const tipKey = buildMiningTipKey(options.nodeBestHash, targetBlockHeight);
|
|
517
|
-
|
|
587
|
+
const priorTipKey = options.loopState.currentTipKey;
|
|
588
|
+
const tipChanged = tipKey !== null && tipKey !== priorTipKey;
|
|
589
|
+
if (tipKey !== priorTipKey) {
|
|
518
590
|
options.loopState.currentTipKey = tipKey;
|
|
519
591
|
resetMiningUiForTip(options.loopState, targetBlockHeight);
|
|
520
592
|
if (options.recentWin !== null) {
|
|
521
593
|
options.loopState.ui.recentWin = options.recentWin;
|
|
522
594
|
}
|
|
523
595
|
}
|
|
524
|
-
syncMiningUiSettledBoard(options.loopState, options.snapshotState, options.snapshotTipHeight);
|
|
596
|
+
syncMiningUiSettledBoard(options.loopState, options.snapshotState, options.snapshotTipHeight, options.snapshotTipPreviousHashHex);
|
|
525
597
|
return {
|
|
526
598
|
targetBlockHeight,
|
|
527
599
|
tipKey,
|
|
600
|
+
tipChanged,
|
|
528
601
|
};
|
|
529
602
|
}
|
|
530
603
|
function setMiningUiCandidate(loopState, candidate) {
|
|
@@ -556,6 +629,123 @@ function clearSelectedCandidate(loopState) {
|
|
|
556
629
|
loopState.selectedCandidateTipKey = null;
|
|
557
630
|
loopState.selectedCandidate = null;
|
|
558
631
|
}
|
|
632
|
+
function clearMiningUiTransientCandidate(loopState) {
|
|
633
|
+
loopState.ui.provisionalRequiredWords = [];
|
|
634
|
+
loopState.ui.provisionalEntry = {
|
|
635
|
+
domainName: null,
|
|
636
|
+
sentence: null,
|
|
637
|
+
};
|
|
638
|
+
loopState.ui.latestSentence = null;
|
|
639
|
+
}
|
|
640
|
+
function discardMiningLoopTransientWork(loopState, walletRootId) {
|
|
641
|
+
clearMiningGateCache(walletRootId);
|
|
642
|
+
clearSelectedCandidate(loopState);
|
|
643
|
+
clearMiningUiTransientCandidate(loopState);
|
|
644
|
+
loopState.waitingNote = null;
|
|
645
|
+
}
|
|
646
|
+
function resolveMiningBitcoindRecoveryIdentity(value) {
|
|
647
|
+
const raw = (value ?? {});
|
|
648
|
+
return {
|
|
649
|
+
serviceInstanceId: raw.serviceInstanceId ?? null,
|
|
650
|
+
processId: raw.processId ?? raw.pid ?? null,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function miningBitcoindRecoveryIdentityMatches(left, right) {
|
|
654
|
+
if (left.serviceInstanceId !== null && right.serviceInstanceId !== null) {
|
|
655
|
+
return left.serviceInstanceId === right.serviceInstanceId;
|
|
656
|
+
}
|
|
657
|
+
if (left.processId !== null && right.processId !== null) {
|
|
658
|
+
return left.processId === right.processId;
|
|
659
|
+
}
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
function rememberMiningBitcoindRecoveryIdentity(loopState, value) {
|
|
663
|
+
const next = resolveMiningBitcoindRecoveryIdentity(value);
|
|
664
|
+
if (next.serviceInstanceId === null && next.processId === null) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
const previous = {
|
|
668
|
+
serviceInstanceId: loopState.bitcoinRecoveryServiceInstanceId,
|
|
669
|
+
processId: loopState.bitcoinRecoveryProcessId,
|
|
670
|
+
};
|
|
671
|
+
const changed = (previous.serviceInstanceId !== null
|
|
672
|
+
|| previous.processId !== null) && !miningBitcoindRecoveryIdentityMatches(previous, next);
|
|
673
|
+
loopState.bitcoinRecoveryServiceInstanceId = next.serviceInstanceId ?? (next.processId !== null && previous.processId === next.processId
|
|
674
|
+
? previous.serviceInstanceId
|
|
675
|
+
: null);
|
|
676
|
+
loopState.bitcoinRecoveryProcessId = next.processId ?? (next.serviceInstanceId !== null && previous.serviceInstanceId === next.serviceInstanceId
|
|
677
|
+
? previous.processId
|
|
678
|
+
: null);
|
|
679
|
+
return changed;
|
|
680
|
+
}
|
|
681
|
+
function resetMiningBitcoindRecoveryState(loopState, value) {
|
|
682
|
+
const hadRecovery = loopState.bitcoinRecoveryFirstFailureAtUnixMs !== null;
|
|
683
|
+
loopState.bitcoinRecoveryFirstFailureAtUnixMs = null;
|
|
684
|
+
loopState.bitcoinRecoveryFirstUnreachableAtUnixMs = null;
|
|
685
|
+
loopState.bitcoinRecoveryLastRestartAttemptAtUnixMs = null;
|
|
686
|
+
if (value !== undefined) {
|
|
687
|
+
rememberMiningBitcoindRecoveryIdentity(loopState, value);
|
|
688
|
+
}
|
|
689
|
+
return hadRecovery;
|
|
690
|
+
}
|
|
691
|
+
function isMiningBitcoindRecoveryPidAlive(pid) {
|
|
692
|
+
if (pid === null || pid === undefined || !Number.isInteger(pid) || pid <= 0) {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
process.kill(pid, 0);
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
if (error instanceof Error && "code" in error && error.code === "EPERM") {
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function describeRecoverableMiningBitcoindError(error) {
|
|
707
|
+
return error instanceof Error ? error.message : String(error);
|
|
708
|
+
}
|
|
709
|
+
function isRecoverableMiningBitcoindError(error) {
|
|
710
|
+
if (isRetryableManagedRpcError(error)) {
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
if (!(error instanceof Error)) {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
if ("code" in error) {
|
|
717
|
+
const code = error.code;
|
|
718
|
+
if (code === "ENOENT" || code === "ECONNREFUSED" || code === "ECONNRESET") {
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return error.message === "managed_bitcoind_service_start_timeout"
|
|
723
|
+
|| error.message === "bitcoind_cookie_timeout"
|
|
724
|
+
|| error.message.includes("cookie file is unavailable")
|
|
725
|
+
|| error.message.includes("cookie file could not be read")
|
|
726
|
+
|| error.message.includes("ECONNREFUSED")
|
|
727
|
+
|| error.message.includes("ECONNRESET")
|
|
728
|
+
|| error.message.includes("socket hang up");
|
|
729
|
+
}
|
|
730
|
+
async function attachManagedBitcoindForRecovery(options) {
|
|
731
|
+
try {
|
|
732
|
+
const service = await options.attachService({
|
|
733
|
+
dataDir: options.dataDir,
|
|
734
|
+
chain: "main",
|
|
735
|
+
startHeight: 0,
|
|
736
|
+
walletRootId: options.walletRootId,
|
|
737
|
+
});
|
|
738
|
+
const serviceStatus = await service.refreshServiceStatus?.().catch(() => null);
|
|
739
|
+
rememberMiningBitcoindRecoveryIdentity(options.loopState, serviceStatus ?? { pid: service.pid });
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
if (!isRecoverableMiningBitcoindError(error)) {
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
559
749
|
async function resolveFundingDisplaySats(state, rpc) {
|
|
560
750
|
const utxos = await rpc.listUnspent(state.managedCoreWallet.walletName, 0);
|
|
561
751
|
return utxos.reduce((sum, entry) => {
|
|
@@ -604,6 +794,7 @@ function createIndexedMiningFollowVisualizerState(readContext) {
|
|
|
604
794
|
const settledBoard = resolveCurrentMinedBlockBoard({
|
|
605
795
|
snapshotState: readContext.snapshot?.state ?? null,
|
|
606
796
|
snapshotTipHeight: readContext.snapshot?.tip?.height ?? readContext.indexer.snapshotTip?.height ?? null,
|
|
797
|
+
snapshotTipPreviousHashHex: readContext.snapshot?.tip?.previousHashHex ?? readContext.indexer.snapshotTip?.previousHashHex ?? null,
|
|
607
798
|
nodeBestHeight: readContext.nodeStatus?.nodeBestHeight ?? null,
|
|
608
799
|
});
|
|
609
800
|
uiState.settledBlockHeight = settledBoard.settledBlockHeight;
|
|
@@ -925,6 +1116,8 @@ function buildStatusSnapshot(view, overrides = {}) {
|
|
|
925
1116
|
currentAbsoluteFeeSats: resolveSnapshotOverride(overrides.currentAbsoluteFeeSats, view.runtime.currentAbsoluteFeeSats),
|
|
926
1117
|
currentBlockFeeSpentSats: resolveSnapshotOverride(overrides.currentBlockFeeSpentSats, view.runtime.currentBlockFeeSpentSats),
|
|
927
1118
|
lastSuspendDetectedAtUnixMs: resolveSnapshotOverride(overrides.lastSuspendDetectedAtUnixMs, view.runtime.lastSuspendDetectedAtUnixMs),
|
|
1119
|
+
reconnectSettledUntilUnixMs: resolveSnapshotOverride(overrides.reconnectSettledUntilUnixMs, view.runtime.reconnectSettledUntilUnixMs),
|
|
1120
|
+
tipSettledUntilUnixMs: resolveSnapshotOverride(overrides.tipSettledUntilUnixMs, view.runtime.tipSettledUntilUnixMs),
|
|
928
1121
|
providerState: resolveSnapshotOverride(overrides.providerState, view.runtime.providerState),
|
|
929
1122
|
corePublishState: resolveSnapshotOverride(overrides.corePublishState, view.runtime.corePublishState),
|
|
930
1123
|
currentPublishDecision: resolveSnapshotOverride(overrides.currentPublishDecision, view.runtime.currentPublishDecision),
|
|
@@ -990,6 +1183,93 @@ async function refreshAndSaveStatus(options) {
|
|
|
990
1183
|
async function appendEvent(paths, event) {
|
|
991
1184
|
await appendMiningEvent(paths.miningEventsPath, event);
|
|
992
1185
|
}
|
|
1186
|
+
async function handleRecoverableMiningBitcoindFailure(options) {
|
|
1187
|
+
const failureMessage = describeRecoverableMiningBitcoindError(options.error);
|
|
1188
|
+
const walletRootId = options.readContext.localState.walletRootId ?? undefined;
|
|
1189
|
+
if (options.loopState.bitcoinRecoveryFirstFailureAtUnixMs === null) {
|
|
1190
|
+
options.loopState.bitcoinRecoveryFirstFailureAtUnixMs = options.nowUnixMs;
|
|
1191
|
+
}
|
|
1192
|
+
let restartedService = false;
|
|
1193
|
+
const probe = await options.probeService({
|
|
1194
|
+
dataDir: options.dataDir,
|
|
1195
|
+
chain: "main",
|
|
1196
|
+
startHeight: 0,
|
|
1197
|
+
walletRootId,
|
|
1198
|
+
}).catch((probeError) => {
|
|
1199
|
+
if (!isRecoverableMiningBitcoindError(probeError)) {
|
|
1200
|
+
throw probeError;
|
|
1201
|
+
}
|
|
1202
|
+
return null;
|
|
1203
|
+
});
|
|
1204
|
+
if (probe !== null) {
|
|
1205
|
+
if (probe.compatibility === "compatible") {
|
|
1206
|
+
rememberMiningBitcoindRecoveryIdentity(options.loopState, probe.status);
|
|
1207
|
+
options.loopState.bitcoinRecoveryFirstUnreachableAtUnixMs = null;
|
|
1208
|
+
}
|
|
1209
|
+
else if (probe.compatibility === "unreachable") {
|
|
1210
|
+
const identityChanged = rememberMiningBitcoindRecoveryIdentity(options.loopState, probe.status);
|
|
1211
|
+
const livePid = isMiningBitcoindRecoveryPidAlive(probe.status?.processId ?? null);
|
|
1212
|
+
if (identityChanged || options.loopState.bitcoinRecoveryFirstUnreachableAtUnixMs === null) {
|
|
1213
|
+
options.loopState.bitcoinRecoveryFirstUnreachableAtUnixMs = options.nowUnixMs;
|
|
1214
|
+
}
|
|
1215
|
+
if (!livePid) {
|
|
1216
|
+
restartedService = await attachManagedBitcoindForRecovery({
|
|
1217
|
+
dataDir: options.dataDir,
|
|
1218
|
+
walletRootId,
|
|
1219
|
+
attachService: options.attachService,
|
|
1220
|
+
loopState: options.loopState,
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
const graceElapsed = (options.loopState.bitcoinRecoveryFirstUnreachableAtUnixMs !== null
|
|
1225
|
+
&& options.nowUnixMs - options.loopState.bitcoinRecoveryFirstUnreachableAtUnixMs
|
|
1226
|
+
>= MINING_BITCOIN_RECOVERY_GRACE_MS);
|
|
1227
|
+
const cooldownElapsed = (options.loopState.bitcoinRecoveryLastRestartAttemptAtUnixMs === null
|
|
1228
|
+
|| options.nowUnixMs - options.loopState.bitcoinRecoveryLastRestartAttemptAtUnixMs
|
|
1229
|
+
>= MINING_BITCOIN_RECOVERY_RESTART_COOLDOWN_MS);
|
|
1230
|
+
if (graceElapsed && cooldownElapsed) {
|
|
1231
|
+
options.loopState.bitcoinRecoveryLastRestartAttemptAtUnixMs = options.nowUnixMs;
|
|
1232
|
+
await options.stopService({
|
|
1233
|
+
dataDir: options.dataDir,
|
|
1234
|
+
walletRootId,
|
|
1235
|
+
}).catch((stopError) => {
|
|
1236
|
+
if (!isRecoverableMiningBitcoindError(stopError)) {
|
|
1237
|
+
throw stopError;
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
await attachManagedBitcoindForRecovery({
|
|
1241
|
+
dataDir: options.dataDir,
|
|
1242
|
+
walletRootId,
|
|
1243
|
+
attachService: options.attachService,
|
|
1244
|
+
loopState: options.loopState,
|
|
1245
|
+
});
|
|
1246
|
+
restartedService = true;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
throw new Error(probe.error ?? "managed_bitcoind_protocol_error");
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (restartedService) {
|
|
1255
|
+
discardMiningLoopTransientWork(options.loopState, walletRootId);
|
|
1256
|
+
setMiningReconnectSettleWindow(options.loopState, options.nowUnixMs);
|
|
1257
|
+
}
|
|
1258
|
+
await refreshAndSaveStatus({
|
|
1259
|
+
paths: options.paths,
|
|
1260
|
+
provider: options.provider,
|
|
1261
|
+
readContext: options.readContext,
|
|
1262
|
+
overrides: {
|
|
1263
|
+
runMode: options.runMode,
|
|
1264
|
+
currentPhase: "waiting-bitcoin-network",
|
|
1265
|
+
lastError: failureMessage,
|
|
1266
|
+
note: MINING_BITCOIN_RECOVERY_NOTE,
|
|
1267
|
+
...buildMiningSettleWindowStatusOverrides(options.loopState, options.nowUnixMs),
|
|
1268
|
+
},
|
|
1269
|
+
visualizer: options.visualizer,
|
|
1270
|
+
visualizerState: options.loopState.ui,
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
993
1273
|
async function handleDetectedMiningRuntimeResume(options) {
|
|
994
1274
|
const readContext = await options.openReadContext({
|
|
995
1275
|
dataDir: options.dataDir,
|
|
@@ -999,6 +1279,7 @@ async function handleDetectedMiningRuntimeResume(options) {
|
|
|
999
1279
|
});
|
|
1000
1280
|
try {
|
|
1001
1281
|
clearMiningGateCache(readContext.localState.walletRootId);
|
|
1282
|
+
setMiningReconnectSettleWindow(options.loopState, options.detectedAtUnixMs);
|
|
1002
1283
|
await refreshAndSaveStatus({
|
|
1003
1284
|
paths: options.paths,
|
|
1004
1285
|
provider: options.provider,
|
|
@@ -1011,6 +1292,7 @@ async function handleDetectedMiningRuntimeResume(options) {
|
|
|
1011
1292
|
currentPhase: "resuming",
|
|
1012
1293
|
lastSuspendDetectedAtUnixMs: options.detectedAtUnixMs,
|
|
1013
1294
|
note: "Mining discarded stale in-flight work after a large local runtime gap and is rechecking health.",
|
|
1295
|
+
...buildMiningSettleWindowStatusOverrides(options.loopState, options.detectedAtUnixMs),
|
|
1014
1296
|
},
|
|
1015
1297
|
visualizer: options.visualizer,
|
|
1016
1298
|
visualizerState: createIndexedMiningFollowVisualizerState(readContext),
|
|
@@ -1077,7 +1359,7 @@ function determineCorePublishState(info) {
|
|
|
1077
1359
|
}
|
|
1078
1360
|
function createMiningPlan(options) {
|
|
1079
1361
|
const fundingUtxos = options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
|
|
1080
|
-
&& entry.confirmations >=
|
|
1362
|
+
&& entry.confirmations >= MINING_FUNDING_MIN_CONF
|
|
1081
1363
|
&& entry.spendable !== false
|
|
1082
1364
|
&& entry.safe !== false
|
|
1083
1365
|
&& !(options.conflictOutpoint !== null
|
|
@@ -1130,6 +1412,7 @@ async function buildMiningTransaction(options) {
|
|
|
1130
1412
|
finalizeErrorCode: "wallet_mining_finalize_failed",
|
|
1131
1413
|
mempoolRejectPrefix: "wallet_mining_mempool_rejected",
|
|
1132
1414
|
feeRate: options.plan.feeRateSatVb,
|
|
1415
|
+
availableFundingMinConf: MINING_FUNDING_MIN_CONF,
|
|
1133
1416
|
});
|
|
1134
1417
|
}
|
|
1135
1418
|
export function createMiningPlanForTesting(options) {
|
|
@@ -1206,11 +1489,36 @@ function createStaleMiningCandidateWaitingNote() {
|
|
|
1206
1489
|
function createRetryableMiningPublishWaitingNote() {
|
|
1207
1490
|
return "Selected mining candidate did not reach mempool and will be retried on the current tip with refreshed wallet state.";
|
|
1208
1491
|
}
|
|
1492
|
+
const MINING_FUNDING_MIN_CONF = 0;
|
|
1209
1493
|
function createInsufficientFundsMiningPublishWaitingNote() {
|
|
1210
|
-
return "Mining is waiting for enough
|
|
1494
|
+
return "Mining is waiting for enough safe BTC funding that Bitcoin Core can use for the next publish.";
|
|
1211
1495
|
}
|
|
1212
1496
|
function createInsufficientFundsMiningPublishErrorMessage() {
|
|
1213
|
-
return "Bitcoin Core could not fund the next mining publish with
|
|
1497
|
+
return "Bitcoin Core could not fund the next mining publish with safe BTC.";
|
|
1498
|
+
}
|
|
1499
|
+
function buildMiningGenerationRequest(options) {
|
|
1500
|
+
return {
|
|
1501
|
+
schemaVersion: 1,
|
|
1502
|
+
requestId: options.requestId ?? `mining-${options.targetBlockHeight}-${randomBytes(8).toString("hex")}`,
|
|
1503
|
+
targetBlockHeight: options.targetBlockHeight,
|
|
1504
|
+
referencedBlockHashDisplay: options.referencedBlockHashDisplay,
|
|
1505
|
+
generatedAtUnixMs: options.generatedAtUnixMs ?? Date.now(),
|
|
1506
|
+
extraPrompt: options.extraPrompt,
|
|
1507
|
+
limits: createMiningSentenceRequestLimits(),
|
|
1508
|
+
rootDomains: options.domains.map((domain) => ({
|
|
1509
|
+
domainId: domain.domainId,
|
|
1510
|
+
domainName: domain.domainName,
|
|
1511
|
+
requiredWords: domain.requiredWords,
|
|
1512
|
+
extraPrompt: options.domainExtraPrompts[domain.domainName.toLowerCase()] ?? null,
|
|
1513
|
+
})),
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
export function buildMiningGenerationRequestForTesting(options) {
|
|
1517
|
+
return buildMiningGenerationRequest({
|
|
1518
|
+
...options,
|
|
1519
|
+
domainExtraPrompts: options.domainExtraPrompts ?? {},
|
|
1520
|
+
extraPrompt: options.extraPrompt ?? null,
|
|
1521
|
+
});
|
|
1214
1522
|
}
|
|
1215
1523
|
async function generateCandidatesForDomains(options) {
|
|
1216
1524
|
const bestBlockHash = options.readContext.nodeStatus?.nodeBestHashHex;
|
|
@@ -1223,6 +1531,10 @@ async function generateCandidatesForDomains(options) {
|
|
|
1223
1531
|
...domain,
|
|
1224
1532
|
requiredWords: getWords(domain.domainId, referencedBlockHashInternal),
|
|
1225
1533
|
}));
|
|
1534
|
+
const clientConfig = await loadClientConfig({
|
|
1535
|
+
path: options.paths.clientConfigPath,
|
|
1536
|
+
provider: options.provider,
|
|
1537
|
+
}).catch(() => null);
|
|
1226
1538
|
const abortController = new AbortController();
|
|
1227
1539
|
let stale = false;
|
|
1228
1540
|
let staleIndexerTruth = false;
|
|
@@ -1261,20 +1573,13 @@ async function generateCandidatesForDomains(options) {
|
|
|
1261
1573
|
runId: options.runId ?? null,
|
|
1262
1574
|
pid: process.pid ?? null,
|
|
1263
1575
|
});
|
|
1264
|
-
const generationRequest = {
|
|
1265
|
-
schemaVersion: 1,
|
|
1266
|
-
requestId: `mining-${targetBlockHeight}-${randomBytes(8).toString("hex")}`,
|
|
1576
|
+
const generationRequest = buildMiningGenerationRequest({
|
|
1267
1577
|
targetBlockHeight,
|
|
1268
1578
|
referencedBlockHashDisplay: bestBlockHash,
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
domainId: domain.domainId,
|
|
1274
|
-
domainName: domain.domainName,
|
|
1275
|
-
requiredWords: domain.requiredWords,
|
|
1276
|
-
})),
|
|
1277
|
-
};
|
|
1579
|
+
domains: rootDomains,
|
|
1580
|
+
domainExtraPrompts: clientConfig?.mining.domainExtraPrompts ?? {},
|
|
1581
|
+
extraPrompt: clientConfig?.mining.builtIn?.extraPrompt ?? null,
|
|
1582
|
+
});
|
|
1278
1583
|
let generated;
|
|
1279
1584
|
try {
|
|
1280
1585
|
generated = await generateMiningSentences(generationRequest, {
|
|
@@ -1418,6 +1723,7 @@ function toSentenceBoardEntries(entries) {
|
|
|
1418
1723
|
rank: entry.rank,
|
|
1419
1724
|
domainName: entry.domainName,
|
|
1420
1725
|
sentence: entry.sentence,
|
|
1726
|
+
requiredWords: resolveBip39WordsFromIndices(entry.bip39WordIndices),
|
|
1421
1727
|
}));
|
|
1422
1728
|
}
|
|
1423
1729
|
async function runCompetitivenessGate(options) {
|
|
@@ -1839,7 +2145,6 @@ async function publishCandidateOnce(options) {
|
|
|
1839
2145
|
dataDir: options.dataDir,
|
|
1840
2146
|
chain: "main",
|
|
1841
2147
|
startHeight: 0,
|
|
1842
|
-
serviceLifetime: "ephemeral",
|
|
1843
2148
|
walletRootId: options.readContext.localState.state.walletRootId,
|
|
1844
2149
|
});
|
|
1845
2150
|
const rpc = options.rpcFactory(service.rpc);
|
|
@@ -1850,7 +2155,7 @@ async function publishCandidateOnce(options) {
|
|
|
1850
2155
|
nodeBestHeight: options.readContext.nodeStatus?.nodeBestHeight ?? null,
|
|
1851
2156
|
snapshotState: options.readContext.snapshot.state,
|
|
1852
2157
|
})).state;
|
|
1853
|
-
const allUtxos = await rpc.listUnspent(state.managedCoreWallet.walletName,
|
|
2158
|
+
const allUtxos = await rpc.listUnspent(state.managedCoreWallet.walletName, MINING_FUNDING_MIN_CONF);
|
|
1854
2159
|
const conflictOutpoint = resolveMiningConflictOutpoint({
|
|
1855
2160
|
state,
|
|
1856
2161
|
allUtxos,
|
|
@@ -2120,7 +2425,7 @@ async function publishCandidate(options) {
|
|
|
2120
2425
|
if (isInsufficientFundsError(error)) {
|
|
2121
2426
|
const note = createInsufficientFundsMiningPublishWaitingNote();
|
|
2122
2427
|
const lastError = createInsufficientFundsMiningPublishErrorMessage();
|
|
2123
|
-
await appendEventFn(options.paths, createEvent("publish-paused-insufficient-funds", "Paused mining publish because Bitcoin Core could not fund the next mining transaction with
|
|
2428
|
+
await appendEventFn(options.paths, createEvent("publish-paused-insufficient-funds", "Paused mining publish because Bitcoin Core could not fund the next mining transaction with safe BTC.", {
|
|
2124
2429
|
level: "warn",
|
|
2125
2430
|
runId: options.runId,
|
|
2126
2431
|
targetBlockHeight: refreshedCandidate.targetBlockHeight,
|
|
@@ -2169,6 +2474,7 @@ export async function ensureBuiltInMiningSetupIfNeeded(options) {
|
|
|
2169
2474
|
return true;
|
|
2170
2475
|
}
|
|
2171
2476
|
async function performMiningCycle(options) {
|
|
2477
|
+
const now = options.nowImpl ?? Date.now;
|
|
2172
2478
|
let readContext = await options.openReadContext({
|
|
2173
2479
|
dataDir: options.dataDir,
|
|
2174
2480
|
databasePath: options.databasePath,
|
|
@@ -2177,30 +2483,39 @@ async function performMiningCycle(options) {
|
|
|
2177
2483
|
});
|
|
2178
2484
|
let readContextClosed = false;
|
|
2179
2485
|
try {
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
},
|
|
2191
|
-
});
|
|
2192
|
-
if (readContext.localState.availability !== "ready" || readContext.localState.state === null) {
|
|
2193
|
-
await refreshAndSaveStatus({
|
|
2486
|
+
let clearRecoveredBitcoindError = false;
|
|
2487
|
+
const saveCycleStatus = async (readContext, overrides, includeVisualizer = true) => {
|
|
2488
|
+
const statusNowUnixMs = now();
|
|
2489
|
+
const resolvedOverrides = clearRecoveredBitcoindError && overrides.lastError === undefined
|
|
2490
|
+
? {
|
|
2491
|
+
...overrides,
|
|
2492
|
+
lastError: null,
|
|
2493
|
+
}
|
|
2494
|
+
: overrides;
|
|
2495
|
+
return await refreshAndSaveStatus({
|
|
2194
2496
|
paths: options.paths,
|
|
2195
2497
|
provider: options.provider,
|
|
2196
2498
|
readContext,
|
|
2197
2499
|
overrides: {
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
note: "Wallet state must be locally available for mining to continue.",
|
|
2500
|
+
...buildMiningSettleWindowStatusOverrides(options.loopState, statusNowUnixMs),
|
|
2501
|
+
...resolvedOverrides,
|
|
2201
2502
|
},
|
|
2202
|
-
visualizer: options.visualizer,
|
|
2203
|
-
visualizerState: options.loopState.ui,
|
|
2503
|
+
visualizer: includeVisualizer ? options.visualizer : undefined,
|
|
2504
|
+
visualizerState: includeVisualizer ? options.loopState.ui : undefined,
|
|
2505
|
+
});
|
|
2506
|
+
};
|
|
2507
|
+
checkpointMiningSuspendDetector(options.suspendDetector);
|
|
2508
|
+
await saveCycleStatus(readContext, {
|
|
2509
|
+
runMode: options.runMode,
|
|
2510
|
+
backgroundWorkerPid: options.backgroundWorkerPid,
|
|
2511
|
+
backgroundWorkerRunId: options.backgroundWorkerRunId,
|
|
2512
|
+
backgroundWorkerHeartbeatAtUnixMs: options.runMode === "background" ? now() : null,
|
|
2513
|
+
}, false);
|
|
2514
|
+
if (readContext.localState.availability !== "ready" || readContext.localState.state === null) {
|
|
2515
|
+
await saveCycleStatus(readContext, {
|
|
2516
|
+
runMode: options.runMode,
|
|
2517
|
+
currentPhase: "waiting",
|
|
2518
|
+
note: "Wallet state must be locally available for mining to continue.",
|
|
2204
2519
|
});
|
|
2205
2520
|
return;
|
|
2206
2521
|
}
|
|
@@ -2208,7 +2523,6 @@ async function performMiningCycle(options) {
|
|
|
2208
2523
|
dataDir: options.dataDir,
|
|
2209
2524
|
chain: "main",
|
|
2210
2525
|
startHeight: 0,
|
|
2211
|
-
serviceLifetime: "ephemeral",
|
|
2212
2526
|
walletRootId: readContext.localState.state.walletRootId,
|
|
2213
2527
|
});
|
|
2214
2528
|
checkpointMiningSuspendDetector(options.suspendDetector);
|
|
@@ -2251,28 +2565,25 @@ async function performMiningCycle(options) {
|
|
|
2251
2565
|
indexedTipHashHex: indexedTip?.blockHashHex ?? null,
|
|
2252
2566
|
}).catch(() => ({}));
|
|
2253
2567
|
syncMiningVisualizerBlockTimes(options.loopState, visibleBlockTimes);
|
|
2254
|
-
const { targetBlockHeight, tipKey } = syncMiningUiForCurrentTip({
|
|
2568
|
+
const { targetBlockHeight, tipKey, tipChanged } = syncMiningUiForCurrentTip({
|
|
2255
2569
|
loopState: options.loopState,
|
|
2256
2570
|
snapshotState: effectiveReadContext.snapshot?.state ?? null,
|
|
2257
2571
|
snapshotTipHeight: effectiveReadContext.snapshot?.tip?.height ?? effectiveReadContext.indexer.snapshotTip?.height ?? null,
|
|
2572
|
+
snapshotTipPreviousHashHex: effectiveReadContext.snapshot?.tip?.previousHashHex ?? effectiveReadContext.indexer.snapshotTip?.previousHashHex ?? null,
|
|
2258
2573
|
nodeBestHeight: effectiveReadContext.nodeStatus?.nodeBestHeight ?? null,
|
|
2259
2574
|
nodeBestHash: effectiveReadContext.nodeStatus?.nodeBestHashHex ?? null,
|
|
2260
2575
|
recentWin: reconciliation.recentWin,
|
|
2261
2576
|
});
|
|
2577
|
+
if (tipChanged) {
|
|
2578
|
+
setMiningTipSettleWindow(options.loopState, now());
|
|
2579
|
+
}
|
|
2262
2580
|
const displaySats = await resolveFundingDisplaySats(effectiveReadContext.localState.state, rpc).catch(() => null);
|
|
2263
2581
|
syncMiningVisualizerBalances(options.loopState, effectiveReadContext, displaySats);
|
|
2264
2582
|
if (effectiveReadContext.localState.state.miningState.state === "repair-required") {
|
|
2265
|
-
await
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
overrides: {
|
|
2270
|
-
runMode: options.runMode,
|
|
2271
|
-
currentPhase: "waiting",
|
|
2272
|
-
note: "Mining is blocked until the current mining publish is repaired or reconciled.",
|
|
2273
|
-
},
|
|
2274
|
-
visualizer: options.visualizer,
|
|
2275
|
-
visualizerState: options.loopState.ui,
|
|
2583
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2584
|
+
runMode: options.runMode,
|
|
2585
|
+
currentPhase: "waiting",
|
|
2586
|
+
note: "Mining is blocked until the current mining publish is repaired or reconciled.",
|
|
2276
2587
|
});
|
|
2277
2588
|
return;
|
|
2278
2589
|
}
|
|
@@ -2294,17 +2605,10 @@ async function performMiningCycle(options) {
|
|
|
2294
2605
|
state: nextState,
|
|
2295
2606
|
},
|
|
2296
2607
|
};
|
|
2297
|
-
await
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
overrides: {
|
|
2302
|
-
runMode: options.runMode,
|
|
2303
|
-
currentPhase: "waiting",
|
|
2304
|
-
note: "Mining is paused while another wallet mutation is active.",
|
|
2305
|
-
},
|
|
2306
|
-
visualizer: options.visualizer,
|
|
2307
|
-
visualizerState: options.loopState.ui,
|
|
2608
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2609
|
+
runMode: options.runMode,
|
|
2610
|
+
currentPhase: "waiting",
|
|
2611
|
+
note: "Mining is paused while another wallet mutation is active.",
|
|
2308
2612
|
});
|
|
2309
2613
|
return;
|
|
2310
2614
|
}
|
|
@@ -2322,23 +2626,16 @@ async function performMiningCycle(options) {
|
|
|
2322
2626
|
provider: options.provider,
|
|
2323
2627
|
paths: options.paths,
|
|
2324
2628
|
});
|
|
2325
|
-
await
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
localState: {
|
|
2331
|
-
...effectiveReadContext.localState,
|
|
2332
|
-
state: nextState,
|
|
2333
|
-
},
|
|
2334
|
-
},
|
|
2335
|
-
overrides: {
|
|
2336
|
-
runMode: options.runMode,
|
|
2337
|
-
currentPhase: "waiting",
|
|
2338
|
-
note: "Mining is paused while another wallet command is preempting sentence generation.",
|
|
2629
|
+
await saveCycleStatus({
|
|
2630
|
+
...effectiveReadContext,
|
|
2631
|
+
localState: {
|
|
2632
|
+
...effectiveReadContext.localState,
|
|
2633
|
+
state: nextState,
|
|
2339
2634
|
},
|
|
2340
|
-
|
|
2341
|
-
|
|
2635
|
+
}, {
|
|
2636
|
+
runMode: options.runMode,
|
|
2637
|
+
currentPhase: "waiting",
|
|
2638
|
+
note: "Mining is paused while another wallet command is preempting sentence generation.",
|
|
2342
2639
|
});
|
|
2343
2640
|
return;
|
|
2344
2641
|
}
|
|
@@ -2353,53 +2650,33 @@ async function performMiningCycle(options) {
|
|
|
2353
2650
|
network: networkInfo,
|
|
2354
2651
|
mempool: mempoolInfo,
|
|
2355
2652
|
});
|
|
2653
|
+
clearRecoveredBitcoindError = resetMiningBitcoindRecoveryState(options.loopState, effectiveReadContext.nodeStatus?.serviceStatus ?? { pid: service.pid });
|
|
2356
2654
|
if (corePublishState !== "healthy") {
|
|
2357
|
-
await
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
runMode: options.runMode,
|
|
2363
|
-
currentPhase: "waiting-bitcoin-network",
|
|
2364
|
-
corePublishState,
|
|
2365
|
-
note: "Mining is waiting for the local Bitcoin node to become publishable.",
|
|
2366
|
-
},
|
|
2367
|
-
visualizer: options.visualizer,
|
|
2368
|
-
visualizerState: options.loopState.ui,
|
|
2655
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2656
|
+
runMode: options.runMode,
|
|
2657
|
+
currentPhase: "waiting-bitcoin-network",
|
|
2658
|
+
corePublishState,
|
|
2659
|
+
note: "Mining is waiting for the local Bitcoin node to become publishable.",
|
|
2369
2660
|
});
|
|
2370
2661
|
return;
|
|
2371
2662
|
}
|
|
2372
2663
|
if (effectiveReadContext.indexer.health !== "synced" || effectiveReadContext.nodeHealth !== "synced") {
|
|
2373
|
-
await
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
: "waiting-bitcoin-network",
|
|
2382
|
-
note: effectiveReadContext.indexer.health !== "synced"
|
|
2383
|
-
? "Mining is waiting for Bitcoin Core and the indexer to align."
|
|
2384
|
-
: "Mining is waiting for the local Bitcoin node to become publishable.",
|
|
2385
|
-
},
|
|
2386
|
-
visualizer: options.visualizer,
|
|
2387
|
-
visualizerState: options.loopState.ui,
|
|
2664
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2665
|
+
runMode: options.runMode,
|
|
2666
|
+
currentPhase: effectiveReadContext.indexer.health !== "synced"
|
|
2667
|
+
? "waiting-indexer"
|
|
2668
|
+
: "waiting-bitcoin-network",
|
|
2669
|
+
note: effectiveReadContext.indexer.health !== "synced"
|
|
2670
|
+
? "Mining is waiting for Bitcoin Core and the indexer to align."
|
|
2671
|
+
: "Mining is waiting for the local Bitcoin node to become publishable.",
|
|
2388
2672
|
});
|
|
2389
2673
|
return;
|
|
2390
2674
|
}
|
|
2391
2675
|
if (targetBlockHeight === null) {
|
|
2392
|
-
await
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
overrides: {
|
|
2397
|
-
runMode: options.runMode,
|
|
2398
|
-
currentPhase: "waiting-bitcoin-network",
|
|
2399
|
-
note: "Mining is waiting for the local Bitcoin node to become publishable.",
|
|
2400
|
-
},
|
|
2401
|
-
visualizer: options.visualizer,
|
|
2402
|
-
visualizerState: options.loopState.ui,
|
|
2676
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2677
|
+
runMode: options.runMode,
|
|
2678
|
+
currentPhase: "waiting-bitcoin-network",
|
|
2679
|
+
note: "Mining is waiting for the local Bitcoin node to become publishable.",
|
|
2403
2680
|
});
|
|
2404
2681
|
return;
|
|
2405
2682
|
}
|
|
@@ -2413,24 +2690,17 @@ async function performMiningCycle(options) {
|
|
|
2413
2690
|
provider: options.provider,
|
|
2414
2691
|
paths: options.paths,
|
|
2415
2692
|
});
|
|
2416
|
-
await
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
localState: {
|
|
2422
|
-
...effectiveReadContext.localState,
|
|
2423
|
-
state: nextState,
|
|
2424
|
-
},
|
|
2425
|
-
},
|
|
2426
|
-
overrides: {
|
|
2427
|
-
runMode: options.runMode,
|
|
2428
|
-
currentPhase: "idle",
|
|
2429
|
-
currentPublishDecision: "publish-skipped-zero-reward",
|
|
2430
|
-
note: "Mining is disabled because the target block reward is zero.",
|
|
2693
|
+
await saveCycleStatus({
|
|
2694
|
+
...effectiveReadContext,
|
|
2695
|
+
localState: {
|
|
2696
|
+
...effectiveReadContext.localState,
|
|
2697
|
+
state: nextState,
|
|
2431
2698
|
},
|
|
2432
|
-
|
|
2433
|
-
|
|
2699
|
+
}, {
|
|
2700
|
+
runMode: options.runMode,
|
|
2701
|
+
currentPhase: "idle",
|
|
2702
|
+
currentPublishDecision: "publish-skipped-zero-reward",
|
|
2703
|
+
note: "Mining is disabled because the target block reward is zero.",
|
|
2434
2704
|
});
|
|
2435
2705
|
await appendEvent(options.paths, createEvent("publish-skipped-zero-reward", "Skipped mining because the target block reward is zero.", {
|
|
2436
2706
|
targetBlockHeight,
|
|
@@ -2440,17 +2710,10 @@ async function performMiningCycle(options) {
|
|
|
2440
2710
|
return;
|
|
2441
2711
|
}
|
|
2442
2712
|
if (tipKey !== null && options.loopState.attemptedTipKey === tipKey) {
|
|
2443
|
-
await
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
overrides: {
|
|
2448
|
-
runMode: options.runMode,
|
|
2449
|
-
currentPhase: "waiting",
|
|
2450
|
-
note: options.loopState.waitingNote ?? "Waiting for the next block after the last mining attempt on this tip.",
|
|
2451
|
-
},
|
|
2452
|
-
visualizer: options.visualizer,
|
|
2453
|
-
visualizerState: options.loopState.ui,
|
|
2713
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2714
|
+
runMode: options.runMode,
|
|
2715
|
+
currentPhase: "waiting",
|
|
2716
|
+
note: options.loopState.waitingNote ?? "Waiting for the next block after the last mining attempt on this tip.",
|
|
2454
2717
|
});
|
|
2455
2718
|
return;
|
|
2456
2719
|
}
|
|
@@ -2488,31 +2751,17 @@ async function performMiningCycle(options) {
|
|
|
2488
2751
|
if (selectedCandidate === null) {
|
|
2489
2752
|
const domains = resolveEligibleAnchoredRoots(effectiveReadContext);
|
|
2490
2753
|
if (domains.length === 0) {
|
|
2491
|
-
await
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
overrides: {
|
|
2496
|
-
runMode: options.runMode,
|
|
2497
|
-
currentPhase: "idle",
|
|
2498
|
-
note: "No locally controlled anchored root domains are currently eligible to mine.",
|
|
2499
|
-
},
|
|
2500
|
-
visualizer: options.visualizer,
|
|
2501
|
-
visualizerState: options.loopState.ui,
|
|
2754
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2755
|
+
runMode: options.runMode,
|
|
2756
|
+
currentPhase: "idle",
|
|
2757
|
+
note: "No locally controlled anchored root domains are currently eligible to mine.",
|
|
2502
2758
|
});
|
|
2503
2759
|
return;
|
|
2504
2760
|
}
|
|
2505
|
-
await
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
overrides: {
|
|
2510
|
-
runMode: options.runMode,
|
|
2511
|
-
currentPhase: "generating",
|
|
2512
|
-
note: "Generating mining sentences for eligible root domains.",
|
|
2513
|
-
},
|
|
2514
|
-
visualizer: options.visualizer,
|
|
2515
|
-
visualizerState: options.loopState.ui,
|
|
2761
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2762
|
+
runMode: options.runMode,
|
|
2763
|
+
currentPhase: "generating",
|
|
2764
|
+
note: "Generating mining sentences for eligible root domains.",
|
|
2516
2765
|
});
|
|
2517
2766
|
await appendEvent(options.paths, createEvent("sentence-generation-start", "Started mining sentence generation.", {
|
|
2518
2767
|
targetBlockHeight,
|
|
@@ -2539,19 +2788,12 @@ async function performMiningCycle(options) {
|
|
|
2539
2788
|
options.loopState.attemptedTipKey = tipKey;
|
|
2540
2789
|
options.loopState.waitingNote = "Mining is waiting for the sentence provider to recover.";
|
|
2541
2790
|
}
|
|
2542
|
-
await
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
currentPhase: "waiting-provider",
|
|
2549
|
-
providerState: error.providerState,
|
|
2550
|
-
lastError: error.message,
|
|
2551
|
-
note: "Mining is waiting for the sentence provider to recover.",
|
|
2552
|
-
},
|
|
2553
|
-
visualizer: options.visualizer,
|
|
2554
|
-
visualizerState: options.loopState.ui,
|
|
2791
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2792
|
+
runMode: options.runMode,
|
|
2793
|
+
currentPhase: "waiting-provider",
|
|
2794
|
+
providerState: error.providerState,
|
|
2795
|
+
lastError: error.message,
|
|
2796
|
+
note: "Mining is waiting for the sentence provider to recover.",
|
|
2555
2797
|
});
|
|
2556
2798
|
await appendEvent(options.paths, createEvent("publish-paused-provider", error.message, {
|
|
2557
2799
|
level: "warn",
|
|
@@ -2594,19 +2836,12 @@ async function performMiningCycle(options) {
|
|
|
2594
2836
|
options.loopState.attemptedTipKey = tipKey;
|
|
2595
2837
|
options.loopState.waitingNote = "Mining sentence generation failed for the current tip.";
|
|
2596
2838
|
}
|
|
2597
|
-
await
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
currentPhase: "waiting-provider",
|
|
2604
|
-
providerState: "unavailable",
|
|
2605
|
-
lastError: failureMessage,
|
|
2606
|
-
note: "Mining sentence generation failed for the current tip.",
|
|
2607
|
-
},
|
|
2608
|
-
visualizer: options.visualizer,
|
|
2609
|
-
visualizerState: options.loopState.ui,
|
|
2839
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2840
|
+
runMode: options.runMode,
|
|
2841
|
+
currentPhase: "waiting-provider",
|
|
2842
|
+
providerState: "unavailable",
|
|
2843
|
+
lastError: failureMessage,
|
|
2844
|
+
note: "Mining sentence generation failed for the current tip.",
|
|
2610
2845
|
});
|
|
2611
2846
|
await appendEvent(options.paths, createEvent("sentence-generation-failed", failureMessage, {
|
|
2612
2847
|
level: "error",
|
|
@@ -2616,17 +2851,10 @@ async function performMiningCycle(options) {
|
|
|
2616
2851
|
}));
|
|
2617
2852
|
return;
|
|
2618
2853
|
}
|
|
2619
|
-
await
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
overrides: {
|
|
2624
|
-
runMode: options.runMode,
|
|
2625
|
-
currentPhase: "scoring",
|
|
2626
|
-
note: "Scoring mining candidates for the current tip.",
|
|
2627
|
-
},
|
|
2628
|
-
visualizer: options.visualizer,
|
|
2629
|
-
visualizerState: options.loopState.ui,
|
|
2854
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2855
|
+
runMode: options.runMode,
|
|
2856
|
+
currentPhase: "scoring",
|
|
2857
|
+
note: "Scoring mining candidates for the current tip.",
|
|
2630
2858
|
});
|
|
2631
2859
|
const best = await chooseBestLocalCandidate(candidates);
|
|
2632
2860
|
if (best === null) {
|
|
@@ -2635,18 +2863,11 @@ async function performMiningCycle(options) {
|
|
|
2635
2863
|
options.loopState.waitingNote = "No publishable mining candidate passed scoring gates for the current tip.";
|
|
2636
2864
|
}
|
|
2637
2865
|
clearSelectedCandidate(options.loopState);
|
|
2638
|
-
await
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
runMode: options.runMode,
|
|
2644
|
-
currentPhase: "idle",
|
|
2645
|
-
currentPublishDecision: "publish-skipped-no-candidate",
|
|
2646
|
-
note: "No publishable mining candidate passed scoring gates for the current tip.",
|
|
2647
|
-
},
|
|
2648
|
-
visualizer: options.visualizer,
|
|
2649
|
-
visualizerState: options.loopState.ui,
|
|
2866
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2867
|
+
runMode: options.runMode,
|
|
2868
|
+
currentPhase: "idle",
|
|
2869
|
+
currentPublishDecision: "publish-skipped-no-candidate",
|
|
2870
|
+
note: "No publishable mining candidate passed scoring gates for the current tip.",
|
|
2650
2871
|
});
|
|
2651
2872
|
await appendEvent(options.paths, createEvent("publish-skipped-no-candidate", "No publishable mining candidate passed scoring gates.", {
|
|
2652
2873
|
targetBlockHeight,
|
|
@@ -2693,25 +2914,18 @@ async function performMiningCycle(options) {
|
|
|
2693
2914
|
: gate.decision === "suppressed-top5-mempool"
|
|
2694
2915
|
? `Best local sentence found, but ${gate.higherRankedCompetitorDomainCount} stronger competitor root domains are already in mempool.`
|
|
2695
2916
|
: "Mining skipped this tick because the mempool competitiveness gate could not be verified safely.";
|
|
2696
|
-
await
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
mempoolSequenceCacheStatus: gate.mempoolSequenceCacheStatus,
|
|
2709
|
-
lastMempoolSequence: gate.lastMempoolSequence,
|
|
2710
|
-
lastCompetitivenessGateAtUnixMs: Date.now(),
|
|
2711
|
-
note: options.loopState.waitingNote,
|
|
2712
|
-
},
|
|
2713
|
-
visualizer: options.visualizer,
|
|
2714
|
-
visualizerState: options.loopState.ui,
|
|
2917
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2918
|
+
runMode: options.runMode,
|
|
2919
|
+
currentPhase: "waiting",
|
|
2920
|
+
currentPublishDecision: gate.decision,
|
|
2921
|
+
sameDomainCompetitorSuppressed: gate.sameDomainCompetitorSuppressed,
|
|
2922
|
+
higherRankedCompetitorDomainCount: gate.higherRankedCompetitorDomainCount,
|
|
2923
|
+
dedupedCompetitorDomainCount: gate.dedupedCompetitorDomainCount,
|
|
2924
|
+
competitivenessGateIndeterminate: gate.competitivenessGateIndeterminate,
|
|
2925
|
+
mempoolSequenceCacheStatus: gate.mempoolSequenceCacheStatus,
|
|
2926
|
+
lastMempoolSequence: gate.lastMempoolSequence,
|
|
2927
|
+
lastCompetitivenessGateAtUnixMs: now(),
|
|
2928
|
+
note: options.loopState.waitingNote,
|
|
2715
2929
|
});
|
|
2716
2930
|
await appendEvent(options.paths, createEvent(gate.decision === "suppressed-same-domain-mempool"
|
|
2717
2931
|
? "publish-skipped-same-domain-mempool"
|
|
@@ -2740,19 +2954,12 @@ async function performMiningCycle(options) {
|
|
|
2740
2954
|
if (!await ensureCurrentIndexerTruthOrRestart()) {
|
|
2741
2955
|
return;
|
|
2742
2956
|
}
|
|
2743
|
-
await
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
...buildPrePublishStatusOverrides({
|
|
2750
|
-
state: effectiveReadContext.localState.state,
|
|
2751
|
-
candidate: selectedCandidate,
|
|
2752
|
-
}),
|
|
2753
|
-
},
|
|
2754
|
-
visualizer: options.visualizer,
|
|
2755
|
-
visualizerState: options.loopState.ui,
|
|
2957
|
+
await saveCycleStatus(effectiveReadContext, {
|
|
2958
|
+
runMode: options.runMode,
|
|
2959
|
+
...buildPrePublishStatusOverrides({
|
|
2960
|
+
state: effectiveReadContext.localState.state,
|
|
2961
|
+
candidate: selectedCandidate,
|
|
2962
|
+
}),
|
|
2756
2963
|
});
|
|
2757
2964
|
const publishLock = await acquireFileLock(options.paths.walletControlLockPath, {
|
|
2758
2965
|
purpose: "wallet-mine",
|
|
@@ -2783,32 +2990,25 @@ async function performMiningCycle(options) {
|
|
|
2783
2990
|
if (published.retryable === true) {
|
|
2784
2991
|
cacheSelectedCandidateForTip(options.loopState, tipKey, published.candidate);
|
|
2785
2992
|
options.loopState.waitingNote = published.note;
|
|
2786
|
-
await
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
localState: {
|
|
2792
|
-
...effectiveReadContext.localState,
|
|
2793
|
-
state: published.state,
|
|
2794
|
-
},
|
|
2795
|
-
},
|
|
2796
|
-
overrides: {
|
|
2797
|
-
runMode: options.runMode,
|
|
2798
|
-
currentPhase: "waiting",
|
|
2799
|
-
currentPublishDecision: published.decision,
|
|
2800
|
-
sameDomainCompetitorSuppressed: false,
|
|
2801
|
-
higherRankedCompetitorDomainCount: gateSnapshot.higherRankedCompetitorDomainCount,
|
|
2802
|
-
dedupedCompetitorDomainCount: gateSnapshot.dedupedCompetitorDomainCount,
|
|
2803
|
-
competitivenessGateIndeterminate: false,
|
|
2804
|
-
mempoolSequenceCacheStatus: gateSnapshot.mempoolSequenceCacheStatus,
|
|
2805
|
-
lastMempoolSequence: gateSnapshot.lastMempoolSequence,
|
|
2806
|
-
lastCompetitivenessGateAtUnixMs: Date.now(),
|
|
2807
|
-
note: published.note,
|
|
2808
|
-
livePublishInMempool: published.state.miningState.livePublishInMempool,
|
|
2993
|
+
await saveCycleStatus({
|
|
2994
|
+
...effectiveReadContext,
|
|
2995
|
+
localState: {
|
|
2996
|
+
...effectiveReadContext.localState,
|
|
2997
|
+
state: published.state,
|
|
2809
2998
|
},
|
|
2810
|
-
|
|
2811
|
-
|
|
2999
|
+
}, {
|
|
3000
|
+
runMode: options.runMode,
|
|
3001
|
+
currentPhase: "waiting",
|
|
3002
|
+
currentPublishDecision: published.decision,
|
|
3003
|
+
sameDomainCompetitorSuppressed: false,
|
|
3004
|
+
higherRankedCompetitorDomainCount: gateSnapshot.higherRankedCompetitorDomainCount,
|
|
3005
|
+
dedupedCompetitorDomainCount: gateSnapshot.dedupedCompetitorDomainCount,
|
|
3006
|
+
competitivenessGateIndeterminate: false,
|
|
3007
|
+
mempoolSequenceCacheStatus: gateSnapshot.mempoolSequenceCacheStatus,
|
|
3008
|
+
lastMempoolSequence: gateSnapshot.lastMempoolSequence,
|
|
3009
|
+
lastCompetitivenessGateAtUnixMs: now(),
|
|
3010
|
+
note: published.note,
|
|
3011
|
+
livePublishInMempool: published.state.miningState.livePublishInMempool,
|
|
2812
3012
|
});
|
|
2813
3013
|
return;
|
|
2814
3014
|
}
|
|
@@ -2819,33 +3019,26 @@ async function performMiningCycle(options) {
|
|
|
2819
3019
|
const lastError = published.decision === "publish-paused-insufficient-funds"
|
|
2820
3020
|
? published.lastError ?? createInsufficientFundsMiningPublishErrorMessage()
|
|
2821
3021
|
: undefined;
|
|
2822
|
-
await
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
localState: {
|
|
2828
|
-
...effectiveReadContext.localState,
|
|
2829
|
-
state: published.state,
|
|
2830
|
-
},
|
|
2831
|
-
},
|
|
2832
|
-
overrides: {
|
|
2833
|
-
runMode: options.runMode,
|
|
2834
|
-
currentPhase: "waiting",
|
|
2835
|
-
currentPublishDecision: published.decision,
|
|
2836
|
-
sameDomainCompetitorSuppressed: false,
|
|
2837
|
-
higherRankedCompetitorDomainCount: gateSnapshot.higherRankedCompetitorDomainCount,
|
|
2838
|
-
dedupedCompetitorDomainCount: gateSnapshot.dedupedCompetitorDomainCount,
|
|
2839
|
-
competitivenessGateIndeterminate: false,
|
|
2840
|
-
mempoolSequenceCacheStatus: gateSnapshot.mempoolSequenceCacheStatus,
|
|
2841
|
-
lastMempoolSequence: gateSnapshot.lastMempoolSequence,
|
|
2842
|
-
lastCompetitivenessGateAtUnixMs: Date.now(),
|
|
2843
|
-
lastError,
|
|
2844
|
-
note: published.note,
|
|
2845
|
-
livePublishInMempool: published.state.miningState.livePublishInMempool,
|
|
3022
|
+
await saveCycleStatus({
|
|
3023
|
+
...effectiveReadContext,
|
|
3024
|
+
localState: {
|
|
3025
|
+
...effectiveReadContext.localState,
|
|
3026
|
+
state: published.state,
|
|
2846
3027
|
},
|
|
2847
|
-
|
|
2848
|
-
|
|
3028
|
+
}, {
|
|
3029
|
+
runMode: options.runMode,
|
|
3030
|
+
currentPhase: "waiting",
|
|
3031
|
+
currentPublishDecision: published.decision,
|
|
3032
|
+
sameDomainCompetitorSuppressed: false,
|
|
3033
|
+
higherRankedCompetitorDomainCount: gateSnapshot.higherRankedCompetitorDomainCount,
|
|
3034
|
+
dedupedCompetitorDomainCount: gateSnapshot.dedupedCompetitorDomainCount,
|
|
3035
|
+
competitivenessGateIndeterminate: false,
|
|
3036
|
+
mempoolSequenceCacheStatus: gateSnapshot.mempoolSequenceCacheStatus,
|
|
3037
|
+
lastMempoolSequence: gateSnapshot.lastMempoolSequence,
|
|
3038
|
+
lastCompetitivenessGateAtUnixMs: now(),
|
|
3039
|
+
lastError,
|
|
3040
|
+
note: published.note,
|
|
3041
|
+
livePublishInMempool: published.state.miningState.livePublishInMempool,
|
|
2849
3042
|
});
|
|
2850
3043
|
return;
|
|
2851
3044
|
}
|
|
@@ -2861,32 +3054,25 @@ async function performMiningCycle(options) {
|
|
|
2861
3054
|
: `Mining candidate ${published.decision === "replaced"
|
|
2862
3055
|
? "replaced"
|
|
2863
3056
|
: "broadcast"} as ${published.txid}. Waiting for the next block.`;
|
|
2864
|
-
await
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
localState: {
|
|
2870
|
-
...effectiveReadContext.localState,
|
|
2871
|
-
state: published.state,
|
|
2872
|
-
},
|
|
2873
|
-
},
|
|
2874
|
-
overrides: {
|
|
2875
|
-
runMode: options.runMode,
|
|
2876
|
-
currentPhase: "waiting",
|
|
2877
|
-
currentPublishDecision: published.decision,
|
|
2878
|
-
sameDomainCompetitorSuppressed: false,
|
|
2879
|
-
higherRankedCompetitorDomainCount: gateSnapshot.higherRankedCompetitorDomainCount,
|
|
2880
|
-
dedupedCompetitorDomainCount: gateSnapshot.dedupedCompetitorDomainCount,
|
|
2881
|
-
competitivenessGateIndeterminate: false,
|
|
2882
|
-
mempoolSequenceCacheStatus: gateSnapshot.mempoolSequenceCacheStatus,
|
|
2883
|
-
lastMempoolSequence: gateSnapshot.lastMempoolSequence,
|
|
2884
|
-
lastCompetitivenessGateAtUnixMs: Date.now(),
|
|
2885
|
-
note: options.loopState.waitingNote,
|
|
2886
|
-
livePublishInMempool: published.state.miningState.livePublishInMempool,
|
|
3057
|
+
await saveCycleStatus({
|
|
3058
|
+
...effectiveReadContext,
|
|
3059
|
+
localState: {
|
|
3060
|
+
...effectiveReadContext.localState,
|
|
3061
|
+
state: published.state,
|
|
2887
3062
|
},
|
|
2888
|
-
|
|
2889
|
-
|
|
3063
|
+
}, {
|
|
3064
|
+
runMode: options.runMode,
|
|
3065
|
+
currentPhase: "waiting",
|
|
3066
|
+
currentPublishDecision: published.decision,
|
|
3067
|
+
sameDomainCompetitorSuppressed: false,
|
|
3068
|
+
higherRankedCompetitorDomainCount: gateSnapshot.higherRankedCompetitorDomainCount,
|
|
3069
|
+
dedupedCompetitorDomainCount: gateSnapshot.dedupedCompetitorDomainCount,
|
|
3070
|
+
competitivenessGateIndeterminate: false,
|
|
3071
|
+
mempoolSequenceCacheStatus: gateSnapshot.mempoolSequenceCacheStatus,
|
|
3072
|
+
lastMempoolSequence: gateSnapshot.lastMempoolSequence,
|
|
3073
|
+
lastCompetitivenessGateAtUnixMs: now(),
|
|
3074
|
+
note: options.loopState.waitingNote,
|
|
3075
|
+
livePublishInMempool: published.state.miningState.livePublishInMempool,
|
|
2890
3076
|
});
|
|
2891
3077
|
}
|
|
2892
3078
|
finally {
|
|
@@ -2895,6 +3081,7 @@ async function performMiningCycle(options) {
|
|
|
2895
3081
|
}
|
|
2896
3082
|
catch (error) {
|
|
2897
3083
|
if (error instanceof MiningSuspendDetectedError) {
|
|
3084
|
+
discardMiningLoopTransientWork(options.loopState, readContext?.localState.walletRootId ?? undefined);
|
|
2898
3085
|
if (readContext !== null && !readContextClosed) {
|
|
2899
3086
|
await readContext.close();
|
|
2900
3087
|
readContextClosed = true;
|
|
@@ -2910,6 +3097,24 @@ async function performMiningCycle(options) {
|
|
|
2910
3097
|
detectedAtUnixMs: error.detectedAtUnixMs,
|
|
2911
3098
|
openReadContext: options.openReadContext,
|
|
2912
3099
|
visualizer: options.visualizer,
|
|
3100
|
+
loopState: options.loopState,
|
|
3101
|
+
});
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
if (readContext !== null && isRecoverableMiningBitcoindError(error)) {
|
|
3105
|
+
await handleRecoverableMiningBitcoindFailure({
|
|
3106
|
+
error,
|
|
3107
|
+
dataDir: options.dataDir,
|
|
3108
|
+
provider: options.provider,
|
|
3109
|
+
paths: options.paths,
|
|
3110
|
+
runMode: options.runMode,
|
|
3111
|
+
readContext,
|
|
3112
|
+
loopState: options.loopState,
|
|
3113
|
+
attachService: options.attachService,
|
|
3114
|
+
probeService: options.probeService,
|
|
3115
|
+
stopService: options.stopService,
|
|
3116
|
+
nowUnixMs: now(),
|
|
3117
|
+
visualizer: options.visualizer,
|
|
2913
3118
|
});
|
|
2914
3119
|
return;
|
|
2915
3120
|
}
|
|
@@ -2935,7 +3140,6 @@ async function saveStopSnapshot(options) {
|
|
|
2935
3140
|
dataDir: options.dataDir,
|
|
2936
3141
|
chain: "main",
|
|
2937
3142
|
startHeight: 0,
|
|
2938
|
-
serviceLifetime: "ephemeral",
|
|
2939
3143
|
walletRootId: localState.state.walletRootId,
|
|
2940
3144
|
}).catch(() => null);
|
|
2941
3145
|
if (service !== null) {
|
|
@@ -3012,6 +3216,9 @@ async function attemptSaveMempool(rpc, paths, runId) {
|
|
|
3012
3216
|
async function runMiningLoop(options) {
|
|
3013
3217
|
const suspendDetector = createMiningSuspendDetector();
|
|
3014
3218
|
const loopState = createMiningLoopState();
|
|
3219
|
+
const probeService = options.probeService ?? probeManagedBitcoindService;
|
|
3220
|
+
const stopService = options.stopService ?? stopManagedBitcoindService;
|
|
3221
|
+
const sleepImpl = options.sleepImpl ?? sleep;
|
|
3015
3222
|
await appendEvent(options.paths, createEvent("runtime-start", `Started ${options.runMode} mining runtime.`, {
|
|
3016
3223
|
runId: options.backgroundWorkerRunId,
|
|
3017
3224
|
}));
|
|
@@ -3023,6 +3230,7 @@ async function runMiningLoop(options) {
|
|
|
3023
3230
|
if (!(error instanceof MiningSuspendDetectedError)) {
|
|
3024
3231
|
throw error;
|
|
3025
3232
|
}
|
|
3233
|
+
discardMiningLoopTransientWork(loopState, null);
|
|
3026
3234
|
await handleDetectedMiningRuntimeResume({
|
|
3027
3235
|
dataDir: options.dataDir,
|
|
3028
3236
|
databasePath: options.databasePath,
|
|
@@ -3034,6 +3242,7 @@ async function runMiningLoop(options) {
|
|
|
3034
3242
|
detectedAtUnixMs: error.detectedAtUnixMs,
|
|
3035
3243
|
openReadContext: options.openReadContext,
|
|
3036
3244
|
visualizer: options.visualizer,
|
|
3245
|
+
loopState,
|
|
3037
3246
|
});
|
|
3038
3247
|
continue;
|
|
3039
3248
|
}
|
|
@@ -3041,14 +3250,15 @@ async function runMiningLoop(options) {
|
|
|
3041
3250
|
...options,
|
|
3042
3251
|
suspendDetector,
|
|
3043
3252
|
loopState,
|
|
3253
|
+
probeService,
|
|
3254
|
+
stopService,
|
|
3044
3255
|
});
|
|
3045
|
-
await
|
|
3256
|
+
await sleepImpl(Math.min(MINING_LOOP_INTERVAL_MS, MINING_STATUS_HEARTBEAT_INTERVAL_MS), options.signal);
|
|
3046
3257
|
}
|
|
3047
3258
|
const service = await options.attachService({
|
|
3048
3259
|
dataDir: options.dataDir,
|
|
3049
3260
|
chain: "main",
|
|
3050
3261
|
startHeight: 0,
|
|
3051
|
-
serviceLifetime: "ephemeral",
|
|
3052
3262
|
walletRootId: undefined,
|
|
3053
3263
|
}).catch(() => null);
|
|
3054
3264
|
if (service !== null) {
|
|
@@ -3117,6 +3327,8 @@ export async function runForegroundMining(options) {
|
|
|
3117
3327
|
sleepImpl: options.sleepImpl,
|
|
3118
3328
|
});
|
|
3119
3329
|
visualizer = new MiningFollowVisualizer({
|
|
3330
|
+
clientVersion: options.clientVersion,
|
|
3331
|
+
updateAvailable: options.updateAvailable,
|
|
3120
3332
|
progressOutput: options.progressOutput ?? "auto",
|
|
3121
3333
|
stream: options.stderr,
|
|
3122
3334
|
});
|
|
@@ -3346,7 +3558,10 @@ export async function runBackgroundMiningWorker(options) {
|
|
|
3346
3558
|
});
|
|
3347
3559
|
}
|
|
3348
3560
|
export async function handleDetectedMiningRuntimeResumeForTesting(options) {
|
|
3349
|
-
await handleDetectedMiningRuntimeResume(
|
|
3561
|
+
await handleDetectedMiningRuntimeResume({
|
|
3562
|
+
...options,
|
|
3563
|
+
loopState: options.loopState ?? createMiningLoopState(),
|
|
3564
|
+
});
|
|
3350
3565
|
}
|
|
3351
3566
|
export async function takeOverMiningRuntimeForTesting(options) {
|
|
3352
3567
|
return await takeOverMiningRuntime(options);
|
|
@@ -3354,9 +3569,16 @@ export async function takeOverMiningRuntimeForTesting(options) {
|
|
|
3354
3569
|
export async function performMiningCycleForTesting(options) {
|
|
3355
3570
|
await performMiningCycle({
|
|
3356
3571
|
...options,
|
|
3572
|
+
probeService: options.probeService ?? probeManagedBitcoindService,
|
|
3573
|
+
stopService: options.stopService ?? stopManagedBitcoindService,
|
|
3357
3574
|
loopState: options.loopState ?? createMiningLoopState(),
|
|
3358
3575
|
});
|
|
3359
3576
|
}
|
|
3577
|
+
export async function runMiningLoopForTesting(options) {
|
|
3578
|
+
await runMiningLoop({
|
|
3579
|
+
...options,
|
|
3580
|
+
});
|
|
3581
|
+
}
|
|
3360
3582
|
export function buildPrePublishStatusOverridesForTesting(options) {
|
|
3361
3583
|
return buildPrePublishStatusOverrides(options);
|
|
3362
3584
|
}
|