Skip to content

Commit 096d581

Browse files
committed
Sketch pps-thunder-classic: coinbase-to-pool-BTC + operator-driven deposits
This branch captures the pivot forced by the forknet finding: the LayerTwo-Labs enforcer does not credit coinbase outputs as drivechain deposits (verified on regtest and on the live forknet server, blocks 19613 & 19614 accepted with correct shape but Ctip unchanged). The design in CLASSIC_PAYOUTS.md replaces the coinbase-embedded drivechain path with: 1. Coinbase pays a pool BTC wallet (uses the existing coinbase_build_split — no new C code needed). 2. Operator triggers batched Thunder deposits from the admin dashboard via the enforcer's WalletService/CreateDepositTransaction — these DO credit the Ctip. 3. Existing payout worker + pps_credits + at-most-once ledger drain the Thunder reserve to miners. No change. This commit only contains the design doc + placeholder scaffolding: CLASSIC_PAYOUTS.md full blueprint for the code delta from pps-thunder deploy/schema/deposits.sql the new deposits ledger table src/config.h TODO marker on the pool_mode field The actual C implementation, admin form + POST /admin/deposit endpoint, and enforcer gRPC client are all open. The design is the interesting part — the code changes are mechanical from here.
1 parent 2ebcf81 commit 096d581

3 files changed

Lines changed: 195 additions & 1 deletion

File tree

CLASSIC_PAYOUTS.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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.

deploy/schema/deposits.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Ledger of operator-triggered mainchain → Thunder deposits.
2+
-- Populated by the /admin/deposit action (see CLASSIC_PAYOUTS.md).
3+
-- The C proxy does not touch this table; the dashboard is the only writer.
4+
CREATE TABLE IF NOT EXISTS deposits (
5+
id INTEGER PRIMARY KEY AUTOINCREMENT,
6+
ts INTEGER NOT NULL, -- unix seconds
7+
btc_txid TEXT NOT NULL, -- mainchain deposit tx
8+
sats_deposited INTEGER NOT NULL,
9+
fee_sats INTEGER NOT NULL,
10+
thunder_recipient TEXT NOT NULL, -- s9_<base58>_<hex6>
11+
ctip_seq_before INTEGER, -- from GetCtip pre-deposit
12+
ctip_seq_after INTEGER, -- from GetCtip post-deposit
13+
notes TEXT
14+
);
15+
CREATE INDEX IF NOT EXISTS deposits_ts_idx ON deposits(ts);

src/config.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,15 @@ typedef struct {
4444
* existing per-block direct-payout flow. pool_mode = "pps" enables
4545
* Thunder drivechain deposits in the coinbase and per-share PPS
4646
* accrual in the database. */
47-
char pool_mode[16]; /* "solo" | "pps" */
47+
char pool_mode[16]; /* "solo" | "pps"
48+
* TODO(pps-classic): add
49+
* a "pps-classic" value —
50+
* traditional coinbase to
51+
* pool_btc_address, with
52+
* operator-driven Thunder
53+
* deposits via the admin
54+
* dashboard. See
55+
* CLASSIC_PAYOUTS.md. */
4856
char pool_thunder_reserve_address[128]; /* base58 Thunder address that
4957
* receives every block's deposit */
5058
int thunder_sidechain_number; /* 9 for Thunder */

0 commit comments

Comments
 (0)