upleb.uk

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

summaryrefslogtreecommitdiff
path: root/main/tollgate_api.c
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-19 14:25:18 +0530
committerYour Name <you@example.com>2026-05-19 14:25:18 +0530
commite366ceb336550a72c76efea4c98a2a08cca27bce (patch)
tree4b45ac6f6e97b6763f81aa6d4a9b968d23e41235 /main/tollgate_api.c
parent163b8badec9359373a8fc016c2b1fe9ee38e6406 (diff)
feat(mining): Bitcoin mining-for-bandwidth payment system
New modules: - mining_payment.c/h: hashprice calc (nbits->difficulty->sat/GH/s/day), share validation, client stats, allotment conversion (ms + bytes) - stratum_client.c/h: SV1 upstream pool connection (subscribe/authorize/submit) - stratum_proxy.c/h: Local SV1 TCP server for downstream miners, job broadcast - sw_miner.c/h: Software SHA256d miner (ESP32 CPU fallback) - asic_miner.c/h: ASIC detection stub (BM1366/BM1368 SPI) Config: - config.h/c: mining_payout_mode_t enum (auto/pool/upstream/proxy_only), stratum pool settings, mining port, hashprice override, sandbox mint access - Defaults fill nostr_seed_relays (8/8) and nostr_relays (4/4) with fast relays Integration into existing modules: - session.h/c: payment_method_t enum (CASHU/MINING/BYTES) - firewall.h/c: firewall_set_mining_port(), firewall_set_sandbox_mint_access() - tollgate_api.c: GET /mining/job, POST /mining/share, GET /mining/stats - tollgate_client.h/c: TG_CLIENT_MINING state, mining discovery tag parsing - tollgate_main.c: mining init in start_services(), stratum_client_tick() in loop - captive_portal.c: tabbed Cashu/Mine UI with live hashrate polling Unit tests (69 new assertions across 4 suites): - test_mining_payment (23 tests): nbits->difficulty, hashprice, client stats, allotment - test_stratum_proxy (21 tests): job set/get, stats, type validation - test_session_payment_method (12 tests): PAYMENT_METHOD enum, bytes/cashu methods - test_tollgate_client_mining (20 tests): mining tag parsing, discovery struct - test_firewall_sandbox (16 tests): client grant/revoke, max clients, setters Enhanced test stubs: - BaseType_t/pdPASS in freertos/task.h - lwip: sockets.h, etharp.h, prot/ip.h, prot/ip4.h, prot/tcp.h, netif.h - dns_server.h, esp_wifi_ap_get_sta_list.h Build fixes: - cvm_server.c: replace esp_timer_get_time() with xTaskGetTickCount(), fix process_relay_message() 3-arg call to 2-arg, add WS keepalive ping - stratum_proxy.c: widen task_name buffer 16->20 - sw_miner.c: add missing #include esp_random.h - nucula_src: save_proofs() moved to public in wallet.hpp Nostr relay updates: - nostr_seed_relays: +relay.anzenkodo.workers.dev, +nostr.koning-degraaf.nl, +knostr.neutrine.com, +nostr.einundzwanzig.space (8/8 slots) - nostr_relays: +relay.anzenkodo.workers.dev, +nostr.koning-degraaf.nl (4/4 slots) Squash-merge of feature/mining-payment (5 commits: c75230e..9d98ba1)
Diffstat (limited to 'main/tollgate_api.c')
-rw-r--r--main/tollgate_api.c190
1 files changed, 189 insertions, 1 deletions
diff --git a/main/tollgate_api.c b/main/tollgate_api.c
index 21bf9ef..b775f55 100644
--- a/main/tollgate_api.c
+++ b/main/tollgate_api.c
@@ -7,6 +7,9 @@
7#include "nucula_wallet.h" 7#include "nucula_wallet.h"
8#include "mint_health.h" 8#include "mint_health.h"
9#include "market.h" 9#include "market.h"
10#include "mining_payment.h"
11#include "stratum_proxy.h"
12#include "stratum_client.h"
10#include "esp_log.h" 13#include "esp_log.h"
11#include "esp_system.h" 14#include "esp_system.h"
12#include "cJSON.h" 15#include "cJSON.h"
@@ -150,6 +153,18 @@ static esp_err_t api_get_discovery(httpd_req_t *req)
150 cJSON_AddItemToArray(tips_tag, cJSON_CreateString("5")); 153 cJSON_AddItemToArray(tips_tag, cJSON_CreateString("5"));
151 cJSON_AddItemToArray(tags, tips_tag); 154 cJSON_AddItemToArray(tags, tips_tag);
152 155
156 if (cfg->mining_enabled) {
157 cJSON *mining_tag = cJSON_CreateArray();
158 cJSON_AddItemToArray(mining_tag, cJSON_CreateString("price_per_step"));
159 cJSON_AddItemToArray(mining_tag, cJSON_CreateString("mining"));
160 char mining_port_str[16];
161 snprintf(mining_port_str, sizeof(mining_port_str), "%d", cfg->mining_port);
162 cJSON_AddItemToArray(mining_tag, cJSON_CreateString(mining_port_str));
163 cJSON_AddItemToArray(mining_tag, cJSON_CreateString("GH/s"));
164 cJSON_AddItemToArray(mining_tag, cJSON_CreateString("sv1"));
165 cJSON_AddItemToArray(tags, mining_tag);
166 }
167
153 cJSON_AddItemToObject(root, "tags", tags); 168 cJSON_AddItemToObject(root, "tags", tags);
154 cJSON_AddStringToObject(root, "content", ""); 169 cJSON_AddStringToObject(root, "content", "");
155 170
@@ -504,6 +519,169 @@ static esp_err_t api_get_mints(httpd_req_t *req)
504 return ESP_OK; 519 return ESP_OK;
505} 520}
506 521
522static esp_err_t api_get_mining_job(httpd_req_t *req)
523{
524 const stratum_job_t *job = stratum_proxy_get_current_job();
525 if (!job || !job->valid) {
526 httpd_resp_set_status(req, "503 Service Unavailable");
527 httpd_resp_set_type(req, "application/json");
528 httpd_resp_send(req, "{\"error\":\"no job\"}", 15);
529 return ESP_OK;
530 }
531
532 cJSON *root = cJSON_CreateObject();
533 cJSON_AddNumberToObject(root, "job_id", job->job_id);
534
535 char prevhash_hex[65];
536 for (int i = 0; i < 32; i++) snprintf(prevhash_hex + i * 2, 3, "%02x", job->prevhash[i]);
537 cJSON_AddStringToObject(root, "prevhash", prevhash_hex);
538
539 char merkle_hex[65];
540 for (int i = 0; i < 32; i++) snprintf(merkle_hex + i * 2, 3, "%02x", job->merkle_root[i]);
541 cJSON_AddStringToObject(root, "merkle_root", merkle_hex);
542
543 cJSON_AddNumberToObject(root, "version", job->version);
544 cJSON_AddNumberToObject(root, "nbits", job->nbits);
545 cJSON_AddNumberToObject(root, "ntime", job->ntime);
546 cJSON_AddNumberToObject(root, "hashprice", mining_get_current_hashprice());
547
548 char *json = cJSON_PrintUnformatted(root);
549 httpd_resp_set_type(req, "application/json");
550 httpd_resp_send(req, json, strlen(json));
551 cJSON_free(json);
552 cJSON_Delete(root);
553 return ESP_OK;
554}
555
556static esp_err_t api_post_mining_share(httpd_req_t *req)
557{
558 uint32_t client_ip = 0;
559 get_client_ip(req, &client_ip);
560
561 int content_len = req->content_len;
562 if (content_len <= 0 || content_len > 512) {
563 httpd_resp_set_status(req, "400 Bad Request");
564 httpd_resp_set_type(req, "application/json");
565 httpd_resp_send(req, "{\"error\":\"invalid body\"}", 21);
566 return ESP_OK;
567 }
568
569 char body[512];
570 int total = 0;
571 while (total < content_len) {
572 int r = httpd_req_recv(req, body + total, content_len - total);
573 if (r <= 0) {
574 httpd_resp_set_status(req, "400 Bad Request");
575 httpd_resp_set_type(req, "text/plain");
576 httpd_resp_send(req, "bad request", 11);
577 return ESP_OK;
578 }
579 total += r;
580 }
581 body[total] = '\0';
582
583 cJSON *root = cJSON_Parse(body);
584 if (!root) {
585 httpd_resp_set_status(req, "400 Bad Request");
586 httpd_resp_set_type(req, "application/json");
587 httpd_resp_send(req, "{\"error\":\"invalid json\"}", 21);
588 return ESP_OK;
589 }
590
591 cJSON *j_job_id = cJSON_GetObjectItem(root, "job_id");
592 cJSON *j_nonce = cJSON_GetObjectItem(root, "nonce");
593 cJSON *j_ntime = cJSON_GetObjectItem(root, "ntime");
594 cJSON *j_version = cJSON_GetObjectItem(root, "version");
595 if (!j_job_id || !j_nonce || !j_ntime || !j_version) {
596 cJSON_Delete(root);
597 httpd_resp_set_status(req, "400 Bad Request");
598 httpd_resp_set_type(req, "application/json");
599 httpd_resp_send(req, "{\"error\":\"missing fields\"}", 22);
600 return ESP_OK;
601 }
602
603 uint32_t job_id = (uint32_t)j_job_id->valuedouble;
604 uint32_t nonce = (uint32_t)j_nonce->valuedouble;
605 uint32_t ntime = (uint32_t)j_ntime->valuedouble;
606 uint32_t version = (uint32_t)j_version->valuedouble;
607 cJSON_Delete(root);
608
609 const stratum_job_t *job = stratum_proxy_get_current_job();
610 if (!job || !job->valid || job->job_id != job_id) {
611 httpd_resp_set_status(req, "400 Bad Request");
612 httpd_resp_set_type(req, "application/json");
613 httpd_resp_send(req, "{\"error\":\"stale job\"}", 19);
614 return ESP_OK;
615 }
616
617 esp_err_t share_err = stratum_client_submit_share(job_id, nonce, ntime, version);
618 bool accepted = (share_err == ESP_OK);
619
620 mining_update_hashrate(client_ip, accepted);
621 mining_client_stats_t *stats = mining_get_or_create_client(client_ip);
622
623 if (accepted) {
624 const tollgate_config_t *cfg = tollgate_config_get();
625 double hashprice = mining_get_current_hashprice();
626 uint64_t allotment_ms = mining_shares_to_allotment_ms(
627 stats->hashrate_ghs, hashprice, cfg->price_per_step, cfg->step_size_ms);
628
629 session_t *session = session_find_by_ip(client_ip);
630 if (!session || !session->active || session->payment_method != PAYMENT_METHOD_MINING) {
631 session = session_create(client_ip, allotment_ms);
632 if (session) session->payment_method = PAYMENT_METHOD_MINING;
633 } else {
634 session_extend(session, allotment_ms);
635 }
636 }
637
638 cJSON *resp = cJSON_CreateObject();
639 cJSON_AddBoolToObject(resp, "accepted", accepted);
640 cJSON_AddNumberToObject(resp, "hashrate_ghs", stats ? stats->hashrate_ghs : 0.0);
641 char *json = cJSON_PrintUnformatted(resp);
642 httpd_resp_set_type(req, "application/json");
643 httpd_resp_send(req, json, strlen(json));
644 cJSON_free(json);
645 cJSON_Delete(resp);
646 return ESP_OK;
647}
648
649static esp_err_t api_get_mining_stats(httpd_req_t *req)
650{
651 stratum_proxy_stats_t proxy_stats;
652 stratum_proxy_get_stats(&proxy_stats);
653
654 const stratum_client_state_t *client_state = stratum_client_get_state();
655
656 cJSON *root = cJSON_CreateObject();
657
658 cJSON *proxy = cJSON_CreateObject();
659 cJSON_AddNumberToObject(proxy, "hashrate_ghs", proxy_stats.hashrate_ghs);
660 cJSON_AddNumberToObject(proxy, "total_shares", (double)proxy_stats.total_shares);
661 cJSON_AddNumberToObject(proxy, "total_accepted", (double)proxy_stats.total_accepted);
662 cJSON_AddNumberToObject(proxy, "total_rejected", (double)proxy_stats.total_rejected);
663 cJSON_AddNumberToObject(proxy, "hashprice", proxy_stats.current_hashprice);
664 cJSON_AddNumberToObject(proxy, "active_miners", proxy_stats.active_miners);
665 cJSON_AddItemToObject(root, "proxy", proxy);
666
667 cJSON *upstream = cJSON_CreateObject();
668 cJSON_AddBoolToObject(upstream, "connected", client_state->connected);
669 cJSON_AddStringToObject(upstream, "pool_host", client_state->pool_host);
670 cJSON_AddNumberToObject(upstream, "pool_port", client_state->pool_port);
671 cJSON_AddNumberToObject(upstream, "difficulty", (double)client_state->difficulty);
672 cJSON_AddNumberToObject(upstream, "shares_accepted", (double)client_state->shares_accepted);
673 cJSON_AddNumberToObject(upstream, "shares_rejected", (double)client_state->shares_rejected);
674 cJSON_AddItemToObject(root, "upstream", upstream);
675
676 char *json = cJSON_PrintUnformatted(root);
677 httpd_resp_set_type(req, "application/json");
678 httpd_resp_send(req, json, strlen(json));
679 cJSON_free(json);
680 cJSON_Delete(root);
681>>>>>>> feature/mining-payment
682 return ESP_OK;
683}
684
507static const httpd_uri_t uri_discovery = { .uri = "/", .method = HTTP_GET, .handler = api_get_discovery }; 685static const httpd_uri_t uri_discovery = { .uri = "/", .method = HTTP_GET, .handler = api_get_discovery };
508static const httpd_uri_t uri_payment = { .uri = "/", .method = HTTP_POST, .handler = api_post_payment }; 686static const httpd_uri_t uri_payment = { .uri = "/", .method = HTTP_POST, .handler = api_post_payment };
509static const httpd_uri_t uri_mints = { .uri = "/mints", .method = HTTP_GET, .handler = api_get_mints }; 687static const httpd_uri_t uri_mints = { .uri = "/mints", .method = HTTP_GET, .handler = api_get_mints };
@@ -512,6 +690,9 @@ static const httpd_uri_t uri_whoami = { .uri = "/whoami", .method = HTTP_GET, .h
512static const httpd_uri_t uri_wallet = { .uri = "/wallet", .method = HTTP_GET, .handler = api_get_wallet }; 690static const httpd_uri_t uri_wallet = { .uri = "/wallet", .method = HTTP_GET, .handler = api_get_wallet };
513static const httpd_uri_t uri_wallet_swap = { .uri = "/wallet/swap", .method = HTTP_POST, .handler = api_post_wallet_swap }; 691static const httpd_uri_t uri_wallet_swap = { .uri = "/wallet/swap", .method = HTTP_POST, .handler = api_post_wallet_swap };
514static const httpd_uri_t uri_wallet_send = { .uri = "/wallet/send", .method = HTTP_POST, .handler = api_post_wallet_send }; 692static const httpd_uri_t uri_wallet_send = { .uri = "/wallet/send", .method = HTTP_POST, .handler = api_post_wallet_send };
693static const httpd_uri_t uri_mining_job = { .uri = "/mining/job", .method = HTTP_GET, .handler = api_get_mining_job };
694static const httpd_uri_t uri_mining_share = { .uri = "/mining/share", .method = HTTP_POST, .handler = api_post_mining_share };
695static const httpd_uri_t uri_mining_stats = { .uri = "/mining/stats", .method = HTTP_GET, .handler = api_get_mining_stats };
515 696
516static esp_err_t api_get_market(httpd_req_t *req) 697static esp_err_t api_get_market(httpd_req_t *req)
517{ 698{
@@ -559,7 +740,7 @@ esp_err_t tollgate_api_start(void)
559 httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 740 httpd_config_t config = HTTPD_DEFAULT_CONFIG();
560 config.server_port = 2121; 741 config.server_port = 2121;
561 config.ctrl_port = 32769; 742 config.ctrl_port = 32769;
562 config.max_uri_handlers = 12; 743 config.max_uri_handlers = 16;
563 config.stack_size = 16384; 744 config.stack_size = 16384;
564 745
565 esp_err_t ret = httpd_start(&s_api_server, &config); 746 esp_err_t ret = httpd_start(&s_api_server, &config);
@@ -579,6 +760,13 @@ esp_err_t tollgate_api_start(void)
579 httpd_register_uri_handler(s_api_server, &uri_wallet_send); 760 httpd_register_uri_handler(s_api_server, &uri_wallet_send);
580 httpd_register_uri_handler(s_api_server, &uri_market); 761 httpd_register_uri_handler(s_api_server, &uri_market);
581 762
763 const tollgate_config_t *cfg = tollgate_config_get();
764 if (cfg->mining_enabled) {
765 httpd_register_uri_handler(s_api_server, &uri_mining_job);
766 httpd_register_uri_handler(s_api_server, &uri_mining_share);
767 httpd_register_uri_handler(s_api_server, &uri_mining_stats);
768 }
769
582 ESP_LOGI(TAG, "TollGate API started on port 2121"); 770 ESP_LOGI(TAG, "TollGate API started on port 2121");
583 return ESP_OK; 771 return ESP_OK;
584} 772}