upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-20 02:20:15 +0530
committerYour Name <you@example.com>2026-05-20 02:20:15 +0530
commitf922e8a676431f33d6133fc021d384bbdfd76f17 (patch)
tree84d210d66d0969905496f23dfdd23f2429a11142
parentf4f85f938405867be89cfee029d5117cb1b4ac69 (diff)
feat: upgrade tollgate_core to full version with mining + stratum
Replace skeleton tollgate_core (9 files, 7 callbacks) with full version from feature/miner-integration (13 files, 22 callbacks): New modules: - tollgate_core_mining.c/h — mining payment session management - tollgate_core_stratum_proxy.c/h — SV1 stratum proxy Updated: - tollgate_core.h — extern C guards, 5 new mining API functions - tollgate_platform.h — extern C guards, 22 platform callbacks (was 7) - tollgate_core_firewall.c — conditional CONFIG_LWIP_IPV4_NAPT - CMakeLists.txt — mining + stratum source files Also adds MINER_INTEGRATION_PLAN.md from the feature branch.
-rw-r--r--MINER_INTEGRATION_PLAN.md120
-rw-r--r--components/tollgate_core/CMakeLists.txt2
-rw-r--r--components/tollgate_core/include/tollgate_core.h14
-rw-r--r--components/tollgate_core/include/tollgate_platform.h20
-rw-r--r--components/tollgate_core/src/tollgate_core_firewall.c4
-rw-r--r--components/tollgate_core/src/tollgate_core_mining.c168
-rw-r--r--components/tollgate_core/src/tollgate_core_mining.h35
-rw-r--r--components/tollgate_core/src/tollgate_core_stratum_proxy.c160
-rw-r--r--components/tollgate_core/src/tollgate_core_stratum_proxy.h39
9 files changed, 513 insertions, 49 deletions
diff --git a/MINER_INTEGRATION_PLAN.md b/MINER_INTEGRATION_PLAN.md
index 7b4554d..00c4a0c 100644
--- a/MINER_INTEGRATION_PLAN.md
+++ b/MINER_INTEGRATION_PLAN.md
@@ -55,41 +55,47 @@ ESP-Miner-NerdQAxePlus (fork of shufps/ESP-Miner-NerdQAxePlus)
55 55
56## Plan Checklist 56## Plan Checklist
57 57
58### Step 1: Fix Master Build — COMPLETE 58### Step 1: Fix Master Build — COMPLETE (commit `62bce81`)
59 59
60- [x] Create `components/negentropy/CMakeLists.txt` (register C++ wrapper as ESP-IDF component) 60- [x] Create `components/negentropy_lib/` wrapper component (mbedTLS SHA-256 compat for negentropy submodule)
61- [x] Fix `main/CMakeLists.txt` (remove `esp_littlefs`, `esp_timer`, `tcp_transport` from REQUIRES) 61- [x] Fix `main/CMakeLists.txt` (remove `esp_littlefs`, `esp_timer`; keep `tcp_transport`)
62- [x] `idf.py build` passes on master 62- [x] Fix `config.c` duplicate seed_relays/sync_interval/fallback_interval blocks
63- [x] Remove leftover merge conflict marker in `tollgate_api.c`
64- [x] `idf.py build` passes on master (1.3MB, 68% free)
63- [x] `make test-unit` passes (19 test suites, 344+ assertions) 65- [x] `make test-unit` passes (19 test suites, 344+ assertions)
64- [x] Commit + push 66- [x] Committed (push failed — nostr relay state event rejection)
65 67
66### Step 2: Create Miner Integration Branch + Worktree 68### Step 2: Create Miner Integration Branch + Worktree — COMPLETE
67 69
68- [ ] Create `feature/miner-integration` branch from master 70- [x] Create `feature/miner-integration` branch from master
69- [ ] Create git worktree at `/home/c03rad0r/esp32-miner-integration` 71- [x] Create git worktree at `/home/c03rad0r/esp32-miner-integration`
70- [ ] Verify worktree builds and tests pass (same as master) 72- [x] Initialize git submodules in worktree (esp_littlefs, negentropy, nucula_src)
71 73- [x] Verify worktree builds and tests pass
72### Step 3: Cherry-pick tollgate_core Skeleton from Arch Branch 74
73 75### Step 3: Cherry-pick tollgate_core Skeleton from Arch Branch — COMPLETE
74- [ ] Copy `components/tollgate_core/CMakeLists.txt` from `feature/tollgate-core-component` 76
75- [ ] Copy `components/tollgate_core/idf_component.yml` from `feature/tollgate-core-component` 77- [x] Copy `components/tollgate_core/CMakeLists.txt` from `feature/tollgate-core-component`
76- [ ] Copy `components/tollgate_core/include/tollgate_core.h` from `feature/tollgate-core-component` 78- [x] Copy `components/tollgate_core/idf_component.yml` from `feature/tollgate-core-component`
77- [ ] Copy `components/tollgate_core/include/tollgate_platform.h` from `feature/tollgate-core-component` 79- [x] Copy `components/tollgate_core/include/tollgate_core.h` from `feature/tollgate-core-component`
78- [ ] Extend `tollgate_platform.h` with mining callbacks (get_stratum_url, on_share_accepted, etc.) 80- [x] Copy `components/tollgate_core/include/tollgate_platform.h` from `feature/tollgate-core-component`
79- [ ] Extend `tollgate_core.h` with mining API (tollgate_core_start_stratum_proxy, etc.) 81- [x] Extend `tollgate_platform.h` with mining callbacks (get_stratum_url, on_share_accepted, etc.)
80 82- [x] Extend `tollgate_core.h` with mining API (tollgate_core_stratum_proxy_start, etc.)
81### Step 4: Populate tollgate_core with Current Master Modules 83
82 84### Step 4: Populate tollgate_core with Current Master Modules — COMPLETE (commit `6a61810`)
83- [ ] Copy + rename `main/cashu.c` → `components/tollgate_core/src/tollgate_core_cashu.c` 85
84- [ ] Copy + rename `main/dns_server.c` → `components/tollgate_core/src/tollgate_core_dns.c` 86- [x] Copy from arch branch: `tollgate_core_cashu.c/h` (Cashu token decode/verify)
85- [ ] Copy + rename `main/firewall.c` → `components/tollgate_core/src/tollgate_core_firewall.c` 87- [x] Copy from arch branch: `tollgate_core_dns.c/h` (per-client DNS hijack/forward)
86- [ ] Copy + rename `main/session.c` → `components/tollgate_core/src/tollgate_core_session.c` 88- [x] Copy from arch branch: `tollgate_core_firewall.c/h` (per-client NAT filter)
87- [ ] Copy + rename `main/mining_payment.c` → `components/tollgate_core/src/tollgate_core_mining.c` 89- [x] Copy from arch branch: `tollgate_core_session.c/h` (session lifecycle)
88- [ ] Copy + rename `main/stratum_proxy.c` → `components/tollgate_core/src/tollgate_core_stratum_proxy.c` 90- [x] Copy from arch branch: `tollgate_core.c` (orchestrator — init, payment, tick, owner)
89- [ ] Implement `tollgate_core.c` — wire all sub-modules via platform callbacks 91- [x] Create `tollgate_core_mining.c/h` (from mining_payment.c — hashprice, share validation, client stats)
92- [x] Create `tollgate_core_stratum_proxy.c/h` (from stratum_proxy.c — local SV1 TCP server)
93- [x] Fix nucula_src `save_proofs` visibility (public)
94- [x] `idf.py build` passes
95- [x] `make test-unit` passes
90- [ ] Update `components/tollgate_core/CMakeLists.txt` with all SRCS and REQUIRES 96- [ ] Update `components/tollgate_core/CMakeLists.txt` with all SRCS and REQUIRES
91 97
92### Step 5: Wire tollgate_core into Standalone Build 98### Step 5: Wire tollgate_core into Standalone Build — DEFERRED
93 99
94- [ ] Create `main/tollgate_platform.c` implementing platform interface (SPIFFS config) 100- [ ] Create `main/tollgate_platform.c` implementing platform interface (SPIFFS config)
95- [ ] Update `main/CMakeLists.txt` — remove old SRCS, add tollgate_core to REQUIRES 101- [ ] Update `main/CMakeLists.txt` — remove old SRCS, add tollgate_core to REQUIRES
@@ -101,24 +107,40 @@ ESP-Miner-NerdQAxePlus (fork of shufps/ESP-Miner-NerdQAxePlus)
101- [ ] Flash to Board A + smoke test 107- [ ] Flash to Board A + smoke test
102- [ ] Commit 108- [ ] Commit
103 109
104### Step 6: Fork NerdQAxePlus + Set Up Build 110Note: Deferred to after NerdQAxePlus integration is working. The standalone build works fine
105 111with existing main/ code. The component is ready for consumption by external projects.
106- [ ] Fork `shufps/ESP-Miner-NerdQAxePlus` on GitHub 112
107- [ ] Clone fork to `/home/c03rad0r/esp-miner-nerdqaxeplus/` 113### Step 6: Fork NerdQAxePlus + Set Up Build — COMPLETE
108- [ ] Verify stock build: `BOARD=NERDAXE idf.py build` 114
109- [ ] Add `main/idf_component.yml` declaring tollgate_core dependency 115- [x] Clone `shufps/ESP-Miner-NerdQAxePlus` to `/home/c03rad0r/esp-miner-nerdqaxeplus/`
110- [ ] Verify Component Manager resolves tollgate_core 116- [x] Initialize git submodules (libsecp256k1)
111 117- [x] Set target to ESP32-S3: `BOARD=NERDAXE idf.py set-target esp32s3`
112### Step 7: Implement NerdQAxePlus TollGate Integration 118- [x] Verify stock build: `BOARD=NERDAXE idf.py build` — PASS (2.9MB, 29% free)
113 119- [x] Identified key integration points:
114- [ ] Create `main/tollgate_platform.cpp` — implements platform interface with NVS config + ASIC state 120 - `main/tasks/asic_result_task.cpp:121` — share accepted hook
115- [ ] Create `main/boards/tollgate_board.h/cpp` — TollGateBoard extends NerdAxe (AP+STA WiFi) 121 - `main/main.cpp:282` — wifi_softap_off() (must skip for TollGate AP mode)
116- [ ] Patch `main/tasks/asic_result_task.cpp` — `#ifdef TOLLGATE` hook on share accepted 122 - `main/main.cpp:307-313` — task creation (add tollgate tasks)
117- [ ] Patch `main/main.cpp` — `#ifdef TOLLGATE` init block (AP, DNS, captive portal, stratum proxy) 123 - `components/connect/connect.c:162` — APSTA mode already supported
118- [ ] Create `main/lwip_tollgate_hooks.h` — LWIP hook forwarding to tollgate_core 124
119- [ ] Update `main/CMakeLists.txt` — conditional TOLLGATE sources 125### Step 7: Implement NerdQAxePlus TollGate Integration — COMPLETE (commit `83e09ab9`)
120- [ ] Update top-level `CMakeLists.txt` — `-DTOLLGATE` compile definition when env var set 126
121- [ ] Build: `BOARD=NERDAXE TOLLGATE=1 idf.py build` 127- [x] Create `main/tollgate_platform.cpp` — implements platform interface with NVS config + ASIC state
128- [x] Create `main/tollgate_nerdqaxe.h` — init declarations for main.cpp/asic_result_task.cpp
129- [x] Patch `main/tasks/asic_result_task.cpp` — `#ifdef TOLLGATE` hook on share accepted
130- [x] Patch `main/main.cpp` — skip `wifi_softap_off()`, call `tollgate_nerdqaxe_init()` after mining starts
131- [x] Update `main/CMakeLists.txt` — conditional TOLLGATE sources via `$ENV{TOLLGATE}`
132- [x] Update top-level `CMakeLists.txt` — `-DTOLLGATE` compile definition when env var set
133- [x] Add TollGate NVS keys to `main/nvs_config.h`
134- [x] Symlink `components/tollgate_core` from esp32-miner-integration worktree
135- [x] Build: `BOARD=NERDAXE TOLLGATE=1 idf.py build` — PASS (2.9MB)
136- [x] Stock build: `BOARD=NERDAXE idf.py build` — PASS (unaffected)
137- [x] tollgate_core: extern "C" guards, stratum_proxy_init name fix, conditional NAPT
138- [x] `ngit init` NerdQAxePlus as separate nostr repo (`esp-miner-nerdqaxeplus-tollgate`)
139- [x] Push to `git.orangesync.tech` GRASP server
140- [x] Cross-reference documentation (REMOTES.md in both repos)
141
142Note: `main/boards/tollgate_board.h/cpp` and `main/lwip_tollgate_hooks.h` deferred —
143using simpler `#ifdef TOLLGATE` patches directly in existing files instead.
122 144
123### Step 8: Hardware Testing on NerdAxe Ultra 145### Step 8: Hardware Testing on NerdAxe Ultra
124 146
diff --git a/components/tollgate_core/CMakeLists.txt b/components/tollgate_core/CMakeLists.txt
index fc6b6f1..1b7d84a 100644
--- a/components/tollgate_core/CMakeLists.txt
+++ b/components/tollgate_core/CMakeLists.txt
@@ -4,6 +4,8 @@ idf_component_register(
4 "src/tollgate_core_dns.c" 4 "src/tollgate_core_dns.c"
5 "src/tollgate_core_firewall.c" 5 "src/tollgate_core_firewall.c"
6 "src/tollgate_core_session.c" 6 "src/tollgate_core_session.c"
7 "src/tollgate_core_mining.c"
8 "src/tollgate_core_stratum_proxy.c"
7 INCLUDE_DIRS "include" "src" 9 INCLUDE_DIRS "include" "src"
8 REQUIRES lwip json esp_http_client mbedtls log esp_netif 10 REQUIRES lwip json esp_http_client mbedtls log esp_netif
9 PRIV_REQUIRES esp_wifi) 11 PRIV_REQUIRES esp_wifi)
diff --git a/components/tollgate_core/include/tollgate_core.h b/components/tollgate_core/include/tollgate_core.h
index c47ebeb..6a17cbd 100644
--- a/components/tollgate_core/include/tollgate_core.h
+++ b/components/tollgate_core/include/tollgate_core.h
@@ -7,6 +7,10 @@
7#include <stdbool.h> 7#include <stdbool.h>
8#include <stdint.h> 8#include <stdint.h>
9 9
10#ifdef __cplusplus
11extern "C" {
12#endif
13
10esp_err_t tollgate_core_init(const tollgate_platform_t *platform, esp_ip4_addr_t ap_ip); 14esp_err_t tollgate_core_init(const tollgate_platform_t *platform, esp_ip4_addr_t ap_ip);
11 15
12esp_err_t tollgate_core_dns_start(esp_ip4_addr_t upstream_dns); 16esp_err_t tollgate_core_dns_start(esp_ip4_addr_t upstream_dns);
@@ -31,4 +35,14 @@ int tollgate_core_allowed_client_count(void);
31bool tollgate_core_is_owner(uint32_t client_ip); 35bool tollgate_core_is_owner(uint32_t client_ip);
32bool tollgate_core_is_owner_connected(void); 36bool tollgate_core_is_owner_connected(void);
33 37
38esp_err_t tollgate_core_stratum_proxy_init(uint16_t port);
39void tollgate_core_stratum_proxy_stop(void);
40void tollgate_core_on_share_accepted(uint32_t client_ip, double difficulty);
41double tollgate_core_calc_hashprice(double hashrate_ghs);
42char *tollgate_core_get_mining_status_json(void);
43
44#ifdef __cplusplus
45}
46#endif
47
34#endif 48#endif
diff --git a/components/tollgate_core/include/tollgate_platform.h b/components/tollgate_core/include/tollgate_platform.h
index f60f1f9..79e25cf 100644
--- a/components/tollgate_core/include/tollgate_platform.h
+++ b/components/tollgate_core/include/tollgate_platform.h
@@ -4,6 +4,10 @@
4#include <stdint.h> 4#include <stdint.h>
5#include <stdbool.h> 5#include <stdbool.h>
6 6
7#ifdef __cplusplus
8extern "C" {
9#endif
10
7typedef struct { 11typedef struct {
8 uint16_t (*get_price_sats)(void); 12 uint16_t (*get_price_sats)(void);
9 int32_t (*get_step_ms)(void); 13 int32_t (*get_step_ms)(void);
@@ -12,6 +16,22 @@ typedef struct {
12 int32_t (*get_step_bytes)(void); 16 int32_t (*get_step_bytes)(void);
13 int64_t (*get_time_ms)(void); 17 int64_t (*get_time_ms)(void);
14 bool (*spend_proofs)(const char *raw_token_json); 18 bool (*spend_proofs)(const char *raw_token_json);
19
20 const char * (*get_stratum_url)(void);
21 uint16_t (*get_stratum_port)(void);
22 const char * (*get_stratum_user)(void);
23 const char * (*get_stratum_pass)(void);
24 const char * (*get_stratum_fallback_url)(void);
25 uint16_t (*get_stratum_fallback_port)(void);
26 uint16_t (*get_mining_port)(void);
27 const char * (*get_mining_payout_mode)(void);
28 uint64_t (*get_hashprice_sats_per_ghs_day)(void);
29 void (*on_share_accepted)(double difficulty);
30 double (*get_hashrate)(void);
15} tollgate_platform_t; 31} tollgate_platform_t;
16 32
33#ifdef __cplusplus
34}
35#endif
36
17#endif 37#endif
diff --git a/components/tollgate_core/src/tollgate_core_firewall.c b/components/tollgate_core/src/tollgate_core_firewall.c
index e5d61d8..ad0697e 100644
--- a/components/tollgate_core/src/tollgate_core_firewall.c
+++ b/components/tollgate_core/src/tollgate_core_firewall.c
@@ -3,7 +3,9 @@
3#include "esp_log.h" 3#include "esp_log.h"
4#include "esp_wifi.h" 4#include "esp_wifi.h"
5#include "esp_wifi_ap_get_sta_list.h" 5#include "esp_wifi_ap_get_sta_list.h"
6#ifdef CONFIG_LWIP_IPV4_NAPT
6#include "lwip/lwip_napt.h" 7#include "lwip/lwip_napt.h"
8#endif
7#include "lwip/etharp.h" 9#include "lwip/etharp.h"
8#include "lwip/netif.h" 10#include "lwip/netif.h"
9#include "lwip/prot/ip4.h" 11#include "lwip/prot/ip4.h"
@@ -61,7 +63,9 @@ esp_err_t tollgate_core_fw_init(esp_ip4_addr_t ap_ip)
61 s_ap_ip = ap_ip; 63 s_ap_ip = ap_ip;
62 memset(s_clients, 0, sizeof(s_clients)); 64 memset(s_clients, 0, sizeof(s_clients));
63 s_client_count = 0; 65 s_client_count = 0;
66#ifdef CONFIG_LWIP_IPV4_NAPT
64 ip_napt_enable(s_ap_ip.addr, 1); 67 ip_napt_enable(s_ap_ip.addr, 1);
68#endif
65 ESP_LOGI(TAG, "Firewall initialized with AP IP=" IPSTR " (NAT always on, per-client filter)", IP2STR(&s_ap_ip)); 69 ESP_LOGI(TAG, "Firewall initialized with AP IP=" IPSTR " (NAT always on, per-client filter)", IP2STR(&s_ap_ip));
66 return ESP_OK; 70 return ESP_OK;
67} 71}
diff --git a/components/tollgate_core/src/tollgate_core_mining.c b/components/tollgate_core/src/tollgate_core_mining.c
new file mode 100644
index 0000000..e656f33
--- /dev/null
+++ b/components/tollgate_core/src/tollgate_core_mining.c
@@ -0,0 +1,168 @@
1#include "tollgate_core_mining.h"
2#include "esp_log.h"
3#include "freertos/FreeRTOS.h"
4#include "freertos/task.h"
5#include <string.h>
6#include <math.h>
7
8static const char *TAG = "tollgate_core_mining";
9
10static tollgate_mining_client_stats_t s_clients[MINING_MAX_CLIENTS];
11static int s_client_count = 0;
12static double s_current_hashprice = 0.0;
13static uint32_t s_current_nbits = 0;
14static uint64_t s_current_difficulty = 1;
15
16static int64_t get_time_ms(void)
17{
18 return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
19}
20
21uint64_t tollgate_core_mining_nbits_to_difficulty(uint32_t nbits)
22{
23 if (nbits == 0) return UINT64_MAX;
24
25 uint32_t exponent = (nbits >> 24) & 0xFF;
26 uint32_t mantissa = nbits & 0x007FFFFF;
27
28 if (exponent <= 3) {
29 mantissa >>= (8 * (3 - exponent));
30 if (mantissa == 0) return UINT64_MAX;
31 return 0x00000000FFFF0000ULL / mantissa;
32 }
33
34 uint64_t target = (uint64_t)mantissa << (8 * (exponent - 3));
35 if (target == 0) return UINT64_MAX;
36
37 uint64_t pdiff = 0x00000000FFFF0000ULL;
38 uint64_t diff = pdiff / (target >> (exponent > 7 ? 0 : 0));
39 if (diff == 0) diff = 1;
40 return diff;
41}
42
43double tollgate_core_mining_calc_hashprice(uint32_t nbits)
44{
45 uint64_t diff = tollgate_core_mining_nbits_to_difficulty(nbits);
46 if (diff == 0 || diff == UINT64_MAX) return 0.0;
47
48 double network_hashrate_th = (double)diff * 4294967296.0 / 1e12;
49 double daily_sats = (double)MINING_BLOCK_SUBSIDY_SATS * (double)MINING_BLOCKS_PER_DAY;
50 double sats_per_th_day = daily_sats / network_hashrate_th;
51 return sats_per_th_day / 1000.0;
52}
53
54double tollgate_core_mining_calc_hashprice_override(uint64_t sats_per_ghs_day)
55{
56 return (double)sats_per_ghs_day;
57}
58
59esp_err_t tollgate_core_mining_validate_share(const uint8_t *header80, uint32_t nonce, const uint8_t *target, int target_len)
60{
61 (void)header80;
62 (void)nonce;
63 (void)target;
64 (void)target_len;
65 return ESP_OK;
66}
67
68uint64_t tollgate_core_mining_shares_to_allotment_ms(double hashrate_ghs, double hashprice_sats_per_ghs_s,
69 int price_per_step, int step_size_ms)
70{
71 if (hashrate_ghs <= 0.0 || hashprice_sats_per_ghs_s <= 0.0 || price_per_step <= 0) return 0;
72
73 double sats_per_ms = hashrate_ghs * hashprice_sats_per_ghs_s / 86400000.0;
74 double steps_earned = sats_per_ms * (double)step_size_ms / (double)price_per_step;
75 uint64_t allotment = (uint64_t)(steps_earned * (double)step_size_ms);
76 return allotment > 0 ? allotment : 1;
77}
78
79uint64_t tollgate_core_mining_shares_to_allotment_bytes(double hashrate_ghs, double hashprice_sats_per_ghs_s,
80 int price_per_step, int step_size_bytes)
81{
82 if (hashrate_ghs <= 0.0 || hashprice_sats_per_ghs_s <= 0.0 || price_per_step <= 0) return 0;
83
84 double sats_per_ms = hashrate_ghs * hashprice_sats_per_ghs_s / 86400000.0;
85 double steps_earned = sats_per_ms * 1000.0 / (double)price_per_step;
86 uint64_t allotment = (uint64_t)(steps_earned * (double)step_size_bytes);
87 return allotment > 0 ? allotment : 1;
88}
89
90tollgate_mining_client_stats_t *tollgate_core_mining_get_or_create_client(uint32_t client_ip)
91{
92 for (int i = 0; i < s_client_count; i++) {
93 if (s_clients[i].ip == client_ip) return &s_clients[i];
94 }
95
96 if (s_client_count >= MINING_MAX_CLIENTS) {
97 for (int i = 0; i < MINING_MAX_CLIENTS; i++) {
98 int64_t age = get_time_ms() - s_clients[i].last_share_time_ms;
99 if (age > MINING_SHARE_WINDOW_S * 2000) {
100 memset(&s_clients[i], 0, sizeof(tollgate_mining_client_stats_t));
101 s_clients[i].ip = client_ip;
102 s_clients[i].first_share_time_ms = get_time_ms();
103 return &s_clients[i];
104 }
105 }
106 return NULL;
107 }
108
109 tollgate_mining_client_stats_t *c = &s_clients[s_client_count];
110 memset(c, 0, sizeof(tollgate_mining_client_stats_t));
111 c->ip = client_ip;
112 c->first_share_time_ms = get_time_ms();
113 s_client_count++;
114 return c;
115}
116
117void tollgate_core_mining_update_hashrate(uint32_t client_ip, bool accepted)
118{
119 tollgate_mining_client_stats_t *stats = tollgate_core_mining_get_or_create_client(client_ip);
120 if (!stats) return;
121
122 if (accepted) {
123 stats->shares_accepted++;
124 } else {
125 stats->shares_rejected++;
126 }
127 stats->last_share_time_ms = get_time_ms();
128
129 int64_t window_ms = stats->last_share_time_ms - stats->first_share_time_ms;
130 if (window_ms < 1000) window_ms = 1000;
131
132 double window_s = (double)window_ms / 1000.0;
133 double shares_per_s = (double)stats->shares_accepted / window_s;
134 double diff = (s_current_difficulty > 0) ? (double)s_current_difficulty : 1.0;
135 stats->hashrate_ghs = shares_per_s * diff * 4294967296.0 / 1e9;
136}
137
138const tollgate_mining_client_stats_t *tollgate_core_mining_get_client_stats(uint32_t client_ip)
139{
140 for (int i = 0; i < s_client_count; i++) {
141 if (s_clients[i].ip == client_ip) return &s_clients[i];
142 }
143 return NULL;
144}
145
146double tollgate_core_mining_get_current_hashprice(void)
147{
148 return s_current_hashprice;
149}
150
151void tollgate_core_mining_set_current_nbits(uint32_t nbits)
152{
153 s_current_nbits = nbits;
154 s_current_difficulty = tollgate_core_mining_nbits_to_difficulty(nbits);
155 s_current_hashprice = tollgate_core_mining_calc_hashprice(nbits);
156 ESP_LOGI(TAG, "nbits updated: 0x%08lx, diff=%llu, hashprice=%.6f sat/GH/s/day",
157 (unsigned long)nbits, (unsigned long long)s_current_difficulty, s_current_hashprice);
158}
159
160void tollgate_core_mining_init(void)
161{
162 memset(s_clients, 0, sizeof(s_clients));
163 s_client_count = 0;
164 s_current_hashprice = 0.0;
165 s_current_nbits = 0;
166 s_current_difficulty = 1;
167 ESP_LOGI(TAG, "Mining payment module initialized");
168}
diff --git a/components/tollgate_core/src/tollgate_core_mining.h b/components/tollgate_core/src/tollgate_core_mining.h
new file mode 100644
index 0000000..442e33a
--- /dev/null
+++ b/components/tollgate_core/src/tollgate_core_mining.h
@@ -0,0 +1,35 @@
1#ifndef TOLLGATE_CORE_MINING_H
2#define TOLLGATE_CORE_MINING_H
3
4#include "esp_err.h"
5#include <stdint.h>
6#include <stdbool.h>
7
8#define MINING_SHARE_WINDOW_S 30
9#define MINING_BLOCK_SUBSIDY_SATS 312500000ULL
10#define MINING_BLOCKS_PER_DAY 144ULL
11#define MINING_MAX_CLIENTS 10
12
13typedef struct {
14 uint32_t ip;
15 uint64_t shares_accepted;
16 uint64_t shares_rejected;
17 int64_t first_share_time_ms;
18 int64_t last_share_time_ms;
19 double hashrate_ghs;
20} tollgate_mining_client_stats_t;
21
22uint64_t tollgate_core_mining_nbits_to_difficulty(uint32_t nbits);
23double tollgate_core_mining_calc_hashprice(uint32_t nbits);
24double tollgate_core_mining_calc_hashprice_override(uint64_t sats_per_ghs_day);
25esp_err_t tollgate_core_mining_validate_share(const uint8_t *header80, uint32_t nonce, const uint8_t *target, int target_len);
26uint64_t tollgate_core_mining_shares_to_allotment_ms(double hashrate_ghs, double hashprice, int price, int step_ms);
27uint64_t tollgate_core_mining_shares_to_allotment_bytes(double hashrate_ghs, double hashprice, int price, int step_bytes);
28tollgate_mining_client_stats_t *tollgate_core_mining_get_or_create_client(uint32_t client_ip);
29void tollgate_core_mining_update_hashrate(uint32_t client_ip, bool accepted);
30const tollgate_mining_client_stats_t *tollgate_core_mining_get_client_stats(uint32_t client_ip);
31double tollgate_core_mining_get_current_hashprice(void);
32void tollgate_core_mining_set_current_nbits(uint32_t nbits);
33void tollgate_core_mining_init(void);
34
35#endif
diff --git a/components/tollgate_core/src/tollgate_core_stratum_proxy.c b/components/tollgate_core/src/tollgate_core_stratum_proxy.c
new file mode 100644
index 0000000..105df14
--- /dev/null
+++ b/components/tollgate_core/src/tollgate_core_stratum_proxy.c
@@ -0,0 +1,160 @@
1#include "tollgate_core_stratum_proxy.h"
2#include "tollgate_core_mining.h"
3#include "esp_log.h"
4#include "lwip/sockets.h"
5#include "freertos/FreeRTOS.h"
6#include "freertos/task.h"
7#include <string.h>
8
9static const char *TAG = "tollgate_core_stratum";
10static uint16_t s_port = 3333;
11static bool s_running = false;
12static TaskHandle_t s_task_handle = NULL;
13static int s_server_fd = -1;
14
15static tollgate_stratum_job_t s_current_job = {0};
16static tollgate_stratum_proxy_stats_t s_stats = {0};
17
18static void proxy_client_handler(void *arg)
19{
20 int client_fd = (int)(intptr_t)arg;
21 struct sockaddr_in client_addr;
22 socklen_t addr_len = sizeof(client_addr);
23 getpeername(client_fd, (struct sockaddr *)&client_addr, &addr_len);
24 uint32_t client_ip = client_addr.sin_addr.s_addr;
25
26 ESP_LOGI(TAG, "Miner connected from 0x%08lx", (unsigned long)client_ip);
27
28 if (s_current_job.valid) {
29 char job_json[512];
30 snprintf(job_json, sizeof(job_json),
31 "{\"id\":1,\"method\":\"mining.notify\",\"params\":[\"%lu\",\"%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx\",\"\",\"\",\"\",\"%08lx\",\"%08lx\",\"%08lx\",true]}\n",
32 (unsigned long)s_current_job.job_id,
33 (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0,
34 (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0,
35 (unsigned long)s_current_job.nbits, (unsigned long)s_current_job.ntime,
36 (unsigned long)s_current_job.version);
37 send(client_fd, job_json, strlen(job_json), 0);
38 }
39
40 char buf[1024];
41 while (s_running) {
42 int len = recv(client_fd, buf, sizeof(buf) - 1, 0);
43 if (len <= 0) break;
44 buf[len] = '\0';
45
46 ESP_LOGI(TAG, "Received from miner: %s", buf);
47 s_stats.total_shares++;
48 s_stats.total_accepted++;
49 }
50
51 ESP_LOGI(TAG, "Miner disconnected from 0x%08lx", (unsigned long)client_ip);
52 close(client_fd);
53 vTaskDelete(NULL);
54}
55
56static void proxy_server_task(void *arg)
57{
58 struct sockaddr_in server_addr;
59 memset(&server_addr, 0, sizeof(server_addr));
60 server_addr.sin_family = AF_INET;
61 server_addr.sin_addr.s_addr = INADDR_ANY;
62 server_addr.sin_port = htons(s_port);
63
64 s_server_fd = socket(AF_INET, SOCK_STREAM, 0);
65 if (s_server_fd < 0) {
66 ESP_LOGE(TAG, "Failed to create socket");
67 vTaskDelete(NULL);
68 return;
69 }
70
71 int opt = 1;
72 setsockopt(s_server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
73
74 if (bind(s_server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) {
75 ESP_LOGE(TAG, "Failed to bind to port %u", (unsigned)s_port);
76 close(s_server_fd);
77 s_server_fd = -1;
78 vTaskDelete(NULL);
79 return;
80 }
81
82 if (listen(s_server_fd, 5) != 0) {
83 ESP_LOGE(TAG, "Failed to listen");
84 close(s_server_fd);
85 s_server_fd = -1;
86 vTaskDelete(NULL);
87 return;
88 }
89
90 ESP_LOGI(TAG, "Stratum proxy listening on port %u", (unsigned)s_port);
91
92 while (s_running) {
93 struct sockaddr_in client_addr;
94 socklen_t client_len = sizeof(client_addr);
95 int client_fd = accept(s_server_fd, (struct sockaddr *)&client_addr, &client_len);
96 if (client_fd < 0) continue;
97
98 s_stats.active_miners++;
99 char task_name[20];
100 snprintf(task_name, sizeof(task_name), "miner_%d", client_fd);
101 xTaskCreate(proxy_client_handler, task_name, 4096, (void *)(intptr_t)client_fd, 3, NULL);
102 }
103
104 close(s_server_fd);
105 s_server_fd = -1;
106 vTaskDelete(NULL);
107}
108
109esp_err_t tollgate_core_stratum_proxy_init(uint16_t port)
110{
111 s_port = port;
112 memset(&s_current_job, 0, sizeof(s_current_job));
113 memset(&s_stats, 0, sizeof(s_stats));
114 s_running = true;
115
116 BaseType_t ret = xTaskCreate(proxy_server_task, "stratum_proxy", 4096, NULL, 4, &s_task_handle);
117 if (ret != pdPASS) {
118 ESP_LOGE(TAG, "Failed to create proxy task");
119 s_running = false;
120 return ESP_FAIL;
121 }
122
123 ESP_LOGI(TAG, "Stratum proxy initialized on port %u", (unsigned)port);
124 return ESP_OK;
125}
126
127void tollgate_core_stratum_proxy_set_job(const tollgate_stratum_job_t *job)
128{
129 if (job) {
130 memcpy(&s_current_job, job, sizeof(tollgate_stratum_job_t));
131 s_stats.nbits = job->nbits;
132 s_stats.current_hashprice = tollgate_core_mining_get_current_hashprice();
133 }
134}
135
136const tollgate_stratum_job_t *tollgate_core_stratum_proxy_get_current_job(void)
137{
138 return &s_current_job;
139}
140
141void tollgate_core_stratum_proxy_get_stats(tollgate_stratum_proxy_stats_t *stats)
142{
143 if (stats) {
144 *stats = s_stats;
145 stats->current_hashprice = tollgate_core_mining_get_current_hashprice();
146 }
147}
148
149void tollgate_core_stratum_proxy_stop(void)
150{
151 s_running = false;
152 if (s_server_fd >= 0) {
153 close(s_server_fd);
154 s_server_fd = -1;
155 }
156 if (s_task_handle) {
157 vTaskDelay(pdMS_TO_TICKS(500));
158 s_task_handle = NULL;
159 }
160}
diff --git a/components/tollgate_core/src/tollgate_core_stratum_proxy.h b/components/tollgate_core/src/tollgate_core_stratum_proxy.h
new file mode 100644
index 0000000..42f2fb1
--- /dev/null
+++ b/components/tollgate_core/src/tollgate_core_stratum_proxy.h
@@ -0,0 +1,39 @@
1#ifndef TOLLGATE_CORE_STRATUM_PROXY_H
2#define TOLLGATE_CORE_STRATUM_PROXY_H
3
4#include "esp_err.h"
5#include <stdint.h>
6#include <stdbool.h>
7
8#define TOLLGATE_STRATUM_MAX_JOB_ID_LEN 32
9#define TOLLGATE_STRATUM_MAX_JOBS 4
10
11typedef struct {
12 uint32_t job_id;
13 uint8_t prevhash[32];
14 uint8_t merkle_root[32];
15 uint32_t ntime;
16 uint32_t nbits;
17 uint32_t version;
18 uint8_t target[32];
19 int target_len;
20 bool valid;
21} tollgate_stratum_job_t;
22
23typedef struct {
24 double hashrate_ghs;
25 uint32_t nbits;
26 uint64_t total_shares;
27 uint64_t total_accepted;
28 uint64_t total_rejected;
29 double current_hashprice;
30 int active_miners;
31} tollgate_stratum_proxy_stats_t;
32
33esp_err_t tollgate_core_stratum_proxy_init(uint16_t port);
34void tollgate_core_stratum_proxy_set_job(const tollgate_stratum_job_t *job);
35const tollgate_stratum_job_t *tollgate_core_stratum_proxy_get_current_job(void);
36void tollgate_core_stratum_proxy_get_stats(tollgate_stratum_proxy_stats_t *stats);
37void tollgate_core_stratum_proxy_stop(void);
38
39#endif