diff options
Diffstat (limited to 'MINING_PLAN.md')
| -rw-r--r-- | MINING_PLAN.md | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/MINING_PLAN.md b/MINING_PLAN.md new file mode 100644 index 0000000..bb72d3c --- /dev/null +++ b/MINING_PLAN.md | |||
| @@ -0,0 +1,357 @@ | |||
| 1 | # Mining-for-Bandwidth Implementation Plan | ||
| 2 | |||
| 3 | ## Overview | ||
| 4 | |||
| 5 | Add Bitcoin mining (Stratum v1 + v2) to the TollGate firmware so that devices earn internet access by mining real Bitcoin blocks. A BitAxe (ESP32-S3 + BM1366 ASIC) running TollGate firmware becomes a plug-and-play mesh node — no e-cash, Nostr identity, or prior setup required. | ||
| 6 | |||
| 7 | ## Design Decisions | ||
| 8 | |||
| 9 | ### Why real Bitcoin mining instead of arbitrary proof-of-work? | ||
| 10 | |||
| 11 | The work must be **useful** — mining against a Stratum v2 block template means every share contributes to Bitcoin's security. Even at negligible hashrate (ESP32 software mining at ~10-50 kH/s), the work is real. With a BM1366 ASIC (~500 GH/s), the device produces meaningful hashrate. | ||
| 12 | |||
| 13 | ### Why Stratum v2 upstream + Stratum v1 local? | ||
| 14 | |||
| 15 | - **SV2 upstream (to pool)**: Binary framing is bandwidth-efficient, Noise encryption prevents hash hijacking, and the encrypted tunnel uses minimal megabytes on the paid internet link | ||
| 16 | - **SV1 local (to downstream miners)**: JSON-RPC is trivial to implement, no handshake overhead, works over local WiFi with negligible latency | ||
| 17 | - BitAxe already has both implementations — we reuse them | ||
| 18 | |||
| 19 | ### Why Braiins Pool as default SV2 pool? | ||
| 20 | |||
| 21 | - Native SV2 support with published authority pubkey | ||
| 22 | - 0% PPLNS fee option | ||
| 23 | - Lightning Network payouts (useful for converting mining revenue to e-cash) | ||
| 24 | - The authority pubkey is known: `024e031a0b63c7885b19e48f76d49ddbcda9bf3d7f1d6b05df8b71569e2c2f7ff0` | ||
| 25 | |||
| 26 | ### Why dual payout mode (Lightning sats vs e-cash)? | ||
| 27 | |||
| 28 | A TollGate's position in the mesh determines what it earns: | ||
| 29 | |||
| 30 | | Position | Hashrate goes to | Earns | | ||
| 31 | |----------|-----------------|-------| | ||
| 32 | | Standalone (has direct internet) | Braiins pool | Lightning sats for operator | | ||
| 33 | | Mesh node (upstream TollGate detected) | Upstream TollGate's proxy | e-cash / megabytes / minutes | | ||
| 34 | | Relay (no ASIC, no internet) | Nothing locally | Just proxies for downstream miners | | ||
| 35 | |||
| 36 | The `mining_payout_mode` config field controls this: `auto` (default) detects upstream TollGate and chooses accordingly. | ||
| 37 | |||
| 38 | ### Why mine with CPU too? | ||
| 39 | |||
| 40 | UX. We don't care if CPU mining is profitable. A plain ESP32-S3 without an ASIC can still produce non-zero hashrate (~10-50 kH/s via hardware SHA256 accelerator). This means ANY ESP32 running TollGate firmware can bootstrap itself — even one byte per hour is better than zero. | ||
| 41 | |||
| 42 | ### Why sandbox mint access? | ||
| 43 | |||
| 44 | Miners who earn bandwidth via hashrate should also reach the Cashu mint URLs the TollGate accepts. This way they can receive e-cash from mobile wallets without first having internet access. The firewall whitelists mint URLs for unauthenticated clients. | ||
| 45 | |||
| 46 | ### Why hashprice from block template? | ||
| 47 | |||
| 48 | The conversion from hashrate → bandwidth needs a price signal: | ||
| 49 | |||
| 50 | ``` | ||
| 51 | hashprice = (block_subsidy * blocks_per_day) / (difficulty * 2^32) [sat/GH/day] | ||
| 52 | allotment = (hashrate_ghs * hashprice_per_s * duration_s) / price_per_step * step_size | ||
| 53 | ``` | ||
| 54 | |||
| 55 | Calculating hashprice from the `nbits` field in the SV2 block template is automatic, requires no external API, and is always accurate. A config override (`hashprice_sats_per_ghs_day`) is available as fallback. A ContextVM MCP tool (`get_hashprice`) is planned for future dynamic pricing. | ||
| 56 | |||
| 57 | ### Why BitAxe as git submodule? | ||
| 58 | |||
| 59 | The BitAxe ESP-Miner firmware (GPL-3.0) runs on the same ESP-IDF v5.x on ESP32-S3. It contains production-quality implementations of: | ||
| 60 | - `components/stratum_v2/` — SV2 protocol + Noise handshake | ||
| 61 | - `components/stratum/` — SV1 protocol | ||
| 62 | - `components/asic/` — BM1366/BM1368 serial drivers | ||
| 63 | |||
| 64 | Rather than copying code that will diverge, we reference it as a submodule and compile selected components. | ||
| 65 | |||
| 66 | ## Architecture | ||
| 67 | |||
| 68 | ``` | ||
| 69 | [Bitcoin SV2 Pool (Braiins)] | ||
| 70 | ↑ SV2 + Noise (encrypted, binary) | ||
| 71 | [TollGate Gateway] (ESP32-S3, has internet via STA) | ||
| 72 | ├── stratum_client.c — SV2 upstream to Braiins (Noise handshake, job reception, share forwarding) | ||
| 73 | ├── stratum_proxy.c — Local SV1 TCP :3333 (distribute jobs, collect shares, per-IP hashrate meter) | ||
| 74 | ├── mining_payment.c — Share validation, hashprice calc, session_create() calls | ||
| 75 | ├── sw_miner.c — ESP32-S3 hardware SHA256 accelerator (~10-50 kH/s, always runs) | ||
| 76 | ├── asic_miner.c — BM1366/BM1368 via SPI (~500 GH/s, if detected) | ||
| 77 | ├── Existing: captive portal, Cashu, firewall, sessions, wifistr, CVM | ||
| 78 | |||
| 79 | [TollGate Miner] (BitAxe ESP32-S3 + BM1366, no internet) | ||
| 80 | ├── SV1 client → connects to gateway's :3333 via local WiFi | ||
| 81 | ├── ASIC driver → BM1366 via SPI | ||
| 82 | ├── tollgate_client.c → mining mode (instead of Cashu payment) | ||
| 83 | └── Also runs its own AP for downstream devices | ||
| 84 | |||
| 85 | [Plain ESP32 TollGate] | ||
| 86 | ├── SV1 client → connects to gateway's :3333 | ||
| 87 | ├── sw_miner.c only (~10-50 kH/s) | ||
| 88 | └── Also runs its own AP for downstream devices | ||
| 89 | ``` | ||
| 90 | |||
| 91 | ## Config Fields (config.json) | ||
| 92 | |||
| 93 | ```json | ||
| 94 | { | ||
| 95 | "mining_enabled": true, | ||
| 96 | "mining_payout_mode": "auto", | ||
| 97 | "stratum_host": "v2.pool.braiins.com", | ||
| 98 | "stratum_port": 3333, | ||
| 99 | "stratum_user": "bc1q...TollGate", | ||
| 100 | "stratum_pass": "x", | ||
| 101 | "stratum_sv2_authority_pubkey": "024e031a0b63c7885b19e48f76d49ddbcda9bf3d7f1d6b05df8b71569e2c2f7ff0", | ||
| 102 | "stratum_fallback_host": "public-pool.io", | ||
| 103 | "stratum_fallback_port": 21496, | ||
| 104 | "mining_port": 3333, | ||
| 105 | "hashprice_sats_per_ghs_day": 0, | ||
| 106 | "mining_sandbox_mint_access": true | ||
| 107 | } | ||
| 108 | ``` | ||
| 109 | |||
| 110 | | Field | Default | Description | | ||
| 111 | |-------|---------|-------------| | ||
| 112 | | `mining_enabled` | `false` | Enable mining subsystem | | ||
| 113 | | `mining_payout_mode` | `"auto"` | `"auto"`, `"pool"`, `"upstream"`, `"proxy_only"` | | ||
| 114 | | `stratum_host` | `"v2.pool.braiins.com"` | SV2 pool hostname | | ||
| 115 | | `stratum_port` | `3333` | SV2 pool port | | ||
| 116 | | `stratum_user` | `""` | Bitcoin/Lightning address for pool payout | | ||
| 117 | | `stratum_pass` | `"x"` | Pool password | | ||
| 118 | | `stratum_sv2_authority_pubkey` | Braiins key | Pool authority pubkey for Noise verification | | ||
| 119 | | `stratum_fallback_host` | `"public-pool.io"` | SV1 fallback pool hostname | | ||
| 120 | | `stratum_fallback_port` | `21496` | SV1 fallback pool port | | ||
| 121 | | `mining_port` | `3333` | Local mining proxy listen port | | ||
| 122 | | `hashprice_sats_per_ghs_day` | `0` | Manual hashprice override (0 = auto from nbits) | | ||
| 123 | | `mining_sandbox_mint_access` | `true` | Allow unauthenticated clients to reach mint URLs | | ||
| 124 | |||
| 125 | ## Mining Payout Modes | ||
| 126 | |||
| 127 | ### `auto` (default) | ||
| 128 | ``` | ||
| 129 | TollGate boots → connects to WiFi (STA) | ||
| 130 | → tollgate_client_detect(gw_ip) | ||
| 131 | → GET http://gw_ip:2121/ | ||
| 132 | → If upstream TollGate detected → mine to upstream proxy → earn e-cash/bytes | ||
| 133 | → If regular router → mine to Braiins pool → earn Lightning sats | ||
| 134 | ``` | ||
| 135 | |||
| 136 | ### `pool` | ||
| 137 | Always mine to Braiins/public-pool via SV2. Never mine to upstream TollGate. Gateway's own hashrate earns Lightning sats for the operator. | ||
| 138 | |||
| 139 | ### `upstream` | ||
| 140 | Always mine to upstream TollGate's proxy. Fail if no upstream TollGate detected. Gateway's own hashrate earns e-cash/bytes/minutes. | ||
| 141 | |||
| 142 | ### `proxy_only` | ||
| 143 | Don't mine locally at all. Only run the local proxy for downstream miners. Useful for plain ESP32 relay nodes without ASICs that don't want to waste CPU on mining. | ||
| 144 | |||
| 145 | ## Sandbox / Firewall Changes | ||
| 146 | |||
| 147 | Unauthenticated clients (no Cashu, no session) get access to: | ||
| 148 | - `TCP :3333` — mining proxy (get jobs, submit shares) | ||
| 149 | - `TCP :2121` — tollgate API (`GET /mining/job`, `POST /mining/share`) | ||
| 150 | - `TCP :80` — captive portal | ||
| 151 | - `TCP/443` to `mint_url` — so miners can receive e-cash from mobile wallets | ||
| 152 | |||
| 153 | ## Hashrate-to-Bandwidth Conversion | ||
| 154 | |||
| 155 | ``` | ||
| 156 | difficulty = nbits_to_difficulty(job.nbits) | ||
| 157 | hashprice_sats_per_ghs_day = (312500000 * 144) / (difficulty * 2^32) | ||
| 158 | hashprice_sats_per_ghs_s = hashprice_sats_per_ghs_day / 86400 | ||
| 159 | |||
| 160 | allotment_ms = (client_hashrate_ghs * hashprice_sats_per_ghs_s * measurement_window_s) | ||
| 161 | / price_per_step * step_size_ms | ||
| 162 | ``` | ||
| 163 | |||
| 164 | The measurement window is a sliding interval (e.g., 30 seconds). Shares submitted during the window are counted, hashrate is estimated, and allotment is calculated and granted via `session_create()` or `session_extend()`. | ||
| 165 | |||
| 166 | ## New Files | ||
| 167 | |||
| 168 | | File | Purpose | | ||
| 169 | |------|---------| | ||
| 170 | | `main/stratum_client.c/h` | SV2 upstream client (connect to Braiins, Noise handshake, receive jobs, submit shares) | | ||
| 171 | | `main/stratum_proxy.c/h` | Local SV1 TCP server on :3333 (distribute jobs, collect shares, per-IP hashrate meter) | | ||
| 172 | | `main/mining_payment.c/h` | Share validation (SHA256d check), hashprice calculation, session creation | | ||
| 173 | | `main/sw_miner.c/h` | Software SHA256 miner using ESP32-S3 hardware SHA256 accelerator | | ||
| 174 | | `main/asic_miner.c/h` | BM1366/BM1368 ASIC detection + driver wrapper | | ||
| 175 | | `tests/unit/test_mining_payment.c` | Hashprice calculation tests, share validation tests | | ||
| 176 | | `tests/unit/test_stratum_proxy.c` | SV1/SV2 frame parsing tests | | ||
| 177 | |||
| 178 | ## Modified Files | ||
| 179 | |||
| 180 | | File | Changes | | ||
| 181 | |------|---------| | ||
| 182 | | `main/tollgate_api.c` | Add `GET /mining/job`, `POST /mining/share`; add mining tag to `GET /` discovery | | ||
| 183 | | `main/tollgate_client.c` | Add mining mode (detect upstream mining support, mine instead of Cashu) | | ||
| 184 | | `main/tollgate_client.h` | New states: `TG_CLIENT_MINING`, `TG_CLIENT_MINING_ACTIVE` | | ||
| 185 | | `main/firewall.c/h` | Quarantine allowlist: mining port + mint URLs for unauthenticated clients | | ||
| 186 | | `main/dns_server.c/h` | Resolve mint URLs for unauthenticated clients | | ||
| 187 | | `main/session.c/h` | Add `payment_method` field (Cashu vs mining) | | ||
| 188 | | `main/config.c/h` | Parse all new mining config fields | | ||
| 189 | | `main/captive_portal.c` | "Mine for Access" tab in portal HTML | | ||
| 190 | | `main/tollgate_main.c` | Start mining tasks, init ASIC detection | | ||
| 191 | | `CMakeLists.txt` | Add new source files, reference BitAxe submodule | | ||
| 192 | | `.gitmodules` | Add `bitaxeorg/ESP-Miner` submodule | | ||
| 193 | |||
| 194 | ## Implementation Phases | ||
| 195 | |||
| 196 | ### Phase 1: Foundation (config + submodule + build) | ||
| 197 | - [ ] Add `bitaxeorg/ESP-Miner` as git submodule at `components/esp-miner/` | ||
| 198 | - [ ] Add mining config fields to `config.c/h` | ||
| 199 | - [ ] Update `CMakeLists.txt` to compile new sources | ||
| 200 | - [ ] Verify build compiles cleanly | ||
| 201 | |||
| 202 | ### Phase 2: Stratum client (SV2 upstream) | ||
| 203 | - [ ] Create `main/stratum_client.c/h` | ||
| 204 | - [ ] SV2 connection lifecycle: TCP connect → Noise handshake → SetupConnection → OpenChannel → receive jobs | ||
| 205 | - [ ] Job reception: parse NewMiningJob, SetNewPrevHash, SetTarget | ||
| 206 | - [ ] Share submission: forward shares from local proxy to upstream pool | ||
| 207 | - [ ] Fallback: if SV2 fails, try SV1 to public-pool.io | ||
| 208 | |||
| 209 | ### Phase 3: Stratum proxy (local SV1 server) | ||
| 210 | - [ ] Create `main/stratum_proxy.c/h` | ||
| 211 | - [ ] TCP listener on `:3333` | ||
| 212 | - [ ] SV1 JSON-RPC: `mining.subscribe`, `mining.authorize`, `mining.notify`, `mining.submit` | ||
| 213 | - [ ] Distribute current job to connected miners | ||
| 214 | - [ ] Collect shares, forward to stratum_client for upstream submission | ||
| 215 | - [ ] Per-client IP hashrate meter (shares / time window) | ||
| 216 | |||
| 217 | ### Phase 4: Mining payment | ||
| 218 | - [ ] Create `main/mining_payment.c/h` | ||
| 219 | - [ ] `nbits_to_difficulty()` conversion | ||
| 220 | - [ ] `calculate_hashprice()` from difficulty + block subsidy | ||
| 221 | - [ ] `validate_share()` — SHA256d(header) < target check | ||
| 222 | - [ ] `shares_to_allotment()` — hashrate → bandwidth conversion | ||
| 223 | - [ ] Integration with `session_create()` / `session_extend()` | ||
| 224 | |||
| 225 | ### Phase 5: API endpoints | ||
| 226 | - [ ] `GET /mining/job` — return current block template as JSON | ||
| 227 | - [ ] `POST /mining/share` — accept share, validate, create/extend session | ||
| 228 | - [ ] Add mining tag to `GET /` discovery response | ||
| 229 | - [ ] `GET /mining/stats` — current hashrate, total shares, hashprice | ||
| 230 | |||
| 231 | ### Phase 6: Firewall sandbox | ||
| 232 | - [ ] `firewall.c` — quarantine allowlist for `:3333`, `:2121`, `:80` | ||
| 233 | - [ ] `firewall.c` — conditional allow mint URL hostnames if `mining_sandbox_mint_access` | ||
| 234 | - [ ] `dns_server.c` — resolve mint URLs for unauthenticated clients | ||
| 235 | |||
| 236 | ### Phase 7: Client mining mode | ||
| 237 | - [ ] `tollgate_client.c` — detect upstream mining support via discovery tag | ||
| 238 | - [ ] New state machine: `TG_CLIENT_MINING` → `TG_CLIENT_MINING_ACTIVE` | ||
| 239 | - [ ] Connect to upstream `:3333` mining proxy | ||
| 240 | - [ ] Submit shares to earn bandwidth | ||
| 241 | |||
| 242 | ### Phase 8: Software miner | ||
| 243 | - [ ] Create `main/sw_miner.c/h` | ||
| 244 | - [ ] ESP32-S3 hardware SHA256 accelerator via `esp_sha.h` / mbedtls | ||
| 245 | - [ ] Get job from local stratum proxy, iterate nonces, check against target | ||
| 246 | - [ ] Low-priority FreeRTOS task (don't starve WiFi/routing) | ||
| 247 | - [ ] Expected: ~10-50 kH/s | ||
| 248 | |||
| 249 | ### Phase 9: ASIC miner | ||
| 250 | - [ ] Create `main/asic_miner.c/h` | ||
| 251 | - [ ] Probe SPI bus at boot for BM1366/BM1368 | ||
| 252 | - [ ] If ASIC found: use BitAxe driver (`BM1366_send_work`, `BM1366_process_work`) | ||
| 253 | - [ ] If no ASIC: fall back to software miner | ||
| 254 | - [ ] Expected: ~500 GH/s (BM1366) or ~120 GH/s (BM1368) | ||
| 255 | |||
| 256 | ### Phase 10: Portal UI | ||
| 257 | - [ ] Add "Mine for Access" tab to captive portal HTML | ||
| 258 | - [ ] Show current hashrate, shares submitted, time earned | ||
| 259 | - [ ] Auto-start mining when tab is opened (JavaScript Web Crypto SHA256 in browser) | ||
| 260 | - [ ] Show progress bar / earnings counter | ||
| 261 | |||
| 262 | ### Phase 11: CVM integration | ||
| 263 | - [ ] Add `get_hashprice` MCP tool to `mcp_handler.c/h` | ||
| 264 | - [ ] Returns current hashprice, difficulty, estimated earnings | ||
| 265 | - [ ] `set_mining_config` tool for remote configuration | ||
| 266 | |||
| 267 | ### Phase 12: Main integration | ||
| 268 | - [ ] `tollgate_main.c` — start stratum_client task on boot | ||
| 269 | - [ ] `tollgate_main.c` — start stratum_proxy task on boot | ||
| 270 | - [ ] `tollgate_main.c` — start sw_miner task on boot | ||
| 271 | - [ ] `tollgate_main.c` — start asic_miner task if ASIC detected | ||
| 272 | - [ ] Mining task lifecycle: start/stop with services | ||
| 273 | |||
| 274 | ### Phase 13: Tests | ||
| 275 | - [ ] Unit test: `test_mining_payment.c` — hashprice calc, nbits→difficulty, share validation | ||
| 276 | - [ ] Unit test: `test_stratum_proxy.c` — SV1 frame parsing, SV2 frame encode/decode | ||
| 277 | - [ ] Integration test: `mining.mjs` — submit share, verify session, check bandwidth | ||
| 278 | - [ ] E2E test: `mining.spec.mjs` — portal mining tab, hashrate display | ||
| 279 | |||
| 280 | ## Checklist — Implementation Progress | ||
| 281 | |||
| 282 | ### Phase 1: Foundation | ||
| 283 | - [ ] Add BitAxe git submodule | ||
| 284 | - [ ] Mining config fields in config.c/h | ||
| 285 | - [ ] CMakeLists.txt updated | ||
| 286 | - [ ] Clean build verified | ||
| 287 | |||
| 288 | ### Phase 2: Stratum Client | ||
| 289 | - [ ] stratum_client.c/h created | ||
| 290 | - [ ] SV2 Noise handshake | ||
| 291 | - [ ] Job reception | ||
| 292 | - [ ] Share submission | ||
| 293 | - [ ] SV1 fallback | ||
| 294 | |||
| 295 | ### Phase 3: Stratum Proxy | ||
| 296 | - [ ] stratum_proxy.c/h created | ||
| 297 | - [ ] SV1 JSON-RPC server | ||
| 298 | - [ ] Job distribution | ||
| 299 | - [ ] Share collection | ||
| 300 | - [ ] Per-IP hashrate meter | ||
| 301 | |||
| 302 | ### Phase 4: Mining Payment | ||
| 303 | - [ ] mining_payment.c/h created | ||
| 304 | - [ ] nbits_to_difficulty | ||
| 305 | - [ ] hashprice calculation | ||
| 306 | - [ ] share validation (SHA256d) | ||
| 307 | - [ ] shares_to_allotment conversion | ||
| 308 | - [ ] session_create integration | ||
| 309 | |||
| 310 | ### Phase 5: API Endpoints | ||
| 311 | - [ ] GET /mining/job | ||
| 312 | - [ ] POST /mining/share | ||
| 313 | - [ ] Mining discovery tag | ||
| 314 | - [ ] GET /mining/stats | ||
| 315 | |||
| 316 | ### Phase 6: Firewall Sandbox | ||
| 317 | - [ ] Quarantine allowlist for mining ports | ||
| 318 | - [ ] Mint URL access for unauthenticated clients | ||
| 319 | - [ ] DNS resolution in sandbox | ||
| 320 | |||
| 321 | ### Phase 7: Client Mining Mode | ||
| 322 | - [ ] Mining support detection in tollgate_client.c | ||
| 323 | - [ ] TG_CLIENT_MINING states | ||
| 324 | - [ ] Upstream proxy connection | ||
| 325 | - [ ] Share submission for bandwidth | ||
| 326 | |||
| 327 | ### Phase 8: Software Miner | ||
| 328 | - [ ] sw_miner.c/h created | ||
| 329 | - [ ] ESP32-S3 HW SHA256 | ||
| 330 | - [ ] Job dequeue → nonce iteration → target check | ||
| 331 | - [ ] Low-priority task | ||
| 332 | |||
| 333 | ### Phase 9: ASIC Miner | ||
| 334 | - [ ] asic_miner.c/h created | ||
| 335 | - [ ] BM1366/BM1368 SPI detection | ||
| 336 | - [ ] ASIC mining loop | ||
| 337 | - [ ] Software fallback | ||
| 338 | |||
| 339 | ### Phase 10: Portal UI | ||
| 340 | - [ ] "Mine for Access" tab | ||
| 341 | - [ ] Hashrate display | ||
| 342 | - [ ] Earnings counter | ||
| 343 | |||
| 344 | ### Phase 11: CVM Integration | ||
| 345 | - [ ] get_hashprice MCP tool | ||
| 346 | - [ ] set_mining_config MCP tool | ||
| 347 | |||
| 348 | ### Phase 12: Main Integration | ||
| 349 | - [ ] Mining tasks started on boot | ||
| 350 | - [ ] ASIC detection at boot | ||
| 351 | - [ ] Task lifecycle management | ||
| 352 | |||
| 353 | ### Phase 13: Tests | ||
| 354 | - [ ] test_mining_payment.c | ||
| 355 | - [ ] test_stratum_proxy.c | ||
| 356 | - [ ] integration/mining.mjs | ||
| 357 | - [ ] e2e/mining.spec.mjs | ||