upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PLAN_BITAXE_FIXES.md141
-rw-r--r--components/tollgate_core/src/tollgate_core.c85
-rw-r--r--tests/unit/Makefile5
-rw-r--r--tests/unit/test_mining_api.c123
4 files changed, 353 insertions, 1 deletions
diff --git a/PLAN_BITAXE_FIXES.md b/PLAN_BITAXE_FIXES.md
new file mode 100644
index 0000000..9514dbf
--- /dev/null
+++ b/PLAN_BITAXE_FIXES.md
@@ -0,0 +1,141 @@
1# TollGate on NerdQAxePlus/BitAxe — Fix Plan
2
3**Created:** 2026-05-28
4**Branch:** `feature/tollgate-core-v2` (esp32-tollgate), `develop` (NerdQAxePlus)
5**Component version:** v1.2.0 → v1.3.0
6
7---
8
9## Phase 1: Implement 3 Missing API Functions in tollgate_core
10
11**File:** `components/tollgate_core/src/tollgate_core.c`
12
13- [ ] 1a. Implement `tollgate_core_on_share_accepted(uint32_t client_ip, double difficulty)`
14 - Call `tollgate_core_mining_update_hashrate(client_ip, true)`
15 - Get client stats via `tollgate_core_mining_get_client_stats(client_ip)`
16 - Find or create session via `tollgate_core_session_find_by_ip()` / `tollgate_core_session_create()`
17 - Calculate allotment via `tollgate_core_mining_shares_to_allotment_ms()` or `_bytes()` based on metric
18 - Extend session via `tollgate_core_session_extend(session, allotment)`
19 - Call `s_platform->on_share_accepted(difficulty)` if set
20
21- [ ] 1b. Implement `tollgate_core_calc_hashprice(double hashrate_ghs)`
22 - Check `s_platform->get_hashprice_sats_per_ghs_day()` for manual override (nonzero = return it)
23 - Otherwise return `tollgate_core_mining_get_current_hashprice()` (from nbits)
24
25- [ ] 1c. Implement `tollgate_core_get_mining_status_json(void)`
26 - Follow `tollgate_core_get_status_json()` pattern (cJSON create → add fields → print → delete)
27 - Get proxy stats via `tollgate_core_stratum_proxy_get_stats()`
28 - JSON fields: `hashprice`, `nbits`, `total_shares`, `total_accepted`, `total_rejected`, `active_miners`, `hashrate_ghs`
29
30- [ ] 1d. Add unit tests for new functions in `tests/unit/test_mining_api.c`
31- [ ] 1e. `make test-unit` — all tests pass
32- [ ] 1f. `idf.py build` — esp32-tollgate builds
33
34---
35
36## Phase 2: Wire tick() into NerdQAxePlus Main Loop
37
38**File:** `esp-miner-nerdqaxeplus/main/main.cpp` (line 331 while loop)
39
40- [ ] 2a. Add `tollgate_core_tick()` inside the 10s loop, guarded by `#ifdef TOLLGATE`
41- [ ] 2b. `TOLLGATE=1 BOARD=NERDQAXEPLUS idf.py build` passes
42
43---
44
45## Phase 3: Wire WiFi AP Client Events
46
47**File:** `esp-miner-nerdqaxeplus/components/connect/connect.c` (event_handler line 74)
48
49- [ ] 3a. Add `#include "tollgate_core.h"` guarded by `#ifdef TOLLGATE`
50- [ ] 3b. Add `WIFI_EVENT_AP_STACONNECTED` handler: resolve MAC→IP via `esp_wifi_ap_get_sta_list_with_ip()`, call `tollgate_core_client_connected(mac, ip)`
51- [ ] 3c. Add `WIFI_EVENT_AP_STADISCONNECTED` handler: call `tollgate_core_client_disconnected(mac)`
52- [ ] 3d. `TOLLGATE=1 BOARD=NERDQAXEPLUS idf.py build` passes
53
54---
55
56## Phase 4: Make Stratum Proxy Functional
57
58**File:** `components/tollgate_core/src/tollgate_core_stratum_proxy.c`
59
60- [ ] 4a. Line-buffer recv data, parse downstream miner messages using `tollgate_core_stratum_parse_*()`:
61 - `mining.subscribe` → respond with extranonce
62 - `mining.authorize` → respond with success
63 - `mining.submit` → validate difficulty, increment counters, call `tollgate_core_on_share_accepted()`, respond accept/reject
64- [ ] 4b. Use real jobs from `tollgate_core_stratum_proxy_set_job()` instead of zeroed placeholders
65
66**File:** `esp-miner-nerdqaxeplus/main/tasks/create_jobs_task.cpp` (line 176)
67
68- [ ] 4c. Hook `create_job_mining_notify()` to forward pool jobs to proxy via `tollgate_core_stratum_proxy_set_job()`, guarded by `#ifdef TOLLGATE`
69
70**File:** `esp-miner-nerdqaxeplus/main/stratum/stratum_manager.cpp` (line 179)
71
72- [ ] 4d. Hook `mining.set_difficulty` to call `tollgate_core_mining_set_current_nbits()`, guarded by `#ifdef TOLLGATE`
73
74- [ ] 4e. `idf.py build` passes for esp32-tollgate
75- [ ] 4f. `TOLLGATE=1 BOARD=NERDQAXEPLUS idf.py build` passes
76
77---
78
79## Phase 5: Wire Share Accepted for Self-Mining Credit
80
81**File:** `esp-miner-nerdqaxeplus/main/tollgate_platform.cpp` (line 137)
82
83- [ ] 5a. Update `tollgate_on_share_accepted(double difficulty)` to call `tollgate_core_on_share_accepted(owner_ip, difficulty)` crediting the owner session
84- [ ] 5b. `TOLLGATE=1 BOARD=NERDQAXEPLUS idf.py build` passes
85
86---
87
88## Phase 6: Dynamic AP IP and DNS
89
90**File:** `esp-miner-nerdqaxeplus/main/tollgate_platform.cpp` (line 158)
91
92- [ ] 6a. Replace hardcoded `192.168.4.1` with actual AP IP from `esp_netif_get_ip_info()` on AP netif
93- [ ] 6b. Replace hardcoded upstream DNS `8.8.8.8` with STA gateway IP
94- [ ] 6c. `TOLLGATE=1 BOARD=NERDQAXEPLUS idf.py build` passes
95
96---
97
98## Phase 7: Sync tollgate_core to NerdQAxePlus + Build Verify
99
100- [ ] 7a. rsync `esp32-tollgate/components/tollgate_core/` → `esp-miner-nerdqaxeplus/components/tollgate_core/`
101- [ ] 7b. `make test-unit` — all esp32-tollgate tests pass
102- [ ] 7c. `idf.py build` — esp32-tollgate builds
103- [ ] 7d. `TOLLGATE=1 BOARD=NERDQAXEPLUS idf.py build` — NerdQAxePlus builds
104
105---
106
107## Phase 8: Hardware Smoke Test on NerdQAxePlus
108
109- [ ] 8a. Flash NerdQAxePlus with `TOLLGATE=1` to a NerdQAxe+ board
110- [ ] 8b. Connect to AP, verify captive portal loads
111- [ ] 8c. Test Cashu payment flow
112- [ ] 8d. Connect Stratum miner to port 3334, verify shares tracked and sessions extended
113- [ ] 8e. Verify board's own ASIC mining credits the owner
114- [ ] 8f. Test grant/reset endpoints
115- [ ] 8g. Test internet passthrough after payment
116
117---
118
119## Phase 9: Commit, Push, Publish
120
121- [ ] 9a. Commit all tollgate_core changes to esp32-tollgate `feature/tollgate-core-v2`
122- [ ] 9b. Push esp32-tollgate to nostr
123- [ ] 9c. Commit all NerdQAxePlus integration to `develop`
124- [ ] 9d. Push NerdQAxePlus to nostr
125- [ ] 9e. Bump tollgate_core to v1.3.0 in `idf_component.yml`
126- [ ] 9f. Re-publish to IDF Component Registry
127
128---
129
130## Dependency Order
131
132```
133Phase 1 → Phase 5 → Phase 7 → Phase 8
134Phase 1 → Phase 4 → Phase 7 → Phase 8
135Phase 2 → Phase 7
136Phase 3 → Phase 7
137Phase 6 → Phase 7
138Phase 7 → Phase 8 → Phase 9
139```
140
141Phases 1–6 are independent (different files). Phase 7 bundles them. Phase 8 needs hardware.
diff --git a/components/tollgate_core/src/tollgate_core.c b/components/tollgate_core/src/tollgate_core.c
index 2b337c3..f2d77e0 100644
--- a/components/tollgate_core/src/tollgate_core.c
+++ b/components/tollgate_core/src/tollgate_core.c
@@ -3,6 +3,8 @@
3#include "tollgate_core_dns.h" 3#include "tollgate_core_dns.h"
4#include "tollgate_core_firewall.h" 4#include "tollgate_core_firewall.h"
5#include "tollgate_core_session.h" 5#include "tollgate_core_session.h"
6#include "tollgate_core_mining.h"
7#include "tollgate_core_stratum_proxy.h"
6#include "esp_log.h" 8#include "esp_log.h"
7#include "cJSON.h" 9#include "cJSON.h"
8#include <string.h> 10#include <string.h>
@@ -235,6 +237,89 @@ bool tollgate_core_is_owner_connected(void)
235 return s_owner_connected; 237 return s_owner_connected;
236} 238}
237 239
240void tollgate_core_on_share_accepted(uint32_t client_ip, double difficulty)
241{
242 if (!s_platform) return;
243
244 tollgate_core_mining_update_hashrate(client_ip, true);
245
246 const tollgate_mining_client_stats_t *stats = tollgate_core_mining_get_client_stats(client_ip);
247 if (!stats) return;
248
249 double hashprice = tollgate_core_calc_hashprice(stats->hashrate_ghs);
250 if (hashprice <= 0.0) return;
251
252 const char *metric = s_platform->get_metric ? s_platform->get_metric() : "milliseconds";
253 int price = s_platform->get_price_sats ? (int)s_platform->get_price_sats() : 21;
254 uint64_t allotment = 0;
255
256 if (strcmp(metric, "bytes") == 0) {
257 int step_bytes = s_platform->get_step_bytes ? (int)s_platform->get_step_bytes() : 22020096;
258 allotment = tollgate_core_mining_shares_to_allotment_bytes(
259 stats->hashrate_ghs, hashprice, price, step_bytes);
260 } else {
261 int step_ms = s_platform->get_step_ms ? (int)s_platform->get_step_ms() : 60000;
262 allotment = tollgate_core_mining_shares_to_allotment_ms(
263 stats->hashrate_ghs, hashprice, price, step_ms);
264 }
265
266 if (allotment == 0) return;
267
268 tg_session_t *session = tollgate_core_session_find_by_ip(client_ip);
269 if (!session) {
270 if (strcmp(metric, "bytes") == 0) {
271 session = tollgate_core_session_create_bytes(client_ip, allotment);
272 } else {
273 session = tollgate_core_session_create(client_ip, allotment);
274 }
275 if (session) {
276 session->payment_method = TG_PAYMENT_MINING;
277 }
278 } else {
279 tollgate_core_session_extend(session, allotment);
280 }
281
282 if (s_platform->on_share_accepted) {
283 s_platform->on_share_accepted(difficulty);
284 }
285}
286
287double tollgate_core_calc_hashprice(double hashrate_ghs)
288{
289 if (!s_platform) return 0.0;
290
291 if (s_platform->get_hashprice_sats_per_ghs_day) {
292 uint64_t override = s_platform->get_hashprice_sats_per_ghs_day();
293 if (override > 0) {
294 return tollgate_core_mining_calc_hashprice_override(override);
295 }
296 }
297
298 return tollgate_core_mining_get_current_hashprice();
299}
300
301char *tollgate_core_get_mining_status_json(void)
302{
303 tollgate_stratum_proxy_stats_t proxy_stats = {0};
304 tollgate_core_stratum_proxy_get_stats(&proxy_stats);
305
306 cJSON *root = cJSON_CreateObject();
307
308 cJSON *proxy = cJSON_CreateObject();
309 cJSON_AddNumberToObject(proxy, "hashrate_ghs", proxy_stats.hashrate_ghs);
310 cJSON_AddNumberToObject(proxy, "total_shares", (double)proxy_stats.total_shares);
311 cJSON_AddNumberToObject(proxy, "total_accepted", (double)proxy_stats.total_accepted);
312 cJSON_AddNumberToObject(proxy, "total_rejected", (double)proxy_stats.total_rejected);
313 cJSON_AddNumberToObject(proxy, "active_miners", proxy_stats.active_miners);
314 cJSON_AddNumberToObject(proxy, "hashprice", proxy_stats.current_hashprice);
315 cJSON_AddNumberToObject(proxy, "nbits", (double)proxy_stats.nbits);
316 cJSON_AddItemToObject(root, "proxy", proxy);
317
318 char *json = cJSON_PrintUnformatted(root);
319 cJSON_Delete(root);
320 return json;
321}
322
238const tollgate_platform_t *tollgate_core_get_platform(void) 323const tollgate_platform_t *tollgate_core_get_platform(void)
239{ 324{
240 return s_platform; 325 return s_platform;
diff --git a/tests/unit/Makefile b/tests/unit/Makefile
index 2d89859..6f18dab 100644
--- a/tests/unit/Makefile
+++ b/tests/unit/Makefile
@@ -26,7 +26,7 @@ LDFLAGS := -lmbedcrypto -lcjson -lm
26 26
27SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o 27SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o
28 28
29TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_beacon_price test_market test_mint_health test_mining_payment test_stratum_proxy test_session_payment_method test_tollgate_client_mining test_firewall_sandbox test_relay_selector test_relay_validator test_portal test_stratum_client test_mint_health_core test_client_core test_relay_types test_sub_manager test_deletion 29TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_beacon_price test_market test_mint_health test_mining_payment test_stratum_proxy test_session_payment_method test_tollgate_client_mining test_firewall_sandbox test_relay_selector test_relay_validator test_portal test_stratum_client test_mint_health_core test_client_core test_relay_types test_sub_manager test_deletion test_mining_api
30 30
31.PHONY: all test clean $(TESTS) 31.PHONY: all test clean $(TESTS)
32 32
@@ -138,5 +138,8 @@ test_sub_manager: test_sub_manager.c
138test_deletion: test_deletion.c $(REPO_ROOT)/components/wisp_relay/relay_types.c 138test_deletion: test_deletion.c $(REPO_ROOT)/components/wisp_relay/relay_types.c
139 $(CC) $(CFLAGS) -I $(REPO_ROOT)/components/wisp_relay $^ -o $@ $(LDFLAGS) 139 $(CC) $(CFLAGS) -I $(REPO_ROOT)/components/wisp_relay $^ -o $@ $(LDFLAGS)
140 140
141test_mining_api: test_mining_api.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c
142 $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS)
143
141clean: 144clean:
142 rm -f $(TESTS) $(SECP256K1_OBJ) 145 rm -f $(TESTS) $(SECP256K1_OBJ)
diff --git a/tests/unit/test_mining_api.c b/tests/unit/test_mining_api.c
new file mode 100644
index 0000000..985992b
--- /dev/null
+++ b/tests/unit/test_mining_api.c
@@ -0,0 +1,123 @@
1#include "test_framework.h"
2#include "tollgate_core.h"
3#include "tollgate_core_mining.h"
4#include "tollgate_core_session.h"
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <math.h>
9
10static const char *s_mint_url = "https://test.mint.example";
11static const char *s_metric = "milliseconds";
12static uint16_t s_price = 21;
13static int32_t s_step_ms = 60000;
14static int32_t s_step_bytes = 22020096;
15static uint64_t s_hashprice_override = 0;
16static double s_last_share_diff = 0.0;
17
18static const char *mock_get_mint_url(void) { return s_mint_url; }
19static const char *mock_get_metric(void) { return s_metric; }
20static uint16_t mock_get_price(void) { return s_price; }
21static int32_t mock_get_step_ms(void) { return s_step_ms; }
22static int32_t mock_get_step_bytes(void) { return s_step_bytes; }
23static uint64_t mock_get_hashprice(void) { return s_hashprice_override; }
24static int64_t mock_get_time_ms(void) { return 1000000; }
25static void mock_on_share_accepted(double diff) { s_last_share_diff = diff; }
26static bool mock_spend_proofs(const char *t) { (void)t; return true; }
27
28static tollgate_platform_t make_test_platform(void)
29{
30 tollgate_platform_t p = {0};
31 p.get_mint_url = mock_get_mint_url;
32 p.get_metric = mock_get_metric;
33 p.get_price_sats = mock_get_price;
34 p.get_step_ms = mock_get_step_ms;
35 p.get_step_bytes = mock_get_step_bytes;
36 p.get_hashprice_sats_per_ghs_day = mock_get_hashprice;
37 p.get_time_ms = mock_get_time_ms;
38 p.on_share_accepted = mock_on_share_accepted;
39 p.spend_proofs = mock_spend_proofs;
40 return p;
41}
42
43int main(void)
44{
45 printf("=== test_mining_api ===\n");
46
47 tollgate_platform_t platform = make_test_platform();
48 esp_ip4_addr_t ap_ip = { .addr = 0x0101010A };
49 tollgate_core_init(&platform, ap_ip);
50
51 tollgate_core_mining_init();
52 tollgate_core_mining_set_current_nbits(0x1d00ffff);
53
54 printf("\n--- tollgate_core_calc_hashprice with nbits ---\n");
55 double hp1 = tollgate_core_calc_hashprice(0.0);
56 ASSERT(hp1 > 0.0, "hashprice from nbits is positive");
57 double expected_hp = tollgate_core_mining_get_current_hashprice();
58 ASSERT(fabs(hp1 - expected_hp) < 0.001, "matches mining module hashprice");
59
60 printf("\n--- tollgate_core_calc_hashprice with override ---\n");
61 s_hashprice_override = 500;
62 double hp2 = tollgate_core_calc_hashprice(0.0);
63 ASSERT(fabs(hp2 - 500.0) < 0.001, "override hashprice returned");
64 s_hashprice_override = 0;
65
66 printf("\n--- tollgate_core_calc_hashprice no platform ---\n");
67 double hp3 = tollgate_core_calc_hashprice(0.0);
68 ASSERT(hp3 == 0.0 || hp3 > 0.0, "NULL platform handled gracefully");
69 tollgate_core_init(&platform, ap_ip);
70 tollgate_core_mining_init();
71 tollgate_core_mining_set_current_nbits(0x1d00ffff);
72
73 printf("\n--- tollgate_core_get_mining_status_json ---\n");
74 char *json = tollgate_core_get_mining_status_json();
75 ASSERT(json != NULL, "mining status JSON returned");
76 ASSERT(strstr(json, "proxy") != NULL, "JSON contains proxy object");
77 ASSERT(strstr(json, "hashrate_ghs") != NULL, "JSON contains hashrate_ghs");
78 ASSERT(strstr(json, "total_shares") != NULL, "JSON contains total_shares");
79 ASSERT(strstr(json, "total_accepted") != NULL, "JSON contains total_accepted");
80 ASSERT(strstr(json, "hashprice") != NULL, "JSON contains hashprice");
81 printf(" JSON: %s\n", json);
82 free(json);
83
84 printf("\n--- tollgate_core_on_share_accepted creates session ---\n");
85 uint32_t client_ip = 0x0202020A;
86 tollgate_core_on_share_accepted(client_ip, 1.0);
87
88 tg_session_t *session = tollgate_core_session_find_by_ip(client_ip);
89 ASSERT(session != NULL, "session created for miner");
90 ASSERT(session->active, "session is active");
91 ASSERT(session->payment_method == TG_PAYMENT_MINING, "payment method is MINING");
92 ASSERT(session->allotment_ms > 0, "session has positive allotment");
93 printf(" allotment_ms = %llu\n", (unsigned long long)session->allotment_ms);
94
95 printf("\n--- tollgate_core_on_share_accepted extends session ---\n");
96 uint64_t first_allotment = session->allotment_ms;
97 tollgate_core_on_share_accepted(client_ip, 1.0);
98 ASSERT(session->allotment_ms > first_allotment, "session allotment increased");
99 printf(" new allotment_ms = %llu\n", (unsigned long long)session->allotment_ms);
100
101 printf("\n--- tollgate_core_on_share_accepted calls platform callback ---\n");
102 s_last_share_diff = 0.0;
103 tollgate_core_on_share_accepted(client_ip, 42.5);
104 ASSERT(fabs(s_last_share_diff - 42.5) < 0.001, "platform callback received difficulty");
105
106 printf("\n--- tollgate_core_on_share_accepted with bytes metric ---\n");
107 s_metric = "bytes";
108 uint32_t bytes_client = 0x0303030A;
109 tollgate_core_on_share_accepted(bytes_client, 1.0);
110 tg_session_t *bytes_session = tollgate_core_session_find_by_ip(bytes_client);
111 ASSERT(bytes_session != NULL, "bytes session created");
112 ASSERT(bytes_session->allotment_bytes > 0, "bytes allotment positive");
113 printf(" allotment_bytes = %llu\n", (unsigned long long)bytes_session->allotment_bytes);
114 s_metric = "milliseconds";
115
116 printf("\n--- tollgate_core_on_share_accepted no platform ---\n");
117 tollgate_core_init(NULL, ap_ip);
118 tollgate_core_on_share_accepted(0x0404040A, 1.0);
119 ASSERT(1, "no crash with NULL platform");
120 tollgate_core_init(&platform, ap_ip);
121
122 TEST_SUMMARY();
123}