Skip to content

Commit 423c13d

Browse files
authored
Merge pull request #5 from rsantacroce/pps-thunder-classic
Pps thunder classic
2 parents 1ed9361 + 8474275 commit 423c13d

71 files changed

Lines changed: 8987 additions & 54 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# -----------------------------------------------------------------------------
2+
# Keep the docker build context small. Docker reads this from the context
3+
# root (the repo root, per docker-compose.yml `context: ../..`).
4+
# -----------------------------------------------------------------------------
5+
6+
# host-only build outputs — the image builds fresh from source
7+
build/
8+
**/node_modules/
9+
10+
# runtime data lives in a bind-mounted volume, never in the image
11+
data/
12+
.regtest/
13+
14+
# operator secrets and local notes — never bake into an image
15+
proxy.conf
16+
FORKNET_CHEATSHEET.md
17+
memory.md
18+
*.pre-reconcile-*
19+
*.pre-restart-*
20+
21+
# vcs & editor cruft
22+
.git/
23+
.gitignore
24+
.DS_Store
25+
26+
# docs — not needed at runtime
27+
*.md
28+
!dashboard/README.md
29+
!payout/README.md
30+
!deploy/docker/README.md
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Build the three simplepool container images and push them to the GitHub
2+
# Container Registry (ghcr.io) — the same registry coinshift-rs publishes to.
3+
#
4+
# This job runs only in the canonical LayerTwo-Labs repo (see the `if:` guard
5+
# on the job), so images land in the org namespace, tagged automatically:
6+
# ghcr.io/layertwo-labs/simplepool (C stratum proxy)
7+
# ghcr.io/layertwo-labs/simplepool-dashboard (Node read-only web UI)
8+
# ghcr.io/layertwo-labs/simplepool-payout (Node Thunder payout worker)
9+
#
10+
# Auth uses the built-in GITHUB_TOKEN — no secrets to configure, because the
11+
# repo lives in the same org that owns the packages (exactly like coinshift).
12+
# Pull-request runs build the images (to prove the Dockerfiles still work) but
13+
# do not push; only pushes to main, tags, and manual runs publish.
14+
name: Build and push Docker images
15+
16+
on:
17+
push:
18+
branches:
19+
- main
20+
tags:
21+
- "v*"
22+
workflow_dispatch:
23+
pull_request:
24+
25+
jobs:
26+
build-push-docker:
27+
runs-on: ubuntu-latest
28+
# Publish only from the LayerTwo-Labs org repo. On a personal fork/repo
29+
# (e.g. rsantacroce/simplepool) this is false, so the whole job is skipped
30+
# — nothing is built and nothing is pushed. (String compare is
31+
# case-insensitive, so 'LayerTwo-Labs' also matches 'layertwo-labs'.)
32+
if: github.repository_owner == 'LayerTwo-Labs'
33+
permissions:
34+
contents: read
35+
packages: write
36+
attestations: write
37+
id-token: write
38+
strategy:
39+
fail-fast: false
40+
matrix:
41+
include:
42+
- image: simplepool
43+
dockerfile: deploy/docker/Dockerfile.simplepool
44+
- image: simplepool-dashboard
45+
dockerfile: deploy/docker/Dockerfile.dashboard
46+
- image: simplepool-payout
47+
dockerfile: deploy/docker/Dockerfile.payout
48+
steps:
49+
- name: Checkout
50+
uses: actions/checkout@v4
51+
52+
- name: Docker meta
53+
id: meta
54+
uses: docker/metadata-action@v5
55+
with:
56+
images: |
57+
ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}
58+
tags: |
59+
type=sha,event=push
60+
type=ref,event=pr
61+
type=ref,event=tag
62+
type=semver,pattern={{version}}
63+
type=raw,value=latest,enable={{is_default_branch}}
64+
65+
- name: Set up Docker Buildx
66+
uses: docker/setup-buildx-action@v3
67+
68+
- name: Login to GHCR
69+
# Skipped on pull_request, where we build-only and don't push.
70+
if: github.event_name != 'pull_request'
71+
uses: docker/login-action@v3
72+
with:
73+
registry: ghcr.io
74+
username: ${{ github.repository_owner }}
75+
password: ${{ secrets.GITHUB_TOKEN }}
76+
77+
- name: Build and push
78+
uses: docker/build-push-action@v6
79+
with:
80+
context: .
81+
file: ${{ matrix.dockerfile }}
82+
push: ${{ github.event_name != 'pull_request' }}
83+
tags: ${{ steps.meta.outputs.tags }}
84+
labels: ${{ steps.meta.outputs.labels }}
85+
# Per-image GitHub Actions cache so the three legs don't collide.
86+
cache-from: type=gha,scope=${{ matrix.image }}
87+
cache-to: type=gha,mode=max,scope=${{ matrix.image }}

.github/workflows/check_build.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Compile simplepool and run its unit tests on every push/PR.
2+
# simplepool is pure C11 (see Makefile); its deps are the same -dev packages
3+
# the build stage of deploy/docker/Dockerfile.simplepool installs.
4+
name: Build and test
5+
6+
on:
7+
push:
8+
branches:
9+
- main
10+
pull_request:
11+
workflow_dispatch:
12+
13+
jobs:
14+
build-test:
15+
runs-on: ubuntu-latest
16+
# Match build_docker.yaml: CI runs only in the LayerTwo-Labs org repo, so
17+
# pushing to a personal repo doesn't spend Actions minutes. Remove this line
18+
# if you *do* want build/test CI to run on your own fork too.
19+
if: github.repository_owner == 'LayerTwo-Labs'
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Install build dependencies
25+
run: |
26+
sudo apt-get update
27+
sudo apt-get install -y --no-install-recommends \
28+
build-essential \
29+
libsqlite3-dev \
30+
libcurl4-openssl-dev \
31+
libhiredis-dev
32+
33+
- name: Build
34+
run: make -j"$(nproc)"
35+
36+
- name: Test
37+
run: make test

.gitignore

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
build/
2-
data/
3-
*.o
4-
*.d
5-
proxy.conf
6-
.DS_Store
1+
/build/
2+
/data/
3+
/.regtest/
4+
/proxy.conf

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.

0 commit comments

Comments
 (0)