|
| 1 | +# `pps-thunder-classic` — traditional coinbase + operator-driven deposits |
| 2 | + |
| 3 | +## Why this branch exists |
| 4 | + |
| 5 | +The `pps-thunder` design embeds a BIP300 drivechain deposit in every |
| 6 | +coinbase, aiming to make the pool never custody BTC. End-to-end |
| 7 | +validation on regtest AND on the live forknet server proved that the |
| 8 | +LayerTwo-Labs enforcer **does not credit coinbase outputs as |
| 9 | +drivechain deposits** — the block is accepted into the chain but the |
| 10 | +sidechain Ctip never moves. The rule requires the deposit tx to spend |
| 11 | +real, mature, spendable UTXOs; a coinbase does not qualify. This is |
| 12 | +consensus-level and unlikely to change. |
| 13 | + |
| 14 | +This branch flips the model to the pattern all real drivechain mining |
| 15 | +pools converge on: |
| 16 | + |
| 17 | +1. **Coinbase pays the pool's BTC wallet** — a normal solo-style |
| 18 | + output. Pool now does briefly custody BTC (a design tradeoff, but |
| 19 | + the only path that actually works). |
| 20 | +2. **Operator (via the admin dashboard) triggers batched deposits to |
| 21 | + Thunder.** Each deposit is a real `CreateDepositTransaction` that |
| 22 | + spends accumulated pool UTXOs → OP_DRIVECHAIN + OP_RETURN. This DOES |
| 23 | + credit the Ctip on Thunder. |
| 24 | +3. **Payout worker (already in the code) drains the Thunder reserve to |
| 25 | + miners.** No change from `pps-thunder` — same |
| 26 | + `pps_credits.accrued_sats - paid_sats` sweep, same at-most-once |
| 27 | + protocol. |
| 28 | + |
| 29 | +Everything upstream of the deposit step stays identical to |
| 30 | +`pps-thunder`: same stratum-username-is-a-Thunder-address, same PPS |
| 31 | +accrual math, same in-flight ledger, same audit tooling. |
| 32 | + |
| 33 | +## Concrete code delta from `pps-thunder` |
| 34 | + |
| 35 | +### 1. New pool mode value |
| 36 | + |
| 37 | +Add a third value to `pool_mode` in `src/config.h` / |
| 38 | +`src/config.c`: |
| 39 | + |
| 40 | +- `solo` — unchanged; miners paid direct in coinbase (per-miner |
| 41 | + address as the coinbase spendable output). |
| 42 | +- `pps` — the drivechain-in-coinbase build (does not credit Thunder; |
| 43 | + useful only as a shape validator). |
| 44 | +- **`pps-classic`** — new. Coinbase pays a single `pool_btc_address` |
| 45 | + (P2WPKH) for the full net-of-operator-fee reward. PPS accrual math |
| 46 | + and stratum username validation are the same as `pps`. |
| 47 | + |
| 48 | +In `pps-classic` mode the stratum server: |
| 49 | +- Validates usernames as Thunder addresses (as `pps` does now). |
| 50 | +- Renders one coinbase, identical for every miner, paying |
| 51 | + `pool_btc_address` for `value - fee` and `operator_address` for |
| 52 | + `fee`. No OP_DRIVECHAIN output; no OP_RETURN destination. |
| 53 | +- Accrues `difficulty × pps_sats_per_diff` to each worker's |
| 54 | + `pps_credits.accrued_sats` (unchanged). |
| 55 | + |
| 56 | +### 2. New config keys |
| 57 | + |
| 58 | +Add to `proxy.conf`: |
| 59 | + |
| 60 | +``` |
| 61 | +pool_mode = pps-classic |
| 62 | +
|
| 63 | +# Where mined BTC lands. Should be a wallet the operator controls |
| 64 | +# and that has enough age/maturity for later deposit-tx use. |
| 65 | +pool_btc_address = bc1q... |
| 66 | +
|
| 67 | +# Everything else — pps_sats_per_diff, operator_address, fee_bps — |
| 68 | +# behaves exactly as in pps mode. pool_thunder_reserve_address is |
| 69 | +# ignored in this mode (deposits go via the admin dashboard, not |
| 70 | +# the coinbase). |
| 71 | +``` |
| 72 | + |
| 73 | +### 3. New coinbase builder |
| 74 | + |
| 75 | +`src/coinbase.c`: `coinbase_build_split` already emits |
| 76 | +`[pool_btc_p2wpkh, operator_fee, witness_commit]` — that IS the |
| 77 | +classic-mode layout. No new builder required. Just call it with |
| 78 | +`miner_address = pool_btc_address` in `stratum.c`. |
| 79 | + |
| 80 | +The `_drivechain*` builders stay in the codebase (they still power |
| 81 | +`pool_mode = pps` for shape validation), but aren't reached in |
| 82 | +classic mode. |
| 83 | + |
| 84 | +### 4. New database table |
| 85 | + |
| 86 | +`deposits` — one row per operator-triggered Thunder deposit: |
| 87 | + |
| 88 | +```sql |
| 89 | +CREATE TABLE IF NOT EXISTS deposits ( |
| 90 | + id INTEGER PRIMARY KEY AUTOINCREMENT, |
| 91 | + ts INTEGER NOT NULL, -- unix seconds |
| 92 | + btc_txid TEXT NOT NULL, -- mainchain deposit tx |
| 93 | + sats_deposited INTEGER NOT NULL, |
| 94 | + fee_sats INTEGER NOT NULL, |
| 95 | + thunder_recipient TEXT NOT NULL, -- deposit-format address |
| 96 | + ctip_seq_before INTEGER, -- for audit trail |
| 97 | + ctip_seq_after INTEGER, |
| 98 | + notes TEXT |
| 99 | +); |
| 100 | +CREATE INDEX IF NOT EXISTS deposits_ts_idx ON deposits(ts); |
| 101 | +``` |
| 102 | + |
| 103 | +### 5. New admin controls |
| 104 | + |
| 105 | +Extend `dashboard/views/admin.ejs` and `dashboard/server.js` with a |
| 106 | +new **"Deposit to Thunder"** card: |
| 107 | + |
| 108 | +- Shows: `pool_btc_address` current spendable balance (via bitcoind |
| 109 | + `getreceivedbyaddress` or a lightweight scan), amount already |
| 110 | + waiting on-chain vs already deposited (from `deposits` table). |
| 111 | +- **POST /admin/deposit** — form fields: `amount_sats`, `fee_sats`. |
| 112 | + Server calls the enforcer's gRPC |
| 113 | + `WalletService/CreateDepositTransaction` with the pool's Thunder |
| 114 | + reserve address as the destination. On success, `INSERT INTO |
| 115 | + deposits` + refresh reserve balance. |
| 116 | +- All controls require the same basic auth as the read-only admin |
| 117 | + view; CSRF protection via a per-session token (or an |
| 118 | + `Origin`-header check for simplicity). |
| 119 | + |
| 120 | +### 6. New operator-facing docs |
| 121 | + |
| 122 | +- Update `PPS_THUNDER.md` with a subsection pointing at this file for |
| 123 | + the classic-mode alternative. |
| 124 | +- Update `payout/README.md` — noting that in classic mode the reserve |
| 125 | + is filled manually via the admin, not automatically via coinbase. |
| 126 | + |
| 127 | +## Migration story on the live server |
| 128 | + |
| 129 | +None required. `pps-thunder-classic` is a config change and a coinbase- |
| 130 | +builder swap on the same binary: |
| 131 | + |
| 132 | +```sh |
| 133 | +# on the forknet box, once this branch merges to main deployment: |
| 134 | +sed -i 's/^pool_mode = pps/pool_mode = pps-classic/' proxy.conf |
| 135 | +echo 'pool_btc_address = bc1q...' >> proxy.conf # pool's BTC wallet |
| 136 | +systemctl restart simplepool.service |
| 137 | +``` |
| 138 | + |
| 139 | +Existing `pps_credits` accruals carry over. In-flight ledger keeps |
| 140 | +working. Miners don't reconnect (stratum username is still a Thunder |
| 141 | +address). |
| 142 | + |
| 143 | +## What's NOT in this branch yet |
| 144 | + |
| 145 | +This branch currently contains only: |
| 146 | +- **this doc**, |
| 147 | +- a placeholder in `src/config.h` marked TODO for the new |
| 148 | + `pool_mode` value, |
| 149 | +- a placeholder `deploy/schema/deposits.sql` with the deposits table |
| 150 | + SQL. |
| 151 | + |
| 152 | +The actual C changes (config parser, stratum coinbase-render branch), |
| 153 | +the SQL wiring in `src/store.c`, the admin dashboard controls, and |
| 154 | +the gRPC client for the enforcer's `CreateDepositTransaction` are |
| 155 | +open work. When you're ready to build it, this doc is the blueprint. |
| 156 | + |
| 157 | +## Why do the design first |
| 158 | + |
| 159 | +Because the interesting decisions are all in the deposit-flow shape, |
| 160 | +not the code. Locking in: |
| 161 | + |
| 162 | +- **Manual vs auto deposits.** This design says manual (operator |
| 163 | + clicks a button per deposit). An auto-batching worker is a later |
| 164 | + improvement — no schema change required, just a new service that |
| 165 | + posts to `/admin/deposit`. |
| 166 | +- **One pool BTC address vs many.** One is simpler and matches how |
| 167 | + drivechain-launcher wallets typically hold funds. Migrating to a |
| 168 | + rolling set of addresses is a follow-up. |
| 169 | +- **How much precision on the deposit fee.** Locked to |
| 170 | + `enforcer.WalletService.CreateDepositTransaction`'s `fee_sats` |
| 171 | + field. Operator eyeballs current fee market and picks a number. |
0 commit comments