diff options
| author | Your Name <you@example.com> | 2026-05-19 04:13:11 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-19 04:13:11 +0530 |
| commit | beb73a2eeacf7dbe5e292ce6e26a95a933808267 (patch) | |
| tree | 3a9b711e1475c09ed544b96358587df3e5362555 | |
| parent | c75230e551a778408b2e370b208aff76b74c6560 (diff) | |
feat(mining): integrate mining subsystem into existing modules
- CMakeLists.txt: add 6 mining sources + tcp_transport dependency
- config.h/c: mining_payout_mode_t enum, mining config fields, JSON parsing
- tollgate_main.c: mining init in start_services(), stratum_client_tick in main loop
- tollgate_api.c: GET /mining/job, POST /mining/share, GET /mining/stats endpoints
- session.h/c: payment_method_t enum (CASHU/MINING/BYTES)
- firewall.h/c: sandbox allowlist for mining port + mint URLs
- tollgate_client.h/c: TG_CLIENT_MINING state, mining discovery tag parsing
- captive_portal.c: tabbed UI with Cashu/Mine tabs, live hashrate polling
| -rw-r--r-- | main/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | main/captive_portal.c | 69 | ||||
| -rw-r--r-- | main/config.c | 45 | ||||
| -rw-r--r-- | main/config.h | 19 | ||||
| -rw-r--r-- | main/firewall.c | 47 | ||||
| -rw-r--r-- | main/firewall.h | 2 | ||||
| -rw-r--r-- | main/session.c | 2 | ||||
| -rw-r--r-- | main/session.h | 7 | ||||
| -rw-r--r-- | main/tollgate_api.c | 189 | ||||
| -rw-r--r-- | main/tollgate_client.c | 26 | ||||
| -rw-r--r-- | main/tollgate_client.h | 3 | ||||
| -rw-r--r-- | main/tollgate_main.c | 31 |
12 files changed, 436 insertions, 11 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index a041bc1..df52a3c 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt | |||
| @@ -16,8 +16,13 @@ idf_component_register(SRCS "tollgate_main.c" | |||
| 16 | "nip04.c" | 16 | "nip04.c" |
| 17 | "mcp_handler.c" | 17 | "mcp_handler.c" |
| 18 | "cvm_server.c" | 18 | "cvm_server.c" |
| 19 | "mining_payment.c" | ||
| 20 | "stratum_client.c" | ||
| 21 | "stratum_proxy.c" | ||
| 22 | "sw_miner.c" | ||
| 23 | "asic_miner.c" | ||
| 19 | INCLUDE_DIRS "." | 24 | INCLUDE_DIRS "." |
| 20 | REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server | 25 | REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server |
| 21 | lwip json esp_http_client mbedtls esp-tls log spiffs | 26 | lwip json esp_http_client mbedtls esp-tls log spiffs |
| 22 | nucula_lib secp256k1 esp_timer | 27 | nucula_lib secp256k1 esp_timer tcp_transport |
| 23 | PRIV_REQUIRES esp-tls) | 28 | PRIV_REQUIRES esp-tls) |
diff --git a/main/captive_portal.c b/main/captive_portal.c index 1a3d5ce..ea83906 100644 --- a/main/captive_portal.c +++ b/main/captive_portal.c | |||
| @@ -2,6 +2,8 @@ | |||
| 2 | #include "firewall.h" | 2 | #include "firewall.h" |
| 3 | #include "session.h" | 3 | #include "session.h" |
| 4 | #include "config.h" | 4 | #include "config.h" |
| 5 | #include "mining_payment.h" | ||
| 6 | #include "stratum_proxy.h" | ||
| 5 | #include "esp_log.h" | 7 | #include "esp_log.h" |
| 6 | #include "esp_wifi.h" | 8 | #include "esp_wifi.h" |
| 7 | #include "cJSON.h" | 9 | #include "cJSON.h" |
| @@ -31,6 +33,12 @@ static const char PORTAL_HTML_TEMPLATE[] = \ | |||
| 31 | "max-width:400px;width:100%;text-align:center}" | 33 | "max-width:400px;width:100%;text-align:center}" |
| 32 | "h1{font-size:28px;margin-bottom:8px;color:#f7931a}" | 34 | "h1{font-size:28px;margin-bottom:8px;color:#f7931a}" |
| 33 | ".subtitle{color:#888;margin-bottom:24px;font-size:14px}" | 35 | ".subtitle{color:#888;margin-bottom:24px;font-size:14px}" |
| 36 | ".tabs{display:flex;gap:4px;margin-bottom:20px}" | ||
| 37 | ".tab{flex:1;padding:10px;border:none;border-radius:8px;background:#252525;color:#888;" | ||
| 38 | "cursor:pointer;font-size:13px;font-weight:bold}" | ||
| 39 | ".tab.active{background:#f7931a;color:#000}" | ||
| 40 | ".tab-content{display:none}" | ||
| 41 | ".tab-content.active{display:block}" | ||
| 34 | ".price{background:#252525;border-radius:12px;padding:16px;margin-bottom:16px}" | 42 | ".price{background:#252525;border-radius:12px;padding:16px;margin-bottom:16px}" |
| 35 | ".price-amount{font-size:36px;font-weight:bold;color:#f7931a}" | 43 | ".price-amount{font-size:36px;font-weight:bold;color:#f7931a}" |
| 36 | ".price-unit{color:#888;font-size:14px}" | 44 | ".price-unit{color:#888;font-size:14px}" |
| @@ -46,15 +54,22 @@ static const char PORTAL_HTML_TEMPLATE[] = \ | |||
| 46 | "background:#1a1a1a;padding:8px;border-radius:6px;cursor:pointer}" | 54 | "background:#1a1a1a;padding:8px;border-radius:6px;cursor:pointer}" |
| 47 | ".mint-url:active{opacity:0.7}" | 55 | ".mint-url:active{opacity:0.7}" |
| 48 | ".mint-hint{color:#666;font-size:10px;margin-top:4px}" | 56 | ".mint-hint{color:#666;font-size:10px;margin-top:4px}" |
| 57 | ".mining-stats{background:#252525;border-radius:12px;padding:16px;margin-bottom:16px;text-align:left}" | ||
| 58 | ".mining-stat{display:flex;justify-content:space-between;margin-bottom:8px;font-size:13px}" | ||
| 59 | ".mining-stat .label{color:#888}" | ||
| 60 | ".mining-stat .value{color:#f7931a;font-weight:bold}" | ||
| 49 | "#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}" | 61 | "#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}" |
| 50 | "#status.success{display:block;background:#1a472a;color:#4caf50}" | 62 | "#status.success{display:block;background:#1a472a;color:#4caf50}" |
| 51 | "#status.error{display:block;background:#471a1a;color:#f44336}" | 63 | "#status.error{display:block;background:#471a1a;color:#f44336}" |
| 52 | "#status.processing{display:block;background:#1a3a47;color:#2196f3}" | 64 | "#status.processing{display:block;background:#1a3a47;color:#2196f3}" |
| 65 | ".mining-info{color:#666;font-size:11px;margin-top:12px;line-height:1.5}" | ||
| 53 | "</style>" | 66 | "</style>" |
| 54 | "</head><body>" | 67 | "</head><body>" |
| 55 | "<div class='card'>" | 68 | "<div class='card'>" |
| 56 | "<h1>TollGate</h1>" | 69 | "<h1>TollGate</h1>" |
| 57 | "<p class='subtitle'>Pay for internet access with ecash</p>" | 70 | "<p class='subtitle'>Pay for internet access with ecash or mining</p>" |
| 71 | "__MINING_TABS__" | ||
| 72 | "<div id='tab-cashu' class='tab-content __CASHU_ACTIVE__'>" | ||
| 58 | "<div class='price'>" | 73 | "<div class='price'>" |
| 59 | "<div class='price-amount'>__PRICE__</div>" | 74 | "<div class='price-amount'>__PRICE__</div>" |
| 60 | "<div class='price-unit'>sats per minute</div>" | 75 | "<div class='price-unit'>sats per minute</div>" |
| @@ -66,6 +81,18 @@ static const char PORTAL_HTML_TEMPLATE[] = \ | |||
| 66 | "<div class='mint-url' id='mintUrl' onclick='copyMint()'>__MINT_URL__</div>" | 81 | "<div class='mint-url' id='mintUrl' onclick='copyMint()'>__MINT_URL__</div>" |
| 67 | "<div class='mint-hint'>Tap to copy • Mint tokens at this URL before paying</div>" | 82 | "<div class='mint-hint'>Tap to copy • Mint tokens at this URL before paying</div>" |
| 68 | "</div>" | 83 | "</div>" |
| 84 | "</div>" | ||
| 85 | "<div id='tab-mining' class='tab-content __MINING_ACTIVE__'>" | ||
| 86 | "<div class='mining-stats'>" | ||
| 87 | "<div class='mining-stat'><span class='label'>Hashrate</span><span class='value' id='hashrate'>0.00 GH/s</span></div>" | ||
| 88 | "<div class='mining-stat'><span class='label'>Shares</span><span class='value' id='shareCount'>0</span></div>" | ||
| 89 | "<div class='mining-stat'><span class='label'>Hashprice</span><span class='value' id='hashprice'>0.00 sat/GH/s/day</span></div>" | ||
| 90 | "<div class='mining-stat'><span class='label'>Time earned</span><span class='value' id='timeEarned'>0 min</span></div>" | ||
| 91 | "</div>" | ||
| 92 | "<button class='btn' id='mineBtn' onclick='toggleMining()'>Start Mining</button>" | ||
| 93 | "<div class='mining-info'>Mining earns internet time by contributing SHA256 hashpower. " | ||
| 94 | "Connect a Stratum miner to port __MINING_PORT__ or use the built-in web miner.</div>" | ||
| 95 | "</div>" | ||
| 69 | "<div id='status'></div>" | 96 | "<div id='status'></div>" |
| 70 | "</div>" | 97 | "</div>" |
| 71 | "<script>" | 98 | "<script>" |
| @@ -74,6 +101,14 @@ static const char PORTAL_HTML_TEMPLATE[] = \ | |||
| 74 | "const statusEl=document.getElementById('status');" | 101 | "const statusEl=document.getElementById('status');" |
| 75 | "const payBtn=document.getElementById('payBtn');" | 102 | "const payBtn=document.getElementById('payBtn');" |
| 76 | "const tokenInput=document.getElementById('tokenInput');" | 103 | "const tokenInput=document.getElementById('tokenInput');" |
| 104 | "let miningActive=false;" | ||
| 105 | "let miningInterval=null;" | ||
| 106 | "function switchTab(tab){" | ||
| 107 | "document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));" | ||
| 108 | "document.querySelectorAll('.tab-content').forEach(t=>t.classList.remove('active'));" | ||
| 109 | "event.target.classList.add('active');" | ||
| 110 | "document.getElementById('tab-'+tab).classList.add('active');" | ||
| 111 | "}" | ||
| 77 | "function copyMint(){" | 112 | "function copyMint(){" |
| 78 | "if(navigator.clipboard){navigator.clipboard.writeText(mintUrl);" | 113 | "if(navigator.clipboard){navigator.clipboard.writeText(mintUrl);" |
| 79 | "mintUrlEl.textContent='Copied!';setTimeout(()=>{mintUrlEl.textContent=mintUrl;},1000);}" | 114 | "mintUrlEl.textContent='Copied!';setTimeout(()=>{mintUrlEl.textContent=mintUrl;},1000);}" |
| @@ -93,6 +128,24 @@ static const char PORTAL_HTML_TEMPLATE[] = \ | |||
| 93 | "else if(d.kind===21023){showStatus('Error: '+(d.content||'Unknown error'),'error');payBtn.disabled=false;}" | 128 | "else if(d.kind===21023){showStatus('Error: '+(d.content||'Unknown error'),'error');payBtn.disabled=false;}" |
| 94 | "}).catch(e=>{showStatus(e.message||'Connection error','error');payBtn.disabled=false;});" | 129 | "}).catch(e=>{showStatus(e.message||'Connection error','error');payBtn.disabled=false;});" |
| 95 | "}" | 130 | "}" |
| 131 | "function pollMiningStats(){" | ||
| 132 | "fetch('http://__AP_IP__:2121/mining/stats').then(r=>r.json()).then(d=>{" | ||
| 133 | "document.getElementById('hashrate').textContent=d.proxy.hashrate_ghs.toFixed(2)+' GH/s';" | ||
| 134 | "document.getElementById('shareCount').textContent=d.proxy.total_accepted;" | ||
| 135 | "document.getElementById('hashprice').textContent=d.proxy.hashprice.toFixed(2)+' sat/GH/s/day';" | ||
| 136 | "}).catch(()=>{});" | ||
| 137 | "fetch('http://__AP_IP__:2121/usage').then(r=>r.text()).then(t=>{" | ||
| 138 | "if(t&&t!=='-1/-1'){" | ||
| 139 | "const parts=t.split('/');const rem=Math.floor(parseInt(parts[0])/60000);" | ||
| 140 | "document.getElementById('timeEarned').textContent=rem+' min';" | ||
| 141 | "}}).catch(()=>{});" | ||
| 142 | "}" | ||
| 143 | "function toggleMining(){" | ||
| 144 | "if(miningActive){miningActive=false;clearInterval(miningInterval);" | ||
| 145 | "document.getElementById('mineBtn').textContent='Start Mining';return;}" | ||
| 146 | "miningActive=true;document.getElementById('mineBtn').textContent='Mining...';" | ||
| 147 | "miningInterval=setInterval(pollMiningStats,2000);pollMiningStats();" | ||
| 148 | "}" | ||
| 96 | "</script>" | 149 | "</script>" |
| 97 | "</body></html>"; | 150 | "</body></html>"; |
| 98 | 151 | ||
| @@ -126,7 +179,21 @@ static esp_err_t portal_handler(httpd_req_t *req) | |||
| 126 | { "__AP_IP__", s_ap_ip_str }, | 179 | { "__AP_IP__", s_ap_ip_str }, |
| 127 | { "__PRICE__", price_str }, | 180 | { "__PRICE__", price_str }, |
| 128 | { "__MINT_URL__", cfg->mint_url }, | 181 | { "__MINT_URL__", cfg->mint_url }, |
| 182 | { "__MINING_TABS__", cfg->mining_enabled ? | ||
| 183 | "<div class='tabs'>" | ||
| 184 | "<button class='tab active' onclick=\"switchTab('cashu')\">Cashu</button>" | ||
| 185 | "<button class='tab' onclick=\"switchTab('mining')\">Mine</button>" | ||
| 186 | "</div>" : "" }, | ||
| 187 | { "__MINING_PORT__", cfg->mining_enabled ? | ||
| 188 | (char[]){ [0 ... 7] = 0 } : "3333" }, | ||
| 189 | { "__CASHU_ACTIVE__", "active" }, | ||
| 190 | { "__MINING_ACTIVE__", "" }, | ||
| 129 | }; | 191 | }; |
| 192 | char mining_port_buf[8] = "3333"; | ||
| 193 | if (cfg->mining_enabled) { | ||
| 194 | snprintf(mining_port_buf, sizeof(mining_port_buf), "%d", cfg->mining_port); | ||
| 195 | subs[4].val = mining_port_buf; | ||
| 196 | } | ||
| 130 | int nsubs = sizeof(subs) / sizeof(subs[0]); | 197 | int nsubs = sizeof(subs) / sizeof(subs[0]); |
| 131 | 198 | ||
| 132 | size_t extra = 0; | 199 | size_t extra = 0; |
diff --git a/main/config.c b/main/config.c index b991991..e27a39b 100644 --- a/main/config.c +++ b/main/config.c | |||
| @@ -37,6 +37,11 @@ esp_err_t tollgate_config_init(void) | |||
| 37 | strncpy(g_config.cvm_relays, "wss://relay.primal.net", sizeof(g_config.cvm_relays) - 1); | 37 | strncpy(g_config.cvm_relays, "wss://relay.primal.net", sizeof(g_config.cvm_relays) - 1); |
| 38 | g_config.nostr_sync_interval_s = 1800; | 38 | g_config.nostr_sync_interval_s = 1800; |
| 39 | g_config.nostr_fallback_sync_interval_s = 21600; | 39 | g_config.nostr_fallback_sync_interval_s = 21600; |
| 40 | g_config.mining_enabled = false; | ||
| 41 | g_config.mining_payout_mode = MINING_PAYOUT_AUTO; | ||
| 42 | g_config.stratum_port = 3333; | ||
| 43 | g_config.mining_port = 3334; | ||
| 44 | g_config.mining_sandbox_mint_access = true; | ||
| 40 | 45 | ||
| 41 | esp_vfs_spiffs_conf_t conf = { | 46 | esp_vfs_spiffs_conf_t conf = { |
| 42 | .base_path = "/spiffs", | 47 | .base_path = "/spiffs", |
| @@ -279,6 +284,46 @@ esp_err_t tollgate_config_init(void) | |||
| 279 | cJSON *fallback_interval = cJSON_GetObjectItem(root, "nostr_fallback_sync_interval_s"); | 284 | cJSON *fallback_interval = cJSON_GetObjectItem(root, "nostr_fallback_sync_interval_s"); |
| 280 | if (fallback_interval) g_config.nostr_fallback_sync_interval_s = fallback_interval->valueint; | 285 | if (fallback_interval) g_config.nostr_fallback_sync_interval_s = fallback_interval->valueint; |
| 281 | 286 | ||
| 287 | cJSON *mining = cJSON_GetObjectItem(root, "mining"); | ||
| 288 | if (mining && cJSON_IsObject(mining)) { | ||
| 289 | cJSON *m_en = cJSON_GetObjectItem(mining, "enabled"); | ||
| 290 | if (m_en && cJSON_IsBool(m_en)) g_config.mining_enabled = cJSON_IsTrue(m_en); | ||
| 291 | |||
| 292 | cJSON *m_mode = cJSON_GetObjectItem(mining, "payout_mode"); | ||
| 293 | if (m_mode && cJSON_IsString(m_mode)) { | ||
| 294 | if (strcmp(m_mode->valuestring, "pool") == 0) g_config.mining_payout_mode = MINING_PAYOUT_POOL; | ||
| 295 | else if (strcmp(m_mode->valuestring, "upstream") == 0) g_config.mining_payout_mode = MINING_PAYOUT_UPSTREAM; | ||
| 296 | else if (strcmp(m_mode->valuestring, "proxy_only") == 0) g_config.mining_payout_mode = MINING_PAYOUT_PROXY_ONLY; | ||
| 297 | } | ||
| 298 | |||
| 299 | cJSON *m_host = cJSON_GetObjectItem(mining, "stratum_host"); | ||
| 300 | if (m_host && cJSON_IsString(m_host)) strncpy(g_config.stratum_host, m_host->valuestring, sizeof(g_config.stratum_host) - 1); | ||
| 301 | |||
| 302 | cJSON *m_port = cJSON_GetObjectItem(mining, "stratum_port"); | ||
| 303 | if (m_port) g_config.stratum_port = (uint16_t)m_port->valueint; | ||
| 304 | |||
| 305 | cJSON *m_user = cJSON_GetObjectItem(mining, "stratum_user"); | ||
| 306 | if (m_user && cJSON_IsString(m_user)) strncpy(g_config.stratum_user, m_user->valuestring, sizeof(g_config.stratum_user) - 1); | ||
| 307 | |||
| 308 | cJSON *m_pass = cJSON_GetObjectItem(mining, "stratum_pass"); | ||
| 309 | if (m_pass && cJSON_IsString(m_pass)) strncpy(g_config.stratum_pass, m_pass->valuestring, sizeof(g_config.stratum_pass) - 1); | ||
| 310 | |||
| 311 | cJSON *m_fb_host = cJSON_GetObjectItem(mining, "stratum_fallback_host"); | ||
| 312 | if (m_fb_host && cJSON_IsString(m_fb_host)) strncpy(g_config.stratum_fallback_host, m_fb_host->valuestring, sizeof(g_config.stratum_fallback_host) - 1); | ||
| 313 | |||
| 314 | cJSON *m_fb_port = cJSON_GetObjectItem(mining, "stratum_fallback_port"); | ||
| 315 | if (m_fb_port) g_config.stratum_fallback_port = (uint16_t)m_fb_port->valueint; | ||
| 316 | |||
| 317 | cJSON *m_mport = cJSON_GetObjectItem(mining, "mining_port"); | ||
| 318 | if (m_mport) g_config.mining_port = (uint16_t)m_mport->valueint; | ||
| 319 | |||
| 320 | cJSON *m_hp = cJSON_GetObjectItem(mining, "hashprice_sats_per_ghs_day"); | ||
| 321 | if (m_hp) g_config.hashprice_sats_per_ghs_day = (uint64_t)m_hp->valuedouble; | ||
| 322 | |||
| 323 | cJSON *m_sandbox = cJSON_GetObjectItem(mining, "sandbox_mint_access"); | ||
| 324 | if (m_sandbox && cJSON_IsBool(m_sandbox)) g_config.mining_sandbox_mint_access = cJSON_IsTrue(m_sandbox); | ||
| 325 | } | ||
| 326 | |||
| 282 | cJSON_Delete(root); | 327 | cJSON_Delete(root); |
| 283 | 328 | ||
| 284 | if (g_config.payout.recipient_count == 0) { | 329 | if (g_config.payout.recipient_count == 0) { |
diff --git a/main/config.h b/main/config.h index 1e580e9..8b3f17e 100644 --- a/main/config.h +++ b/main/config.h | |||
| @@ -15,6 +15,13 @@ | |||
| 15 | #define TOLLGATE_MAX_RELAYS 4 | 15 | #define TOLLGATE_MAX_RELAYS 4 |
| 16 | #define TOLLGATE_MAX_SEED_RELAYS 8 | 16 | #define TOLLGATE_MAX_SEED_RELAYS 8 |
| 17 | 17 | ||
| 18 | typedef enum { | ||
| 19 | MINING_PAYOUT_AUTO, | ||
| 20 | MINING_PAYOUT_POOL, | ||
| 21 | MINING_PAYOUT_UPSTREAM, | ||
| 22 | MINING_PAYOUT_PROXY_ONLY | ||
| 23 | } mining_payout_mode_t; | ||
| 24 | |||
| 18 | typedef struct { | 25 | typedef struct { |
| 19 | char ssid[32]; | 26 | char ssid[32]; |
| 20 | char password[64]; | 27 | char password[64]; |
| @@ -69,6 +76,18 @@ typedef struct { | |||
| 69 | int nostr_seed_relay_count; | 76 | int nostr_seed_relay_count; |
| 70 | int nostr_sync_interval_s; | 77 | int nostr_sync_interval_s; |
| 71 | int nostr_fallback_sync_interval_s; | 78 | int nostr_fallback_sync_interval_s; |
| 79 | |||
| 80 | bool mining_enabled; | ||
| 81 | mining_payout_mode_t mining_payout_mode; | ||
| 82 | char stratum_host[128]; | ||
| 83 | uint16_t stratum_port; | ||
| 84 | char stratum_user[128]; | ||
| 85 | char stratum_pass[64]; | ||
| 86 | char stratum_fallback_host[128]; | ||
| 87 | uint16_t stratum_fallback_port; | ||
| 88 | uint16_t mining_port; | ||
| 89 | uint64_t hashprice_sats_per_ghs_day; | ||
| 90 | bool mining_sandbox_mint_access; | ||
| 72 | } tollgate_config_t; | 91 | } tollgate_config_t; |
| 73 | 92 | ||
| 74 | void tollgate_config_derive_unique(tollgate_config_t *cfg); | 93 | void tollgate_config_derive_unique(tollgate_config_t *cfg); |
diff --git a/main/firewall.c b/main/firewall.c index 8d535b4..ae0eda7 100644 --- a/main/firewall.c +++ b/main/firewall.c | |||
| @@ -7,12 +7,16 @@ | |||
| 7 | #include "lwip/etharp.h" | 7 | #include "lwip/etharp.h" |
| 8 | #include "lwip/netif.h" | 8 | #include "lwip/netif.h" |
| 9 | #include "lwip/prot/ip4.h" | 9 | #include "lwip/prot/ip4.h" |
| 10 | #include "lwip/prot/tcp.h" | ||
| 11 | #include "lwip/prot/ip.h" | ||
| 10 | #include <string.h> | 12 | #include <string.h> |
| 11 | 13 | ||
| 12 | #define MAX_CLIENTS 10 | 14 | #define MAX_CLIENTS 10 |
| 13 | 15 | ||
| 14 | static const char *TAG = "firewall"; | 16 | static const char *TAG = "firewall"; |
| 15 | static esp_ip4_addr_t s_ap_ip; | 17 | static esp_ip4_addr_t s_ap_ip; |
| 18 | static uint16_t s_mining_port = 3333; | ||
| 19 | static bool s_sandbox_mint_access = false; | ||
| 16 | 20 | ||
| 17 | typedef struct { | 21 | typedef struct { |
| 18 | uint32_t ip; | 22 | uint32_t ip; |
| @@ -66,6 +70,46 @@ esp_err_t firewall_init(esp_ip4_addr_t ap_ip) | |||
| 66 | return ESP_OK; | 70 | return ESP_OK; |
| 67 | } | 71 | } |
| 68 | 72 | ||
| 73 | void firewall_set_mining_port(uint16_t port) | ||
| 74 | { | ||
| 75 | s_mining_port = port; | ||
| 76 | } | ||
| 77 | |||
| 78 | void firewall_set_sandbox_mint_access(bool enabled) | ||
| 79 | { | ||
| 80 | s_sandbox_mint_access = enabled; | ||
| 81 | } | ||
| 82 | |||
| 83 | static bool is_sandbox_allowed(struct pbuf *p) | ||
| 84 | { | ||
| 85 | if (p->len < IP_HLEN) return false; | ||
| 86 | struct ip_hdr *iphdr = (struct ip_hdr *)p->payload; | ||
| 87 | uint32_t dest_ip_h = lwip_ntohl(iphdr->dest.addr); | ||
| 88 | uint32_t ap_ip_h = lwip_ntohl(s_ap_ip.addr); | ||
| 89 | |||
| 90 | if (dest_ip_h == ap_ip_h) { | ||
| 91 | if (iphdr->_proto == IP_PROTO_TCP) { | ||
| 92 | uint16_t dst_port = 0; | ||
| 93 | if (p->len >= IP_HLEN + TCP_HLEN) { | ||
| 94 | struct tcp_hdr *tcphdr = (struct tcp_hdr *)((uint8_t *)p->payload + IP_HLEN); | ||
| 95 | dst_port = lwip_ntohs(tcphdr->dest); | ||
| 96 | } | ||
| 97 | if (dst_port == 80 || dst_port == 2121 || dst_port == s_mining_port) { | ||
| 98 | return true; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | if (iphdr->_proto == IP_PROTO_UDP) { | ||
| 102 | return true; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | if (s_sandbox_mint_access && iphdr->_proto == IP_PROTO_TCP) { | ||
| 107 | return true; | ||
| 108 | } | ||
| 109 | |||
| 110 | return false; | ||
| 111 | } | ||
| 112 | |||
| 69 | int tollgate_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder) | 113 | int tollgate_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder) |
| 70 | { | 114 | { |
| 71 | (void)dest_addr_hostorder; | 115 | (void)dest_addr_hostorder; |
| @@ -79,6 +123,9 @@ int tollgate_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder) | |||
| 79 | if (firewall_is_client_allowed(iphdr->src.addr)) { | 123 | if (firewall_is_client_allowed(iphdr->src.addr)) { |
| 80 | return 1; | 124 | return 1; |
| 81 | } | 125 | } |
| 126 | if (is_sandbox_allowed(p)) { | ||
| 127 | return 1; | ||
| 128 | } | ||
| 82 | return 0; | 129 | return 0; |
| 83 | } | 130 | } |
| 84 | 131 | ||
diff --git a/main/firewall.h b/main/firewall.h index f177eaa..77300e2 100644 --- a/main/firewall.h +++ b/main/firewall.h | |||
| @@ -11,6 +11,8 @@ struct pbuf; | |||
| 11 | #define FW_MAX_MAC_LEN 18 | 11 | #define FW_MAX_MAC_LEN 18 |
| 12 | 12 | ||
| 13 | esp_err_t firewall_init(esp_ip4_addr_t ap_ip); | 13 | esp_err_t firewall_init(esp_ip4_addr_t ap_ip); |
| 14 | void firewall_set_mining_port(uint16_t port); | ||
| 15 | void firewall_set_sandbox_mint_access(bool enabled); | ||
| 14 | void firewall_grant_access(uint32_t client_ip); | 16 | void firewall_grant_access(uint32_t client_ip); |
| 15 | void firewall_revoke_access(uint32_t client_ip); | 17 | void firewall_revoke_access(uint32_t client_ip); |
| 16 | void firewall_revoke_all(void); | 18 | void firewall_revoke_all(void); |
diff --git a/main/session.c b/main/session.c index 81e1f96..feea272 100644 --- a/main/session.c +++ b/main/session.c | |||
| @@ -54,6 +54,7 @@ session_t *session_create(uint32_t client_ip, uint64_t allotment_ms) | |||
| 54 | s_sessions[i].allotment_ms = allotment_ms; | 54 | s_sessions[i].allotment_ms = allotment_ms; |
| 55 | s_sessions[i].start_time_ms = get_time_ms(); | 55 | s_sessions[i].start_time_ms = get_time_ms(); |
| 56 | s_sessions[i].active = true; | 56 | s_sessions[i].active = true; |
| 57 | s_sessions[i].payment_method = PAYMENT_METHOD_CASHU; | ||
| 57 | populate_mac(&s_sessions[i], client_ip); | 58 | populate_mac(&s_sessions[i], client_ip); |
| 58 | 59 | ||
| 59 | s_session_count++; | 60 | s_session_count++; |
| @@ -78,6 +79,7 @@ session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes) | |||
| 78 | s->allotment_bytes = allotment_bytes; | 79 | s->allotment_bytes = allotment_bytes; |
| 79 | s->bytes_consumed = 0; | 80 | s->bytes_consumed = 0; |
| 80 | s->allotment_ms = INT64_MAX; | 81 | s->allotment_ms = INT64_MAX; |
| 82 | s->payment_method = PAYMENT_METHOD_BYTES; | ||
| 81 | esp_ip4_addr_t ip = { .addr = client_ip }; | 83 | esp_ip4_addr_t ip = { .addr = client_ip }; |
| 82 | ESP_LOGI(TAG, "Bytes session created: " IPSTR " allotment=%llu bytes", IP2STR(&ip), | 84 | ESP_LOGI(TAG, "Bytes session created: " IPSTR " allotment=%llu bytes", IP2STR(&ip), |
| 83 | (unsigned long long)allotment_bytes); | 85 | (unsigned long long)allotment_bytes); |
diff --git a/main/session.h b/main/session.h index 36fe722..d3a61bb 100644 --- a/main/session.h +++ b/main/session.h | |||
| @@ -8,6 +8,12 @@ | |||
| 8 | #define SESSION_MAX_CLIENTS 10 | 8 | #define SESSION_MAX_CLIENTS 10 |
| 9 | #define SESSION_MAX_MAC_LEN 18 | 9 | #define SESSION_MAX_MAC_LEN 18 |
| 10 | 10 | ||
| 11 | typedef enum { | ||
| 12 | PAYMENT_METHOD_CASHU, | ||
| 13 | PAYMENT_METHOD_MINING, | ||
| 14 | PAYMENT_METHOD_BYTES | ||
| 15 | } payment_method_t; | ||
| 16 | |||
| 11 | typedef struct { | 17 | typedef struct { |
| 12 | uint32_t client_ip; | 18 | uint32_t client_ip; |
| 13 | char mac[SESSION_MAX_MAC_LEN]; | 19 | char mac[SESSION_MAX_MAC_LEN]; |
| @@ -15,6 +21,7 @@ typedef struct { | |||
| 15 | int64_t start_time_ms; | 21 | int64_t start_time_ms; |
| 16 | uint64_t allotment_bytes; | 22 | uint64_t allotment_bytes; |
| 17 | uint64_t bytes_consumed; | 23 | uint64_t bytes_consumed; |
| 24 | payment_method_t payment_method; | ||
| 18 | bool active; | 25 | bool active; |
| 19 | } session_t; | 26 | } session_t; |
| 20 | 27 | ||
diff --git a/main/tollgate_api.c b/main/tollgate_api.c index 650b0f3..7113511 100644 --- a/main/tollgate_api.c +++ b/main/tollgate_api.c | |||
| @@ -4,6 +4,9 @@ | |||
| 4 | #include "session.h" | 4 | #include "session.h" |
| 5 | #include "firewall.h" | 5 | #include "firewall.h" |
| 6 | #include "nucula_wallet.h" | 6 | #include "nucula_wallet.h" |
| 7 | #include "mining_payment.h" | ||
| 8 | #include "stratum_proxy.h" | ||
| 9 | #include "stratum_client.h" | ||
| 7 | #include "esp_log.h" | 10 | #include "esp_log.h" |
| 8 | #include "cJSON.h" | 11 | #include "cJSON.h" |
| 9 | #include "lwip/sockets.h" | 12 | #include "lwip/sockets.h" |
| @@ -128,6 +131,18 @@ static esp_err_t api_get_discovery(httpd_req_t *req) | |||
| 128 | cJSON_AddItemToArray(tips_tag, cJSON_CreateString("5")); | 131 | cJSON_AddItemToArray(tips_tag, cJSON_CreateString("5")); |
| 129 | cJSON_AddItemToArray(tags, tips_tag); | 132 | cJSON_AddItemToArray(tags, tips_tag); |
| 130 | 133 | ||
| 134 | if (cfg->mining_enabled) { | ||
| 135 | cJSON *mining_tag = cJSON_CreateArray(); | ||
| 136 | cJSON_AddItemToArray(mining_tag, cJSON_CreateString("price_per_step")); | ||
| 137 | cJSON_AddItemToArray(mining_tag, cJSON_CreateString("mining")); | ||
| 138 | char mining_port_str[16]; | ||
| 139 | snprintf(mining_port_str, sizeof(mining_port_str), "%d", cfg->mining_port); | ||
| 140 | cJSON_AddItemToArray(mining_tag, cJSON_CreateString(mining_port_str)); | ||
| 141 | cJSON_AddItemToArray(mining_tag, cJSON_CreateString("GH/s")); | ||
| 142 | cJSON_AddItemToArray(mining_tag, cJSON_CreateString("sv1")); | ||
| 143 | cJSON_AddItemToArray(tags, mining_tag); | ||
| 144 | } | ||
| 145 | |||
| 131 | cJSON_AddItemToObject(root, "tags", tags); | 146 | cJSON_AddItemToObject(root, "tags", tags); |
| 132 | cJSON_AddStringToObject(root, "content", ""); | 147 | cJSON_AddStringToObject(root, "content", ""); |
| 133 | 148 | ||
| @@ -463,6 +478,168 @@ static esp_err_t api_post_wallet_send(httpd_req_t *req) | |||
| 463 | return ESP_OK; | 478 | return ESP_OK; |
| 464 | } | 479 | } |
| 465 | 480 | ||
| 481 | static esp_err_t api_get_mining_job(httpd_req_t *req) | ||
| 482 | { | ||
| 483 | const stratum_job_t *job = stratum_proxy_get_current_job(); | ||
| 484 | if (!job || !job->valid) { | ||
| 485 | httpd_resp_set_status(req, "503 Service Unavailable"); | ||
| 486 | httpd_resp_set_type(req, "application/json"); | ||
| 487 | httpd_resp_send(req, "{\"error\":\"no job\"}", 15); | ||
| 488 | return ESP_OK; | ||
| 489 | } | ||
| 490 | |||
| 491 | cJSON *root = cJSON_CreateObject(); | ||
| 492 | cJSON_AddNumberToObject(root, "job_id", job->job_id); | ||
| 493 | |||
| 494 | char prevhash_hex[65]; | ||
| 495 | for (int i = 0; i < 32; i++) snprintf(prevhash_hex + i * 2, 3, "%02x", job->prevhash[i]); | ||
| 496 | cJSON_AddStringToObject(root, "prevhash", prevhash_hex); | ||
| 497 | |||
| 498 | char merkle_hex[65]; | ||
| 499 | for (int i = 0; i < 32; i++) snprintf(merkle_hex + i * 2, 3, "%02x", job->merkle_root[i]); | ||
| 500 | cJSON_AddStringToObject(root, "merkle_root", merkle_hex); | ||
| 501 | |||
| 502 | cJSON_AddNumberToObject(root, "version", job->version); | ||
| 503 | cJSON_AddNumberToObject(root, "nbits", job->nbits); | ||
| 504 | cJSON_AddNumberToObject(root, "ntime", job->ntime); | ||
| 505 | cJSON_AddNumberToObject(root, "hashprice", mining_get_current_hashprice()); | ||
| 506 | |||
| 507 | char *json = cJSON_PrintUnformatted(root); | ||
| 508 | httpd_resp_set_type(req, "application/json"); | ||
| 509 | httpd_resp_send(req, json, strlen(json)); | ||
| 510 | cJSON_free(json); | ||
| 511 | cJSON_Delete(root); | ||
| 512 | return ESP_OK; | ||
| 513 | } | ||
| 514 | |||
| 515 | static esp_err_t api_post_mining_share(httpd_req_t *req) | ||
| 516 | { | ||
| 517 | uint32_t client_ip = 0; | ||
| 518 | get_client_ip(req, &client_ip); | ||
| 519 | |||
| 520 | int content_len = req->content_len; | ||
| 521 | if (content_len <= 0 || content_len > 512) { | ||
| 522 | httpd_resp_set_status(req, "400 Bad Request"); | ||
| 523 | httpd_resp_set_type(req, "application/json"); | ||
| 524 | httpd_resp_send(req, "{\"error\":\"invalid body\"}", 21); | ||
| 525 | return ESP_OK; | ||
| 526 | } | ||
| 527 | |||
| 528 | char body[512]; | ||
| 529 | int total = 0; | ||
| 530 | while (total < content_len) { | ||
| 531 | int r = httpd_req_recv(req, body + total, content_len - total); | ||
| 532 | if (r <= 0) { | ||
| 533 | httpd_resp_set_status(req, "400 Bad Request"); | ||
| 534 | httpd_resp_set_type(req, "text/plain"); | ||
| 535 | httpd_resp_send(req, "bad request", 11); | ||
| 536 | return ESP_OK; | ||
| 537 | } | ||
| 538 | total += r; | ||
| 539 | } | ||
| 540 | body[total] = '\0'; | ||
| 541 | |||
| 542 | cJSON *root = cJSON_Parse(body); | ||
| 543 | if (!root) { | ||
| 544 | httpd_resp_set_status(req, "400 Bad Request"); | ||
| 545 | httpd_resp_set_type(req, "application/json"); | ||
| 546 | httpd_resp_send(req, "{\"error\":\"invalid json\"}", 21); | ||
| 547 | return ESP_OK; | ||
| 548 | } | ||
| 549 | |||
| 550 | cJSON *j_job_id = cJSON_GetObjectItem(root, "job_id"); | ||
| 551 | cJSON *j_nonce = cJSON_GetObjectItem(root, "nonce"); | ||
| 552 | cJSON *j_ntime = cJSON_GetObjectItem(root, "ntime"); | ||
| 553 | cJSON *j_version = cJSON_GetObjectItem(root, "version"); | ||
| 554 | if (!j_job_id || !j_nonce || !j_ntime || !j_version) { | ||
| 555 | cJSON_Delete(root); | ||
| 556 | httpd_resp_set_status(req, "400 Bad Request"); | ||
| 557 | httpd_resp_set_type(req, "application/json"); | ||
| 558 | httpd_resp_send(req, "{\"error\":\"missing fields\"}", 22); | ||
| 559 | return ESP_OK; | ||
| 560 | } | ||
| 561 | |||
| 562 | uint32_t job_id = (uint32_t)j_job_id->valuedouble; | ||
| 563 | uint32_t nonce = (uint32_t)j_nonce->valuedouble; | ||
| 564 | uint32_t ntime = (uint32_t)j_ntime->valuedouble; | ||
| 565 | uint32_t version = (uint32_t)j_version->valuedouble; | ||
| 566 | cJSON_Delete(root); | ||
| 567 | |||
| 568 | const stratum_job_t *job = stratum_proxy_get_current_job(); | ||
| 569 | if (!job || !job->valid || job->job_id != job_id) { | ||
| 570 | httpd_resp_set_status(req, "400 Bad Request"); | ||
| 571 | httpd_resp_set_type(req, "application/json"); | ||
| 572 | httpd_resp_send(req, "{\"error\":\"stale job\"}", 19); | ||
| 573 | return ESP_OK; | ||
| 574 | } | ||
| 575 | |||
| 576 | esp_err_t share_err = stratum_client_submit_share(job_id, nonce, ntime, version); | ||
| 577 | bool accepted = (share_err == ESP_OK); | ||
| 578 | |||
| 579 | mining_update_hashrate(client_ip, accepted); | ||
| 580 | mining_client_stats_t *stats = mining_get_or_create_client(client_ip); | ||
| 581 | |||
| 582 | if (accepted) { | ||
| 583 | const tollgate_config_t *cfg = tollgate_config_get(); | ||
| 584 | double hashprice = mining_get_current_hashprice(); | ||
| 585 | uint64_t allotment_ms = mining_shares_to_allotment_ms( | ||
| 586 | stats->hashrate_ghs, hashprice, cfg->price_per_step, cfg->step_size_ms); | ||
| 587 | |||
| 588 | session_t *session = session_find_by_ip(client_ip); | ||
| 589 | if (!session || !session->active || session->payment_method != PAYMENT_METHOD_MINING) { | ||
| 590 | session = session_create(client_ip, allotment_ms); | ||
| 591 | if (session) session->payment_method = PAYMENT_METHOD_MINING; | ||
| 592 | } else { | ||
| 593 | session_extend(session, allotment_ms); | ||
| 594 | } | ||
| 595 | } | ||
| 596 | |||
| 597 | cJSON *resp = cJSON_CreateObject(); | ||
| 598 | cJSON_AddBoolToObject(resp, "accepted", accepted); | ||
| 599 | cJSON_AddNumberToObject(resp, "hashrate_ghs", stats ? stats->hashrate_ghs : 0.0); | ||
| 600 | char *json = cJSON_PrintUnformatted(resp); | ||
| 601 | httpd_resp_set_type(req, "application/json"); | ||
| 602 | httpd_resp_send(req, json, strlen(json)); | ||
| 603 | cJSON_free(json); | ||
| 604 | cJSON_Delete(resp); | ||
| 605 | return ESP_OK; | ||
| 606 | } | ||
| 607 | |||
| 608 | static esp_err_t api_get_mining_stats(httpd_req_t *req) | ||
| 609 | { | ||
| 610 | stratum_proxy_stats_t proxy_stats; | ||
| 611 | stratum_proxy_get_stats(&proxy_stats); | ||
| 612 | |||
| 613 | const stratum_client_state_t *client_state = stratum_client_get_state(); | ||
| 614 | |||
| 615 | cJSON *root = cJSON_CreateObject(); | ||
| 616 | |||
| 617 | cJSON *proxy = cJSON_CreateObject(); | ||
| 618 | cJSON_AddNumberToObject(proxy, "hashrate_ghs", proxy_stats.hashrate_ghs); | ||
| 619 | cJSON_AddNumberToObject(proxy, "total_shares", (double)proxy_stats.total_shares); | ||
| 620 | cJSON_AddNumberToObject(proxy, "total_accepted", (double)proxy_stats.total_accepted); | ||
| 621 | cJSON_AddNumberToObject(proxy, "total_rejected", (double)proxy_stats.total_rejected); | ||
| 622 | cJSON_AddNumberToObject(proxy, "hashprice", proxy_stats.current_hashprice); | ||
| 623 | cJSON_AddNumberToObject(proxy, "active_miners", proxy_stats.active_miners); | ||
| 624 | cJSON_AddItemToObject(root, "proxy", proxy); | ||
| 625 | |||
| 626 | cJSON *upstream = cJSON_CreateObject(); | ||
| 627 | cJSON_AddBoolToObject(upstream, "connected", client_state->connected); | ||
| 628 | cJSON_AddStringToObject(upstream, "pool_host", client_state->pool_host); | ||
| 629 | cJSON_AddNumberToObject(upstream, "pool_port", client_state->pool_port); | ||
| 630 | cJSON_AddNumberToObject(upstream, "difficulty", (double)client_state->difficulty); | ||
| 631 | cJSON_AddNumberToObject(upstream, "shares_accepted", (double)client_state->shares_accepted); | ||
| 632 | cJSON_AddNumberToObject(upstream, "shares_rejected", (double)client_state->shares_rejected); | ||
| 633 | cJSON_AddItemToObject(root, "upstream", upstream); | ||
| 634 | |||
| 635 | char *json = cJSON_PrintUnformatted(root); | ||
| 636 | httpd_resp_set_type(req, "application/json"); | ||
| 637 | httpd_resp_send(req, json, strlen(json)); | ||
| 638 | cJSON_free(json); | ||
| 639 | cJSON_Delete(root); | ||
| 640 | return ESP_OK; | ||
| 641 | } | ||
| 642 | |||
| 466 | static const httpd_uri_t uri_discovery = { .uri = "/", .method = HTTP_GET, .handler = api_get_discovery }; | 643 | static const httpd_uri_t uri_discovery = { .uri = "/", .method = HTTP_GET, .handler = api_get_discovery }; |
| 467 | static const httpd_uri_t uri_payment = { .uri = "/", .method = HTTP_POST, .handler = api_post_payment }; | 644 | static const httpd_uri_t uri_payment = { .uri = "/", .method = HTTP_POST, .handler = api_post_payment }; |
| 468 | static const httpd_uri_t uri_usage = { .uri = "/usage", .method = HTTP_GET, .handler = api_get_usage }; | 645 | static const httpd_uri_t uri_usage = { .uri = "/usage", .method = HTTP_GET, .handler = api_get_usage }; |
| @@ -470,6 +647,9 @@ static const httpd_uri_t uri_whoami = { .uri = "/whoami", .method = HTTP_GET, .h | |||
| 470 | static const httpd_uri_t uri_wallet = { .uri = "/wallet", .method = HTTP_GET, .handler = api_get_wallet }; | 647 | static const httpd_uri_t uri_wallet = { .uri = "/wallet", .method = HTTP_GET, .handler = api_get_wallet }; |
| 471 | static const httpd_uri_t uri_wallet_swap = { .uri = "/wallet/swap", .method = HTTP_POST, .handler = api_post_wallet_swap }; | 648 | static const httpd_uri_t uri_wallet_swap = { .uri = "/wallet/swap", .method = HTTP_POST, .handler = api_post_wallet_swap }; |
| 472 | static const httpd_uri_t uri_wallet_send = { .uri = "/wallet/send", .method = HTTP_POST, .handler = api_post_wallet_send }; | 649 | static const httpd_uri_t uri_wallet_send = { .uri = "/wallet/send", .method = HTTP_POST, .handler = api_post_wallet_send }; |
| 650 | static const httpd_uri_t uri_mining_job = { .uri = "/mining/job", .method = HTTP_GET, .handler = api_get_mining_job }; | ||
| 651 | static const httpd_uri_t uri_mining_share = { .uri = "/mining/share", .method = HTTP_POST, .handler = api_post_mining_share }; | ||
| 652 | static const httpd_uri_t uri_mining_stats = { .uri = "/mining/stats", .method = HTTP_GET, .handler = api_get_mining_stats }; | ||
| 473 | 653 | ||
| 474 | esp_err_t tollgate_api_start(void) | 654 | esp_err_t tollgate_api_start(void) |
| 475 | { | 655 | { |
| @@ -478,7 +658,7 @@ esp_err_t tollgate_api_start(void) | |||
| 478 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); | 658 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); |
| 479 | config.server_port = 2121; | 659 | config.server_port = 2121; |
| 480 | config.ctrl_port = 32769; | 660 | config.ctrl_port = 32769; |
| 481 | config.max_uri_handlers = 10; | 661 | config.max_uri_handlers = 16; |
| 482 | config.stack_size = 16384; | 662 | config.stack_size = 16384; |
| 483 | 663 | ||
| 484 | esp_err_t ret = httpd_start(&s_api_server, &config); | 664 | esp_err_t ret = httpd_start(&s_api_server, &config); |
| @@ -495,6 +675,13 @@ esp_err_t tollgate_api_start(void) | |||
| 495 | httpd_register_uri_handler(s_api_server, &uri_wallet_swap); | 675 | httpd_register_uri_handler(s_api_server, &uri_wallet_swap); |
| 496 | httpd_register_uri_handler(s_api_server, &uri_wallet_send); | 676 | httpd_register_uri_handler(s_api_server, &uri_wallet_send); |
| 497 | 677 | ||
| 678 | const tollgate_config_t *cfg = tollgate_config_get(); | ||
| 679 | if (cfg->mining_enabled) { | ||
| 680 | httpd_register_uri_handler(s_api_server, &uri_mining_job); | ||
| 681 | httpd_register_uri_handler(s_api_server, &uri_mining_share); | ||
| 682 | httpd_register_uri_handler(s_api_server, &uri_mining_stats); | ||
| 683 | } | ||
| 684 | |||
| 498 | ESP_LOGI(TAG, "TollGate API started on port 2121"); | 685 | ESP_LOGI(TAG, "TollGate API started on port 2121"); |
| 499 | return ESP_OK; | 686 | return ESP_OK; |
| 500 | } | 687 | } |
diff --git a/main/tollgate_client.c b/main/tollgate_client.c index ac8dcfe..33b108e 100644 --- a/main/tollgate_client.c +++ b/main/tollgate_client.c | |||
| @@ -126,15 +126,25 @@ static bool parse_discovery_response(const char *json_str, tollgate_discovery_t | |||
| 126 | if (val && cJSON_IsString(val)) { | 126 | if (val && cJSON_IsString(val)) { |
| 127 | out->step_size_ms = atoi(val->valuestring); | 127 | out->step_size_ms = atoi(val->valuestring); |
| 128 | } | 128 | } |
| 129 | } else if (strcmp(tag_name->valuestring, "price_per_step") == 0 && tag_len >= 6) { | 129 | } else if (strcmp(tag_name->valuestring, "price_per_step") == 0 && tag_len >= 4) { |
| 130 | cJSON *amount = cJSON_GetArrayItem(tag, 2); | 130 | cJSON *payment_type = cJSON_GetArrayItem(tag, 2); |
| 131 | cJSON *mint = cJSON_GetArrayItem(tag, 4); | 131 | |
| 132 | if (cJSON_IsString(payment_type) && strcmp(payment_type->valuestring, "mining") == 0 && tag_len >= 5) { | ||
| 133 | out->mining_available = true; | ||
| 134 | cJSON *port_val = cJSON_GetArrayItem(tag, 3); | ||
| 135 | if (port_val && cJSON_IsString(port_val)) { | ||
| 136 | out->mining_port = (uint16_t)atoi(port_val->valuestring); | ||
| 137 | } | ||
| 138 | } else { | ||
| 139 | cJSON *amount = cJSON_GetArrayItem(tag, 2); | ||
| 140 | cJSON *mint = cJSON_GetArrayItem(tag, 4); | ||
| 132 | 141 | ||
| 133 | if (amount && cJSON_IsString(amount)) { | 142 | if (amount && cJSON_IsString(amount)) { |
| 134 | out->price_per_step = atoi(amount->valuestring); | 143 | out->price_per_step = atoi(amount->valuestring); |
| 135 | } | 144 | } |
| 136 | if (mint && cJSON_IsString(mint)) { | 145 | if (mint && cJSON_IsString(mint)) { |
| 137 | strncpy(out->mint_url, mint->valuestring, sizeof(out->mint_url) - 1); | 146 | strncpy(out->mint_url, mint->valuestring, sizeof(out->mint_url) - 1); |
| 147 | } | ||
| 138 | } | 148 | } |
| 139 | } | 149 | } |
| 140 | } | 150 | } |
diff --git a/main/tollgate_client.h b/main/tollgate_client.h index 2055e52..ccee624 100644 --- a/main/tollgate_client.h +++ b/main/tollgate_client.h | |||
| @@ -17,6 +17,7 @@ typedef enum { | |||
| 17 | TG_CLIENT_PAYING, | 17 | TG_CLIENT_PAYING, |
| 18 | TG_CLIENT_PAID, | 18 | TG_CLIENT_PAID, |
| 19 | TG_CLIENT_RENEWING, | 19 | TG_CLIENT_RENEWING, |
| 20 | TG_CLIENT_MINING, | ||
| 20 | TG_CLIENT_ERROR | 21 | TG_CLIENT_ERROR |
| 21 | } tollgate_client_state_t; | 22 | } tollgate_client_state_t; |
| 22 | 23 | ||
| @@ -26,6 +27,8 @@ typedef struct { | |||
| 26 | int step_size_ms; | 27 | int step_size_ms; |
| 27 | char mint_url[TG_CLIENT_MAX_MINT_URL]; | 28 | char mint_url[TG_CLIENT_MAX_MINT_URL]; |
| 28 | char metric[TG_CLIENT_MAX_METRIC]; | 29 | char metric[TG_CLIENT_MAX_METRIC]; |
| 30 | bool mining_available; | ||
| 31 | uint16_t mining_port; | ||
| 29 | } tollgate_discovery_t; | 32 | } tollgate_discovery_t; |
| 30 | 33 | ||
| 31 | esp_err_t tollgate_client_init(void); | 34 | esp_err_t tollgate_client_init(void); |
diff --git a/main/tollgate_main.c b/main/tollgate_main.c index fa7a692..30a63db 100644 --- a/main/tollgate_main.c +++ b/main/tollgate_main.c | |||
| @@ -23,6 +23,11 @@ | |||
| 23 | #include "tollgate_client.h" | 23 | #include "tollgate_client.h" |
| 24 | #include "lightning_payout.h" | 24 | #include "lightning_payout.h" |
| 25 | #include "cvm_server.h" | 25 | #include "cvm_server.h" |
| 26 | #include "stratum_client.h" | ||
| 27 | #include "stratum_proxy.h" | ||
| 28 | #include "sw_miner.h" | ||
| 29 | #include "asic_miner.h" | ||
| 30 | #include "mining_payment.h" | ||
| 26 | 31 | ||
| 27 | #define MAX_STA_RETRY 5 | 32 | #define MAX_STA_RETRY 5 |
| 28 | static const char *TAG = "tollgate_main"; | 33 | static const char *TAG = "tollgate_main"; |
| @@ -154,6 +159,11 @@ static void start_services(void) | |||
| 154 | session_manager_init(); | 159 | session_manager_init(); |
| 155 | 160 | ||
| 156 | const tollgate_config_t *cfg = tollgate_config_get(); | 161 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 162 | if (cfg->mining_enabled) { | ||
| 163 | firewall_set_mining_port(cfg->mining_port); | ||
| 164 | firewall_set_sandbox_mint_access(cfg->mining_sandbox_mint_access); | ||
| 165 | } | ||
| 166 | |||
| 157 | nucula_wallet_init(cfg->mint_url); | 167 | nucula_wallet_init(cfg->mint_url); |
| 158 | lightning_payout_init(&cfg->payout); | 168 | lightning_payout_init(&cfg->payout); |
| 159 | 169 | ||
| @@ -175,6 +185,26 @@ static void start_services(void) | |||
| 175 | cvm_server_start(); | 185 | cvm_server_start(); |
| 176 | } | 186 | } |
| 177 | 187 | ||
| 188 | if (cfg2->mining_enabled) { | ||
| 189 | ESP_LOGI(TAG, "Mining subsystem enabled, initializing..."); | ||
| 190 | mining_payment_init(); | ||
| 191 | stratum_client_init(); | ||
| 192 | stratum_proxy_init(cfg2->mining_port); | ||
| 193 | |||
| 194 | if (cfg2->mining_payout_mode != MINING_PAYOUT_UPSTREAM) { | ||
| 195 | stratum_client_start(); | ||
| 196 | } | ||
| 197 | |||
| 198 | asic_miner_init(); | ||
| 199 | if (asic_miner_is_present()) { | ||
| 200 | asic_miner_start(); | ||
| 201 | ESP_LOGI(TAG, "ASIC miner started"); | ||
| 202 | } else { | ||
| 203 | sw_miner_start(); | ||
| 204 | ESP_LOGI(TAG, "Software miner started (no ASIC)"); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 178 | s_services_running = true; | 208 | s_services_running = true; |
| 179 | if (s_services_mutex) xSemaphoreGive(s_services_mutex); | 209 | if (s_services_mutex) xSemaphoreGive(s_services_mutex); |
| 180 | ESP_LOGI(TAG, "=== TollGate services started ==="); | 210 | ESP_LOGI(TAG, "=== TollGate services started ==="); |
| @@ -329,5 +359,6 @@ void app_main(void) | |||
| 329 | session_tick(); | 359 | session_tick(); |
| 330 | tollgate_client_tick(); | 360 | tollgate_client_tick(); |
| 331 | lightning_payout_tick(); | 361 | lightning_payout_tick(); |
| 362 | stratum_client_tick(); | ||
| 332 | } | 363 | } |
| 333 | } | 364 | } |