|
| 1 | +# Pool operator guide (pps-thunder-classic) |
| 2 | + |
| 3 | +Everything you need to run this pool day-to-day. Assumes the branch |
| 4 | +already deployed (see `PPS_THUNDER.md` and `CLASSIC_PAYOUTS.md` for |
| 5 | +background on why the design looks like this). |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Quick reference |
| 10 | + |
| 11 | +| what | where | credentials | |
| 12 | +| --- | --- | --- | |
| 13 | +| Public dashboard | http://45.33.100.24:8081/ | none | |
| 14 | +| **Admin dashboard** | **http://45.33.100.24:8081/admin** | **`admin` / `4DqSK1d6jkefposLEw5DEP5luPT`** | |
| 15 | +| Admin JSON API | http://45.33.100.24:8081/api/admin/summary | same basic auth | |
| 16 | +| Stratum (miner endpoint) | `stratum+tcp://45.33.100.24:3334` | username = Thunder base58 address | |
| 17 | +| SSH | `root@45.33.100.24` | `~/.ssh/id_epitetus` | |
| 18 | + |
| 19 | +The admin password is also stashed at `/root/simplepool-admin-cred.txt` |
| 20 | +on the box (root-only). To rotate, edit |
| 21 | +`/etc/systemd/system/simplepool-dashboard.service.d/pps-thunder.conf` |
| 22 | +and `systemctl daemon-reload && systemctl restart simplepool-dashboard`. |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## Where things live on the box |
| 27 | + |
| 28 | +- **Root**: `/home/forknet/pps-thunder-test/` |
| 29 | + - `simplepool/` — this repo, checked out at `pps-thunder-classic` |
| 30 | + - `data/shares.db` — pool state (SQLite, WAL mode) |
| 31 | + - `data/archive/` — old DB snapshots from mode switches |
| 32 | + - `logs/` — pool + payout + Thunder logs (plus stashed Thunder mnemonic) |
| 33 | + - `thunder-data/` — Thunder node chain data |
| 34 | + |
| 35 | +- **Services** (all `systemctl status <name>`): |
| 36 | + - `simplepool.service` — the stratum proxy (managed manually right now; |
| 37 | + launched via `nohup` from the deploy). Systemd unit exists but not |
| 38 | + always enabled. |
| 39 | + - `simplepool-payout.service` — Node payout worker |
| 40 | + - `simplepool-dashboard.service` — Node public + admin dashboards |
| 41 | + |
| 42 | +- **Config**: `/home/forknet/pps-thunder-test/simplepool/proxy.conf` |
| 43 | + (gitignored). Current `pool_mode = pps-classic`. |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +## The value flow, end to end |
| 48 | + |
| 49 | +``` |
| 50 | + ┌──────────┐ diff × 1000 sats |
| 51 | + │ miner │─────────────────────────┐ |
| 52 | + │ ASIC │ ▼ |
| 53 | + └────┬─────┘ ┌────────────────┐ |
| 54 | + │ stratum share │ pps_credits │ |
| 55 | + │ │ accrued_sats │ |
| 56 | + ▼ └────────┬───────┘ |
| 57 | + ┌───────────┐ block payout worker |
| 58 | + │ simplepool│ found ↓ every 30s |
| 59 | + │ :3334 │──────►coinbase ──┘ IF Thunder reserve has funds |
| 60 | + └───────────┘ │ |
| 61 | + │ pays pool_btc_address (BTC P2WPKH) |
| 62 | + │ |
| 63 | + ▼ |
| 64 | + ┌───────────────────┐ |
| 65 | + │ pool BTC wallet │ accumulates BTC over blocks |
| 66 | + │ │ |
| 67 | + └────────┬──────────┘ |
| 68 | + │ operator triggers deposit |
| 69 | + │ (admin dashboard button OR grpcurl) |
| 70 | + ▼ |
| 71 | + ┌──────────────────────┐ |
| 72 | + │ BIP300 deposit tx │ |
| 73 | + │ CreateDepositTx │ mainchain → sidechain |
| 74 | + └────────┬─────────────┘ |
| 75 | + │ Ctip updates on enforcer |
| 76 | + ▼ |
| 77 | + ┌──────────────────────┐ |
| 78 | + │ Thunder wallet │ |
| 79 | + │ available_sats > 0 │ |
| 80 | + └────────┬─────────────┘ |
| 81 | + │ payout worker's next tick |
| 82 | + │ thunder.transfer(miner_addr, owed, fee) |
| 83 | + ▼ |
| 84 | + ┌──────────────────────┐ |
| 85 | + │ miner Thunder wallet │ |
| 86 | + └──────────────────────┘ |
| 87 | +``` |
| 88 | + |
| 89 | +Everything before `pool_btc_address` runs automatically. Everything |
| 90 | +after is either automatic (payout worker) or one operator action |
| 91 | +per deposit (moving BTC into Thunder). |
| 92 | + |
| 93 | +--- |
| 94 | + |
| 95 | +## Daily operations |
| 96 | + |
| 97 | +### 1. Health check (30 seconds) |
| 98 | + |
| 99 | +Open the admin dashboard. Look at the top three cards: |
| 100 | + |
| 101 | +- **Thunder reserve** — how much you can pay out right now |
| 102 | +- **PPS ledger totals** — how much you *owe* (owed = accrued − paid) |
| 103 | +- **⚠ Reserve is short** banner — appears when owed > available; means |
| 104 | + the payout worker will keep skipping ticks until you deposit more BTC |
| 105 | + |
| 106 | +If you see a `⚠` banner AND owed is significant, do a deposit (below). |
| 107 | + |
| 108 | +Also check the **In-flight payouts** card — should be empty. Any row |
| 109 | +with a set `txid` means a payout crashed mid-flight and needs manual |
| 110 | +reconciliation. |
| 111 | + |
| 112 | +### 2. Deposit BTC into Thunder (when reserve is short) |
| 113 | + |
| 114 | +**Currently manual — no admin button yet.** The runbook: |
| 115 | + |
| 116 | +```sh |
| 117 | +ssh -i ~/.ssh/id_epitetus root@45.33.100.24 |
| 118 | + |
| 119 | +# 1. Get a Thunder-owned deposit address (formatted for the mainchain wallet) |
| 120 | +TCLI=/home/forknet/forknet-software/thunder-rust/target/debug/thunder_app_cli |
| 121 | +POOL_ADDR=$(sudo -u forknet $TCLI get-new-address) |
| 122 | +DEP=$(sudo -u forknet $TCLI format-deposit-address "$POOL_ADDR") |
| 123 | +echo "deposit target: $DEP" |
| 124 | + |
| 125 | +# 2. Trigger the deposit via the enforcer's wallet. AMOUNT + FEE are yours to pick. |
| 126 | +GRPCURL=/home/forknet/forknet-software/grpcurl |
| 127 | +AMOUNT_SATS=1000000 # 0.01 BTC = 1_000_000 sats |
| 128 | +FEE_SATS=1000 |
| 129 | +$GRPCURL -plaintext \ |
| 130 | + -d "{\"sidechain_id\":9,\"address\":\"$DEP\",\"value_sats\":$AMOUNT_SATS,\"fee_sats\":$FEE_SATS}" \ |
| 131 | + 127.0.0.1:50051 cusf.mainchain.v1.WalletService/CreateDepositTransaction |
| 132 | + |
| 133 | +# 3. Wait for a mainchain block. If you want to force one on the test: |
| 134 | +$GRPCURL -plaintext -d '{"blocks":1}' \ |
| 135 | + 127.0.0.1:50051 cusf.mainchain.v1.WalletService/GenerateBlocks |
| 136 | + |
| 137 | +# 4. Confirm the Ctip moved |
| 138 | +$GRPCURL -plaintext -d '{"sidechain_number":9}' \ |
| 139 | + 127.0.0.1:50051 cusf.mainchain.v1.ValidatorService/GetCtip |
| 140 | +``` |
| 141 | + |
| 142 | +**About the "enforcer's wallet"**: the enforcer runs its own on-box BTC |
| 143 | +wallet, seeded during the setup phase. Its balance is visible via |
| 144 | +`WalletService/GetBalance`. Right now it's ~1 BTC because we mined some |
| 145 | +funding blocks during activation. **Long-term**, you want the pool |
| 146 | +coinbase to accumulate straight into an enforcer-wallet-owned address — |
| 147 | +that means changing `pool_btc_address` in `proxy.conf` to an address |
| 148 | +generated by `WalletService/CreateNewAddress` on the enforcer. Until |
| 149 | +that's done, the operator's own BTC (at the current `pool_btc_address`) |
| 150 | +sits in an external wallet the enforcer can't spend directly. |
| 151 | + |
| 152 | +### 3. Watch payouts land |
| 153 | + |
| 154 | +After a deposit completes: |
| 155 | + |
| 156 | +```sh |
| 157 | +# tail the payout worker |
| 158 | +sudo journalctl -u simplepool-payout.service -f |
| 159 | +# or read the log directly: |
| 160 | +tail -f /home/forknet/pps-thunder-test/logs/payout.log |
| 161 | +``` |
| 162 | + |
| 163 | +You should see lines like: |
| 164 | +``` |
| 165 | +payout: 3Z6z1hPySN…rig1 -> 3Z6z1hPySN… 96085 sats txid=… |
| 166 | +``` |
| 167 | + |
| 168 | +Refresh the admin dashboard — `owed` should drop, `paid` should rise, |
| 169 | +`in-flight` should stay empty. |
| 170 | + |
| 171 | +**Known Thunder-side gotcha:** for `payout: reserve short — available=0` |
| 172 | +to actually go away, Thunder itself has to mine a sidechain block (BMM) |
| 173 | +that recognizes the deposit. Deposits are always visible on the |
| 174 | +enforcer's Ctip, but Thunder's own wallet balance stays 0 until BMM |
| 175 | +runs. On this box Thunder's `mine` RPC is currently blocking without |
| 176 | +producing a sidechain block — likely a BMM-side infrastructure issue |
| 177 | +that needs investigation. In the meantime, the pool correctly logs the |
| 178 | +short-reserve state and does not double-pay or lose accrual data. |
| 179 | + |
| 180 | +### 4. Reconciling stuck payouts |
| 181 | + |
| 182 | +If the admin dashboard's **In-flight payouts** card is non-empty, each |
| 183 | +row means a payout crashed between broadcast and DB finalize. Two cases: |
| 184 | + |
| 185 | +- **`txid = —`** (no txid): the broadcast never happened. Safe to just |
| 186 | + delete the row and let the worker retry next tick: |
| 187 | + ```sh |
| 188 | + sudo -u forknet sqlite3 /home/forknet/pps-thunder-test/data/shares.db \ |
| 189 | + "DELETE FROM payouts_in_flight WHERE id = <ID>;" |
| 190 | + ``` |
| 191 | + |
| 192 | +- **`txid = <something>`**: the Thunder tx went out; the DB finalize |
| 193 | + crashed. Verify the tx is on Thunder (via `thunder_app_cli |
| 194 | + get-transaction <txid>` or a Thunder wallet viewer). If confirmed, |
| 195 | + finalize by hand: |
| 196 | + ```sh |
| 197 | + sudo -u forknet sqlite3 /home/forknet/pps-thunder-test/data/shares.db " |
| 198 | + BEGIN; |
| 199 | + UPDATE pps_credits SET paid_sats = paid_sats + (SELECT sats FROM payouts_in_flight WHERE id = <ID>) |
| 200 | + WHERE worker_id = (SELECT worker_id FROM payouts_in_flight WHERE id = <ID>); |
| 201 | + DELETE FROM payouts_in_flight WHERE id = <ID>; |
| 202 | + COMMIT; |
| 203 | + " |
| 204 | + ``` |
| 205 | + |
| 206 | +The pool never auto-resolves in-flight rows because it can't safely |
| 207 | +tell apart "broadcast didn't happen" from "broadcast happened, |
| 208 | +finalize crashed" without Thunder-side mempool/chain lookup. |
| 209 | + |
| 210 | +--- |
| 211 | + |
| 212 | +## Onboarding a new miner |
| 213 | + |
| 214 | +Nothing on the pool side. The miner points their ASIC firmware at: |
| 215 | + |
| 216 | +``` |
| 217 | +stratum+tcp://45.33.100.24:3334 |
| 218 | +username: <their-Thunder-address>[.<rig_label>] |
| 219 | +password: (anything) |
| 220 | +``` |
| 221 | + |
| 222 | +The username **must** be a valid Thunder address — base58 of a 20-byte |
| 223 | +hash. If they don't have a Thunder wallet, tell them to run a Thunder |
| 224 | +node and do `thunder-cli get-new-address`. |
| 225 | + |
| 226 | +A Bitcoin address as the username produces `invalid thunder address: |
| 227 | +thunder address decoded length 25 (expected 20)` on authorize and no |
| 228 | +shares accrue. Rejects show up in the admin dashboard's implicit |
| 229 | +`rejects` table (surfaced on the per-worker page) so you can help |
| 230 | +someone debug their config. |
| 231 | + |
| 232 | +**Same address, multiple rigs:** append `.<rig_label>` (e.g. |
| 233 | +`3Z6z1hPySN….basement`, `3Z6z1hPySN….garage`). They show as separate |
| 234 | +rows on the workers page but the same payout address on the admin |
| 235 | +view. |
| 236 | + |
| 237 | +--- |
| 238 | + |
| 239 | +## Rotating the admin password |
| 240 | + |
| 241 | +```sh |
| 242 | +ssh -i ~/.ssh/id_epitetus root@45.33.100.24 |
| 243 | +NEW_PASS=$(openssl rand -base64 21 | tr -d '=+/') |
| 244 | +sed -i "s/^Environment=ADMIN_PASSWORD=.*/Environment=ADMIN_PASSWORD=$NEW_PASS/" \ |
| 245 | + /etc/systemd/system/simplepool-dashboard.service.d/pps-thunder.conf |
| 246 | +echo "admin / $NEW_PASS" > /root/simplepool-admin-cred.txt |
| 247 | +systemctl daemon-reload |
| 248 | +systemctl restart simplepool-dashboard.service |
| 249 | +cat /root/simplepool-admin-cred.txt |
| 250 | +``` |
| 251 | + |
| 252 | +Old password stops working immediately; the dashboard is up in ~1s. |
| 253 | + |
| 254 | +--- |
| 255 | + |
| 256 | +## Rolling back / switching modes |
| 257 | + |
| 258 | +To pause and restore the old solo pool exactly as it was: |
| 259 | + |
| 260 | +```sh |
| 261 | +# stop pps-classic pool |
| 262 | +pkill -9 -x simplepool |
| 263 | +# start the pre-existing solo unit (still installed) |
| 264 | +systemctl start simplepool.service |
| 265 | +``` |
| 266 | + |
| 267 | +That points at the OLD proxy.conf at `~/forknet-software/simplepool/`. |
| 268 | +The dashboard drop-in stays pointed at the pps-classic DB by default; |
| 269 | +edit `/etc/systemd/system/simplepool-dashboard.service.d/pps-thunder.conf` |
| 270 | +if you want to swing it back to the solo DB. |
| 271 | + |
| 272 | +--- |
| 273 | + |
| 274 | +## Troubleshooting |
| 275 | + |
| 276 | +### "invalid thunder address" reject storm |
| 277 | + |
| 278 | +A miner is using a BTC address. Have them update their firmware config |
| 279 | +to use a Thunder address. Until they do, they burn hashrate for zero |
| 280 | +credit — the pool has no fallback. |
| 281 | + |
| 282 | +### Payout worker log shows `reserve short — available=0` forever |
| 283 | + |
| 284 | +Either (a) you haven't done a deposit yet (see runbook above), or (b) |
| 285 | +you deposited but Thunder hasn't seen the credit yet due to BMM not |
| 286 | +running. Check with: |
| 287 | + |
| 288 | +```sh |
| 289 | +# enforcer says the deposit was consensus-accepted? |
| 290 | +grpcurl -plaintext -d '{"sidechain_number":9}' \ |
| 291 | + 127.0.0.1:50051 cusf.mainchain.v1.ValidatorService/GetCtip |
| 292 | +# Thunder wallet sees the balance? |
| 293 | +sudo -u forknet /home/forknet/forknet-software/thunder-rust/target/debug/thunder_app_cli balance |
| 294 | +``` |
| 295 | + |
| 296 | +If Ctip shows the deposit but Thunder wallet balance is 0, it's a |
| 297 | +BMM/Thunder-side sync issue. Escalate to Thunder infra. |
| 298 | + |
| 299 | +### Dashboard shows the OLD DB (pre-pps-classic reset) |
| 300 | + |
| 301 | +Check the drop-in override: |
| 302 | +```sh |
| 303 | +cat /etc/systemd/system/simplepool-dashboard.service.d/pps-thunder.conf |
| 304 | +# PROXY_DB_PATH should be /home/forknet/pps-thunder-test/data/shares.db |
| 305 | +``` |
| 306 | +Fix + `systemctl daemon-reload && systemctl restart simplepool-dashboard`. |
| 307 | + |
| 308 | +### Simplepool won't start — `Address already in use` |
| 309 | + |
| 310 | +There's a stale binary still bound. **DO NOT** `pkill -f simplepool` |
| 311 | +from an SSH session — that pattern matches your own command line and |
| 312 | +kills your SSH. Use exact-name match instead: |
| 313 | + |
| 314 | +```sh |
| 315 | +pkill -9 -x simplepool |
| 316 | +sleep 2 |
| 317 | +ss -tlnp | grep :3334 || echo ":3334 free" |
| 318 | +``` |
| 319 | + |
| 320 | +### Miner shares accrue but payouts never fire |
| 321 | + |
| 322 | +Read the payout log. Common causes: |
| 323 | +- Reserve short (fix: deposit) |
| 324 | +- Thunder RPC unreachable (fix: `systemctl status`, log tail) |
| 325 | +- Worker's Thunder address is `NULL` in the `workers` table (means it |
| 326 | + never authorized cleanly — check the `rejects` table for that |
| 327 | + worker_name) |
| 328 | + |
| 329 | +--- |
| 330 | + |
| 331 | +## Open items on the operator side |
| 332 | + |
| 333 | +1. **Point `pool_btc_address` at an enforcer-wallet-owned address** so |
| 334 | + the enforcer can spend accumulated coinbase directly into deposit |
| 335 | + txs. Right now it's set to the operator's external wallet — every |
| 336 | + deposit currently spends the enforcer's own seed funds. |
| 337 | +2. **BMM / Thunder sidechain block production** on this box currently |
| 338 | + doesn't complete. Deposits are consensus-recognized (Ctip updates) |
| 339 | + but Thunder wallet balance stays 0 until BMM runs. Investigating |
| 340 | + this is a Thunder-side task, not a pool task. |
| 341 | +3. **Admin "Deposit to Thunder" form** — CLASSIC_PAYOUTS.md sketches |
| 342 | + the endpoint (`POST /admin/deposit`) but it's not implemented yet. |
| 343 | + Until it lands, deposits are the CLI runbook above. |
| 344 | + |
| 345 | +None of these block the pool from running correctly — they're |
| 346 | +improvements to the operator UX and end-to-end value flow, not |
| 347 | +correctness gaps. |
0 commit comments