diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3fd64ac --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Published installers and their checksum files must be byte-identical on +# every checkout. A Windows clone with core.autocrlf=true would otherwise turn +# LF into CRLF, break the published SHA-256, and fail the byte-exact tests. +static/scripts/*.sh text eol=lf +static/scripts/*.ps1 text eol=lf +static/scripts/*.sha256 text eol=lf +static/mining-compatibility.json text eol=lf diff --git a/CLAUDE.md b/CLAUDE.md index 3872272..051c5ce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,19 @@ # Quantus Docs +## Agent setup entry point (2026-09-05) + +`static/agent-setup/prompt.md` adds the one-line mining handoff to the existing +`codex/one-command-mining-9-9` branch / PR #14. The mining guide links it before +manual prerequisites. It reuses the existing manifest and installers, pauses +before secret input, and distinguishes syncing from mining. Do not claim the +endpoint is live until this PR is deployed. No wallet input or mining runtime +was exercised by this documentation change. + +Validation: TypeScript, all 23 tests via `bun test`, and the production build +passed. The five-process PowerShell test now has an explicit 20-second timeout; +assertions are unchanged. The build emits existing Browserslist age and UMD +dependency warnings. The new production URL still returns 404 before deployment. + Technical documentation site for Quantus Network. Built with Docusaurus, deployed to docs.quantus.com. ## Build diff --git a/bun.lock b/bun.lock index 83f8645..4d75164 100644 --- a/bun.lock +++ b/bun.lock @@ -22,6 +22,7 @@ "@docusaurus/module-type-aliases": "^3.10.2", "@docusaurus/tsconfig": "^3.10.2", "@docusaurus/types": "^3.10.2", + "@types/bun": "^1.4.0", "typescript": "~5.6.2", }, }, @@ -742,6 +743,8 @@ "@types/bonjour": ["@types/bonjour@3.5.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ=="], + "@types/bun": ["@types/bun@1.4.0", "", { "dependencies": { "bun-types": "1.4.0" } }, "sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ=="], + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], "@types/connect-history-api-fallback": ["@types/connect-history-api-fallback@1.5.4", "", { "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw=="], @@ -1016,6 +1019,8 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="], + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], "bytes": ["bytes@3.0.0", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="], diff --git a/docs/guides/miner-app.md b/docs/guides/miner-app.md index 593c330..95784aa 100644 --- a/docs/guides/miner-app.md +++ b/docs/guides/miner-app.md @@ -1,55 +1,71 @@ --- sidebar_position: 2 -title: Miner App (GUI) -draft: true +title: Miner App Preview --- -# Miner App +# Miner App Preview -The Quantus Miner App is the easiest way to start mining. It provides a graphical interface for both CPU and GPU mining -- no terminal required. +The Quantus Miner App provides one desktop interface for wallet setup, node sync, CPU or GPU mining, hash rate, and rewards. Release `miner-v0.6.1` is a **preview**, not the verified beginner path. -## Download +The app downloads and starts node and miner binaries at runtime. It does not bundle them, pin the shared compatibility manifest, or verify their release checksums in this release. Use a dedicated Planck testnet wallet and do not use the app for assets with monetary value. -Download the latest Miner App for your platform from [GitHub Releases](https://github.com/Quantus-Network/quantus-apps/releases/latest): +## Direct downloads -| Platform | File | -|----------|------| -| macOS (Intel / Apple Silicon) | `quantus_miner_macos.zip` | -| Linux (x64) | `quantus_miner_linux.tar.gz` | -| Windows (x64) | `quantus_miner_windows.zip` | +| Platform | Download | SHA-256 | +| --- | --- | --- | +| Windows x64 | [quantus_miner_windows.zip](https://github.com/Quantus-Network/quantus-apps/releases/download/miner-v0.6.1/quantus_miner_windows.zip) | `081308152ba6f9eba62b9ac143f8c9e115ffab400a978a9d03a996e55ece8ad1` | +| macOS | [quantus_miner_macos.zip](https://github.com/Quantus-Network/quantus-apps/releases/download/miner-v0.6.1/quantus_miner_macos.zip) | `fb4769d4c1e8ccd3c480e0caeca07c9baa2b3ef0540828894dddeea3d4ed016e` | +| Linux x64 | [quantus_miner_linux.tar.gz](https://github.com/Quantus-Network/quantus-apps/releases/download/miner-v0.6.1/quantus_miner_linux.tar.gz) | `fd2755f2fe5df05af3deb8e1081fa808f6c872bd9d4bf2853eea0fe354655076` | -## Prerequisites +These URLs and hashes are also recorded in the [compatibility manifest](/mining-compatibility.json). -Before you start mining, you need: +Verify the downloaded archive before opening it. -1. A Quantus wallet address to receive rewards. Download the Quantus mobile wallet from [quantus.com/wallet](https://www.quantus.com/wallet/) and create an account. +### Windows PowerShell -## Installation +```powershell +(Get-FileHash .\quantus_miner_windows.zip -Algorithm SHA256).Hash.ToLower() +# Expected: 081308152ba6f9eba62b9ac143f8c9e115ffab400a978a9d03a996e55ece8ad1 +``` -**macOS:** Download the `.zip` file, extract it into your desired directory, and run `Quantus Miner.app`. If macOS blocks the app, go to System Settings > Privacy & Security and click "Open Anyway." +### macOS Terminal -**Linux:** Download the `.tar.gz` file, extract it into your desired directory, and run the `quantus_miner` executable. +```bash +shasum -a 256 quantus_miner_macos.zip +# Expected: fb4769d4c1e8ccd3c480e0caeca07c9baa2b3ef0540828894dddeea3d4ed016e +``` -## Note on Syncing -Once you input your inner hash (or seed phrase) you can begin syncing your node. +### Linux Terminal -Wait until the node is fully synced, and then begin mining. +```bash +sha256sum quantus_miner_linux.tar.gz +# Expected: fd2755f2fe5df05af3deb8e1081fa808f6c872bd9d4bf2853eea0fe354655076 +``` -Depending on your internet speed it may take 5-30 minutes for the node to full sync. +## Install -## Monitoring +### Windows -Check your mining progress on the [telemetry dashboard](https://telemetry.quantus.cat/). +1. Download and extract `quantus_miner_windows.zip`. +2. Open the extracted folder and run `quantus_miner.exe`. +3. If Windows Defender warns, verify the SHA-256 above before choosing to run it. -## Troubleshooting +### macOS -| Problem | Solution | -|---------|----------| -| macOS blocks the app | System Settings > Privacy & Security > "Open Anyway" | -| App won't connect | Make sure your Quantus node is running first | -| Low hash rate | Increase CPU workers or enable GPU mining in settings | -| Mining not producing blocks | Verify your node is fully synced before mining | +1. Download and extract `quantus_miner_macos.zip`. +2. Open `Quantus Miner.app`. +3. If macOS blocks it, open System Settings, Privacy & Security, then choose Open Anyway. -## Next Steps +### Linux x64 -For advanced mining configuration (external miner, build from source), see the full [Mining and Running a Node](./mining) guide. +1. Download and extract `quantus_miner_linux.tar.gz`. +2. Mark `quantus_miner` executable with `chmod u+x quantus_miner`. +3. Run `./quantus_miner`. + +The release does not publish minimum OS versions. The release team must supply those facts before the app can be presented as universally supported. + +## What the app handles + +The app downloads the node and external miner, creates the local node identity, starts both processes, monitors sync, and shows mining statistics. A recovery phrase entered into the app is handled locally by its wallet flow. + +The app is ready to become the primary route after it consumes the shared compatibility manifest, verifies every runtime download, and avoids exposing reward material in logs or process arguments. Until then, use the [verified terminal guide](/guides/mining) where supported. diff --git a/docs/guides/mining.md b/docs/guides/mining.md index 67f110e..1e19ad3 100644 --- a/docs/guides/mining.md +++ b/docs/guides/mining.md @@ -1,284 +1,134 @@ --- sidebar_position: 1 -title: Mining and Running a Node +title: Start Mining --- -# Mining and Running a Node +# Start Mining -This guide covers connecting to the Quantus Planck testnet and mining. Works on macOS and Linux (including WSL2 on Windows). +This guide connects a computer to the **Planck testnet** and starts the supported Quantus node and miner pair. Planck tokens have no monetary value and the network may reset. -Use the **Copy Context** button at the top of this page to copy everything as Markdown -- the full guide plus an AI mining skill. Paste it to an agent like Claude Code to be walked through setup interactively, or keep it as an offline reference. +## Set up with an agent -## Prerequisites +Paste this into your coding agent: -Before starting, you will need: - -1. **Quantus wallet.** Download the [Quantus wallet](https://linktr.ee/quantusnetwork) to hold funds, send transactions, and spend your mining rewards. - -2. **A mnemonic / seed phrase.** You can create one in the wallet app or in the CLI. - -3. **A wormhole address for rewards.** The chain only accumulates mining rewards to **wormhole addresses** -- not regular wallet addresses. The wallet app supports wormhole addresses and encrypted accounts, so rewards mined to your wormhole address show up in the app and can be spent directly from your wormhole balance -- no separate claiming step. You'll generate your wormhole address in the setup below. - -## Understanding Wormhole Addresses - -Mining rewards are sent to a **wormhole address** derived from a 32-byte preimage you generate during setup (aka your inner hash). - -This is privacy-preserving by default. All mining rewards are paid to wormhole addresses. - -Wormhole addresses look identical to regular transparent addresses, but they have a separate derivation path. - -**If you already have an existing wallet:** You can derive a wormhole keypair from an existing mnemonic or seed instead of generating a fresh one. This is the recommended approach if you are mining for the first time -- use the same seed phrase as your wallet app, and your mining rewards will appear in the app automatically, spendable straight from your wormhole balance. - -During setup you will run `key quantus --scheme wormhole`, which outputs three values: - -| Value | What it is | What to do | -|-------|-----------|------------| -| **Address** | Your wormhole address (where rewards are sent) | Note for monitoring | -| **Inner Hash** | 32-byte preimage | Pass to the node via `--rewards-inner-hash` | -| **Secret phrase** | The mnemonic that proves ownership | Back up securely -- this recovers everything | - -The node derives your wormhole address from the `inner_hash` and logs it on startup. - -The most important thing to back up is your 24 word phrase. - -You should keep both your 24 word phrase and your secret secure and do not share either with anyone. - ---- - -## Automated Setup - -For a guided terminal workflow, use the mining setup script (macOS, Linux, or WSL2): - -```bash -curl -fsSL https://docs.quantus.com/scripts/quantus-mining.sh -o quantus-mining.sh -chmod +x quantus-mining.sh -./quantus-mining.sh setup -./quantus-mining.sh start +```text +Fetch and follow https://docs.quantus.com/agent-setup/prompt.md to set up Quantus mining on this computer. ``` -The script generates your wormhole inner hash, node identity, and a config file at `~/quantus-mining/mining.conf`. It downloads native `quantus-node` and `quantus-miner` binaries into `~/quantus-mining/bin/`. GPU mining is recommended; the miner runs on the host so it can use Metal / Vulkan / DirectX. - -The script checks the downloaded pair's `--help` output and only passes auth/TLS flags when **both** binaries support miner QUIC auth (`quantus-miner/2`). Mixing an auth-capable node with a pre-auth miner (or the reverse) is rejected. Pin a matching pair with `NODE_VERSION` / `MINER_VERSION` in `mining.conf` (or `./quantus-mining.sh config set NODE_VERSION `), then run `./quantus-mining.sh setup --force` to download those tags. Environment variables of the same name override the file. Unset pins fetch GitHub `releases/latest` independently and may not match. +The agent handles setup and checks. Enter wallet recovery words only in your private local terminal, never in chat. Prefer manual setup? Continue below. -**Linux ARM64:** there is no native `quantus-miner` release. Mine from macOS or Linux x86_64. +## Before you start -If an older installer created a Docker stack under `~/quantus-mining/docker/`, `./quantus-mining.sh stop` and `uninstall` still shut it down. New Docker setup is not supported (`setup --mode docker` is rejected). After stopping, migrate with `./quantus-mining.sh setup --force`. +You need a Quantus wallet with its 24-word recovery phrase. Keep that phrase offline. Never paste it into chat, email, a support ticket, or a command. -### Running the stack +There are two verified installers, one per shell, and they behave identically: `quantus-mining.sh` for macOS, Linux x64, and WSL2, and `quantus-mining.ps1` for native Windows 10/11 x64. Both read the same [compatibility manifest](/mining-compatibility.json), verify the same checksums, ask for the recovery phrase the same way, and print the same status. On Windows, use PowerShell rather than WSL2: the miner needs the native graphics driver to use the GPU. The [desktop Miner App](/guides/miner-app) remains a preview and is not the verified path. -**One terminal:** `./quantus-mining.sh start` +## Three steps -Node output in your terminal (foreground); miner runs in the background (`~/quantus-mining/logs/miner.log`). Ctrl+C stops both. +### 1. Open a shell -**Two terminals (matches the manual steps below):** run `./quantus-mining.sh start-node` in one terminal, then `./quantus-mining.sh start-miner` in another after the node is listening. +On macOS or Linux, open Terminal. On Windows, press the Windows key, type `PowerShell`, and press Enter. No WSL is needed. -Add `-d` or `--detach` to run both in the background. Stop with `./quantus-mining.sh stop` from any terminal (works for foreground and detached runs). +### 2. Run the verified installer -Manage settings with `./quantus-mining.sh config show` or `./quantus-mining.sh config set CPU_WORKERS 4`. Editable keys: `NODE_NAME`, `CPU_WORKERS`, `GPU_DEVICES`, `MINER_LISTEN_PORT`, `CHAIN`, `NODE_VERSION`, `MINER_VERSION`. Changing version pins does not swap binaries until you re-run `setup --force`. +This downloads the script to disk, verifies its SHA-256 checksum, and only then runs it. It does not pipe remote code into a shell. -GPU mining is recommended when available. Mining rewards accumulate at your wormhole address and appear in the wallet app, ready to spend. - -The script wires miner authentication when the downloaded pair supports it: after the node starts it reads `miner-auth-token` and `miner-tls-cert-sha256` from the node's chain directory and passes them to the miner. You do not need to copy those values by hand. If the pair predates miner auth, the script starts without those flags. - -Example config template: [mining.conf.example](/scripts/mining.conf.example). - -## Manual Installation (Mac / Linux) - -### 1. Download the Node Binary - -Get a `quantus-node` binary that matches your miner from [GitHub Releases](https://github.com/Quantus-Network/chain/releases). Do not mix `chain/releases/latest` with an unrelated `quantus-miner` latest tag — they are published independently and may not speak the same miner protocol. - -Download it in your working directory. - -Extract it into your working directory (on macOS, double-clicking the archive works). - -Note: `aarch64-apple` builds are for Apple Silicon Macs (M1 and above); `x86-apple` is for Intel-based Macs. - -Now open your terminal to generate your node key and inner hash, and run the node in this terminal window. - -**macOS only -- fix Gatekeeper permissions:** +macOS, Linux, or WSL2: ```bash -xattr -d com.apple.quarantine quantus-node -chmod u+x quantus-node +curl --proto '=https' --tlsv1.2 -fsS https://docs.quantus.com/scripts/quantus-mining.sh -o quantus-mining.sh && \ +curl --proto '=https' --tlsv1.2 -fsS https://docs.quantus.com/scripts/quantus-mining.sh.sha256 -o quantus-mining.sh.sha256 && \ +{ if command -v sha256sum >/dev/null 2>&1; then sha256sum -c quantus-mining.sh.sha256; else shasum -a 256 -c quantus-mining.sh.sha256; fi; } && \ +chmod u+x quantus-mining.sh && ./quantus-mining.sh mine ``` -### 2. Generate Node Identity +Windows PowerShell: -```bash -./quantus-node key generate-node-key --file node_key.p2p +```powershell +[Net.ServicePointManager]::SecurityProtocol = 'Tls12' +Invoke-WebRequest -UseBasicParsing https://docs.quantus.com/scripts/quantus-mining.ps1 -OutFile quantus-mining.ps1 +Invoke-WebRequest -UseBasicParsing https://docs.quantus.com/scripts/quantus-mining.ps1.sha256 -OutFile quantus-mining.ps1.sha256 +if ((Get-FileHash quantus-mining.ps1 -Algorithm SHA256).Hash.ToLower() -ne (Get-Content quantus-mining.ps1.sha256).Split(' ')[0]) { throw 'Checksum mismatch. Delete both files and retry.' } +Unblock-File quantus-mining.ps1 +powershell -ExecutionPolicy Bypass -File .\quantus-mining.ps1 mine ``` -### 3. Generate Inner Hash -Have the 24 word secret phrase from your wallet app ready. +The installer selects the published pair from the [compatibility manifest](/mining-compatibility.json): node `v0.10.0`, miner `v4.0.2`, and protocol `quantus-miner/2`. It verifies every release asset before installation. There are no version or network choices. -**Note: Keep this secret phrase secure and do not share it with anyone. It is used to access your rewards, move funds, and derive any information you need in the future.** +### 3. Enter your recovery phrase locally -Run the command below. It prompts for your 24 words and reads them **without echoing** -- the phrase is never passed on the command line, so it stays out of your shell history. +The prompt is hidden. The phrase is used locally to derive your wormhole reward address, then discarded. It is not saved, logged, sent over the network, or placed in command history. -Save the `Inner Hash` from the output: +The installer names the node, detects a conservative CPU or GPU configuration, starts both processes, and prints status. Initial chain sync downloads and executes every block, which is the only sync mode this network supports, and takes one to a few hours depending on the connection. It does not count as hands-on setup time, and the miner starts working the moment the node reports it is synced. -```sh -./quantus-node key quantus --scheme wormhole --words -``` +## Know when it works -Alternatively, to generate a fresh wallet separate from the one in your wallet app: +Run: ```bash -./quantus-node key quantus --scheme wormhole +./quantus-mining.sh status ``` -Copy the words from the output -- that is your secret phrase. - -**Note: Save this secret phrase securely and do not share with anyone. It is used to access your rewards, move funds, and derive any information you need in the future.** - - -### 4. Start the Node +On Windows, every command is the same with `.\quantus-mining.ps1` in place of `./quantus-mining.sh`. While syncing, the Windows status line also shows the current block, the target, the rate, and the time left. -Replace the two placeholders before running: +A complete success state shows: -- `` -- any name you like (this is how your node appears on [telemetry](https://telemetry.quantus.cat/)) -- `` -- the `inner_hash` value from step 3 - -`node_key.p2p` is the file generated in step 2. - -```bash -./quantus-node \ - --name \ - --validator \ - --miner-listen-port 9833 \ - --chain planck \ - --node-key-file node_key.p2p \ - --rewards-inner-hash \ - --max-blocks-per-request 64 \ - --sync full -``` -#### Note on Syncing -Once you begin syncing your node, wait until the node is fully synced before you begin mining. Blocks mined before your node reaches the chain tip are orphans and earn nothing (the miner pauses automatically if your node has no peers). - -Sync time grows with the chain: expect anywhere from ~15 minutes to a couple of hours depending on your hardware and connection. Your node is synced when the log switches from `Syncing` to `Idle` at the current tip. - -**Run the node version that matches the network.** If your node stalls mid-sync with `Verification failed` errors and drops to 0 peers, your node version is out of step with the network -- check [Releases](https://github.com/Quantus-Network/chain/releases) and community announcements for which version the network is currently running. - -On first start with `--miner-listen-port`, the node writes miner auth material under the chain directory (token is **not** logged -- read the file): - -| File | Purpose | -|------|---------| -| `miner-auth-token` | Shared secret the miner sends in `Ready`. Mode `0600`. Never put this on the command line or in logs. | -| `miner-tls-cert-sha256` | SHA-256 of the miner QUIC cert. Miners must pin this. Also printed in node logs. | -| `miner-tls-cert.der` / `miner-tls-key.der` | Node TLS material (do not copy the private key to miners). | - -Default chain directory: - -| Platform | Path | -|----------|------| -| Linux | `~/.local/share/quantus-node/chains/planck/` | -| macOS | `~/Library/Application Support/quantus-node/chains/planck/` | - -Wait until logs show the miner server is listening (and the auth/TLS file paths) before starting the miner. If miner-server startup fails, the node exits -- it does not fall back to local mining. - -### 5. Start the Miner - -Download the miner binary from [Miner Releases](https://github.com/Quantus-Network/quantus-miner/releases). Node and miner versions must be a matching pair: the authenticated wire protocol ALPN is `quantus-miner/2`. Confirm `quantus-node --help` lists `--miner-auth-token-file` and `quantus-miner serve --help` lists `--auth-token-file` before using the commands below. A mismatched pair fails at TLS handshake with "no application protocol". Older releases (node v0.9.0, miner v3.3.1 and earlier) do not include miner auth — omit the auth/TLS flags and connect with `--node-addr` only. - -**Open a new terminal window (cmd + t). Let the node run in the original terminal.** - -**macOS only:** - -```bash -xattr -d com.apple.quarantine quantus-miner-macos-aarch64 && chmod u+x quantus-miner-macos-aarch64 -``` - -Wait for the node logs to show the miner server is listening, then run the following in the **separate terminal**. Quote `CHAIN_DIR` — the macOS path contains a space. If not on Apple Silicon, replace `quantus-miner-macos-aarch64` with your platform's binary name. - -```bash -CHAIN_DIR="$HOME/Library/Application Support/quantus-node/chains/planck" -# Linux: CHAIN_DIR="$HOME/.local/share/quantus-node/chains/planck" - -./quantus-miner-macos-aarch64 serve \ - --cpu-workers 4 \ - --gpu-devices 0 \ - --node-addr 127.0.0.1:9833 \ - --auth-token-file "$CHAIN_DIR/miner-auth-token" \ - --tls-cert-sha256-file "$CHAIN_DIR/miner-tls-cert-sha256" -``` - -Prefer `--auth-token-file` / `--tls-cert-sha256-file` over inline `--auth-token` / `--tls-cert-sha256` so the secret is not stored in shell history. When the node/miner pair includes miner auth, both flags are required; a wrong token or TLS pin is a permanent error (the miner will not reconnect-loop). On pre-auth releases, omit those flags. - -Depending on your machine and resources you can adjust `--gpu-devices` and `--cpu-workers` to see what provides the best balance of hash rate and system usability. - -The above command is fairly conservative for most modern hardware. - -For example if you want to use your GPU and have many CPU cores available you could run - -```bash -CHAIN_DIR="$HOME/Library/Application Support/quantus-node/chains/planck" -# Linux: CHAIN_DIR="$HOME/.local/share/quantus-node/chains/planck" - -./quantus-miner-macos-aarch64 serve \ - --cpu-workers 8 \ - --gpu-devices 1 \ - --node-addr 127.0.0.1:9833 \ - --auth-token-file "$CHAIN_DIR/miner-auth-token" \ - --tls-cert-sha256-file "$CHAIN_DIR/miner-tls-cert-sha256" -``` - -If the miner exits immediately, it is usually auth or version mismatch: confirm both files exist, that you waited for the miner server to listen, and that node and miner releases match (`quantus-miner/2`). A wrong token or TLS pin is a permanent error -- re-read the files (the token is never logged). - -## Monitoring - -### Your Rewards - -Rewards accumulate at your wormhole address as you mine. The wallet app supports wormhole addresses and encrypted accounts, so if you mine with the same seed phrase as your app wallet, rewards appear in the app and are spendable directly from your wormhole balance -- there is no separate claiming step. - -### Monitoring Your Node -- **Telemetry dashboard:** [telemetry.quantus.cat](https://telemetry.quantus.cat/) -- find your node by name -- **Prometheus metrics (detailed node metrics):** `http://localhost:9615/metrics` -- **RPC endpoint:** `http://localhost:9944` -- **Check your address in the explorer:** Your wormhole address is in the `Address` field from key generation, or in your node's startup logs. - - - -### **Logs & Diagnostics** - -```bash -# Linux -tail -f ~/.local/share/quantus-node/chains/planck/network/quantus-node.log - -# macOS -tail -f ~/Library/Application\ Support/quantus-node/chains/planck/network/quantus-node.log - -# Or run with verbose logging -RUST_LOG=info ./quantus-node [options] -``` +| Field | Ready value | +| --- | --- | +| Network | `Planck testnet` | +| Compatibility | node `v0.10.0` + miner `v4.0.2` | +| Node | `Running` | +| Sync | `Synced` | +| Miner | `Running` | +| Hash rate | a live rate from the miner | +| Reward address | your public wormhole address | +| Telemetry | your node name at [telemetry.quantus.cat](https://telemetry.quantus.cat/) | -#### **Inspect your node's P2P identity:** +Confirm stop-start recovery once: ```bash -./quantus-node key inspect-node-key --file node_key.p2p +./quantus-mining.sh restart-check ``` -## Security Best Practices +## Daily commands -### Key Management +| Goal | Command | +| --- | --- | +| Start or resume | `./quantus-mining.sh mine` | +| Check readiness | `./quantus-mining.sh status` | +| Stop | `./quantus-mining.sh stop` | +| Reinstall the supported pair | `./quantus-mining.sh setup --force` | +| Change resource use | `./quantus-mining.sh config set CPU_WORKERS 4` | -- **Back up your seed phrase securely** +## Security boundary -### Node Security +- Only port `30333` should be public for peer-to-peer networking. +- Keep miner `9833/UDP`, RPC `9944`, and metrics `9615` private and local. +- The miner reads its auth token and TLS pin from local files. Do not paste either value into chat or command arguments. +- The recovery phrase is entered only into the hidden local prompt. +- The reward preimage is stored locally with owner-only permissions because the current node requires it at startup. +- `status` redacts 64-character secret-like values from log excerpts. +- The installer refuses unsupported networks, protocols, release URLs, assets, and checksums. -- **Firewall:** Only expose port 30333 (P2P). Keep 9833/UDP (miner), 9944 (RPC), and 9615 (metrics) on localhost. The miner port binds `0.0.0.0` -- reachability is entirely your firewall. Auth + TLS pinning do **not** make it safe to publish to the internet. -- **Miner secrets:** Treat `miner-auth-token` like a password. Back it up with the same care as other node files; anyone with the token and network access to port 9833 can submit job results and observe mining jobs. -- **Remote miners:** Put node and miners on a private network or VPN (WireGuard, Tailscale, VPC). Do not open 9833/UDP to `0.0.0.0/0`. -- **Updates:** Check [GitHub Releases](https://github.com/Quantus-Network/chain/releases/latest) for new versions regularly. Node and miner must ship the same miner protocol (`quantus-miner/2`). -- **Monitoring:** Watch for unusual peer counts, sync stalls, or dropped miner connections +## Compatibility policy -### Testnet Disclaimer +The machine-readable [compatibility manifest](/mining-compatibility.json) is the release source of truth. The current pair is grounded in the `v4.0.0` miner release note, which requires node `v0.10.0+`. The installer does not resolve independent `latest` releases. -Planck is testnet software for testing purposes only. Tokens have no monetary value. The network may be reset periodically, and breaking changes are expected between releases. +The manifest currently publishes minimum OS versions as `not-published`. That is an owner fact still needed from the release team. It does not claim a launch date, mainnet support, or rewards with monetary value. +## Fix one problem at a time -### Getting Help +| Problem | One recovery action | +| --- | --- | +| Checksum failed | Delete the named download and run the verified installer again. | +| Unsupported platform | Use macOS, Linux x64, or 64-bit Windows 10/11. Windows on ARM has no published binaries. | +| PowerShell refuses to run the script | Run `Unblock-File .\quantus-mining.ps1`, or start it with `powershell -ExecutionPolicy Bypass -File .\quantus-mining.ps1 mine`. | +| Windows: sync shows peers but the block number is not moving | Windows Defender is scanning the chain database. Run once in an elevated PowerShell: `Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\quantus-node"`, then `.\quantus-mining.ps1 restart-check`. | +| Installed pair is stale | Run `./quantus-mining.sh setup --force`. | +| Node or miner stopped | Run `./quantus-mining.sh mine`. | +| Sync still says `Syncing` | Leave the process running and check `status` later. | +| macOS blocks a binary | Run `xattr -d com.apple.quarantine ~/quantus-mining/bin/quantus-node ~/quantus-mining/bin/quantus-miner`. | +| No hash rate after sync | Run `./quantus-mining.sh setup --force` to restore the supported pair. | +| Computer is sluggish | Run `./quantus-mining.sh config set CPU_WORKERS 1`, then `./quantus-mining.sh restart-check`. | -- **GitHub Issues:** [Report bugs](https://github.com/Quantus-Network/chain/issues) -- **Telegram:** [Quantus community](https://t.me/quantusnetwork) -- **Research forum:** [research.quantus.com](https://research.quantus.com) -- deeper technical discussion with the Quantus team \ No newline at end of file +For protocol development and manual commands, use the chain repository's [MINING.md](https://github.com/Quantus-Network/chain/blob/main/MINING.md). Report reproducible defects in [GitHub Issues](https://github.com/Quantus-Network/chain/issues). diff --git a/package.json b/package.json index 41c905f..9d8d2cc 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", + "test": "bun test", + "check": "bun run typecheck && bun test && bun run build", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", "typecheck": "tsc" @@ -32,6 +34,7 @@ "@docusaurus/module-type-aliases": "^3.10.2", "@docusaurus/tsconfig": "^3.10.2", "@docusaurus/types": "^3.10.2", + "@types/bun": "^1.4.0", "typescript": "~5.6.2" }, "browserslist": { diff --git a/src/components/DocActionButtons/index.tsx b/src/components/DocActionButtons/index.tsx index 26cc872..a0d4977 100644 --- a/src/components/DocActionButtons/index.tsx +++ b/src/components/DocActionButtons/index.tsx @@ -27,6 +27,7 @@ export default function DocActionButtons(): ReactNode { const [container, setContainer] = useState(null); const location = useLocation(); const skillUrl = useBaseUrl('/skills/mining-skill.md'); + const compatibilityUrl = useBaseUrl('/mining-compatibility.json'); const isMiningPage = location.pathname.replace(/\/$/, '') === MINING_PATH; @@ -53,16 +54,31 @@ export default function DocActionButtons(): ReactNode { if (isMiningPage) { try { - const res = await fetch(skillUrl); - if (!res.ok) { - throw new Error(`Skill fetch returned ${res.status}`); + const [skillResponse, compatibilityResponse] = await Promise.all([ + fetch(skillUrl), + fetch(compatibilityUrl), + ]); + if (!skillResponse.ok) { + throw new Error(`Skill fetch returned ${skillResponse.status}`); } - const skill = await res.text(); + if (!compatibilityResponse.ok) { + throw new Error( + `Compatibility fetch returned ${compatibilityResponse.status}`, + ); + } + const skill = await skillResponse.text(); + const compatibility = await compatibilityResponse.text(); text = [ '', '', skill.trim(), '', + '## Pinned compatibility manifest', + '', + '```json', + compatibility.trim(), + '```', + '', '---', '', '# Reference: full mining guide (docs.quantus.com/guides/mining)', @@ -81,7 +97,7 @@ export default function DocActionButtons(): ReactNode { await copyTextToClipboard(text); setCopyState('copied'); setTimeout(() => setCopyState('idle'), 2000); - }, [isMiningPage, skillUrl]); + }, [compatibilityUrl, isMiningPage, skillUrl]); if (!container) return null; diff --git a/static/agent-setup/prompt.md b/static/agent-setup/prompt.md new file mode 100644 index 0000000..3849da1 --- /dev/null +++ b/static/agent-setup/prompt.md @@ -0,0 +1,75 @@ +# Set up Quantus mining + +Help the user set up Planck testnet mining on this computer. Complete ordinary +setup work with your tools, but pause for private wallet input. Planck tokens +have no monetary value and the network may reset. This is not a mainnet setup. + +## Read before running + +Fetch these resources from docs.quantus.com: + +- https://docs.quantus.com/skills/mining-skill.md +- https://docs.quantus.com/mining-compatibility.json +- https://docs.quantus.com/guides/mining/ + +If a resource is missing, returns an HTML error instead of the expected content, +or disagrees with the others, stop and report the exact blocker. Do not invent +commands or substitute a third-party installer. The compatibility manifest is +the source for the supported node/miner pair; never choose independent latest +versions. Read its linked release evidence before installing. + +## Preflight + +1. Detect OS and architecture, free disk space and available CPU/GPU resources. + Use native PowerShell on Windows x64, or the Unix installer on a platform + listed by the guide and manifest. Do not silently install WSL, buy a VPS or + assume phone support. Report unpublished minimum requirements as unknown. +2. Check whether a Quantus setup already exists without reading its secrets. + Preserve existing wallets, configuration and chain data. Do not overwrite or + force-reinstall a working setup without explaining why and asking first. +3. Explain that mining consumes power, storage and compute, and sync can take + time. Use conservative resource settings. Ask before starting a paid cloud + instance or changing firewall, antivirus, privileges or startup services. + Never add antivirus exclusions as an automatic troubleshooting step. + +## Install and start + +Download the matching installer and its published SHA-256 file to a local setup +directory. Verify the bytes before execution and inspect the script. Never pipe +remote code into a shell. A checksum served beside a script checks integrity, +not an independent signature or proof that the code is safe. + +- Windows: https://docs.quantus.com/scripts/quantus-mining.ps1 +- Unix: https://docs.quantus.com/scripts/quantus-mining.sh +- Append `.sha256` to the chosen URL for the checksum file. + +Use the guide's verified command block and the installer's `mine` command. The +installer must validate release downloads against the pinned manifest. Do not +disable verification, supply version overrides or change networks. + +Never ask for or capture a recovery phrase, private key, reward preimage, auth +token or TLS key in chat, command arguments, logs, screenshots or repositories. +Before any wallet prompt, hand control to the user in an unrecorded local +terminal and stop reading it. If your tools cannot provide a private terminal, +prepare the files and ask the user to perform that step locally. Do not run the +interactive wallet step in an agent-captured terminal. Resume only after the +user confirms completion. Do not inspect wallet or credential files. + +Keep miner control, RPC and metrics private. Do not expose ports 9833/UDP, 9944 +or 9615. Only the documented peer-to-peer service may be public. + +## Verify, do not assume + +Use the chosen installer's `status` command. A running process alone is not +success. Distinguish installed, syncing, connected and actively mining states. +Require a synced node, live miner work and a current nonzero hash rate before +reporting mining success. Then run `restart-check` and confirm recovery. + +If sync is unfinished, report "installed, syncing" and the next status command, +not "mining complete". If a check fails, give one specific next action. Do not +automatically weaken security or repeatedly reinstall to hide a failure. + +Finish with non-secret facts: platform, network, pinned versions, sync state, +mining state, hash rate and restart result. Include the local `status` and `stop` +commands for the actual shell. Never paste raw logs or promise earnings, a +launch date, instant sync or a leaderboard position. diff --git a/static/mining-compatibility.json b/static/mining-compatibility.json new file mode 100644 index 0000000..67d8ea7 --- /dev/null +++ b/static/mining-compatibility.json @@ -0,0 +1,42 @@ +{ + "schemaVersion": "1", + "updatedAt": "2026-09-03T07:00:00Z", + "status": "supported", + "networkId": "planck", + "networkLabel": "Planck testnet", + "networkKind": "testnet", + "tokenValue": "none", + "minerProtocol": "quantus-miner/2", + "compatibilityEvidenceUrl": "https://github.com/Quantus-Network/quantus-miner/releases/tag/v4.0.0", + "nodeVersion": "v0.10.0", + "minerVersion": "v4.0.2", + "desktopAppVersion": "miner-v0.6.1", + "desktopAppStatus": "preview", + "desktopAppBundlesRuntime": "false", + "desktopAppVerifiesRuntimeDownloads": "false", + "minimumMacos": "not-published", + "minimumLinux": "not-published", + "minimumWindows": "not-published", + "nodeDarwinArm64Url": "https://github.com/Quantus-Network/chain/releases/download/v0.10.0/quantus-node-v0.10.0-aarch64-apple-darwin.tar.gz", + "nodeDarwinArm64Sha256": "978feecb876e9cfb671a62481a3f5b58190ca94d0df98cf4a903cc4dc9be9b92", + "minerDarwinArm64Url": "https://github.com/Quantus-Network/quantus-miner/releases/download/v4.0.2/quantus-miner-macos-aarch64", + "minerDarwinArm64Sha256": "0d991ecab2734d31c18b9f83c23512e2f6d39b2f7962e6f8efd3235c767523f9", + "nodeDarwinX8664Url": "https://github.com/Quantus-Network/chain/releases/download/v0.10.0/quantus-node-v0.10.0-x86_64-apple-darwin.tar.gz", + "nodeDarwinX8664Sha256": "a78d228bd44a304f284b716ef312d2f7aa14f8a4fefa7752b4d861a9bd42babe", + "minerDarwinX8664Url": "https://github.com/Quantus-Network/quantus-miner/releases/download/v4.0.2/quantus-miner-macos-x86_64", + "minerDarwinX8664Sha256": "709615a5d10086d82be1caa18aee0dae259d71617599037500d1fa86620a14e9", + "nodeLinuxX8664Url": "https://github.com/Quantus-Network/chain/releases/download/v0.10.0/quantus-node-v0.10.0-x86_64-unknown-linux-gnu.tar.gz", + "nodeLinuxX8664Sha256": "de1a3aa85fb83780b8880b74516824c95b42d4e081c06d7a32d477c78a160bcc", + "minerLinuxX8664Url": "https://github.com/Quantus-Network/quantus-miner/releases/download/v4.0.2/quantus-miner-linux-x86_64", + "minerLinuxX8664Sha256": "614a71f8d3a421ad186b973c744c93d0e54c5a1aaa7629e1763db41e019a8993", + "nodeWindowsX8664Url": "https://github.com/Quantus-Network/chain/releases/download/v0.10.0/quantus-node-v0.10.0-x86_64-pc-windows-msvc.zip", + "nodeWindowsX8664Sha256": "5d0816d5fc3d78920f4eab932bde35700766f1989e3f52756aca3d244ff485e6", + "minerWindowsX8664Url": "https://github.com/Quantus-Network/quantus-miner/releases/download/v4.0.2/quantus-miner-windows-x86_64.exe", + "minerWindowsX8664Sha256": "6b9a27aaf0ad394f095d88939bab47a7613f8edd355e61ea5457a906ce3890ce", + "desktopAppMacosUrl": "https://github.com/Quantus-Network/quantus-apps/releases/download/miner-v0.6.1/quantus_miner_macos.zip", + "desktopAppMacosSha256": "fb4769d4c1e8ccd3c480e0caeca07c9baa2b3ef0540828894dddeea3d4ed016e", + "desktopAppLinuxUrl": "https://github.com/Quantus-Network/quantus-apps/releases/download/miner-v0.6.1/quantus_miner_linux.tar.gz", + "desktopAppLinuxSha256": "fd2755f2fe5df05af3deb8e1081fa808f6c872bd9d4bf2853eea0fe354655076", + "desktopAppWindowsUrl": "https://github.com/Quantus-Network/quantus-apps/releases/download/miner-v0.6.1/quantus_miner_windows.zip", + "desktopAppWindowsSha256": "081308152ba6f9eba62b9ac143f8c9e115ffab400a978a9d03a996e55ece8ad1" +} diff --git a/static/scripts/mining.conf.example b/static/scripts/mining.conf.example index e7e1006..2667d16 100644 --- a/static/scripts/mining.conf.example +++ b/static/scripts/mining.conf.example @@ -1,44 +1,45 @@ # Quantus mining configuration example -# Copy to ~/quantus-mining/mining.conf or run: ./quantus-mining.sh setup +# Written by: ./quantus-mining.sh setup or .\quantus-mining.ps1 setup +# Location: ~/quantus-mining/mining.conf (owner-only permissions) # -# File permissions should be 600 (the setup script sets this automatically). -# Never store your 24-word mnemonic in this file — only INNER_HASH is persisted. +# This file holds public settings only. The reward preimage lives in the +# separate owner-only file ~/quantus-mining/rewards-inner-hash, and the +# 24-word recovery phrase is never written anywhere by the installer. -# Node name shown on https://telemetry.quantus.cat/ -NODE_NAME="my-planck-node" +RUN_MODE="binary" -# 32-byte wormhole preimage — required for --rewards-inner-hash -INNER_HASH="0x..." +# Node name shown on https://telemetry.quantus.cat/ +NODE_NAME="quantus-my-machine" -# Wormhole SS58 address where mining rewards accumulate (informational) -WORMHOLE_ADDRESS="..." +# Wormhole address where mining rewards accumulate (informational) +WORMHOLE_ADDRESS="qz..." -# P2P node identity file (relative to QUANTUS_MINING_DIR) +# P2P node identity file (relative to the working directory) NODE_KEY_FILE="node_key.p2p" -# Chain spec +# Chain spec. The installers only support planck. CHAIN="planck" -# QUIC miner server port (node listens; miner connects via --node-addr) -# Auth files live in the node's chain dir (not this file): +# QUIC miner server port (node listens; miner connects via --node-addr). +# Auth files live in the node's chain dir, not here: # miner-auth-token, miner-tls-cert-sha256 MINER_LISTEN_PORT=9833 # External miner resource allocation -# GPU mining recommended: GPU_DEVICES=1, CPU_WORKERS=0 -# CPU-only: leave ~2 cores free for OS/node +# GPU mining: GPU_DEVICES=1, CPU_WORKERS=0 +# CPU-only: GPU_DEVICES=0, CPU_WORKERS= CPU_WORKERS=0 GPU_DEVICES=1 -# Pinned versions. `setup` and `setup --force` download these tags. -# Environment NODE_VERSION / MINER_VERSION override the file. -# Leave empty to use GitHub latest (independent latest tags may not match). -# After changing pins, re-run: ./quantus-mining.sh setup --force -NODE_VERSION="" -MINER_VERSION="" +# The pair the installer verified against the compatibility manifest. +# Not user-editable: start refuses to run if these drift from the manifest. +# To move to a newer published pair, re-run: setup --force +NODE_VERSION="v0.10.0" +MINER_VERSION="v4.0.2" +MINER_PROTOCOL="auth" # Editable via: ./quantus-mining.sh config set KEY VALUE -# NODE_NAME, CPU_WORKERS, GPU_DEVICES, MINER_LISTEN_PORT, CHAIN, -# NODE_VERSION, MINER_VERSION +# NODE_NAME, CPU_WORKERS, GPU_DEVICES, MINER_LISTEN_PORT # -# To change INNER_HASH / rewards destination, re-run setup wormhole keygen. +# To change the reward destination, re-run setup --force and enter the +# recovery phrase for the wallet you want rewards sent to. diff --git a/static/scripts/quantus-mining.ps1 b/static/scripts/quantus-mining.ps1 new file mode 100644 index 0000000..8c046f8 --- /dev/null +++ b/static/scripts/quantus-mining.ps1 @@ -0,0 +1,840 @@ +#Requires -Version 5.1 +<# +quantus-mining.ps1 - Set up and manage verified Quantus Planck testnet mining on Windows. + +Native Windows twin of quantus-mining.sh. Same commands, same compatibility +manifest, same checksums, same hidden recovery-phrase prompt, same status +contract. Requires Windows 10/11 x64 and PowerShell 5.1 or later. No WSL. + +Default working directory: $HOME\quantus-mining\ +Config file: $HOME\quantus-mining\mining.conf (owner-only) + +Usage: + .\quantus-mining.ps1 mine + .\quantus-mining.ps1 setup [-Force] + .\quantus-mining.ps1 config show|set KEY VALUE + .\quantus-mining.ps1 start + .\quantus-mining.ps1 stop|restart + .\quantus-mining.ps1 status|restart-check + .\quantus-mining.ps1 uninstall [-Force] + .\quantus-mining.ps1 help + +If PowerShell refuses to run the file, it was downloaded with a web mark. Run: + Unblock-File .\quantus-mining.ps1 +or start it as: + powershell -ExecutionPolicy Bypass -File .\quantus-mining.ps1 mine + +Override directory: $env:QUANTUS_MINING_DIR = 'D:\path'; .\quantus-mining.ps1 ... +#> + +[CmdletBinding()] +param( + [Parameter(Position = 0)][string]$Command = 'help', + [Parameter(Position = 1, ValueFromRemainingArguments = $true)][string[]]$Rest = @(), + [switch]$Force +) + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = 'Stop' + +# --------------------------------------------------------------------------- +# Paths and constants +# --------------------------------------------------------------------------- + +$script:ScriptName = 'quantus-mining.ps1' +$script:MiningDir = if ($env:QUANTUS_MINING_DIR) { $env:QUANTUS_MINING_DIR } else { Join-Path $HOME 'quantus-mining' } +$script:ConfigFile = Join-Path $script:MiningDir 'mining.conf' +$script:BinDir = Join-Path $script:MiningDir 'bin' +$script:LogDir = Join-Path $script:MiningDir 'logs' +$script:NodeBin = Join-Path $script:BinDir 'quantus-node.exe' +$script:MinerBin = Join-Path $script:BinDir 'quantus-miner.exe' +$script:NodePidFile = Join-Path $script:MiningDir 'node.pid' +$script:MinerPidFile = Join-Path $script:MiningDir 'miner.pid' +$script:NodeKeyPath = Join-Path $script:MiningDir 'node_key.p2p' +$script:InnerHashFile = Join-Path $script:MiningDir 'rewards-inner-hash' +$script:CompatibilityFile = Join-Path $script:MiningDir 'mining-compatibility.json' +$script:CompatibilityUrl = if ($env:QUANTUS_COMPATIBILITY_URL) { $env:QUANTUS_COMPATIBILITY_URL } else { 'https://docs.quantus.com/mining-compatibility.json' } + +$script:ChainRepo = 'Quantus-Network/chain' +$script:MinerRepo = 'Quantus-Network/quantus-miner' +$script:EditableKeys = @('NODE_NAME', 'CPU_WORKERS', 'GPU_DEVICES', 'MINER_LISTEN_PORT') + +# Only x64 Windows has published release assets. ARM64 Windows can run the +# x64 binaries under emulation, and the miner would still not see the GPU +# natively, so it is refused rather than half-supported. +$script:PlatformKey = 'WindowsX8664' +$script:NodeTarget = 'x86_64-pc-windows-msvc' +$script:MinerAsset = 'quantus-miner-windows-x86_64.exe' + +$script:Manifest = @{} +$script:Config = @{} +# 'auth' or 'legacy', decided by probing both binaries' --help. Declared here so +# strict mode never sees it unset; every path that reads it runs the probe first. +$script:MinerProtocol = '' + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +function Fail([string]$Message) { + [Console]::Error.WriteLine("Error: $Message") + exit 1 +} + +function Info([string]$Message) { Write-Output $Message } + +function Warn([string]$Message) { [Console]::Error.WriteLine("Warning: $Message") } + +function Test-Platform { + if (-not [Environment]::Is64BitOperatingSystem) { + Fail 'Unsupported platform: 32-bit Windows. Use a 64-bit Windows 10/11 machine.' + } + $arch = $env:PROCESSOR_ARCHITECTURE + if ($arch -ne 'AMD64') { + Fail "Unsupported architecture: $arch. Quantus publishes Windows binaries for x64 only." + } +} + +function Ensure-Dirs { + foreach ($d in @($script:MiningDir, $script:BinDir, $script:LogDir)) { + if (-not (Test-Path $d)) { New-Item -ItemType Directory -Force $d | Out-Null } + } +} + +# Owner-only file, the Windows equivalent of chmod 600. Inheritance is +# removed first so the parent folder's Users entry does not leak through. +function Protect-File([string]$Path) { + $me = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + & icacls $Path /inheritance:r /grant:r "${me}:(F)" | Out-Null +} + +function Get-CpuCount { + try { return [int](Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors } catch { return 4 } +} + +function Get-DiscreteGpuPresent { + # The miner skips integrated adapters by default when a discrete one exists, + # so this only decides the default of GPU_DEVICES, not which device is used. + try { + $names = (Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name) -join ' ' + return ($names -match 'NVIDIA|GeForce|Quadro|RTX|GTX|Radeon RX|Radeon Pro|Arc') + } catch { return $false } +} + +function Get-FileSha256([string]$Path) { + return (Get-FileHash -Path $Path -Algorithm SHA256).Hash.ToLowerInvariant() +} + +function Assert-Sha256([string]$Path, [string]$Expected) { + $name = Split-Path $Path -Leaf + if (-not $Expected -or $Expected -notmatch '^[0-9a-f]{64}$') { + Fail "Invalid SHA-256 value for $name. No files were installed." + } + $actual = Get-FileSha256 $Path + if ($actual -ne $Expected) { + Remove-Item $Path -Force -ErrorAction SilentlyContinue + Fail ("Checksum verification failed for {0}.`nExpected: {1}`nActual: {2}`nThe file was not installed. Delete the download and retry." -f $name, $Expected, $actual) + } + Info "Verified SHA-256: $name" +} + +function Assert-ReleaseUrl([string]$Url, [string]$Repo, [string]$Version) { + $prefix = "https://github.com/$Repo/releases/download/$Version/" + if (-not $Url.StartsWith($prefix)) { + Fail "Compatibility manifest contains an unexpected download URL for $Repo. No files were installed." + } +} + +function Invoke-Download([string]$Url, [string]$OutFile) { + # TLS 1.2 is not the default on older PowerShell 5.1 hosts. + [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + try { + Invoke-WebRequest -Uri $Url -OutFile $OutFile -UseBasicParsing + } catch { + Fail "Failed to download $Url. Check your connection and retry." + } +} + +# --------------------------------------------------------------------------- +# Compatibility manifest +# --------------------------------------------------------------------------- + +function Read-ManifestString([hashtable]$Manifest, [string]$Key) { + if (-not $Manifest.ContainsKey($Key) -or [string]::IsNullOrWhiteSpace([string]$Manifest[$Key])) { + Fail "Compatibility manifest is missing $Key. No files were installed." + } + return [string]$Manifest[$Key] +} + +function Import-CompatibilityManifest([string]$Path) { + if (-not (Test-Path $Path) -or (Get-Item $Path).Length -eq 0) { + Fail "Compatibility manifest not found at $Path. Run: $script:ScriptName setup -Force" + } + $raw = Get-Content $Path -Raw + try { $json = $raw | ConvertFrom-Json } catch { Fail 'Compatibility manifest is not valid JSON. No files were installed.' } + $m = @{} + foreach ($p in $json.PSObject.Properties) { $m[$p.Name] = [string]$p.Value } + + $status = Read-ManifestString $m 'status' + if ($status -ne 'supported') { + Fail "Mining compatibility status is '$status', not 'supported'. Nothing will be installed or started." + } + $m['_chain'] = Read-ManifestString $m 'networkId' + $kind = Read-ManifestString $m 'networkKind' + $token = Read-ManifestString $m 'tokenValue' + $m['_nodeVersion'] = Read-ManifestString $m 'nodeVersion' + $m['_minerVersion'] = Read-ManifestString $m 'minerVersion' + $m['_minerProtocol'] = Read-ManifestString $m 'minerProtocol' + $evidence = Read-ManifestString $m 'compatibilityEvidenceUrl' + + if ($m['_chain'] -ne 'planck' -or $kind -ne 'testnet' -or $token -ne 'none') { + Fail 'This installer is restricted to the Planck testnet. The manifest requested a different network.' + } + if ($m['_minerProtocol'] -ne 'quantus-miner/2') { + Fail "Unsupported miner protocol '$($m['_minerProtocol'])'. Nothing will be installed or started." + } + if (-not $evidence.StartsWith('https://github.com/Quantus-Network/quantus-miner/releases/tag/')) { + Fail 'Compatibility evidence URL is not an official Quantus miner release.' + } + + $m['_nodeUrl'] = Read-ManifestString $m "node$($script:PlatformKey)Url" + $m['_nodeSha'] = Read-ManifestString $m "node$($script:PlatformKey)Sha256" + $m['_minerUrl'] = Read-ManifestString $m "miner$($script:PlatformKey)Url" + $m['_minerSha'] = Read-ManifestString $m "miner$($script:PlatformKey)Sha256" + + Assert-ReleaseUrl $m['_nodeUrl'] $script:ChainRepo $m['_nodeVersion'] + Assert-ReleaseUrl $m['_minerUrl'] $script:MinerRepo $m['_minerVersion'] + $expectedNodeAsset = "quantus-node-$($m['_nodeVersion'])-$($script:NodeTarget).zip" + if ((Split-Path $m['_nodeUrl'] -Leaf) -ne $expectedNodeAsset) { + Fail 'Node asset does not match windows/x64. No files were installed.' + } + if ((Split-Path $m['_minerUrl'] -Leaf) -ne $script:MinerAsset) { + Fail 'Miner asset does not match windows/x64. No files were installed.' + } + $script:Manifest = $m + return $m +} + +function Get-CompatibilityManifest { + $temp = "$($script:CompatibilityFile).download" + Info 'Fetching the supported Planck release pair...' + Invoke-Download $script:CompatibilityUrl $temp + Import-CompatibilityManifest $temp | Out-Null + Move-Item $temp $script:CompatibilityFile -Force + Protect-File $script:CompatibilityFile + Info "Supported pair: node $($script:Manifest['_nodeVersion']) + miner $($script:Manifest['_minerVersion']) ($($script:Manifest['_minerProtocol']))" +} + +# --------------------------------------------------------------------------- +# Pair protocol probe. Same test the shell installer runs. +# --------------------------------------------------------------------------- + +function Get-MinerProtocol([string]$NodeHelp, [string]$MinerHelp) { + $nodeAuth = $NodeHelp.Contains('miner-auth-token-file') + $minerAuth = $MinerHelp.Contains('auth-token-file') -and $MinerHelp.Contains('tls-cert-sha256-file') + if ($nodeAuth -and $minerAuth) { return 'auth' } + if (-not $nodeAuth -and -not $minerAuth) { return 'legacy' } + Fail ("Incompatible node/miner pair for the miner protocol.`n Node --miner-auth-token-file: {0}`n Miner --auth-token-file / --tls-cert-sha256-file: {1}`n`nDo not mix a release that requires Ready {{ token }} (quantus-miner/2) with one that does not.`nRun {2} setup -Force to reinstall the supported pair from:`n {3}" -f $nodeAuth, $minerAuth, $script:ScriptName, $script:CompatibilityUrl) +} + +function Test-InstalledPair { + if (-not (Test-Path $script:NodeBin)) { Fail "quantus-node not found at $($script:NodeBin)" } + if (-not (Test-Path $script:MinerBin)) { Fail "quantus-miner not found at $($script:MinerBin)" } + $nodeHelp = (& $script:NodeBin --help 2>&1 | Out-String) + if ($LASTEXITCODE -ne 0) { Fail "Failed to probe quantus-node --help (exit $LASTEXITCODE)." } + $minerHelp = (& $script:MinerBin serve --help 2>&1 | Out-String) + if ($LASTEXITCODE -ne 0) { Fail "Failed to probe quantus-miner serve --help (exit $LASTEXITCODE)." } + $script:MinerProtocol = Get-MinerProtocol $nodeHelp $minerHelp + Info "Miner protocol: $($script:MinerProtocol)" +} + +# --------------------------------------------------------------------------- +# Downloads +# --------------------------------------------------------------------------- + +function Install-NodeBinary { + $url = $script:Manifest['_nodeUrl']; $sha = $script:Manifest['_nodeSha'] + Info "Downloading quantus-node $($script:Manifest['_nodeVersion']) for $($script:NodeTarget)..." + $temp = Join-Path ([IO.Path]::GetTempPath()) ("quantus-node-" + [Guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Force $temp | Out-Null + $zip = Join-Path $temp (Split-Path $url -Leaf) + Invoke-Download $url $zip + Assert-Sha256 $zip $sha + Expand-Archive -Path $zip -DestinationPath $temp -Force + $exe = Get-ChildItem $temp -Recurse -Filter 'quantus-node.exe' | Select-Object -First 1 + if (-not $exe) { Remove-Item $temp -Recurse -Force; Fail 'quantus-node.exe not found in archive' } + Copy-Item $exe.FullName $script:NodeBin -Force + Remove-Item $temp -Recurse -Force + Info "Installed quantus-node to $($script:NodeBin)" +} + +function Install-MinerBinary { + $url = $script:Manifest['_minerUrl']; $sha = $script:Manifest['_minerSha'] + Info "Downloading quantus-miner $($script:Manifest['_minerVersion']) ($($script:MinerAsset))..." + $temp = "$($script:MinerBin).download" + Invoke-Download $url $temp + Assert-Sha256 $temp $sha + Move-Item $temp $script:MinerBin -Force + Info "Installed quantus-miner to $($script:MinerBin)" +} + +function Install-Binaries { + Get-CompatibilityManifest + Install-NodeBinary + Install-MinerBinary + Test-InstalledPair + if ($script:MinerProtocol -ne 'auth') { + Fail 'The downloaded binaries do not implement the manifest protocol quantus-miner/2. Nothing will be started.' + } +} + +# --------------------------------------------------------------------------- +# Wallet +# --------------------------------------------------------------------------- + +function Read-WormholeOutput([string]$Output) { + $addr = ($Output -split "`r?`n" | Where-Object { $_ -match '^\s*Address:\s*(\S+)' } | ForEach-Object { $Matches[1] } | Select-Object -First 1) + $hash = ($Output -split "`r?`n" | Where-Object { $_ -match '^\s*Inner [Hh]ash:\s*(\S+)' } | ForEach-Object { $Matches[1] } | Select-Object -First 1) + if (-not $hash) { + $hash = ($Output -split "`r?`n" | Where-Object { $_ -match '^\s*inner_hash:\s*(\S+)' } | ForEach-Object { $Matches[1] } | Select-Object -First 1) + } + if (-not $addr) { Fail 'Could not parse wormhole Address from keygen output' } + if (-not $hash) { Fail 'Could not parse Inner Hash from keygen output' } + return @{ Address = $addr; InnerHash = $hash } +} + +function New-WormholeKeys { + Write-Output '' + Info 'Wallet step: enter your existing Quantus 24-word recovery phrase locally.' + Info 'Input is hidden and is not written to disk, logs, command arguments, or network requests.' + Info 'Never paste a recovery phrase into chat or a support ticket.' + $secure = Read-Host -Prompt 'Recovery phrase' -AsSecureString + $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) + try { + $phrase = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) + } finally { + [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) + } + if ([string]::IsNullOrWhiteSpace($phrase)) { Fail 'Recovery phrase cannot be empty. Open your Quantus wallet backup and retry.' } + + # The phrase goes to the node on stdin only, never as an argument. + $output = ($phrase | & $script:NodeBin key quantus --scheme wormhole --words 2>&1 | Out-String) + $phrase = $null + $keys = Read-WormholeOutput $output + $output = $null + + Set-Content -Path $script:InnerHashFile -Value $keys.InnerHash -NoNewline -Encoding ascii + Protect-File $script:InnerHashFile + $script:Config['WORMHOLE_ADDRESS'] = $keys.Address + + Write-Output '' + Info "Reward address: $($keys.Address)" + Info 'Your recovery phrase was not saved. Keep your existing offline backup.' +} + +# --------------------------------------------------------------------------- +# Config +# --------------------------------------------------------------------------- + +function Set-ResourceDefaults { + $cores = Get-CpuCount + $workers = [Math]::Max(1, $cores - 2) + if (Get-DiscreteGpuPresent) { + $script:Config['GPU_DEVICES'] = '1' + $script:Config['CPU_WORKERS'] = '0' + Info 'Mining resources: one detected GPU, CPU reserved for the node.' + } else { + $script:Config['GPU_DEVICES'] = '0' + $script:Config['CPU_WORKERS'] = [string]$workers + Info "Mining resources: $workers CPU workers, two cores reserved when available." + } +} + +function Write-Config { + $c = $script:Config + if (-not $c.ContainsKey('CHAIN')) { $c['CHAIN'] = 'planck' } + if (-not $c.ContainsKey('MINER_LISTEN_PORT')) { $c['MINER_LISTEN_PORT'] = '9833' } + if (-not $c.ContainsKey('CPU_WORKERS')) { $c['CPU_WORKERS'] = '0' } + if (-not $c.ContainsKey('GPU_DEVICES')) { $c['GPU_DEVICES'] = '0' } + $lines = @( + "# Quantus mining configuration - $($script:ConfigFile)", + "# Generated by $($script:ScriptName) setup", + '', + 'RUN_MODE=binary', + "NODE_NAME=$($c['NODE_NAME'])", + "WORMHOLE_ADDRESS=$($c['WORMHOLE_ADDRESS'])", + 'NODE_KEY_FILE=node_key.p2p', + "CHAIN=$($c['CHAIN'])", + "MINER_LISTEN_PORT=$($c['MINER_LISTEN_PORT'])", + "CPU_WORKERS=$($c['CPU_WORKERS'])", + "GPU_DEVICES=$($c['GPU_DEVICES'])", + "NODE_VERSION=$($c['NODE_VERSION'])", + "MINER_VERSION=$($c['MINER_VERSION'])", + "MINER_PROTOCOL=$($c['MINER_PROTOCOL'])" + ) + Set-Content -Path $script:ConfigFile -Value ($lines -join "`r`n") -Encoding ascii + Protect-File $script:ConfigFile + Info "Wrote config to $($script:ConfigFile)" +} + +function Read-Config { + if (-not (Test-Path $script:ConfigFile)) { Fail "Config not found at $($script:ConfigFile). Run: $($script:ScriptName) setup" } + $c = @{} + foreach ($line in Get-Content $script:ConfigFile) { + if ($line -match '^\s*#' -or $line -notmatch '=') { continue } + $k, $v = $line -split '=', 2 + $c[$k.Trim()] = $v.Trim().Trim('"') + } + if (-not $c.ContainsKey('NODE_NAME')) { Fail 'NODE_NAME missing in config' } + if (-not (Test-Path $script:InnerHashFile) -or (Get-Item $script:InnerHashFile).Length -eq 0) { + Fail "Reward preimage file is missing. Re-run $($script:ScriptName) setup -Force and enter the phrase locally." + } + $c['_innerHash'] = (Get-Content $script:InnerHashFile -Raw).Trim() + if (-not $c['_innerHash']) { Fail "Reward preimage file is empty. Re-run $($script:ScriptName) setup -Force." } + foreach ($pair in @(@('CHAIN', 'planck'), @('MINER_LISTEN_PORT', '9833'), @('CPU_WORKERS', '0'), @('GPU_DEVICES', '0'), @('NODE_KEY_FILE', 'node_key.p2p'))) { + if (-not $c.ContainsKey($pair[0])) { $c[$pair[0]] = $pair[1] } + } + $script:Config = $c + return $c +} + +# --------------------------------------------------------------------------- +# Processes +# --------------------------------------------------------------------------- + +function Get-NodeDataPath { + if ($env:QUANTUS_NODE_DATA_PATH) { return $env:QUANTUS_NODE_DATA_PATH } + return Join-Path $env:LOCALAPPDATA 'quantus-node' +} + +function Get-NodeChainDir { return Join-Path (Get-NodeDataPath) "chains\$($script:Config['CHAIN'])" } +function Get-MinerAuthTokenPath { return Join-Path (Get-NodeChainDir) 'miner-auth-token' } +function Get-MinerTlsPinPath { return Join-Path (Get-NodeChainDir) 'miner-tls-cert-sha256' } + +function Read-PidFile([string]$Path) { + if (Test-Path $Path) { return (Get-Content $Path -Raw).Trim() } + return '' +} + +function Test-ProcessAlive([string]$procId, [string]$ExpectedName = '') { + if (-not $procId) { return $false } + $p = Get-Process -Id ([int]$procId) -ErrorAction SilentlyContinue + if (-not $p) { return $false } + if ($ExpectedName -and $p.ProcessName -ne $ExpectedName) { return $false } + return $true +} + +function Stop-Tracked([string]$PidFile, [string]$ProcessName, [string]$Label) { + $stopped = $false + $procId = Read-PidFile $PidFile + if (Test-ProcessAlive $procId $ProcessName) { + Info "Stopping $Label (PID $procId)..." + Stop-Process -Id ([int]$procId) -Force -ErrorAction SilentlyContinue + $stopped = $true + } + # Any stray copy started from this install's bin dir is ours too. + foreach ($p in Get-Process -Name $ProcessName -ErrorAction SilentlyContinue) { + try { + if ($p.Path -and $p.Path.StartsWith($script:BinDir, [StringComparison]::OrdinalIgnoreCase)) { + Info "Stopping $Label (PID $($p.Id))..." + Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue + $stopped = $true + } + } catch { } + } + Remove-Item $PidFile -Force -ErrorAction SilentlyContinue + return $stopped +} + +function Test-MinerPortListening([int]$Port) { + # QUIC is UDP. The TCP check is kept for parity with the shell installer. + $udp = Get-NetUDPEndpoint -LocalPort $Port -ErrorAction SilentlyContinue + if ($udp) { return $true } + $tcp = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue + return [bool]$tcp +} + +function Test-StackRunning { + if (Test-ProcessAlive (Read-PidFile $script:NodePidFile) 'quantus-node') { return $true } + if (Test-ProcessAlive (Read-PidFile $script:MinerPidFile) 'quantus-miner') { return $true } + return (Test-MinerPortListening ([int]$script:Config['MINER_LISTEN_PORT'])) +} + +function Get-NodeLaunchArgs { + $c = $script:Config + return @( + '--name', $c['NODE_NAME'], + '--validator', + '--base-path', (Get-NodeDataPath), + '--miner-listen-port', $c['MINER_LISTEN_PORT'], + '--chain', $c['CHAIN'], + '--node-key-file', (Join-Path $script:MiningDir $c['NODE_KEY_FILE']), + '--rewards-inner-hash', $c['_innerHash'], + '--max-blocks-per-request', '64', + '--sync', 'full' + ) +} + +function Get-MinerLaunchArgs { + $c = $script:Config + $launch = @( + 'serve', + '--cpu-workers', $c['CPU_WORKERS'], + '--gpu-devices', $c['GPU_DEVICES'], + '--node-addr', "127.0.0.1:$($c['MINER_LISTEN_PORT'])" + ) + if ($script:MinerProtocol -eq 'auth') { + $token = Get-MinerAuthTokenPath; $pin = Get-MinerTlsPinPath + if (-not (Test-Path $token)) { Fail "Miner auth token not found at $token. Start the node first and wait until it is listening." } + if (-not (Test-Path $pin)) { Fail "Miner TLS pin not found at $pin. Start the node first and wait until it is listening." } + $launch += @('--auth-token-file', $token, '--tls-cert-sha256-file', $pin) + } + return $launch +} + +function Wait-ForMinerServer([int]$Port, [string]$NodeLog, [int]$Timeout = 120) { + Info "Waiting for miner server on port $Port (up to ${Timeout}s)..." + for ($i = 0; $i -lt $Timeout; $i++) { + if ((Test-Path $NodeLog) -and (Select-String -Path $NodeLog -Pattern 'Miner server listening' -Quiet)) { return } + if (Test-MinerPortListening $Port) { return } + Start-Sleep 1 + } + Fail "Timed out waiting for miner server on port $Port. Check $NodeLog" +} + +function Wait-ForMinerAuthFiles([int]$Timeout = 30) { + if ($script:MinerProtocol -ne 'auth') { return } + Info "Waiting for miner auth files (up to ${Timeout}s)..." + for ($i = 0; $i -lt $Timeout; $i++) { + if ((Test-Path (Get-MinerAuthTokenPath)) -and (Test-Path (Get-MinerTlsPinPath))) { return } + Start-Sleep 1 + } + Fail ("Timed out waiting for miner auth files:`n {0}`n {1}`nStart the node first and wait until it is listening." -f (Get-MinerAuthTokenPath), (Get-MinerTlsPinPath)) +} + +function Start-Hidden([string]$Exe, [string[]]$Arguments, [string]$Log) { + $p = Start-Process -FilePath $Exe -ArgumentList $Arguments -RedirectStandardOutput "$Log.out" -RedirectStandardError $Log -WindowStyle Hidden -PassThru + return $p +} + +function Confirm-StartPrerequisites { + Read-Config | Out-Null + Ensure-Dirs + Test-Platform + Import-CompatibilityManifest $script:CompatibilityFile | Out-Null + $c = $script:Config; $m = $script:Manifest + if ($c['NODE_VERSION'] -ne $m['_nodeVersion'] -or $c['MINER_VERSION'] -ne $m['_minerVersion'] -or $c['CHAIN'] -ne $m['_chain']) { + Fail ("Installed mining files do not match the supported manifest.`nInstalled: node {0} + miner {1} on {2}`nRequired: node {3} + miner {4} on {5}`nRun: {6} setup -Force" -f $c['NODE_VERSION'], $c['MINER_VERSION'], $c['CHAIN'], $m['_nodeVersion'], $m['_minerVersion'], $m['_chain'], $script:ScriptName) + } + if (-not (Test-Path $script:NodeBin)) { Fail "quantus-node not found at $($script:NodeBin). Run: $($script:ScriptName) setup" } + if (-not (Test-Path $script:MinerBin)) { Fail "quantus-miner not found at $($script:MinerBin). Run: $($script:ScriptName) setup" } + $key = Join-Path $script:MiningDir $c['NODE_KEY_FILE'] + if (-not (Test-Path $key)) { Fail "Node key not found at $key. Run: $($script:ScriptName) setup" } + Test-InstalledPair +} + +# --------------------------------------------------------------------------- +# Status helpers +# --------------------------------------------------------------------------- + +function Invoke-NodeRpc([string]$Method) { + $body = '{"jsonrpc":"2.0","id":1,"method":"' + $Method + '","params":[]}' + try { + $r = Invoke-RestMethod -Uri 'http://127.0.0.1:9944' -Method Post -ContentType 'application/json' -Body $body -TimeoutSec 3 + return $r.result + } catch { return $null } +} + +function Hide-Secrets([string]$Text) { + $t = $Text -replace '(?i)(recovery phrase|seed phrase|mnemonic|private key|inner hash|auth token)([=:]\s*|\s+)\S+', '$1: [redacted]' + $t = $t -replace '0x[0-9a-fA-F]{64}', '[redacted-hex]' + $t = $t -replace '[0-9a-fA-F]{64}', '[redacted-hex]' + return $t +} + +function Format-Eta([double]$Seconds) { + if ($Seconds -lt 60) { return '{0:N0}s' -f $Seconds } + if ($Seconds -lt 3600) { return '{0:N0} min' -f ($Seconds / 60) } + return '{0:N1} h' -f ($Seconds / 3600) +} + +# --------------------------------------------------------------------------- +# Commands +# --------------------------------------------------------------------------- + +function Invoke-Help { + @" +$($script:ScriptName) - Set up and manage verified Quantus Planck testnet mining on Windows. + +Working directory: $($script:MiningDir) +Config file: $($script:ConfigFile) + +Commands: + mine Set up if needed, start in background, and show status + setup [-Force] Interactive setup: download binaries, generate keys, write config + config show Show non-secret configuration + config set KEY VALUE Update an editable config key + start Start node + miner in the background + stop Stop node, miner, and related helper processes + restart Stop then start + restart-check Restart in background and verify both processes + status Show a redacted mining readiness summary + uninstall [-Force] Stop processes and remove $($script:MiningDir) (config, keys, binaries, logs) + help Show this help + +Editable config keys: $($script:EditableKeys -join ' ') + +Environment: + QUANTUS_MINING_DIR Override default working directory + QUANTUS_NODE_DATA_PATH Node --base-path (default: $env:LOCALAPPDATA\quantus-node) + QUANTUS_COMPATIBILITY_URL Official compatibility manifest ($($script:CompatibilityUrl)) + +One-time Windows note: + Windows Defender real-time scanning can stall chain sync on the node's + database. If status shows peers but the block number is not moving, run this + once in an elevated PowerShell (Run as administrator): + Add-MpPreference -ExclusionPath "$(Get-NodeDataPath)" +"@ | Write-Output +} + +function Invoke-Setup { + Test-Platform + Ensure-Dirs + Info "Platform: windows / x64 ($($script:NodeTarget))" + Info "Working directory: $($script:MiningDir)" + + if ((Test-Path $script:ConfigFile) -and -not $Force) { + Warn "Config already exists at $($script:ConfigFile)" + $confirm = Read-Host 'Overwrite existing setup? (y/N)' + if ($confirm -notmatch '^(y|yes)$') { Info 'Setup cancelled.'; return } + } + + Install-Binaries + + if (-not (Test-Path $script:NodeKeyPath)) { + Info 'Generating node P2P identity...' + & $script:NodeBin key generate-node-key --file $script:NodeKeyPath | Out-Null + } else { + Info "Using existing node key at $($script:NodeKeyPath)" + } + + $host_ = ($env:COMPUTERNAME.ToLowerInvariant() -replace '[^a-z0-9-]', '') + if ($host_.Length -gt 24) { $host_ = $host_.Substring(0, 24) } + $script:Config['NODE_NAME'] = if ($host_) { "quantus-$host_" } else { 'quantus-miner' } + Info "Node name: $($script:Config['NODE_NAME'])" + + New-WormholeKeys + Set-ResourceDefaults + $script:Config['NODE_VERSION'] = $script:Manifest['_nodeVersion'] + $script:Config['MINER_VERSION'] = $script:Manifest['_minerVersion'] + $script:Config['MINER_PROTOCOL'] = $script:MinerProtocol + $script:Config['CHAIN'] = $script:Manifest['_chain'] + Write-Config + + Write-Output '' + Info 'Setup complete.' + Info "Start mining with: $($script:ScriptName) mine" + Info 'Telemetry dashboard: https://telemetry.quantus.cat/' + Info 'If sync later stalls with peers connected, see the Defender note in: quantus-mining.ps1 help' +} + +function Invoke-Config([string[]]$Args) { + $sub = if ($Args.Count -gt 0) { $Args[0] } else { '' } + switch ($sub) { + 'show' { + if (-not (Test-Path $script:ConfigFile)) { Fail "Config not found. Run: $($script:ScriptName) setup" } + Get-Content $script:ConfigFile | Write-Output + } + 'set' { + if ($Args.Count -lt 3) { Fail "Usage: $($script:ScriptName) config set KEY VALUE" } + $key = $Args[1]; $value = $Args[2] + if ($script:EditableKeys -notcontains $key) { Fail "Key not editable via 'set': $key. Editable: $($script:EditableKeys -join ' ')" } + Read-Config | Out-Null + $script:Config[$key] = $value + Write-Config + Info "Updated $key=$value" + } + default { Fail "Usage: $($script:ScriptName) config show|set KEY VALUE" } + } +} + +function Invoke-Start { + Confirm-StartPrerequisites + if (Test-StackRunning) { + Fail "Mining stack already running. Run: $($script:ScriptName) stop" + } + $nodeLog = Join-Path $script:LogDir 'node.log' + $minerLog = Join-Path $script:LogDir 'miner.log' + + Info 'Starting quantus-node in background...' + $node = Start-Hidden $script:NodeBin (Get-NodeLaunchArgs) $nodeLog + Set-Content $script:NodePidFile $node.Id + Info "Node started (PID $($node.Id)). Log: $nodeLog" + + Wait-ForMinerServer ([int]$script:Config['MINER_LISTEN_PORT']) $nodeLog 120 + Wait-ForMinerAuthFiles 30 + + Info "Starting quantus-miner in background (log: $minerLog)..." + $miner = Start-Hidden $script:MinerBin (Get-MinerLaunchArgs) $minerLog + Start-Sleep 2 + if ($miner.HasExited) { + Warn "Last lines from ${minerLog}:" + Get-Content $minerLog -Tail 10 -ErrorAction SilentlyContinue | ForEach-Object { [Console]::Error.WriteLine($_) } + Fail "Miner exited immediately. Check $minerLog" + } + Set-Content $script:MinerPidFile $miner.Id + Info "Miner started (PID $($miner.Id))." + + Write-Output '' + Info 'Mining stack running in background.' + Info "Wait for full sync before expecting blocks (check $nodeLog or run: $($script:ScriptName) status)." + Info "Telemetry: https://telemetry.quantus.cat/ (search for '$($script:Config['NODE_NAME'])')" + Info "Stop with: $($script:ScriptName) stop" +} + +function Invoke-Stop { + $stopped = $false + if (Test-Path $script:ConfigFile) { Read-Config | Out-Null } + # Miner first, then node. + if (Stop-Tracked $script:MinerPidFile 'quantus-miner' 'quantus-miner') { $stopped = $true } + if (Stop-Tracked $script:NodePidFile 'quantus-node' 'quantus-node') { $stopped = $true } + if ($stopped) { Info 'Mining stack stopped.' } else { Warn "No running quantus-node or quantus-miner processes found under $($script:MiningDir)." } +} + +function Invoke-Status { + if (-not (Test-Path $script:ConfigFile)) { Fail "Mining is not configured. Run: $($script:ScriptName) mine" } + $c = Read-Config + $nodeState = if (Test-ProcessAlive (Read-PidFile $script:NodePidFile) 'quantus-node') { 'Running' } else { 'Stopped' } + $minerState = if (Test-ProcessAlive (Read-PidFile $script:MinerPidFile) 'quantus-miner') { 'Running' } else { 'Stopped' } + + $syncState = 'Unknown'; $syncDetail = '' + $health = Invoke-NodeRpc 'system_health' + if ($null -ne $health) { + $syncState = if ($health.isSyncing) { 'Syncing' } else { 'Synced' } + $peers = [int]$health.peers + $s1 = Invoke-NodeRpc 'system_syncState' + if ($null -ne $s1 -and $health.isSyncing) { + $cur1 = [long]$s1.currentBlock; $high = [long]$s1.highestBlock + Start-Sleep 5 + $s2 = Invoke-NodeRpc 'system_syncState' + $cur2 = if ($null -ne $s2) { [long]$s2.currentBlock } else { $cur1 } + $rate = ($cur2 - $cur1) / 5.0 + $pct = if ($high -gt 0) { 100.0 * $cur2 / $high } else { 0 } + if ($rate -gt 0) { + $syncDetail = ('block {0:N0} of {1:N0} ({2:N1}%), {3:N0} blocks/s, about {4} left, {5} peers' -f $cur2, $high, $pct, $rate, (Format-Eta (($high - $cur2) / $rate)), $peers) + } elseif ($peers -gt 0) { + $syncDetail = ('block {0:N0} of {1:N0} ({2:N1}%), NOT ADVANCING with {3} peers' -f $cur2, $high, $pct, $peers) + $syncState = 'Stalled' + } else { + $syncDetail = ('block {0:N0} of {1:N0}, no peers yet' -f $cur2, $high) + } + } elseif (-not $health.isSyncing) { + $syncDetail = "$peers peers" + } + } + + $hashRate = 'Waiting for miner output'; $latest = '' + $minerLog = Join-Path $script:LogDir 'miner.log' + if (Test-Path $minerLog) { + $line = Select-String -Path $minerLog -Pattern 'hash.?rate|\d+(\.\d+)?\s*[kmgKMG]?H/s' | Select-Object -Last 1 + if ($line) { $latest = Hide-Secrets $line.Line; $hashRate = $latest } + } + + $overall = 'STARTING' + if ($nodeState -eq 'Running' -and $minerState -eq 'Running' -and $syncState -eq 'Synced' -and $latest) { $overall = 'MINING' } + elseif ($nodeState -eq 'Stopped' -or $minerState -eq 'Stopped') { $overall = 'STOPPED' } + + @" + +Quantus mining status +Overall: $overall +Network: Planck testnet (tokens have no monetary value) +Compatibility: node $($c['NODE_VERSION']) + miner $($c['MINER_VERSION']) +Node: $nodeState +Sync: $syncState$(if ($syncDetail) { ", $syncDetail" }) +Miner: $minerState +Hash rate: $hashRate +Reward address: $($c['WORMHOLE_ADDRESS']) +Node name: $($c['NODE_NAME']) +Telemetry: https://telemetry.quantus.cat/ (search for $($c['NODE_NAME'])) +Restart recovery: Run $($script:ScriptName) restart-check +"@ | Write-Output + + switch ($overall) { + 'MINING' { Info 'Success: the node is synced and the miner is reporting hash rate.' } + 'STOPPED' { Info "Recovery: run $($script:ScriptName) mine to start the verified pair." } + default { + if ($syncState -eq 'Stalled') { + Info 'Recovery: sync has peers but is not advancing. Windows Defender is the usual cause. Run once, in an elevated PowerShell:' + Info " Add-MpPreference -ExclusionPath `"$(Get-NodeDataPath)`"" + Info "Then run: $($script:ScriptName) restart-check" + } else { + Info "Recovery: wait for sync, then run $($script:ScriptName) status again." + } + } + } +} + +function Invoke-Mine { + if (-not (Test-Path $script:ConfigFile)) { Invoke-Setup } + Read-Config | Out-Null + if (-not (Test-StackRunning)) { Invoke-Start } + Invoke-Status +} + +function Invoke-RestartCheck { + if (-not (Test-Path $script:ConfigFile)) { Fail "Mining is not configured. Run: $($script:ScriptName) mine" } + Invoke-Stop + Invoke-Start + if ((Test-ProcessAlive (Read-PidFile $script:NodePidFile) 'quantus-node') -and (Test-ProcessAlive (Read-PidFile $script:MinerPidFile) 'quantus-miner')) { + Info 'Restart recovery: PASSED' + Invoke-Status + return + } + Fail "Restart recovery failed. Run $($script:ScriptName) status, then apply the single recovery action shown." +} + +function Invoke-Uninstall { + $chainData = Get-NodeDataPath + if (-not (Test-Path $script:MiningDir)) { Warn "Nothing to uninstall at $($script:MiningDir)."; return } + if (-not $Force) { + Write-Output '' + Warn "This permanently removes $($script:MiningDir), including:" + Write-Output ' - mining.conf (public settings and reward address)' + Write-Output ' - rewards-inner-hash (owner-only reward preimage)' + Write-Output ' - node_key.p2p' + Write-Output ' - downloaded binaries and logs' + Write-Output '' + Warn 'Ensure your 24-word seed phrase is backed up before continuing.' + $confirm = Read-Host 'Uninstall Quantus mining setup? (y/N)' + if ($confirm -notmatch '^(y|yes)$') { Info 'Uninstall cancelled.'; return } + } + try { Invoke-Stop } catch { } + Info "Removing $($script:MiningDir)..." + Remove-Item $script:MiningDir -Recurse -Force + Info 'Uninstall complete.' + if (Test-Path $chainData) { + Info "Chain sync data was not removed: $chainData" + Info 'Delete it manually to reclaim disk space.' + } +} + +# --------------------------------------------------------------------------- +# Main. Dot-source the file to load the functions without running a command. +# --------------------------------------------------------------------------- + +if ($MyInvocation.InvocationName -ne '.') { + switch ($Command) { + 'mine' { Invoke-Mine } + 'setup' { Invoke-Setup } + 'config' { Invoke-Config $Rest } + 'start' { Invoke-Start } + 'stop' { Invoke-Stop } + 'restart' { Invoke-Stop; Invoke-Start } + 'restart-check' { Invoke-RestartCheck } + 'status' { Invoke-Status } + 'uninstall' { Invoke-Uninstall } + { $_ -in 'help', '-h', '--help' } { Invoke-Help } + default { Fail "Unknown command: $Command. Run: $($script:ScriptName) help" } + } +} diff --git a/static/scripts/quantus-mining.ps1.sha256 b/static/scripts/quantus-mining.ps1.sha256 new file mode 100644 index 0000000..775c3e5 --- /dev/null +++ b/static/scripts/quantus-mining.ps1.sha256 @@ -0,0 +1 @@ +98479626b238df0faf9bcce9032e380ca3681fe76c17e5f1ce7bebd90b5c613a quantus-mining.ps1 diff --git a/static/scripts/quantus-mining.sh b/static/scripts/quantus-mining.sh index ae654a3..319fcef 100755 --- a/static/scripts/quantus-mining.sh +++ b/static/scripts/quantus-mining.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# quantus-mining.sh — Set up and manage Quantus Planck testnet mining. +# quantus-mining.sh - Set up and manage verified Quantus Planck testnet mining. # # Supports macOS, Linux, and WSL2. Requires bash, curl, and tar. # @@ -8,11 +8,13 @@ # Config file: ~/quantus-mining/mining.conf (mode 600) # # Usage: +# ./quantus-mining.sh mine # ./quantus-mining.sh setup [--force] # ./quantus-mining.sh config show|set KEY VALUE|edit # ./quantus-mining.sh start [-d|--detach] # ./quantus-mining.sh start-node|start-miner # ./quantus-mining.sh stop|restart [-d|--detach] +# ./quantus-mining.sh status|restart-check # ./quantus-mining.sh uninstall [--force] # ./quantus-mining.sh help # @@ -31,15 +33,23 @@ readonly MINER_BIN="${BIN_DIR}/quantus-miner" readonly NODE_PID_FILE="${MINING_DIR}/node.pid" readonly MINER_PID_FILE="${MINING_DIR}/miner.pid" readonly NODE_KEY_PATH="${MINING_DIR}/node_key.p2p" +readonly INNER_HASH_FILE="${MINING_DIR}/rewards-inner-hash" +readonly COMPATIBILITY_FILE="${MINING_DIR}/mining-compatibility.json" +readonly COMPATIBILITY_URL="${QUANTUS_COMPATIBILITY_URL:-https://docs.quantus.com/mining-compatibility.json}" readonly CHAIN_REPO="Quantus-Network/chain" readonly MINER_REPO="Quantus-Network/quantus-miner" -readonly EDITABLE_KEYS="NODE_NAME CPU_WORKERS GPU_DEVICES MINER_LISTEN_PORT CHAIN NODE_VERSION MINER_VERSION" +readonly EDITABLE_KEYS="NODE_NAME CPU_WORKERS GPU_DEVICES MINER_LISTEN_PORT" OS="" ARCH="" +PLATFORM_KEY="" NODE_TARGET="" MINER_ASSET="" +NODE_DOWNLOAD_URL="" +NODE_DOWNLOAD_SHA256="" +MINER_DOWNLOAD_URL="" +MINER_DOWNLOAD_SHA256="" DOCKER_COMPOSE="" NODE_LAUNCH_ARGS=() @@ -70,7 +80,7 @@ detect_platform() { Linux*) OS="linux" ;; Darwin*) OS="macos" ;; *) - die "Unsupported operating system: $(uname -s). Use macOS, Linux, or WSL2." + die "Unsupported operating system: $(uname -s). Use macOS, Linux, or WSL2. On native Windows, use quantus-mining.ps1 in PowerShell." ;; esac @@ -78,9 +88,11 @@ detect_platform() { x86_64|amd64) ARCH="x86_64" if [ "$OS" = "linux" ]; then + PLATFORM_KEY="LinuxX8664" NODE_TARGET="x86_64-unknown-linux-gnu" MINER_ASSET="quantus-miner-linux-x86_64" else + PLATFORM_KEY="DarwinX8664" NODE_TARGET="x86_64-apple-darwin" MINER_ASSET="quantus-miner-macos-x86_64" fi @@ -88,9 +100,11 @@ detect_platform() { arm64|aarch64) ARCH="arm64" if [ "$OS" = "linux" ]; then + PLATFORM_KEY="LinuxArm64" NODE_TARGET="aarch64-unknown-linux-gnu" MINER_ASSET="" else + PLATFORM_KEY="DarwinArm64" NODE_TARGET="aarch64-apple-darwin" MINER_ASSET="quantus-miner-macos-aarch64" fi @@ -260,21 +274,116 @@ tolower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' } -fetch_latest_tag() { - local repo="$1" - local release_json tag +manifest_string() { + local file="$1" key="$2" + sed -n "s/^[[:space:]]*\"${key}\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\"[,[:space:]]*$/\1/p" "$file" \ + | head -n 1 +} + +require_manifest_string() { + local file="$1" key="$2" value + value="$(manifest_string "$file" "$key")" + [ -n "$value" ] || die "Compatibility manifest is missing ${key}. No files were installed." + printf '%s' "$value" +} + +sha256_file() { + local file="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$file" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$file" | awk '{print $1}' + else + die "No SHA-256 tool found. Install sha256sum (Linux) or use macOS shasum, then retry." + fi +} + +verify_sha256() { + local file="$1" expected="$2" actual + case "$expected" in + *[!0-9a-f]*|'') die "Invalid SHA-256 value for $(basename "$file"). No files were installed." ;; + esac + [ "${#expected}" -eq 64 ] \ + || die "Invalid SHA-256 length for $(basename "$file"). No files were installed." + + actual="$(sha256_file "$file")" + if [ "$actual" != "$expected" ]; then + die "Checksum verification failed for $(basename "$file"). +Expected: ${expected} +Actual: ${actual} +The file was not installed. Delete the download and retry." + fi + info "Verified SHA-256: $(basename "$file")" +} - release_json="$(curl -fsSL "https://api.github.com/repos/${repo}/releases/latest")" \ - || die "Failed to fetch latest release for ${repo}" +validate_release_url() { + local url="$1" repo="$2" version="$3" + case "$url" in + "https://github.com/${repo}/releases/download/${version}/"*) ;; + *) die "Compatibility manifest contains an unexpected download URL for ${repo}. No files were installed." ;; + esac +} + +load_compatibility_manifest() { + local file="$1" status network_kind token_value evidence + local node_url_key node_sha_key miner_url_key miner_sha_key + + [ -s "$file" ] || die "Compatibility manifest not found at ${file}. Run: ${SCRIPT_NAME} setup --force" - tag="$(printf '%s' "$release_json" | grep -o '"tag_name": "[^"]*"' | head -n 1 | cut -d'"' -f4)" - [ -n "$tag" ] || die "Could not determine latest release tag for ${repo}" - printf '%s' "$tag" + status="$(require_manifest_string "$file" status)" + [ "$status" = "supported" ] \ + || die "Mining compatibility status is '${status}', not 'supported'. Nothing will be installed or started." + + CHAIN="$(require_manifest_string "$file" networkId)" + network_kind="$(require_manifest_string "$file" networkKind)" + token_value="$(require_manifest_string "$file" tokenValue)" + NODE_VERSION="$(require_manifest_string "$file" nodeVersion)" + MINER_VERSION="$(require_manifest_string "$file" minerVersion)" + MINER_PROTOCOL="$(require_manifest_string "$file" minerProtocol)" + evidence="$(require_manifest_string "$file" compatibilityEvidenceUrl)" + + [ "$CHAIN" = "planck" ] && [ "$network_kind" = "testnet" ] && [ "$token_value" = "none" ] \ + || die "This installer is restricted to the Planck testnet. The manifest requested a different network." + [ "$MINER_PROTOCOL" = "quantus-miner/2" ] \ + || die "Unsupported miner protocol '${MINER_PROTOCOL}'. Nothing will be installed or started." + case "$evidence" in + https://github.com/Quantus-Network/quantus-miner/releases/tag/*) ;; + *) die "Compatibility evidence URL is not an official Quantus miner release." ;; + esac + + node_url_key="node${PLATFORM_KEY}Url" + node_sha_key="node${PLATFORM_KEY}Sha256" + miner_url_key="miner${PLATFORM_KEY}Url" + miner_sha_key="miner${PLATFORM_KEY}Sha256" + NODE_DOWNLOAD_URL="$(require_manifest_string "$file" "$node_url_key")" + NODE_DOWNLOAD_SHA256="$(require_manifest_string "$file" "$node_sha_key")" + MINER_DOWNLOAD_URL="$(require_manifest_string "$file" "$miner_url_key")" + MINER_DOWNLOAD_SHA256="$(require_manifest_string "$file" "$miner_sha_key")" + + validate_release_url "$NODE_DOWNLOAD_URL" "$CHAIN_REPO" "$NODE_VERSION" + validate_release_url "$MINER_DOWNLOAD_URL" "$MINER_REPO" "$MINER_VERSION" + case "$(basename "$NODE_DOWNLOAD_URL")" in + "quantus-node-${NODE_VERSION}-${NODE_TARGET}.tar.gz") ;; + *) die "Node asset does not match ${OS}/${ARCH}. No files were installed." ;; + esac + [ "$(basename "$MINER_DOWNLOAD_URL")" = "$MINER_ASSET" ] \ + || die "Miner asset does not match ${OS}/${ARCH}. No files were installed." +} + +fetch_compatibility_manifest() { + local temp_file="${COMPATIBILITY_FILE}.download" + info "Fetching the supported Planck release pair..." + curl --proto '=https' --tlsv1.2 -fsSL "$COMPATIBILITY_URL" -o "$temp_file" \ + || die "Could not download ${COMPATIBILITY_URL}. Check your connection and retry." + load_compatibility_manifest "$temp_file" + mv "$temp_file" "$COMPATIBILITY_FILE" + chmod 600 "$COMPATIBILITY_FILE" + info "Supported pair: node ${NODE_VERSION} + miner ${MINER_VERSION} (${MINER_PROTOCOL})" } # Miner protocol this script can drive: # auth = Ready { token } + TLS pin (quantus-miner/2) -# legacy = unauthenticated Ready (pre-auth releases, e.g. node v0.9.0 / miner v3.3.1) +# legacy = unauthenticated Ready from older release pairs MINER_PROTOCOL="" incompatible_miner_pair_die() { @@ -284,14 +393,8 @@ incompatible_miner_pair_die() { Miner --auth-token-file / --tls-cert-sha256-file: ${miner_auth} Do not mix a release that requires Ready { token } (quantus-miner/2) with one that does not. -GitHub latest tags are published independently — pin a matching pair: - - NODE_VERSION= - MINER_VERSION= - -in ${CONFIG_FILE}, or re-run setup after coordinated releases: - https://github.com/${CHAIN_REPO}/releases - https://github.com/${MINER_REPO}/releases" +Run ${SCRIPT_NAME} setup --force to reinstall the supported pair from: + ${COMPATIBILITY_URL}" } classify_miner_protocol() { @@ -342,18 +445,22 @@ maybe_wait_for_miner_auth_files() { } download_node_binary() { - local tag="$1" - local asset="quantus-node-${tag}-${NODE_TARGET}.tar.gz" - local url="https://github.com/${CHAIN_REPO}/releases/download/${tag}/${asset}" + local tag="$1" url="$2" expected_sha256="$3" + local asset local temp_dir asset_path + asset="$(basename "$url")" + info "Downloading quantus-node ${tag} for ${NODE_TARGET}..." temp_dir="$(mktemp -d)" asset_path="${temp_dir}/${asset}" - curl -fsSL "$url" -o "$asset_path" || { rm -rf "$temp_dir"; die "Failed to download ${url}"; } + curl --proto '=https' --tlsv1.2 -fsSL "$url" -o "$asset_path" \ + || { rm -rf "$temp_dir"; die "Failed to download ${url}"; } + verify_sha256 "$asset_path" "$expected_sha256" - tar -xzf "$asset_path" -C "$temp_dir" + tar -xzf "$asset_path" -C "$temp_dir" \ + || { rm -rf "$temp_dir"; die "Could not extract ${asset}. Delete the download and retry."; } if [ ! -f "${temp_dir}/quantus-node" ]; then rm -rf "$temp_dir" die "quantus-node not found in archive" @@ -366,162 +473,81 @@ download_node_binary() { } download_miner_binary() { - local tag="$1" - local url="https://github.com/${MINER_REPO}/releases/download/${tag}/${MINER_ASSET}" + local tag="$1" url="$2" expected_sha256="$3" + local temp_file info "Downloading quantus-miner ${tag} (${MINER_ASSET})..." - curl -fsSL "$url" -o "$MINER_BIN" || die "Failed to download ${url}" + temp_file="${MINER_BIN}.download" + curl --proto '=https' --tlsv1.2 -fsSL "$url" -o "$temp_file" \ + || die "Failed to download ${url}" + verify_sha256 "$temp_file" "$expected_sha256" + mv "$temp_file" "$MINER_BIN" make_executable "$MINER_BIN" info "Installed quantus-miner to ${MINER_BIN}" } -read_conf_value() { - local key="$1" - local raw="" - [ -f "$CONFIG_FILE" ] || return 0 - raw="$(grep -E "^${key}=" "$CONFIG_FILE" 2>/dev/null | head -n 1 | cut -d= -f2- || true)" - raw="${raw#\"}" - raw="${raw%\"}" - printf '%s' "$raw" -} - -# Env NODE_VERSION / MINER_VERSION override mining.conf. Empty pins fetch GitHub latest. -load_version_pins() { - local conf_node conf_miner - conf_node="$(read_conf_value NODE_VERSION)" - conf_miner="$(read_conf_value MINER_VERSION)" - if [ -z "${NODE_VERSION:-}" ] && [ -n "$conf_node" ]; then - NODE_VERSION="$conf_node" - fi - if [ -z "${MINER_VERSION:-}" ] && [ -n "$conf_miner" ]; then - MINER_VERSION="$conf_miner" - fi -} - download_binaries() { - local force="${1:-false}" - local node_tag miner_tag - - load_version_pins - - if [ "$force" = "true" ] || [ ! -x "$NODE_BIN" ]; then - node_tag="${NODE_VERSION:-}" - [ -n "$node_tag" ] || node_tag="$(fetch_latest_tag "$CHAIN_REPO")" - download_node_binary "$node_tag" - else - node_tag="${NODE_VERSION:-}" - [ -n "$node_tag" ] || node_tag="$(fetch_latest_tag "$CHAIN_REPO")" - info "Using existing quantus-node at ${NODE_BIN}" - fi - - if [ "$force" = "true" ] || [ ! -x "$MINER_BIN" ]; then - miner_tag="${MINER_VERSION:-}" - [ -n "$miner_tag" ] || miner_tag="$(fetch_latest_tag "$MINER_REPO")" - download_miner_binary "$miner_tag" - else - miner_tag="${MINER_VERSION:-}" - [ -n "$miner_tag" ] || miner_tag="$(fetch_latest_tag "$MINER_REPO")" - info "Using existing quantus-miner at ${MINER_BIN}" - fi - - NODE_VERSION="$node_tag" - MINER_VERSION="$miner_tag" + fetch_compatibility_manifest + download_node_binary "$NODE_VERSION" "$NODE_DOWNLOAD_URL" "$NODE_DOWNLOAD_SHA256" + download_miner_binary "$MINER_VERSION" "$MINER_DOWNLOAD_URL" "$MINER_DOWNLOAD_SHA256" detect_binary_miner_protocol + [ "$MINER_PROTOCOL" = "auth" ] \ + || die "The downloaded binaries do not implement the manifest protocol quantus-miner/2. Nothing will be started." } parse_wormhole_output() { local output="$1" - local line WORMHOLE_ADDRESS="$(printf '%s\n' "$output" | grep -E '^Address:' | head -n 1 | awk '{print $2}')" INNER_HASH="$(printf '%s\n' "$output" | grep -E '^Inner [Hh]ash:' | head -n 1 | awk '{print $3}')" if [ -z "$INNER_HASH" ]; then INNER_HASH="$(printf '%s\n' "$output" | grep -E '^inner_hash:' | head -n 1 | awk '{print $2}')" fi - WORMHOLE_SECRET="" - line="$(printf '%s\n' "$output" | grep -E '^Secret:' | head -n 1 || true)" - if [ -n "$line" ]; then - WORMHOLE_SECRET="$(printf '%s' "$line" | awk '{print $2}')" - fi - - line="$(printf '%s\n' "$output" | grep -E '^Secret phrase:' | head -n 1 || true)" - if [ -n "$line" ]; then - WORMHOLE_SECRET_PHRASE="${line#*Secret phrase: }" - else - WORMHOLE_SECRET_PHRASE="" - fi - [ -n "$WORMHOLE_ADDRESS" ] || die "Could not parse wormhole Address from keygen output" [ -n "$INNER_HASH" ] || die "Could not parse Inner Hash from keygen output" } generate_wormhole_keys() { - local choice output mnemonic + local output mnemonic echo "" - echo "Wormhole address generation:" - echo " [1] Derive from existing 24-word wallet mnemonic (recommended)" - echo " [2] Generate a fresh keypair" - read -r -p "Enter choice (1/2) [1]: " choice - choice="${choice:-1}" - - case "$choice" in - 1) - echo "Enter your 24-word mnemonic (input hidden):" - read -r -s mnemonic - echo "" - [ -n "$mnemonic" ] || die "Mnemonic cannot be empty" - output="$(printf '%s\n' "$mnemonic" | wormhole_keygen --words)" - ;; - 2) - output="$(wormhole_keygen)" - ;; - *) - die "Invalid choice: $choice" - ;; - esac + info "Wallet step: enter your existing Quantus 24-word recovery phrase locally." + info "Input is hidden and is not written to disk, logs, command arguments, or network requests." + info "Never paste a recovery phrase into chat or a support ticket." + read -r -s -p "Recovery phrase: " mnemonic + echo "" + [ -n "$mnemonic" ] || die "Recovery phrase cannot be empty. Open your Quantus wallet backup and retry." + output="$(printf '%s\n' "$mnemonic" | wormhole_keygen --words)" + unset mnemonic parse_wormhole_output "$output" + unset output + + printf '%s\n' "$INNER_HASH" > "$INNER_HASH_FILE" + chmod 600 "$INNER_HASH_FILE" echo "" - echo "Wormhole keypair generated. Save these values securely:" - echo "$output" - echo "" - warn "Back up your 24-word seed phrase. Loss means loss of mining rewards." + info "Reward address: ${WORMHOLE_ADDRESS}" + info "Your recovery phrase was not saved. Keep your existing offline backup." } -prompt_resource_allocation() { - local cores has_gpu choice default_workers +configure_resource_defaults() { + local cores default_workers cores="$(cpu_count)" - echo "" - echo "This machine has ${cores} CPU cores." - echo "GPU mining is strongly recommended (~500-1000 MH/s vs ~15 MH/s per CPU worker)." - read -r -p "Do you have a GPU available for mining? (y/N): " has_gpu - - case "$(tolower "$has_gpu")" in - y|yes) - GPU_DEVICES=1 - CPU_WORKERS=0 - info "Default: GPU mining with --gpu-devices 1 --cpu-workers 0" - ;; - *) - default_workers=$((cores - 2)) - [ "$default_workers" -lt 1 ] && default_workers=1 - GPU_DEVICES=0 - CPU_WORKERS="$default_workers" - info "Default: CPU-only mining with --cpu-workers ${CPU_WORKERS} (leaving 2 cores for OS/node)" - ;; - esac - - read -r -p "CPU workers [${CPU_WORKERS}]: " choice - if [ -n "$choice" ]; then - CPU_WORKERS="$choice" - fi - - read -r -p "GPU devices [${GPU_DEVICES}]: " choice - if [ -n "$choice" ]; then - GPU_DEVICES="$choice" + default_workers=$((cores - 2)) + [ "$default_workers" -lt 1 ] && default_workers=1 + + if [ "$OS" = "macos" ] \ + || command -v nvidia-smi >/dev/null 2>&1 \ + || command -v vulkaninfo >/dev/null 2>&1; then + GPU_DEVICES=1 + CPU_WORKERS=0 + info "Mining resources: one detected GPU, CPU reserved for the node." + else + GPU_DEVICES=0 + CPU_WORKERS="$default_workers" + info "Mining resources: ${CPU_WORKERS} CPU workers, two cores reserved when available." fi } @@ -532,12 +558,11 @@ write_config() { GPU_DEVICES="${GPU_DEVICES:-0}" cat > "$CONFIG_FILE" </dev/null)"; then + case "$health" in + *'"isSyncing":false'*) sync_state="Synced" ;; + *'"isSyncing":true'*) sync_state="Syncing" ;; + esac + fi + + if [ -f "${LOG_DIR}/miner.log" ]; then + latest_rate="$(grep -Ei 'hash.?rate|[0-9]+([.][0-9]+)?[[:space:]]*[kmg]?h/s' "${LOG_DIR}/miner.log" 2>/dev/null \ + | tail -n 1 | redact_sensitive_stream || true)" + [ -z "$latest_rate" ] || hash_rate="$latest_rate" + fi + + if [ "$node_state" = "Running" ] && [ "$miner_state" = "Running" ] \ + && [ "$sync_state" = "Synced" ] && [ -n "$latest_rate" ]; then + overall="MINING" + elif [ "$node_state" = "Stopped" ] || [ "$miner_state" = "Stopped" ]; then + overall="STOPPED" + fi + + cat <