From e366ceb336550a72c76efea4c98a2a08cca27bce Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 19 May 2026 14:25:18 +0530 Subject: 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) --- main/stratum_proxy.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 main/stratum_proxy.c (limited to 'main/stratum_proxy.c') diff --git a/main/stratum_proxy.c b/main/stratum_proxy.c new file mode 100644 index 0000000..288c633 --- /dev/null +++ b/main/stratum_proxy.c @@ -0,0 +1,160 @@ +#include "stratum_proxy.h" +#include "mining_payment.h" +#include "esp_log.h" +#include "lwip/sockets.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include + +static const char *TAG = "stratum_proxy"; +static uint16_t s_port = 3333; +static bool s_running = false; +static TaskHandle_t s_task_handle = NULL; +static int s_server_fd = -1; + +static stratum_job_t s_current_job = {0}; +static stratum_proxy_stats_t s_stats = {0}; + +static void proxy_client_handler(void *arg) +{ + int client_fd = (int)(intptr_t)arg; + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + getpeername(client_fd, (struct sockaddr *)&client_addr, &addr_len); + uint32_t client_ip = client_addr.sin_addr.s_addr; + + ESP_LOGI(TAG, "Miner connected from 0x%08lx", (unsigned long)client_ip); + + if (s_current_job.valid) { + char job_json[512]; + snprintf(job_json, sizeof(job_json), + "{\"id\":1,\"method\":\"mining.notify\",\"params\":[\"%lu\",\"%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx\",\"\",\"\",\"\",\"%08lx\",\"%08lx\",\"%08lx\",true]}\n", + (unsigned long)s_current_job.job_id, + (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0, + (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0, + (unsigned long)s_current_job.nbits, (unsigned long)s_current_job.ntime, + (unsigned long)s_current_job.version); + send(client_fd, job_json, strlen(job_json), 0); + } + + char buf[1024]; + while (s_running) { + int len = recv(client_fd, buf, sizeof(buf) - 1, 0); + if (len <= 0) break; + buf[len] = '\0'; + + ESP_LOGI(TAG, "Received from miner: %s", buf); + s_stats.total_shares++; + s_stats.total_accepted++; + } + + ESP_LOGI(TAG, "Miner disconnected from 0x%08lx", (unsigned long)client_ip); + close(client_fd); + vTaskDelete(NULL); +} + +static void proxy_server_task(void *arg) +{ + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(s_port); + + s_server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (s_server_fd < 0) { + ESP_LOGE(TAG, "Failed to create socket"); + vTaskDelete(NULL); + return; + } + + int opt = 1; + setsockopt(s_server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + if (bind(s_server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) { + ESP_LOGE(TAG, "Failed to bind to port %u", (unsigned)s_port); + close(s_server_fd); + s_server_fd = -1; + vTaskDelete(NULL); + return; + } + + if (listen(s_server_fd, 5) != 0) { + ESP_LOGE(TAG, "Failed to listen"); + close(s_server_fd); + s_server_fd = -1; + vTaskDelete(NULL); + return; + } + + ESP_LOGI(TAG, "Stratum proxy listening on port %u", (unsigned)s_port); + + while (s_running) { + struct sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + int client_fd = accept(s_server_fd, (struct sockaddr *)&client_addr, &client_len); + if (client_fd < 0) continue; + + s_stats.active_miners++; + char task_name[20]; + snprintf(task_name, sizeof(task_name), "miner_%d", client_fd); + xTaskCreate(proxy_client_handler, task_name, 4096, (void *)(intptr_t)client_fd, 3, NULL); + } + + close(s_server_fd); + s_server_fd = -1; + vTaskDelete(NULL); +} + +esp_err_t stratum_proxy_init(uint16_t port) +{ + s_port = port; + memset(&s_current_job, 0, sizeof(s_current_job)); + memset(&s_stats, 0, sizeof(s_stats)); + s_running = true; + + BaseType_t ret = xTaskCreate(proxy_server_task, "stratum_proxy", 4096, NULL, 4, &s_task_handle); + if (ret != pdPASS) { + ESP_LOGE(TAG, "Failed to create proxy task"); + s_running = false; + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Stratum proxy initialized on port %u", (unsigned)port); + return ESP_OK; +} + +void stratum_proxy_set_job(const stratum_job_t *job) +{ + if (job) { + memcpy(&s_current_job, job, sizeof(stratum_job_t)); + s_stats.nbits = job->nbits; + s_stats.current_hashprice = mining_get_current_hashprice(); + } +} + +const stratum_job_t *stratum_proxy_get_current_job(void) +{ + return &s_current_job; +} + +void stratum_proxy_get_stats(stratum_proxy_stats_t *stats) +{ + if (stats) { + *stats = s_stats; + stats->current_hashprice = mining_get_current_hashprice(); + } +} + +void stratum_proxy_stop(void) +{ + s_running = false; + if (s_server_fd >= 0) { + close(s_server_fd); + s_server_fd = -1; + } + if (s_task_handle) { + vTaskDelay(pdMS_TO_TICKS(500)); + s_task_handle = NULL; + } +} -- cgit v1.2.3