diff options
Diffstat (limited to 'main')
| -rw-r--r-- | main/captive_portal.c | 108 | ||||
| -rw-r--r-- | main/mint_health.c | 94 | ||||
| -rw-r--r-- | main/stratum_client.c | 95 | ||||
| -rw-r--r-- | main/tollgate_client.c | 171 | ||||
| -rw-r--r-- | main/tollgate_client.h | 27 |
5 files changed, 111 insertions, 384 deletions
diff --git a/main/captive_portal.c b/main/captive_portal.c index f95a791..8113856 100644 --- a/main/captive_portal.c +++ b/main/captive_portal.c | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | #include "tollgate_core.h" | 2 | #include "tollgate_core.h" |
| 3 | #include "tollgate_core_firewall.h" | 3 | #include "tollgate_core_firewall.h" |
| 4 | #include "tollgate_core_session.h" | 4 | #include "tollgate_core_session.h" |
| 5 | #include "tollgate_core_portal.h" | ||
| 5 | #include "config.h" | 6 | #include "config.h" |
| 6 | #include "stratum_proxy.h" | 7 | #include "stratum_proxy.h" |
| 7 | #include "esp_log.h" | 8 | #include "esp_log.h" |
| @@ -176,10 +177,12 @@ static esp_err_t portal_handler(httpd_req_t *req) | |||
| 176 | char price_str[16]; | 177 | char price_str[16]; |
| 177 | snprintf(price_str, sizeof(price_str), "%d", cfg->price_per_step); | 178 | snprintf(price_str, sizeof(price_str), "%d", cfg->price_per_step); |
| 178 | 179 | ||
| 179 | const char *tpl = PORTAL_HTML_TEMPLATE; | 180 | char mining_port_buf[8] = "3333"; |
| 180 | size_t tpl_len = strlen(tpl); | 181 | if (cfg->mining_enabled) { |
| 182 | snprintf(mining_port_buf, sizeof(mining_port_buf), "%d", cfg->mining_port); | ||
| 183 | } | ||
| 181 | 184 | ||
| 182 | struct { const char *key; const char *val; } subs[] = { | 185 | tollgate_portal_sub_t subs[] = { |
| 183 | { "__AP_IP__", s_ap_ip_str }, | 186 | { "__AP_IP__", s_ap_ip_str }, |
| 184 | { "__PRICE__", price_str }, | 187 | { "__PRICE__", price_str }, |
| 185 | { "__MINT_URL__", cfg->mint_url }, | 188 | { "__MINT_URL__", cfg->mint_url }, |
| @@ -188,63 +191,19 @@ static esp_err_t portal_handler(httpd_req_t *req) | |||
| 188 | "<button class='tab active' onclick=\"switchTab('cashu')\">Cashu</button>" | 191 | "<button class='tab active' onclick=\"switchTab('cashu')\">Cashu</button>" |
| 189 | "<button class='tab' onclick=\"switchTab('mining')\">Mine</button>" | 192 | "<button class='tab' onclick=\"switchTab('mining')\">Mine</button>" |
| 190 | "</div>" : "" }, | 193 | "</div>" : "" }, |
| 191 | { "__MINING_PORT__", cfg->mining_enabled ? | 194 | { "__MINING_PORT__", mining_port_buf }, |
| 192 | (char[]){ [0 ... 7] = 0 } : "3333" }, | ||
| 193 | { "__CASHU_ACTIVE__", "active" }, | 195 | { "__CASHU_ACTIVE__", "active" }, |
| 194 | { "__MINING_ACTIVE__", "" }, | 196 | { "__MINING_ACTIVE__", "" }, |
| 195 | }; | 197 | }; |
| 196 | char mining_port_buf[8] = "3333"; | ||
| 197 | if (cfg->mining_enabled) { | ||
| 198 | snprintf(mining_port_buf, sizeof(mining_port_buf), "%d", cfg->mining_port); | ||
| 199 | subs[4].val = mining_port_buf; | ||
| 200 | } | ||
| 201 | int nsubs = sizeof(subs) / sizeof(subs[0]); | 198 | int nsubs = sizeof(subs) / sizeof(subs[0]); |
| 202 | 199 | ||
| 203 | size_t extra = 0; | 200 | char *html = tollgate_core_portal_render(PORTAL_HTML_TEMPLATE, subs, nsubs); |
| 204 | for (int i = 0; i < nsubs; i++) { | ||
| 205 | const char *p = tpl; | ||
| 206 | size_t klen = strlen(subs[i].key); | ||
| 207 | while ((p = strstr(p, subs[i].key)) != NULL) { | ||
| 208 | extra += strlen(subs[i].val) - klen; | ||
| 209 | p += klen; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | size_t out_size = tpl_len + extra + 1; | ||
| 214 | char *html = malloc(out_size); | ||
| 215 | if (!html) { | 201 | if (!html) { |
| 216 | httpd_resp_send_500(req); | 202 | httpd_resp_send_500(req); |
| 217 | return ESP_OK; | 203 | return ESP_OK; |
| 218 | } | 204 | } |
| 219 | 205 | ||
| 220 | char *out = html; | 206 | httpd_resp_send(req, html, strlen(html)); |
| 221 | const char *src = tpl; | ||
| 222 | while (*src) { | ||
| 223 | const char *earliest = NULL; | ||
| 224 | int ei = -1; | ||
| 225 | for (int i = 0; i < nsubs; i++) { | ||
| 226 | const char *found = strstr(src, subs[i].key); | ||
| 227 | if (found && (earliest == NULL || found < earliest)) { | ||
| 228 | earliest = found; | ||
| 229 | ei = i; | ||
| 230 | } | ||
| 231 | } | ||
| 232 | if (earliest) { | ||
| 233 | size_t vlen = strlen(subs[ei].val); | ||
| 234 | memcpy(out, src, earliest - src); | ||
| 235 | out += earliest - src; | ||
| 236 | memcpy(out, subs[ei].val, vlen); | ||
| 237 | out += vlen; | ||
| 238 | src = earliest + strlen(subs[ei].key); | ||
| 239 | } else { | ||
| 240 | strcpy(out, src); | ||
| 241 | out += strlen(src); | ||
| 242 | break; | ||
| 243 | } | ||
| 244 | } | ||
| 245 | *out = '\0'; | ||
| 246 | |||
| 247 | httpd_resp_send(req, html, out - html); | ||
| 248 | free(html); | 207 | free(html); |
| 249 | return ESP_OK; | 208 | return ESP_OK; |
| 250 | } | 209 | } |
| @@ -312,19 +271,10 @@ static esp_err_t usage_handler(httpd_req_t *req) | |||
| 312 | } | 271 | } |
| 313 | 272 | ||
| 314 | const tollgate_config_t *cfg = tollgate_config_get(); | 273 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 315 | bool is_bytes = (strcmp(cfg->metric, "bytes") == 0); | 274 | int64_t now_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; |
| 316 | 275 | ||
| 317 | char resp[64]; | 276 | char resp[64]; |
| 318 | if (is_bytes) { | 277 | tollgate_core_portal_format_usage(session, cfg->metric, now_ms, resp, sizeof(resp)); |
| 319 | int64_t remaining = (int64_t)session->allotment_bytes - (int64_t)session->bytes_consumed; | ||
| 320 | if (remaining < 0) remaining = 0; | ||
| 321 | snprintf(resp, sizeof(resp), "%lld/%llu", (long long)remaining, (unsigned long long)session->allotment_bytes); | ||
| 322 | } else { | ||
| 323 | int64_t elapsed = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS - session->start_time_ms; | ||
| 324 | int64_t remaining = session->allotment_ms - elapsed; | ||
| 325 | if (remaining < 0) remaining = 0; | ||
| 326 | snprintf(resp, sizeof(resp), "%lld/%llu", (long long)remaining, (unsigned long long)session->allotment_ms); | ||
| 327 | } | ||
| 328 | httpd_resp_set_type(req, "text/plain"); | 278 | httpd_resp_set_type(req, "text/plain"); |
| 329 | httpd_resp_send(req, resp, strlen(resp)); | 279 | httpd_resp_send(req, resp, strlen(resp)); |
| 330 | return ESP_OK; | 280 | return ESP_OK; |
| @@ -342,7 +292,9 @@ static esp_err_t reset_auth_handler(httpd_req_t *req) | |||
| 342 | 292 | ||
| 343 | static esp_err_t redirect_to_portal_handler(httpd_req_t *req) | 293 | static esp_err_t redirect_to_portal_handler(httpd_req_t *req) |
| 344 | { | 294 | { |
| 345 | ESP_LOGI(TAG, "Captive detect: GET %s → 200 portal HTML", req->uri); | 295 | if (tollgate_core_portal_is_captive_uri(req->uri)) { |
| 296 | ESP_LOGI(TAG, "Captive detect: GET %s → 200 portal HTML", req->uri); | ||
| 297 | } | ||
| 346 | return portal_handler(req); | 298 | return portal_handler(req); |
| 347 | } | 299 | } |
| 348 | 300 | ||
| @@ -493,37 +445,7 @@ static const char SETUP_HTML_TEMPLATE[] = \ | |||
| 493 | "</body></html>"; | 445 | "</body></html>"; |
| 494 | 446 | ||
| 495 | static char *template_replace(const char *tpl, const char *key, const char *val) { | 447 | static char *template_replace(const char *tpl, const char *key, const char *val) { |
| 496 | const char *p; | 448 | return tollgate_core_portal_template_replace(tpl, key, val); |
| 497 | size_t klen = strlen(key); | ||
| 498 | size_t vlen = strlen(val); | ||
| 499 | size_t tlen = strlen(tpl); | ||
| 500 | size_t extra = 0; | ||
| 501 | p = tpl; | ||
| 502 | while ((p = strstr(p, key)) != NULL) { | ||
| 503 | extra += vlen - klen; | ||
| 504 | p += klen; | ||
| 505 | } | ||
| 506 | size_t out_size = tlen + extra + 1; | ||
| 507 | char *out = malloc(out_size); | ||
| 508 | if (!out) return NULL; | ||
| 509 | char *dst = out; | ||
| 510 | p = tpl; | ||
| 511 | while (*p) { | ||
| 512 | const char *found = strstr(p, key); | ||
| 513 | if (found) { | ||
| 514 | memcpy(dst, p, found - p); | ||
| 515 | dst += found - p; | ||
| 516 | memcpy(dst, val, vlen); | ||
| 517 | dst += vlen; | ||
| 518 | p = found + klen; | ||
| 519 | } else { | ||
| 520 | strcpy(dst, p); | ||
| 521 | dst += strlen(p); | ||
| 522 | break; | ||
| 523 | } | ||
| 524 | } | ||
| 525 | *dst = '\0'; | ||
| 526 | return out; | ||
| 527 | } | 449 | } |
| 528 | 450 | ||
| 529 | static bool is_setup_available(void) { | 451 | static bool is_setup_available(void) { |
diff --git a/main/mint_health.c b/main/mint_health.c index d309731..48a4333 100644 --- a/main/mint_health.c +++ b/main/mint_health.c | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | #include "mint_health.h" | 1 | #include "mint_health.h" |
| 2 | #include "tls_worker.h" | 2 | #include "tls_worker.h" |
| 3 | #include "tollgate_core_mint_health.h" | ||
| 3 | #include "esp_log.h" | 4 | #include "esp_log.h" |
| 4 | #include "esp_http_client.h" | 5 | #include "esp_http_client.h" |
| 5 | #include "esp_crt_bundle.h" | 6 | #include "esp_crt_bundle.h" |
| @@ -18,8 +19,7 @@ static QueueHandle_t s_wallet_queue = NULL; | |||
| 18 | 19 | ||
| 19 | static int s_last_probe_err = 0; | 20 | static int s_last_probe_err = 0; |
| 20 | 21 | ||
| 21 | static mint_status_t s_mints[MINT_HEALTH_MAX]; | 22 | static tollgate_mint_health_t s_health_state; |
| 22 | static int s_mint_count = 0; | ||
| 23 | static bool s_running = false; | 23 | static bool s_running = false; |
| 24 | static TaskHandle_t s_task_handle = NULL; | 24 | static TaskHandle_t s_task_handle = NULL; |
| 25 | static SemaphoreHandle_t s_mutex = NULL; | 25 | static SemaphoreHandle_t s_mutex = NULL; |
| @@ -38,18 +38,18 @@ static void fire_callbacks(void) | |||
| 38 | esp_err_t mint_health_init(const char urls[][256], int count) | 38 | esp_err_t mint_health_init(const char urls[][256], int count) |
| 39 | { | 39 | { |
| 40 | if (count > MINT_HEALTH_MAX) count = MINT_HEALTH_MAX; | 40 | if (count > MINT_HEALTH_MAX) count = MINT_HEALTH_MAX; |
| 41 | s_mint_count = count; | 41 | s_health_state.count = count; |
| 42 | s_callback_count = 0; | 42 | s_callback_count = 0; |
| 43 | 43 | ||
| 44 | if (!s_mutex) s_mutex = xSemaphoreCreateMutex(); | 44 | if (!s_mutex) s_mutex = xSemaphoreCreateMutex(); |
| 45 | 45 | ||
| 46 | memset(s_mints, 0, sizeof(s_mints)); | 46 | memset(s_health_state.mints, 0, sizeof(s_health_state.mints)); |
| 47 | for (int i = 0; i < count; i++) { | 47 | for (int i = 0; i < count; i++) { |
| 48 | strncpy(s_mints[i].url, urls[i], sizeof(s_mints[i].url) - 1); | 48 | strncpy(s_health_state.mints[i].url, urls[i], sizeof(s_health_state.mints[i].url) - 1); |
| 49 | s_mints[i].reachable = false; | 49 | s_health_state.mints[i].reachable = false; |
| 50 | s_mints[i].consecutive_successes = 0; | 50 | s_health_state.mints[i].consecutive_successes = 0; |
| 51 | s_mints[i].last_probe_ms = 0; | 51 | s_health_state.mints[i].last_probe_ms = 0; |
| 52 | s_mints[i].last_http_status = 0; | 52 | s_health_state.mints[i].last_http_status = 0; |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | ESP_LOGI(TAG, "Initialized with %d mints", count); | 55 | ESP_LOGI(TAG, "Initialized with %d mints", count); |
| @@ -106,35 +106,17 @@ static void run_probes(void) | |||
| 106 | 106 | ||
| 107 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; | 107 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; |
| 108 | 108 | ||
| 109 | for (int i = 0; i < s_mint_count; i++) { | 109 | old_reachable = tollgate_core_mint_health_count_reachable(&s_health_state); |
| 110 | if (s_mints[i].reachable) old_reachable++; | ||
| 111 | } | ||
| 112 | |||
| 113 | for (int i = 0; i < s_mint_count; i++) { | ||
| 114 | bool ok = probe_mint(s_mints[i].url); | ||
| 115 | s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; | ||
| 116 | s_mints[i].last_http_status = ok ? 200 : 0; | ||
| 117 | s_mints[i].last_err = ok ? 0 : s_last_probe_err; | ||
| 118 | 110 | ||
| 119 | if (ok) { | 111 | for (int i = 0; i < s_health_state.count; i++) { |
| 120 | s_mints[i].consecutive_successes++; | 112 | bool ok = probe_mint(s_health_state.mints[i].url); |
| 121 | if (s_mints[i].consecutive_successes >= MINT_HEALTH_RECOVERY_THRESHOLD) { | 113 | int64_t probe_time = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; |
| 122 | if (!s_mints[i].reachable) { | ||
| 123 | ESP_LOGI(TAG, "Mint RECOVERED: %s", s_mints[i].url); | ||
| 124 | } | ||
| 125 | s_mints[i].reachable = true; | ||
| 126 | } | ||
| 127 | } else { | ||
| 128 | if (s_mints[i].reachable) { | ||
| 129 | ESP_LOGW(TAG, "Mint UNREACHABLE: %s err=0x%x", s_mints[i].url, s_last_probe_err); | ||
| 130 | } | ||
| 131 | s_mints[i].reachable = false; | ||
| 132 | s_mints[i].consecutive_successes = 0; | ||
| 133 | } | ||
| 134 | 114 | ||
| 135 | if (s_mints[i].reachable) new_reachable++; | 115 | tollgate_core_mint_health_update(&s_health_state, i, ok, ok ? 200 : 0, |
| 116 | ok ? 0 : s_last_probe_err, probe_time); | ||
| 136 | } | 117 | } |
| 137 | 118 | ||
| 119 | new_reachable = tollgate_core_mint_health_count_reachable(&s_health_state); | ||
| 138 | bool changed = (old_reachable != new_reachable); | 120 | bool changed = (old_reachable != new_reachable); |
| 139 | xSemaphoreGive(s_mutex); | 121 | xSemaphoreGive(s_mutex); |
| 140 | 122 | ||
| @@ -148,20 +130,17 @@ static void run_initial_probes(void) | |||
| 148 | { | 130 | { |
| 149 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; | 131 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; |
| 150 | 132 | ||
| 151 | for (int i = 0; i < s_mint_count; i++) { | 133 | for (int i = 0; i < s_health_state.count; i++) { |
| 152 | bool ok = probe_mint(s_mints[i].url); | 134 | bool ok = probe_mint(s_health_state.mints[i].url); |
| 153 | s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; | 135 | int64_t probe_time = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; |
| 154 | s_mints[i].last_http_status = ok ? 200 : 0; | 136 | |
| 155 | s_mints[i].last_err = ok ? 0 : s_last_probe_err; | 137 | tollgate_core_mint_health_update_initial(&s_health_state, i, ok, ok ? 200 : 0, |
| 138 | ok ? 0 : s_last_probe_err, probe_time); | ||
| 156 | 139 | ||
| 157 | if (ok) { | 140 | if (ok) { |
| 158 | s_mints[i].consecutive_successes = MINT_HEALTH_RECOVERY_THRESHOLD; | 141 | ESP_LOGI(TAG, "Initial probe OK: %s (reachable)", s_health_state.mints[i].url); |
| 159 | s_mints[i].reachable = true; | ||
| 160 | ESP_LOGI(TAG, "Initial probe OK: %s (reachable)", s_mints[i].url); | ||
| 161 | } else { | 142 | } else { |
| 162 | s_mints[i].consecutive_successes = 0; | 143 | ESP_LOGW(TAG, "Initial probe FAIL: %s (unreachable)", s_health_state.mints[i].url); |
| 163 | s_mints[i].reachable = false; | ||
| 164 | ESP_LOGW(TAG, "Initial probe FAIL: %s (unreachable)", s_mints[i].url); | ||
| 165 | } | 144 | } |
| 166 | } | 145 | } |
| 167 | 146 | ||
| @@ -246,24 +225,18 @@ const mint_status_t *mint_health_get_all(int *out_count) | |||
| 246 | { | 225 | { |
| 247 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { | 226 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { |
| 248 | *out_count = 0; | 227 | *out_count = 0; |
| 249 | return s_mints; | 228 | return (const mint_status_t *)s_health_state.mints; |
| 250 | } | 229 | } |
| 251 | *out_count = s_mint_count; | 230 | *out_count = s_health_state.count; |
| 252 | xSemaphoreGive(s_mutex); | 231 | xSemaphoreGive(s_mutex); |
| 253 | return s_mints; | 232 | return (const mint_status_t *)s_health_state.mints; |
| 254 | } | 233 | } |
| 255 | 234 | ||
| 256 | bool mint_health_is_reachable(const char *url) | 235 | bool mint_health_is_reachable(const char *url) |
| 257 | { | 236 | { |
| 258 | if (!url) return false; | 237 | if (!url) return false; |
| 259 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return false; | 238 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return false; |
| 260 | bool result = false; | 239 | bool result = tollgate_core_mint_health_is_reachable(&s_health_state, url); |
| 261 | for (int i = 0; i < s_mint_count; i++) { | ||
| 262 | if (strcmp(s_mints[i].url, url) == 0 || strstr(url, s_mints[i].url) != NULL) { | ||
| 263 | result = s_mints[i].reachable; | ||
| 264 | break; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | xSemaphoreGive(s_mutex); | 240 | xSemaphoreGive(s_mutex); |
| 268 | return result; | 241 | return result; |
| 269 | } | 242 | } |
| @@ -272,16 +245,7 @@ void mint_health_mark_unreachable(const char *url) | |||
| 272 | { | 245 | { |
| 273 | if (!url) return; | 246 | if (!url) return; |
| 274 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return; | 247 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return; |
| 275 | for (int i = 0; i < s_mint_count; i++) { | 248 | tollgate_core_mint_health_mark_unreachable(&s_health_state, url); |
| 276 | if (strcmp(s_mints[i].url, url) == 0 || strstr(url, s_mints[i].url) != NULL) { | ||
| 277 | if (s_mints[i].reachable) { | ||
| 278 | s_mints[i].reachable = false; | ||
| 279 | s_mints[i].consecutive_successes = 0; | ||
| 280 | ESP_LOGW(TAG, "Reactively marked unreachable: %s", url); | ||
| 281 | } | ||
| 282 | break; | ||
| 283 | } | ||
| 284 | } | ||
| 285 | xSemaphoreGive(s_mutex); | 249 | xSemaphoreGive(s_mutex); |
| 286 | } | 250 | } |
| 287 | 251 | ||
diff --git a/main/stratum_client.c b/main/stratum_client.c index a308a5b..14f0880 100644 --- a/main/stratum_client.c +++ b/main/stratum_client.c | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | #include "stratum_client.h" | 1 | #include "stratum_client.h" |
| 2 | #include "stratum_proxy.h" | 2 | #include "stratum_proxy.h" |
| 3 | #include "tollgate_core_mining.h" | 3 | #include "tollgate_core_mining.h" |
| 4 | #include "tollgate_core_stratum_client.h" | ||
| 4 | #include "config.h" | 5 | #include "config.h" |
| 5 | #include "esp_log.h" | 6 | #include "esp_log.h" |
| 6 | #include "esp_transport.h" | 7 | #include "esp_transport.h" |
| @@ -66,76 +67,53 @@ static esp_err_t stratum_connect(const char *host, uint16_t port) | |||
| 66 | static void send_subscribe(void) | 67 | static void send_subscribe(void) |
| 67 | { | 68 | { |
| 68 | char subscribe[256]; | 69 | char subscribe[256]; |
| 69 | snprintf(subscribe, sizeof(subscribe), | 70 | int len = tollgate_core_stratum_build_subscribe(subscribe, sizeof(subscribe), s_req_id++); |
| 70 | "{\"id\":%lu,\"method\":\"mining.subscribe\",\"params\":[\"TollGate/1.0\"]}\n", | 71 | if (len > 0) { |
| 71 | (unsigned long)s_req_id++); | 72 | esp_transport_write(s_transport, subscribe, len, 5000); |
| 72 | esp_transport_write(s_transport, subscribe, strlen(subscribe), 5000); | 73 | ESP_LOGI(TAG, "Sent mining.subscribe"); |
| 73 | ESP_LOGI(TAG, "Sent mining.subscribe"); | 74 | } |
| 74 | } | 75 | } |
| 75 | 76 | ||
| 76 | static void send_authorize(void) | 77 | static void send_authorize(void) |
| 77 | { | 78 | { |
| 78 | const tollgate_config_t *cfg = tollgate_config_get(); | 79 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 79 | char authorize[512]; | 80 | char authorize[512]; |
| 80 | snprintf(authorize, sizeof(authorize), | 81 | int len = tollgate_core_stratum_build_authorize(authorize, sizeof(authorize), |
| 81 | "{\"id\":%lu,\"method\":\"mining.authorize\",\"params\":[\"%s\",\"%s\"]}\n", | 82 | s_req_id++, cfg->stratum_user, cfg->stratum_pass); |
| 82 | (unsigned long)s_req_id++, cfg->stratum_user, cfg->stratum_pass); | 83 | if (len > 0) { |
| 83 | esp_transport_write(s_transport, authorize, strlen(authorize), 5000); | 84 | esp_transport_write(s_transport, authorize, len, 5000); |
| 84 | ESP_LOGI(TAG, "Sent mining.authorize for user=%s", cfg->stratum_user); | 85 | ESP_LOGI(TAG, "Sent mining.authorize for user=%s", cfg->stratum_user); |
| 85 | } | ||
| 86 | |||
| 87 | static void hex_to_bytes(const char *hex, uint8_t *out, int len) | ||
| 88 | { | ||
| 89 | for (int i = 0; i < len && hex[i * 2] && hex[i * 2 + 1]; i++) { | ||
| 90 | char byte[3] = {hex[i * 2], hex[i * 2 + 1], 0}; | ||
| 91 | out[i] = (uint8_t)strtoul(byte, NULL, 16); | ||
| 92 | } | 86 | } |
| 93 | } | 87 | } |
| 94 | 88 | ||
| 95 | static void handle_mining_notify(cJSON *params) | 89 | static void handle_mining_notify(cJSON *params) |
| 96 | { | 90 | { |
| 97 | if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 6) return; | 91 | tollgate_stratum_job_t job = {0}; |
| 98 | 92 | uint32_t nbits = 0; | |
| 99 | cJSON *p_job_id = cJSON_GetArrayItem(params, 0); | 93 | if (!tollgate_core_stratum_parse_notify(params, &job, &nbits)) return; |
| 100 | cJSON *p_prevhash = cJSON_GetArrayItem(params, 1); | 94 | |
| 101 | cJSON *p_version = cJSON_GetArrayItem(params, 5); | 95 | stratum_job_t proxy_job = {0}; |
| 102 | cJSON *p_nbits = cJSON_GetArrayItem(params, 6); | 96 | proxy_job.job_id = job.job_id; |
| 103 | cJSON *p_ntime = cJSON_GetArrayItem(params, 7); | 97 | memcpy(proxy_job.prevhash, job.prevhash, 32); |
| 104 | 98 | proxy_job.version = job.version; | |
| 105 | if (!p_job_id || !p_prevhash || !p_nbits) return; | 99 | proxy_job.nbits = job.nbits; |
| 106 | 100 | proxy_job.ntime = job.ntime; | |
| 107 | stratum_job_t job = {0}; | 101 | memcpy(proxy_job.target, job.target, 32); |
| 108 | job.job_id = (uint32_t)atoi(p_job_id->valuestring); | 102 | proxy_job.target_len = job.target_len; |
| 109 | job.valid = true; | 103 | proxy_job.valid = job.valid; |
| 110 | 104 | ||
| 111 | hex_to_bytes(p_prevhash->valuestring, job.prevhash, 32); | 105 | tollgate_core_mining_set_current_nbits(proxy_job.nbits); |
| 112 | 106 | stratum_proxy_set_job(&proxy_job); | |
| 113 | if (p_version && cJSON_IsString(p_version)) { | 107 | |
| 114 | job.version = (uint32_t)strtoul(p_version->valuestring, NULL, 16); | 108 | if (nbits) s_state.nbits = nbits; |
| 115 | } | ||
| 116 | if (p_nbits && cJSON_IsString(p_nbits)) { | ||
| 117 | job.nbits = (uint32_t)strtoul(p_nbits->valuestring, NULL, 16); | ||
| 118 | s_state.nbits = job.nbits; | ||
| 119 | } | ||
| 120 | if (p_ntime && cJSON_IsString(p_ntime)) { | ||
| 121 | job.ntime = (uint32_t)strtoul(p_ntime->valuestring, NULL, 16); | ||
| 122 | } | ||
| 123 | |||
| 124 | memset(job.target, 0xFF, 32); | ||
| 125 | job.target_len = 32; | ||
| 126 | |||
| 127 | tollgate_core_mining_set_current_nbits(job.nbits); | ||
| 128 | stratum_proxy_set_job(&job); | ||
| 129 | |||
| 130 | ESP_LOGI(TAG, "New mining job: id=%lu, nbits=0x%08lx", (unsigned long)job.job_id, (unsigned long)job.nbits); | 109 | ESP_LOGI(TAG, "New mining job: id=%lu, nbits=0x%08lx", (unsigned long)job.job_id, (unsigned long)job.nbits); |
| 131 | } | 110 | } |
| 132 | 111 | ||
| 133 | static void handle_mining_set_difficulty(cJSON *params) | 112 | static void handle_mining_set_difficulty(cJSON *params) |
| 134 | { | 113 | { |
| 135 | if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 1) return; | 114 | uint64_t diff = 0; |
| 136 | cJSON *diff = cJSON_GetArrayItem(params, 0); | 115 | if (tollgate_core_stratum_parse_difficulty(params, &diff)) { |
| 137 | if (diff && cJSON_IsNumber(diff)) { | 116 | s_state.difficulty = diff; |
| 138 | s_state.difficulty = (uint64_t)diff->valuedouble; | ||
| 139 | ESP_LOGI(TAG, "Pool set difficulty: %llu", (unsigned long long)s_state.difficulty); | 117 | ESP_LOGI(TAG, "Pool set difficulty: %llu", (unsigned long long)s_state.difficulty); |
| 140 | } | 118 | } |
| 141 | } | 119 | } |
| @@ -243,12 +221,11 @@ esp_err_t stratum_client_submit_share(uint32_t job_id, uint32_t nonce, uint32_t | |||
| 243 | const tollgate_config_t *cfg = tollgate_config_get(); | 221 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 244 | 222 | ||
| 245 | char submit[512]; | 223 | char submit[512]; |
| 246 | snprintf(submit, sizeof(submit), | 224 | int len = tollgate_core_stratum_build_submit(submit, sizeof(submit), s_req_id++, |
| 247 | "{\"id\":%lu,\"method\":\"mining.submit\",\"params\":[\"%s\",\"%lu\",\"%08lx\",\"%08lx\",\"%08lx\"]}\n", | 225 | cfg->stratum_user, job_id, ntime, nonce, version); |
| 248 | (unsigned long)s_req_id++, cfg->stratum_user, | 226 | if (len <= 0) return ESP_FAIL; |
| 249 | (unsigned long)job_id, (unsigned long)ntime, (unsigned long)nonce, (unsigned long)version); | ||
| 250 | 227 | ||
| 251 | int written = esp_transport_write(s_transport, submit, strlen(submit), 5000); | 228 | int written = esp_transport_write(s_transport, submit, len, 5000); |
| 252 | if (written < 0) { | 229 | if (written < 0) { |
| 253 | ESP_LOGW(TAG, "Failed to submit share"); | 230 | ESP_LOGW(TAG, "Failed to submit share"); |
| 254 | s_state.shares_rejected++; | 231 | s_state.shares_rejected++; |
diff --git a/main/tollgate_client.c b/main/tollgate_client.c index 73c8370..70a25c2 100644 --- a/main/tollgate_client.c +++ b/main/tollgate_client.c | |||
| @@ -88,123 +88,17 @@ static esp_err_t http_post_text(const char *url, const char *body, char *resp_bu | |||
| 88 | 88 | ||
| 89 | static bool parse_discovery_response(const char *json_str, tollgate_discovery_t *out) | 89 | static bool parse_discovery_response(const char *json_str, tollgate_discovery_t *out) |
| 90 | { | 90 | { |
| 91 | cJSON *root = cJSON_Parse(json_str); | 91 | return tollgate_core_client_parse_discovery(json_str, out); |
| 92 | if (!root) return false; | ||
| 93 | |||
| 94 | cJSON *kind = cJSON_GetObjectItemCaseSensitive(root, "kind"); | ||
| 95 | if (!kind || !cJSON_IsNumber(kind) || kind->valueint != 10021) { | ||
| 96 | cJSON_Delete(root); | ||
| 97 | return false; | ||
| 98 | } | ||
| 99 | |||
| 100 | memset(out, 0, sizeof(tollgate_discovery_t)); | ||
| 101 | out->is_tollgate = true; | ||
| 102 | |||
| 103 | cJSON *tags = cJSON_GetObjectItemCaseSensitive(root, "tags"); | ||
| 104 | if (!tags || !cJSON_IsArray(tags)) { | ||
| 105 | cJSON_Delete(root); | ||
| 106 | return true; | ||
| 107 | } | ||
| 108 | |||
| 109 | int tag_count = cJSON_GetArraySize(tags); | ||
| 110 | for (int i = 0; i < tag_count; i++) { | ||
| 111 | cJSON *tag = cJSON_GetArrayItem(tags, i); | ||
| 112 | if (!tag || !cJSON_IsArray(tag)) continue; | ||
| 113 | |||
| 114 | int tag_len = cJSON_GetArraySize(tag); | ||
| 115 | if (tag_len < 2) continue; | ||
| 116 | |||
| 117 | cJSON *tag_name = cJSON_GetArrayItem(tag, 0); | ||
| 118 | if (!tag_name || !cJSON_IsString(tag_name)) continue; | ||
| 119 | |||
| 120 | if (strcmp(tag_name->valuestring, "metric") == 0) { | ||
| 121 | cJSON *val = cJSON_GetArrayItem(tag, 1); | ||
| 122 | if (val && cJSON_IsString(val)) { | ||
| 123 | strncpy(out->metric, val->valuestring, sizeof(out->metric) - 1); | ||
| 124 | } | ||
| 125 | } else if (strcmp(tag_name->valuestring, "step_size") == 0) { | ||
| 126 | cJSON *val = cJSON_GetArrayItem(tag, 1); | ||
| 127 | if (val && cJSON_IsString(val)) { | ||
| 128 | out->step_size_ms = atoi(val->valuestring); | ||
| 129 | } | ||
| 130 | } else if (strcmp(tag_name->valuestring, "price_per_step") == 0 && tag_len >= 4) { | ||
| 131 | cJSON *payment_type = cJSON_GetArrayItem(tag, 2); | ||
| 132 | |||
| 133 | if (cJSON_IsString(payment_type) && strcmp(payment_type->valuestring, "mining") == 0 && tag_len >= 5) { | ||
| 134 | out->mining_available = true; | ||
| 135 | cJSON *port_val = cJSON_GetArrayItem(tag, 3); | ||
| 136 | if (port_val && cJSON_IsString(port_val)) { | ||
| 137 | out->mining_port = (uint16_t)atoi(port_val->valuestring); | ||
| 138 | } | ||
| 139 | } else { | ||
| 140 | cJSON *amount = cJSON_GetArrayItem(tag, 2); | ||
| 141 | cJSON *mint = cJSON_GetArrayItem(tag, 4); | ||
| 142 | |||
| 143 | if (amount && cJSON_IsString(amount)) { | ||
| 144 | out->price_per_step = atoi(amount->valuestring); | ||
| 145 | } | ||
| 146 | if (mint && cJSON_IsString(mint)) { | ||
| 147 | strncpy(out->mint_url, mint->valuestring, sizeof(out->mint_url) - 1); | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | cJSON_Delete(root); | ||
| 154 | return true; | ||
| 155 | } | 92 | } |
| 156 | 93 | ||
| 157 | static bool parse_session_response(const char *json_str, int64_t *allotment_ms_out) | 94 | static bool parse_session_response(const char *json_str, int64_t *allotment_ms_out) |
| 158 | { | 95 | { |
| 159 | cJSON *root = cJSON_Parse(json_str); | 96 | return tollgate_core_client_parse_session(json_str, allotment_ms_out); |
| 160 | if (!root) return false; | ||
| 161 | |||
| 162 | cJSON *kind = cJSON_GetObjectItemCaseSensitive(root, "kind"); | ||
| 163 | if (!kind || !cJSON_IsNumber(kind)) { | ||
| 164 | cJSON_Delete(root); | ||
| 165 | return false; | ||
| 166 | } | ||
| 167 | |||
| 168 | if (kind->valueint != 1022) { | ||
| 169 | cJSON_Delete(root); | ||
| 170 | return false; | ||
| 171 | } | ||
| 172 | |||
| 173 | cJSON *tags = cJSON_GetObjectItemCaseSensitive(root, "tags"); | ||
| 174 | if (tags && cJSON_IsArray(tags)) { | ||
| 175 | int tag_count = cJSON_GetArraySize(tags); | ||
| 176 | for (int i = 0; i < tag_count; i++) { | ||
| 177 | cJSON *tag = cJSON_GetArrayItem(tags, i); | ||
| 178 | if (!tag || !cJSON_IsArray(tag)) continue; | ||
| 179 | cJSON *tag_name = cJSON_GetArrayItem(tag, 0); | ||
| 180 | if (tag_name && cJSON_IsString(tag_name) && strcmp(tag_name->valuestring, "allotment") == 0) { | ||
| 181 | cJSON *val = cJSON_GetArrayItem(tag, 1); | ||
| 182 | if (val && cJSON_IsString(val)) { | ||
| 183 | *allotment_ms_out = atoll(val->valuestring); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | cJSON_Delete(root); | ||
| 190 | return true; | ||
| 191 | } | 97 | } |
| 192 | 98 | ||
| 193 | static bool parse_usage_response(const char *resp, int64_t *remaining_out, int64_t *total_out) | 99 | static bool parse_usage_response(const char *resp, int64_t *remaining_out, int64_t *total_out) |
| 194 | { | 100 | { |
| 195 | char remaining_str[32] = {0}; | 101 | return tollgate_core_client_parse_usage(resp, remaining_out, total_out); |
| 196 | char total_str[32] = {0}; | ||
| 197 | const char *slash = strchr(resp, '/'); | ||
| 198 | if (!slash) return false; | ||
| 199 | |||
| 200 | size_t rlen = slash - resp; | ||
| 201 | if (rlen >= sizeof(remaining_str)) return false; | ||
| 202 | memcpy(remaining_str, resp, rlen); | ||
| 203 | strncpy(total_str, slash + 1, sizeof(total_str) - 1); | ||
| 204 | |||
| 205 | *remaining_out = atoll(remaining_str); | ||
| 206 | *total_out = atoll(total_str); | ||
| 207 | return true; | ||
| 208 | } | 102 | } |
| 209 | 103 | ||
| 210 | esp_err_t tollgate_client_detect(const char *gw_ip, tollgate_discovery_t *discovery) | 104 | esp_err_t tollgate_client_detect(const char *gw_ip, tollgate_discovery_t *discovery) |
| @@ -358,13 +252,11 @@ esp_err_t tollgate_client_on_sta_connected(const char *gw_ip_str) | |||
| 358 | const market_t *mkt = market_get(); | 252 | const market_t *mkt = market_get(); |
| 359 | int cheapest = market_find_cheapest(); | 253 | int cheapest = market_find_cheapest(); |
| 360 | if (cheapest >= 0 && mkt->entries[cheapest].valid && mkt->entries[cheapest].ssid[0] != '\0') { | 254 | if (cheapest >= 0 && mkt->entries[cheapest].valid && mkt->entries[cheapest].ssid[0] != '\0') { |
| 361 | uint32_t upstream_step = s_discovery.step_size_ms > 0 ? s_discovery.step_size_ms : 1; | 255 | int upstream_eff = tollgate_core_client_calc_price_per_min(s_discovery.price_per_step, s_discovery.step_size_ms); |
| 362 | uint32_t upstream_eff = (uint32_t)s_discovery.price_per_step * 60000 / upstream_step; | 256 | int cheap_eff = tollgate_core_client_calc_price_per_min(mkt->entries[cheapest].price_per_step, mkt->entries[cheapest].step_size); |
| 363 | uint32_t cheap_step = mkt->entries[cheapest].step_size > 0 ? mkt->entries[cheapest].step_size : 1; | ||
| 364 | uint32_t cheap_eff = (uint32_t)mkt->entries[cheapest].price_per_step * 60000 / cheap_step; | ||
| 365 | if (cheap_eff < upstream_eff) { | 257 | if (cheap_eff < upstream_eff) { |
| 366 | ESP_LOGW(TAG, "CHEAPER TOLLGATE AVAILABLE: %s at %lu sats/min vs upstream %lu sats/min", | 258 | ESP_LOGW(TAG, "CHEAPER TOLLGATE AVAILABLE: %s at %d sats/min vs upstream %d sats/min", |
| 367 | mkt->entries[cheapest].ssid, (unsigned long)cheap_eff, (unsigned long)upstream_eff); | 259 | mkt->entries[cheapest].ssid, cheap_eff, upstream_eff); |
| 368 | } | 260 | } |
| 369 | } | 261 | } |
| 370 | return ESP_OK; | 262 | return ESP_OK; |
| @@ -430,32 +322,29 @@ void tollgate_client_tick(void) | |||
| 430 | 322 | ||
| 431 | const tollgate_config_t *cfg = tollgate_config_get(); | 323 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 432 | int threshold_pct = cfg->client_renewal_threshold_pct; | 324 | int threshold_pct = cfg->client_renewal_threshold_pct; |
| 433 | if (threshold_pct <= 0) threshold_pct = 20; | 325 | |
| 434 | 326 | if (tollgate_core_client_should_renew(s_remaining_ms, s_allotment_ms, threshold_pct)) { | |
| 435 | if (s_allotment_ms > 0 && s_remaining_ms >= 0) { | 327 | ESP_LOGI(TAG, "session nearing expiry (%lld/%lldms, %d%%), renewing", |
| 436 | int remaining_pct = (int)((s_remaining_ms * 100) / s_allotment_ms); | 328 | (long long)s_remaining_ms, (long long)s_allotment_ms, |
| 437 | if (remaining_pct <= threshold_pct) { | 329 | (int)((s_remaining_ms * 100) / s_allotment_ms)); |
| 438 | ESP_LOGI(TAG, "session nearing expiry (%lld/%lldms, %d%%), renewing", | 330 | |
| 439 | (long long)s_remaining_ms, (long long)s_allotment_ms, remaining_pct); | 331 | s_state = TG_CLIENT_RENEWING; |
| 440 | 332 | int steps = cfg->client_steps_to_buy; | |
| 441 | s_state = TG_CLIENT_RENEWING; | 333 | if (steps <= 0) steps = 1; |
| 442 | int steps = cfg->client_steps_to_buy; | 334 | int amount_sats = steps * s_discovery.price_per_step; |
| 443 | if (steps <= 0) steps = 1; | 335 | |
| 444 | int amount_sats = steps * s_discovery.price_per_step; | 336 | int64_t allotment = 0; |
| 445 | 337 | err = tollgate_client_pay(s_gw_ip, amount_sats, &allotment); | |
| 446 | int64_t allotment = 0; | 338 | if (err == ESP_OK) { |
| 447 | err = tollgate_client_pay(s_gw_ip, amount_sats, &allotment); | 339 | s_allotment_ms = allotment; |
| 448 | if (err == ESP_OK) { | 340 | s_remaining_ms = allotment; |
| 449 | s_allotment_ms = allotment; | 341 | s_last_pay_time_ms = get_time_ms(); |
| 450 | s_remaining_ms = allotment; | 342 | s_state = TG_CLIENT_PAID; |
| 451 | s_last_pay_time_ms = get_time_ms(); | 343 | ESP_LOGI(TAG, "renewal succeeded: %lldms", (long long)allotment); |
| 452 | s_state = TG_CLIENT_PAID; | 344 | } else { |
| 453 | ESP_LOGI(TAG, "renewal succeeded: %lldms", (long long)allotment); | 345 | s_state = TG_CLIENT_ERROR; |
| 454 | } else { | 346 | s_last_pay_time_ms = get_time_ms(); |
| 455 | s_state = TG_CLIENT_ERROR; | 347 | ESP_LOGE(TAG, "renewal payment failed"); |
| 456 | s_last_pay_time_ms = get_time_ms(); | ||
| 457 | ESP_LOGE(TAG, "renewal payment failed"); | ||
| 458 | } | ||
| 459 | } | 348 | } |
| 460 | } | 349 | } |
| 461 | } | 350 | } |
diff --git a/main/tollgate_client.h b/main/tollgate_client.h index ccee624..463cf63 100644 --- a/main/tollgate_client.h +++ b/main/tollgate_client.h | |||
| @@ -1,36 +1,11 @@ | |||
| 1 | #ifndef TOLLGATE_CLIENT_H | 1 | #ifndef TOLLGATE_CLIENT_H |
| 2 | #define TOLLGATE_CLIENT_H | 2 | #define TOLLGATE_CLIENT_H |
| 3 | 3 | ||
| 4 | #include "tollgate_core_client.h" | ||
| 4 | #include "esp_err.h" | 5 | #include "esp_err.h" |
| 5 | #include <stdint.h> | 6 | #include <stdint.h> |
| 6 | #include <stdbool.h> | 7 | #include <stdbool.h> |
| 7 | 8 | ||
| 8 | #define TG_CLIENT_MAX_GW_IP_LEN 16 | ||
| 9 | #define TG_CLIENT_MAX_MINT_URL 256 | ||
| 10 | #define TG_CLIENT_MAX_METRIC 32 | ||
| 11 | |||
| 12 | typedef enum { | ||
| 13 | TG_CLIENT_IDLE, | ||
| 14 | TG_CLIENT_DETECTING, | ||
| 15 | TG_CLIENT_NO_TOLLGATE, | ||
| 16 | TG_CLIENT_NEEDS_PAY, | ||
| 17 | TG_CLIENT_PAYING, | ||
| 18 | TG_CLIENT_PAID, | ||
| 19 | TG_CLIENT_RENEWING, | ||
| 20 | TG_CLIENT_MINING, | ||
| 21 | TG_CLIENT_ERROR | ||
| 22 | } tollgate_client_state_t; | ||
| 23 | |||
| 24 | typedef struct { | ||
| 25 | bool is_tollgate; | ||
| 26 | int price_per_step; | ||
| 27 | int step_size_ms; | ||
| 28 | char mint_url[TG_CLIENT_MAX_MINT_URL]; | ||
| 29 | char metric[TG_CLIENT_MAX_METRIC]; | ||
| 30 | bool mining_available; | ||
| 31 | uint16_t mining_port; | ||
| 32 | } tollgate_discovery_t; | ||
| 33 | |||
| 34 | esp_err_t tollgate_client_init(void); | 9 | esp_err_t tollgate_client_init(void); |
| 35 | 10 | ||
| 36 | esp_err_t tollgate_client_on_sta_connected(const char *gw_ip_str); | 11 | esp_err_t tollgate_client_on_sta_connected(const char *gw_ip_str); |